BREAKING CHANGE: this changes Doom's CLI framework in subtle ways, which is listed in greater detail below. If you've never extended Doom's CLI, then this won't affect you, but otherwise it'd be recommended you read on below. This commit focuses on the CLI framework itself and backports some foundational changes to its DSL and how it resolves command line arguments to CLIs, validates input, displays documentation, and persists state across sessions -- and more. This is done in preparation for the final stretch towarding completing the CLI rewrite (see #4273). This is also an effort to generalize Doom's CLI (both its framework and bin/doom), to increase it versatility and make it a viable dev tool for other Doom projects (on our Github org) and beyond. However, there is a *lot* to cover so I'll try to be brief: - Refactor: generalize Doom's CLI framework by moving all bin/doom specific configuration/commands out of core-cli into bin/doom. This makes it easier to use bin/doom as a project-agnostic development tool (or for users to write their own). - Refactor: change the namespace for CLI variables/functions from doom-cli-X to doom-X. - Fix: subcommands being mistaken as arguments. "doom make index" will resolve to (defcli! (doom make index)) if it exists, otherwise (defcli! (doom make)) with "index" as an argument. Before this, it would resolve to the latter no matter what. &rest can override this; with (defcli! (doom make) (&rest args)), (defcli! (doom make index)) will never be invoked. - Refactor!: redesign our output library (was core/autoload/output.el, is now core/autoload/print.el), and how our CLI framework buffers and logs output, and now merges logs across (exit! ...) restarts. - Feat: add support for :before and :after pseudo commands. E.g. (defcli! (:before doom help) () ...) (defcli! (:after doom sync) () ...) Caveat: unlike advice, only one of each can be defined per-command. - Feat: option arguments now have rudimentary type validation (see `doom-cli-option-arg-types`). E.g. (defcli! (doom foo) ((foo ("--foo" num))) ...) If NUM is not a numeric, it will throw a validation error. Any type that isn't in `doom-cli-option-arg-types` will be treated as a wildcard string type. `num` can also be replaced with a specification, e.g. "HOST[:PORT]", and can be formatted by using symbol quotes: "`HOST'[:`PORT']". - Feat: it is no longer required that options *immediately* follow the command that defines them (but it must be somewhere after it, not before). E.g. With: (defcli! (:before doom foo) ((foo ("--foo"))) ...) (defcli! (doom foo baz) () ...) Before: FAIL: doom --foo foo baz GOOD: doom foo --foo baz FAIL: doom foo baz --foo After: FAIL: doom --foo foo baz GOOD: doom foo --foo baz GOOD: doom foo baz --foo - Refactor: CLI session state is now kept in a doom-cli-context struct (which can be bound to a CLI-local variable with &context in the arglist): (defcli! (doom sync) (&context context) (print! "Command: " (doom-cli-context-command context))) These contexts are persisted across sessions (when restarted). This is necessary to support seamless script restarting (i.e. execve emulation) in post-3.0. - Feat: Doom's CLI framework now understands "--". Everything after it will be treated as regular arguments, instead of sub-commands or options. - Refactor!: the semantics of &rest for CLIs has changed. It used to be "all extra literal, non-option arguments". It now means *all* unprocessed arguments, and its use will suppress "unrecognized option" errors, and tells the framework not to process any further subcommands. Use &args if you just want "all literal arguments following this command". - Feat: add new auxiliary keywords for CLI arglists: &context, &multiple, &flags, &args, &stdin, &whole, and &cli. - &context SYM: binds the currently running context to SYM (a `doom-cli-context` struct). Helpful for introspection or passing along state when calling subcommands by hand (with `call!`). - &stdin SYM: SYM will be bound to a string containing any input piped into the running script, or nil if none. Use `doom-cli-context-pipe-p` to detect whether the script has been piped into or out of. - &multiple OPTIONS...: allows all following OPTIONS to be repeated. E.g. "foo -x a -x b -x c" will pass (list ("-x" . "a") ("-x" . "b") ("-x" . "c")) as -x's value. - &flags OPTIONS...: All options after "&flags" get an implicit --no-* switch and cannot accept arguments. Will be set to :yes or :no depending on which flag is provided, and nil if the flag isn't provided. Otherwise, a default value can be specified in that options' arglist. E.g. (defcli! (doom foo) (&flags (foo ("--foo" :no))) ...) When called, this command sets FOO to :yes if --foo, :no if --no-foo, and defaults to :no otherwise. - &args SYM: this replaces what &rest used to be; it binds to SYM a list of all unprocessed (non-option) arguments. - &rest SYM: now binds SYM to a list of all unprocessed arguments, including options. This also suppresses "unrecognized option" errors, but will render any sub-commands inaccessible. E.g. (defcli! (doom make) (&rest rest) ...) ;; These are now inaccessible! (defcli! (doom make foo) (&rest rest) ...) (defcli! (doom make bar) (&rest rest) ...) - &cli SYM: binds SYM to the currently running `doom-cli` struct. Can also be obtained via `(doom-cli-get (doom-cli-context-command context))`. Possibly useful for introspection. - feat: add defobsolete! macro for quickly defining obsolete commands. - feat: add defalias! macro for quickly defining alias commands. - feat: add defautoload! macro for defining an autoloaded command (won't be loaded until it is called for). - refactor!: rename defcligroup! to defgroup! for consistency. - fix: CLIs will now recursively inherit plist properties from parent defcli-group!'s (but will stack :prefix). - refactor!: remove obsolete 'doom update': - refactor!: further generalize 'doom ci' - In an effort to generalize 'doom ci' (so other Doom--or non-doom--projects can use it), all its subcommands have been changed to operate on the current working directory's repo instead of $EMACSDIR. - Doom-specific CI configuration was moved to .github/ci.el. - All 'doom ci' commands will now preload one of \$CURRENT_REPO_ROOT/ci.el or \$DOOMDIR/ci.el before executing. - refactor!: changed 'doom env' - 'doom env {-c,--clear}' is now 'doom env {clear,c}' - -r/--reject and -a/--allow may now be specified multiple times - refactor!: rewrote CLI help framework and error handling to be more sophisticated and detailed. - feat: can now initiate $PAGER on output with (exit! :pager) (or use :pager? to only invoke pager is output is longer than the terminal is tall). - refactor!: changed semantics+conventions for global bin/doom options - Single-character global options are now uppercased, to distinguish them from local options: - -d (for debug mode) is now -D - -y (to suppress prompts) is now -! - -l (to load elisp) is now -L - -h (short for --help) is now -? - Replace --yes/-y switches with --force/-! - -L/--load FILE: now silently ignores file errors. - Add --strict-load FILE: does the same as -L/--load, but throws an error if FILE does not exist/is unreadable. - Add -E/--eval FORM: evaluates arbitrary lisp before commands are processed. - -L/--load, --strict-load, and -E/--eval can now be used multiple times in one command. - Add --pager COMMAND to specify an explicit pager. Will also obey $DOOMPAGER envvar. Does not obey $PAGER. - Fix #3746: which was likely caused by the generated post-script overwriting the old mid-execution. By salting the postscript filenames (with both an overarching session ID and a step counter). - Docs: document websites, environment variables, and exit codes in 'doom --help' - Feat: add imenu support for def{cli,alias,obsolete}! Ref: #4273 Fix: #3746 Fix: #3844
218 lines
9.4 KiB
EmacsLisp
218 lines
9.4 KiB
EmacsLisp
;;; core/cli/commands/byte-compile.el -*- lexical-binding: t; -*-
|
|
|
|
;;
|
|
;;; Variables
|
|
|
|
;; None yet!
|
|
|
|
|
|
;;
|
|
;;; Commands
|
|
|
|
(defcli! ((compile c))
|
|
((recompile-p ("-r" "--recompile"))
|
|
(core-p ("-c" "--core"))
|
|
(private-p ("-p" "--private"))
|
|
(verbose-p ("-v" "--verbose")))
|
|
"Byte-compiles your config or selected modules.
|
|
|
|
compile [TARGETS...]
|
|
compile :core :private lang/python
|
|
compile feature lang
|
|
|
|
Accepts :core and :private as special arguments, which target Doom's core files
|
|
and your private config files, respectively. To recompile your packages, use
|
|
'doom build' instead."
|
|
(doom-cli-compile
|
|
(if (or core-p private-p)
|
|
(append (if core-p (doom-glob doom-emacs-dir "init.el"))
|
|
(if core-p (list doom-core-dir))
|
|
(if private-p (list doom-private-dir)))
|
|
(or (y-or-n-p
|
|
(concat "WARNING: Changes made to your config after compiling it won't take effect until\n"
|
|
"this command is rerun or you run 'doom clean'! It will also make error backtraces\n"
|
|
"much more difficult to decipher.\n\n"
|
|
"If you intend to use it anyway, remember this or it will come back to bite you!\n\n"
|
|
"Continue anyway?"))
|
|
(user-error "Aborted"))
|
|
(append (doom-glob doom-emacs-dir "init.el")
|
|
(list doom-core-dir)
|
|
(seq-filter
|
|
;; Only compile Doom's modules
|
|
(doom-rpartial #'file-in-directory-p doom-emacs-dir)
|
|
;; Omit `doom-private-dir', which is always first
|
|
(cdr (doom-module-load-path)))))
|
|
recompile-p
|
|
verbose-p))
|
|
|
|
(defcli! clean ()
|
|
"Delete all *.elc files."
|
|
(doom-compile-clean))
|
|
|
|
|
|
;;
|
|
;;; Helpers
|
|
|
|
(cl-defun doom-cli-compile (&optional targets recompile-p verbose-p)
|
|
"Byte compiles your emacs configuration.
|
|
|
|
init.el is always byte-compiled by this.
|
|
|
|
If TARGETS is specified, as a list of direcotries
|
|
|
|
If MODULES is specified (a list of module strings, e.g. \"lang/php\"), those are
|
|
byte-compiled. Otherwise, all enabled modules are byte-compiled, including Doom
|
|
core. It always ignores unit tests and files with `no-byte-compile' enabled.
|
|
|
|
WARNING: byte-compilation yields marginal gains and makes debugging new issues
|
|
difficult. It is recommended you don't use it unless you understand the
|
|
reprecussions.
|
|
|
|
Use `doom-compile-clean' or `make clean' to reverse
|
|
byte-compilation.
|
|
|
|
If RECOMPILE-P is non-nil, only recompile out-of-date files."
|
|
(let* ((default-directory doom-emacs-dir)
|
|
(targets (nreverse (delete-dups targets)))
|
|
;; In case it is changed during compile-time
|
|
(auto-mode-alist auto-mode-alist)
|
|
kill-emacs-hook kill-buffer-query-functions)
|
|
|
|
(let ((after-load-functions
|
|
(if (null targets)
|
|
after-load-functions
|
|
;; Assemble el files we want to compile, and preserve in the order
|
|
;; they are loaded in, so we don't run into any scary catch-22s
|
|
;; while byte-compiling, like missing macros.
|
|
(cons (let ((target-dirs (seq-filter #'file-directory-p targets)))
|
|
(lambda (path)
|
|
(and (not (doom-compile--ignore-file-p path))
|
|
(seq-find (doom-partial #'file-in-directory-p path)
|
|
target-dirs)
|
|
(cl-pushnew path targets))))
|
|
after-load-functions))))
|
|
(doom-log "Reloading Doom in preparation for byte-compilation")
|
|
;; But first we must be sure that Doom and your private config have been
|
|
;; fully loaded. Which usually aren't so in an noninteractive session.
|
|
(let ((load-prefer-newer t)
|
|
(noninteractive t)
|
|
doom-interactive-p)
|
|
(doom-initialize 'force)
|
|
(quiet! (doom-initialize-packages))
|
|
(quiet! (doom-initialize-modules))))
|
|
|
|
(if (null targets)
|
|
(print! (item "No targets to %scompile" (if recompile-p "re" "")))
|
|
(print! (start "%scompiling your config...")
|
|
(if recompile-p "Re" "Byte-"))
|
|
|
|
(dolist (dir
|
|
(cl-remove-if-not #'file-directory-p targets)
|
|
(setq targets (cl-remove-if #'file-directory-p targets)))
|
|
(prependq! targets
|
|
(doom-files-in
|
|
dir :match "\\.el" :filter #'doom-compile--ignore-file-p)))
|
|
|
|
(print-group!
|
|
(require 'use-package)
|
|
(condition-case-unless-debug e
|
|
(let* ((total-ok 0)
|
|
(total-fail 0)
|
|
(total-noop 0)
|
|
(byte-compile-verbose nil)
|
|
(byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local))
|
|
(byte-compile-dynamic-docstrings t)
|
|
(use-package-compute-statistics nil)
|
|
(use-package-defaults use-package-defaults)
|
|
(use-package-expand-minimally t)
|
|
(targets (delete-dups targets))
|
|
(modules (seq-group-by #'doom-module-from-path targets))
|
|
(total-files (length targets))
|
|
(total-modules (length modules))
|
|
(i 0)
|
|
last-module)
|
|
;; Prevent packages from being loaded at compile time if they
|
|
;; don't meet their own predicates.
|
|
(push (list :no-require t
|
|
(lambda (_name args)
|
|
(or (when-let (pred (or (plist-get args :if)
|
|
(plist-get args :when)))
|
|
(not (eval pred t)))
|
|
(when-let (pred (plist-get args :unless))
|
|
(eval pred t)))))
|
|
use-package-defaults)
|
|
(dolist (module-files modules)
|
|
(cl-incf i)
|
|
(dolist (target (cdr module-files))
|
|
(let ((elc-file (byte-compile-dest-file target)))
|
|
(cl-incf
|
|
(if (and recompile-p (not (file-newer-than-file-p target elc-file)))
|
|
total-noop
|
|
(pcase (if (not (doom-file-cookie-p target "if" t))
|
|
'no-byte-compile
|
|
(unless (equal last-module (car module-files))
|
|
(print! (success "(% 3d/%d) Compiling %s")
|
|
i total-modules
|
|
(if-let (m (caar module-files))
|
|
(format "%s %s module..." m (cdar module-files))
|
|
(format "%d stand alone elisp files..."
|
|
(length (cdr module-files))))
|
|
(caar module-files) (cdar module-files))
|
|
(setq last-module (car module-files)))
|
|
(if verbose-p
|
|
(byte-compile-file target)
|
|
(quiet! (byte-compile-file target))))
|
|
(`no-byte-compile
|
|
(doom-log "(% 3d/%d) Ignored %s" i total-modules (relpath target))
|
|
total-noop)
|
|
(`nil
|
|
(print! (error "(% 3d/%d) Failed to compile %s")
|
|
i total-modules (relpath target))
|
|
total-fail)
|
|
(_ total-ok)))))))
|
|
(print! (class (if (= total-fail 0) 'success 'warn)
|
|
"%s %d/%d file(s) (%d ignored)")
|
|
(if recompile-p "Recompiled" "Byte-compiled")
|
|
total-ok total-files
|
|
total-noop)
|
|
(= total-fail 0))
|
|
((debug error)
|
|
(print! (error "There were breaking errors.\n\n%s")
|
|
"Reverting changes...")
|
|
(signal 'doom-error (list 'byte-compile e))))))))
|
|
|
|
(defun doom-compile--ignore-file-p (path)
|
|
(let ((filename (file-name-nondirectory path)))
|
|
(or (not (equal (file-name-extension path) "el"))
|
|
(member filename (list "packages.el" "doctor.el"))
|
|
(string-prefix-p "." filename)
|
|
(string-prefix-p "test-" filename)
|
|
(string-prefix-p "flycheck_" filename)
|
|
(string-suffix-p ".example.el" filename))))
|
|
|
|
(defun doom-compile-clean ()
|
|
"Delete all the compiled elc files in your Emacs configuration and private
|
|
module. This does not include your byte-compiled, third party packages.'"
|
|
(require 'core-modules)
|
|
(print! (start "Cleaning .elc files"))
|
|
(print-group!
|
|
(cl-loop with default-directory = doom-emacs-dir
|
|
with success = 0
|
|
with esc = (if doom-debug-p "" "\033[1A")
|
|
for path
|
|
in (append (doom-glob doom-emacs-dir "*.elc")
|
|
(doom-files-in doom-private-dir :match "\\.elc$" :depth 1)
|
|
(doom-files-in doom-core-dir :match "\\.elc$")
|
|
(doom-files-in doom-modules-dirs :match "\\.elc$" :depth 4))
|
|
if (file-exists-p path)
|
|
do (delete-file path)
|
|
and do (print! (success "\033[KDeleted %s%s") (relpath path) esc)
|
|
and do (cl-incf success)
|
|
finally do
|
|
(print! (if (> success 0)
|
|
(success "\033[K%d elc files deleted" success)
|
|
(item "\033[KNo elc files to clean"))))
|
|
t))
|
|
|
|
(provide 'core-cli-compile)
|
|
;;; compile.el ends here
|