refactor: introduce doom-context

Introduces a system to announce what execution contexts are active, so I
can react appropriately, emit more helpful logs/warnings in the case of
issues, and throw more meaningful errors.

* bin/doom: load module CLIs in the 'modules' context.
* lisp/cli/doctor.el: load package files in 'packages' context.
* lisp/doom-cli.el:
  - (doom-before-init-hook, doom-after-init-hook): trigger hooks at the
    correct time. This may increase startup load time, as the benchmark
    now times more of the startup process.
  - (doom-cli-execute, doom-cli-context-execute,
    doom-cli-context-restore, doom-cli-context-parse,
    doom-cli--output-benchmark-h, doom-cli-call, doom-cli--restart,
    doom-cli-load, run!): remove redundant context prefix in debug logs,
    it's now redundant with doom-context, which doom-log now prefixes
    them with.
* lisp/doom-lib.el (doom-log): prefix doom-context to doom-log output,
  unless it starts with :.
* lisp/doom-packages.el (package!, doom-packages--read): throw error if
  not used in a packages.el file or in the context of our package
  manager.
* lisp/doom-profiles.el (doom-profile--generate-init-vars,
  doom-profile--generate-load-modules): use modules doom-context instead
  of doom-init-time to detect startup.
* lisp/doom-start.el (doom-load-packages-incrementally-h): move function
  closer to end of doom-after-init-hook.
* lisp/doom.el:
  - (doom-before-init-hook, doom--set-initial-values-h,
    doom--begin-init-h): rename doom--set-initial-values-h to
    doom--begin-init-h and ensure it runs as late in
    doom-before-init-hook as possible, as that is the point where Doom's
    "initialization" formally begins.
  - (doom-after-init-hook): don't trigger at the end of command-line-1
    in non-interactive sessions. This will be triggered manually in
    doom-cli.el's run!.
* lisp/lib/config.el (doom/reload, doom/reload-autoloads,
  doom/reload-env): use 'reload' context for reload commands.
* modules/lang/emacs-lisp/autoload.el (+emacs-lisp-eval): use 'eval'
  context.
* modules/lang/org/config.el: remove doom-reloading-p; check for
  'reload' doom context instead.
This commit is contained in:
Henrik Lissner 2022-09-24 12:38:25 +02:00
parent 1c4217aa27
commit f9201eb218
No known key found for this signature in database
GPG key ID: B60957CA074D39A3
12 changed files with 290 additions and 210 deletions

View file

