Major rewrite of doom module API

+ Fix #446, where the .local/packages.el cache was generated with
  a faulty load-path.
+ Entries in the doom-modules hash table are now plists, containing
  :flags and :path, at least.
+ Add doom-initialize-modules for loading module config.el files.
+ Add doom-module-get for accessing this plist, e.g.

    (doom-module-get :some module)         ; returns plist
    (doom-module-get :some module :flags)  ; return specific property

+ Replace doom-module-enable with doom-module-set, e.g.

    (doom-module-set :some module :flags '(+a +b +c))

+ Remove doom-module-flags (use doom-module-get instead)
+ Rename doom-module-enabled-p with doom-module-p
+ Replace doom-module-path with doom-module-find-path and
  doom-module-expand-file. The former will search for an existing module
  or file in doom-modules-dirs. The latter will expand the path from
  whatever path is stored in doom-modules.
+ Replace doom-module-paths with doom-module-load-path
+ Changed doom! to allow for nested doom! calls by delaying the loading
  of module config.el files until as late as possible.
+ Refactor doom-initialize-packages to only ihitialize package state
  (i.e. doom-packages, package-alist, and quelpa-cache), rather than its
  previous behavior of loading all Doom files (and sometimes all module
  files). This is faster and more predictable.
This commit is contained in:
Henrik Lissner 2018-03-02 17:45:15 -05:00
parent 01f9ca9e67
commit 0425724571
No known key found for this signature in database
GPG key ID: 5F6C0EA160557395
7 changed files with 351 additions and 282 deletions

View file

@ -1,5 +1,5 @@
# Ensure emacs always runs from this makefile's PWD # Ensure emacs always runs from this makefile's PWD
EMACS_FLAGS=--eval '(setq user-emacs-directory default-directory)' -l core/core.el EMACS_FLAGS=--eval '(setq user-emacs-directory default-directory)' -l init.el
EMACS=emacs --quick --batch $(EMACS_FLAGS) EMACS=emacs --quick --batch $(EMACS_FLAGS)
EMACSI=emacs -q $(EMACS_FLAGS) EMACSI=emacs -q $(EMACS_FLAGS)
@ -71,9 +71,9 @@ testi: init.el .local/autoloads.el
## Utility tasks ## Utility tasks
# Runs Emacs from a different folder than ~/.emacs.d # Runs Emacs from a different folder than ~/.emacs.d; only use this for testing!
run: run:
@$(EMACSI) -l init.el @$(EMACSI) --eval "(run-hooks 'after-init-hook 'emacs-startup-hook 'window-setup-hook)"
# Diagnoses potential OS/environment issues # Diagnoses potential OS/environment issues
doctor: doctor:

View file

@ -120,7 +120,7 @@ ready to be pasted in a bug report on github."
if (or (not cat) (not (eq cat (car key)))) if (or (not cat) (not (eq cat (car key))))
do (setq cat (car key)) and collect cat do (setq cat (car key)) and collect cat
else collect else collect
(let ((flags (doom-module-flags cat (cdr key)))) (let ((flags (doom-module-get cat (cdr key) :flags)))
(if (equal flags '(t)) (if (equal flags '(t))
(cdr key) (cdr key)
(list (cdr key) flags)))) (list (cdr key) flags))))

View file

@ -95,9 +95,9 @@ in, or d) the module associated with the current major mode (see
nil t module)))) nil t module))))
(cl-destructuring-bind (category submodule) (cl-destructuring-bind (category submodule)
(mapcar #'intern (split-string module " ")) (mapcar #'intern (split-string module " "))
(unless (doom-module-enabled-p category submodule) (unless (doom-module-p category submodule)
(error "'%s' isn't a valid module" module)) (error "'%s' isn't a valid module" module))
(let ((doc-path (expand-file-name "README.org" (doom-module-path category submodule)))) (let ((doc-path (doom-module-expand-file category submodule "README.org")))
(unless (file-exists-p doc-path) (unless (file-exists-p doc-path)
(error "There is no documentation for this module")) (error "There is no documentation for this module"))
(find-file doc-path)))) (find-file doc-path))))

View file

