fix: add :depth field to modules

This introduces a depth field for modules so that they may dictate their
load order explicitly, it also treats depths <= -100 or >= 100 as
special depths, which will be loaded early, before their respective
doom-{before,after}-module-{init,config}-hook. This permits psuedo
modules like :core and :user modules to be treated as normal modules
without too many special cases.

This also fixes a module load order issue on Emacs 29 (#6813), caused by
emacs-mirror/emacs@4311bd0bd7, which changed the return value order of
hash-table-{keys,values} causing modules to be loaded in reverse order;
resulting in the loss of evil keybinds, among other things.

Other notable changes:
- Changes the data structure for module data caches from a list to a
  vector. Uses less memory and permits faster lookups. Also adds two
  depth fields to the front of it.
- Changes the signature of doom-module-list and doom-package-list.
- Renames doom--read-packages -> doom-packages--read for consistency
  with naming convention.
- Add doom-module-depth function.
- Adds a temporary doom-core-dir/init.el file, which is responsible for
  loading doom-*.el.

Fix: #6813
Ref: emacs-mirror/emacs@4311bd0bd7
This commit is contained in:
Henrik Lissner
2022-09-23 18:55:20 +02:00
parent 772f9f26d9
commit 5a5195b84d
9 changed files with 250 additions and 178 deletions

View File

@ -107,6 +107,18 @@ your `doom!' block, a warning is emitted before replacing it with :emacs vc and
(and (memq flag (plist-get plist :flags))
t))))
(defun doom-module-depth (category module &optional initdepth?)
"Return the depth of CATEGORY MODULE.
If INITDEPTH? is non-nil, use the CAR if a module was given two depths (see
`doom-module-set')."
(if-let (depth (doom-module-get category module :depth))
(or (if initdepth?
(car-safe depth)
(cdr-safe depth))
depth)
0))
(defun doom-module-get (category module &optional property)
"Returns the plist for CATEGORY MODULE. Gets PROPERTY, specifically, if set."
(declare (pure t) (side-effect-free t))
@ -138,24 +150,76 @@ of PROPERTY and VALUEs.
CATEGORY is a keyword, module is a symbol, PLIST is a plist that accepts the
following properties:
:flags [SYMBOL LIST] list of enabled category flags
:path [STRING] path to category root directory
:path STRING
Path to the directory where this module lives.
:depth INT|(INITDEPTH . CONFIGDEPTH)
Determines module load order. If a cons cell, INITDEPTH determines the load
order of the module's init.el, while CONFIGDEPTH determines the same for all
other config files (config.el, packages.el, doctor.el, etc).
:flags (SYMBOL...)
A list of activated flags for this module.
:features (SYMBOL...)
A list of active features, determined from module's metadata. NOT
IMPLEMENTED YET.
If PLIST consists of a single nil, unset and disable CATEGORY MODULE.
Example:
(doom-module-set :lang 'haskell :flags '(+lsp))"
If PLIST consists of a single nil, the module is purged from memory instead."
(if (car plist)
(progn
;; Doom caches flags and features using symbol plists for fast lookups in
;; `modulep!'. plists lack the overhead, and are much faster for datasets this
;; small. The format of this case is (cons FEATURES FLAGS)
(put category module (cons t (plist-get plist :flags)))
;; PERF: Doom caches module index, flags, and features in symbol plists
;; for fast lookups in `modulep!' and elsewhere. plists are lighter
;; and faster than hash tables for datasets this size, and this
;; information is looked up *very* often.
(put category module
(let ((depth (ensure-list (or (plist-get plist :depth) 0))))
(cl-destructuring-bind (i j)
(with-memoization (get 'doom-modules depth) '(0 0))
(dolist (n (list i j))
(when (> n 999)
;; No one will have more than 999 modules at any single
;; depth enabled, right? ...Right?
(signal 'doom-module-error
(list (cons category module) "Over 999 module limit" n))))
(put 'doom-modules depth (list (1+ i) (1+ j)))
(vector (+ (* (or (cdr depth) (car depth)) 1000) j)
(+ (* (car depth) 1000) i)
(plist-get plist :flags)
(plist-get plist :features)))))
;; But the hash table will always been Doom's formal storage for modules.
(puthash (cons category module) plist doom-modules))
(remhash (cons category module) doom-modules)
(cl-remf (symbol-plist category) module)))
(defun doom-module-list (&optional paths-or-all initorder?)
"Return a list of (:group . name) module keys in order of their :depth.
PATHS-OR-ALL can either be a non-nil value or a list of directories. If given a
list of directories, return a list of module keys for all modules present
underneath it. If non-nil, return the same, but search `doom-modules-dirs'
(includes :core and :user). Modules that are enabled are sorted first by their
:depth, followed by disabled modules in lexicographical order (unless a :depth
is specified in their .doommodule).
If INITORDER? is non-nil, sort modules by their initdepth, rather than their
configdepth. See `doom-module-set' for details."
(sort (if paths-or-all
(delete-dups
(append (seq-remove #'cdr (doom-module-list nil initorder?))
(doom-files-in (if (listp paths-or-all)
paths-or-all
doom-modules-dirs)
:map #'doom-module-from-path
:type 'dirs
:mindepth 1
:depth 1)))
(hash-table-keys doom-modules))
(let ((idx (if initorder? 1 0)))
(lambda! ((groupa . namea) (groupb . nameb))
(let ((a (get groupa namea))
(b (get groupb nameb)))
(or (null b)
(if a (< (aref a idx)
(aref b idx)))))))))
(defun doom-module-expand-path (category module &optional file)
"Expands a path to FILE relative to CATEGORY and MODULE.
@ -223,17 +287,8 @@ The list is in no particular order and its file paths are absolute. If
MODULE-DIRS is non-nil, include all modules (even disabled ones) available in
those directories."
(declare (pure t) (side-effect-free t))
(if module-dirs
(mapcar (lambda (m) (doom-module-locate-path (car m) (cdr m)))
(delete-dups
(doom-files-in module-dirs
:map #'doom-module-from-path
:type 'dirs
:mindepth 1
:depth 1)))
(delq
nil (cl-loop for (cat . mod) in (cddr (doom-module-list))
collect (doom-module-get cat mod :path)))))
(cl-loop for (cat . mod) in (doom-module-list module-dirs)
collect (doom-module-locate-path cat mod)))
(defun doom-module-mplist-map (fn mplist)
"Apply FN to each module in MPLIST."
@ -289,17 +344,6 @@ those directories."
(setq doom-inhibit-module-warnings t))
(nreverse results)))
(defun doom-module-list (&optional all-p)
"Return modules as a list of (:CATEGORY . MODULE) in their enabled order.
If ALL-P, return a list of *all* available modules instead, whether or not
they're enabled, and in lexicographical order.
If ALL-P is `real', only return *real"
(if all-p
(mapcar #'doom-module-from-path (doom-module-load-path doom-modules-dirs))
(hash-table-keys doom-modules)))
;;
;;; Use-package modifications
@ -516,6 +560,7 @@ WARNINGS:
;; DEPRECATED Remove in 3.0
(define-obsolete-function-alias 'featurep! 'modulep! "3.0.0")
(defvar doom--empty-module [nil nil nil nil])
(defmacro modulep! (category &optional module flag)
"Return t if :CATEGORY MODULE (and +FLAGS) are enabled.
@ -529,14 +574,22 @@ source (except your DOOMDIR, which is a special module). Like so:
(modulep! +flag)
For more about modules and flags, see `doom!'."
(and (cond (flag (memq flag (cdr (get category module))))
;; PERF: This macro bypasses the module API to spare startup their runtime
;; cost, as `modulep!' gets called *a lot* during startup. In the future,
;; Doom will byte-compile its core files. At that time, we can use it again.
(and (cond (flag (memq flag (aref (or (get category module) doom--empty-module) 2)))
(module (get category module))
(doom--current-flags (memq category doom--current-flags))
(doom--current-module
(memq category (cdr (get (car doom--current-module)
(cdr doom--current-module)))))
(memq category
(aref (or (get (car doom--current-module)
(cdr doom--current-module))
doom--empty-module)
2)))
((if-let (module (doom-module-from-path (macroexpand '(file!))))
(memq category (cdr (get (car module) (cdr module))))
(memq category (aref (or (get (car module) (cdr module))
doom--empty-module)
2))
(error "(modulep! %s %s %s) couldn't figure out what module it was called from (in %s)"
category module flag (file!)))))
t))
@ -547,8 +600,8 @@ For more about modules and flags, see `doom!'."
;; Register Doom's two virtual module categories, representing Doom's core and
;; the user's config; which are always enabled.
(doom-module-set :core nil :path doom-core-dir)
(doom-module-set :user nil :path doom-user-dir)
(doom-module-set :core nil :path doom-core-dir :depth -110)
(doom-module-set :user nil :path doom-user-dir :depth '(-105 . 105))
(provide 'doom-modules)
;;; doom-modules.el ends here