Adapted stuff from autoload
[elinstall.git] / elinstall.el
blob31f8cf6b856af1adbe244e443f4f2358796f3f83
1 ;;;_ elinstall.el --- Automatically and flexibly install elisp files
3 ;;;_. Headers
4 ;;;_ , License
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)
13 ;; any later version.
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.
25 ;;;_ , Commentary:
27 ;;
30 ;;;_ , Requires
34 ;;;_. Body
35 ;;;_ , Customizations
36 (defgroup elinstall
37 '()
38 "Customizations for elinstall"
39 :group 'elinstall)
41 (defcustom elinstall-default-priority
43 "Default priority for site-start"
44 :group 'elinstall
45 :type 'integer)
47 (defcustom elinstall-default-target
48 'my-site-start
49 "Default target for registering autoloads"
50 :group 'elinstall
51 :options '(my-site-start))
54 (defcustom elinstall-already-installed
55 '()
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." )
60 ;;;_ , Compatibility
61 ;;$$OBSOLETE
62 (defconst elinstall-autoload-updator
63 (cond
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.")
70 ;;;_ , Utilities
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."
75 (file-name-directory
76 (if load-file-name
77 (file-truename load-file-name)
78 (file-truename buffer-file-name))))
80 ;;;_ , Work
81 ;;;_ . Actions
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."
86 (insert "\n")
87 (pp
88 `(add-to-list ',path-sym
89 (expand-file-name ,new
90 (if load-file-name
91 (file-name-directory
92 (file-truename load-file-name)))))
93 (current-buffer)))
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"
117 (when action
118 (case (car action)
119 (add-dir-autoloads
120 (apply #'elinstall-insert-dir-autoloads (cdr action)))
122 (add-to-load-path
123 ;;Go to the right buffer, right position, then call:
124 '(elinstall-insert-add-to-load-path))
126 ;;Similar, but for info-path.
127 (add-to-info-path
128 'elinstall-insert-add-to-info-path)
130 (preload-file
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
138 ;;a new arg.
139 ;;`relative-to' can be:
140 ;; * nil: act as at present. Assume that FILE's immediate directory
141 ;;is in load-path.
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
150 are used.
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))
154 (autoloads-done '())
155 (load-name (let ((name
156 (cond
157 ((not relative-to)
158 (file-name-nondirectory file))
159 ((eq relative-to t)
160 (file-relative-name file))
162 (file-relative-name relative-to)))))
163 (if (string-match "\\.elc?\\(\\.\\|$\\)" name)
164 (substring name 0 (match-beginning 0))
165 name)))
166 (print-length nil)
167 (print-readably t) ; This does something in Lucid Emacs.
168 (float-output-format nil)
169 (done-any nil)
170 (visited (get-file-buffer file))
171 output-start)
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)))
196 (save-excursion
197 (save-restriction
198 (widen)
199 (goto-char (point-min))
200 (while (not (eobp))
201 (skip-chars-forward " \t\n\f")
202 (cond
203 ((looking-at (regexp-quote generate-autoload-cookie))
204 (search-forward generate-autoload-cookie)
205 (skip-chars-forward " \t")
206 (setq done-any t)
207 (if (eolp)
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)))
212 (if autoload
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
220 (progn
221 ;; Back up over whitespace, to preserve it.
222 (skip-chars-backward " \f\t")
223 (if (= (char-after (1+ (point))) ? )
224 ;; Eat one space.
225 (forward-char 1))
226 (point))
227 (progn (forward-line 1) (point)))
228 outbuf)))
229 ((looking-at ";")
230 ;; Don't read the comment.
231 (forward-line 1))
233 (forward-sexp 1)
234 (forward-line 1))))))
236 (when done-any
237 (with-current-buffer outbuf
238 (save-excursion
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))
249 (or visited
250 ;; We created this buffer, so we should kill it.
251 (kill-buffer (current-buffer))))
252 (not done-any)))
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),
258 save the buffer too.
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))
265 name)))
266 (found nil)
267 (existing-buffer (get-file-buffer file))
268 (no-autoloads nil))
269 (save-excursion
270 ;; We want to get a value for generated-autoload-file from
271 ;; the local variables section if it's there.
272 (if existing-buffer
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))
289 (save-excursion
290 (save-restriction
291 (widen)
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)))
307 (progn
308 (if (interactive-p)
309 (message "\
310 Autoload section for %s is up to date."
311 file))
312 (setq found 'up-to-date))
313 (search-forward generate-autoload-section-trailer)
314 (delete-region begin (point))
315 (setq found t))))
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)))))
323 (or found
324 (progn
325 (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)))))
331 (and save-after
332 (buffer-modified-p)
333 (save-buffer))
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)))))
354 (files (apply 'nconc
355 (mapcar (lambda (dir)
356 (directory-files (expand-file-name dir)
357 t files-re))
358 dirs)))
359 (this-time (current-time))
360 (no-autoloads nil) ;files with no autoload cookies.
361 (autoloads-file
362 (expand-file-name generated-autoload-file
363 (expand-file-name "lisp" source-directory)))
364 (top-dir (file-name-directory autoloads-file)))
366 (with-current-buffer
367 (find-file-noselect (autoload-ensure-default-file autoloads-file))
368 (save-excursion
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))
377 (file (nth 3 form)))
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)))
384 (dolist (file file)
385 (let ((file-time (nth 5 (file-attributes file))))
386 (when (and file-time
387 (not (time-less-p last-time file-time)))
388 ;; file unchanged
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.
397 nil)
399 (elinstall-update-file-autoloads file)))
400 (setq files (delete file files)))))
401 ;; Elements remaining in FILES have no existing autoload sections yet.
402 (setq no-autoloads
403 (append no-autoloads
404 (delq nil (mapcar #'elinstall-update-file-autoloads files))))
405 (when no-autoloads
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))
414 ;;(save-buffer)
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"
423 (cons
424 (cons key new-value)
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"
436 (expand-file-name
437 filename
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."
444 (if (consp spec)
445 ;;$$IMPROVE ME by adding the other cases in the design.
446 (case (car spec)
448 (let
449 ((new-path
450 (elinstall-expand-filename
451 (second spec)
452 parameters)))
454 (elinstall-find-actions-by-spec
455 (third spec)
456 (elinstall-add-parameter parameters
457 'path new-path))))
459 (all
460 (apply #'append
461 (mapcar
462 #'(lambda (sub-spec)
463 (elinstall-find-actions-by-spec
464 sub-spec
465 parameters))
466 (cdr spec))))
468 (dir
469 (list
470 (unless
471 (elinstall-get-parameter
472 parameters 'block-add-to-load-path)
473 `(add-to-load-path
474 ,(elinstall-get-parameter
475 parameters 'def-file)
476 ,(elinstall-expand-filename
477 (second spec)
478 parameters)))
479 `(add-dir-autoloads
480 ,(elinstall-get-parameter
481 parameters 'def-file)
482 ,(elinstall-expand-filename
483 (second spec)
484 parameters))))
486 (def-file
487 (let
488 ((new-def-file
489 (elinstall-expand-filename
490 (second spec)
491 parameters)))
492 (elinstall-find-actions-by-spec
493 (third spec)
494 (elinstall-add-parameter parameters
495 'def-file new-def-file)))))
497 ;;$$PUNTED
498 (case spec
499 (t))))
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"))
517 ;;;_ , Entry points
518 (defun elinstall (project-name dir spec &optional target force)
519 "Install elisp files.
521 They need not be presented as a package.
523 Parameters:
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
529 `(in DIRECTORY t).
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
535 installed."
537 (when
538 (and
539 (or
540 force
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)))
544 (let
545 ((actions
546 (elinstall-find-actions-by-spec
547 spec
549 (path . ,dir)
550 ;;This is just the default loaddefs file, spec actually
551 ;;controls it.
552 (def-file . "elinstall-loaddefs.el" )))))
553 (mapcar #'elinstall-do-action actions)
554 '(elinstall-record-installed project-name))))
558 ;;;_. Footers
559 ;;;_ , Provides
561 (provide 'elinstall)
563 ;;;_ * Local emacs vars.
564 ;;;_ + Local variables:
565 ;;;_ + mode: allout
566 ;;;_ + End:
568 ;;;_ , End
569 ;;; elinstall.el ends here