Merge branch 'master' into core-updates
[jackhill/guix/guix.git] / guix / build / emacs-build-system.scm
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2015 Federico Beffa <beffa@fbengineering.ch>
3 ;;; Copyright © 2016 David Thompson <davet@gnu.org>
4 ;;; Copyright © 2016 Alex Kost <alezost@gmail.com>
5 ;;; Copyright © 2018 Maxim Cournoyer <maxim.cournoyer@gmail.com>
6 ;;;
7 ;;; This file is part of GNU Guix.
8 ;;;
9 ;;; GNU Guix is free software; you can redistribute it and/or modify it
10 ;;; under the terms of the GNU General Public License as published by
11 ;;; the Free Software Foundation; either version 3 of the License, or (at
12 ;;; your option) any later version.
13 ;;;
14 ;;; GNU Guix is distributed in the hope that it will be useful, but
15 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;;; GNU General Public License for more details.
18 ;;;
19 ;;; You should have received a copy of the GNU General Public License
20 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
21
22 (define-module (guix build emacs-build-system)
23 #:use-module ((guix build gnu-build-system) #:prefix gnu:)
24 #:use-module (guix build utils)
25 #:use-module (guix build emacs-utils)
26 #:use-module (srfi srfi-1)
27 #:use-module (srfi srfi-11)
28 #:use-module (srfi srfi-26)
29 #:use-module (ice-9 rdelim)
30 #:use-module (ice-9 regex)
31 #:use-module (ice-9 match)
32 #:export (%standard-phases
33 %default-include
34 %default-exclude
35 emacs-build))
36
37 ;; Commentary:
38 ;;
39 ;; Builder-side code of the build procedure for ELPA Emacs packages.
40 ;;
41 ;; Code:
42
43 ;; Directory suffix where we install ELPA packages. We avoid ".../elpa" as
44 ;; Emacs expects to find the ELPA repository 'archive-contents' file and the
45 ;; archive signature.
46 (define %legacy-install-suffix "/share/emacs/site-lisp")
47 (define %install-suffix (string-append %legacy-install-suffix "/guix.d"))
48
49 ;; These are the default inclusion/exclusion regexps for the install phase.
50 (define %default-include '("^[^/]*\\.el$" "^[^/]*\\.info$" "^doc/.*\\.info$"))
51 (define %default-exclude '("^\\.dir-locals\\.el$" "-pkg\\.el$"
52 "^[^/]*tests?\\.el$"))
53
54 (define gnu:unpack (assoc-ref gnu:%standard-phases 'unpack))
55
56 (define (store-file->elisp-source-file file)
57 "Convert FILE, a store file name for an Emacs Lisp source file, into a file
58 name that has been stripped of the hash and version number."
59 (let ((suffix ".el"))
60 (let-values (((name version)
61 (package-name->name+version
62 (basename
63 (strip-store-file-name file) suffix))))
64 (string-append name suffix))))
65
66 (define* (unpack #:key source #:allow-other-keys)
67 "Unpack SOURCE into the build directory. SOURCE may be a compressed
68 archive, a directory, or an Emacs Lisp file."
69 (if (string-suffix? ".el" source)
70 (begin
71 (mkdir "source")
72 (chdir "source")
73 (copy-file source (store-file->elisp-source-file source))
74 #t)
75 (gnu:unpack #:source source)))
76
77 (define* (set-emacs-load-path #:key inputs #:allow-other-keys)
78 "Set the EMACSLOADPATH environment variable so that dependencies are found."
79 (let* ((input-elisp-dirs (emacs-inputs-el-directories
80 (emacs-inputs-directories inputs)))
81 (emacs-load-path-value (string-join
82 input-elisp-dirs ":" 'suffix)))
83 (setenv "EMACSLOADPATH" emacs-load-path-value)
84 (format #t "environment variable `EMACSLOADPATH' set to ~a\n"
85 emacs-load-path-value)))
86
87 (define* (build #:key outputs inputs #:allow-other-keys)
88 "Compile .el files."
89 (let* ((emacs (string-append (assoc-ref inputs "emacs") "/bin/emacs"))
90 (out (assoc-ref outputs "out"))
91 (elpa-name-ver (store-directory->elpa-name-version out))
92 (el-dir (string-append out %install-suffix "/" elpa-name-ver)))
93 (setenv "SHELL" "sh")
94 (parameterize ((%emacs emacs))
95 (emacs-byte-compile-directory el-dir))))
96
97 (define* (patch-el-files #:key outputs #:allow-other-keys)
98 "Substitute the absolute \"/bin/\" directory with the right location in the
99 store in '.el' files."
100
101 (define (file-contains-nul-char? file)
102 (call-with-input-file file
103 (lambda (in)
104 (let loop ((line (read-line in 'concat)))
105 (cond
106 ((eof-object? line) #f)
107 ((string-index line #\nul) #t)
108 (else (loop (read-line in 'concat))))))
109 #:binary #t))
110
111 (let* ((out (assoc-ref outputs "out"))
112 (elpa-name-ver (store-directory->elpa-name-version out))
113 (el-dir (string-append out %install-suffix "/" elpa-name-ver))
114
115 ;; (ice-9 regex) uses libc's regexp routines, which cannot deal with
116 ;; strings containing NULs. Filter out such files. TODO: Remove
117 ;; this workaround when <https://bugs.gnu.org/30116> is fixed.
118 (el-files (remove file-contains-nul-char?
119 (find-files (getcwd) "\\.el$"))))
120 (define (substitute-program-names)
121 (substitute* el-files
122 (("\"/bin/([^.]\\S*)\"" _ cmd-name)
123 (let ((cmd (which cmd-name)))
124 (unless cmd
125 (error "patch-el-files: unable to locate " cmd-name))
126 (string-append "\"" cmd "\"")))))
127
128 (with-directory-excursion el-dir
129 ;; Some old '.el' files (e.g., tex-buf.el in AUCTeX) are still
130 ;; ISO-8859-1-encoded.
131 (unless (false-if-exception (substitute-program-names))
132 (with-fluids ((%default-port-encoding "ISO-8859-1"))
133 (substitute-program-names))))
134 #t))
135
136 (define* (install #:key outputs
137 (include %default-include)
138 (exclude %default-exclude)
139 #:allow-other-keys)
140 "Install the package contents."
141
142 (define source (getcwd))
143
144 (define* (install-file? file stat #:key verbose?)
145 (let* ((stripped-file (string-trim
146 (string-drop file (string-length source)) #\/)))
147 (define (match-stripped-file action regex)
148 (let ((result (string-match regex stripped-file)))
149 (when (and result verbose?)
150 (format #t "info: ~A ~A as it matches \"~A\"\n"
151 stripped-file action regex))
152 result))
153
154 (when verbose?
155 (format #t "info: considering installing ~A\n" stripped-file))
156
157 (and (any (cut match-stripped-file "included" <>) include)
158 (not (any (cut match-stripped-file "excluded" <>) exclude)))))
159
160 (let* ((out (assoc-ref outputs "out"))
161 (elpa-name-ver (store-directory->elpa-name-version out))
162 (target-directory (string-append out %install-suffix "/" elpa-name-ver))
163 (files-to-install (find-files source install-file?)))
164 (cond
165 ((not (null? files-to-install))
166 (for-each
167 (lambda (file)
168 (let* ((stripped-file (string-drop file (string-length source)))
169 (target-file (string-append target-directory stripped-file)))
170 (format #t "`~a' -> `~a'~%" file target-file)
171 (install-file file (dirname target-file))))
172 files-to-install)
173 #t)
174 (else
175 (format #t "error: No files found to install.\n")
176 (find-files source (lambda (file stat)
177 (install-file? file stat #:verbose? #t)))
178 #f))))
179
180 (define* (move-doc #:key outputs #:allow-other-keys)
181 "Move info files from the ELPA package directory to the info directory."
182 (let* ((out (assoc-ref outputs "out"))
183 (elpa-name-ver (store-directory->elpa-name-version out))
184 (el-dir (string-append out %install-suffix "/" elpa-name-ver))
185 (name-ver (strip-store-file-name out))
186 (info-dir (string-append out "/share/info/"))
187 (info-files (find-files el-dir "\\.info$")))
188 (unless (null? info-files)
189 (mkdir-p info-dir)
190 (with-directory-excursion el-dir
191 (when (file-exists? "dir") (delete-file "dir"))
192 (for-each (lambda (f)
193 (copy-file f (string-append info-dir "/" (basename f)))
194 (delete-file f))
195 info-files)))
196 #t))
197
198 (define* (make-autoloads #:key outputs inputs #:allow-other-keys)
199 "Generate the autoloads file."
200 (let* ((emacs (string-append (assoc-ref inputs "emacs") "/bin/emacs"))
201 (out (assoc-ref outputs "out"))
202 (elpa-name-ver (store-directory->elpa-name-version out))
203 (elpa-name (package-name->name+version elpa-name-ver))
204 (el-dir (string-append out %install-suffix "/" elpa-name-ver)))
205 (parameterize ((%emacs emacs))
206 (emacs-generate-autoloads elpa-name el-dir))
207 #t))
208
209 (define (emacs-package? name)
210 "Check if NAME correspond to the name of an Emacs package."
211 (string-prefix? "emacs-" name))
212
213 (define (emacs-inputs inputs)
214 "Retrieve the list of Emacs packages from INPUTS."
215 (filter (match-lambda
216 ((label . directory)
217 (emacs-package? ((compose package-name->name+version
218 strip-store-file-name)
219 directory)))
220 (_ #f))
221 inputs))
222
223 (define (emacs-inputs-directories inputs)
224 "Extract the list of Emacs package directories from INPUTS."
225 (let ((inputs (emacs-inputs inputs)))
226 (match inputs
227 (((names . directories) ...) directories))))
228
229 (define (emacs-input->el-directory emacs-input)
230 "Return the correct Elisp directory location of EMACS-INPUT or #f if none."
231 (let ((legacy-elisp-dir (string-append emacs-input %legacy-install-suffix))
232 (guix-elisp-dir (string-append
233 emacs-input %install-suffix "/"
234 (store-directory->elpa-name-version emacs-input))))
235 (cond
236 ((file-exists? guix-elisp-dir) guix-elisp-dir)
237 ((file-exists? legacy-elisp-dir) legacy-elisp-dir)
238 (else (format #t "warning: could not locate elisp directory under `~a'\n"
239 emacs-input)
240 #f))))
241
242 (define (emacs-inputs-el-directories dirs)
243 "Build the list of Emacs Lisp directories from the Emacs package directory
244 DIRS."
245 (filter-map emacs-input->el-directory dirs))
246
247 (define (package-name-version->elpa-name-version name-ver)
248 "Convert the Guix package NAME-VER to the corresponding ELPA name-version
249 format. Essentially drop the prefix used in Guix."
250 (if (emacs-package? name-ver) ; checks for "emacs-" prefix
251 (string-drop name-ver (string-length "emacs-"))
252 name-ver))
253
254 (define (store-directory->elpa-name-version store-dir)
255 "Given a store directory STORE-DIR return the part of the basename after the
256 second hyphen. This corresponds to 'name-version' as used in ELPA packages."
257 ((compose package-name-version->elpa-name-version
258 strip-store-file-name)
259 store-dir))
260
261 (define %standard-phases
262 (modify-phases gnu:%standard-phases
263 (add-after 'set-paths 'set-emacs-load-path set-emacs-load-path)
264 (replace 'unpack unpack)
265 (delete 'bootstrap)
266 (delete 'configure)
267 ;; Move the build phase after install: the .el files are byte compiled
268 ;; directly in the store.
269 (delete 'build)
270 (replace 'install install)
271 (add-after 'install 'build build)
272 (add-after 'install 'make-autoloads make-autoloads)
273 (add-after 'make-autoloads 'patch-el-files patch-el-files)
274 (add-after 'make-autoloads 'move-doc move-doc)))
275
276 (define* (emacs-build #:key inputs (phases %standard-phases)
277 #:allow-other-keys #:rest args)
278 "Build the given Emacs package, applying all of PHASES in order."
279 (apply gnu:gnu-build
280 #:inputs inputs #:phases phases
281 args))
282
283 ;;; emacs-build-system.scm ends here