This avoids the risk of file name collisions between Emacs packages installed to a profile. One example of such a clash is the 'snippets' directory installed by both the emacs-elpy and emacs-yasnippet-snippets packages. * guix/build/emacs-build-system.scm (%install-prefix): Append '/guix' to the existing %install-dir prefix. (version->elpa-version): New procedure. (build, patch-el-files, install, move-doc, make-autoloads): Adjust phases. (create-pkg.el): New phase. (outputs->elpa-install-dir): New procedure. (%standard-phases): Register the new 'create-pkg.el phase. * guix/build/emacs-utils.scm (%emacs-quick-arguments): New variable. (emacs-batch-eval): Remove the "--quick" option, and use %emacs-quick-arguments instead. (emacs-batch-edit-file): Likewise. * gnu/packages/aux-files/emacs/guix-emacs.el: Update various comments. (guix-emacs-package-initialize-ran-p): New variable. (guix-emacs-package-initialize): New procedure, to replace guix-emacs-autoload-packages. Use 'package-initialize' from the 'package' library of Emacs to do the initialization (load path configuration and autoloads loading) of the Emacs packages. Use the same autoload invocation as 'package-initialize', to harmonize the output. (guix-package-initialize): New alias. (guix-emacs-autoload-packages): New deprecated alias. * gnu/packages/emacs.scm (emacs)[phases]{install-site-start}: Adjust for the auxiliary file name change and new procedure name. Streamline the require directive: the 'guix-emacs library is shipped with our Emacs package; it cannot be missing. Fix typo in a comment. Do not use the "--quick" option when byte compiling. * doc/guix.texi: Update doc. --- doc/contributing.texi | 7 +- doc/guix.texi | 37 ++++--- gnu/packages/aux-files/emacs/guix-emacs.el | 79 +++++++++++---- gnu/packages/emacs.scm | 13 ++- guix/build/emacs-build-system.scm | 109 +++++++++++++++------ guix/build/emacs-utils.scm | 20 ++-- 6 files changed, 186 insertions(+), 79 deletions(-) diff --git a/doc/contributing.texi b/doc/contributing.texi index 4195cb4105..1081ddc6d6 100644 --- a/doc/contributing.texi +++ b/doc/contributing.texi @@ -652,7 +652,12 @@ enabled by setting the @code{#:tests?} argument to @code{#true}. By default, the command to run the test is @command{make check}, but any command can be specified via the @code{#:test-command} argument. The @code{#:test-command} argument expects a list containing a command and -its arguments, to be invoked during the @code{check} phase. +its arguments, to be invoked during the @code{check} phase. The Elisp +libraries discovery mechanism used in Guix relies on the site file being +run. For this reason, scripts or Makefile recipes must not invoke Emacs +with the @option{--no-site-file}, @option{--quick} or @option{-Q} +options as this would cause the Elisp dependencies added as inputs to +appear unavailable. The Elisp dependencies of Emacs packages are typically provided as @code{propagated-inputs} when required at run time. As for other diff --git a/doc/guix.texi b/doc/guix.texi index 392baf5910..74713b1331 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -1887,19 +1887,27 @@ information. @subsection Emacs Packages @cindex @code{emacs} -When you install Emacs packages with Guix, the Elisp files are placed -under the @file{share/emacs/site-lisp/} directory of the profile in -which they are installed. The Elisp libraries are made available to -Emacs through the @env{EMACSLOADPATH} environment variable, which is -set when installing Emacs itself. - -Additionally, autoload definitions are automatically evaluated at the -initialization of Emacs, by the Guix-specific -@code{guix-emacs-autoload-packages} procedure. If, for some reason, you -want to avoid auto-loading the Emacs packages installed with Guix, you -can do so by running Emacs with the @option{--no-site-file} option -(@pxref{Init File,,, emacs, The GNU Emacs Manual}). - +When you install Emacs packages with Guix, the Elisp files are placed in +their own directory under the @file{share/emacs/site-lisp/guix} +installation prefix of the profile in which they are installed. The +Elisp libraries are made available to Emacs through the +@env{EMACSLOADPATH} environment variable, which is set when installing +Emacs itself. + +@cindex package activation, emacs +@cindex guix-package-initialize +Additionally, the packages are ``activated'', that is, their autoload +definitions are evaluated and their specific installation directory +added to the load path at the initialization of Emacs, by the +Guix-specific @code{guix-package-initialize} procedure. This procedure +can also be called interactively in Emacs to refresh the list of +available packages, for example, after installing new Emacs packages to +the user profile. + +If, for some reason, you want to avoid auto-loading the Emacs packages +installed with Guix, you can do so by running Emacs with the +@option{--no-site-file} option (@pxref{Init File,,, emacs, The GNU Emacs +Manual}). @node Upgrading Guix @section Upgrading Guix @@ -7940,7 +7948,8 @@ It first creates the @code{@code{package}-autoloads.el} file, then it byte compiles all Emacs Lisp files. Differently from the Emacs packaging system, the Info documentation files are moved to the standard documentation directory and the @file{dir} file is deleted. The Elisp -package files are installed directly under @file{share/emacs/site-lisp}. +package files are installed in their own directory under the +@file{share/site-lisp/guix} installation prefix. @end defvr @defvr {Scheme Variable} font-build-system diff --git a/gnu/packages/aux-files/emacs/guix-emacs.el b/gnu/packages/aux-files/emacs/guix-emacs.el index ca9146c535..a3c0eaa6fc 100644 --- a/gnu/packages/aux-files/emacs/guix-emacs.el +++ b/gnu/packages/aux-files/emacs/guix-emacs.el @@ -1,8 +1,8 @@ -;;; guix-emacs.el --- Emacs packages installed with Guix +;;; guix-emacs.el --- Autoload Emacs packages installed with Guix ;; Copyright © 2014, 2015, 2016, 2017 Alex Kost ;; Copyright © 2017 Kyle Meyer -;; Copyright © 2019 Maxim Cournoyer +;; Copyright © 2019, 2020 Maxim Cournoyer ;; This file is part of GNU Guix. @@ -21,43 +21,84 @@ ;;; Commentary: -;; This file provides auxiliary code to autoload Emacs packages +;; This file provides auxiliary code to initialize the Emacs packages ;; installed with Guix. ;;; Code: +(require 'package) (require 'seq) (defvar guix-emacs-autoloads-regexp (rx (* any) "-autoloads.el" (zero-or-one "c") string-end) - "Regexp to match Emacs 'autoloads' file.") + "Regexp to match 'autoloads' file.") (defun guix-emacs-find-autoloads (directory) - "Return a list of Emacs 'autoloads' files in DIRECTORY. + "Return a list of 'autoloads' files in DIRECTORY. The files in the list do not have extensions (.el, .elc)." ;; `directory-files' doesn't honor group in regexp. (delete-dups (mapcar #'file-name-sans-extension (directory-files directory 'full-name guix-emacs-autoloads-regexp)))) +(defvar guix-emacs-package-initialize-ran-p nil + "Non-nil if `guix-emacs-package-initialize' has run.") + ;;;###autoload -(defun guix-emacs-autoload-packages () - "Autoload Emacs packages found in EMACSLOADPATH. +(defun guix-emacs-package-initialize () + "Initialize the Emacs packages. -'Autoload' means to load the 'autoloads' files matching -`guix-emacs-autoloads-regexp'." +Load the autoloads files of the packages, and add their directory +to the Emacs load path if they aren't already." (interactive) - (let* ((emacs-non-core-load-path-directories - ;; Filter out core Elisp directories, which are already autoloaded - ;; by Emacs. - (seq-filter (lambda (dir) - (string-match-p "/share/emacs/site-lisp" dir)) - load-path)) - (autoloads (mapcan #'guix-emacs-find-autoloads - emacs-non-core-load-path-directories))) + (let* (;; Filter out core Elisp directories, which have already been + ;; autoloaded by Emacs. + (load-path* (seq-filter (lambda (f) + (string-match-p "/share/emacs/site-lisp" f)) + load-path)) + (autoloads (mapcan #'guix-emacs-find-autoloads load-path*)) + (package-directory-list* (mapcar (lambda (f) + (expand-file-name "guix" f)) + load-path*)) + (user-emacs-directory-warning nil) ;squelch extraneous warnings + (package-user-dir "")) ;do not activate package.el user packages + + ;; Autoload packages installed without using 'emacs-build-system'; these + ;; are found directly under the site-lisp directory (flat hierarchy). (mapc (lambda (f) - (load f 'noerror)) - autoloads))) + (with-demoted-errors "Error loading autoloads: %s" + (load f nil t))) + autoloads) + + ;; Set the package-directory-list variable to a meaningful default value + ;; on Guix, which for 'package.el' is akin to system packages. It's + ;; important that this variable contains the correct value to see the + ;; Guix packages listed (as 'external') in M-x package-list. + (setq-default package-directory-list package-directory-list*) + + ;; Initialize the Guix-installed Emacs packages. + (package-initialize) + + (unless guix-emacs-package-initialize-ran-p + ;; If this is running the first time, hide the fact that + ;; `'package-initialization' has run in order to leave the default + ;; behavior of package.el unchanged for its users (that is, to run + ;; automatically after the early-init.el file but before the user init + ;; file -- this can be useful if they configured their `'package-user-dir' + ;; variable). + (setq package--initialized nil) + (setq package--activated nil)) + + (setq guix-emacs-package-initialize-ran-p t))) + +;;;###autoload +(defalias 'guix-package-initialize #'guix-emacs-package-initialize) + +;;;###autoload +(define-obsolete-function-alias + 'guix-emacs-autoload-packages 'guix-package-initialize "20201218") +;;; The symbol name is suffixed by -emacs to avoid a name clash with +;;; Emacs-Guix's guix.el. (provide 'guix-emacs) ;;; guix-emacs.el ends here diff --git a/gnu/packages/emacs.scm b/gnu/packages/emacs.scm index ca14584ada..98f886278a 100644 --- a/gnu/packages/emacs.scm +++ b/gnu/packages/emacs.scm @@ -145,8 +145,8 @@ "pwd")) #t)) (add-after 'install 'install-site-start - ;; Use 'guix-emacs' in "site-start.el", which is used autoload the - ;; Elisp packages found in EMACSLOADPATH. + ;; Use 'guix-emacs' in "site-start.el", which is used to activate + ;; the Elisp packages found in EMACSLOADPATH. (lambda* (#:key inputs outputs #:allow-other-keys) (let* ((out (assoc-ref outputs "out")) (lisp-dir (string-append out "/share/emacs/site-lisp")) @@ -158,18 +158,17 @@ (setq byte-compile-debug t) (byte-recompile-directory (file-name-as-directory ,dir) 0 1)))) - (invoke emacs "--quick" "--batch" - (format #f "--eval=~s" expr)))) + (invoke emacs "--batch" (format #f "--eval=~s" expr)))) (copy-file (assoc-ref inputs "guix-emacs.el") (string-append lisp-dir "/guix-emacs.el")) (with-output-to-file (string-append lisp-dir "/site-start.el") (lambda () (display - (string-append "(when (require 'guix-emacs nil t)\n" - " (guix-emacs-autoload-packages))\n")))) + (string-append "(require 'guix-emacs)\n" + "(guix-package-initialize)\n")))) ;; Remove the extraneous subdirs.el file, as it causes Emacs to - ;; add recursively all the the sub-directories of a profile's + ;; add recursively all the sub-directories of a profile's ;; share/emacs/site-lisp union when added to EMACSLOADPATH, ;; which leads to conflicts. (delete-file (string-append lisp-dir "/subdirs.el")) diff --git a/guix/build/emacs-build-system.scm b/guix/build/emacs-build-system.scm index 26ea59bc25..2846926298 100644 --- a/guix/build/emacs-build-system.scm +++ b/guix/build/emacs-build-system.scm @@ -2,7 +2,7 @@ ;;; Copyright © 2015 Federico Beffa ;;; Copyright © 2016 David Thompson ;;; Copyright © 2016 Alex Kost -;;; Copyright © 2018, 2019 Maxim Cournoyer +;;; Copyright © 2018, 2019, 2020 Maxim Cournoyer ;;; ;;; This file is part of GNU Guix. ;;; @@ -32,7 +32,8 @@ #:export (%standard-phases %default-include %default-exclude - emacs-build)) + emacs-build + outputs->elpa-install-dir)) ;; Commentary: ;; @@ -40,10 +41,13 @@ ;; ;; Code: -;;; All the packages are installed directly under site-lisp, which means that -;;; having that directory in the EMACSLOADPATH is enough to have them found by -;;; Emacs. -(define %install-dir "/share/emacs/site-lisp") +;;; All the packages are installed under their own directory under +;;; site-lisp/guix, to prevent file name collisions in profiles. This means +;;; that some machinery is needed to activate the packages, since they aren't +;;; directly on the load path. This is taken care of by the +;;; 'guix-package-initialize' procedure called from the site-start.el file +;;; shipped with Emacs from Guix. +(define %install-prefix "/share/emacs/site-lisp/guix") ;; These are the default inclusion/exclusion regexps for the install phase. (define %default-include '("^[^/]*\\.el$" "^[^/]*\\.info$" "^doc/.*\\.info$")) @@ -62,6 +66,26 @@ name that has been stripped of the hash and version number." (strip-store-file-name file) suffix)))) (string-append name suffix)))) +(define (version->elpa-version version) + "Sanitize VERSION into an ELPA-compatible version. + +package.el uses the 'version-to-list' procedure from the 'subr' Emacs library, +which supports a very limited set of version snapshot annotations, defined in +the 'version-regexp-alist' variable of the same library. + +Strip the least significant components of a version string, so that a Guix +version such as \"0.4.1-1.a41d5cc\" becomes the valid ELPA version \"0.4.1\". +In case a valid ELPA version cannot be derived from VERSION, \"0.0.0\" is +returned." + (let ((m (string-match "[0-9]+(\\.[0-9]+)*" version))) + (if m + (match:substring m) + (begin + (format (current-error-port) + "warning: failed producing an ELPA-compatible \ +version string from ~s; using \"0.0.0\"~%" version) + "0.0.0")))) + (define* (unpack #:key source #:allow-other-keys) "Unpack SOURCE into the build directory. SOURCE may be a compressed archive, a directory, or an Emacs Lisp file." @@ -93,13 +117,12 @@ archive, a directory, or an Emacs Lisp file." environment variable\n" source-directory))) (define* (build #:key outputs inputs #:allow-other-keys) - "Compile .el files." - (let* ((emacs (string-append (assoc-ref inputs "emacs") "/bin/emacs")) - (out (assoc-ref outputs "out")) - (site-lisp (string-append out %install-dir))) + "Byte compile .el files." + (let ((emacs (string-append (assoc-ref inputs "emacs") "/bin/emacs")) + (install-dir (outputs->elpa-install-dir outputs))) (setenv "SHELL" "sh") (parameterize ((%emacs emacs)) - (emacs-byte-compile-directory site-lisp)))) + (emacs-byte-compile-directory install-dir)))) (define* (patch-el-files #:key outputs #:allow-other-keys) "Substitute the absolute \"/bin/\" directory with the right location in the @@ -115,13 +138,12 @@ store in '.el' files." (else (loop (read-line in 'concat)))))) #:binary #t)) - (let* ((out (assoc-ref outputs "out")) - (site-lisp (string-append out %install-dir)) - ;; (ice-9 regex) uses libc's regexp routines, which cannot deal with - ;; strings containing NULs. Filter out such files. TODO: Remove - ;; this workaround when is fixed. - (el-files (remove file-contains-nul-char? - (find-files (getcwd) "\\.el$")))) + (let ((install-dir (outputs->elpa-install-dir outputs)) + ;; (ice-9 regex) uses libc's regexp routines, which cannot deal with + ;; strings containing NULs. Filter out such files. TODO: Remove + ;; this workaround when is fixed. + (el-files (remove file-contains-nul-char? + (find-files (getcwd) "\\.el$")))) (define (substitute-program-names) (substitute* el-files (("\"/bin/([^.]\\S*)\"" _ cmd-name) @@ -130,7 +152,7 @@ store in '.el' files." (error "patch-el-files: unable to locate " cmd-name)) (string-append "\"" cmd "\""))))) - (with-directory-excursion site-lisp + (with-directory-excursion install-dir ;; Some old '.el' files (e.g., tex-buf.el in AUCTeX) are still ;; ISO-8859-1-encoded. (unless (false-if-exception (substitute-program-names)) @@ -180,15 +202,14 @@ parallel. PARALLEL-TESTS? is ignored when using a non-make TEST-COMMAND." (and (any (cut match-stripped-file "included" <>) include) (not (any (cut match-stripped-file "excluded" <>) exclude))))) - (let* ((out (assoc-ref outputs "out")) - (site-lisp (string-append out %install-dir)) + (let ((install-dir (outputs->elpa-install-dir outputs)) (files-to-install (find-files source install-file?))) (cond ((not (null? files-to-install)) (for-each (lambda (file) (let* ((stripped-file (string-drop file (string-length source))) - (target-file (string-append site-lisp stripped-file))) + (target-file (string-append install-dir stripped-file))) (format #t "`~a' -> `~a'~%" file target-file) (install-file file (dirname target-file)))) files-to-install) @@ -199,15 +220,31 @@ parallel. PARALLEL-TESTS? is ignored when using a non-make TEST-COMMAND." (install-file? file stat #:verbose? #t))) #f)))) +(define* (create-pkg.el #:key outputs #:allow-other-keys) + "Generate the name-pkg.el file required by package.el." + (let*-values (((install-dir) (outputs->elpa-install-dir outputs)) + ((elpa-name-ver) (store-directory->elpa-name-version + (assoc-ref outputs "out"))) + ((name version) (package-name->name+version elpa-name-ver)) + ((elpa-version) (version->elpa-version version))) + (call-with-output-file (string-append install-dir "/" name "-pkg.el") + (lambda (port) + ;; Note: the file cannot be byte compiled, as the loader expects a + ;; single form present, which leaves no option to include (require + ;; 'package). + (display ";; Generated by Guix -*- no-byte-compile: t -*-\n" port) + (write `(define-package ,name ,elpa-version) port) + (display "\n" port) + #t)))) + (define* (move-doc #:key outputs #:allow-other-keys) "Move info files from the ELPA package directory to the info directory." - (let* ((out (assoc-ref outputs "out")) - (site-lisp (string-append out %install-dir)) - (info-dir (string-append out "/share/info/")) - (info-files (find-files site-lisp "\\.info$"))) + (let* ((install-dir (outputs->elpa-install-dir outputs)) + (info-dir (string-append (assoc-ref outputs "out") "/share/info/")) + (info-files (find-files install-dir "\\.info$"))) (unless (null? info-files) (mkdir-p info-dir) - (with-directory-excursion site-lisp + (with-directory-excursion install-dir (when (file-exists? "dir") (delete-file "dir")) (for-each (lambda (f) (copy-file f (string-append info-dir "/" (basename f))) @@ -218,12 +255,12 @@ parallel. PARALLEL-TESTS? is ignored when using a non-make TEST-COMMAND." (define* (make-autoloads #:key outputs inputs #:allow-other-keys) "Generate the autoloads file." (let* ((emacs (string-append (assoc-ref inputs "emacs") "/bin/emacs")) - (out (assoc-ref outputs "out")) - (site-lisp (string-append out %install-dir)) - (elpa-name-ver (store-directory->elpa-name-version out)) + (install-dir (outputs->elpa-install-dir outputs)) + (elpa-name-ver (store-directory->elpa-name-version + (assoc-ref outputs "out"))) (elpa-name (package-name->name+version elpa-name-ver))) (parameterize ((%emacs emacs)) - (emacs-generate-autoloads elpa-name site-lisp)))) + (emacs-generate-autoloads elpa-name install-dir)))) (define* (enable-autoloads-compilation #:key outputs #:allow-other-keys) "Remove the NO-BYTE-COMPILATION local variable embedded in the generated @@ -258,6 +295,13 @@ second hyphen. This corresponds to 'name-version' as used in ELPA packages." strip-store-file-name) store-dir)) +(define (outputs->elpa-install-dir outputs) + "Given OUTPUTS, a package outputs, return the ELPA-like Guix installation +directory of the Emacs package." + (let ((out (assoc-ref outputs "out"))) + (string-append out %install-prefix "/" + (store-directory->elpa-name-version out)))) + (define %standard-phases (modify-phases gnu:%standard-phases (replace 'unpack unpack) @@ -267,7 +311,8 @@ second hyphen. This corresponds to 'name-version' as used in ELPA packages." (delete 'build) (replace 'check check) (replace 'install install) - (add-after 'install 'make-autoloads make-autoloads) + (add-after 'install 'create-pkg.el create-pkg.el) + (add-after 'create-pkg.el 'make-autoloads make-autoloads) (add-after 'make-autoloads 'enable-autoloads-compilation enable-autoloads-compilation) (add-after 'enable-autoloads-compilation 'patch-el-files patch-el-files) diff --git a/guix/build/emacs-utils.scm b/guix/build/emacs-utils.scm index 5f7ba71244..485218d040 100644 --- a/guix/build/emacs-utils.scm +++ b/guix/build/emacs-utils.scm @@ -23,6 +23,7 @@ #:use-module (guix build utils) #:use-module (ice-9 format) #:export (%emacs + %emacs-quick-arguments emacs-batch-eval emacs-batch-edit-file emacs-batch-disable-compilation @@ -42,6 +43,11 @@ ;; The `emacs' command. (make-parameter "emacs")) +(define %emacs-quick-arguments + ;; Like --quick (-Q), except the site-start.el file is loaded since it is + ;; required to load packages. + '("--no-init-file" "--no-site-lisp" "--no-splash" "--no-x-resources")) + (define (expr->string expr) "Converts EXPR, an expression, into a string." (if (string? expr) @@ -51,15 +57,17 @@ (define* (emacs-batch-eval expr #:key dynamic?) "Run Emacs in batch mode, and execute the Elisp code EXPR. If DYNAMIC? is true, evaluate using dynamic scoping." - (invoke (%emacs) "--quick" "--batch" - (format #f "--eval=(eval '~a ~:[t~;nil~])" - (expr->string expr) dynamic?))) + (apply invoke (%emacs) "--batch" + (format #f "--eval=(eval '~a ~:[t~;nil~])" + (expr->string expr) dynamic?) + %emacs-quick-arguments)) (define (emacs-batch-edit-file file expr) "Load FILE in Emacs using batch mode, and execute the elisp code EXPR." - (invoke (%emacs) "--quick" "--batch" - (string-append "--visit=" file) - (string-append "--eval=" (expr->string expr)))) + (apply invoke (%emacs) "--batch" + (string-append "--visit=" file) + (string-append "--eval=" (expr->string expr)) + %emacs-quick-arguments)) (define (emacs-batch-disable-compilation file) (emacs-batch-edit-file file -- 2.29.2