Added docstring
[elinstall.git] / elinstall.el
blob823574570e574c41914ae3204077f65c2bc77035
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.
24 ;;;_ , Version
25 ;;Version: 1.0
27 ;;;_ , Commentary:
29 ;; Entry points:
30 ;; elinstall Use this for overall loading
32 ;; elinstall-arrange-preload - Use this for non-autogenerated
33 ;; files that need to be linked in.
35 ;; elinstall-update-directory-autoloads
36 ;; elinstall-update-file-autoloads
38 ;;;_ , Requires
40 (require 'autoload)
41 (require 'pp)
42 (require 'cus-edit) ;;Because we save "installedness" manually
43 (require 'byte-compile nil t) ;;
46 ;;;_. Body
47 ;;;_ , Customizations
48 ;;;_ . Group
49 (defgroup elinstall
50 '()
51 "Customizations for elinstall"
52 :group 'development)
53 ;;;_ . elinstall-default-priority
54 (defcustom elinstall-default-priority
56 "Default priority for site-start"
57 :group 'elinstall
58 :type 'integer)
59 ;;;_ . elinstall-default-preload-target
60 (defcustom elinstall-default-preload-target
61 "~/.emacs.d/site-start.d/"
62 "Default preload-target for registering autoloads"
63 :group 'elinstall
64 :type
65 '(choice
66 (const "~/.emacs.d/site-start.d/")
67 (const "/etc/emacs/site-start.d/")
68 (directory "" )
69 (const nil)
70 (const 'dot-emacs)))
71 ;;;_ . elinstall-already-installed
72 (with-no-warnings
73 (defcustom elinstall-already-installed
74 '()
75 "(AUTOMATIC) Things that have already been installed.
76 This exists for recording what has been installed.
78 Though it's saved as customizable, user interaction is not
79 contemplated." ))
80 ;;;_ , Types
81 ;;;_ . elinstall-stages
82 (defstruct (elinstall-stages
83 (:constructor elinstall-make-stages)
84 (:conc-name elinstall-stages->)
85 (:copier nil))
86 "The elinstall stages"
87 build-deffiles
88 run-tests
89 byte-compile
90 arrange-preloads)
91 ;;;_ , Data
92 ;;;_ . Regular expressions
93 ;;;_ , elinstall-elisp-regexp
94 (defconst elinstall-elisp-regexp
95 (let ((tmp nil))
96 (dolist
97 (suf (get-load-suffixes))
98 (unless (string-match "\\.elc" suf) (push suf tmp)))
99 (concat "^[^=.].*" (regexp-opt tmp t) "\\'"))
100 "Regular expression that matches elisp files" )
101 ;;;_ , Utilities
102 ;;;_ . elinstall-file-mod-time
103 (defsubst elinstall-file-mod-time (file)
104 "Return the modification time of FILE"
105 (nth 5 (file-attributes file)))
107 ;;;_ . elinstall-directory-true-name
108 (defun elinstall-directory-true-name ()
109 "Get the true name of the directory the calling code lives in.
110 CAUTION: This is sensitive to where it's called. That's the point of it."
111 (file-name-directory
112 (if load-file-name
113 (file-truename load-file-name)
114 (file-truename buffer-file-name))))
115 ;;;_ . Checking installedness
116 ;;;_ , elinstall-get-installation-record
117 (defun elinstall-get-installation-record (project-name)
118 "Return the installation record for PROJECT-NAME."
120 (assoc project-name elinstall-already-installed))
122 ;;;_ , elinstall-already-installed
123 (defun elinstall-already-installed (project-name)
124 "Return non-nil if PROJECT-NAME has been installed."
125 (elinstall-get-installation-record project-name))
127 ;;;_ , elinstall-record-installed
128 (defun elinstall-record-installed (project-name &optional version)
129 "Record that PROJECT-NAME has been installed."
130 (let
131 ((new-item
132 (list
133 project-name
134 (or version "0")
135 (current-time)
136 'installed))
137 (old-item
138 (elinstall-get-installation-record project-name)))
139 (when old-item
140 (setq elinstall-already-installed
141 (delete old-item elinstall-already-installed)))
142 (push new-item elinstall-already-installed)
143 (customize-save-variable
144 'elinstall-already-installed
145 elinstall-already-installed
146 "Set by elinstall-record-installed")))
147 ;;;_ . Finding deffiles
148 ;;;_ , elinstall-expand-deffile-name
149 (defun elinstall-expand-deffile-name (deffile)
150 "Expand DEFFILE autoload.el's way."
152 (expand-file-name (or deffile "loaddefs.el")
153 (expand-file-name "lisp"
154 source-directory)))
156 ;;;_ , Work
157 ;;;_ . Doing actions
159 ;;;_ , Doing autoload actions (adapted from autoload.el)
160 ;;;_ . Utilities about the action list
161 ;;;_ , elinstall-remove-autogen-action
162 (defun elinstall-remove-autogen-action (file actions)
163 "Return ACTIONS minus any add-file-autoloads on FILE removed."
165 (delq nil
166 (mapcar
167 #'(lambda (act)
168 (case (car act)
169 (add-file-autoloads
170 (if (equal file (third act))
172 act))
173 (t act)))
174 actions)))
175 ;;;_ , elinstall-get-autogen-action
176 (defun elinstall-get-autogen-action (file actions)
178 (let
179 ((the-act))
180 (dolist (act actions)
181 (case (car act)
182 (add-file-autoloads
183 (when (equal file (third act))
184 (setq the-act act)))))
185 the-act))
186 ;;;_ . About printing to autoload file
187 ;;;_ , elinstall-insert-section-header
188 (defun elinstall-insert-section-header (outbuf form)
189 "Insert the section-header line,
190 which lists the file name and which functions are in it, etc."
191 (insert generate-autoload-section-header)
192 (prin1 form outbuf)
193 (terpri outbuf)
194 ;; Break that line at spaces, to avoid very long lines.
195 ;; Make each sub-line into a comment.
196 (with-current-buffer outbuf
197 (save-excursion
198 (forward-line -1)
199 (while (not (eolp))
200 (move-to-column 64)
201 (skip-chars-forward "^ \n")
202 (or (eolp)
203 (insert "\n" generate-autoload-section-continuation))))))
205 ;;;_ , elinstall-insert-autoload-section
206 (defun elinstall-insert-autoload-section (text form &optional comment-string)
207 "Insert TEXT into current buffer as an autoload section"
209 (let* ( ;; This does something in Lucid Emacs.
210 (print-length nil)
211 (print-readably t)
212 (float-output-format nil))
214 (elinstall-insert-section-header (current-buffer) form)
215 (when comment-string
216 (insert ";;; " comment-string "\n"))
217 (insert text)
218 (insert generate-autoload-section-trailer)))
220 ;;;_ . Making autoloads
221 ;;;_ , elinstall-make-autoload-action
222 (defun elinstall-make-autoload-action (buf def-file load-path-element full-path)
223 "Return the autoloads for current buffer as a string"
225 ;;We put all the text into a temp buffer, then get that buffer's
226 ;;string.
227 (let
228 ((outbuf
229 (generate-new-buffer " *temp*"))
230 (autoloads-done '())
231 (short-name
232 (file-name-nondirectory full-path))
234 ;; Apparently this does something in Lucid Emacs.
235 (print-length nil)
236 (print-readably t)
237 (float-output-format nil)
239 ;;load-name relative to a member of load-path.
240 (relative-name
241 (file-name-sans-extension
242 (file-relative-name
243 full-path
244 load-path-element))))
246 (with-current-buffer buf
247 (unwind-protect
248 (save-excursion
249 (save-restriction
250 (widen)
251 (goto-char (point-min))
252 (message "Finding autoloads for %s..." short-name)
253 (while (not (eobp))
254 (skip-chars-forward " \t\n\f")
255 (cond
256 ((looking-at (regexp-quote generate-autoload-cookie))
257 (search-forward generate-autoload-cookie)
258 (skip-chars-forward " \t")
259 (setq done-any t)
260 (if (eolp)
261 ;; Read the next form and make an autoload.
262 (let* ((form (prog1 (read (current-buffer))
263 (or (bolp) (forward-line 1))))
264 (autoload
265 (make-autoload form relative-name)))
266 (if autoload
267 (push (nth 1 form) autoloads-done)
268 (setq autoload form))
269 (let ((autoload-print-form-outbuf outbuf))
270 (autoload-print-form autoload)))
272 ;; Copy the rest of the line to the output.
273 (princ (buffer-substring
274 (progn
275 ;; Back up over whitespace,
276 ;; to preserve it.
277 (skip-chars-backward " \f\t")
278 (if (= (char-after (1+ (point))) ? )
279 ;; Eat one space.
280 (forward-char 1))
281 (point))
282 (progn (forward-line 1) (point)))
283 outbuf)))
284 ((looking-at ";")
285 ;; Don't read the comment.
286 (forward-line 1))
288 (forward-sexp 1)
289 (forward-line 1))))
290 (message "Finding autoloads for %s...done" short-name))
292 ;;Return this action. The temp buffer's contents is
293 ;;our final string.
294 `(add-file-autoloads
295 ,def-file
296 ,relative-name
297 ,full-path
298 ,(with-current-buffer outbuf (buffer-string))
299 ,autoloads-done))
301 ;;This in unwind-protected
302 (when (buffer-live-p outbuf) (kill-buffer outbuf))))))
305 ;;;_ , elinstall-generate-file-autoloads
307 (defun elinstall-generate-file-autoloads
308 (relative-name full-name text autoloads-done)
309 "Insert at point a loaddefs autoload section for FILE.
310 Autoloads are generated for defuns and defmacros in FILE
311 marked by `generate-autoload-cookie' (which see).
312 If FILE is being visited in a buffer, the contents of the buffer
313 are used.
314 Return non-nil in the case where no autoloads were added at point.
316 FULL-NAME is the absolute name of the file.
317 RELATIVE-NAME is its name respective to some component of load-path."
318 (if (not (equal text ""))
319 ;; Insert the section-header line which lists the file name and
320 ;; which functions are in it, etc.
321 (elinstall-insert-autoload-section
322 text
323 (list 'autoloads
324 autoloads-done
325 relative-name
326 (autoload-trim-file-name full-name)
327 (elinstall-file-mod-time full-name))
328 (concat
329 "Generated autoloads from "
330 (autoload-trim-file-name full-name)))
333 ;;;_ , elinstall-deffile-insert-autoloads
334 (defun elinstall-deffile-insert-autoloads (file args)
335 "Update the autoloads for FILE in current buffer.
336 Return FILE if there was no autoload cookie in it, else nil.
338 Current buffer must be a loaddef-style file.
340 LOAD-NAME is the absolute name of the file.
341 RELATIVE-NAME is its name respective to some component of load-path."
342 (let (
343 (found nil)
344 (no-autoloads nil))
346 (save-excursion
347 (save-restriction
348 (widen)
349 (goto-char (point-min))
350 ;; Look for the section for FILE
351 (while (and (not found)
352 (search-forward generate-autoload-section-header nil t))
353 (let ((form (autoload-read-section-header)))
354 (cond
355 ((equal (nth 2 form) file)
356 ;; We found the section for this file.
357 (let ((begin (match-beginning 0)))
358 (progn
359 (search-forward generate-autoload-section-trailer)
360 (delete-region begin (point))
361 (setq found t))))
362 ((string< file (nth 2 form))
363 ;; We've come to a section alphabetically later than
364 ;; FILE. We assume the file is in order and so
365 ;; there must be no section for FILE. We will
366 ;; insert one before the section here.
367 (goto-char (match-beginning 0))
368 (setq found 'new)))))
369 (unless found
370 (progn
371 (setq found 'new)
372 ;; No later sections in the file. Put before the last page.
373 (goto-char (point-max))
374 (search-backward "\f" nil t)))
375 (setq no-autoloads
376 (apply #'elinstall-generate-file-autoloads
377 file args))))
379 (if no-autoloads file nil)))
380 ;;;_ . Arranging to add to info-path and load-path
381 ;;;_ , elinstall-generate-add-to-path
382 (defun elinstall-generate-add-to-path (path-element type)
383 "Insert code at point to add PATH-ELEMENT to a path.
384 If TYPE is:
385 * `add-to-load-path', add to load-path
386 * `add-to-info-path', add to Info-additional-directory-list
388 Current buffer must be a loaddef-style file."
389 (let ( (path-symbol
390 (case type
391 (add-to-load-path 'load-path)
392 (add-to-info-path 'Info-additional-directory-list)
393 (t (error "Type not recognized"))))
394 (description
395 (case type
396 (add-to-load-path "load-path")
397 (add-to-info-path "info-path")))
398 (autoloads-done '())
399 (print-length nil)
400 (print-readably t) ; This does something in Lucid Emacs.
401 (float-output-format nil))
403 (message "Generating %s additions..." description)
405 (elinstall-insert-autoload-section
406 (pp-to-string
407 `(add-to-list ',path-symbol
408 (expand-file-name
409 ,(file-relative-name path-element)
410 (if load-file-name
411 (file-name-directory
412 (file-truename load-file-name))))))
413 (list type (list path-element) nil nil nil)
414 nil)
415 (message "Generating %s additions...done" description)))
418 ;;;_ , elinstall-deffile-insert-add-to-path
419 (defun elinstall-deffile-insert-add-to-path (path-element type)
420 "Insert code in current buffer to add PATH-ELEMENT to a path.
421 If TYPE is:
422 * `add-to-load-path', add to load-path
423 * `add-to-info-path', add to Info-default-directory-list
425 Current buffer must be a loaddef-style file."
426 (let (
427 (found nil)
428 (no-autoloads nil))
430 (save-excursion
431 (save-restriction
432 (widen)
433 (goto-char (point-min))
434 ;; Look for the section for PATH-ELEMENT
435 (while (and (not found)
436 (search-forward generate-autoload-section-header nil t))
437 (let ((form (autoload-read-section-header)))
438 (cond
439 ((and
440 (equal (nth 0 form) type)
441 (member path-element (nth 1 form)))
443 ;; We found the section for this add.
444 (let ((begin (match-beginning 0)))
445 (progn
446 (search-forward generate-autoload-section-trailer)
447 (delete-region begin (point))
448 (setq found t)))))))
450 (unless found
451 (progn
452 (setq found 'new)
453 ;; No later sections in the file. Put before the last page.
454 (goto-char (point-max))
455 (search-backward "\f" nil t)))
457 (elinstall-generate-add-to-path path-element type)))
459 ;;This never belongs in the no-autoloads section.
460 nil))
461 ;;;_ . elinstall-deffile-insert
463 (defun elinstall-deffile-insert (action)
464 "Insert autoloads etc into current file according to ACTION.
465 The format of ACTION is described in the design docs.
467 Return filename if this action belongs in the no-autoload section."
469 (when action
470 (case (car action)
471 (add-file-autoloads
472 (elinstall-deffile-insert-autoloads
473 (third action)
474 (nthcdr 3 action)))
476 (add-to-load-path
477 (elinstall-deffile-insert-add-to-path
478 (third action)
479 'add-to-load-path)
480 nil)
482 (add-to-info-path
483 (elinstall-deffile-insert-add-to-path
484 (third action)
485 'add-to-info-path)
486 nil)
488 ((preload-file run-tests byte-compile)
489 (error "This case should not come here.")))))
491 ;;;_ . elinstall-prepare-deffile
492 (defun elinstall-prepare-deffile (deffile)
493 "Try to ensure that DEFFILE is available for receiving autoloads"
495 (autoload-ensure-default-file deffile)
496 (with-current-buffer (find-file-noselect deffile)
499 ;; We must read/write the file without any code conversion,
500 ;; but still decode EOLs.
501 (let ((coding-system-for-read 'raw-text))
503 ;; This is to make generated-autoload-file have Unix EOLs, so
504 ;; that it is portable to all platforms.
505 (setq buffer-file-coding-system 'raw-text-unix))
506 (or (> (buffer-size) 0)
507 (error "Autoloads file %s does not exist" buffer-file-name))
508 (or (file-writable-p buffer-file-name)
509 (error "Autoloads file %s is not writable"
510 buffer-file-name))))
512 ;;;_ . elinstall-update-deffile
514 ;;Adapted from autoload.el `update-directory-autoloads'.
516 (defun elinstall-update-deffile (target actions &optional
517 use-load-path force)
519 Update file TARGET with current autoloads as specified by ACTIONS.
520 Also remove any old definitions pointing to libraries that can no
521 longer be found.
523 ACTIONS must be a list of actions (See the format doc). Each one's
524 filename must be relative to some element of load-path.
526 USE-LOAD-PATH is a list to use as load-path. It should include
527 any new load-path that we are arranging to create. If it's not given,
528 load-path itself is used.
530 If FORCE is `t', do it regardless of timestamps etc. (Not implemented)
531 Other non-nil cases of FORCE are reserved for future development.
533 This uses `update-file-autoloads' (which see) to do its work.
534 In an interactive call, you must give one argument, the name
535 of a single directory."
536 (let
538 (use-load-path (or use-load-path load-path))
539 (this-time (current-time))
540 ;;files with no autoload cookies.
541 (no-autoloads nil))
543 (elinstall-prepare-deffile target)
544 (with-current-buffer
545 (find-file-noselect target)
546 (save-excursion
547 (setq actions
548 (elinstall-remove-autogen-action
549 (autoload-trim-file-name target)
550 actions))
552 (goto-char (point-min))
553 (while (search-forward generate-autoload-section-header nil t)
554 (let* ((form (autoload-read-section-header))
555 (file (nth 3 form)))
556 (cond ((and (consp file) (stringp (car file)))
557 ;; This is a list of files that have no
558 ;; autoload cookies.
559 ;; There shouldn't be more than one such entry.
560 ;; Remove the obsolete section.
561 (autoload-remove-section (match-beginning 0))
562 (let ((last-time (nth 4 form)))
563 (dolist (file file)
564 (let ((file-time (elinstall-file-mod-time file)))
565 (when (and file-time
566 (not (time-less-p last-time file-time)))
567 ;; file unchanged
568 (push file no-autoloads)
569 (setq actions
570 (elinstall-remove-autogen-action
571 file actions)))))))
572 ((not (stringp file)))
574 (let
575 ((file-path
576 (locate-library file nil use-load-path)))
577 (cond
578 ;;File doesn't exist, so remove its
579 ;;section.
580 ((not file-path)
581 (autoload-remove-section
582 (match-beginning 0)))
584 ;; File hasn't changed, so do nothing.
585 ((equal
586 (nth 4 form)
587 (elinstall-file-mod-time file-path))
588 nil)
590 (elinstall-deffile-insert
591 (elinstall-get-autogen-action
592 file actions))))
594 (setq actions
595 (elinstall-remove-autogen-action
596 file actions))))))))
598 ;; Remaining actions have no existing autoload sections yet.
599 (setq no-autoloads
600 (append no-autoloads
601 (delq nil (mapcar #'elinstall-deffile-insert actions))))
602 (when no-autoloads
603 ;; Sort them for better readability.
604 (setq no-autoloads (sort no-autoloads 'string<))
605 ;; Add the `no-autoloads' section.
606 (goto-char (point-max))
607 (search-backward "\f" nil t)
608 (elinstall-insert-autoload-section
610 (list 'autoloads nil nil no-autoloads this-time)))
611 (save-buffer))))
613 ;;;_ . elinstall-stage-update-deffiles
614 (defun elinstall-stage-update-deffiles (segment-list force use-load-path)
615 "Update any deffiles mentioned in SEGMENT-LIST.
616 FORCE and USE-LOAD-PATH have the same meaning as in
617 `elinstall-update-deffile'.
619 (mapcar
620 #'(lambda (segment)
621 (let*
622 ((deffile (car segment)))
623 (if (stringp deffile)
624 (elinstall-update-deffile deffile (cdr segment) force
625 use-load-path))))
626 segment-list))
628 ;;;_ , Doing actions to arrange preloads
629 ;;;_ . elinstall-symlink-on-emacs-start
630 (defun elinstall-symlink-on-emacs-start
631 (filename target-basename target-dir &optional priority force)
632 "Symlink to TARGET-BASENAME.el in TARGET-DIR
634 If PRIORITY is given, it will be used as the priority prefix,
635 otherwise elinstall-default-priority will be.
636 PRIORITY must be an integer or nil.
637 If FORCE is `t', do it regardless of timestamps etc.
638 Other non-nil cases of FORCE are reserved for future development."
639 (let*
641 (priority (or priority elinstall-default-priority))
642 (target-name-nodir
643 (format
644 "%d%s.el"
645 priority
646 target-basename))
647 (target
648 (expand-file-name target-name-nodir target-dir)))
651 (cond
652 ;;Path should already exist.
653 ((not
654 (file-exists-p target-dir))
655 (message "The target directory doesn't exist."))
656 ;;Target shouldn't already exist, but if force is given, let
657 ;;user override.
658 ;;$$IMPROVE ME If it is a symlink pointing to the same place,
659 ;;do nothing even on force.
660 ((and
661 (file-exists-p target)
663 (not force)
664 (not
665 (yes-or-no-p
666 (format "Really overwrite %s? " target))))
667 (message "File %s already exists" target)))
670 (make-symbolic-link
671 filename
672 target
673 nil)))))
675 ;;;_ . elinstall-add-to-dot-emacs
676 (defun elinstall-add-to-dot-emacs (dot-emacs-name filename force &rest r)
677 "Add code to load FILENAME to .emacs.
678 FILENAME should not have an extension"
680 ;;Visit .emacs
681 (with-current-buffer (find-file-noselect dot-emacs-name)
682 (save-excursion
683 ;;add at end of file
684 (goto-char (point-max))
685 (insert "\n;;Added by elinstall")
686 (insert "\n;;Consider using my-site-start to manage .emacs\n")
687 (pp `(load ,filename) (current-buffer))
688 (save-buffer))))
691 ;;;_ . elinstall-arrange-preload
692 ;;;###autoload
693 (defun elinstall-arrange-preload (force filename basename &optional priority)
694 "Arrange for FILENAME to be loaded on emacs start.
695 FORCE has its usual meaning.
696 BASENAME and PRIORITY are used as arguments to
697 `elinstall-symlink-on-emacs-start'.
700 (let
701 ((preload-target elinstall-default-preload-target))
703 ;;Dispatch the possibilities.
704 (cond
705 ((eq preload-target 'dot-emacs)
706 (elinstall-add-to-dot-emacs "~/.emacs" filename force))
707 ((stringp preload-target)
708 (elinstall-symlink-on-emacs-start
709 filename basename preload-target priority force))
710 ((null preload-target)
711 (message "Not arranging for preloads"))
713 (message "I don't recognize that")))))
714 ;;;_ . elinstall-stage-arrange-preloads
715 (defun elinstall-stage-arrange-preloads (actions deffiles-used force)
716 "Arrange any preloads mentioned in ACTIONS."
718 (mapcar
719 #'(lambda (act)
720 (case (car act)
721 (preload-file
722 (let*
723 ( (filename
724 (caddr act))
725 (proceed-p
726 (case (second act)
727 ((t) t)
728 ((nil) nil)
729 (if-used
730 (member filename deffiles-used)))))
732 (when proceed-p
733 (apply
734 #'elinstall-arrange-preload
735 force
736 (cddr act)))))
738 (error
739 "elinstall-stage-arrange-preloads: Action not
740 recognized."))) )
741 actions))
744 ;;;_ , Run tests
745 ;;;_ . elinstall-stage-run-tests
746 (defun elinstall-stage-run-tests (actions)
747 "Run any tests mentioned in ACTIONS."
749 (mapcar
750 #'(lambda (act)
751 (case (car act)
752 (run-tests
753 ;;$$WRITE ME - not a high priority right now.
754 nil)
756 (error
757 "elinstall-stage-run-tests: Action not
758 recognized."))) )
759 actions))
762 ;;;_ , Byte compile
763 ;;;_ . elinstall-stage-byte-compile
764 (defun elinstall-stage-byte-compile (actions)
765 "Do any byte-compilation mentioned in ACTIONS."
767 (mapcar
768 #'(lambda (act)
769 (case (car act)
770 ;;$$IMPROVE ME Understand flags to control second
771 ;;argument (whether to load file after
772 ;;compilation)
773 (byte-compile
774 (byte-compile-file (second act)))
776 (error
777 "elinstall-stage-byte-compile: Action not
778 recognized."))) )
779 actions))
780 ;;;_ . Segregating actions
781 ;;;_ , elinstall-remove-empty-segs
782 (defun elinstall-remove-empty-segs (segment-list)
783 "Return SEGMENT-LIST minus any segments that have no actions.
784 Intended only for the deffile stage data."
785 (delq nil
786 (mapcar
787 #'(lambda (segment)
788 (if (cdr segment)
789 segment
790 nil))
791 segment-list)))
793 ;;;_ , elinstall-segregate-actions
794 (defun elinstall-segregate-actions (actions)
795 "Return actions segregated by deffile.
797 Returns a list whose elements are each a cons of:
798 * deffile filename or nil
799 * A list of actions to be done for that deffile."
801 (let
803 (build-deffiles '())
804 (run-tests '())
805 (byte-compile '())
806 (arrange-preloads '()))
808 (dolist (act actions)
809 (when act
810 (case (car act)
811 ((add-file-autoloads
812 add-to-info-path
813 add-to-load-path)
814 (let*
815 ((deffile-name (second act))
816 (cell-already
817 (assoc deffile-name build-deffiles)))
818 (if cell-already
819 ;;There are already actions on this deffile.
820 ;;Splice this action in.
821 (setcdr cell-already
822 (cons act (cdr cell-already)))
823 ;;There are no actions on this deffile. Add a
824 ;;place for them and include this action.
825 (push (list deffile-name act) build-deffiles))))
826 (preload-file
827 (push act arrange-preloads))
828 (run-tests
829 (push act run-tests))
830 (byte-compile
831 (push act byte-compile)))))
833 (elinstall-make-stages
834 :build-deffiles
835 (elinstall-remove-empty-segs build-deffiles)
836 :run-tests
837 run-tests
838 :byte-compile
839 byte-compile
840 :arrange-preloads
841 arrange-preloads)))
842 ;;;_ . Finding actions
843 ;;;_ , Informational
844 ;;;_ . elinstall-dir-has-info
846 ;;$$IMPROVE ME - Can this test be made more precise?
847 (defun elinstall-dir-has-info (dir)
848 "Return non-nil if DIR has info files in it.
849 DIR should be an absolute path."
851 (string-match "/info/" dir)
852 (directory-files dir nil "\\.info\\(-[0-9]+\\)?\\(\\.gz\\)?$")))
854 ;;;_ , Workers
855 ;;;_ . List of special variables used in this section
856 ;;load-path-element - The relevant element of load-path
857 ;;def-file - The file the autoload definitions etc will go into.
858 ;;add-to-load-path-p - Controls whether to add to load-path.
859 ;;recurse-dirs-p - Whether to recurse into subdirectories.
860 (defconst elinstall-find-actions-control-vars
861 '(add-to-load-path-p recurse-dirs-p compile-p force-recompile-p)
862 "Control special variables that the find-actions tree recognizes" )
863 ;;;_ . elinstall-actions-for-source-file
864 (defun elinstall-actions-for-source-file (filename dir)
865 "Return a list of actions to do for FILENAME in DIR.
866 Special variables are as noted in \"List of special variables\"."
867 (declare (special
868 load-path-element def-file compile-p force-recompile-p))
869 (let
870 ((full-path
871 (expand-file-name filename dir)))
872 (when
873 (and
874 (file-readable-p full-path)
875 (not (auto-save-file-name-p full-path)))
876 ;;$$IMPROVE ME create and use relevant control variables.
877 (let*
879 (visited (get-file-buffer full-path))
880 (buf
881 (or
882 visited
883 ;;Visit the file cheaply.
884 ;;hack-local-variables can give errors.
885 (ignore-errors (autoload-find-file full-path))))
886 (def-file
888 (ignore-errors
889 (with-current-buffer buf
890 (if (local-variable-p 'generated-autoload-file)
891 (elinstall-expand-deffile-name
892 generated-autoload-file)
893 nil)))
894 def-file))
895 ;;Figure out whether to run some actions, by file local vars.
896 (autoloads-p
897 (ignore-errors
898 (with-current-buffer buf
899 (not no-update-autoloads))))
900 (do-compile-p
901 (and
902 (featurep 'byte-compile)
903 (string-match emacs-lisp-file-regexp filename)
904 (ignore-errors
905 (with-current-buffer buf
906 (not no-byte-compile)))
907 (let
908 ((dest (byte-compile-dest-file full-path)))
909 (if (file-exists-p dest)
910 ;; File was already compiled.
911 (or force-recompile-p
912 (file-newer-than-file-p full-path dest))
913 (or compile-p
914 (y-or-n-p (concat "Compile " filename "? "))))))))
916 (prog1
917 (list
918 (if do-compile-p
919 `(byte-compile ,full-path)
920 nil)
921 (if autoloads-p
922 (elinstall-make-autoload-action
923 buf def-file load-path-element full-path)
924 nil))
925 (unless visited (kill-buffer-if-not-modified buf)))))))
926 ;;;_ . elinstall-actions-for-dir
927 (defun elinstall-actions-for-dir (dirname &optional recurse-dirs-p)
928 "Make actions for DIR.
929 Recurse just if RECURSE-DIRS-P"
930 (declare (special
931 load-path-element def-file add-to-load-path-p))
932 ;;This does not treat symlinks specially. $$IMPROVE ME it could
933 ;;treat/not treat them conditional on control variables.
934 (let*
936 ;;Relative filenames of the source files. We know our
937 ;;loaddefs.el isn't really source so remove it. We'd have
938 ;;removed it anyways after seeing file local vars.
940 (elisp-source-files
941 (remove def-file
942 (directory-files
943 dirname
944 nil
945 elinstall-elisp-regexp)))
946 ;;Absolute filenames of subdirectories.
947 ;;Don't accept any directories beginning with dot. If user
948 ;;really wants to explore one he can use `(dir ".NAME")'.
949 (sub-dirs
950 (if recurse-dirs-p
951 (delq nil
952 (mapcar
953 #'(lambda (filename)
955 (file-directory-p filename)
956 filename
957 nil))
958 (directory-files
959 dirname t
960 "[^\\.]")))
961 '()))
963 (load-path-here
964 (and
965 elisp-source-files ;;If list is not empty.
966 add-to-load-path-p))
967 (load-path-element
968 (if load-path-here
969 dirname
970 load-path-element)))
972 (append
973 ;;Sometimes arrange to add this directory to load-path.
974 (if load-path-here
975 `((add-to-load-path
976 ,def-file
977 ,load-path-element))
978 '())
980 ;;$$IMPROVE ME - be controlled by a control variable.
981 ;;Sometimes add this directory to info path.
983 (elinstall-dir-has-info dirname)
984 `((add-to-info-path
985 ,def-file
986 "."))
987 '())
989 (apply #'nconc
990 (mapcar
991 #'(lambda (filename)
992 (elinstall-actions-for-source-file
993 filename
994 dirname))
995 elisp-source-files))
997 (if recurse-dirs-p
998 (apply #'nconc
999 (mapcar
1000 #'(lambda (filename)
1001 (elinstall-find-actions-by-spec-x
1003 (expand-file-name
1004 filename
1005 dirname)))
1006 sub-dirs))
1007 '()))))
1009 ;;;_ . elinstall-find-actions-by-spec-x
1011 (defun elinstall-find-actions-by-spec-x (spec dir)
1012 "Return a list of actions to do, controlled by SPEC."
1013 (declare (special
1014 load-path-element def-file add-to-load-path-p
1015 recurse-dirs-p))
1017 (if (consp spec)
1018 (case (car spec)
1019 (all
1020 ;;(all . SPEC*)
1021 (apply #'nconc
1022 (mapcar
1023 #'(lambda (sub-spec)
1024 (elinstall-find-actions-by-spec-x
1025 sub-spec
1026 dir))
1027 (cdr spec))))
1029 ;;Rather than trying to bind all control variables each time
1030 ;;thru, we use `set' and `unwind-protect'.
1031 (control
1032 ;;control TYPE DISPOSITION SPEC
1033 (let
1034 ((key (second spec))
1035 old-value)
1036 (if (memq key elinstall-find-actions-control-vars)
1037 (unwind-protect
1038 (progn
1039 (set old-value (symbol-value key))
1040 (set key (third spec))
1041 (elinstall-find-actions-by-spec-x
1042 (second spec)
1043 dir))
1044 (set key old-value))
1045 (error "Unrecognized control variable %s" key))))
1048 (def-file
1049 ;;(def-file FN ARGS SPEC)
1050 (let
1051 ((def-file
1052 (expand-file-name
1053 (second spec)
1054 dir))
1055 (for-preload (third spec)))
1056 (assert (listp for-preload))
1057 (append
1058 (list
1060 (and for-preload (car for-preload))
1061 `(preload-file
1062 ,(car for-preload)
1063 ,def-file
1064 ,@(cdr for-preload))
1065 '()))
1067 (elinstall-find-actions-by-spec-x
1068 (fourth spec) dir))))
1070 (dir
1071 ;;(dir FN)
1072 (elinstall-actions-for-dir
1073 (expand-file-name
1074 (second spec)
1075 dir)
1076 nil))
1078 (file
1079 ;;(file FN)
1080 (elinstall-actions-for-source-file
1081 (second spec) dir))
1084 ;;(in FN SPEC)
1085 (let
1086 ((new-dir
1087 (expand-file-name
1088 (second spec)
1089 dir)))
1091 (elinstall-find-actions-by-spec-x
1092 (third spec)
1093 new-dir)))
1095 (load-path
1096 ;;(load-path SPEC)
1097 (append
1098 `((add-to-load-path ,def-file ,dir))
1099 (let
1100 ((load-path-element dir)
1101 (add-to-load-path-p nil))
1102 (elinstall-find-actions-by-spec-x
1103 (second spec)
1104 dir))))
1106 (matching
1107 ;;(matching PATTERN SPEC)
1108 (apply #'nconc
1109 (mapcar
1110 #'(lambda (dir)
1111 (elinstall-find-actions-by-spec-x
1112 (third spec)
1113 dir))
1114 (directory-files
1115 dir t (second spec)))))
1116 (preload
1117 ;;(preload FN SYM &rest ARGS)
1118 (let
1119 ((key (third spec)))
1121 (and (symbolp key) (symbol-value key))
1122 `((preload-file
1124 ,(expand-file-name (second spec) dir)
1125 ,@(nthcdr 3 spec)))
1126 '()))))
1128 ;;Single symbols
1129 (case spec
1130 (dir
1131 (elinstall-actions-for-dir dir nil))
1132 ((t)
1133 (elinstall-actions-for-dir dir t)))))
1135 ;;;_ . elinstall-find-actions-by-spec
1136 (defun elinstall-find-actions-by-spec
1137 (spec load-path-element dir def-file redo-old)
1138 "Find the list of actions to do according to SPEC."
1140 (let
1142 (def-file-time (elinstall-file-mod-time def-file))
1143 (add-to-load-path-p t)
1144 (recurse-dirs-p t)
1145 (force-recompile-p nil)
1146 (compile-p t))
1147 (declare (special
1148 load-path-element def-file add-to-load-path-p
1149 recurse-dirs-p force-recompile-p compile-p))
1151 (elinstall-find-actions-by-spec-x spec dir)))
1153 ;;;_ . high-level work
1154 ;;;_ , elinstall-get-relevant-load-path
1155 (defun elinstall-get-relevant-load-path (actions)
1157 (delq nil
1158 (mapcar
1159 #'(lambda (act)
1160 (case (car act)
1161 (add-to-load-path
1162 (second act))
1163 (t nil)))
1164 actions)))
1165 ;;;_ , elinstall-get-deffile-list
1166 (defun elinstall-get-deffile-list (stages)
1167 "Get a list of deffile names"
1169 (mapcar
1170 #'car
1171 (elinstall-stages->build-deffiles stages)))
1172 ;;;_ , elinstall-x
1173 (defun elinstall-x (dir spec &optional force)
1174 "High-level worker function to install elisp files."
1175 (let*
1177 ;;This is just the default deffile, spec can override it.
1178 (def-file
1179 (elinstall-expand-deffile-name nil))
1180 (actions
1181 (elinstall-find-actions-by-spec
1182 spec
1185 def-file
1186 (eq force 'redo-old)))
1187 (stages (elinstall-segregate-actions actions))
1188 (use-load-path
1189 (elinstall-get-relevant-load-path
1190 actions)))
1192 (elinstall-stage-update-deffiles
1193 (elinstall-stages->build-deffiles stages)
1194 force
1195 use-load-path)
1196 (elinstall-stage-arrange-preloads
1197 (elinstall-stages->arrange-preloads stages)
1198 (elinstall-get-deffile-list stages)
1199 force)
1200 (elinstall-stage-byte-compile
1201 (elinstall-stages->byte-compile stages))
1203 ;;;_ , Entry points
1204 ;;;_ . elinstall
1205 ;;;###autoload
1206 (defun elinstall (project-name path spec &optional force version-string)
1207 "Install elisp files.
1208 They need not be a formal package.
1210 Parameters:
1212 PROJECT-NAME - the name of the project
1214 PATH - Path to the project.
1215 Suggestion: (elinstall-directory-true-name)
1217 SPEC - a spec for the autoloads etc to make. It can be as simple as
1218 \(dir \"\.\") for installing one directory.
1220 If FORCE is t, install a package even if it has already been
1221 installed. Other non-nil cases of FORCE are reserved for future
1222 development."
1224 (when
1226 force
1227 (not (elinstall-already-installed project-name))
1228 (yes-or-no-p (format "Re-install %s? " project-name)))
1229 (elinstall-x
1230 path
1231 `(def-file "loaddefs.el" (if-used ,project-name) ,spec)
1232 force)
1233 (elinstall-record-installed project-name version-string)))
1237 ;;;_ . elinstall-update-directory-autoloads
1239 ;;The control variables and values of `force' that would stop other
1240 ;;actions don't exist yet. Similarly for
1241 ;;`elinstall-update-file-autoloads'.
1242 ;;;###autoload
1243 (defun elinstall-update-directory-autoloads (dir)
1244 "Update autoloads for directory DIR"
1246 (interactive "DUpdate autoloads for all elisp files from directory: ")
1247 (elinstall-x
1249 `(control compile-p nil
1250 (dir "."))))
1252 ;;;_ . elinstall-update-directory
1253 ;;;###autoload
1254 (defun elinstall-update-directory (dir)
1255 "Update autoloads for directory DIR"
1257 (interactive "DInstall all elisp files from directory: ")
1258 (elinstall-x
1260 '(dir ".")))
1262 ;;;_ . elinstall-update-file-autoloads
1263 ;;;###autoload
1264 (defun elinstall-update-file-autoloads (file)
1265 "Update autoloads for elisp file FILE"
1267 (interactive "fUpdate autoloads for elisp file: ")
1268 (elinstall-x
1269 (file-name-directory file)
1270 `(control compile-p nil
1271 (file ,(file-name-nondirectory file)))))
1273 ;;;_ . elinstall-update-file
1274 ;;;###autoload
1275 (defun elinstall-update-file (file)
1276 "Install elisp file FILE"
1278 (interactive "fInstall elisp file: ")
1279 (elinstall-x
1280 (file-name-directory file)
1281 `(file ,(file-name-nondirectory file))))
1283 ;;;_. Footers
1284 ;;;_ , Provides
1286 (provide 'elinstall)
1288 ;;;_ * Local emacs vars.
1289 ;;;_ + Local variables:
1290 ;;;_ + mode: allout
1291 ;;;_ + End:
1293 ;;;_ , End
1294 ;;; elinstall.el ends here