From f44ebbb7ed5d7011a5a05e62780e761fe1403bdd Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Thu, 14 Jun 2018 19:46:31 +0200 Subject: [PATCH] Refactor autoload generator, plus autodef support Soon, autodefs will replace settings (i.e. def-setting! & set!). This refactor prepares for it. --- core/core-dispatcher.el | 288 ++++++++++++++++++++++++++-------------- 1 file changed, 189 insertions(+), 99 deletions(-) diff --git a/core/core-dispatcher.el b/core/core-dispatcher.el index 879e15469..a23b93b02 100644 --- a/core/core-dispatcher.el +++ b/core/core-dispatcher.el @@ -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)))