@ -3,10 +3,9 @@
(load! cache) (load! cache)
(require 'use-package) (require 'use-package)
(require 'quelpa) (require 'quelpa)
(require 'package)
(require 'async) (require 'async)
(doom-initialize-packages)
;;;###autoload ;;;###autoload
(defun doom-refresh-packages (&optional force-p) (defun doom-refresh-packages (&optional force-p)
"Refresh ELPA packages." "Refresh ELPA packages."
@ -113,7 +112,7 @@ Warning: this function is expensive; it re-evaluates all of doom's config files.
Be careful not to use it in a loop. Be careful not to use it in a loop.
If INSTALLED-ONLY-P, only return packages that are installed." If INSTALLED-ONLY-P, only return packages that are installed."
(doom-initialize-packages t) (doom-initialize-packages 'internal)
(cl-loop with packages = (append doom-core-packages (mapcar #'car doom-packages)) (cl-loop with packages = (append doom-core-packages (mapcar #'car doom-packages))
for sym in (cl-delete-duplicates packages) for sym in (cl-delete-duplicates packages)
if (and (or (not installed-only-p) if (and (or (not installed-only-p)
@ -146,7 +145,7 @@ containing (PACKAGE-SYMBOL OLD-VERSION-LIST NEW-VERSION-LIST).
If INCLUDE-FROZEN-P is non-nil, check frozen packages as well. If INCLUDE-FROZEN-P is non-nil, check frozen packages as well.
Used by `doom//packages-update'." Used by `doom//packages-update'."
(doom-initialize-packages t) (doom-initialize-packages 'internal)
(require 'async) (require 'async)
(let (quelpa-pkgs elpa-pkgs) (let (quelpa-pkgs elpa-pkgs)
;; Separate quelpa from elpa packages ;; Separate quelpa from elpa packages
@ -184,7 +183,7 @@ Used by `doom//packages-update'."
depended on. depended on.
Used by `doom//packages-autoremove'." Used by `doom//packages-autoremove'."
(doom-initialize-packages t) (doom-initialize-packages 'internal)
(let ((package-selected-packages (let ((package-selected-packages
(append (mapcar #'car doom-packages) doom-core-packages))) (append (mapcar #'car doom-packages) doom-core-packages)))
(append (package--removable-packages) (append (package--removable-packages)
@ -204,7 +203,7 @@ If INCLUDE-IGNORED-P is non-nil, includes missing packages that are ignored,
i.e. they have an :ignore property. i.e. they have an :ignore property.
Used by `doom//packages-install'." Used by `doom//packages-install'."
(doom-initialize-packages t) (doom-initialize-packages 'internal)
(cl-loop for desc in (doom-get-packages) (cl-loop for desc in (doom-get-packages)
for (name . plist) = desc for (name . plist) = desc
if (and (or include-ignored-p if (and (or include-ignored-p

View file

@ -8,6 +8,7 @@ command line args following a double dash (each arg should be in the
If neither is available, run all tests in all enabled modules." If neither is available, run all tests in all enabled modules."
(interactive) (interactive)
(let ((doom-modules (make-hash-table :test #'equal)))
;; ensure DOOM is initialized ;; ensure DOOM is initialized
(doom-initialize-packages t) (doom-initialize-packages t)
(condition-case-unless-debug ex (condition-case-unless-debug ex
@ -33,13 +34,12 @@ If neither is available, run all tests in all enabled modules."
(modules ; cons-cells given to MODULES (modules ; cons-cells given to MODULES
(cl-loop for (module . submodule) in modules (cl-loop for (module . submodule) in modules
if (doom-module-path module submodule) if (doom-module-find-path module submodule)
collect it)) collect it))
((let (noninteractive) ((let (noninteractive)
(setq doom-modules (clrhash doom-modules))
(load (expand-file-name "init.test.el" user-emacs-directory) nil t) (load (expand-file-name "init.test.el" user-emacs-directory) nil t)
(append (list doom-core-dir) (doom-module-paths))))))) (append (list doom-core-dir) (doom-module-load-path)))))))
;; Load all the unit test files... ;; Load all the unit test files...
(dolist (path target-paths) (dolist (path target-paths)
(let ((test-path (expand-file-name "test/" path))) (let ((test-path (expand-file-name "test/" path)))
@ -53,7 +53,7 @@ If neither is available, run all tests in all enabled modules."
('error ('error
(lwarn 'doom-test :error (lwarn 'doom-test :error
"%s -> %s" "%s -> %s"
(car ex) (error-message-string ex))))) (car ex) (error-message-string ex))))))
;; --- Test helpers ----------------------- ;; --- Test helpers -----------------------

View file

@ -90,8 +90,11 @@ missing) and shouldn't be deleted.")
(defvar doom--refreshed-p nil) (defvar doom--refreshed-p nil)
(defvar doom--current-module nil) (defvar doom--current-module nil)
(defvar doom--initializing nil)
(defvar generated-autoload-load-name)
(setq package--init-file-ensured t (setq autoload-compute-prefixes nil
package--init-file-ensured t
package-user-dir (expand-file-name "elpa" doom-packages-dir) package-user-dir (expand-file-name "elpa" doom-packages-dir)
package-enable-at-startup nil package-enable-at-startup nil
package-archives package-archives
@ -124,24 +127,36 @@ missing) and shouldn't be deleted.")
byte-compile-verbose doom-debug-mode byte-compile-verbose doom-debug-mode
byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local)) byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local))
(defun doom-packages--benchmark ()
(format "Doom loaded %s packages across %d modules in %.03fs"
;; Certainly imprecise, especially where custom additions to
;; load-path are concerned, but I don't mind a [small] margin of
;; error in the plugin count in exchange for faster startup.
(- (length load-path) (length doom-site-load-path))
(hash-table-size doom-modules)
(or doom-init-time
(setq doom-init-time (float-time (time-subtract (current-time) before-init-time))))))
;; ;;
;; Bootstrap function ;; Bootstrap API
;; ;;
(defun doom-initialize (&optional force-p) (defun doom-initialize (&optional force-p)
"Initialize package.el, create all the essential directories, and ensure core "Bootstrap the bare essentials to get Doom running, if it hasn't already. If
packages are installed. FORCE-P is non-nil, do it anyway.
If you byte-compile core/core.el, this function will be avoided to speed up 1. Ensures all the essential directories exist,
startup." 2. Ensures core packages are installed,
3. Loads your autoloads file in `doom-autoload-file',
4. Builds and caches `load-path' and `Info-directory-list' in `doom-packages-file'"
;; Called early during initialization; only use native (and cl-lib) functions! ;; Called early during initialization; only use native (and cl-lib) functions!
(when (or force-p (not doom-init-p)) (when (or force-p (not doom-init-p))
(unless (load doom-autoload-file t t t) (unless (load doom-autoload-file t t t)
(unless noninteractive (unless noninteractive
(error "No autoloads file! Run make autoloads"))) (error "No autoloads file! Run make autoloads")))
(when (or (not (load doom-packages-file t t t)) (when (or force-p (not (load doom-packages-file t t t)))
force-p) (setq load-path doom-site-load-path)
;; Ensure core folders exist, otherwise we get errors ;; Ensure core folders exist, otherwise we get errors
(dolist (dir (list doom-local-dir doom-etc-dir doom-cache-dir doom-packages-dir)) (dolist (dir (list doom-local-dir doom-etc-dir doom-cache-dir doom-packages-dir))
(unless (file-directory-p dir) (unless (file-directory-p dir)
@ -166,12 +181,13 @@ startup."
(message "✓ Installed %s" package) (message "✓ Installed %s" package)
(error "✕ Couldn't install %s" package))) (error "✕ Couldn't install %s" package)))
(message "Installing core packages...done"))) (message "Installing core packages...done")))
(unless noninteractive
(with-temp-buffer (with-temp-buffer
(cl-pushnew doom-core-dir load-path :test #'string=) (cl-pushnew doom-core-dir load-path :test #'string=)
(prin1 `(setq load-path ',load-path (prin1 `(setq load-path ',load-path
Info-directory-list ',Info-directory-list) Info-directory-list ',Info-directory-list)
(current-buffer)) (current-buffer))
(write-file doom-packages-file))) (write-file doom-packages-file))))
(setq doom-init-p t))) (setq doom-init-p t)))
(defun doom-initialize-autoloads () (defun doom-initialize-autoloads ()
@ -180,15 +196,25 @@ startup."
(unless (file-exists-p doom-autoload-file) (unless (file-exists-p doom-autoload-file)
(quiet! (doom//reload-autoloads)))) (quiet! (doom//reload-autoloads))))
(defun doom-initialize-packages (&optional force-p load-p) (defun doom-initialize-modules ()
"Crawls across your emacs.d to fill `doom-modules' (from init.el), "Bootstraps all enabled modules by loading their config.el files."
`doom-packages' (from packages.el files) and `quelpa-cache'. If they aren't set (maphash (lambda (key plist)
already. Also runs every enabled module's init.el. (let ((doom--current-module key))
(load (expand-file-name "config" (plist-get plist :path))
'noerror (not doom-debug-mode))))
doom-modules))
If FORCE-P is non-nil, do it even if they are. (defun doom-initialize-packages (&optional force-p)
"Ensures that `doom-packages', `packages-alist' and `quelpa-cache' are
populated.
This aggressively reloads core autoload files." This reads modules' packages.el files, runs `package-initialize', and
(doom-initialize force-p) initializes quelpa, if they haven't already. If FORCE-P is non-nil, do it
anyway.
Use this before any of package.el, quelpa or Doom's package management's API to
ensure all the necessary package metadata is initialized and available for
them."
(with-temp-buffer ; prevent buffer-local settings from propagating (with-temp-buffer ; prevent buffer-local settings from propagating
(cl-flet (cl-flet
((_load ((_load
@ -196,48 +222,109 @@ This aggressively reloads core autoload files."
(condition-case-unless-debug ex (condition-case-unless-debug ex
(let ((load-prefer-newer t) (let ((load-prefer-newer t)
(noninteractive (not interactive))) (noninteractive (not interactive)))
(load file noerror :nomessage :nosuffix)) (load file noerror 'nomessage 'nosuffix))
('error ('error
(lwarn 'doom-initialize-packages :warning (lwarn 'doom-initialize-packages :warning
"%s in %s: %s" "%s in %s: %s"
(car ex) (car ex)
(file-relative-name file doom-emacs-dir) (file-relative-name file doom-emacs-dir)
(error-message-string ex)))))) (error-message-string ex))))))
(when (or force-p (not doom-modules)) ;; package.el and quelpa handle themselves if their state changes during
(setq doom-modules (clrhash doom-modules) ;; the current session, but if you change an packages.el file in a module,
doom-packages nil) ;; there's no non-trivial way to detect that, so we give you a way to
(_load (concat doom-core-dir "core.el") nil 'interactive) ;; reload only doom-packages.
(_load (expand-file-name "init.el" doom-emacs-dir)) (when (eq force-p 'internal)
(when load-p (setq force-p nil
(mapc #'_load (file-expand-wildcards (expand-file-name "autoload/*.el" doom-core-dir))) doom-packages nil))
(_load (expand-file-name "init.el" doom-emacs-dir) nil 'interactive)))
;; `doom-packages'
(when (or force-p (not doom-packages)) (when (or force-p (not doom-packages))
(require 'quelpa) (setq doom-packages nil)
(setq doom-packages nil
quelpa-initialized-p nil)
(quelpa-setup-p)
(_load (expand-file-name "packages.el" doom-core-dir)) (_load (expand-file-name "packages.el" doom-core-dir))
(cl-loop for key being the hash-keys of doom-modules (cl-loop for key being the hash-keys of doom-modules
if (doom-module-path (car key) (cdr key) "packages.el") for path = (doom-module-expand-file (car key) (cdr key) "packages.el")
do (let ((doom--current-module key)) (_load it))) if (file-exists-p path)
do (let ((doom--current-module key)) (_load path)))
(cl-loop for dir in doom-psuedo-module-dirs (cl-loop for dir in doom-psuedo-module-dirs
for path = (expand-file-name "packages.el" dir) for path = (expand-file-name "packages.el" dir)
if (file-exists-p path) if (file-exists-p path)
do (_load path)))))) do (_load path)))
(defun doom-module-path (module submodule &optional file root) ;; `package-alist'
(when (or force-p (not (bound-and-true-p package-alist)))
(setq load-path doom-site-load-path)
(require 'package)
(setq package-activated-list nil)
(package-initialize))
;; `quelpa-cache'
(when (or force-p (not (bound-and-true-p quelpa-cache)))
(require 'quelpa)
(setq quelpa-initialized-p nil)
(or (quelpa-setup-p)
(error "Could not initialize quelpa"))))))
;;
;; Module API
;;
(defun doom-module-p (module submodule)
"Returns t if MODULE SUBMODULE is enabled (ie. present in `doom-modules')."
(and (hash-table-p doom-modules)
(gethash (cons module submodule) doom-modules)
t))
(defun doom-module-get (module submodule &optional property)
"Returns the plist for MODULE/SUBMODULE. If PROPERTY is set, get its property."
(when-let* ((plist (gethash (cons module submodule) doom-modules)))
(if property
(plist-get plist property)
plist)))
(defun doom-module-put (module submodule property value)
"TODO"
(when-let* ((plist (doom-module-get module submodule)))
(puthash (cons module submodule)
(plist-put plist property value)
doom-modules)))
(defun doom-module-set (module submodule &rest plist)
"Adds MODULE and SUBMODULE to `doom-modules' and sets its plist to PLIST,
which should contain a minimum of :flags and :path.
MODULE is a keyword, SUBMODULE is a symbol, PLIST is a plist that accepts the
following properties:
:flags [SYMBOL LIST] list of enabled module flags
:path [STRING] path to module root directory
Example:
(doom-module-set :lang 'haskell :flags '(+intero))
Used by `require!'."
(when plist
(let ((old-plist (doom-module-get module submodule)))
(unless (plist-member plist :flags)
(plist-put plist :flags (plist-get old-plist :flags)))
(unless (plist-member plist :path)
(plist-put plist :path (or (plist-get old-plist :path)
(doom-module-find-path module submodule))))))
(let ((key (cons module submodule)))
(puthash key plist doom-modules)))
(defun doom-module-find-path (module submodule &optional file)
"Get the full path to a module: e.g. :lang emacs-lisp maps to "Get the full path to a module: e.g. :lang emacs-lisp maps to
~/.emacs.d/modules/lang/emacs-lisp/ and will append FILE if non-nil." ~/.emacs.d/modules/lang/emacs-lisp/ and will append FILE if non-nil."
(when (keywordp module) (when (keywordp module)
(setq module (substring (symbol-name module) 1))) (setq module (substring (symbol-name module) 1)))
(when (symbolp submodule) (when (symbolp submodule)
(setq submodule (symbol-name submodule))) (setq submodule (symbol-name submodule)))
(if root
(expand-file-name (concat "modules/" module "/" submodule "/" file) root)
(cl-loop for default-directory in doom-modules-dirs (cl-loop for default-directory in doom-modules-dirs
for path = (concat module "/" submodule "/" file) for path = (concat module "/" submodule "/" file)
if (file-exists-p path) if (file-exists-p path)
return (expand-file-name path)))) return (expand-file-name path)))
(defun doom-module-from-path (&optional path) (defun doom-module-from-path (&optional path)
"Get module cons cell (MODULE . SUBMODULE) for PATH, if possible." "Get module cons cell (MODULE . SUBMODULE) for PATH, if possible."
@ -250,53 +337,24 @@ This aggressively reloads core autoload files."
(cons (intern (concat ":" module)) (cons (intern (concat ":" module))
(intern submodule))))))) (intern submodule)))))))
(defun doom-module-paths (&optional append-file) (defun doom-module-expand-file (module submodule &optional file)
"Like `expand-file-name', but expands FILE relative to MODULE (keywordp) and
SUBMODULE (symbol)"
(let ((path (doom-module-get module submodule :path)))
(if file
(expand-file-name file path)
path)))
(defun doom-module-load-path ()
"Returns a list of absolute file paths to activated modules, with APPEND-FILE "Returns a list of absolute file paths to activated modules, with APPEND-FILE
added, if the file exists." added, if the file exists."
(append (cl-loop for key being the hash-keys of doom-modules (append (cl-loop for plist being the hash-values of doom-modules
if (doom-module-path (car key) (cdr key) append-file) collect (plist-get plist :path))
collect it)
(cl-remove-if-not #'file-directory-p doom-psuedo-module-dirs))) (cl-remove-if-not #'file-directory-p doom-psuedo-module-dirs)))
(defun doom-module-flags (module submodule)
"Returns a list of flags provided for MODULE SUBMODULE."
(gethash (cons module submodule) doom-modules))
(defun doom-module-enabled-p (module submodule)
"Returns t if MODULE SUBMODULE is enabled (ie. present in `doom-modules')."
(and (hash-table-p doom-modules)
(doom-module-flags module submodule)
t))
(defun doom-module-enable (module submodule &optional flags)
"Adds MODULE and SUBMODULE to `doom-modules', overwriting it if it exists.
MODULE is a keyword, SUBMODULE is a symbol, FLAGS is a list of arbitrary
symbols:
(doom-module-enable :lang 'haskell '(+intero))
Used by `require!' and `depends-on!'."
(let ((key (cons module submodule)))
(puthash key
(or (doom-enlist flags)
(gethash key doom-modules)
'(t))
doom-modules)))
(defun doom-packages--benchmark ()
(format "Doom loaded %s packages across %d modules in %.03fs"
;; Certainly imprecise, especially where custom additions to
;; load-path are concerned, but I don't mind a [small] margin of
;; error in the plugin count in exchange for faster startup.
(- (length load-path) (length doom-site-load-path))
(hash-table-size doom-modules)
(or doom-init-time
(setq doom-init-time (float-time (time-subtract (current-time) before-init-time))))))
;; ;;
;; Macros ;; Module config macros
;; ;;
(autoload 'use-package "use-package" nil nil 'macro) (autoload 'use-package "use-package" nil nil 'macro)
@ -305,7 +363,7 @@ Used by `require!' and `depends-on!'."
"Bootstraps DOOM Emacs and its modules. "Bootstraps DOOM Emacs and its modules.
MODULES is an malformed plist of modules to load." MODULES is an malformed plist of modules to load."
(let (init-forms config-forms module file-name-handler-alist) (let (load-forms module file-name-handler-alist)
(let ((modules-dir (let ((modules-dir
(expand-file-name "modules/" (file-name-directory (or load-file-name byte-compile-current-file))))) (expand-file-name "modules/" (file-name-directory (or load-file-name byte-compile-current-file)))))
(cl-pushnew modules-dir doom-modules-dirs :test #'string=) (cl-pushnew modules-dir doom-modules-dirs :test #'string=)
@ -314,17 +372,19 @@ MODULES is an malformed plist of modules to load."
((not module) (error "No namespace specified in `doom!' for %s" m)) ((not module) (error "No namespace specified in `doom!' for %s" m))
((let ((submodule (if (listp m) (car m) m)) ((let ((submodule (if (listp m) (car m) m))
(flags (if (listp m) (cdr m)))) (flags (if (listp m) (cdr m))))
(doom-module-enable module submodule flags) (let ((path (doom-module-find-path module submodule)))
(let ((path (doom-module-path module submodule)) (doom-module-set module submodule :flags flags :path path)
(mod `(doom--current-module ',(cons module submodule)))) (push `(let ((doom--current-module ',(cons module submodule)))
(push `(let (,mod) (load! init ,path t)) init-forms) (load! init ,path t))
(push `(let (,mod) (load! config ,path t)) config-forms)))))) load-forms))))))
`(let (file-name-handler-alist) `(let (file-name-handler-alist)
(setq doom-modules ',doom-modules (setq doom-modules ',doom-modules
doom-modules-dirs ',doom-modules-dirs) doom-modules-dirs ',doom-modules-dirs)
,@(nreverse init-forms) (let ((doom--initializing t))
(push '(lambda () ,@(nreverse config-forms)) ,@(nreverse load-forms))
doom--delayed-modules))))) ,(unless doom--initializing
'(unless noninteractive
(doom-initialize-modules)))))))
(defmacro def-package! (name &rest plist) (defmacro def-package! (name &rest plist)
"A thin wrapper around `use-package'." "A thin wrapper around `use-package'."
@ -396,14 +456,17 @@ If NOERROR is non-nil, don't throw an error if the file doesn't exist."
(unless noerror (unless noerror
(error "Could not load file '%s' from '%s'" file path)))))) (error "Could not load file '%s' from '%s'" file path))))))
(defmacro require! (module submodule &optional flags reload-p) (defmacro require! (module submodule &optional flags path reload-p)
"Loads the module specified by MODULE (a property) and SUBMODULE (a symbol). "Loads the module specified by MODULE (a property) and SUBMODULE (a symbol).
The module is only loaded once. If RELOAD-P is non-nil, load it again." The module is only loaded once. If RELOAD-P is non-nil, load it again."
(when (or reload-p (not (doom-module-enabled-p module submodule))) (let ((enabled-p (doom-module-p module submodule)))
(let ((module-path (doom-module-path module submodule))) (when (or reload-p (not enabled-p))
(when (hash-table-p doom-modules) (if (not enabled-p)
(doom-module-enable module submodule flags)) (doom-module-set module submodule :flags flags :path path)
(if flags (doom-module-put module submodule :flags flags))
(if path (doom-module-put module submodule :path path)))
(let ((module-path (doom-module-expand-file module submodule)))
(if (file-directory-p module-path) (if (file-directory-p module-path)
`(condition-case-unless-debug ex `(condition-case-unless-debug ex
(let ((doom--current-module ',(cons module submodule))) (let ((doom--current-module ',(cons module submodule)))
@ -415,7 +478,7 @@ The module is only loaded once. If RELOAD-P is non-nil, load it again."
(car ex) ,module ',submodule (car ex) ,module ',submodule
(error-message-string ex)))) (error-message-string ex))))
(warn 'doom-modules :warning "Couldn't find module '%s %s'" (warn 'doom-modules :warning "Couldn't find module '%s %s'"
module submodule))))) module submodule))))))
(defmacro featurep! (module &optional submodule flag) (defmacro featurep! (module &optional submodule flag)
"Returns t if MODULE SUBMODULE is enabled. If FLAG is provided, returns t if "Returns t if MODULE SUBMODULE is enabled. If FLAG is provided, returns t if
@ -439,12 +502,12 @@ omitted. eg. (featurep! +flag1)"
module (car module-pair) module (car module-pair)
submodule (cdr module-pair)))) submodule (cdr module-pair))))
(if flag (if flag
(and (memq flag (doom-module-flags module submodule)) t) (and (memq flag (doom-module-get module submodule :flags)) t)
(doom-module-enabled-p module submodule))) (doom-module-p module submodule)))
;; ;;
;; Declarative macros ;; Module package macros
;; ;;
(defmacro package! (name &rest plist) (defmacro package! (name &rest plist)
@ -483,15 +546,18 @@ Accepts the following properties:
,(if (and pkg-pin t) `(map-put package-pinned-packages ',name ,pkg-pin)) ,(if (and pkg-pin t) `(map-put package-pinned-packages ',name ,pkg-pin))
(map-put doom-packages ',name ',plist)))) (map-put doom-packages ',name ',plist))))
(defmacro depends-on! (module submodule) (defmacro depends-on! (module submodule &optional flags)
"Declares that this module depends on another. "Declares that this module depends on another.
Only use this macro in a module's packages.el file. Only use this macro in a module's packages.el file.
MODULE is a keyword, and SUBMODULE is a symbol. Under the hood, this simply MODULE is a keyword, and SUBMODULE is a symbol. Under the hood, this simply
loads MODULE SUBMODULE's packages.el file." loads MODULE SUBMODULE's packages.el file."
(doom-module-enable module submodule) `(let ((doom-modules ,doom-modules)
`(load! packages ,(doom-module-path module submodule) t)) (flags ,flags))
(when flags
(doom-module-put ,module ',submodule :flags flags))
(load! packages ,(doom-module-find-path module submodule) t)))
;; ;;
@ -558,42 +624,44 @@ This should be run whenever init.el or an autoload file is modified. Running
;; This function must not use autoloaded functions or external dependencies. ;; This function must not use autoloaded functions or external dependencies.
;; It must assume nothing is set up! ;; It must assume nothing is set up!
(if (not noninteractive) (if (not noninteractive)
;; This is done in another instance to protect the current session's ;; This is done in another instance to protect the current session's state
;; state. `doom-initialize-packages' will have side effects otherwise. ;; in case this function has side effects.
(progn (progn
(doom-packages--async-run 'doom//reload-autoloads) (doom-packages--async-run 'doom//reload-autoloads)
(load doom-autoload-file t nil t)) (load doom-autoload-file t nil t))
(doom-initialize-packages t) (let ((default-directory doom-emacs-dir)
(let ((targets (targets
(file-expand-wildcards (file-expand-wildcards
(expand-file-name "autoload/*.el" doom-core-dir)))) (expand-file-name "autoload/*.el" doom-core-dir))))
(dolist (path (append doom-psuedo-module-dirs (doom-module-paths))) (dolist (path (doom-module-load-path))
(let ((auto-dir (expand-file-name "autoload" path)) (let ((auto-dir (expand-file-name "autoload" path))
(auto-file (expand-file-name "autoload.el" path))) (auto-file (expand-file-name "autoload.el" path)))
(when (file-exists-p auto-file) (when (file-exists-p auto-file)
(push (file-truename auto-file) targets)) (push auto-file targets))
(when (file-directory-p auto-dir) (when (file-directory-p auto-dir)
(dolist (file (doom-packages--files auto-dir "\\.el$")) (dolist (file (doom-packages--files auto-dir "\\.el$"))
(push (file-truename file) targets))))) (push file targets)))))
(when (file-exists-p doom-autoload-file) (when (file-exists-p doom-autoload-file)
(delete-file doom-autoload-file) (delete-file doom-autoload-file)
(message "Deleted old autoloads.el")) (message "Deleted old autoloads.el"))
(dolist (file (reverse targets)) (message "Generating new autoloads.el")
(dolist (file (mapcar #'file-truename (reverse targets)))
(let ((generated-autoload-load-name file))
(message (message
(cond ((not (doom-packages--read-if-cookies file)) (cond ((not (doom-packages--read-if-cookies file))
"⚠ Ignoring %s") "⚠ Ignoring %s")
((update-file-autoloads file nil doom-autoload-file) ((update-file-autoloads file nil doom-autoload-file)
"✕ Nothing in %s") "✕ Nothing in %s")
(t ("✓ Scanned %s"))
"✓ Scanned %s")) (if (file-in-directory-p file default-directory)
(if (file-in-directory-p file doom-emacs-dir) (file-relative-name file)
(file-relative-name file doom-emacs-dir) (abbreviate-file-name file)))))
(abbreviate-file-name file))))
(make-directory (file-name-directory doom-autoload-file) t) (make-directory (file-name-directory doom-autoload-file) t)
(let ((buf (find-file-noselect doom-autoload-file t)) (let ((buf (find-file-noselect doom-autoload-file t))
(load-path (append (list doom-emacs-dir) (load-path (append (list doom-emacs-dir)
doom-psuedo-module-dirs doom-psuedo-module-dirs
doom-modules-dirs)) doom-modules-dirs
load-path))
current-sexp) current-sexp)
(unwind-protect (unwind-protect
(condition-case-unless-debug ex (condition-case-unless-debug ex
@ -604,17 +672,18 @@ This should be run whenever init.el or an autoload file is modified. Running
(nth 3 (syntax-ppss))) (nth 3 (syntax-ppss)))
;; Replace autoload paths with absolute paths for faster ;; Replace autoload paths with absolute paths for faster
;; resolution during load and simpler `load-path' ;; resolution during load and simpler `load-path'
(when (eq (sexp-at-point) 'autoload) (when (memq (sexp-at-point) '(autoload custom-autoload))
(save-excursion (save-excursion
(forward-sexp 2) (forward-sexp 2)
(let ((pt (point))) (let ((pt (point)))
(forward-sexp 1) (forward-sexp 1)
(when-let* ((sexp (thing-at-point 'sexp t)) (when-let* ((sexp (thing-at-point 'sexp t))
(path (eval (read sexp) t))) (path (eval (read sexp) t)))
(when (and (stringp path) (not (file-name-absolute-p path)))
(delete-region pt (point)) (delete-region pt (point))
(if-let* ((lib (locate-library path))) (if-let* ((lib (locate-library path)))
(insert " \"" (file-name-sans-extension lib) "\"") (insert " \"" (file-name-sans-extension lib) "\"")
(warn "Couldn't find absolute path for: %s" path)))))) (warn "Couldn't find absolute path for: %s" path)))))))
;; Run each form in autoloads to see if there are any ;; Run each form in autoloads to see if there are any
;; errors. We do it piecemeal because that will tell us ;; errors. We do it piecemeal because that will tell us
;; more about where the issue originated. ;; more about where the issue originated.
@ -624,7 +693,7 @@ This should be run whenever init.el or an autoload file is modified. Running
(eval current-sexp t)) (eval current-sexp t))
(forward-char))) (forward-char)))
(save-buffer) (save-buffer)
(message "Finished generating autoloads.el!")) (message "Done!"))
('error ('error
(delete-file doom-autoload-file) (delete-file doom-autoload-file)
(error "Error in autoloads.el: (%s %s ...) %s -- %s" (error "Error in autoloads.el: (%s %s ...) %s -- %s"
@ -651,21 +720,32 @@ If RECOMPILE-P is non-nil, only recompile out-of-date files."
(interactive (interactive
(list nil current-prefix-arg)) (list nil current-prefix-arg))
(let ((default-directory doom-emacs-dir) (let ((default-directory doom-emacs-dir)
(recompile-p (or recompile-p (recompile-p (or recompile-p (and (member "-r" (cdr argv)) t))))
(and (member "-r" (cdr argv)) t))))
(if (not noninteractive) (if (not noninteractive)
;; This is done in another instance to protect the current session's ;; This is done in another instance to protect the current session's
;; state. `doom-initialize-packages' will have side effects otherwise. ;; state, because this function has side effects.
(doom-packages--async-run 'doom//byte-compile) (doom-packages--async-run 'doom//byte-compile)
(let ((total-ok 0) (let ((total-ok 0)
(total-fail 0) (total-fail 0)
(total-noop 0) (total-noop 0)
(modules (or modules (cdr argv))) (modules (or modules (cdr argv)))
compile-targets) compile-targets)
(doom-initialize-packages t t) ;; Ensure that Doom has been fully loaded, some of its state may be
;; pertinent to files compiled later.
(let (noninteractive)
;; Core libraries aren't fully loaded in a noninteractive session, so
;; we reload it with `noninteractive' set to nil to force them to.
(load (expand-file-name "core.el" doom-core-dir) nil t t)
;; In case autoloads.el hasn't been properly generated at this point.
(dolist (file (file-expand-wildcards (expand-file-name "autoload/*.el" doom-core-dir)))
(load file t t t)))
(doom-initialize-modules)
;; Assemble el files we want to compile; taking into account that
;; MODULES may be a list of MODULE/SUBMODULE strings from the command
;; line.
(setq compile-targets (setq compile-targets
(cl-loop for target (cl-loop for target
in (or modules (append (list doom-core-dir) (doom-module-paths))) in (or modules (append (list doom-core-dir) (doom-module-load-path)))
if (equal target "core") if (equal target "core")
nconc (nreverse (doom-packages--files doom-core-dir "\\.el$")) nconc (nreverse (doom-packages--files doom-core-dir "\\.el$"))
else if (file-directory-p target) else if (file-directory-p target)
@ -674,7 +754,8 @@ If RECOMPILE-P is non-nil, only recompile out-of-date files."
nconc (nreverse (doom-packages--files it "\\.el$")) nconc (nreverse (doom-packages--files it "\\.el$"))
else if (string-match "^\\([^/]+\\)/\\([^/]+\\)$" target) else if (string-match "^\\([^/]+\\)/\\([^/]+\\)$" target)
nconc (nreverse (doom-packages--files nconc (nreverse (doom-packages--files
(doom-module-path (intern (format ":%s" (match-string 1 target))) (doom-module-find-path
(intern (format ":%s" (match-string 1 target)))
(intern (match-string 2 target))) (intern (match-string 2 target)))
"\\.el$")) "\\.el$"))
else if (file-exists-p target) else if (file-exists-p target)
@ -682,11 +763,10 @@ If RECOMPILE-P is non-nil, only recompile out-of-date files."
finally do (setq argv nil))) finally do (setq argv nil)))
(unless compile-targets (unless compile-targets
(error "No targets to compile")) (error "No targets to compile"))
(condition-case ex
(let ((use-package-expand-minimally t)) (let ((use-package-expand-minimally t))
(push (expand-file-name "init.el" doom-emacs-dir) compile-targets) (push (expand-file-name "init.el" doom-emacs-dir) compile-targets)
(condition-case ex (dolist (target (cl-delete-duplicates (mapcar #'file-truename compile-targets) :test #'string=))
(progn
(dolist (target (cl-delete-duplicates compile-targets :test #'string= :key #'file-truename))
(when (or (not recompile-p) (when (or (not recompile-p)
(let ((elc-file (byte-compile-dest-file target))) (let ((elc-file (byte-compile-dest-file target)))
(and (file-exists-p elc-file) (and (file-exists-p elc-file)
@ -721,7 +801,7 @@ If RECOMPILE-P is non-nil, only recompile out-of-date files."
(error-message-string ex) (error-message-string ex)
"Reverting changes...") "Reverting changes...")
(doom//clean-byte-compiled-files) (doom//clean-byte-compiled-files)
(message! (green "Finished (nothing was byte-compiled)"))))))))) (message! (green "Finished (nothing was byte-compiled)"))))))))
(defun doom//byte-compile-core (&optional recompile-p) (defun doom//byte-compile-core (&optional recompile-p)
"Byte compile the core Doom files. "Byte compile the core Doom files.
@ -752,24 +832,23 @@ If RECOMPILE-P is non-nil, only recompile out-of-date core files."
"Delete all the compiled elc files in your Emacs configuration. This excludes "Delete all the compiled elc files in your Emacs configuration. This excludes
compiled packages.'" compiled packages.'"
(interactive) (interactive)
(ignore-errors (doom-initialize-packages t)) (unless
(let ((targets (cl-loop with default-directory = doom-emacs-dir
(append (list (expand-file-name "init.elc" doom-emacs-dir)) for path
in (append (file-expand-wildcards "*.elc" t)
(doom-packages--files doom-core-dir "\\.elc$") (doom-packages--files doom-core-dir "\\.elc$")
(cl-loop for dir in (append doom-modules-dirs doom-psuedo-module-dirs) (cl-loop for dir in (doom-module-load-path)
if (file-directory-p dir) nconc (doom-packages--files dir "\\.elc$")))
nconc (doom-packages--files dir "\\.elc$")))) for truepath = (file-truename path)
(default-directory doom-emacs-dir)) if (file-exists-p truepath)
(unless (cl-loop for path in targets
if (file-exists-p path)
collect path collect path
and do (delete-file path) and do (delete-file truepath)
and do and do
(message "✓ Deleted %s" (message "✓ Deleted %s"
(if (file-in-directory-p path doom-emacs-dir) (if (file-in-directory-p truepath default-directory)
(file-relative-name path) (file-relative-name truepath)
(abbreviate-file-name path)))) (abbreviate-file-name path))))
(message "Everything is clean")))) (message "Everything is clean")))
;; ;;

View file

@ -25,7 +25,7 @@
"If non-nil, all doom functions will be verbose. Set DEBUG=1 in the command "If non-nil, all doom functions will be verbose. Set DEBUG=1 in the command
line or use --debug-init to enable this.") line or use --debug-init to enable this.")
(defvar doom-emacs-dir (file-truename user-emacs-directory) (defvar doom-emacs-dir (eval-when-compile (file-truename user-emacs-directory))
"The path to this emacs.d directory.") "The path to this emacs.d directory.")
(defvar doom-core-dir (concat doom-emacs-dir "core/") (defvar doom-core-dir (concat doom-emacs-dir "core/")
@ -119,8 +119,6 @@ Use this for essential functionality.")
"A list of hooks run after DOOM initialization is complete, and after "A list of hooks run after DOOM initialization is complete, and after
`doom-init-hook'. Use this for extra, non-essential functionality.") `doom-init-hook'. Use this for extra, non-essential functionality.")
(defvar doom--delayed-modules nil)
(defun doom-try-run-hook (fn hook) (defun doom-try-run-hook (fn hook)
"Runs a hook wrapped in a `condition-case-unless-debug' block; its objective "Runs a hook wrapped in a `condition-case-unless-debug' block; its objective
is to include more information in the error message, without sacrificing your is to include more information in the error message, without sacrificing your
@ -159,35 +157,28 @@ ability to invoke the debugger in debug mode."
(load! core-keybinds)) ; centralized keybind system + which-key (load! core-keybinds)) ; centralized keybind system + which-key
(defun doom|after-init () (defun doom|after-init ()
"Load the config.el file of all pending modules that have been enabled by a
recent `doom!' call. This should be attached to `after-init-hook'."
(mapc #'funcall (reverse doom--delayed-modules))
(setq doom--delayed-modules nil))
(defun doom|after-startup ()
"Run `doom-init-hook' and `doom-post-init-hook', start the Emacs server, and "Run `doom-init-hook' and `doom-post-init-hook', start the Emacs server, and
display the loading benchmark." display the loading benchmark."
(unless (or (not after-init-time) noninteractive)
(dolist (hook '(doom-init-hook doom-post-init-hook)) (dolist (hook '(doom-init-hook doom-post-init-hook))
(run-hook-wrapped hook #'doom-try-run-hook hook)) (run-hook-wrapped hook #'doom-try-run-hook hook))
(when (display-graphic-p) (when (display-graphic-p)
(require 'server) (require 'server)
(unless (server-running-p) (unless (server-running-p)
(server-start))) (server-start))))
(message "%s" (doom-packages--benchmark))))
(defun doom|finalize () (defun doom|finalize ()
"Resets garbage collection settings to reasonable defaults (if you don't do "Resets garbage collection settings to reasonable defaults (if you don't do
this, you'll get stuttering and random freezes), and resets this, you'll get stuttering and random freezes), and resets
`file-name-handler-alist'." `file-name-handler-alist'."
(unless noninteractive
(message "%s" (doom-packages--benchmark)))
(setq gc-cons-threshold 16777216 (setq gc-cons-threshold 16777216
gc-cons-percentage 0.1 gc-cons-percentage 0.1
file-name-handler-alist doom--file-name-handler-alist) file-name-handler-alist doom--file-name-handler-alist)
t) t)
(add-hook! '(emacs-startup-hook doom-reload-hook) #'doom|finalize) (add-hook! '(emacs-startup-hook doom-reload-hook) #'doom|finalize)
(add-hook 'after-init-hook #'doom|after-init) (add-hook 'emacs-startup-hook #'doom|after-init))
(add-hook 'emacs-startup-hook #'doom|after-startup))
;; ;;