From 3b9795200fbc11c5b136b62b9ac1a70b2565da18 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Sat, 23 Jun 2007 20:31:33 +0000 Subject: (autoload-generated-file): New function. (update-file-autoloads, update-directory-autoloads): Use it. (autoload-file-load-name): New function. (generate-file-autoloads, update-file-autoloads): Use it. (autoload-find-file): Accept non-absolute argument. Set default-dir. (generate-file-autoloads): If the autoloaded form is malformed, indicate the problem with a warning instead of aborting. --- lisp/emacs-lisp/autoload.el | 59 +++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 26 deletions(-) (limited to 'lisp/emacs-lisp/autoload.el') diff --git a/lisp/emacs-lisp/autoload.el b/lisp/emacs-lisp/autoload.el index 5e37e275632..47d167eeecc 100644 --- a/lisp/emacs-lisp/autoload.el +++ b/lisp/emacs-lisp/autoload.el @@ -41,15 +41,18 @@ A `.el' file can set this in its local variables section to make its autoloads go somewhere else. The autoload file is assumed to contain a trailer starting with a FormFeed character.") +(put 'generated-autoload-file 'safe-local-variable 'stringp) -(defconst generate-autoload-cookie ";;;###autoload" +;; This feels like it should be a defconst, but MH-E sets it to +;; ";;;###mh-autoload" for the autoloads that are to go into mh-loaddefs.el. +(defvar generate-autoload-cookie ";;;###autoload" "Magic comment indicating the following form should be autoloaded. Used by \\[update-file-autoloads]. This string should be meaningless to Lisp (e.g., a comment). This string is used: -;;;###autoload +\;;;###autoload \(defun function-to-be-autoloaded () ...) If this string appears alone on a line, the following form will be @@ -149,6 +152,10 @@ or macro definition or a defcustom)." ;; the doc-string in FORM. ;; Those properties are now set in lisp-mode.el. +(defun autoload-generated-file () + (expand-file-name generated-autoload-file + (expand-file-name "lisp" + source-directory))) (defun autoload-trim-file-name (file) ;; Returns a relative file path for FILE @@ -272,12 +279,14 @@ which lists the file name and which functions are in it, etc." (defun autoload-find-file (file) "Fetch file and put it in a temp buffer. Return the buffer." ;; It is faster to avoid visiting the file. + (setq file (expand-file-name file)) (with-current-buffer (get-buffer-create " *autoload-file*") (kill-all-local-variables) (erase-buffer) (setq buffer-undo-list t buffer-read-only nil) (emacs-lisp-mode) + (setq default-directory (file-name-directory file)) (insert-file-contents file nil) (let ((enable-local-variables :safe)) (hack-local-variables)) @@ -286,6 +295,12 @@ which lists the file name and which functions are in it, etc." (defvar no-update-autoloads nil "File local variable to prevent scanning this file for autoload cookies.") +(defun autoload-file-load-name (file) + (let ((name (file-name-nondirectory file))) + (if (string-match "\\.elc?\\(\\.\\|\\'\\)" name) + (substring name 0 (match-beginning 0)) + name))) + (defun generate-file-autoloads (file) "Insert at point a loaddefs autoload section for FILE. Autoloads are generated for defuns and defmacros in FILE @@ -296,10 +311,7 @@ Return non-nil in the case where no autoloads were added at point." (interactive "fGenerate autoloads for file: ") (let ((outbuf (current-buffer)) (autoloads-done '()) - (load-name (let ((name (file-name-nondirectory file))) - (if (string-match "\\.elc?\\(\\.\\|$\\)" name) - (substring name 0 (match-beginning 0)) - name))) + (load-name (autoload-file-load-name file)) (print-length nil) (print-readably t) ; This does something in Lucid Emacs. (float-output-format nil) @@ -342,15 +354,18 @@ Return non-nil in the case where no autoloads were added at point." (skip-chars-forward " \t") (setq done-any t) (if (eolp) - ;; Read the next form and make an autoload. - (let* ((form (prog1 (read (current-buffer)) - (or (bolp) (forward-line 1)))) - (autoload (make-autoload form load-name))) - (if autoload - (push (nth 1 form) autoloads-done) - (setq autoload form)) - (let ((autoload-print-form-outbuf outbuf)) - (autoload-print-form autoload))) + (condition-case err + ;; Read the next form and make an autoload. + (let* ((form (prog1 (read (current-buffer)) + (or (bolp) (forward-line 1)))) + (autoload (make-autoload form load-name))) + (if autoload + (push (nth 1 form) autoloads-done) + (setq autoload form)) + (let ((autoload-print-form-outbuf outbuf)) + (autoload-print-form autoload))) + (error + (message "Error in %s: %S" file err))) ;; Copy the rest of the line to the output. (princ (buffer-substring @@ -397,10 +412,7 @@ save the buffer too. Return FILE if there was no autoload cookie in it, else nil." (interactive "fUpdate autoloads for file: \np") - (let ((load-name (let ((name (file-name-nondirectory file))) - (if (string-match "\\.elc?\\(\\.\\|$\\)" name) - (substring name 0 (match-beginning 0)) - name))) + (let ((load-name (autoload-file-load-name file)) (found nil) (existing-buffer (get-file-buffer file)) (no-autoloads nil)) @@ -413,10 +425,7 @@ Return FILE if there was no autoload cookie in it, else nil." ;; but still decode EOLs. (let ((coding-system-for-read 'raw-text)) (set-buffer (find-file-noselect - (autoload-ensure-default-file - (expand-file-name generated-autoload-file - (expand-file-name "lisp" - source-directory))))) + (autoload-ensure-default-file (autoload-generated-file)))) ;; This is to make generated-autoload-file have Unix EOLs, so ;; that it is portable to all platforms. (setq buffer-file-coding-system 'raw-text-unix)) @@ -500,9 +509,7 @@ directory or directories specified." dirs))) (this-time (current-time)) (no-autoloads nil) ;files with no autoload cookies. - (autoloads-file - (expand-file-name generated-autoload-file - (expand-file-name "lisp" source-directory))) + (autoloads-file (autoload-generated-file)) (top-dir (file-name-directory autoloads-file))) (with-current-buffer -- cgit v1.2.1 From 57536a837158dec3f2fa3635faa59c3fea0cd166 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Mon, 25 Jun 2007 03:01:22 +0000 Subject: Refactor for upcoming changes. (autoload-find-destination): New function extracted from update-file-autoloads. (update-file-autoloads): Use it. --- lisp/emacs-lisp/autoload.el | 124 +++++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 58 deletions(-) (limited to 'lisp/emacs-lisp/autoload.el') diff --git a/lisp/emacs-lisp/autoload.el b/lisp/emacs-lisp/autoload.el index 47d167eeecc..eb2de503d54 100644 --- a/lisp/emacs-lisp/autoload.el +++ b/lisp/emacs-lisp/autoload.el @@ -412,74 +412,82 @@ save the buffer too. Return FILE if there was no autoload cookie in it, else nil." (interactive "fUpdate autoloads for file: \np") + (let ((existing-buffer (get-file-buffer file))) + (with-temp-buffer + ;; Let's presume the file is not visited, so we call + ;; autoload-find-destination from a dummy buffer, except if the file + ;; is visited, in which case we use that buffer instead. + (if existing-buffer (set-buffer existing-buffer)) + + (catch 'up-to-date + (let ((buf (autoload-find-destination file))) + (with-current-buffer buf + (let ((no-autoloads (generate-file-autoloads file))) + + (and save-after + (buffer-modified-p) + (save-buffer)) + + (if no-autoloads file)))))))) + +(defun autoload-find-destination (file) + "Find the destination point of the current buffer's autoloads. +FILE is the file name of the current buffer. +Returns a buffer whose point is placed at the requested location. +Throws `up-to-date' if the file's autoloads are uptodate, otherwise +removes any prior now out-of-date autoload entries. +The current buffer only matters if it is visiting a file or if it has a buffer-local +value for some variables such as `generated-autoload-file', so it's OK +to call it from a dummy buffer if FILE is not currently visited." + ;; (message "autoload-find-destination %S" file) (let ((load-name (autoload-file-load-name file)) - (found nil) - (existing-buffer (get-file-buffer file)) - (no-autoloads nil)) - (save-excursion - ;; We want to get a value for generated-autoload-file from - ;; the local variables section if it's there. - (if existing-buffer - (set-buffer existing-buffer)) - ;; We must read/write the file without any code conversion, - ;; but still decode EOLs. - (let ((coding-system-for-read 'raw-text)) - (set-buffer (find-file-noselect - (autoload-ensure-default-file (autoload-generated-file)))) - ;; This is to make generated-autoload-file have Unix EOLs, so - ;; that it is portable to all platforms. - (setq buffer-file-coding-system 'raw-text-unix)) + (existing-buffer (if buffer-file-name (current-buffer))) + (found nil)) + (with-current-buffer + ;; We must read/write the file without any code conversion, + ;; but still decode EOLs. + (let ((coding-system-for-read 'raw-text)) + (find-file-noselect + (autoload-ensure-default-file (autoload-generated-file)))) + ;; This is to make generated-autoload-file have Unix EOLs, so + ;; that it is portable to all platforms. + (setq buffer-file-coding-system 'raw-text-unix) (or (> (buffer-size) 0) (error "Autoloads file %s does not exist" buffer-file-name)) (or (file-writable-p buffer-file-name) (error "Autoloads file %s is not writable" buffer-file-name)) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - ;; Look for the section for LOAD-NAME. - (while (and (not found) - (search-forward generate-autoload-section-header nil t)) - (let ((form (autoload-read-section-header))) - (cond ((string= (nth 2 form) load-name) - ;; We found the section for this file. - ;; Check if it is up to date. - (let ((begin (match-beginning 0)) - (last-time (nth 4 form)) + (widen) + (goto-char (point-min)) + ;; Look for the section for LOAD-NAME. + (while (and (not found) + (search-forward generate-autoload-section-header nil t)) + (let ((form (autoload-read-section-header))) + (cond ((string= (nth 2 form) load-name) + ;; We found the section for this file. + ;; Check if it is up to date. + (let ((begin (match-beginning 0)) + (last-time (nth 4 form)) (file-time (nth 5 (file-attributes file)))) (if (and (or (null existing-buffer) (not (buffer-modified-p existing-buffer))) (listp last-time) (= (length last-time) 2) (not (time-less-p last-time file-time))) - (progn - (if (interactive-p) - (message "\ -Autoload section for %s is up to date." - file)) - (setq found 'up-to-date)) - (search-forward generate-autoload-section-trailer) - (delete-region begin (point)) - (setq found t)))) - ((string< load-name (nth 2 form)) - ;; We've come to a section alphabetically later than - ;; LOAD-NAME. We assume the file is in order and so - ;; there must be no section for LOAD-NAME. We will - ;; insert one before the section here. - (goto-char (match-beginning 0)) - (setq found 'new))))) - (or found - (progn - (setq found 'new) - ;; No later sections in the file. Put before the last page. - (goto-char (point-max)) - (search-backward "\f" nil t))) - (or (eq found 'up-to-date) - (setq no-autoloads (generate-file-autoloads file))))) - (and save-after - (buffer-modified-p) - (save-buffer)) - - (if no-autoloads file)))) + (throw 'up-to-date nil) + (autoload-remove-section (match-beginning 0)) + (setq found t)))) + ((string< load-name (nth 2 form)) + ;; We've come to a section alphabetically later than + ;; LOAD-NAME. We assume the file is in order and so + ;; there must be no section for LOAD-NAME. We will + ;; insert one before the section here. + (goto-char (match-beginning 0)) + (setq found t))))) + (or found + (progn + ;; No later sections in the file. Put before the last page. + (goto-char (point-max)) + (search-backward "\f" nil t))) + (current-buffer)))) (defun autoload-remove-section (begin) (goto-char begin) -- cgit v1.2.1 From ceea9b1803ec19805fd887c58eff545ae102b01c Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Mon, 25 Jun 2007 03:48:10 +0000 Subject: Refactor for upcoming changes. (autoload-generate-file-autoloads): New function extracted from generate-file-autoloads. Use file-relative-name. Delay computation of output-start to the first cookie. Remove done-any, replaced by output-start. (generate-file-autoloads): Use it. (autoload-find-destination): Make use of `begin' var. --- lisp/emacs-lisp/autoload.el | 59 ++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 30 deletions(-) (limited to 'lisp/emacs-lisp/autoload.el') diff --git a/lisp/emacs-lisp/autoload.el b/lisp/emacs-lisp/autoload.el index eb2de503d54..01abb242ea8 100644 --- a/lisp/emacs-lisp/autoload.el +++ b/lisp/emacs-lisp/autoload.el @@ -309,39 +309,32 @@ If FILE is being visited in a buffer, the contents of the buffer are used. Return non-nil in the case where no autoloads were added at point." (interactive "fGenerate autoloads for file: ") - (let ((outbuf (current-buffer)) - (autoloads-done '()) + (autoload-generate-file-autoloads file (current-buffer))) + +(defun autoload-generate-file-autoloads (file outbuf) + "Insert an autoload section for FILE in the appropriate buffer. +Autoloads are generated for defuns and defmacros in FILE +marked by `generate-autoload-cookie' (which see). +If FILE is being visited in a buffer, the contents of the buffer are used. +OUTBUF is the buffer in which the autoload statements will be inserted. +Return non-nil in the case where no autoloads were added in the buffer." + (let ((autoloads-done '()) (load-name (autoload-file-load-name file)) (print-length nil) (print-readably t) ; This does something in Lucid Emacs. (float-output-format nil) - (done-any nil) (visited (get-file-buffer file)) + (absfile (expand-file-name file)) + relfile + ;; nil until we found a cookie. output-start) - ;; If the autoload section we create here uses an absolute - ;; file name for FILE in its header, and then Emacs is installed - ;; under a different path on another system, - ;; `update-autoloads-here' won't be able to find the files to be - ;; autoloaded. So, if FILE is in the same directory or a - ;; subdirectory of the current buffer's directory, we'll make it - ;; relative to the current buffer's directory. - (setq file (expand-file-name file)) - (let* ((source-truename (file-truename file)) - (dir-truename (file-name-as-directory - (file-truename default-directory))) - (len (length dir-truename))) - (if (and (< len (length source-truename)) - (string= dir-truename (substring source-truename 0 len))) - (setq file (substring source-truename len)))) - (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 (message "Generating autoloads for %s..." file) - (setq output-start (with-current-buffer outbuf (point))) (save-excursion (save-restriction (widen) @@ -350,9 +343,16 @@ Return non-nil in the case where no autoloads were added at point." (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 + (with-current-buffer outbuf + (setq relfile (file-relative-name absfile)) + (setq output-start (point))) + ;; (message "file=%S, relfile=%S, dest=%S" + ;; file relfile (autoload-generated-file)) + ) (search-forward generate-autoload-cookie) (skip-chars-forward " \t") - (setq done-any t) (if (eolp) (condition-case err ;; Read the next form and make an autoload. @@ -385,23 +385,22 @@ Return non-nil in the case where no autoloads were added at point." (forward-sexp 1) (forward-line 1)))))) - (when done-any + (when output-start (with-current-buffer outbuf (save-excursion ;; Insert the section-header line which lists the file name ;; and which functions are in it, etc. (goto-char output-start) (autoload-insert-section-header - outbuf autoloads-done load-name file - (nth 5 (file-attributes file))) - (insert ";;; Generated autoloads from " - (autoload-trim-file-name file) "\n")) + outbuf autoloads-done load-name relfile + (nth 5 (file-attributes relfile))) + (insert ";;; Generated autoloads from " relfile "\n")) (insert generate-autoload-section-trailer))) (message "Generating autoloads for %s...done" file)) (or visited ;; We created this buffer, so we should kill it. (kill-buffer (current-buffer)))) - (not done-any))) + (not output-start))) ;;;###autoload (defun update-file-autoloads (file &optional save-after) @@ -467,13 +466,13 @@ to call it from a dummy buffer if FILE is not currently visited." ;; Check if it is up to date. (let ((begin (match-beginning 0)) (last-time (nth 4 form)) - (file-time (nth 5 (file-attributes file)))) - (if (and (or (null existing-buffer) + (file-time (nth 5 (file-attributes file)))) + (if (and (or (null existing-buffer) (not (buffer-modified-p existing-buffer))) (listp last-time) (= (length last-time) 2) (not (time-less-p last-time file-time))) (throw 'up-to-date nil) - (autoload-remove-section (match-beginning 0)) + (autoload-remove-section begin) (setq found t)))) ((string< load-name (nth 2 form)) ;; We've come to a section alphabetically later than -- cgit v1.2.1 From e66466a6fb52a1f661842325fdc03110e667288b Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Mon, 25 Jun 2007 05:09:05 +0000 Subject: (autoload-modified-buffers): New var. (autoload-find-destination): Keep it uptodate. (autoload-save-buffers): New fun. (update-file-autoloads): Use it. Re-add the "up to date" message. --- lisp/emacs-lisp/autoload.el | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) (limited to 'lisp/emacs-lisp/autoload.el') diff --git a/lisp/emacs-lisp/autoload.el b/lisp/emacs-lisp/autoload.el index 01abb242ea8..970df447548 100644 --- a/lisp/emacs-lisp/autoload.el +++ b/lisp/emacs-lisp/autoload.el @@ -402,6 +402,13 @@ Return non-nil in the case where no autoloads were added in the buffer." (kill-buffer (current-buffer)))) (not output-start))) +(defvar autoload-modified-buffers nil) + +(defun autoload-save-buffers () + (while autoload-modified-buffers + (with-current-buffer (pop autoload-modified-buffers) + (save-buffer)))) + ;;;###autoload (defun update-file-autoloads (file &optional save-after) "Update the autoloads for FILE in `generated-autoload-file' @@ -411,23 +418,24 @@ save the buffer too. Return FILE if there was no autoload cookie in it, else nil." (interactive "fUpdate autoloads for file: \np") - (let ((existing-buffer (get-file-buffer file))) + (let ((existing-buffer (get-file-buffer file)) + (no-autoloads nil)) (with-temp-buffer ;; Let's presume the file is not visited, so we call ;; autoload-find-destination from a dummy buffer, except if the file ;; is visited, in which case we use that buffer instead. (if existing-buffer (set-buffer existing-buffer)) - (catch 'up-to-date - (let ((buf (autoload-find-destination file))) - (with-current-buffer buf - (let ((no-autoloads (generate-file-autoloads file))) - - (and save-after - (buffer-modified-p) - (save-buffer)) - - (if no-autoloads file)))))))) + (if (catch 'up-to-date + (with-current-buffer (autoload-find-destination file) + (setq no-autoloads (generate-file-autoloads file)) + t)) + (if save-after (autoload-save-buffers)) + (if (interactive-p) + (message "Autoload section for %s is up to date." file)))) + ;; If we caught `up-to-date', it means there are autoload entries, since + ;; otherwise we wouldn't have detected their up-to-dateness. + (if no-autoloads file))) (defun autoload-find-destination (file) "Find the destination point of the current buffer's autoloads. @@ -486,6 +494,8 @@ to call it from a dummy buffer if FILE is not currently visited." ;; No later sections in the file. Put before the last page. (goto-char (point-max)) (search-backward "\f" nil t))) + (unless (memq (current-buffer) autoload-modified-buffers) + (push (current-buffer) autoload-modified-buffers)) (current-buffer)))) (defun autoload-remove-section (begin) -- cgit v1.2.1 From 986c5ad54880307e4e220ffe2b462719fc36c794 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Mon, 25 Jun 2007 16:19:05 +0000 Subject: (autoload-generate-file-autoloads): Make `outbuf' optional. (update-file-autoloads): Use it. --- lisp/emacs-lisp/autoload.el | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) (limited to 'lisp/emacs-lisp/autoload.el') diff --git a/lisp/emacs-lisp/autoload.el b/lisp/emacs-lisp/autoload.el index 970df447548..2861aedef3e 100644 --- a/lisp/emacs-lisp/autoload.el +++ b/lisp/emacs-lisp/autoload.el @@ -311,13 +311,17 @@ Return non-nil in the case where no autoloads were added at point." (interactive "fGenerate autoloads for file: ") (autoload-generate-file-autoloads file (current-buffer))) -(defun autoload-generate-file-autoloads (file outbuf) +(defun autoload-generate-file-autoloads (file &optional outbuf) "Insert an autoload section for FILE in the appropriate buffer. Autoloads are generated for defuns and defmacros in FILE marked by `generate-autoload-cookie' (which see). If FILE is being visited in a buffer, the contents of the buffer are used. OUTBUF is the buffer in which the autoload statements will be inserted. -Return non-nil in the case where no autoloads were added in the buffer." +If OUTBUF is nil, it will be determined by `autoload-generated-file'. +Return non-nil in the case where no autoloads were added to OUTBUF. + +Can throw `up-to-date' to mean that the entries were found already and are +up-to-date. Of course, this can only be the case if OUTBUF is not used." (let ((autoloads-done '()) (load-name (autoload-file-load-name file)) (print-length nil) @@ -345,6 +349,8 @@ Return non-nil in the case where no autoloads were added in the buffer." ((looking-at (regexp-quote generate-autoload-cookie)) ;; If not done yet, figure out where to insert this text. (unless output-start + (unless outbuf + (setq outbuf (autoload-find-destination absfile))) (with-current-buffer outbuf (setq relfile (file-relative-name absfile)) (setq output-start (point))) @@ -418,21 +424,14 @@ save the buffer too. Return FILE if there was no autoload cookie in it, else nil." (interactive "fUpdate autoloads for file: \np") - (let ((existing-buffer (get-file-buffer file)) - (no-autoloads nil)) - (with-temp-buffer - ;; Let's presume the file is not visited, so we call - ;; autoload-find-destination from a dummy buffer, except if the file - ;; is visited, in which case we use that buffer instead. - (if existing-buffer (set-buffer existing-buffer)) - - (if (catch 'up-to-date - (with-current-buffer (autoload-find-destination file) - (setq no-autoloads (generate-file-autoloads file)) - t)) - (if save-after (autoload-save-buffers)) - (if (interactive-p) - (message "Autoload section for %s is up to date." file)))) + (let ((no-autoloads nil)) + (if (catch 'up-to-date + (progn + (setq no-autoloads (autoload-generate-file-autoloads file)) + t)) + (if save-after (autoload-save-buffers)) + (if (interactive-p) + (message "Autoload section for %s is up to date." file))) ;; If we caught `up-to-date', it means there are autoload entries, since ;; otherwise we wouldn't have detected their up-to-dateness. (if no-autoloads file))) -- cgit v1.2.1 From 1fad2b12bad86310146e4e579501407ab55819f5 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Tue, 26 Jun 2007 19:07:14 +0000 Subject: (autoload-find-destination): Return nil rather than throwing `up-to-date'. (autoload-generate-file-autoloads): Adjust correspondingly. (update-file-autoloads): Be careful to let-bind autoload-modified-buffers and adjust to new calling conventions. (autoload-modified-buffers): Make it a dynamically scoped var. (update-directory-autoloads): Use file-relative-name instead of autoload-trim-file-name. (autoload-insert-section-header): Don't use autoload-trim-file-name since the file is already relative now. (autoload-trim-file-name): Remove. --- lisp/emacs-lisp/autoload.el | 317 +++++++++++++++++++++----------------------- 1 file changed, 152 insertions(+), 165 deletions(-) (limited to 'lisp/emacs-lisp/autoload.el') diff --git a/lisp/emacs-lisp/autoload.el b/lisp/emacs-lisp/autoload.el index 2861aedef3e..c0362f3e6f6 100644 --- a/lisp/emacs-lisp/autoload.el +++ b/lisp/emacs-lisp/autoload.el @@ -68,6 +68,8 @@ that text will be copied verbatim to `generated-autoload-file'.") (defconst generate-autoload-section-continuation ";;;;;; " "String to add on each continuation of the section header form.") +(defvar autoload-modified-buffers) ;Dynamically scoped var. + (defun make-autoload (form file) "Turn FORM into an autoload or defvar for source file FILE. Returns nil if FORM is not a special autoload form (i.e. a function definition @@ -157,16 +159,6 @@ or macro definition or a defcustom)." (expand-file-name "lisp" source-directory))) -(defun autoload-trim-file-name (file) - ;; Returns a relative file path for FILE - ;; starting from the directory that loaddefs.el is in. - ;; That is normally a directory in load-path, - ;; which means Emacs will be able to find FILE when it looks. - ;; Any extra directory names here would prevent finding the file. - (setq file (expand-file-name file)) - (file-relative-name file - (file-name-directory generated-autoload-file))) - (defun autoload-read-section-header () "Read a section header form. Since continuation lines have been marked as comments, @@ -260,9 +252,7 @@ put the output in." "Insert the section-header line, which lists the file name and which functions are in it, etc." (insert generate-autoload-section-header) - (prin1 (list 'autoloads autoloads load-name - (if (stringp file) (autoload-trim-file-name file) file) - time) + (prin1 (list 'autoloads autoloads load-name file time) outbuf) (terpri outbuf) ;; Break that line at spaces, to avoid very long lines. @@ -318,98 +308,99 @@ marked by `generate-autoload-cookie' (which see). If FILE is being visited in a buffer, the contents of the buffer are used. OUTBUF is the buffer in which the autoload statements will be inserted. If OUTBUF is nil, it will be determined by `autoload-generated-file'. -Return non-nil in the case where no autoloads were added to OUTBUF. - -Can throw `up-to-date' to mean that the entries were found already and are -up-to-date. Of course, this can only be the case if OUTBUF is not used." - (let ((autoloads-done '()) - (load-name (autoload-file-load-name file)) - (print-length nil) - (print-readably t) ; This does something in Lucid Emacs. - (float-output-format nil) - (visited (get-file-buffer file)) - (absfile (expand-file-name file)) - relfile - ;; 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 - (message "Generating autoloads for %s..." file) - (save-excursion - (save-restriction - (widen) - (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 - (unless outbuf - (setq outbuf (autoload-find-destination absfile))) - (with-current-buffer outbuf - (setq relfile (file-relative-name absfile)) - (setq output-start (point))) - ;; (message "file=%S, relfile=%S, dest=%S" - ;; file relfile (autoload-generated-file)) - ) - (search-forward generate-autoload-cookie) - (skip-chars-forward " \t") - (if (eolp) - (condition-case err - ;; Read the next form and make an autoload. - (let* ((form (prog1 (read (current-buffer)) - (or (bolp) (forward-line 1)))) - (autoload (make-autoload form load-name))) - (if autoload - (push (nth 1 form) autoloads-done) - (setq autoload form)) - (let ((autoload-print-form-outbuf outbuf)) - (autoload-print-form autoload))) - (error - (message "Error in %s: %S" file err))) - - ;; Copy the rest of the line to the output. - (princ (buffer-substring - (progn - ;; Back up over whitespace, to preserve it. - (skip-chars-backward " \f\t") - (if (= (char-after (1+ (point))) ? ) - ;; Eat one space. - (forward-char 1)) - (point)) - (progn (forward-line 1) (point))) - outbuf))) - ((looking-at ";") - ;; Don't read the comment. - (forward-line 1)) - (t - (forward-sexp 1) - (forward-line 1)))))) - - (when output-start - (with-current-buffer outbuf - (save-excursion - ;; Insert the section-header line which lists the file name - ;; and which functions are in it, etc. - (goto-char output-start) - (autoload-insert-section-header - outbuf autoloads-done load-name relfile - (nth 5 (file-attributes relfile))) - (insert ";;; Generated autoloads from " relfile "\n")) - (insert generate-autoload-section-trailer))) - (message "Generating autoloads for %s...done" file)) - (or visited - ;; We created this buffer, so we should kill it. - (kill-buffer (current-buffer)))) - (not output-start))) - -(defvar autoload-modified-buffers nil) +Return non-nil iff FILE adds no autoloads to OUTBUF." + (catch 'done + (let ((autoloads-done '()) + (load-name (autoload-file-load-name file)) + (print-length nil) + (print-readably t) ; This does something in Lucid Emacs. + (float-output-format nil) + (visited (get-file-buffer file)) + (absfile (expand-file-name file)) + relfile + ;; 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 + (message "Generating autoloads for %s..." file) + (save-excursion + (save-restriction + (widen) + (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 + (unless outbuf + (setq outbuf (autoload-find-destination absfile)) + (unless outbuf + ;; The file has autoload cookies, but they're + ;; already up-to-date. + (throw 'done t))) + (with-current-buffer outbuf + (setq relfile (file-relative-name absfile)) + (setq output-start (point))) + ;; (message "file=%S, relfile=%S, dest=%S" + ;; file relfile (autoload-generated-file)) + ) + (search-forward generate-autoload-cookie) + (skip-chars-forward " \t") + (if (eolp) + (condition-case err + ;; Read the next form and make an autoload. + (let* ((form (prog1 (read (current-buffer)) + (or (bolp) (forward-line 1)))) + (autoload (make-autoload form load-name))) + (if autoload + (push (nth 1 form) autoloads-done) + (setq autoload form)) + (let ((autoload-print-form-outbuf outbuf)) + (autoload-print-form autoload))) + (error + (message "Error in %s: %S" file err))) + + ;; Copy the rest of the line to the output. + (princ (buffer-substring + (progn + ;; Back up over whitespace, to preserve it. + (skip-chars-backward " \f\t") + (if (= (char-after (1+ (point))) ? ) + ;; Eat one space. + (forward-char 1)) + (point)) + (progn (forward-line 1) (point))) + outbuf))) + ((looking-at ";") + ;; Don't read the comment. + (forward-line 1)) + (t + (forward-sexp 1) + (forward-line 1)))))) + + (when output-start + (with-current-buffer outbuf + (save-excursion + ;; Insert the section-header line which lists the file name + ;; and which functions are in it, etc. + (goto-char output-start) + (autoload-insert-section-header + outbuf autoloads-done load-name relfile + (nth 5 (file-attributes relfile))) + (insert ";;; Generated autoloads from " relfile "\n")) + (insert generate-autoload-section-trailer))) + (message "Generating autoloads for %s...done" file)) + (or visited + ;; We created this buffer, so we should kill it. + (kill-buffer (current-buffer)))) + (not output-start)))) + (defun autoload-save-buffers () (while autoload-modified-buffers (with-current-buffer (pop autoload-modified-buffers) @@ -424,78 +415,74 @@ save the buffer too. Return FILE if there was no autoload cookie in it, else nil." (interactive "fUpdate autoloads for file: \np") - (let ((no-autoloads nil)) - (if (catch 'up-to-date - (progn - (setq no-autoloads (autoload-generate-file-autoloads file)) - t)) + (let* ((autoload-modified-buffers nil) + (no-autoloads (autoload-generate-file-autoloads file))) + (if autoload-modified-buffers (if save-after (autoload-save-buffers)) (if (interactive-p) (message "Autoload section for %s is up to date." file))) - ;; If we caught `up-to-date', it means there are autoload entries, since - ;; otherwise we wouldn't have detected their up-to-dateness. (if no-autoloads file))) (defun autoload-find-destination (file) "Find the destination point of the current buffer's autoloads. FILE is the file name of the current buffer. Returns a buffer whose point is placed at the requested location. -Throws `up-to-date' if the file's autoloads are uptodate, otherwise +Returns nil if the file's autoloads are uptodate, otherwise removes any prior now out-of-date autoload entries. The current buffer only matters if it is visiting a file or if it has a buffer-local value for some variables such as `generated-autoload-file', so it's OK to call it from a dummy buffer if FILE is not currently visited." - ;; (message "autoload-find-destination %S" file) - (let ((load-name (autoload-file-load-name file)) - (existing-buffer (if buffer-file-name (current-buffer))) - (found nil)) - (with-current-buffer - ;; We must read/write the file without any code conversion, - ;; but still decode EOLs. - (let ((coding-system-for-read 'raw-text)) - (find-file-noselect - (autoload-ensure-default-file (autoload-generated-file)))) - ;; This is to make generated-autoload-file have Unix EOLs, so - ;; that it is portable to all platforms. - (setq buffer-file-coding-system 'raw-text-unix) - (or (> (buffer-size) 0) - (error "Autoloads file %s does not exist" buffer-file-name)) - (or (file-writable-p buffer-file-name) - (error "Autoloads file %s is not writable" buffer-file-name)) - (widen) - (goto-char (point-min)) - ;; Look for the section for LOAD-NAME. - (while (and (not found) - (search-forward generate-autoload-section-header nil t)) - (let ((form (autoload-read-section-header))) - (cond ((string= (nth 2 form) load-name) - ;; We found the section for this file. - ;; Check if it is up to date. - (let ((begin (match-beginning 0)) - (last-time (nth 4 form)) - (file-time (nth 5 (file-attributes file)))) - (if (and (or (null existing-buffer) - (not (buffer-modified-p existing-buffer))) - (listp last-time) (= (length last-time) 2) - (not (time-less-p last-time file-time))) - (throw 'up-to-date nil) - (autoload-remove-section begin) - (setq found t)))) - ((string< load-name (nth 2 form)) - ;; We've come to a section alphabetically later than - ;; LOAD-NAME. We assume the file is in order and so - ;; there must be no section for LOAD-NAME. We will - ;; insert one before the section here. - (goto-char (match-beginning 0)) - (setq found t))))) - (or found - (progn - ;; No later sections in the file. Put before the last page. - (goto-char (point-max)) - (search-backward "\f" nil t))) - (unless (memq (current-buffer) autoload-modified-buffers) - (push (current-buffer) autoload-modified-buffers)) - (current-buffer)))) + (catch 'up-to-date + (let ((load-name (autoload-file-load-name file)) + (existing-buffer (if buffer-file-name (current-buffer))) + (found nil)) + (with-current-buffer + ;; We must read/write the file without any code conversion, + ;; but still decode EOLs. + (let ((coding-system-for-read 'raw-text)) + (find-file-noselect + (autoload-ensure-default-file (autoload-generated-file)))) + ;; This is to make generated-autoload-file have Unix EOLs, so + ;; that it is portable to all platforms. + (setq buffer-file-coding-system 'raw-text-unix) + (or (> (buffer-size) 0) + (error "Autoloads file %s does not exist" buffer-file-name)) + (or (file-writable-p buffer-file-name) + (error "Autoloads file %s is not writable" buffer-file-name)) + (widen) + (goto-char (point-min)) + ;; Look for the section for LOAD-NAME. + (while (and (not found) + (search-forward generate-autoload-section-header nil t)) + (let ((form (autoload-read-section-header))) + (cond ((string= (nth 2 form) load-name) + ;; We found the section for this file. + ;; Check if it is up to date. + (let ((begin (match-beginning 0)) + (last-time (nth 4 form)) + (file-time (nth 5 (file-attributes file)))) + (if (and (or (null existing-buffer) + (not (buffer-modified-p existing-buffer))) + (listp last-time) (= (length last-time) 2) + (not (time-less-p last-time file-time))) + (throw 'up-to-date nil) + (autoload-remove-section begin) + (setq found t)))) + ((string< load-name (nth 2 form)) + ;; We've come to a section alphabetically later than + ;; LOAD-NAME. We assume the file is in order and so + ;; there must be no section for LOAD-NAME. We will + ;; insert one before the section here. + (goto-char (match-beginning 0)) + (setq found t))))) + (or found + (progn + ;; No later sections in the file. Put before the last page. + (goto-char (point-max)) + (search-backward "\f" nil t))) + (unless (memq (current-buffer) autoload-modified-buffers) + (push (current-buffer) autoload-modified-buffers)) + (current-buffer))))) (defun autoload-remove-section (begin) (goto-char begin) @@ -533,8 +520,8 @@ directory or directories specified." (save-excursion ;; Canonicalize file names and remove the autoload file itself. - (setq files (delete (autoload-trim-file-name buffer-file-name) - (mapcar 'autoload-trim-file-name files))) + (setq files (delete (file-relative-name buffer-file-name) + (mapcar 'file-relative-name files))) (goto-char (point-min)) (while (search-forward generate-autoload-section-header nil t) -- cgit v1.2.1 From 438d6bb6065ba62ecb4ee9c9c2adefca9ade3272 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Tue, 26 Jun 2007 19:53:12 +0000 Subject: (autoload-generated-file): Interpret names relative to current dir for file-local settings. (autoload-generate-file-autoloads): Add `outfile' arg. (update-directory-autoloads): Use it to directly call autoload-generate-file-autoloads instead of going through update-file-autoloads so we avoid redundant searches and so we can know the set of buffers changed so we can save them all. --- lisp/emacs-lisp/autoload.el | 71 +++++++++++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 18 deletions(-) (limited to 'lisp/emacs-lisp/autoload.el') diff --git a/lisp/emacs-lisp/autoload.el b/lisp/emacs-lisp/autoload.el index c0362f3e6f6..e9c05d48c65 100644 --- a/lisp/emacs-lisp/autoload.el +++ b/lisp/emacs-lisp/autoload.el @@ -156,8 +156,12 @@ or macro definition or a defcustom)." (defun autoload-generated-file () (expand-file-name generated-autoload-file - (expand-file-name "lisp" - source-directory))) + ;; File-local settings of generated-autoload-file should + ;; be interpreted relative to the file's location, + ;; of course. + (if (not (local-variable-p 'generated-autoload-file)) + (expand-file-name "lisp" source-directory)))) + (defun autoload-read-section-header () "Read a section header form. @@ -301,15 +305,26 @@ Return non-nil in the case where no autoloads were added at point." (interactive "fGenerate autoloads for file: ") (autoload-generate-file-autoloads file (current-buffer))) -(defun autoload-generate-file-autoloads (file &optional outbuf) +;; When called from `generate-file-autoloads' we should ignore +;; `generated-autoload-file' altogether. When called from +;; `update-file-autoloads' we don't know `outbuf'. And when called from +;; `update-directory-autoloads' it's in between: we know the default +;; `outbuf' but we should obey any file-local setting of +;; `generated-autoload-file'. +(defun autoload-generate-file-autoloads (file &optional outbuf outfile) "Insert an autoload section for FILE in the appropriate buffer. Autoloads are generated for defuns and defmacros in FILE marked by `generate-autoload-cookie' (which see). If FILE is being visited in a buffer, the contents of the buffer are used. -OUTBUF is the buffer in which the autoload statements will be inserted. +OUTBUF is the buffer in which the autoload statements should be inserted. If OUTBUF is nil, it will be determined by `autoload-generated-file'. -Return non-nil iff FILE adds no autoloads to OUTBUF." +If provided, OUTFILE is expected to be the file name of OUTBUF. +If OUTFILE is non-nil and FILE specifies a `generated-autoload-file' +different from OUTFILE, then OUTBUF is ignored. + +Return non-nil iff FILE adds no autoloads to OUTFILE +\(or OUTBUF if OUTFILE is nil)." (catch 'done (let ((autoloads-done '()) (load-name (autoload-file-load-name file)) @@ -317,6 +332,7 @@ Return non-nil iff FILE adds no autoloads to OUTBUF." (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)) relfile ;; nil until we found a cookie. @@ -338,12 +354,20 @@ Return non-nil iff FILE adds no autoloads to OUTBUF." ((looking-at (regexp-quote generate-autoload-cookie)) ;; If not done yet, figure out where to insert this text. (unless output-start + (when (and outfile + (not (equal outfile (autoload-generated-file)))) + ;; A file-local setting of autoload-generated-file says + ;; we should ignore OUTBUF. + (setq outbuf nil) + (setq otherbuf t)) (unless outbuf (setq outbuf (autoload-find-destination absfile)) (unless outbuf ;; The file has autoload cookies, but they're - ;; already up-to-date. - (throw 'done t))) + ;; already up-to-date. If OUTFILE is nil, the + ;; entries are in the expected OUTBUF, otherwise + ;; they're elsewhere. + (throw 'done outfile))) (with-current-buffer outbuf (setq relfile (file-relative-name absfile)) (setq output-start (point))) @@ -399,7 +423,9 @@ Return non-nil iff FILE adds no autoloads to OUTBUF." (or visited ;; We created this buffer, so we should kill it. (kill-buffer (current-buffer)))) - (not output-start)))) + ;; If the entries were added to some other buffer, then the file + ;; doesn't add entries to OUTFILE. + (or (not output-start) otherbuf)))) (defun autoload-save-buffers () (while autoload-modified-buffers @@ -511,12 +537,14 @@ directory or directories specified." t files-re)) dirs))) (this-time (current-time)) - (no-autoloads nil) ;files with no autoload cookies. - (autoloads-file (autoload-generated-file)) - (top-dir (file-name-directory autoloads-file))) + ;; Files with no autoload cookies or whose autoloads go to other + ;; files because of file-local autoload-generated-file settings. + (no-autoloads nil) + (autoload-modified-buffers nil)) (with-current-buffer - (find-file-noselect (autoload-ensure-default-file autoloads-file)) + (find-file-noselect + (autoload-ensure-default-file (autoload-generated-file))) (save-excursion ;; Canonicalize file names and remove the autoload file itself. @@ -541,19 +569,23 @@ directory or directories specified." (push file no-autoloads) (setq files (delete file files))))))) ((not (stringp file))) - ((not (file-exists-p (expand-file-name file top-dir))) + ((not (file-exists-p file)) ;; Remove the obsolete section. (autoload-remove-section (match-beginning 0))) ((equal (nth 4 form) (nth 5 (file-attributes file))) ;; File hasn't changed. nil) (t - (update-file-autoloads file))) + (autoload-remove-section (match-beginning 0)) + (if (autoload-generate-file-autoloads + file (current-buffer) buffer-file-name) + (push file no-autoloads)))) (setq files (delete file files))))) ;; Elements remaining in FILES have no existing autoload sections yet. - (setq no-autoloads - (append no-autoloads - (delq nil (mapcar 'update-file-autoloads files)))) + (dolist (file files) + (if (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<)) @@ -564,7 +596,10 @@ directory or directories specified." (current-buffer) nil nil no-autoloads this-time) (insert generate-autoload-section-trailer)) - (save-buffer)))) + (save-buffer) + ;; In case autoload entries were added to other files because of + ;; file-local autoload-generated-file settings. + (autoload-save-buffers)))) (define-obsolete-function-alias 'update-autoloads-from-directories 'update-directory-autoloads "22.1") -- cgit v1.2.1 From 0b7750a95dffb8cb8095a5c9ca4e9df1cf7acd54 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Sat, 7 Jul 2007 04:56:00 +0000 Subject: (autoload-find-destination): Understand a new format of autoload block where the file's time-stamp is replaced by its MD5 checksum. (autoload-generate-file-autoloads): Use MD5 checksum instead of time-stamp for secondary autoloads files. (update-directory-autoloads): Remove duplicate entries. Use time-less-p for time-stamps, as done in autoload-find-destination. --- lisp/emacs-lisp/autoload.el | 68 +++++++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 21 deletions(-) (limited to 'lisp/emacs-lisp/autoload.el') diff --git a/lisp/emacs-lisp/autoload.el b/lisp/emacs-lisp/autoload.el index e9c05d48c65..30b7c7e1937 100644 --- a/lisp/emacs-lisp/autoload.el +++ b/lisp/emacs-lisp/autoload.el @@ -409,16 +409,33 @@ Return non-nil iff FILE adds no autoloads to OUTFILE (forward-line 1)))))) (when output-start - (with-current-buffer outbuf - (save-excursion - ;; Insert the section-header line which lists the file name - ;; and which functions are in it, etc. - (goto-char output-start) - (autoload-insert-section-header - outbuf autoloads-done load-name relfile - (nth 5 (file-attributes relfile))) - (insert ";;; Generated autoloads from " relfile "\n")) - (insert generate-autoload-section-trailer))) + (let ((secondary-autoloads-file-buf + (if (local-variable-p 'generated-autoload-file) + (current-buffer)))) + (with-current-buffer outbuf + (save-excursion + ;; Insert the section-header line which lists the file name + ;; and which functions are in it, etc. + (goto-char output-start) + (autoload-insert-section-header + outbuf autoloads-done 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 nil nil 'emacs-mule) + (nth 5 (file-attributes relfile)))) + (insert ";;; Generated autoloads from " relfile "\n")) + (insert generate-autoload-section-trailer)))) (message "Generating autoloads for %s...done" file)) (or visited ;; We created this buffer, so we should kill it. @@ -454,14 +471,12 @@ Return FILE if there was no autoload cookie in it, else nil." FILE is the file name of the current buffer. Returns a buffer whose point is placed at the requested location. Returns nil if the file's autoloads are uptodate, otherwise -removes any prior now out-of-date autoload entries. -The current buffer only matters if it is visiting a file or if it has a buffer-local -value for some variables such as `generated-autoload-file', so it's OK -to call it from a dummy buffer if FILE is not currently visited." +removes any prior now out-of-date autoload entries." (catch 'up-to-date - (let ((load-name (autoload-file-load-name file)) - (existing-buffer (if buffer-file-name (current-buffer))) - (found nil)) + (let* ((load-name (autoload-file-load-name file)) + (buf (current-buffer)) + (existing-buffer (if buffer-file-name buf)) + (found nil)) (with-current-buffer ;; We must read/write the file without any code conversion, ;; but still decode EOLs. @@ -489,8 +504,16 @@ to call it from a dummy buffer if FILE is not currently visited." (file-time (nth 5 (file-attributes file)))) (if (and (or (null existing-buffer) (not (buffer-modified-p existing-buffer))) - (listp last-time) (= (length last-time) 2) - (not (time-less-p last-time file-time))) + (or + ;; 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))) + ;; last-time is an MD5 checksum instead. + (and (stringp last-time) + (equal last-time + (md5 buf nil nil 'emacs-mule))))) (throw 'up-to-date nil) (autoload-remove-section begin) (setq found t)))) @@ -569,10 +592,13 @@ directory or directories specified." (push file no-autoloads) (setq files (delete file files))))))) ((not (stringp file))) - ((not (file-exists-p file)) + ((not (and (file-exists-p file) + ;; Remove duplicates as well, just in case. + (member file files))) ;; Remove the obsolete section. (autoload-remove-section (match-beginning 0))) - ((equal (nth 4 form) (nth 5 (file-attributes file))) + ((not (time-less-p (nth 4 form) + (nth 5 (file-attributes file)))) ;; File hasn't changed. nil) (t -- cgit v1.2.1