;; autoload.el --- maintain autoloads in loaddefs.el -*- lexical-binding: t -*-
-;; Copyright (C) 1991-1997, 2001-2015 Free Software Foundation, Inc.
+;; Copyright (C) 1991-1997, 2001-2016 Free Software Foundation, Inc.
;; Author: Roland McGrath <roland@gnu.org>
;; Keywords: maint
(require 'lisp-mode) ;for `doc-string-elt' properties.
(require 'lisp-mnt)
-(require 'help-fns) ;for help-add-fundoc-usage.
(eval-when-compile (require 'cl-lib))
(defvar generated-autoload-file nil
(defconst generate-autoload-section-continuation ";;;;;; "
"String to add on each continuation of the section header form.")
+(defvar autoload-timestamps t
+ "Non-nil means insert a timestamp for each input file into the output.
+We use these in incremental updates of the output file to decide
+if we need to rescan an input file. If you set this to nil,
+then we use the timestamp of the output file instead. As a result:
+ - for fixed inputs, the output will be the same every time
+ - incremental updates of the output file might not be correct if:
+ i) the timestamp of the output file cannot be trusted (at least
+ relative to that of the input files)
+ ii) any of the input files can be modified during the time it takes
+ to create the output
+ iii) only a subset of the input files are scanned
+ These issues are unlikely to happen in practice, and would arguably
+ represent bugs in the build system. Item iii) will happen if you
+ use a command like `update-file-autoloads', though, since it only
+ checks a single input file.")
+
(defvar autoload-modified-buffers) ;Dynamically scoped var.
(defun make-autoload (form file &optional expansion)
(enable-local-eval nil))
;; We used to use `raw-text' to read this file, but this causes
;; problems when the file contains non-ASCII characters.
- (find-file-noselect
- (autoload-ensure-default-file (autoload-generated-file)))))
+ (let ((delay-mode-hooks t))
+ (find-file-noselect
+ (autoload-ensure-default-file (autoload-generated-file))))))
(defun autoload-generated-file ()
(expand-file-name generated-autoload-file
(erase-buffer)
(setq buffer-undo-list t
buffer-read-only nil)
- (emacs-lisp-mode)
+ (delay-mode-hooks (emacs-lisp-mode))
(setq default-directory (file-name-directory file))
(insert-file-contents file nil)
(let ((enable-local-variables :safe)
different from OUTFILE, then OUTBUF is ignored.
Return non-nil if and only if FILE adds no autoloads to OUTFILE
-\(or OUTBUF if OUTFILE is nil)."
- (catch 'done
- (let (load-name
- (print-length nil)
- (print-level nil)
- (print-readably t) ; This does something in Lucid Emacs.
- (float-output-format nil)
- (visited (get-file-buffer file))
- (otherbuf nil)
- (absfile (expand-file-name file))
- ;; nil until we found a cookie.
- output-start)
- (with-current-buffer (or visited
- ;; It is faster to avoid visiting the file.
- (autoload-find-file file))
- ;; Obey the no-update-autoloads file local variable.
- (unless no-update-autoloads
- (or noninteractive (message "Generating autoloads for %s..." file))
- (setq load-name
- (if (stringp generated-autoload-load-name)
- generated-autoload-load-name
- (autoload-file-load-name absfile)))
- ;; FIXME? Comparing file-names for equality with just equal
- ;; is fragile, eg if one has an automounter prefix and one
- ;; does not, but both refer to the same physical file.
- (when (and outfile
- (not
- (if (memq system-type '(ms-dos windows-nt))
- (equal (downcase outfile)
- (downcase (autoload-generated-file)))
- (equal outfile (autoload-generated-file)))))
- (setq otherbuf t))
- (save-excursion
- (save-restriction
- (widen)
- (when autoload-builtin-package-versions
- (let ((version (lm-header "version"))
- package)
- (and version
- (setq version (ignore-errors (version-to-list version)))
- (setq package (or (lm-header "package")
- (file-name-sans-extension
- (file-name-nondirectory file))))
- (setq output-start (autoload--setup-output
- otherbuf outbuf absfile load-name))
- (let ((standard-output (marker-buffer output-start))
- (print-quoted t))
- (princ `(push (purecopy
- ',(cons (intern package) version))
- package--builtin-versions))
- (princ "\n")))))
-
- (goto-char (point-min))
- (while (not (eobp))
- (skip-chars-forward " \t\n\f")
- (cond
- ((looking-at (regexp-quote generate-autoload-cookie))
- ;; If not done yet, figure out where to insert this text.
- (unless output-start
- (setq output-start (autoload--setup-output
- otherbuf outbuf absfile load-name)))
- (autoload--print-cookie-text output-start load-name file))
- ((looking-at ";")
- ;; Don't read the comment.
- (forward-line 1))
- (t
- (forward-sexp 1)
- (forward-line 1))))))
-
- (when output-start
- (let ((secondary-autoloads-file-buf
- (if otherbuf (current-buffer))))
- (with-current-buffer (marker-buffer output-start)
- (save-excursion
- ;; Insert the section-header line which lists the file name
- ;; and which functions are in it, etc.
- (goto-char output-start)
- (let ((relfile (file-relative-name absfile)))
- (autoload-insert-section-header
- (marker-buffer output-start)
- () load-name relfile
- (if secondary-autoloads-file-buf
- ;; MD5 checksums are much better because they do not
- ;; change unless the file changes (so they'll be
- ;; equal on two different systems and will change
- ;; less often than time-stamps, thus leading to fewer
- ;; unneeded changes causing spurious conflicts), but
- ;; using time-stamps is a very useful optimization,
- ;; so we use time-stamps for the main autoloads file
- ;; (loaddefs.el) where we have special ways to
- ;; circumvent the "random change problem", and MD5
- ;; checksum in secondary autoload files where we do
- ;; not need the time-stamp optimization because it is
- ;; already provided by the primary autoloads file.
- (md5 secondary-autoloads-file-buf
- ;; We'd really want to just use
- ;; `emacs-internal' instead.
- nil nil 'emacs-mule-unix)
- (nth 5 (file-attributes relfile))))
- (insert ";;; Generated autoloads from " relfile "\n")))
- (insert generate-autoload-section-trailer))))
- (or noninteractive
- (message "Generating autoloads for %s...done" file)))
- (or visited
- ;; We created this buffer, so we should kill it.
- (kill-buffer (current-buffer))))
- (or (not output-start)
- ;; If the entries were added to some other buffer, then the file
- ;; doesn't add entries to OUTFILE.
- otherbuf))))
+\(or OUTBUF if OUTFILE is nil). The actual return value is
+FILE's modification time."
+ ;; Include the file name in any error messages
+ (condition-case err
+ (let (load-name
+ (print-length nil)
+ (print-level nil)
+ (print-readably t) ; This does something in Lucid Emacs.
+ (float-output-format nil)
+ (visited (get-file-buffer file))
+ (otherbuf nil)
+ (absfile (expand-file-name file))
+ ;; nil until we found a cookie.
+ output-start)
+ (when
+ (catch 'done
+ (with-current-buffer (or visited
+ ;; It is faster to avoid visiting the file.
+ (autoload-find-file file))
+ ;; Obey the no-update-autoloads file local variable.
+ (unless no-update-autoloads
+ (or noninteractive (message "Generating autoloads for %s..." file))
+ (setq load-name
+ (if (stringp generated-autoload-load-name)
+ generated-autoload-load-name
+ (autoload-file-load-name absfile)))
+ ;; FIXME? Comparing file-names for equality with just equal
+ ;; is fragile, eg if one has an automounter prefix and one
+ ;; does not, but both refer to the same physical file.
+ (when (and outfile
+ (not
+ (if (memq system-type '(ms-dos windows-nt))
+ (equal (downcase outfile)
+ (downcase (autoload-generated-file)))
+ (equal outfile (autoload-generated-file)))))
+ (setq otherbuf t))
+ (save-excursion
+ (save-restriction
+ (widen)
+ (when autoload-builtin-package-versions
+ (let ((version (lm-header "version"))
+ package)
+ (and version
+ (setq version (ignore-errors (version-to-list version)))
+ (setq package (or (lm-header "package")
+ (file-name-sans-extension
+ (file-name-nondirectory file))))
+ (setq output-start (autoload--setup-output
+ otherbuf outbuf absfile load-name))
+ (let ((standard-output (marker-buffer output-start))
+ (print-quoted t))
+ (princ `(push (purecopy
+ ',(cons (intern package) version))
+ package--builtin-versions))
+ (princ "\n")))))
+
+ (goto-char (point-min))
+ (while (not (eobp))
+ (skip-chars-forward " \t\n\f")
+ (cond
+ ((looking-at (regexp-quote generate-autoload-cookie))
+ ;; If not done yet, figure out where to insert this text.
+ (unless output-start
+ (setq output-start (autoload--setup-output
+ otherbuf outbuf absfile load-name)))
+ (autoload--print-cookie-text output-start load-name file))
+ ((looking-at ";")
+ ;; Don't read the comment.
+ (forward-line 1))
+ (t
+ (forward-sexp 1)
+ (forward-line 1))))))
+
+ (when output-start
+ (let ((secondary-autoloads-file-buf
+ (if otherbuf (current-buffer))))
+ (with-current-buffer (marker-buffer output-start)
+ (save-excursion
+ ;; Insert the section-header line which lists the file name
+ ;; and which functions are in it, etc.
+ (goto-char output-start)
+ (let ((relfile (file-relative-name absfile)))
+ (autoload-insert-section-header
+ (marker-buffer output-start)
+ () load-name relfile
+ (if secondary-autoloads-file-buf
+ ;; MD5 checksums are much better because they do not
+ ;; change unless the file changes (so they'll be
+ ;; equal on two different systems and will change
+ ;; less often than time-stamps, thus leading to fewer
+ ;; unneeded changes causing spurious conflicts), but
+ ;; using time-stamps is a very useful optimization,
+ ;; so we use time-stamps for the main autoloads file
+ ;; (loaddefs.el) where we have special ways to
+ ;; circumvent the "random change problem", and MD5
+ ;; checksum in secondary autoload files where we do
+ ;; not need the time-stamp optimization because it is
+ ;; already provided by the primary autoloads file.
+ (md5 secondary-autoloads-file-buf
+ ;; We'd really want to just use
+ ;; `emacs-internal' instead.
+ nil nil 'emacs-mule-unix)
+ (if autoload-timestamps
+ (nth 5 (file-attributes relfile))
+ t)))
+ (insert ";;; Generated autoloads from " relfile "\n")))
+ (insert generate-autoload-section-trailer))))
+ (or noninteractive
+ (message "Generating autoloads for %s...done" file)))
+ (or visited
+ ;; We created this buffer, so we should kill it.
+ (kill-buffer (current-buffer))))
+ (or (not output-start)
+ ;; If the entries were added to some other buffer, then the file
+ ;; doesn't add entries to OUTFILE.
+ otherbuf))
+ (nth 5 (file-attributes absfile))))
+ (error
+ ;; Probably unbalanced parens in forward-sexp. In that case, the
+ ;; condition is scan-error, and the signal data includes point
+ ;; where the error was found; we'd like to convert that to
+ ;; line:col, but line-number-at-pos gets the wrong line in batch
+ ;; mode for some reason.
+ ;;
+ ;; At least this gets the file name in the error message; the
+ ;; developer can use goto-char to get to the error position.
+ (error "%s:0:0: error: %s: %s" file (car err) (cdr err)))
+ ))
\f
(defun autoload-save-buffers ()
(while autoload-modified-buffers
(defun autoload-find-destination (file load-name)
"Find the destination point of the current buffer's autoloads.
FILE is the file name of the current buffer.
+LOAD-NAME is the name as it appears in the output.
Returns a buffer whose point is placed at the requested location.
-Returns nil if the file's autoloads are uptodate, otherwise
+Returns nil if the file's autoloads are up-to-date, otherwise
removes any prior now out-of-date autoload entries."
(catch 'up-to-date
(let* ((buf (current-buffer))
(existing-buffer (if buffer-file-name buf))
+ (output-file (autoload-generated-file))
+ (output-time (if (file-exists-p output-file)
+ (nth 5 (file-attributes output-file))))
(found nil))
(with-current-buffer (autoload-find-generated-file)
;; This is to make generated-autoload-file have Unix EOLs, so
(file-time (nth 5 (file-attributes file))))
(if (and (or (null existing-buffer)
(not (buffer-modified-p existing-buffer)))
- (or
+ (cond
;; last-time is the time-stamp (specifying
;; the last time we looked at the file) and
;; the file hasn't been changed since.
- (and (listp last-time) (= (length last-time) 2)
- (not (time-less-p last-time file-time)))
+ ((listp last-time) (= (length last-time) 2)
+ (not (time-less-p last-time file-time)))
+ ;; FIXME? Arguably we should throw a
+ ;; user error, or some kind of warning,
+ ;; if we were called from update-file-autoloads,
+ ;; which can update only a single input file.
+ ;; It's not appropriate to use the output
+ ;; file modtime in such a case,
+ ;; if there are multiple input files
+ ;; contributing to the output.
+ ((and output-time (eq t last-time))
+ (not (time-less-p output-time file-time)))
;; last-time is an MD5 checksum instead.
- (and (stringp last-time)
- (equal last-time
- (md5 buf nil nil 'emacs-mule)))))
+ ((stringp last-time)
+ (equal last-time
+ (md5 buf nil nil 'emacs-mule)))))
(throw 'up-to-date nil)
(autoload-remove-section begin)
(setq found t))))
t files-re))
dirs)))
(done ())
- (this-time (current-time))
+ (last-time)
;; Files with no autoload cookies or whose autoloads go to other
;; files because of file-local autoload-generated-file settings.
(no-autoloads nil)
(generated-autoload-file
(if (called-interactively-p 'interactive)
(read-file-name "Write autoload definitions to file: ")
- generated-autoload-file)))
+ generated-autoload-file))
+ (output-time
+ (if (file-exists-p generated-autoload-file)
+ (nth 5 (file-attributes generated-autoload-file)))))
(with-current-buffer (autoload-find-generated-file)
(save-excursion
;; There shouldn't be more than one such entry.
;; Remove the obsolete section.
(autoload-remove-section (match-beginning 0))
- (let ((last-time (nth 4 form)))
- (dolist (file file)
- (let ((file-time (nth 5 (file-attributes file))))
- (when (and file-time
- (not (time-less-p last-time file-time)))
- ;; file unchanged
- (push file no-autoloads)
- (setq files (delete file files)))))))
+ (setq last-time (nth 4 form))
+ (if (equal t last-time)
+ (setq last-time output-time))
+ (dolist (file file)
+ (let ((file-time (nth 5 (file-attributes file))))
+ (when (and file-time
+ (not (time-less-p last-time file-time)))
+ ;; file unchanged
+ (push file no-autoloads)
+ (setq files (delete file files))))))
((not (stringp file)))
((or (not (file-exists-p file))
;; Remove duplicates as well, just in case.
(member (expand-file-name file) autoload-excludes))
;; Remove the obsolete section.
(autoload-remove-section (match-beginning 0)))
- ((not (time-less-p (nth 4 form)
+ ((not (time-less-p (let ((oldtime (nth 4 form)))
+ (if (equal t oldtime)
+ output-time
+ oldtime))
(nth 5 (file-attributes file))))
;; File hasn't changed.
nil)
(push file done)
(setq files (delete file files)))))
;; Elements remaining in FILES have no existing autoload sections yet.
- (dolist (file files)
- (cond
- ((member (expand-file-name file) autoload-excludes) nil)
- ;; Passing nil as second argument forces
- ;; autoload-generate-file-autoloads to look for the right
- ;; spot where to insert each autoloads section.
- ((autoload-generate-file-autoloads file nil buffer-file-name)
- (push file no-autoloads))))
-
- (when no-autoloads
- ;; Sort them for better readability.
- (setq no-autoloads (sort no-autoloads 'string<))
- ;; Add the `no-autoloads' section.
- (goto-char (point-max))
- (search-backward "\f" nil t)
- (autoload-insert-section-header
- (current-buffer) nil nil no-autoloads this-time)
- (insert generate-autoload-section-trailer))
+ (let ((no-autoloads-time (or last-time '(0 0 0 0))) file-time)
+ (dolist (file files)
+ (cond
+ ((member (expand-file-name file) autoload-excludes) nil)
+ ;; Passing nil as second argument forces
+ ;; autoload-generate-file-autoloads to look for the right
+ ;; spot where to insert each autoloads section.
+ ((setq file-time
+ (autoload-generate-file-autoloads file nil buffer-file-name))
+ (push file no-autoloads)
+ (if (time-less-p no-autoloads-time file-time)
+ (setq no-autoloads-time file-time)))))
+
+ (when no-autoloads
+ ;; Sort them for better readability.
+ (setq no-autoloads (sort no-autoloads 'string<))
+ ;; Add the `no-autoloads' section.
+ (goto-char (point-max))
+ (search-backward "\f" nil t)
+ (autoload-insert-section-header
+ (current-buffer) nil nil no-autoloads (if autoload-timestamps
+ no-autoloads-time
+ t))
+ (insert generate-autoload-section-trailer)))
(let ((version-control 'never))
(save-buffer))