1 ;;;_ elinstall.el --- Automatically and flexibly install elisp files
5 ;; Copyright (C) 2010 Tom Breton (Tehom)
7 ;; Author: Tom Breton (Tehom) <tehom@panix.com>
8 ;; Keywords: maint, tools, internal
10 ;; This file is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; This file is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to
22 ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
38 "Customizations for elinstall"
41 (defcustom elinstall-default-priority
43 "Default priority for site-start"
47 (defcustom elinstall-default-target
49 "Default target for registering autoloads"
51 :options
'(my-site-start))
54 (defcustom elinstall-already-installed
56 "Things that have already been installed.
57 This exists for recording what has been installed. User interaction is not
58 contemplated at this time." )
62 (defconst elinstall-autoload-updator
64 ((fboundp 'update-autoloads-from-directories
)
65 'update-autoloads-from-directories
)
66 ((fboundp 'update-directory-autoloads
)
67 'update-directory-autoloads
))
68 "Function used to extract autoloads.")
72 (defun elinstall-directory-true-name ()
73 "Get the true name of the directory the calling code lives in.
74 CAUTION: This is sensitive to where it's called. That's the point of it."
77 (file-truename load-file-name
)
78 (file-truename buffer-file-name
))))
82 ;;;_ , elinstall-insert-add-to-path
83 (defun elinstall-insert-add-to-path (new path-sym
)
84 "Insert code to add NEW to PATH-SYM.
85 Insert this at point in current buffer."
88 `(add-to-list ',path-sym
89 (expand-file-name ,new
92 (file-truename load-file-name
)))))
95 ;;;_ , elinstall-insert-add-to-load-path
96 (defun elinstall-insert-add-to-load-path (new)
97 "Insert code to add NEW to load-path.
98 Insert this at point in current buffer."
99 (elinstall-insert-add-to-path new
'load-path
))
101 ;;;_ , elinstall-insert-add-to-info-path
102 (defun elinstall-insert-add-to-info-path (new)
103 "Insert code to add NEW to info-path.
104 Insert this at point in current buffer."
105 (elinstall-insert-add-to-path new
'Info-default-directory-list
))
107 ;;;_ , elinstall-insert-dir-autoloads
108 (defun elinstall-insert-dir-autoloads (autoloads-file directory
)
109 "Generate autoloads for DIRECTORY into AUTOLOADS-FILE."
110 (declare (special elinstall-autoload-updator
))
111 (let ((generated-autoload-file autoloads-file
))
112 (elinstall-update-directory-autoloads directory
)))
114 ;;;_ , elinstall-do-actions
115 (defun elinstall-do-action (action)
116 "Do a single installating action"
120 (apply #'elinstall-insert-dir-autoloads
(cdr action
)))
123 ;;Go to the right buffer, right position, then call:
124 '(elinstall-insert-add-to-load-path))
126 ;;Similar, but for info-path.
128 'elinstall-insert-add-to-info-path
)
131 ;;Symlink file to target.
135 ;;;_ . Overrides (All adapted from autoload.el)
136 ;;;_ , generate-file-autoloads override to allow slashed load-paths
137 ;;Quick and dirty: We just override `generate-file-autoloads' and add
139 ;;`relative-to' can be:
140 ;; * nil: act as at present. Assume that FILE's immediate directory
142 ;; * t :: use default-directory
143 ;; * a string :: relative to it, as a filename
145 (defun generate-file-autoloads (file &optional relative-to
)
146 "Insert at point a loaddefs autoload section for FILE.
147 Autoloads are generated for defuns and defmacros in FILE
148 marked by `generate-autoload-cookie' (which see).
149 If FILE is being visited in a buffer, the contents of the buffer
151 Return non-nil in the case where no autoloads were added at point."
152 (interactive "fGenerate autoloads for file: ")
153 (let ((outbuf (current-buffer))
155 (load-name (let ((name
158 (file-name-nondirectory file
))
160 (file-relative-name file
))
162 (file-relative-name relative-to
)))))
163 (if (string-match "\\.elc?\\(\\.\\|$\\)" name
)
164 (substring name
0 (match-beginning 0))
167 (print-readably t
) ; This does something in Lucid Emacs.
168 (float-output-format nil
)
170 (visited (get-file-buffer file
))
173 ;; If the autoload section we create here uses an absolute
174 ;; file name for FILE in its header, and then Emacs is installed
175 ;; under a different path on another system,
176 ;; `update-autoloads-here' won't be able to find the files to be
177 ;; autoloaded. So, if FILE is in the same directory or a
178 ;; subdirectory of the current buffer's directory, we'll make it
179 ;; relative to the current buffer's directory.
180 (setq file
(expand-file-name file
))
181 (let* ((source-truename (file-truename file
))
182 (dir-truename (file-name-as-directory
183 (file-truename default-directory
)))
184 (len (length dir-truename
)))
185 (if (and (< len
(length source-truename
))
186 (string= dir-truename
(substring source-truename
0 len
)))
187 (setq file
(substring source-truename len
))))
189 (with-current-buffer (or visited
190 ;; It is faster to avoid visiting the file.
191 (autoload-find-file file
))
192 ;; Obey the no-update-autoloads file local variable.
193 (unless no-update-autoloads
194 (message "Generating autoloads for %s..." file
)
195 (setq output-start
(with-current-buffer outbuf
(point)))
199 (goto-char (point-min))
201 (skip-chars-forward " \t\n\f")
203 ((looking-at (regexp-quote generate-autoload-cookie
))
204 (search-forward generate-autoload-cookie
)
205 (skip-chars-forward " \t")
208 ;; Read the next form and make an autoload.
209 (let* ((form (prog1 (read (current-buffer))
210 (or (bolp) (forward-line 1))))
211 (autoload (make-autoload form load-name
)))
213 (push (nth 1 form
) autoloads-done
)
214 (setq autoload form
))
215 (let ((autoload-print-form-outbuf outbuf
))
216 (autoload-print-form autoload
)))
218 ;; Copy the rest of the line to the output.
219 (princ (buffer-substring
221 ;; Back up over whitespace, to preserve it.
222 (skip-chars-backward " \f\t")
223 (if (= (char-after (1+ (point))) ?
)
227 (progn (forward-line 1) (point)))
230 ;; Don't read the comment.
234 (forward-line 1))))))
237 (with-current-buffer outbuf
239 ;; Insert the section-header line which lists the file name
240 ;; and which functions are in it, etc.
241 (goto-char output-start
)
242 (autoload-insert-section-header
243 outbuf autoloads-done load-name file
244 (nth 5 (file-attributes file
)))
245 (insert ";;; Generated autoloads from "
246 (autoload-trim-file-name file
) "\n"))
247 (insert generate-autoload-section-trailer
)))
248 (message "Generating autoloads for %s...done" file
))
250 ;; We created this buffer, so we should kill it.
251 (kill-buffer (current-buffer))))
253 ;;;_ , elinstall-update-file-autoloads
254 (defun elinstall-update-file-autoloads (file &optional save-after
)
255 "Update the autoloads for FILE in `generated-autoload-file'
256 \(which FILE might bind in its local variables).
257 If SAVE-AFTER is non-nil (which is always, when called interactively),
260 Return FILE if there was no autoload cookie in it, else nil."
261 (interactive "fUpdate autoloads for file: \np")
262 (let ((load-name (let ((name (file-name-nondirectory file
)))
263 (if (string-match "\\.elc?\\(\\.\\|$\\)" name
)
264 (substring name
0 (match-beginning 0))
267 (existing-buffer (get-file-buffer file
))
270 ;; We want to get a value for generated-autoload-file from
271 ;; the local variables section if it's there.
273 (set-buffer existing-buffer
))
274 ;; We must read/write the file without any code conversion,
275 ;; but still decode EOLs.
276 (let ((coding-system-for-read 'raw-text
))
277 (set-buffer (find-file-noselect
278 (autoload-ensure-default-file
279 (expand-file-name generated-autoload-file
280 (expand-file-name "lisp"
281 source-directory
)))))
282 ;; This is to make generated-autoload-file have Unix EOLs, so
283 ;; that it is portable to all platforms.
284 (setq buffer-file-coding-system
'raw-text-unix
))
285 (or (> (buffer-size) 0)
286 (error "Autoloads file %s does not exist" buffer-file-name
))
287 (or (file-writable-p buffer-file-name
)
288 (error "Autoloads file %s is not writable" buffer-file-name
))
292 (goto-char (point-min))
293 ;; Look for the section for LOAD-NAME.
294 (while (and (not found
)
295 (search-forward generate-autoload-section-header nil t
))
296 (let ((form (autoload-read-section-header)))
297 (cond ((string= (nth 2 form
) load-name
)
298 ;; We found the section for this file.
299 ;; Check if it is up to date.
300 (let ((begin (match-beginning 0))
301 (last-time (nth 4 form
))
302 (file-time (nth 5 (file-attributes file
))))
303 (if (and (or (null existing-buffer
)
304 (not (buffer-modified-p existing-buffer
)))
305 (listp last-time
) (= (length last-time
) 2)
306 (not (time-less-p last-time file-time
)))
310 Autoload section for %s is up to date."
312 (setq found
'up-to-date
))
313 (search-forward generate-autoload-section-trailer
)
314 (delete-region begin
(point))
316 ((string< load-name
(nth 2 form
))
317 ;; We've come to a section alphabetically later than
318 ;; LOAD-NAME. We assume the file is in order and so
319 ;; there must be no section for LOAD-NAME. We will
320 ;; insert one before the section here.
321 (goto-char (match-beginning 0))
322 (setq found
'new
)))))
326 ;; No later sections in the file. Put before the last page.
327 (goto-char (point-max))
328 (search-backward "\f" nil t
)))
329 (or (eq found
'up-to-date
)
330 (setq no-autoloads
(generate-file-autoloads file
)))))
335 (if no-autoloads file
))))
337 ;;;_ , elinstall-update-directory-autoloads
339 (defun elinstall-update-directory-autoloads (&rest dirs
)
341 Update loaddefs.el with all the current autoloads from DIRS, and no old ones.
342 This uses `update-file-autoloads' (which see) to do its work.
343 In an interactive call, you must give one argument, the name
344 of a single directory. In a call from Lisp, you can supply multiple
345 directories as separate arguments, but this usage is discouraged.
347 The function does NOT recursively descend into subdirectories of the
348 directory or directories specified."
349 (interactive "DUpdate autoloads from directory: ")
350 (let* ((files-re (let ((tmp nil
))
351 (dolist (suf (get-load-suffixes)
352 (concat "^[^=.].*" (regexp-opt tmp t
) "\\'"))
353 (unless (string-match "\\.elc" suf
) (push suf tmp
)))))
355 (mapcar (lambda (dir)
356 (directory-files (expand-file-name dir
)
359 (this-time (current-time))
360 (no-autoloads nil
) ;files with no autoload cookies.
362 (expand-file-name generated-autoload-file
363 (expand-file-name "lisp" source-directory
)))
364 (top-dir (file-name-directory autoloads-file
)))
367 (find-file-noselect (autoload-ensure-default-file autoloads-file
))
370 ;; Canonicalize file names and remove the autoload file itself.
371 (setq files
(delete (autoload-trim-file-name buffer-file-name
)
372 (mapcar 'autoload-trim-file-name files
)))
374 (goto-char (point-min))
375 (while (search-forward generate-autoload-section-header nil t
)
376 (let* ((form (autoload-read-section-header))
378 (cond ((and (consp file
) (stringp (car file
)))
379 ;; This is a list of files that have no autoload cookies.
380 ;; There shouldn't be more than one such entry.
381 ;; Remove the obsolete section.
382 (autoload-remove-section (match-beginning 0))
383 (let ((last-time (nth 4 form
)))
385 (let ((file-time (nth 5 (file-attributes file
))))
387 (not (time-less-p last-time file-time
)))
389 (push file no-autoloads
)
390 (setq files
(delete file files
)))))))
391 ((not (stringp file
)))
392 ((not (file-exists-p (expand-file-name file top-dir
)))
393 ;; Remove the obsolete section.
394 (autoload-remove-section (match-beginning 0)))
395 ((equal (nth 4 form
) (nth 5 (file-attributes file
)))
396 ;; File hasn't changed.
399 (elinstall-update-file-autoloads file
)))
400 (setq files
(delete file files
)))))
401 ;; Elements remaining in FILES have no existing autoload sections yet.
404 (delq nil
(mapcar #'elinstall-update-file-autoloads files
))))
406 ;; Sort them for better readability.
407 (setq no-autoloads
(sort no-autoloads
'string
<))
408 ;; Add the `no-autoloads' section.
409 (goto-char (point-max))
410 (search-backward "\f" nil t
)
411 (autoload-insert-section-header
412 (current-buffer) nil nil no-autoloads this-time
)
413 (insert generate-autoload-section-trailer
))
417 ;;;_ . Generating autoloads etc by spec
418 ;;;_ , Treating the parameter list
419 ;;;_ . elinstall-add-parameter
420 (defun elinstall-add-parameter (alist key new-value
)
421 "Add a new value for KEY to ALIST"
425 (assq-delete-all key
(copy-list alist
))))
427 ;;;_ . elinstall-get-parameter
428 (defun elinstall-get-parameter (alist key
)
429 "Get the value of KEY from ALIST"
431 (cdr (assq key alist
)))
432 ;;;_ . elinstall-expand-filename
433 (defun elinstall-expand-filename (filename alist
)
434 "Expand FILENAME by the value of `path' in ALIST"
438 (elinstall-get-parameter alist
'path
)))
440 ;;;_ , elinstall-find-actions-by-spec
442 (defun elinstall-find-actions-by-spec (spec parameters
)
443 "Return a list of actions to do, controlled by SPEC and PARAMETERS."
445 ;;$$IMPROVE ME by adding the other cases in the design.
450 (elinstall-expand-filename
454 (elinstall-find-actions-by-spec
456 (elinstall-add-parameter parameters
463 (elinstall-find-actions-by-spec
471 (elinstall-get-parameter
472 parameters
'block-add-to-load-path
)
474 ,(elinstall-get-parameter
475 parameters
'def-file
)
476 ,(elinstall-expand-filename
480 ,(elinstall-get-parameter
481 parameters
'def-file
)
482 ,(elinstall-expand-filename
489 (elinstall-expand-filename
492 (elinstall-find-actions-by-spec
494 (elinstall-add-parameter parameters
495 'def-file new-def-file
)))))
501 ;;;_ . Checking installedness
502 ;;;_ , elinstall-already-installed
503 (defun elinstall-already-installed (project-name)
504 "Return non-nil if PROJECT-NAME has been installed."
505 (member project-name elinstall-already-installed
))
507 ;;;_ , elinstall-record-installed
508 (defun elinstall-record-installed (project-name)
509 "Record that PROJECT-NAME has been installed."
511 (add-to-list 'elinstall-already-installed project-name
)
512 (customize-save-variable
513 'elinstall-already-installed
514 elinstall-already-installed
515 "Set by elinstall-record-installed"))
518 (defun elinstall (project-name dir spec
&optional target force
)
519 "Install elisp files.
521 They need not be presented as a package.
524 PROJECT-NAME - the name of the project
525 DIR - the root directory of the project.
526 Suggestion: (elinstall-directory-true-name)
528 SPEC - a spec for the autoloads etc to make. It can be as simple as
530 Suggestion: `(in ,(elinstall-directory-true-name) t)
531 TARGET is where the autoload files are to be symlinked in. If
532 `nil' `elinstall-default-target' is used instead.
534 If FORCE is non-nil, install a package even if it has already been
541 (not (elinstall-already-installed project-name
)))
542 ;;$$REMOVE ME - I commented it out just for development.
543 '(yes-or-no-p (format "Install %s " project-name
)))
546 (elinstall-find-actions-by-spec
550 ;;This is just the default loaddefs file, spec actually
552 (def-file .
"elinstall-loaddefs.el" )))))
553 (mapcar #'elinstall-do-action actions
)
554 '(elinstall-record-installed project-name
))))
563 ;;;_ * Local emacs vars.
564 ;;;_ + Local variables:
569 ;;; elinstall.el ends here