Refactor autoload generator, plus autodef support

Soon, autodefs will replace settings (i.e. def-setting! & set!). This
refactor prepares for it.
This commit is contained in:
Henrik Lissner 2018-06-14 19:46:31 +02:00
parent b2b6ff67f2
commit f44ebbb7ed
No known key found for this signature in database
GPG key ID: 5F6C0EA160557395

View file

@ -330,6 +330,10 @@ one wants that.")
it exists."
(cl-check-type file string)
(when (file-exists-p file)
(when-let* ((buf (find-buffer-visiting doom-autoload-file)))
(with-current-buffer buf
(set-buffer-modified-p nil))
(kill-buffer buf))
(delete-file file)
(ignore-errors (delete-file (byte-compile-dest-file file)))
(message "Deleted old %s" (file-name-nondirectory file))))
@ -372,7 +376,103 @@ even if it doesn't need reloading!"
(doom//reload-doom-autoloads force-p)
(doom//reload-package-autoloads force-p)))))
(defvar generated-autoload-load-name)
;;
;; Doom autoloads
;;
(defun doom--generate-header (func)
(goto-char (point-min))
(insert ";; -*- lexical-binding:t -*-\n"
";; This file is autogenerated by `" (symbol-name func) "', DO NOT EDIT !!\n\n"))
(defun doom--generate-autoloads (targets)
(require 'autoload)
(dolist (file targets)
(let* ((file (file-truename file))
(generated-autoload-file doom-autoload-file)
(generated-autoload-load-name (file-name-sans-extension file))
(noninteractive (not doom-debug-mode))
autoload-timestamps)
(print!
(cond ((not (doom-file-cookie-p file))
"⚠ Ignoring %s")
((autoload-generate-file-autoloads file (current-buffer))
(yellow "✕ Nothing in %%s"))
((green "✓ Scanned %%s")))
(if (file-in-directory-p file default-directory)
(file-relative-name file)
(abbreviate-file-name file))))))
(defun doom--expand-autoloads ()
(let ((load-path (append doom-modules-dirs load-path))
cache)
(while (re-search-forward "^\\s-*(autoload\\s-+'[^ ]+\\s-+\"\\([^\"]*\\)\"" nil t)
(let ((path (match-string 1)))
(replace-match
(or (cdr (assoc path cache))
(when-let* ((libpath (locate-library path))
(libpath (file-name-sans-extension libpath)))
(push (cons path (abbreviate-file-name libpath)) cache)
libpath)
path)
t t nil 1)))))
(defun doom--generate-autodefs (targets enabled-targets)
(goto-char (point-max))
(search-backward ";;;***" nil t)
(save-excursion (insert "\n"))
(dolist (path targets)
(insert
(with-temp-buffer
(insert-file-contents path)
(let ((member-p (or (member path enabled-targets)
(file-in-directory-p path doom-core-dir)))
forms sexp cond)
(while (re-search-forward "^;;;###autodef *\\([^\n]+\\)?\n" nil t)
(setq sexp (sexp-at-point)
cond (match-string 1))
(when cond
(setq cond (read cond)))
(let* ((type (car sexp))
(name (nth 1 sexp))
(arglist (nth 2 sexp))
(docstring (format "(Actually defined in %s)\n\n%s"
(abbreviate-file-name path)
(if (stringp (nth 3 sexp)) (nth 3 sexp) "No docstring")))
(body (cdddr (if (stringp (nth 3 sexp)) (cdr sexp) sexp))))
(cond ((not (memq type '(defun defmacro cl-defun cl-defmacro)))
(message "Ignoring invalid autodef %s (found %s)"
name type))
((and member-p
(or (null cond)
(eval cond t)))
(push (if (memq type '(defmacro cl-defmacro))
sexp
(make-autoload sexp path))
forms))
(sexp
(condition-case e
(push (append (list 'defmacro name arglist docstring)
(cl-loop for arg in arglist
if (and (symbolp arg)
(not (keywordp arg))
(not (memq arg cl--lambda-list-keywords)))
collect (list 'ignore arg)))
forms)
('error
(message "Ignoring autodef %s (%s)"
name e)))))))
(if forms
(concat (string-join (mapcar #'prin1-to-string forms) "\n")
"\n")
""))))))
(defun doom--cleanup-autoloads ()
(goto-char (point-min))
(when (re-search-forward "^;;\\(;[^\n]*\\| no-byte-compile: t\\)\n" nil t)
(replace-match "" t t)))
(defun doom//reload-doom-autoloads (&optional force-p)
"Refreshes the autoloads.el file, specified by `doom-autoload-file', if
necessary (or if FORCE-P is non-nil).
@ -384,18 +484,25 @@ Emacs where to find lazy-loaded functions.
This should be run whenever your `doom!' block, or a module autoload file, is
modified."
(interactive)
(let ((default-directory doom-emacs-dir)
(targets
(file-expand-wildcards
(expand-file-name "autoload/*.el" doom-core-dir))))
(dolist (path (doom-module-load-path))
(let ((auto-dir (expand-file-name "autoload" path))
(auto-file (expand-file-name "autoload.el" path)))
(let* ((default-directory doom-emacs-dir)
(doom-modules (doom-modules))
(targets
(file-expand-wildcards
(expand-file-name "autoload/*.el" doom-core-dir)))
(enabled-targets (copy-sequence targets))
case-fold-search)
(dolist (path (doom-module-load-path t))
(let* ((auto-dir (expand-file-name "autoload" path))
(auto-file (expand-file-name "autoload.el" path))
(module (doom-module-from-path auto-file))
(module-p (or (doom-module-p (car module) (cdr module))
(file-equal-p path doom-private-dir))))
(when (file-exists-p auto-file)
(push auto-file targets))
(when (file-directory-p auto-dir)
(dolist (file (doom-files-in auto-dir :match "\\.el$" :full t))
(push file targets)))))
(push auto-file targets)
(if module-p (push auto-file enabled-targets)))
(dolist (file (doom-files-in auto-dir :match "\\.el$" :full t))
(push file targets)
(if module-p (push file enabled-targets)))))
(if (and (not force-p)
(not doom-emacs-changed-p)
(file-exists-p doom-autoload-file)
@ -404,65 +511,70 @@ modified."
(not (cl-loop for file in targets
if (file-newer-than-file-p file doom-autoload-file)
return t)))
(ignore (print! (green "Doom core autoloads is up-to-date"))
(doom-initialize-autoloads doom-autoload-file))
(progn (print! (green "Doom core autoloads is up-to-date"))
(doom-initialize-autoloads doom-autoload-file)
nil)
(doom-delete-autoloads-file doom-autoload-file)
;; in case the buffer is open somewhere and modified
(when-let* ((buf (find-buffer-visiting doom-autoload-file)))
(with-current-buffer buf
(set-buffer-modified-p nil))
(kill-buffer buf))
(message "Generating new autoloads.el")
(dolist (file (nreverse targets))
(let* ((file (file-truename file))
(generated-autoload-load-name (file-name-sans-extension file))
(noninteractive (not doom-debug-mode)))
(print!
(cond ((not (doom-file-cookie-p file))
"⚠ Ignoring %s")
((update-file-autoloads file nil doom-autoload-file)
(yellow "✕ Nothing in %%s"))
((green "✓ Scanned %%s")))
(if (file-in-directory-p file default-directory)
(file-relative-name file)
(abbreviate-file-name file)))))
(make-directory (file-name-directory doom-autoload-file) t)
(let ((buf (find-file-noselect doom-autoload-file t))
case-fold-search)
(unwind-protect
(with-current-buffer buf
(goto-char (point-min))
(insert ";;; -*- lexical-binding:t -*-\n"
";; This file is autogenerated by `doom//reload-doom-autoloads', DO NOT EDIT !!\n\n")
(save-excursion
;; Replace autoload paths (only for module autoloads) with
;; absolute paths for faster resolution during load and
;; simpler `load-path'
(let ((load-path (append doom-modules-dirs load-path))
cache)
(save-excursion
(while (re-search-forward "^\\s-*(autoload\\s-+'[^ ]+\\s-+\"\\([^\"]*\\)\"" nil t)
(let ((path (match-string 1)))
(replace-match
(or (cdr (assoc path cache))
(when-let* ((libpath (locate-library path))
(libpath (file-name-sans-extension libpath)))
(push (cons path (abbreviate-file-name libpath)) cache)
libpath)
path)
t t nil 1)))
(print! (green "✓ Autoload paths expanded")))))
;; Remove byte-compile inhibiting file variables so we can
;; byte-compile the file.
(when (re-search-forward "^;; no-byte-compile: t\n" nil t)
(replace-match "" t t))
;; Byte compile it to give the file a chance to reveal errors.
(save-buffer)
(doom--byte-compile-file doom-autoload-file)
(when (and noninteractive (not (daemonp)))
(doom--server-load doom-autoload-file))
t)
(kill-buffer buf))))))
(with-temp-file doom-autoload-file
(doom--generate-header 'doom//reload-doom-autoloads)
(save-excursion
(doom--generate-autoloads (reverse enabled-targets)))
;; Replace autoload paths (only for module autoloads) with absolute
;; paths for faster resolution during load and simpler `load-path'
(save-excursion
(doom--expand-autoloads)
(print! (green "✓ Expanded module autoload paths")))
;; Generates stub definitions for functions/macros defined in disabled
;; modules, so that you will never get a void-function when you use
;; them.
(save-excursion
(doom--generate-autodefs (reverse targets) enabled-targets))
;; Remove byte-compile inhibiting file variables so we can byte-compile
;; the file, and autoload comments.
(doom--cleanup-autoloads)
(print! (green "✓ Clean up autoloads")))
;; Byte compile it to give the file a chance to reveal errors.
(doom--byte-compile-file doom-autoload-file)
(when (and noninteractive (not (daemonp)))
(doom--server-load doom-autoload-file))
t)))
;;
;; Package autoloads
;;
(defun doom--generate-package-autoloads ()
(dolist (spec (doom-get-package-alist))
(if-let* ((pkg (car spec))
(desc (cdr spec)))
(unless (memq pkg doom-autoload-excluded-packages)
(let ((file (concat (package--autoloads-file-name desc) ".el")))
(when (file-exists-p file)
(insert "(let ((load-file-name " (prin1-to-string (abbreviate-file-name file)) "))\n")
(insert-file-contents file)
(while (re-search-forward "^\\(?:;;\\(.*\n\\)\\|\n\\|(provide '[^\n]+\\)" nil t)
(unless (nth 8 (syntax-ppss))
(replace-match "" t t)))
(unless (bolp) (insert "\n"))
(insert ")\n"))))
(message "Couldn't find package desc for %s" (car spec)))))
(defun doom--generate-var-cache ()
(doom-initialize-packages)
(prin1 `(setq load-path ',load-path
auto-mode-alist ',auto-mode-alist
Info-directory-list ',Info-directory-list
doom-disabled-packages ',doom-disabled-packages
package-activated-list ',package-activated-list)
(current-buffer)))
(defun doom--cleanup-package-autoloads ()
(while (re-search-forward "^\\s-*\\((\\(?:add-to-list\\|\\(?:when\\|if\\) (boundp\\)\\s-+'\\(?:load-path\\|auto-mode-alist\\)\\)" nil t)
(goto-char (match-beginning 1))
(kill-sexp)))
(defun doom//reload-package-autoloads (&optional force-p)
"Compiles `doom-package-autoload-file' from the autoloads files of all
@ -485,43 +597,21 @@ This should be run whenever your `doom!' block or update your packages."
return t))))
(ignore (print! (green "Doom package autoloads is up-to-date"))
(doom-initialize-autoloads doom-package-autoload-file))
(doom-delete-autoloads-file doom-package-autoload-file)
(with-temp-file doom-package-autoload-file
(insert ";;; -*- lexical-binding:t -*-\n"
";; This file is autogenerated by `doom//reload-package-autoloads', DO NOT EDIT !!\n\n")
(let (case-fold-search)
(let (case-fold-search)
(doom-delete-autoloads-file doom-package-autoload-file)
(with-temp-file doom-package-autoload-file
(doom--generate-header 'doom//reload-package-autoloads)
(save-excursion
;; Cache the important and expensive-to-initialize state here.
(doom-initialize-packages)
(prin1 `(setq load-path ',load-path
auto-mode-alist ',auto-mode-alist
Info-directory-list ',Info-directory-list
doom-disabled-packages ',doom-disabled-packages
package-activated-list ',package-activated-list)
(current-buffer))
(doom--generate-var-cache)
(print! (green "✓ Cached package state"))
;; insert package autoloads
(dolist (spec (doom-get-package-alist))
(if-let* ((pkg (car spec))
(desc (cdr spec)))
(unless (memq pkg doom-autoload-excluded-packages)
(let ((file (concat (package--autoloads-file-name desc) ".el")))
(when (file-exists-p file)
(insert "(let ((load-file-name " (prin1-to-string (abbreviate-file-name file)) "))\n")
(insert-file-contents file)
(while (re-search-forward "^\\(?:;;\\(.*\n\\)\\|\n\\|(provide '[^\n]+\\)" nil t)
(unless (nth 8 (syntax-ppss))
(replace-match "" t t)))
(unless (bolp) (insert "\n"))
(insert ")\n"))))
(print! (yellow "⚠ Couldn't find package desc for %s" (car spec))))))
(print! (green "✓ Package autoloads included"))
;; Loop through packages and concatenate all their autoloads files.
(doom--generate-package-autoloads)
(print! (green "✓ Package autoloads included")))
;; Remove `load-path' and `auto-mode-alist' modifications (most of them,
;; at least); they are cached later, so all those membership checks are
;; unnecessary overhead.
(while (re-search-forward "^\\s-*\\((\\(?:add-to-list\\|\\(?:when\\|if\\) (boundp\\)\\s-+'\\(?:load-path\\|auto-mode-alist\\)\\)" nil t)
(goto-char (match-beginning 1))
(kill-sexp))
(doom--cleanup-package-autoloads)
(print! (green "✓ Removed load-path/auto-mode-alist entries"))))
(doom--byte-compile-file doom-package-autoload-file)
(when (and noninteractive (not (daemonp)))