@ -304,10 +304,11 @@ SEE ALSO:
(let ((cli-file "cli.el")) (let ((cli-file "cli.el"))
(defcli-group! "Module commands" (defcli-group! "Module commands"
(dolist (key (doom-module-list)) (doom-context-with 'modules
(when-let (path (doom-module-locate-path (car key) (cdr key) cli-file)) (dolist (key (doom-module-list))
(defcli-group! :prefix (if (cdr key) (format "+%s" (cdr key))) (when-let (path (doom-module-locate-path (car key) (cdr key) cli-file))
(doom-load (file-name-sans-extension path))))))) (defcli-group! :prefix (if (cdr key) (format "+%s" (cdr key)))
(doom-load (file-name-sans-extension path))))))))
;; Allow per-project Doom settings in .doom files. ;; Allow per-project Doom settings in .doom files.
(let (doomrc) (let (doomrc)

View file

@ -247,10 +247,11 @@ in."
(doctor-file (doom-module-expand-path (car key) (cdr key) "doctor.el")) (doctor-file (doom-module-expand-path (car key) (cdr key) "doctor.el"))
(packages-file (doom-module-expand-path (car key) (cdr key) doom-module-packages-file))) (packages-file (doom-module-expand-path (car key) (cdr key) doom-module-packages-file)))
(cl-loop with doom-output-indent = 6 (cl-loop with doom-output-indent = 6
for name in (let* (doom-packages for name in (doom-context-with 'packages
doom-disabled-packages) (let* (doom-packages
(load packages-file 'noerror 'nomessage) doom-disabled-packages)
(mapcar #'car doom-packages)) (load packages-file 'noerror 'nomessage)
(mapcar #'car doom-packages)))
unless (or (doom-package-get name :disable) unless (or (doom-package-get name :disable)
(eval (doom-package-get name :ignore)) (eval (doom-package-get name :ignore))
(plist-member (doom-package-get name :recipe) :local-repo) (plist-member (doom-package-get name :recipe) :local-repo)

View file

@ -72,9 +72,7 @@
;; Ensure straight and core packages are ready to go for CLI commands. ;; Ensure straight and core packages are ready to go for CLI commands.
(require 'doom-modules) (require 'doom-modules)
(require 'doom-packages) (require 'doom-packages)
(require 'doom-profiles) (require 'doom-profiles))
;; For any last-minute initialization.
(run-hooks 'doom-before-init-hook))
;; ;;
@ -298,7 +296,7 @@ the return value of the executed CLI.")
BINDINGS is an alist of (SYMBOL . VALUE) to bind lexically during CLI's BINDINGS is an alist of (SYMBOL . VALUE) to bind lexically during CLI's
execution. Can be generated from a `doom-cli-context' with execution. Can be generated from a `doom-cli-context' with
`doom-cli--bindings'." `doom-cli--bindings'."
(doom-log "cli-execute: %s %s" (doom-cli-key cli) bindings) (doom-log "execute: %s %s" (doom-cli-key cli) bindings)
(funcall (doom-cli-fn cli) cli bindings)) (funcall (doom-cli-fn cli) cli bindings))
(defun doom-cli-key (cli) (defun doom-cli-key (cli)
@ -725,7 +723,7 @@ executable context."
(let* ((command (doom-cli-context-command context)) (let* ((command (doom-cli-context-command context))
(cli (doom-cli-get command t)) (cli (doom-cli-get command t))
(prefix (doom-cli-context-prefix context))) (prefix (doom-cli-context-prefix context)))
(doom-log "cli-context-execute: %s" (doom-log "context-execute: %s"
(mapconcat #'doom-cli-command-string (mapconcat #'doom-cli-command-string
(delq nil (list (car (doom-cli-context-path context)) command)) (delq nil (list (car (doom-cli-context-path context)) command))
" -> ")) " -> "))
@ -786,7 +784,7 @@ executable context."
(_ old-value)))))) (_ old-value))))))
(run-hook-with-args 'doom-cli-create-context-functions context) (run-hook-with-args 'doom-cli-create-context-functions context)
(delete-file file) (delete-file file)
(doom-log "cli-context-restore: %s" (doom-cli-context-pid context)))) (doom-log "context-restore: %s" (doom-cli-context-pid context))))
context) context)
(defun doom-cli-context-parse (args context) (defun doom-cli-context-parse (args context)
@ -801,7 +799,7 @@ executable context."
(save-match-data (save-match-data
(cond (cond
((equal arg "--") ((equal arg "--")
(doom-log "cli-context-parse: found arg separator" arg) (doom-log "context-parse: found arg separator" arg)
(setq arguments (cdr args) (setq arguments (cdr args)
args nil)) args nil))
@ -814,7 +812,7 @@ executable context."
((and (stringp arg) ((and (stringp arg)
(or (string-match "^\\(--\\w[a-z0-9-_]+\\)\\(?:=\\(.*\\)\\)?$" arg) (or (string-match "^\\(--\\w[a-z0-9-_]+\\)\\(?:=\\(.*\\)\\)?$" arg)
(string-match "^\\(-[^-]\\)$" arg))) (string-match "^\\(-[^-]\\)$" arg)))
(doom-log "cli-context-parse: found switch %S" arg) (doom-log "context-parse: found switch %S" arg)
(catch :skip (catch :skip
(let* ((fullflag (match-string 1 arg)) (let* ((fullflag (match-string 1 arg))
(normflag (if (string-prefix-p "--no-" fullflag) (normflag (if (string-prefix-p "--no-" fullflag)
@ -822,7 +820,7 @@ executable context."
fullflag)) fullflag))
(option (or (doom-cli-context-find-option context normflag) (option (or (doom-cli-context-find-option context normflag)
(when (member fullflag '("-?" "--help" "--version")) (when (member fullflag '("-?" "--help" "--version"))
(doom-log "cli-context-parse: found help switch %S" arg) (doom-log "context-parse: found help switch %S" arg)
(setf (doom-cli-context-meta-p context) fullflag) (setf (doom-cli-context-meta-p context) fullflag)
(throw :skip t)) (throw :skip t))
(when rest? (when rest?
@ -866,11 +864,11 @@ executable context."
(cli (doom-cli-get command t)) (cli (doom-cli-get command t))
(rcli (doom-cli-get command)) (rcli (doom-cli-get command))
(key (doom-cli-key rcli))) (key (doom-cli-key rcli)))
(doom-log "cli-context-parse: found command %s" command) (doom-log "context-parse: found command %s" command)
;; Show warnings depending on CLI plists ;; Show warnings depending on CLI plists
(when (doom-cli-alias cli) (when (doom-cli-alias cli)
(dolist (pcli (doom-cli-path cli)) (dolist (pcli (doom-cli-path cli))
(doom-log "cli-context-parse: path += %s" (doom-cli-key pcli)) (doom-log "context-parse: path += %s" (doom-cli-key pcli))
(push (doom-cli-key pcli) (doom-cli-context-path context)))) (push (doom-cli-key pcli) (doom-cli-context-path context))))
;; Collect &rest for this command ;; Collect &rest for this command
(setf (doom-cli-context-command context) key (setf (doom-cli-context-command context) key
@ -893,7 +891,7 @@ executable context."
t)) t))
((push arg arguments) ((push arg arguments)
(doom-log "cli-context-parse: found arg %S" arg))))) (doom-log "context-parse: found arg %S" arg)))))
(setf (alist-get t (doom-cli-context-arguments context)) (setf (alist-get t (doom-cli-context-arguments context))
(append (alist-get t (doom-cli-context-arguments context)) (append (alist-get t (doom-cli-context-arguments context))
@ -1111,7 +1109,7 @@ command takes >5s to run. If :benchmark is explicitly set to nil (or
`doom-cli-benchmark-threshold' is nil), under no condition should a benchmark be `doom-cli-benchmark-threshold' is nil), under no condition should a benchmark be
shown." shown."
(doom-cli-redirect-output context (doom-cli-redirect-output context
(doom-log "cli: %s (GCs: %d, elapsed: %.6fs)" (doom-log "%s (GCs: %d, elapsed: %.6fs)"
(if (= doom-cli--exit-code 254) "Restarted" "Finished") (if (= doom-cli--exit-code 254) "Restarted" "Finished")
gcs-done gc-elapsed) gcs-done gc-elapsed)
(when-let* ((init-time (doom-cli-context-init-time context)) (when-let* ((init-time (doom-cli-context-init-time context))
@ -1144,8 +1142,8 @@ If ERROR is provided, store the error in CONTEXT, in case a later CLI wants to
read/use it (e.g. like a :help CLI)." read/use it (e.g. like a :help CLI)."
(let ((oldcommand (doom-cli-context-command context))) (let ((oldcommand (doom-cli-context-command context)))
(if oldcommand (if oldcommand
(doom-log "cli-call: %s -> %s" oldcommand args) (doom-log "call: %s -> %s" oldcommand args)
(doom-log "cli-call: %s" oldcommand args)) (doom-log "call: %s" oldcommand args))
(when error (when error
(setf (doom-cli-context-error context) error)) (setf (doom-cli-context-error context) error))
(setf (doom-cli-context-command context) nil (setf (doom-cli-context-command context) nil
@ -1183,7 +1181,7 @@ Emacs' batch library lacks an implementation of the exec system call."
(shell-quote-argument (match-string 2 env))))))) (shell-quote-argument (match-string 2 env)))))))
(cl-incf (doom-cli-context-step context)) (cl-incf (doom-cli-context-step context))
(with-file-modes #o600 (with-file-modes #o600
(doom-log "cli:restart: writing context to %s" context-file) (doom-log "restart: writing context to %s" context-file)
(doom-file-write (doom-file-write
context-file (let ((newcontext (copy-doom-cli-context context)) context-file (let ((newcontext (copy-doom-cli-context context))
(print-level nil) (print-level nil)
@ -1199,7 +1197,7 @@ Emacs' batch library lacks an implementation of the exec system call."
(convert-buffer doom-cli-context-stdout) (convert-buffer doom-cli-context-stdout)
(convert-buffer doom-cli-context-stderr)) (convert-buffer doom-cli-context-stderr))
newcontext)) newcontext))
(doom-log "cli:restart: writing post-script to %s" script-file) (doom-log "restart: writing post-script to %s" script-file)
(doom-file-write (doom-file-write
script-file `("#!/usr/bin/env sh\n" script-file `("#!/usr/bin/env sh\n"
"trap _doomcleanup EXIT\n" "trap _doomcleanup EXIT\n"
@ -1437,7 +1435,7 @@ ARGS are options passed to less. If DOOMPAGER is set, ARGS are ignored."
"If CLI is autoloaded, load it, otherwise return it unchanged." "If CLI is autoloaded, load it, otherwise return it unchanged."
(or (when-let* ((path (doom-cli-autoload cli)) (or (when-let* ((path (doom-cli-autoload cli))
(path (locate-file-internal path doom-cli-load-path load-suffixes))) (path (locate-file-internal path doom-cli-load-path load-suffixes)))
(doom-log "cli-load: autoload %s" path) (doom-log "load: autoload %s" path)
(let ((doom-cli--plist (doom-cli-plist cli))) (let ((doom-cli--plist (doom-cli-plist cli)))
(load! path)) (load! path))
(let* ((key (doom-cli-key cli)) (let* ((key (doom-cli-key cli))
@ -1888,85 +1886,87 @@ Once done, this function kills Emacs gracefully and writes output to log files
errors to `doom-cli-error-file')." errors to `doom-cli-error-file')."
(when doom-cli--context (when doom-cli--context
(error "Cannot nest `run!' calls")) (error "Cannot nest `run!' calls"))
(let* ((args (flatten-list args)) (doom-run-hooks 'doom-after-init-hook)
(context (make-doom-cli-context :prefix prefix :whole args)) (doom-context-with 'cli
(doom-cli--context context) (let* ((args (flatten-list args))
(write-logs-fn (doom-partial #'doom-cli--output-write-logs-h context)) (context (make-doom-cli-context :prefix prefix :whole args))
(show-benchmark-fn (doom-partial #'doom-cli--output-benchmark-h context))) (doom-cli--context context)
;; Clone output to stdout/stderr buffers for logging. (write-logs-fn (doom-partial #'doom-cli--output-write-logs-h context))
(doom-cli-redirect-output context (show-benchmark-fn (doom-partial #'doom-cli--output-benchmark-h context)))
(doom-log "run!: %s %s" prefix (combine-and-quote-strings args)) ;; Clone output to stdout/stderr buffers for logging.
(add-hook 'kill-emacs-hook show-benchmark-fn 94) (doom-cli-redirect-output context
(add-hook 'kill-emacs-hook write-logs-fn 95) (doom-log "run!: %s %s" prefix (combine-and-quote-strings args))
(when (doom-cli-context-pipe-p context :out t) (add-hook 'kill-emacs-hook show-benchmark-fn 94)
(setq doom-print-backend nil)) (add-hook 'kill-emacs-hook write-logs-fn 95)
(when (doom-cli-context-pipe-p context :in) (when (doom-cli-context-pipe-p context :out t)
(with-current-buffer (doom-cli-context-stdin context) (setq doom-print-backend nil))
(while (if-let (in (ignore-errors (read-from-minibuffer ""))) (when (doom-cli-context-pipe-p context :in)
(insert in "\n") (with-current-buffer (doom-cli-context-stdin context)
(ignore-errors (delete-char -1)))))) (while (if-let (in (ignore-errors (read-from-minibuffer "")))
(doom-cli--exit (insert in "\n")
(condition-case e (ignore-errors (delete-char -1))))))
(let* ((args (cons (if (getenv "__DOOMDUMP") :dump prefix) args)) (doom-cli--exit
(context (doom-cli-context-restore (getenv "__DOOMCONTEXT") context)) (condition-case e
(context (doom-cli-context-parse args context))) (let* ((args (cons (if (getenv "__DOOMDUMP") :dump prefix) args))
(run-hook-with-args 'doom-cli-before-run-functions context) (context (doom-cli-context-restore (getenv "__DOOMCONTEXT") context))
(let ((result (doom-cli-context-execute context))) (context (doom-cli-context-parse args context)))
(run-hook-with-args 'doom-cli-after-run-functions context result)) (run-hook-with-args 'doom-cli-before-run-functions context)
0) (let ((result (doom-cli-context-execute context)))
(doom-cli-wrong-number-of-arguments-error (run-hook-with-args 'doom-cli-after-run-functions context result))
(pcase-let ((`(,command ,flag ,args ,min ,max) (cdr e))) 0)
(print! (red "Error: %S expected %s argument%s, but got %d") (doom-cli-wrong-number-of-arguments-error
(or flag (doom-cli-command-string (pcase-let ((`(,command ,flag ,args ,min ,max) (cdr e)))
(if (keywordp (car command)) (print! (red "Error: %S expected %s argument%s, but got %d")
command (or flag (doom-cli-command-string
(cdr command)))) (if (keywordp (car command))
(if (or (= min max) command
(= max most-positive-fixnum)) (cdr command))))
min (if (or (= min max)
(format "%d-%d" min max)) (= max most-positive-fixnum))
(if (or (= min 0) (> min 1)) "s" "") min
(length args)) (format "%d-%d" min max))
(doom-cli-call `(:help "--synopsis" "--postamble" ,@(cdr (doom-cli--command context))) context e)) (if (or (= min 0) (> min 1)) "s" "")
5) (length args))
(doom-cli-unrecognized-option-error (doom-cli-call `(:help "--synopsis" "--postamble" ,@(cdr (doom-cli--command context))) context e))
(print! (red "Error: unknown option %s") (cadr e)) 5)
(doom-cli-call `(:help "--synopsis" "--postamble" ,@(cdr (doom-cli--command context))) context e) (doom-cli-unrecognized-option-error
5) (print! (red "Error: unknown option %s") (cadr e))
(doom-cli-invalid-option-error (doom-cli-call `(:help "--synopsis" "--postamble" ,@(cdr (doom-cli--command context))) context e)
(pcase-let ((`(,types ,option ,value ,errors) (cdr e))) 5)
(print! (red "Error: %s received invalid value %S") (doom-cli-invalid-option-error
(string-join (doom-cli-option-switches option) "/") (pcase-let ((`(,types ,option ,value ,errors) (cdr e)))
value) (print! (red "Error: %s received invalid value %S")
(print! (bold "\nValidation errors:")) (string-join (doom-cli-option-switches option) "/")
(dolist (err errors) (print! (item "%s." (fill err))))) value)
(doom-cli-call `(:help "--postamble" ,@(cdr (doom-cli--command context))) context e) (print! (bold "\nValidation errors:"))
5) (dolist (err errors) (print! (item "%s." (fill err)))))
(doom-cli-command-not-found-error (doom-cli-call `(:help "--postamble" ,@(cdr (doom-cli--command context))) context e)
(let* ((command (cdr e)) 5)
(cli (doom-cli-get command))) (doom-cli-command-not-found-error
(cond ((null cli) (let* ((command (cdr e))
(print! (red "Error: unrecognized command '%s'") (cli (doom-cli-get command)))
(doom-cli-command-string (or (cdr command) command))) (cond ((null cli)
(doom-cli-call `(:help "--similar" "--postamble" ,@(cdr command)) context e)) (print! (red "Error: unrecognized command '%s'")
((null (doom-cli-fn cli)) (doom-cli-command-string (or (cdr command) command)))
(print! (red "Error: a subcommand is required")) (doom-cli-call `(:help "--similar" "--postamble" ,@(cdr command)) context e))
(doom-cli-call `(:help "--subcommands" "--postamble" ,@(cdr command)) context e)))) ((null (doom-cli-fn cli))
4) (print! (red "Error: a subcommand is required"))
(doom-cli-invalid-prefix-error (doom-cli-call `(:help "--subcommands" "--postamble" ,@(cdr command)) context e))))
(let ((prefix (cadr e))) 4)
(print! (red "Error: `run!' called with invalid prefix %S") prefix) (doom-cli-invalid-prefix-error
(if-let (suggested (cl-loop for cli being the hash-value of doom-cli--table (let ((prefix (cadr e)))
unless (doom-cli-type cli) (print! (red "Error: `run!' called with invalid prefix %S") prefix)
return (car (doom-cli-command cli)))) (if-let (suggested (cl-loop for cli being the hash-value of doom-cli--table
(print! "Did you mean %S?" suggested) unless (doom-cli-type cli)
(print! "There are no commands defined under %S." prefix))) return (car (doom-cli-command cli))))
4) (print! "Did you mean %S?" suggested)
(user-error (print! "There are no commands defined under %S." prefix)))
(print! (red "Error: %s") (cadr e)) 4)
(print! "\nAborting...") (user-error
3)) (print! (red "Error: %s") (cadr e))
context)))) (print! "\nAborting...")
3))
context)))))
(defalias 'sh! #'doom-call-process) (defalias 'sh! #'doom-call-process)
@ -1998,5 +1998,12 @@ errors to `doom-cli-error-file')."
;; Kill manually so we don't save output to logs. ;; Kill manually so we don't save output to logs.
(let (kill-emacs) (kill-emacs 0)))) (let (kill-emacs) (kill-emacs 0))))
;;
;;; Last minute initialization
(when noninteractive
(doom-run-hooks 'doom-before-init-hook))
(provide 'doom-cli) (provide 'doom-cli)
;;; doom-cli.el ends here ;;; doom-cli.el ends here

View file

@ -17,6 +17,10 @@
;; ;;
;;; Logging ;;; Logging
(defun doom--log (text)
(let ((inhibit-message (not init-file-debug)))
(message "%s" (propertize text 'face 'font-lock-doc-face))))
(defmacro doom-log (output &rest args) (defmacro doom-log (output &rest args)
"Log a message in *Messages*. "Log a message in *Messages*.
@ -25,15 +29,17 @@ function to prevent the potentially expensive evaluation of its arguments when
debug mode is off." debug mode is off."
(declare (debug t)) (declare (debug t))
`(when (or init-file-debug noninteractive) `(when (or init-file-debug noninteractive)
(let ((inhibit-message (not init-file-debug))) (doom--log
(message (with-no-warnings ; suppress 'more args than %-sequences' warning
"%s" (propertize (let* ((output ,output)
;; Byte compiler: don't complain about more args than %-sequences. (absolute? (string-prefix-p ":" output)))
(with-no-warnings (format (concat "* %.06f%s" (if absolute? output (concat ":" output)))
(format (concat "* %.06f: " ,output) (float-time (time-subtract (current-time) before-init-time))
(float-time (time-subtract (current-time) before-init-time)) (let ((context (remq t (reverse doom-context))))
,@args)) (if (and context (not absolute?))
'face 'font-lock-doc-face))))) (concat "::" (mapconcat #'symbol-name context ":"))
""))
,@args))))))
;; ;;

View file

@ -410,39 +410,40 @@ installed."
;;; Package getters ;;; Package getters
(defun doom-packages--read (file &optional noeval noerror) (defun doom-packages--read (file &optional noeval noerror)
(condition-case-unless-debug e (doom-context-with 'packages
(with-temp-buffer ; prevent buffer-local state from propagating (condition-case-unless-debug e
(let* ((doom--current-module (doom-module-from-path file)) (with-temp-buffer ; prevent buffer-local state from propagating
(doom--current-flags (let* ((doom--current-module (doom-module-from-path file))
(doom-module-get (car doom--current-module) (doom--current-flags
(cdr doom--current-module) (doom-module-get (car doom--current-module)
:flags))) (cdr doom--current-module)
(if (not noeval) :flags)))
(load file noerror 'nomessage 'nosuffix) (if (not noeval)
(when (file-exists-p file) (load file noerror 'nomessage 'nosuffix)
(insert-file-contents file) (when (file-exists-p file)
(let (emacs-lisp-mode) (emacs-lisp-mode)) (insert-file-contents file)
;; Scrape `package!' blocks from FILE for a comprehensive listing of (let (emacs-lisp-mode) (emacs-lisp-mode))
;; packages used by this module. ;; Scrape `package!' blocks from FILE for a comprehensive listing of
(while (search-forward "(package!" nil t) ;; packages used by this module.
(let ((ppss (save-excursion (syntax-ppss)))) (while (search-forward "(package!" nil t)
;; Don't collect packages in comments or strings (let ((ppss (save-excursion (syntax-ppss))))
(unless (or (nth 3 ppss) ;; Don't collect packages in comments or strings
(nth 4 ppss)) (unless (or (nth 3 ppss)
(goto-char (match-beginning 0)) (nth 4 ppss))
(cl-destructuring-bind (_ name . plist) (goto-char (match-beginning 0))
(read (current-buffer)) (cl-destructuring-bind (_ name . plist)
(push (cons (read (current-buffer))
name (plist-put (push (cons
plist :modules name (plist-put
(list doom--current-module))) plist :modules
doom-packages))))))))) (list doom--current-module)))
(user-error doom-packages)))))))))
(user-error (error-message-string e))) (user-error
(error (user-error (error-message-string e)))
(signal 'doom-package-error (error
(list (doom-module-from-path file) (signal 'doom-package-error
file e))))) (list (doom-module-from-path file)
file e))))))
(defun doom-package-list (&optional module-list) (defun doom-package-list (&optional module-list)
"Retrieve a list of explicitly declared packages from MODULE-LIST. "Retrieve a list of explicitly declared packages from MODULE-LIST.
@ -454,7 +455,7 @@ also be a list of module keys."
(let ((module-list (cond ((null module-list) (doom-module-list)) (let ((module-list (cond ((null module-list) (doom-module-list))
((symbolp module-list) (doom-module-list 'all)) ((symbolp module-list) (doom-module-list 'all))
(module-list))) (module-list)))
;; TODO: doom-module-context + doom-context ;; TODO: doom-module-context
(packages-file doom-module-packages-file) (packages-file doom-module-packages-file)
doom-disabled-packages doom-disabled-packages
doom-packages) doom-packages)
@ -552,10 +553,13 @@ elsewhere."
(cl-callf plist-put plist :ignore built-in)) (cl-callf plist-put plist :ignore built-in))
`(let* ((name ',name) `(let* ((name ',name)
(plist (cdr (assq name doom-packages))) (plist (cdr (assq name doom-packages)))
(dir (dir!))) (dir (dir!))
(module (doom-module-from-path dir)))
(unless (doom-context-p 'packages)
(signal 'doom-module-error
(list module "package! can only be used in packages.el files")))
;; Record what module this declaration was found in ;; Record what module this declaration was found in
(let ((module-list (plist-get plist :modules)) (let ((module-list (plist-get plist :modules)))
(module (doom-module-from-path dir)))
(unless (member module module-list) (unless (member module module-list)
(cl-callf plist-put plist :modules (cl-callf plist-put plist :modules
(append module-list (append module-list

View file

@ -365,15 +365,10 @@ Defaults to the profile at `doom-profile-default'."
(let ((v (version-to-list doom-version)) (let ((v (version-to-list doom-version))
(ref (doom-call-process "git" "-C" (doom-path doom-emacs-dir) "rev-parse" "HEAD")) (ref (doom-call-process "git" "-C" (doom-path doom-emacs-dir) "rev-parse" "HEAD"))
(branch (doom-call-process "git" "-C" (doom-path doom-emacs-dir) "branch" "--show-current"))) (branch (doom-call-process "git" "-C" (doom-path doom-emacs-dir) "branch" "--show-current")))
;; FIX: The `doom-init-time' guard protects us from a nefarious edge case in ;; FIX: Make sure this only runs at startup to protect us Emacs' interpreter
;; which Emacs' interpreter, while lazy-loading docstrings in ;; re-evaluating this file when lazy-loading dynamic docstrings from the
;; byte-compiled elisp, ends up re-evaluating the whole file. This can ;; byte-compiled init file.
;; happen rapidly, multiple times, if something loads these docstrings (by `((when (doom-context-p 'init)
;; calling the `documentation' function) rapidly, which is the case for
;; `marginalia' and each symbol in the M-x and describe-* command
;; completion lists. By guarding the expensive part of this file, this
;; process becomes instant.
`((unless doom-init-time
,@(cl-loop for var in doom-autoloads-cached-vars ,@(cl-loop for var in doom-autoloads-cached-vars
if (boundp var) if (boundp var)
collect `(set-default ',var ',(symbol-value var))) collect `(set-default ',var ',(symbol-value var)))
@ -411,28 +406,30 @@ Defaults to the profile at `doom-profile-default'."
if (doom-module-locate-path cat mod file) if (doom-module-locate-path cat mod file)
collect (module-loader cat mod it noerror)))) collect (module-loader cat mod it noerror))))
;; FIX: Same as above (see `doom-profile--generate-init-vars'). ;; FIX: Same as above (see `doom-profile--generate-init-vars').
`((unless doom-init-time `((if (or (doom-context-p 'init)
(set 'doom-modules ',doom-modules) (doom-context-p 'reload))
(set 'doom-disabled-packages ',doom-disabled-packages) (doom-context-with 'modules
;; Cache module state and flags in symbol plists for quick lookup by (set 'doom-modules ',doom-modules)
;; `modulep!' later. (set 'doom-disabled-packages ',doom-disabled-packages)
,@(cl-loop ;; Cache module state and flags in symbol plists for quick lookup by
for (category . modules) in (seq-group-by #'car config-modules-list) ;; `modulep!' later.
collect ,@(cl-loop
`(setplist ',category for (category . modules) in (seq-group-by #'car config-modules-list)
(quote ,(cl-loop for (_ . module) in modules collect
nconc `(,module ,(get category module)))))) `(setplist ',category
(let ((old-custom-file custom-file)) (quote ,(cl-loop for (_ . module) in modules
,@(module-list-loader pre-init-modules init-file) nconc `(,module ,(get category module))))))
(doom-run-hooks 'doom-before-modules-init-hook) (let ((old-custom-file custom-file))
,@(module-list-loader init-modules init-file) ,@(module-list-loader pre-init-modules init-file)
(doom-run-hooks 'doom-after-modules-init-hook) (doom-run-hooks 'doom-before-modules-init-hook)
(doom-run-hooks 'doom-before-modules-config-hook) ,@(module-list-loader init-modules init-file)
,@(module-list-loader config-modules config-file) (doom-run-hooks 'doom-after-modules-init-hook)
(doom-run-hooks 'doom-after-modules-config-hook) (doom-run-hooks 'doom-before-modules-config-hook)
,@(module-list-loader post-config-modules config-file t) ,@(module-list-loader config-modules config-file)
(when (eq custom-file old-custom-file) (doom-run-hooks 'doom-after-modules-config-hook)
(doom-load custom-file 'noerror)))))))) ,@(module-list-loader post-config-modules config-file t)
(when (eq custom-file old-custom-file)
(doom-load custom-file 'noerror)))))))))
(defun doom-profile--generate-doom-autoloads () (defun doom-profile--generate-doom-autoloads ()
(doom-autoloads--scan (doom-autoloads--scan

View file

@ -302,7 +302,7 @@ If RETURN-P, return the message as a string instead of displaying it."
(doom-load-envvars-file doom-env-file 'noerror)) (doom-load-envvars-file doom-env-file 'noerror))
;;; Last minute setup ;;; Last minute setup
(add-hook 'doom-after-init-hook #'doom-load-packages-incrementally-h) (add-hook 'doom-after-init-hook #'doom-load-packages-incrementally-h 100)
(add-hook 'doom-after-init-hook #'doom-display-benchmark-h 110) (add-hook 'doom-after-init-hook #'doom-display-benchmark-h 110)
(doom-run-hook-on 'doom-first-buffer-hook '(find-file-hook doom-switch-buffer-hook)) (doom-run-hook-on 'doom-first-buffer-hook '(find-file-hook doom-switch-buffer-hook))
(doom-run-hook-on 'doom-first-file-hook '(find-file-hook dired-initial-position-hook)) (doom-run-hook-on 'doom-first-file-hook '(find-file-hook dired-initial-position-hook))

View file

@ -442,6 +442,67 @@ users).")
(setq command-line-x-option-alist nil)))) (setq command-line-x-option-alist nil))))
;;
;;; `doom-context'
(defvar doom-context '(t)
"A list of symbols identifying all active Doom execution contexts.
This should never be directly changed, only let-bound, and should never be
empty. Each context describes what phase Doom is in, and may respond to.
All valid contexts:
cli -- while executing a Doom CLI
compile -- while byte-compilation is in progress
eval -- during inline evaluation of elisp
init -- while doom is formally starting up for the first time, after its
core libraries are loaded, but before user config is.
modules -- while loading modules and their files
sandbox -- This session was launched from Doom's sandbox.
packages -- when packagedefs are being read
reload -- while reloading doom")
(put 'doom-context 'valid-values '(cli compile eval init modules packages reload sandbox))
(put 'doom-context 'risky-local-variable t)
(defun doom-context--check (context)
(let ((valid (get 'doom-context 'valid-values)))
(unless (memq context valid)
(signal 'doom-context-error
(list context "Unrecognized context" valid)))))
(defun doom-context-p (context)
"Return t if CONTEXT is active (i.e. in `doom-context')."
(if (memq context doom-context) t))
(defun doom-context-push (context)
"Add CONTEXT to `doom-context', if it isn't already.
Return non-nil if successful. Throws an error if CONTEXT is invalid."
(unless (memq context doom-context)
(doom-context--check context)
(doom-log ":context: +%s %s" context doom-context)
(push context doom-context)))
(defun doom-context-pop (context &optional strict?)
"Remove CONTEXT from `doom-context'.
Return non-nil if successful. If STRICT? is non-nil, throw an error if CONTEXT
wasn't active when this was called."
(if (not (doom-context-p context))
(when strict?
(signal 'doom-context-error
(list doom-context "Attempt to pop missing context" context)))
(doom-log ":context: -%s %s" context doom-context)
(setq doom-context (delq context doom-context))))
(defmacro doom-context-with (contexts &rest body)
"Evaluate BODY with CONTEXT added to `doom-context'."
(declare (indent 1))
`(let ((doom-context doom-context))
(dolist (context (ensure-list ,contexts))
(doom-context-push context))
,@body))
;; ;;
;;; Reasonable, global defaults ;;; Reasonable, global defaults
@ -588,21 +649,25 @@ appropriately against `noninteractive' or the `cli' context."
;; ;;
;;; Last minute initialization ;;; Last minute initialization
(add-hook! 'doom-before-init-hook (add-hook! 'doom-before-init-hook :depth -105
(defun doom--set-initial-values-h () (defun doom--begin-init-h ()
;; Remember these variables' initial values, so we can safely reset them at "Begin the startup process."
;; a later time, or consult them without fear of contamination. (when (doom-context-push 'init)
(dolist (var '(exec-path load-path process-environment)) ;; Remember these variables' initial values, so we can safely reset them at
(put var 'initial-value (default-toplevel-value var))))) ;; a later time, or consult them without fear of contamination.
(dolist (var '(exec-path load-path process-environment))
(put var 'initial-value (default-toplevel-value var))))))
(add-hook! 'doom-after-init-hook :depth -110 (add-hook! 'doom-after-init-hook :depth 105
(defun doom--end-init-h () (defun doom--end-init-h ()
"Set `doom-init-time'." "Set `doom-init-time'."
(setq doom-init-time (float-time (time-subtract (current-time) before-init-time))))) (when (doom-context-pop 'init)
(setq doom-init-time (float-time (time-subtract (current-time) before-init-time))))))
;; This is the absolute latest a hook can run in Emacs' startup process. (unless noninteractive
(define-advice command-line-1 (:after (&rest _) run-after-init-hook) ;; This is the absolute latest a hook can run in Emacs' startup process.
(doom-run-hooks 'doom-after-init-hook)) (define-advice command-line-1 (:after (&rest _) run-after-init-hook)
(doom-run-hooks 'doom-after-init-hook)))
(provide 'doom) (provide 'doom)
;;; doom.el ends here ;;; doom.el ends here

View file

@ -11,10 +11,6 @@
(defvar doom-before-reload-hook nil (defvar doom-before-reload-hook nil
"A list of hooks to run before `doom/reload' has reloaded Doom.") "A list of hooks to run before `doom/reload' has reloaded Doom.")
;;;###autoload
(defvar doom-reloading-p nil
"TODO")
;;;###autoload ;;;###autoload
(defun doom/open-private-config () (defun doom/open-private-config ()
"Browse your `doom-user-dir'." "Browse your `doom-user-dir'."
@ -85,7 +81,7 @@ Runs `doom-after-reload-hook' afterwards."
(interactive) (interactive)
(mapc #'require (cdr doom-incremental-packages)) (mapc #'require (cdr doom-incremental-packages))
(doom--if-compile (format "%S sync -e" doom-bin) (doom--if-compile (format "%S sync -e" doom-bin)
(let ((doom-reloading-p t)) (doom-with-context '(reload modules)
(doom-run-hooks 'doom-before-reload-hook) (doom-run-hooks 'doom-before-reload-hook)
(doom-load (file-name-concat doom-user-dir doom-module-init-file) t) (doom-load (file-name-concat doom-user-dir doom-module-init-file) t)
(with-demoted-errors "PRIVATE CONFIG ERROR: %s" (with-demoted-errors "PRIVATE CONFIG ERROR: %s"
@ -110,10 +106,11 @@ line."
(interactive) (interactive)
(require 'doom-profiles) (require 'doom-profiles)
;; TODO: Make this more robust ;; TODO: Make this more robust
(dolist (file (mapcar #'car doom-profile-generators)) (doom-with-context 'reload
(when (string-match-p "/[0-9]+-loaddefs[.-]" file) (dolist (file (mapcar #'car doom-profile-generators))
(load (doom-path doom-profile-dir doom-profile-init-dir-name file) (when (string-match-p "/[0-9]+-loaddefs[.-]" file)
'noerror)))) (load (doom-path doom-profile-dir doom-profile-init-dir-name file)
'noerror)))))
;;;###autoload ;;;###autoload
(defun doom/reload-env () (defun doom/reload-env ()
@ -125,10 +122,11 @@ Doing so from within Emacs will taint your shell environment.
An envvar file contains a snapshot of your shell environment, which can be An envvar file contains a snapshot of your shell environment, which can be
imported into Emacs." imported into Emacs."
(interactive) (interactive)
(let ((default-directory doom-emacs-dir)) (doom-with-context 'reload
(with-temp-buffer (let ((default-directory doom-emacs-dir))
(doom-load-envvars-file doom-env-file) (with-temp-buffer
(message "Reloaded %S" (abbreviate-file-name doom-env-file))))) (doom-load-envvars-file doom-env-file)
(message "Reloaded %S" (abbreviate-file-name doom-env-file))))))
;;;###autoload ;;;###autoload
(defun doom/upgrade () (defun doom/upgrade ()

View file

@ -20,7 +20,7 @@
;; `evil-collection-list' (now I can just copy it from time to time). ;; `evil-collection-list' (now I can just copy it from time to time).
(when (and (not noninteractive) (when (and (not noninteractive)
(not doom-reloading-p) (not (doom-context-p 'reload))
(modulep! +everywhere)) (modulep! +everywhere))
(setq evil-collection-company-use-tng (modulep! :completion company +tng) (setq evil-collection-company-use-tng (modulep! :completion company +tng)

View file

@ -14,7 +14,8 @@ to a pop up buffer."
(unwind-protect (unwind-protect
(condition-case-unless-debug e (condition-case-unless-debug e
(let ((doom--current-module (ignore-errors (doom-module-from-path buffer-file-name)))) (let ((doom--current-module (ignore-errors (doom-module-from-path buffer-file-name))))
(eval-region beg end buffer load-read-function) (doom-context-with 'eval
(eval-region beg end buffer load-read-function))
(with-current-buffer buffer (with-current-buffer buffer
(let ((pp-max-width nil)) (let ((pp-max-width nil))
(require 'pp) (require 'pp)

View file

@ -1417,7 +1417,7 @@ between the two."
;; In case the user has eagerly loaded org from their configs ;; In case the user has eagerly loaded org from their configs
(when (and (featurep 'org) (when (and (featurep 'org)
(not byte-compile-current-file)) (not byte-compile-current-file))
(unless doom-reloading-p (unless (doom-context-p 'reload)
(message "`org' was already loaded by the time lang/org loaded, this may cause issues")) (message "`org' was already loaded by the time lang/org loaded, this may cause issues"))
(run-hooks 'org-load-hook)) (run-hooks 'org-load-hook))