From b90dede1abd57716c663b4032c740d3c9bf775fb Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Sun, 21 Jul 2019 15:39:45 +0200 Subject: [PATCH] :boom: Replace package.el/quelpa with straight #374 There are a few kinks to iron out, but for the most part it's done. Doom Emacs, powered by straight. Goodbye gnutls and elpa/quelpa issues. This update doesn't come with rollback or lockfile support yet, but I will eventually include one with Doom, and packages will be (by default, anyway) updated in sync with Doom. Relevant threads: #1577 #1566 #1473 --- Makefile | 5 +- README.md | 2 +- bin/doom-doctor | 122 +----- core/autoload/cli.el | 19 +- core/autoload/debug.el | 158 ++++---- core/autoload/help.el | 8 +- core/autoload/hydras.el | 2 +- core/autoload/packages.el | 507 ++++-------------------- core/cli/autoloads.el | 527 ++++++++++++++----------- core/cli/byte-compile.el | 288 +++++++------- core/cli/debug.el | 27 +- core/cli/env.el | 88 ++--- core/cli/packages.el | 395 ++++++++++-------- core/cli/patch-macos.el | 11 +- core/cli/quickstart.el | 144 ++++--- core/cli/test.el | 108 ++--- core/cli/upgrade.el | 9 +- core/core-cli.el | 286 ++++++++------ core/core-modules.el | 199 +++++----- core/core-packages.el | 237 ++++++----- core/core.el | 85 ++-- core/packages.el | 3 - init.example.el | 8 +- modules/completion/helm/packages.el | 2 +- modules/editor/evil/packages.el | 3 +- modules/editor/rotate-text/packages.el | 2 +- modules/editor/snippets/packages.el | 2 +- modules/lang/cc/packages.el | 2 +- modules/lang/ocaml/packages.el | 18 +- modules/lang/org/config.el | 9 + modules/lang/org/packages.el | 21 +- modules/lang/php/packages.el | 4 +- modules/lang/terra/packages.el | 3 +- modules/tools/magit/config.el | 5 + modules/tools/pass/packages.el | 4 +- 35 files changed, 1542 insertions(+), 1771 deletions(-) diff --git a/Makefile b/Makefile index 4b33a0ca0..5bc6eb0f5 100644 --- a/Makefile +++ b/Makefile @@ -25,8 +25,7 @@ cp: compile-plugins re: recompile d: doctor -quickstart: deprecated - @$(DOOM) quickstart +quickstart: install ## Package management @@ -49,7 +48,7 @@ compile-core: deprecated compile-private: deprecated @$(DOOM) compile :private compile-plugins: deprecated - @$(DOOM) compile :plugins + @$(DOOM) build recompile: deprecated @$(DOOM) recompile clean: deprecated diff --git a/README.md b/README.md index dd499db2e..39b1af1ca 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ **Quick start** ```bash git clone https://github.com/hlissner/doom-emacs ~/.emacs.d -~/.emacs.d/bin/doom quickstart +~/.emacs.d/bin/doom install ``` **Table of Contents** diff --git a/bin/doom-doctor b/bin/doom-doctor index 119d4b741..1ed40ae87 100755 --- a/bin/doom-doctor +++ b/bin/doom-doctor @@ -27,6 +27,7 @@ (when (getenv "DEBUG") (setq debug-on-error t)) +(require 'subr-x) (require 'pp) (load (expand-file-name "core/autoload/format" user-emacs-directory) nil t) @@ -147,7 +148,7 @@ emacs-version) (explain! "Byte-code compiled in one version of Emacs may not work in another version." "It is recommended that you reinstall your plugins or recompile them with" - "`bin/doom compile :plugins'."))) + "`bin/doom rebuild'."))) (section! "Checking for Emacs config conflicts...") (when (file-exists-p "~/.emacs") @@ -182,109 +183,7 @@ ;; on windows? (when (memq system-type '(windows-nt ms-dos cygwin)) (warn! "Warning: Windows detected") - (explain! "DOOM was designed for MacOS and Linux. Expect a bumpy ride!")) - - ;; gnutls-cli & openssl - (section! "Checking gnutls/openssl...") - (cond ((executable-find "gnutls-cli")) - ((executable-find "openssl") - (let* ((output (sh "openssl ciphers -v")) - (protocols - (let (protos) - (mapcar (lambda (row) - (add-to-list 'protos (cadr (split-string row " " t)))) - (split-string (sh "openssl ciphers -v") "\n")) - (delq nil protos)))) - (unless (or (member "TLSv1.1" protocols) - (member "TLSv1.2" protocols)) - (let ((version (cadr (split-string (sh "openssl version") " " t)))) - (warn! "Warning: couldn't find gnutls-cli, and OpenSSL is out-of-date (v%s)" version) - (explain! - "This may not affect your Emacs experience, but there are security " - "vulnerabilities in the SSL2/3 & TLS1.0 protocols. You should use " - "TLS 1.1+, which wasn't introduced until OpenSSL v1.0.1.\n\n" - - "Please consider updating (or install gnutls-cli, which is preferred)."))))) - (t - (error! "Important: couldn't find either gnutls-cli nor openssl") - (explain! - "You may not be able to install/update packages because Emacs won't be able to " - "verify HTTPS ELPA sources. Install gnutls-cli or openssl v1.0.0+. If for some " - "reason you can't, you can bypass this verification with the INSECURE flag:\n\n" - - " INSECURE=1 make install\n\n" - - "Or change `package-archives' to use non-https sources.\n\n" - - "But remember that you're leaving your security in the hands of your " - "network, provider, government, neckbearded mother-in-laws, geeky roommates, " - "or just about anyone who knows more about computers than you do!"))) - - ;; are certificates validated properly? - (section! "Testing your root certificates...") - (cond ((not (ignore-errors (gnutls-available-p))) - (warn! "Warning: Emacs wasn't installed with gnutls support") - (explain! - "This may cause 'pecular error' errors with the Doom doctor, and is likely to " - "interfere with package management. Your mileage may vary." - (when (eq system-type 'darwin) - (concat "\nMacOS users are advised to install Emacs via homebrew with one of the following:\n" - " brew install emacs --with-gnutls" - " or" - " brew tap d12frosted/emacs-plus" - " brew install emacs-plus")))) - - ((not (fboundp 'url-retrieve-synchronously)) - (error! "Can't find url-retrieve-synchronously function. Are you sure you're on Emacs 24+?")) - - ((or (executable-find "gnutls-cli") - (executable-find "openssl")) - (let ((tls-checktrust t) - (gnutls-verify-error t)) - (dolist (url '("https://elpa.gnu.org" "https://melpa.org")) - (pcase (condition-case-unless-debug e - (unless (let ((inhibit-message t)) (url-retrieve-synchronously url)) - 'empty) - ('timed-out 'timeout) - ('error e)) - (`nil nil) - (`empty (error! "Couldn't reach %s" url)) - (`timeout (error! "Timed out trying to contact %s" ex)) - (it - (error! "Failed to validate %s" url) - (explain! (pp-to-string it))))) - (dolist (url '("https://self-signed.badssl.com" - "https://wrong.host.badssl.com/")) - (pcase (condition-case-unless-debug e - (if (let ((inhibit-message t)) (url-retrieve-synchronously url)) - t - 'empty) - ('timed-out 'timeout) - ('error)) - (`nil nil) - (`empty (error! "Couldn't reach %s" url)) - (`timeout (error! "Timed out trying to contact %s" ex)) - (_ - (error! "Validated %s (this shouldn't happen!)" url))))))) - - ;; which variant of tar is on your system? bsd or gnu tar? - (section! "Checking for GNU/BSD tar...") - (let ((tar-bin (or (executable-find "gtar") - (executable-find "tar")))) - (if tar-bin - (unless (string-match-p "(GNU tar)" (sh "%s --version" tar-bin)) - (warn! "Warning: BSD tar detected") - (explain! - "QUELPA (through package-build) uses the system tar to build plugins, but it " - "expects GNU tar. BSD tar *could* cause errors during package installation or " - "updating from non-ELPA sources." - (when (eq system-type 'darwin) - (concat "\nMacOS users can install gnu-tar via homebrew:\n" - " brew install gnu-tar")))) - (error! "Important: Couldn't find tar") - (explain! - "This is required by package.el and QUELPA to build packages and will " - "prevent you from installing & updating packages.")))) + (explain! "DOOM was designed for MacOS and Linux. Expect a bumpy ride!"))) ;; @@ -292,12 +191,11 @@ (condition-case-unless-debug ex (let ((after-init-time (current-time)) - (doom-message-backend 'ansi) - noninteractive) + (doom-format-backend 'ansi)) (section! "Checking DOOM Emacs...") (load (concat user-emacs-directory "core/core.el") nil t) (unless (file-directory-p doom-private-dir) - (error "No DOOMDIR was found, did you run `doom quickstart` yet?")) + (error "No DOOMDIR was found, did you run `doom install` yet?")) (let ((indent 2)) ;; Make sure everything is loaded @@ -317,6 +215,7 @@ (success! "Initialized %d modules" (hash-table-count doom-modules)) (warn! "Failed to load any modules. Do you have an private init.el?")) + (doom-ensure-straight) (doom-initialize-packages) (success! "Initialized %d packages" (length doom-packages)) @@ -343,11 +242,10 @@ doom-disabled-packages) (load packages-file 'noerror 'nomessage) (mapcar #'car doom-packages)) - for name = (doom-package-true-name name) - unless (or (doom-package-prop name :disable) - (eval (doom-package-prop name :ignore)) - (package-built-in-p name) - (package-installed-p name)) + unless (or (doom-package-get name :disable) + (eval (doom-package-get name :ignore)) + (doom-package-built-in-p name) + (doom-package-installed-p name)) do (error! "%s is not installed" name)) (load doctor-file 'noerror 'nomessage)) (file-missing (error! "%s" (error-message-string ex))) diff --git a/core/autoload/cli.el b/core/autoload/cli.el index 693a31250..9eb3f8406 100644 --- a/core/autoload/cli.el +++ b/core/autoload/cli.el @@ -1,15 +1,18 @@ ;;; core/autoload/cli.el -*- lexical-binding: t; -*- +;; Externs +(defvar evil-collection-mode-list) + (require 'core-cli) ;;;###autoload -(defun doom-cli-run (command &rest _args) +(defun doom--cli-run (command &rest _args) (when (featurep 'general) (general-auto-unbind-keys)) (let* ((evil-collection-mode-list nil) (default-directory doom-emacs-dir) (buf (get-buffer-create " *bin/doom*")) - (doom-message-backend 'ansi) + (doom-format-backend 'ansi) (ignore-window-parameters t) (noninteractive t) (standard-output @@ -39,21 +42,21 @@ "TODO" (interactive "P") (let ((doom-auto-accept yes)) - (doom-cli-run "autoloads"))) + (doom--cli-run "autoloads"))) ;;;###autoload (defun doom//update (&optional yes) "TODO" (interactive "P") (let ((doom-auto-accept yes)) - (doom-cli-run "update"))) + (doom--cli-run "update"))) ;;;###autoload (defun doom//upgrade (&optional yes) "TODO" (interactive "P") (let ((doom-auto-accept yes)) - (doom-cli-run "upgrade")) + (doom--cli-run "upgrade")) (when (y-or-n-p "You must restart Emacs for the upgrade to take effect. Restart?") (doom/restart-and-restore))) @@ -62,18 +65,18 @@ "TODO" (interactive "P") (let ((doom-auto-accept yes)) - (doom-cli-run "install"))) + (doom--cli-run "install"))) ;;;###autoload (defun doom//autoremove (&optional yes) "TODO" (interactive "P") (let ((doom-auto-accept yes)) - (doom-cli-run "autoremove"))) + (doom--cli-run "autoremove"))) ;;;###autoload (defun doom//refresh (&optional yes) "TODO" (interactive "P") (let ((doom-auto-accept yes)) - (doom-cli-run "refresh"))) + (doom--cli-run "refresh"))) diff --git a/core/autoload/debug.el b/core/autoload/debug.el index 953af45cf..10dd3662d 100644 --- a/core/autoload/debug.el +++ b/core/autoload/debug.el @@ -16,65 +16,54 @@ ready to be pasted in a bug report on github." (require 'vc-git) (let ((default-directory doom-emacs-dir) (doom-modules (doom-modules))) - (format - (concat "- OS: %s (%s)\n" - "- Shell: %s\n" - "- Emacs: %s (%s)\n" - "- Doom: %s (%s)\n" - "- Graphic display: %s (daemon: %s)\n" - "- System features: %s\n" - "- Details:\n" - " ```elisp\n" - " env bootstrapper: %s\n" - " elc count: %s\n" - " uname -a: %s\n" - " modules: %s\n" - " packages: %s\n" - " exec-path: %s\n" - " ```") - system-type system-configuration - shell-file-name - emacs-version (format-time-string "%b %d, %Y" emacs-build-time) - doom-version - (or (string-trim (shell-command-to-string "git log -1 --format=\"%D %h %ci\"")) - "n/a") - (display-graphic-p) (daemonp) - (bound-and-true-p system-configuration-features) - (cond ((file-exists-p doom-env-file) 'envvar-file) - ((featurep 'exec-path-from-shell) 'exec-path-from-shell)) - ;; details - (length (doom-files-in `(,@doom-modules-dirs - ,doom-core-dir - ,doom-private-dir) - :type 'files :match "\\.elc$" :sort nil)) - (if IS-WINDOWS - "n/a" - (with-temp-buffer - (unless (zerop (call-process "uname" nil t nil "-msrv")) - (insert (format "%s" system-type))) - (string-trim (buffer-string)))) - (or (cl-loop with cat = nil - for key being the hash-keys of doom-modules - if (or (not cat) (not (eq cat (car key)))) - do (setq cat (car key)) - and collect cat - and collect (cdr key) - else collect - (let ((flags (doom-module-get cat (cdr key) :flags))) - (if flags - `(,(cdr key) ,@flags) - (cdr key)))) - "n/a") - (or (ignore-errors - (require 'use-package) - (cl-loop for (name . plist) in (doom-find-packages :private t) - if (use-package-plist-delete (copy-sequence plist) :modules) - collect (format "%s" (cons name it)) - else - collect (symbol-name name))) - "n/a") - ;; abbreviate $HOME to hide username - (mapcar #'abbreviate-file-name exec-path)))) + (cl-letf + (((symbol-function 'sh) + (lambda (format) + (string-trim + (shell-command-to-string format))))) + `((emacs + (version . ,emacs-version) + (features ,@system-configuration-features) + (build . ,(format-time-string "%b %d, %Y" emacs-build-time)) + (buildopts ,system-configuration-options)) + (doom + (version . ,doom-version) + (build . ,(sh "git log -1 --format=\"%D %h %ci\""))) + (system + (type . ,system-type) + (config . ,system-configuration) + (shell . ,shell-file-name) + (uname . ,(if IS-WINDOWS + "n/a" + (sh "uname -msrv"))) + (path . ,(mapcar #'abbreviate-file-name exec-path))) + (config + (envfile . ,(cond ((file-exists-p doom-env-file) 'envvar-file) + ((featurep 'exec-path-from-shell) 'exec-path-from-shell))) + (elc-files . ,(length (doom-files-in `(,@doom-modules-dirs + ,doom-core-dir + ,doom-private-dir) + :type 'files :match "\\.elc$" :sort nil))) + (modules ,@(or (cl-loop with cat = nil + for key being the hash-keys of doom-modules + if (or (not cat) (not (eq cat (car key)))) + do (setq cat (car key)) + and collect cat + and collect (cdr key) + else collect + (let ((flags (doom-module-get cat (cdr key) :flags))) + (if flags + `(,(cdr key) ,@flags) + (cdr key)))) + '("n/a"))) + (packages ,@(or (ignore-errors + (require 'use-package) + (cl-loop for (name . plist) in (doom-find-packages :private t) + if (use-package-plist-delete (copy-sequence plist) :modules) + collect (format "%s" (cons name it)) + else + collect (symbol-name name))) + '("n/a")))))))) ;; @@ -86,24 +75,55 @@ ready to be pasted in a bug report on github." branch and commit." (interactive) (require 'vc-git) - (print! "Doom v%s (Emacs v%s)\nBranch: %s\nCommit: %s" + (print! "Doom v%s (Emacs v%s)\nBranch: %s\nCommit: %s\nBuild date: %s" doom-version emacs-version (or (vc-git--symbolic-ref doom-core-dir) "n/a") (or (vc-git-working-revision doom-core-dir) + "n/a") + (or (string-trim (shell-command-to-string "git log -1 --format=%ci")) "n/a"))) ;;;###autoload -(defun doom/info () +(defun doom/info (&optional raw) "Collects some debug information about your Emacs session, formats it into markdown and copies it to your clipboard, ready to be pasted into bug reports!" - (interactive) - (message "Generating Doom info...") - (if noninteractive - (print! (doom-info)) - (kill-new (doom-info)) - (message "Done! Copied to clipboard."))) + (interactive "P") + (let ((buffer (get-buffer-create "*doom-info*")) + (info (doom-info))) + (with-current-buffer buffer + (unless (or noninteractive + (eq major-mode 'markdown-mode) + (not (fboundp 'markdown-mode))) + (markdown-mode)) + (erase-buffer) + (if raw + (progn + (save-excursion + (pp info (current-buffer))) + (when (re-search-forward "(modules " nil t) + (goto-char (match-beginning 0)) + (cl-destructuring-bind (beg . end) + (bounds-of-thing-at-point 'sexp) + (let ((sexp (prin1-to-string (sexp-at-point)))) + (delete-region beg end) + (insert sexp))))) + (insert "
\n\n```\n") + (dolist (group info) + (insert! "%-8s%-10s %s\n" + ((car group) + (caadr group) + (cdadr group))) + (dolist (spec (cddr group)) + (insert! (indent 8 "%-10s %s\n") + ((car spec) (cdr spec))))) + (insert "```\n
")) + (if noninteractive + (print! (buffer-string)) + (switch-to-buffer buffer) + (kill-new (buffer-string)) + (print! (green "Copied markdown to clipboard")))))) ;;;###autoload (defun doom/am-i-secure () @@ -144,11 +164,11 @@ markdown and copies it to your clipboard, ready to be pasted into bug reports!" (macroexp-progn (append `((setq noninteractive nil doom-debug-mode t + load-path ',load-path package--init-file-ensured t package-user-dir ,package-user-dir package-archives ',package-archives - user-emacs-directory ,doom-emacs-dir - doom--modules-cache nil) + user-emacs-directory ,doom-emacs-dir) (with-eval-after-load 'undo-tree ;; undo-tree throws errors because `buffer-undo-tree' isn't ;; corrrectly initialized diff --git a/core/autoload/help.el b/core/autoload/help.el index 9a204ebb3..d7bac09d6 100644 --- a/core/autoload/help.el +++ b/core/autoload/help.el @@ -365,10 +365,14 @@ current file is in, or d) the module associated with the current major mode (see (recenter) (message "Couldn't find the config block")))))))) +(defvar doom--help-packages-list nil) (defun doom--help-packages-list (&optional refresh) (or (unless refresh - (doom-cache-get 'help-packages)) - (doom-cache-set 'help-packages (doom-package-list 'all)))) + doom--help-packages-list) + (setq doom--help-packages-list + (append (cl-loop for package in doom-core-packages + collect (list package :modules '((:core internal)))) + (doom-package-list 'all))))) (defun doom--help-package-configs (package) ;; TODO Add git checks, in case ~/.emacs.d isn't a git repo diff --git a/core/autoload/hydras.el b/core/autoload/hydras.el index 68f6b4360..6ab2dd7a4 100644 --- a/core/autoload/hydras.el +++ b/core/autoload/hydras.el @@ -1,4 +1,4 @@ -;;; core/autoload/hydras.el -*- lexical-binding: t; -*- +;;; core/autoload/hydras.el -*- lexical-binding: t; no-byte-compile: t; -*- ;;;###autoload (autoload 'doom-text-zoom-hydra/body "core/autoload/hydras" nil t) (defhydra doom-text-zoom-hydra (:hint t :color red) diff --git a/core/autoload/packages.el b/core/autoload/packages.el index 4da43c8d8..c26673ac2 100644 --- a/core/autoload/packages.el +++ b/core/autoload/packages.el @@ -1,128 +1,67 @@ ;;; core/autoload/packages.el -*- lexical-binding: t; -*- (require 'core-packages) -(load! "cache") ; in case autoloads haven't been generated yet - - -(defun doom--packages-choose (prompt) - (let ((table (cl-loop for pkg in package-alist - unless (doom-package-built-in-p (cdr pkg)) - collect (cons (package-desc-full-name (cdr pkg)) - (cdr pkg))))) - (cdr (assoc (completing-read prompt - (mapcar #'car table) - nil t) - table)))) - -(defun doom--refresh-pkg-cache () - "Clear the cache for `doom-refresh-packages-maybe'." - (setq doom--refreshed-p nil) - (doom-cache-set 'last-pkg-refresh nil)) - -;;;###autoload -(defun doom-refresh-packages-maybe (&optional force-p) - "Refresh ELPA packages, if it hasn't been refreshed recently." - (when force-p - (doom--refresh-pkg-cache)) - (unless (or (doom-cache-get 'last-pkg-refresh) - doom--refreshed-p) - (condition-case e - (progn - (message "Refreshing package archives") - (package-refresh-contents) - (doom-cache-set 'last-pkg-refresh t 1200)) - ((debug error) - (doom--refresh-pkg-cache) - (signal 'doom-error e))))) ;; ;;; Package metadata ;;;###autoload -(defun doom-package-plist (package) +(defun doom-package-get (package &optional prop nil-value) "Returns PACKAGE's `package!' recipe from `doom-packages'." - (cdr (assq package doom-packages))) + (let ((plist (cdr (assq package doom-packages)))) + (if prop + (if (plist-member plist prop) + (plist-get plist prop) + nil-value) + plist))) ;;;###autoload -(defun doom-package-desc (package) - "Returns PACKAGE's desc struct from `package-alist'." - (cadr (assq (or (car (doom-package-prop package :recipe)) - package) - package-alist))) +(defun doom-package-recipe (package &optional prop nil-value) + "Returns the `straight' recipe PACKAGE was registered with." + (let ((plist (gethash (symbol-name package) straight--recipe-cache))) + (if prop + (if (plist-member plist prop) + (plist-get plist prop) + nil-value) + plist))) ;;;###autoload -(defun doom-package-true-name (package) - "Return PACKAGE's true name. - -It is possible for quelpa packages to be given a psuedonym (the first argument -of `package!'). Its real name is the car of package's :recipe. e.g. - - (package! X :recipe (Y :fetcher github :repo \"abc/def\")) - -X's real name is Y." - (let ((sym (car (doom-package-prop package :recipe)))) - (or (and (symbolp sym) - (not (keywordp sym)) - sym) - package))) +(defun doom-package-build-recipe (package &optional prop nil-value) + "Returns the `straight' recipe PACKAGE was installed with." + (let ((plist (nth 2 (gethash (symbol-name package) straight--build-cache)))) + (if prop + (if (plist-member plist prop) + (plist-get plist prop) + nil-value) + plist))) ;;;###autoload -(defun doom-package-psuedo-name (package) +(defun doom-package-build-time (package) "TODO" - (or (cl-loop for (package . plist) in doom-packages - for recipe-name = (car (plist-get plist :recipe)) - if (eq recipe-name package) - return recipe-name) - package)) + (car (gethash (symbol-name package) straight--build-cache))) ;;;###autoload -(defun doom-package-backend (package &optional noerror) - "Return backend that PACKAGE was installed with. +(defun doom-package-dependencies (package &optional recursive noerror) + "Return a list of dependencies for a package." + (let ((deps (nth 1 (gethash (symbol-name package) straight--build-cache)))) + (if recursive + (nconc deps (mapcan (lambda (dep) (doom-package-dependencies dep t t)) + deps)) + deps))) -Can either be elpa, quelpa or emacs (built-in). Throws an error if NOERROR is -nil and the package isn't installed. - -See `doom-package-recipe-backend' to get the backend PACKAGE is registered with -\(as opposed to what it is was installed with)." - (cl-check-type package symbol) - (let ((package-truename (doom-package-true-name package))) - (cond ((assq package-truename quelpa-cache) 'quelpa) - ((assq package-truename package-alist) 'elpa) - ((doom-package-built-in-p package) 'emacs) - ((not noerror) (error "%s package is not installed" package))))) - -;;;###autoload -(defun doom-package-recipe-backend (package &optional noerror) - "Return backend that PACKAGE is registered with. - -See `doom-package-backend' to get backend for currently installed package." - (cl-check-type package symbol) - (cond ((not (doom-package-registered-p package)) - (unless noerror - (error "%s package is not registered" package))) - ((let ((builtin (eval (doom-package-prop package :built-in) t))) - (or (and (eq builtin 'prefer) - (locate-library (symbol-name package) nil doom-site-load-path)) - (eq builtin 't))) - 'emacs) - ((doom-package-prop package :recipe) - 'quelpa) - ('elpa))) - -;;;###autoload -(defun doom-package-prop (package prop &optional nil-value) - "Return PROPerty in PACKAGE's plist. - -Otherwise returns NIL-VALUE if package isn't registered or PROP doesn't -exist/isn't specified." - (cl-check-type package symbol) - (cl-check-type prop keyword) - (if-let (plist (doom-package-plist package)) - (if (plist-member plist prop) - (plist-get plist prop) - nil-value) - nil-value)) +(defun doom-package-depending-on (package &optional noerror) + "Return a list of packages that depend on the package named NAME." + (cl-check-type name symbol) + ;; can't get dependencies for built-in packages + (unless (or (doom-package-build-recipe name) + noerror) + (error "Couldn't find %s, is it installed?" name)) + (cl-loop for pkg in (hash-table-keys straight--build-cache) + for deps = (doom-package-dependencies pkg) + if (memq package deps) + collect pkg + and append (doom-package-depending-on pkg t))) ;; @@ -131,99 +70,52 @@ exist/isn't specified." ;;;###autoload (defun doom-package-built-in-p (package) "Return non-nil if PACKAGE (a symbol) is built-in." - (unless (doom-package-installed-p package) - (or (package-built-in-p (doom-package-true-name package)) - (locate-library (symbol-name package) nil doom-site-load-path)))) + (eq (doom-package-build-recipe package :type) + 'built-in)) ;;;###autoload (defun doom-package-installed-p (package) "Return non-nil if PACKAGE (a symbol) is installed." - (when-let (desc (doom-package-desc package)) - (and (package-installed-p desc) - (file-directory-p (package-desc-dir desc))))) + (file-directory-p (straight--build-dir (symbol-name package)))) ;;;###autoload (defun doom-package-registered-p (package) "Return non-nil if PACKAGE (a symbol) has been registered with `package!'. Excludes packages that have a non-nil :built-in property." - (let ((package (or (cl-loop for (pkg . plist) in doom-packages - for newname = (car (plist-get plist :recipe)) - if (and (symbolp newname) - (eq newname package)) - return pkg) - package))) - (when-let (plist (doom-package-plist package)) - (not (eval (plist-get plist :ignore)))))) + (when-let (plist (doom-package-get package)) + (not (eval (plist-get plist :ignore) t)))) ;;;###autoload (defun doom-package-private-p (package) "Return non-nil if PACKAGE was installed by the user's private config." - (doom-package-prop package :private)) + (doom-package-get package :private)) ;;;###autoload (defun doom-package-protected-p (package) "Return non-nil if PACKAGE is protected. A protected package cannot be deleted and will be auto-installed if missing." - (memq (doom-package-true-name package) doom-core-packages)) + (memq package doom-core-packages)) ;;;###autoload (defun doom-package-core-p (package) "Return non-nil if PACKAGE is a core Doom package." (or (doom-package-protected-p package) - (assq :core (doom-package-prop package :modules)))) - -;;;###autoload -(defun doom-package-different-backend-p (package) - "Return t if a PACKAGE (a symbol) has a new backend than what it was installed -with. Returns nil otherwise, or if package isn't installed." - (cl-check-type package symbol) - (and (doom-package-installed-p package) - (not (doom-get-depending-on package)) ; not a dependency - (not (eq (doom-package-backend package 'noerror) - (doom-package-recipe-backend package 'noerror))))) + (assq :core (doom-package-get package :modules)))) ;;;###autoload (defun doom-package-different-recipe-p (name) "Return t if a package named NAME (a symbol) has a different recipe than it was installed with." (cl-check-type name symbol) - (when (doom-package-installed-p name) - (let ((package-truename (doom-package-true-name name))) - (when-let* ((quelpa-recipe (assq package-truename quelpa-cache)) - (doom-recipe (assq package-truename doom-packages))) - (not (equal (cdr quelpa-recipe) - (cdr (plist-get (cdr doom-recipe) :recipe)))))))) - -(defvar quelpa-upgrade-p) -;;;###autoload -(defun doom-package-outdated-p (name) - "Determine whether NAME (a symbol) is outdated or not. - -If outdated, returns a list, whose car is NAME, and cdr the current version list -and latest version list of the package." - (cl-check-type name symbol) - (when-let (desc (doom-package-desc name)) - (let* ((old-version (package-desc-version desc)) - (new-version - (pcase (doom-package-backend name) - (`quelpa - (let ((recipe (doom-package-prop name :recipe)) - (dir (expand-file-name (symbol-name name) quelpa-build-dir)) - (inhibit-message (not doom-debug-mode)) - (quelpa-upgrade-p t)) - (if-let (ver (quelpa-checkout recipe dir)) - (version-to-list ver) - old-version))) - (`elpa - (let ((desc (cadr (assq name package-archive-contents)))) - (when (package-desc-p desc) - (package-desc-version desc))))))) - (unless (and (listp old-version) (listp new-version)) - (error "Couldn't get version for %s" name)) - (when (version-list-< old-version new-version) - (list name old-version new-version))))) + ;; TODO + ;; (when (doom-package-installed-p name) + ;; (when-let* ((doom-recipe (assq name doom-packages)) + ;; (install-recipe (doom-package-recipe))) + ;; (not (equal (cdr quelpa-recipe) + ;; (cdr (plist-get (cdr doom-recipe) :recipe)))))) + ) ;; @@ -307,32 +199,37 @@ files." collect (cons sym plist) and if (and deps (not (doom-package-built-in-p sym))) nconc - (cl-loop for pkg in (doom-get-dependencies-for sym 'recursive 'noerror) + (cl-loop for pkg in (doom-package-dependencies sym 'recursive 'noerror) if (or (eq installed 'any) (if installed (doom-package-installed-p pkg) (not (doom-package-installed-p pkg)))) collect (cons pkg (cdr (assq pkg doom-packages))))))) -(defun doom--read-module-packages-file (file &optional raw noerror) +(defun doom--read-module-packages-file (file &optional eval noerror) (with-temp-buffer ; prevent buffer-local settings from propagating (condition-case e - (if (not raw) + (if (not eval) (load file noerror t t) (when (file-readable-p file) (insert-file-contents file) + (delay-mode-hooks (emacs-lisp-mode)) (while (re-search-forward "(package! " nil t) (save-excursion (goto-char (match-beginning 0)) - (cl-destructuring-bind (name . plist) (cdr (sexp-at-point)) - (push (cons name - (plist-put plist :modules - (cond ((file-in-directory-p file doom-private-dir) - '((:private))) - ((file-in-directory-p file doom-core-dir) - '((:core))) - ((doom-module-from-path file))))) - doom-packages)))))) + (unless (string-match-p + "^.*;" (buffer-substring-no-properties + (line-beginning-position) + (point))) + (cl-destructuring-bind (name . plist) (cdr (sexp-at-point)) + (push (cons name + (plist-put plist :modules + (cond ((file-in-directory-p file doom-private-dir) + '((:private))) + ((file-in-directory-p file doom-core-dir) + '((:core))) + ((doom-module-from-path file))))) + doom-packages))))))) ((debug error) (signal 'doom-package-error (list (or (doom-module-from-path file) @@ -350,15 +247,16 @@ ones." (let ((noninteractive t) (doom-modules (doom-modules)) doom-packages - doom-disabled-packages - package-pinned-packages) - (doom--read-module-packages-file (expand-file-name "packages.el" doom-core-dir) all-p) + doom-disabled-packages) + (doom--read-module-packages-file + (expand-file-name "packages.el" doom-core-dir) + all-p t) (let ((private-packages (expand-file-name "packages.el" doom-private-dir))) (unless all-p ;; We load the private packages file twice to ensure disabled packages ;; are seen ASAP, and a second time to ensure privately overridden ;; packages are properly overwritten. - (doom--read-module-packages-file private-packages nil t)) + (doom--read-module-packages-file private-packages t t)) (if all-p (mapc #'doom--read-module-packages-file (doom-files-in doom-modules-dir @@ -371,192 +269,11 @@ ones." for doom--current-module = key do (doom--read-module-packages-file path nil t))) (doom--read-module-packages-file private-packages all-p t)) - (append (cl-loop for package in doom-core-packages - collect (list package :modules '((:core internal)))) - (nreverse doom-packages)))) - -;;;###autoload -(defun doom-get-package-alist () - "Returns a list of all desired packages, their dependencies and their desc -objects, in the order of their `package! blocks.'" - (cl-remove-duplicates - (cl-loop for name in (mapcar #'car doom-packages) - if (assq name package-alist) - nconc (cl-loop for dep in (package--get-deps name) - if (assq dep package-alist) - collect (cons dep (cadr it))) - and collect (cons name (cadr it))) - :key #'car - :from-end t)) - -;;;###autoload -(defun doom-get-depending-on (name &optional noerror) - "Return a list of packages that depend on the package named NAME." - (cl-check-type name symbol) - (setq name (or (car (doom-package-prop name :recipe)) name)) - (unless (doom-package-built-in-p name) - (if-let (desc (cadr (assq name package-alist))) - (mapcar #'package-desc-name (package--used-elsewhere-p desc nil t)) - (unless noerror - (error "Couldn't find %s, is it installed?" name))))) - -;;;###autoload -(defun doom-get-dependencies-for (name &optional recursive noerror) - "Return a list of dependencies for a package." - (cl-check-type name symbol) - ;; can't get dependencies for built-in packages - (unless (doom-package-built-in-p name) - (if-let (desc (doom-package-desc name)) - (let* ((deps (mapcar #'car (package-desc-reqs desc))) - (deps (cl-remove-if #'doom-package-built-in-p deps))) - (if recursive - (nconc deps (mapcan (lambda (dep) (doom-get-dependencies-for dep t t)) - deps)) - deps)) - (unless noerror - (error "Couldn't find %s, is it installed?" name))))) - -;;;###autoload -(defun doom-get-outdated-packages (&optional include-frozen-p) - "Return a list of packages that are out of date. Each element is a list, -containing (PACKAGE-SYMBOL OLD-VERSION-LIST NEW-VERSION-LIST). - -If INCLUDE-FROZEN-P is non-nil, check frozen packages as well. - -Used by `doom-packages-update'." - (doom-refresh-packages-maybe doom-debug-mode) - (cl-loop for package in (mapcar #'car package-alist) - when (and (or (not (eval (doom-package-prop package :freeze))) - include-frozen-p) - (not (eval (doom-package-prop package :ignore))) - (not (doom-package-different-backend-p package)) - (doom-package-outdated-p package)) - collect it)) - -;;;###autoload -(defun doom-get-orphaned-packages () - "Return a list of symbols representing packages that are no longer needed or -depended on. - -Used by `doom-packages-autoremove'." - (let ((package-selected-packages - (mapcar #'car (doom-find-packages :ignored nil :disabled nil)))) - (append (cl-remove-if #'doom-package-registered-p (package--removable-packages)) - (cl-loop for pkg in package-selected-packages - if (and (doom-package-different-backend-p pkg) - (not (doom-package-built-in-p pkg))) - collect pkg)))) - -;;;###autoload -(defun doom-get-missing-packages () - "Return a list of requested packages that aren't installed or built-in, but -are enabled (with a `package!' directive). Each element is a list whose CAR is -the package symbol, and whose CDR is a plist taken from that package's -`package!' declaration. - -Used by `doom-packages-install'." - (cl-loop for (name . plist) - in (doom-find-packages :ignored nil - :disabled nil - :deps t) - if (and (equal (plist-get plist :pin) - (ignore-errors - (package-desc-archive - (cadr (assq name package-alist))))) - (or (not (doom-package-installed-p name)) - (doom-package-different-backend-p name) - (doom-package-different-recipe-p name))) - collect (cons name plist))) + (nreverse doom-packages))) ;; -;; Main functions - -(defun doom--delete-package-files (name-or-desc) - (let ((pkg-build-dir - (if (package-desc-p name-or-desc) - (package-desc-dir name-or-desc) - (expand-file-name (symbol-name name-or-desc) quelpa-build-dir)))) - (when (file-directory-p pkg-build-dir) - (delete-directory pkg-build-dir t)))) - -;;;###autoload -(defun doom-install-package (name &optional plist) - "Installs package NAME with optional quelpa RECIPE (see `quelpa-recipe' for an -example; the package name can be omitted)." - (cl-check-type name symbol) - (when (and (doom-package-installed-p name) - (not (doom-package-built-in-p name))) - (if (or (doom-package-different-backend-p name) - (doom-package-different-recipe-p name)) - (doom-delete-package name t) - (user-error "%s is already installed" name))) - (let* ((inhibit-message (not doom-debug-mode)) - (plist (or plist (doom-package-plist name)))) - (if-let (recipe (plist-get plist :recipe)) - (condition-case e - (let (quelpa-upgrade-p) - (quelpa recipe)) - ((debug error) - (doom--delete-package-files name) - (signal (car e) (cdr e)))) - (package-install name)) - (if (not (doom-package-installed-p name)) - (doom--delete-package-files name) - (add-to-list 'package-selected-packages name nil 'eq) - (setf (alist-get name doom-packages) plist) - name))) - -;;;###autoload -(defun doom-update-package (name &optional force-p) - "Updates package NAME (a symbol) if it is out of date, using quelpa or -package.el as appropriate." - (cl-check-type name symbol) - (unless (doom-package-installed-p name) - (error "%s isn't installed" name)) - (when (doom-package-different-backend-p name) - (user-error "%s's backend has changed and must be uninstalled first" name)) - (when (or force-p (doom-package-outdated-p name)) - (let ((inhibit-message (not doom-debug-mode)) - (desc (doom-package-desc name))) - (pcase (doom-package-backend name) - (`quelpa - (let ((name (doom-package-true-name name))) - (condition-case e - (let ((quelpa-upgrade-p t)) - (quelpa (assq name quelpa-cache))) - ((debug error) - (doom--delete-package-files name) - (signal (car e) (cdr e)))))) - (`elpa - (let* ((archive (cadr (assq name package-archive-contents))) - (packages - (if (package-desc-p archive) - (package-compute-transaction (list archive) (package-desc-reqs archive)) - (package-compute-transaction () (list (list archive)))))) - (package-download-transaction packages)))) - (unless (doom-package-outdated-p name) - (doom--delete-package-files desc) - t)))) - -;;;###autoload -(defun doom-delete-package (name &optional force-p) - "Uninstalls package NAME if it exists, and clears it from `quelpa-cache'." - (cl-check-type name symbol) - (unless (doom-package-installed-p name) - (user-error "%s isn't installed" name)) - (let ((inhibit-message (not doom-debug-mode)) - (name (doom-package-true-name name))) - (when-let (spec (assq name quelpa-cache)) - (delq! spec quelpa-cache) - (quelpa-save-cache)) - (package-delete (doom-package-desc name) force-p) - (doom--delete-package-files name) - (not (doom-package-installed-p name)))) - - -;; -;; Interactive commands +;;; Main functions ;;;###autoload (defun doom/reload-packages () @@ -565,61 +282,3 @@ package.el as appropriate." (message "Reloading packages") (doom-initialize-packages t) (message "Reloading packages...DONE")) - -;;;###autoload -(defun doom/update-package (pkg) - "Prompts the user with a list of outdated packages and updates the selected -package. Use this interactively. Use `doom-update-package' for direct -calls." - (declare (interactive-only t)) - (interactive - (let* ((packages (doom-get-outdated-packages)) - (selection (if packages - (completing-read "Update package: " - (mapcar #'car packages) - nil t) - (user-error "All packages are up to date"))) - (name (car (assoc (intern selection) package-alist)))) - (unless name - (user-error "'%s' is already up-to-date" selection)) - (list (assq name packages)))) - (cl-destructuring-bind (package old-version new-version) pkg - (if-let (desc (doom-package-outdated-p package)) - (let ((old-v-str (package-version-join old-version)) - (new-v-str (package-version-join new-version))) - (if (y-or-n-p (format "%s will be updated from %s to %s. Update?" - package old-v-str new-v-str)) - (message "%s %s (%s => %s)" - (if (doom-update-package package t) "Updated" "Failed to update") - package old-v-str new-v-str) - (message "Aborted"))) - (message "%s is up-to-date" package)))) - - -;; -;; Advice - -;;;###autoload -(defun doom*package-delete (desc &rest _) - "Update `quelpa-cache' upon a successful `package-delete'." - (let ((name (package-desc-name desc))) - (unless (doom-package-installed-p name) - (when-let (spec (assq name quelpa-cache)) - (setq quelpa-cache (delq spec quelpa-cache)) - (quelpa-save-cache) - (doom--delete-package-files name))))) - - -;; -;; Make package.el cooperate with Doom - -;; Updates QUELPA after deleting a package -;;;###autoload -(advice-add #'package-delete :after #'doom*package-delete) - -;; Replace with Doom variants -;;;###autoload -(advice-add #'package-autoremove :override #'doom//autoremove) - -;;;###autoload -(advice-add #'package-install-selected-packages :override #'doom//install) diff --git a/core/cli/autoloads.el b/core/cli/autoloads.el index e16b74ebe..2f4bbf98a 100644 --- a/core/cli/autoloads.el +++ b/core/cli/autoloads.el @@ -1,7 +1,6 @@ ;;; core/cli/autoloads.el -*- lexical-binding: t; -*- -(dispatcher! (autoloads a) - (doom-reload-autoloads nil 'force) +(def-command! (autoloads a) () "Regenerates Doom's autoloads files. It scans and reads autoload cookies (;;;###autoload) in core/autoload/*.el, @@ -10,7 +9,8 @@ byte-compiles `doom-autoload-file', as well as `doom-package-autoload-file' (created from the concatenated autoloads files of all installed packages). It also caches `load-path', `Info-directory-list', `doom-disabled-packages', -`package-activated-list' and `auto-mode-alist'.") +`package-activated-list' and `auto-mode-alist'." + (doom-reload-autoloads nil 'force)) ;; external variables (defvar autoload-timestamps) @@ -21,55 +21,45 @@ It also caches `load-path', `Info-directory-list', `doom-disabled-packages', ;; ;;; Helpers -(defvar doom-autoload-excluded-packages '(marshal gh) - "Packages that have silly or destructive autoload files that try to load -everyone in the universe and their dog, causing errors that make babies cry. No -one wants that.") - (defun doom-delete-autoloads-file (file) "Delete FILE (an autoloads file) and accompanying *.elc file, if any." (cl-check-type file string) (when (file-exists-p file) - (when-let (buf (find-buffer-visiting doom-autoload-file)) + (when-let (buf (find-buffer-visiting file)) (with-current-buffer buf (set-buffer-modified-p nil)) (kill-buffer buf)) (delete-file file) (ignore-errors (delete-file (byte-compile-dest-file file))) - (message "Deleted old %s" (file-name-nondirectory file)))) + t)) (defun doom--warn-refresh-session () - (print! (bold (green "\nFinished!"))) - (message "If you have a running Emacs Session, you will need to restart it or") - (message "reload Doom for changes to take effect:\n") + (message "Restart or reload Doom Emacs for changes to take effect:\n") (message " M-x doom/restart-and-restore") (message " M-x doom/restart") (message " M-x doom/reload")) (defun doom--reload-files (&rest files) - (if (not noninteractive) - (dolist (file files) - (load-file (byte-compile-dest-file file))) - (add-hook 'kill-emacs-hook #'doom--warn-refresh-session))) + (if noninteractive + (add-hook 'doom-cli-post-execute-hook #'doom--warn-refresh-session) + (dolist (file files) + (load-file (byte-compile-dest-file file))))) (defun doom--byte-compile-file (file) - (let ((short-name (file-name-nondirectory file)) - (byte-compile-dynamic-docstrings t)) + (let ((byte-compile-warnings (if doom-debug-mode byte-compile-warnings))) (condition-case e (when (byte-compile-file file) ;; Give autoloads file a chance to report error (load (if doom-debug-mode file (byte-compile-dest-file file)) - nil t) - (unless noninteractive - (message "Finished compiling %s" short-name))) + nil t)) ((debug error) (let ((backup-file (concat file ".bk"))) - (message "Copied backup to %s" backup-file) + (print! (warn "- Copied backup to %s") (relpath backup-file)) (copy-file file backup-file 'overwrite)) (doom-delete-autoloads-file file) - (signal 'doom-autoload-error (list short-name e)))))) + (signal 'doom-autoload-error (list file e)))))) (defun doom-reload-autoloads (&optional file force-p) "Reloads FILE (an autoload file), if it needs reloading. @@ -82,71 +72,133 @@ even if it doesn't need reloading!" (signal 'wrong-type-argument (list 'stringp file))) (if (stringp file) (cond ((file-equal-p file doom-autoload-file) - (doom-reload-doom-autoloads force-p)) + (doom-reload-core-autoloads force-p)) ((file-equal-p file doom-package-autoload-file) (doom-reload-package-autoloads force-p)) ((error "Invalid autoloads file: %s" file))) - (doom-reload-doom-autoloads force-p) + (doom-reload-core-autoloads force-p) (doom-reload-package-autoloads force-p))) ;; ;;; Doom autoloads -(defun doom--file-cookie-p (file) - "Returns the return value of the ;;;###if predicate form in FILE." - (with-temp-buffer - (insert-file-contents-literally file nil 0 256) - (if (and (re-search-forward "^;;;###if " nil t) - (<= (line-number-at-pos) 3)) - (let ((load-file-name file)) - (eval (sexp-at-point))) - t))) - (defun doom--generate-header (func) (goto-char (point-min)) - (insert ";; -*- lexical-binding:t -*-\n" + (insert ";; -*- lexical-binding:t; byte-compile-dynamic-docstrings: t; -*-\n" ";; This file is autogenerated by `" (symbol-name func) "', DO NOT EDIT !!\n\n")) (defun doom--generate-autoloads (targets) - (require 'autoload) - (dolist (file targets) - (let* ((file (file-truename file)) - (generated-autoload-file doom-autoload-file) - (generated-autoload-load-name (file-name-sans-extension file)) - (noninteractive (not doom-debug-mode)) - autoload-timestamps) - (print! - (cond ((not (doom--file-cookie-p file)) - "⚠ Ignoring %s") - ((autoload-generate-file-autoloads file (current-buffer)) - (yellow "✕ Nothing in %s")) - ((green "✓ Scanned %s"))) - (if (file-in-directory-p file default-directory) - (file-relative-name file) - (abbreviate-file-name file)))))) + (let ((n 0)) + (dolist (file targets) + (insert + (with-temp-buffer + (cond ((not (doom-file-cookie-p file)) + (print! (debug "Ignoring %s") (relpath file))) -(defun doom--expand-autoloads () + ((let ((generated-autoload-load-name (file-name-sans-extension file))) + (require 'autoload) + (autoload-generate-file-autoloads file (current-buffer))) + (print! (debug "Nothing in %s") (relpath file))) + + ((cl-incf n) + (print! (debug "Scanning %s...") (relpath file)))) + (buffer-string)))) + (print! (class (if (> n 0) 'success 'info) + "Scanned %d file(s)") + n))) + +(defun doom--expand-autoload-paths (&optional allow-internal-paths) (let ((load-path ;; NOTE With `doom-private-dir' in `load-path', Doom autoloads files ;; will be unable to declare autoloads for the built-in autoload.el ;; Emacs package, should $DOOMDIR/autoload.el exist. Not sure why ;; they'd want to though, so it's an acceptable compromise. - (append (list doom-private-dir doom-emacs-dir) + (append (list doom-private-dir) doom-modules-dirs - load-path)) - cache) - (while (re-search-forward "^\\s-*(autoload\\s-+'[^ ]+\\s-+\"\\([^\"]*\\)\"" nil t) + (straight--directory-files (straight--build-dir) nil t) + load-path))) + (defvar doom--autoloads-path-cache nil) + (while (re-search-forward "^\\s-*(\\(?:custom-\\)?autoload\\s-+'[^ ]+\\s-+\"\\([^\"]*\\)\"" nil t) (let ((path (match-string 1))) (replace-match - (or (cdr (assoc path cache)) - (when-let* ((libpath (locate-library path)) - (libpath (file-name-sans-extension libpath))) - (push (cons path (abbreviate-file-name libpath)) cache) + (or (cdr (assoc path doom--autoloads-path-cache)) + (when-let* ((libpath (or (and allow-internal-paths + (locate-library path nil (cons doom-emacs-dir doom-modules-dirs))) + (locate-library path))) + (libpath (file-name-sans-extension libpath)) + (libpath (abbreviate-file-name libpath))) + (push (cons path libpath) doom--autoloads-path-cache) libpath) path) t t nil 1))))) +(defun doom--generate-autodefs-1 (path &optional member-p) + (let (forms) + (while (re-search-forward "^;;;###autodef *\\([^\n]+\\)?\n" nil t) + (let* ((sexp (sexp-at-point)) + (alt-sexp (match-string 1)) + (type (car sexp)) + (name (doom-unquote (cadr sexp))) + (origin (cond ((doom-module-from-path path)) + ((file-in-directory-p path doom-private-dir) + `(:private . ,(intern (file-name-base path)))) + ((file-in-directory-p path doom-emacs-dir) + `(:core . ,(intern (file-name-base path))))))) + (cond + ((and (not member-p) + alt-sexp) + (push (read alt-sexp) forms)) + + ((memq type '(defun defmacro cl-defun cl-defmacro)) + (cl-destructuring-bind (_ _name arglist &rest body) sexp + (let ((docstring (if (stringp (car body)) + (pop body) + "No documentation."))) + (appendq! + forms + (list (if member-p + (make-autoload sexp (abbreviate-file-name (file-name-sans-extension path))) + (setq docstring (format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s" + origin docstring)) + (condition-case-unless-debug e + (if alt-sexp + (read alt-sexp) + (append (list (pcase type + (`defun 'defmacro) + (`cl-defun `cl-defmacro) + (_ type)) + name arglist docstring) + (cl-loop for arg in arglist + if (and (symbolp arg) + (not (keywordp arg)) + (not (memq arg cl--lambda-list-keywords))) + collect arg into syms + else if (listp arg) + collect (car arg) into syms + finally return (if syms `((ignore ,@syms)))))) + ('error + (print! "- Ignoring autodef %s (%s)" name e) + nil))) + `(put ',name 'doom-module ',origin)))))) + + ((eq type 'defalias) + (cl-destructuring-bind (_type name target &optional docstring) sexp + (let ((name (doom-unquote name)) + (target (doom-unquote target))) + (unless member-p + (setq target #'ignore + docstring + (format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s" + origin docstring))) + (appendq! + forms + `((put ',name 'doom-module ',origin) + (defalias ',name #',target ,docstring)))))) + + (member-p (push sexp forms))))) + forms)) + (defun doom--generate-autodefs (targets enabled-targets) (goto-char (point-max)) (search-backward ";;;***" nil t) @@ -155,83 +207,17 @@ even if it doesn't need reloading!" (insert (with-temp-buffer (insert-file-contents path) - (let ((member-p (or (member path enabled-targets) - (file-in-directory-p path doom-core-dir))) - forms) - (while (re-search-forward "^;;;###autodef *\\([^\n]+\\)?\n" nil t) - (let* ((sexp (sexp-at-point)) - (alt-sexp (match-string 1)) - (type (car sexp)) - (name (doom-unquote (cadr sexp))) - (origin (cond ((doom-module-from-path path)) - ((file-in-directory-p path doom-private-dir) - `(:private . ,(intern (file-name-base path)))) - ((file-in-directory-p path doom-emacs-dir) - `(:core . ,(intern (file-name-base path)))))) - (doom-file-form - `(put ',name 'doom-file ,(abbreviate-file-name path)))) - (cond ((and (not member-p) alt-sexp) - (push (read alt-sexp) forms)) - - ((memq type '(defun defmacro cl-defun cl-defmacro)) - (cl-destructuring-bind (_ name arglist &rest body) sexp - (let ((docstring (if (stringp (car body)) - (pop body) - "No documentation."))) - (push (if member-p - (make-autoload sexp (abbreviate-file-name (file-name-sans-extension path))) - (push doom-file-form forms) - (setq docstring (format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s" - origin docstring)) - (condition-case-unless-debug e - (if alt-sexp - (read alt-sexp) - (append (list (pcase type - (`defun 'defmacro) - (`cl-defun `cl-defmacro) - (_ type)) - name arglist docstring) - (cl-loop for arg in arglist - if (and (symbolp arg) - (not (keywordp arg)) - (not (memq arg cl--lambda-list-keywords))) - collect arg into syms - else if (listp arg) - collect (car arg) into syms - finally return (if syms `((ignore ,@syms)))))) - ('error - (message "Ignoring autodef %s (%s)" - name e) - nil))) - forms) - (push `(put ',name 'doom-module ',origin) forms)))) - - ((eq type 'defalias) - (cl-destructuring-bind (_type name target &optional docstring) sexp - (let ((name (doom-unquote name)) - (target (doom-unquote target))) - (unless member-p - (setq docstring (format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s" - origin docstring)) - (setq target #'ignore)) - (push doom-file-form forms) - (push `(put ',name 'doom-module ',origin) forms) - (push `(defalias ',name #',target ,docstring) - forms)))) - - (member-p - (push sexp forms))))) - (if forms - (concat (mapconcat #'prin1-to-string (nreverse forms) "\n") - "\n") - "")))))) + (if-let (forms (doom--generate-autodefs-1 path (member path enabled-targets))) + (concat (mapconcat #'prin1-to-string (nreverse forms) "\n") + "\n") + ""))))) (defun doom--cleanup-autoloads () (goto-char (point-min)) (when (re-search-forward "^;;\\(;[^\n]*\\| no-byte-compile: t\\)\n" nil t) (replace-match "" t t))) -(defun doom-reload-doom-autoloads (&optional force-p) +(defun doom-reload-core-autoloads (&optional force-p) "Refreshes `doom-autoload-file', if necessary (or if FORCE-P is non-nil). It scans and reads autoload cookies (;;;###autoload) in core/autoload/*.el, @@ -241,62 +227,78 @@ modules/*/*/autoload.el and modules/*/*/autoload/*.el, and generates Run this whenever your `doom!' block, or a module autoload file, is modified." (let* ((default-directory doom-emacs-dir) (doom-modules (doom-modules)) - (abbreviated-home-dir (if IS-WINDOWS "\\`'" abbreviated-home-dir)) - (targets - (file-expand-wildcards - (expand-file-name "autoload/*.el" doom-core-dir))) - (enabled-targets (copy-sequence targets)) - case-fold-search) - (dolist (path (doom-module-load-path t)) - (let* ((auto-dir (expand-file-name "autoload" path)) - (auto-file (expand-file-name "autoload.el" path)) - (module (doom-module-from-path auto-file)) - (module-p (or (doom-module-p (car module) (cdr module)) - (file-equal-p path doom-private-dir)))) - (when (file-exists-p auto-file) - (push auto-file targets) - (if module-p (push auto-file enabled-targets))) - (dolist (file (doom-files-in auto-dir :match "\\.el$" :full t :sort nil)) - (push file targets) - (if module-p (push file enabled-targets))))) - (if (and (not force-p) - (not doom-emacs-changed-p) - (file-exists-p doom-autoload-file) - (not (file-newer-than-file-p (expand-file-name "init.el" doom-private-dir) - doom-autoload-file)) - (not (cl-loop for file in targets - if (file-newer-than-file-p file doom-autoload-file) - return t))) - (progn (print! (green "Doom core autoloads is up-to-date")) - (doom-initialize-autoloads doom-autoload-file) - nil) - (doom-delete-autoloads-file doom-autoload-file) - (message "Generating new autoloads.el") - (make-directory (file-name-directory doom-autoload-file) t) - (with-temp-file doom-autoload-file - (doom--generate-header 'doom-reload-doom-autoloads) - (prin1 `(setq doom--modules-cache ',doom-modules) (current-buffer)) - (save-excursion - (doom--generate-autoloads (reverse enabled-targets))) - ;; Replace autoload paths (only for module autoloads) with absolute - ;; paths for faster resolution during load and simpler `load-path' - (save-excursion - (doom--expand-autoloads) - (print! (green "✓ Expanded module autoload paths"))) - ;; Generates stub definitions for functions/macros defined in disabled - ;; modules, so that you will never get a void-function when you use - ;; them. - (save-excursion - (doom--generate-autodefs (reverse targets) enabled-targets) - (print! (green "✓ Generated autodefs"))) - ;; Remove byte-compile-inhibiting file variables so we can byte-compile - ;; the file, and autoload comments. - (doom--cleanup-autoloads) - (print! (green "✓ Clean up autoloads"))) - ;; Byte compile it to give the file a chance to reveal errors. - (doom--byte-compile-file doom-autoload-file) - (doom--reload-files doom-autoload-file) - t))) + + ;; The following bindings are in `package-generate-autoloads'. + ;; Presumably for a good reason, so I just copied them + (noninteractive t) + (backup-inhibited t) + (version-control 'never) + (case-fold-search nil) ; reduce magit + (autoload-timestamps nil) + + ;; Where we'll store the files we'll scan for autoloads. This should + ;; contain *all* autoload files, even in disabled modules, so we can + ;; scan those for autodefs. We start with the core libraries. + (targets (doom-glob doom-core-dir "autoload/*.el")) + ;; A subset of `targets' in enabled modules + (active-targets (copy-sequence targets))) + + (dolist (path (doom-module-load-path 'all-p)) + (when-let* ((files (cons (doom-glob path "autoload.el") + (doom-files-in (doom-path path "autoload") + :match "\\.el$"))) + (files (delq nil files))) + (appendq! targets files) + (when (or (doom-module-from-path path 'enabled-only) + (file-equal-p path doom-private-dir)) + (appendq! active-targets files)))) + + (print! (start "Checking core autoloads file")) + (print-group! + (if (and (not force-p) + (file-exists-p doom-autoload-file) + (not (file-newer-than-file-p doom-emacs-dir doom-autoload-file)) + (not (cl-loop for dir + in (append (doom-glob doom-private-dir "init.el*") + targets) + if (file-newer-than-file-p dir doom-autoload-file) + return t))) + (ignore + (print! (success "Skipping core autoloads, they are up-to-date")) + (doom-initialize-autoloads doom-autoload-file)) + (print! (start "Regenerating core autoloads file")) + + (if (doom-delete-autoloads-file doom-autoload-file) + (print! (success "Deleted old %s") (filename doom-autoload-file)) + (make-directory (file-name-directory doom-autoload-file) t)) + + (with-temp-file doom-autoload-file + (doom--generate-header 'doom-reload-core-autoloads) + (save-excursion + (doom--generate-autoloads active-targets) + (print! (success "Generated new autoloads.el"))) + ;; Replace autoload paths (only for module autoloads) with absolute + ;; paths for faster resolution during load and simpler `load-path' + (save-excursion + (doom--expand-autoload-paths 'allow-internal-paths) + (print! (success "Expanded module autoload paths"))) + ;; Generates stub definitions for functions/macros defined in disabled + ;; modules, so that you will never get a void-function when you use + ;; them. + (save-excursion + (doom--generate-autodefs targets (reverse active-targets)) + (print! (success "Generated autodefs"))) + ;; Remove byte-compile-inhibiting file variables so we can byte-compile + ;; the file, and autoload comments. + (doom--cleanup-autoloads) + (print! (success "Clean up autoloads"))) + ;; Byte compile it to give the file a chance to reveal errors (and buy us a + ;; few marginal performance boosts) + (print! "> Byte-compiling %s..." (relpath doom-autoload-file)) + (when (doom--byte-compile-file doom-autoload-file) + (print! (success "Finished compiling %s") (relpath doom-autoload-file))) + (doom--reload-files doom-autoload-file)) + t))) ;; @@ -304,33 +306,49 @@ Run this whenever your `doom!' block, or a module autoload file, is modified." (defun doom--generate-package-autoloads () "Concatenates package autoload files, let-binds `load-file-name' around -them,and remove unnecessary `provide' statements or blank links. - -Skips over packages in `doom-autoload-excluded-packages'." - (dolist (spec (doom-get-package-alist)) - (if-let* ((pkg (car spec)) - (desc (cdr spec))) - (unless (memq pkg doom-autoload-excluded-packages) - (let ((file (concat (package--autoloads-file-name desc) ".el"))) - (when (file-exists-p file) - (insert "(let ((load-file-name " (prin1-to-string (abbreviate-file-name file)) "))\n") - (insert-file-contents file) - (while (re-search-forward "^\\(?:;;\\(.*\n\\)\\|\n\\|(provide '[^\n]+\\)" nil t) - (unless (nth 8 (syntax-ppss)) - (replace-match "" t t))) - (unless (bolp) (insert "\n")) - (insert ")\n")))) - (message "Couldn't find package desc for %s" (car spec))))) +them,and remove unnecessary `provide' statements or blank links." + (dolist (pkg (straight--directory-files (straight--build-dir))) + (let ((file (straight--autoloads-file pkg))) + (when (file-exists-p file) + (insert-file-contents file) + (when (save-excursion + (and (re-search-forward "\\_" nil t) + (not (nth 8 (syntax-ppss))))) + ;; Set `load-file-name' so that the contents of autoloads + ;; files can pretend they're in the file they're expected to + ;; be in, rather than `doom-package-autoload-file'. + (insert (format "(setq load-file-name %S)\n" (abbreviate-file-name file)))) + (while (re-search-forward "^\\(?:;;\\(.*\n\\)\\|\n\\|(provide '[^\n]+\\)" nil t) + (unless (nth 8 (syntax-ppss)) + (replace-match "" t t))) + (unless (bolp) (insert "\n")))))) (defun doom--generate-var-cache () "Print a `setq' form for expensive-to-initialize variables, so we can cache them in Doom's autoloads file." (doom-initialize-packages) (prin1 `(setq load-path ',load-path - auto-mode-alist ',auto-mode-alist + auto-mode-alist + ',(let ((alist (copy-sequence auto-mode-alist)) + newalist + last-group + last-mode + it) + (while (setq it (pop alist)) + (cl-destructuring-bind (re . mode) it + (unless (eq mode last-mode) + (when last-mode + (push (cons (if (cdr last-group) + (concat "\\(?:" (string-join last-group "\\)\\|\\(?:") "\\)") + (car last-group)) + last-mode) + newalist)) + (setq last-mode mode + last-group nil)) + (push re last-group))) + (nreverse newalist)) Info-directory-list ',Info-directory-list - doom-disabled-packages ',(mapcar #'car (doom-find-packages :disabled t)) - package-activated-list ',package-activated-list) + doom-disabled-packages ',doom-disabled-packages) (current-buffer))) (defun doom--cleanup-package-autoloads () @@ -351,34 +369,63 @@ Will do nothing if none of your installed packages have been modified. If FORCE-P (universal argument) is non-nil, regenerate it anyway. This should be run whenever your `doom!' block or update your packages." - (let ((abbreviated-home-dir (if IS-WINDOWS "\\`'" abbreviated-home-dir))) - (if (and (not force-p) - (not doom-emacs-changed-p) - (file-exists-p doom-package-autoload-file) - (not (file-newer-than-file-p doom-packages-dir doom-package-autoload-file)) - (not (ignore-errors - (cl-loop for key being the hash-keys of (doom-modules) - for path = (doom-module-path (car key) (cdr key) "packages.el") - if (file-newer-than-file-p path doom-package-autoload-file) - return t)))) - (ignore (print! (green "Doom package autoloads is up-to-date")) - (doom-initialize-autoloads doom-package-autoload-file)) - (let (case-fold-search) - (doom-delete-autoloads-file doom-package-autoload-file) - (with-temp-file doom-package-autoload-file - (doom--generate-header 'doom-reload-package-autoloads) - (save-excursion - ;; Cache important and expensive-to-initialize state here. - (doom--generate-var-cache) - (print! (green "✓ Cached package state")) - ;; Concatenate the autoloads of all installed packages. - (doom--generate-package-autoloads) - (print! (green "✓ Package autoloads included"))) - ;; Remove `load-path' and `auto-mode-alist' modifications (most of them, - ;; at least); they are cached later, so all those membership checks are - ;; unnecessary overhead. - (doom--cleanup-package-autoloads) - (print! (green "✓ Removed load-path/auto-mode-alist entries")))) - (doom--byte-compile-file doom-package-autoload-file) - (doom--reload-files doom-package-autoload-file) - t))) + (print! (start "Checking package autoloads file")) + (print-group! + (if (and (not force-p) + (file-exists-p doom-package-autoload-file) + (not (file-newer-than-file-p doom-elpa-dir doom-package-autoload-file)) + (not (ignore-errors + (cl-loop for dir in (straight--directory-files (straight--repos-dir)) + if (cl-find-if (lambda (dir) (file-newer-than-file-p dir doom-package-autoload-file)) + (glob! (straight--repos-dir dir) "*.el")) + return t))) + (not (ignore-errors + (cl-loop for key being the hash-keys of (doom-modules) + for path = (doom-module-path (car key) (cdr key) "packages.el") + if (file-newer-than-file-p path doom-package-autoload-file) + return t)))) + (ignore + (print! (success "Skipping package autoloads, they are up-to-date")) + (doom-initialize-autoloads doom-package-autoload-file)) + (let (;; The following bindings are in `package-generate-autoloads'. + ;; Presumably for a good reason, so I just copied them + (noninteractive t) + (backup-inhibited t) + (version-control 'never) + (case-fold-search nil) ; reduce magit + (autoload-timestamps nil)) + (print! (start "Regenerating package autoloads file")) + + (if (doom-delete-autoloads-file doom-package-autoload-file) + (print! (success "Deleted old %s") (filename doom-package-autoload-file)) + (make-directory (file-name-directory doom-autoload-file) t)) + + (with-temp-file doom-package-autoload-file + (doom--generate-header 'doom-reload-package-autoloads) + + (save-excursion + ;; Cache important and expensive-to-initialize state here. + (doom--generate-var-cache) + (print! (success "Cached package state")) + ;; Concatenate the autoloads of all installed packages. + (doom--generate-package-autoloads) + (print! (success "Package autoloads included"))) + + ;; Replace autoload paths (only for module autoloads) with absolute + ;; paths for faster resolution during load and simpler `load-path' + (save-excursion + (doom--expand-autoload-paths) + (print! (success "Expanded module autoload paths"))) + + ;; Remove `load-path' and `auto-mode-alist' modifications (most of them, + ;; at least); they are cached later, so all those membership checks are + ;; unnecessary overhead. + (doom--cleanup-package-autoloads) + (print! (success "Removed load-path/auto-mode-alist entries"))) + ;; Byte compile it to give the file a chance to reveal errors (and buy us a + ;; few marginal performance boosts) + (print! (start "Byte-compiling %s...") (relpath doom-package-autoload-file)) + (when (doom--byte-compile-file doom-package-autoload-file) + (print! (success "Finished compiling %s") (relpath doom-package-autoload-file))) + (doom--reload-files doom-package-autoload-file))) + t)) diff --git a/core/cli/byte-compile.el b/core/cli/byte-compile.el index 4d7533011..9de5cfa4a 100644 --- a/core/cli/byte-compile.el +++ b/core/cli/byte-compile.el @@ -1,21 +1,24 @@ ;;; core/cli/byte-compile.el -*- lexical-binding: t; -*- -(dispatcher! (compile c) (doom-byte-compile args) +(def-command! (compile c) (&rest targets) "Byte-compiles your config or selected modules. compile [TARGETS...] compile :core :private lang/python compile feature lang -Accepts :core, :private and :plugins as special arguments, indicating you want -to byte-compile Doom's core files, your private config or your ELPA plugins, -respectively.") +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 rebuild' instead." + (doom-byte-compile targets)) -(dispatcher! (recompile rc) (doom-byte-compile args 'recompile) - "Re-byte-compiles outdated *.elc files.") +(def-command! (recompile rc) (&rest targets) + "Re-byte-compiles outdated *.elc files." + (doom-byte-compile targets 'recompile)) -(dispatcher! clean (doom-clean-byte-compiled-files) - "Delete all *.elc files.") +(def-command! clean () + "Delete all *.elc files." + (doom-clean-byte-compiled-files)) ;; @@ -25,9 +28,10 @@ respectively.") (let ((filename (file-name-nondirectory path))) (or (string-prefix-p "." filename) (string-prefix-p "test-" filename) - (not (equal (file-name-extension path) "el"))))) + (not (equal (file-name-extension path) "el")) + (member filename (list "packages.el" "doctor.el"))))) -(defun doom-byte-compile (&optional modules recompile-p) +(cl-defun doom-byte-compile (&optional modules recompile-p) "Byte compiles your emacs configuration. init.el is always byte-compiled by this. @@ -45,35 +49,35 @@ byte-compilation. If RECOMPILE-P is non-nil, only recompile out-of-date files." (let ((default-directory doom-emacs-dir) - (total-ok 0) - (total-fail 0) - (total-noop 0) - compile-plugins-p + (doom-modules (doom-modules)) + (byte-compile-verbose doom-debug-mode) + (byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local)) + + ;; In case it is changed during compile-time + (auto-mode-alist auto-mode-alist) + (noninteractive t) + targets) - (dolist (module (delete-dups modules) (nreverse targets)) - (pcase module - (":core" (push doom-core-dir targets)) - (":private" (push doom-private-dir targets)) - (":plugins" - (cl-loop for (_name . desc) in (doom-get-package-alist) - do (package--compile desc)) - (setq compile-plugins-p t - modules (delete ":plugins" modules))) - ((pred file-directory-p) - (push module targets)) - ((pred (string-match "^\\([^/]+\\)/\\([^/]+\\)$")) - (push (doom-module-locate-path - (doom-keyword-intern (match-string 1 module)) - (intern (match-string 2 module))) - targets)))) - (cl-block 'byte-compile - ;; If we're just here to byte-compile our plugins, we're done! - (and (not modules) - compile-plugins-p - (cl-return-from 'byte-compile t)) - (unless (or (equal modules '(":core")) - recompile-p) - (unless (or doom-auto-accept + + (let (target-dirs) + (dolist (module (delete-dups modules)) + (pcase module + (":core" + (push (doom-glob doom-emacs-dir "init.el") targets) + (push doom-core-dir target-dirs)) + (":private" + (push doom-private-dir target-dirs)) + ((pred file-directory-p) + (push module target-dirs)) + ((pred (string-match "^\\([^/]+\\)/\\([^/]+\\)$")) + (push (doom-module-locate-path + (doom-keyword-intern (match-string 1 module)) + (intern (match-string 2 module))) + target-dirs)))) + + (and (or (null modules) (member ":private" modules)) + (not recompile-p) + (not (or doom-auto-accept (y-or-n-p (concat "Warning: byte compiling is for advanced users. It will interfere with your\n" "efforts to debug issues. It is not recommended you do it if you frequently\n" @@ -82,109 +86,113 @@ If RECOMPILE-P is non-nil, only recompile out-of-date files." "Doom core files, as these don't change often.\n\n" "If you have issues, please make sure byte-compilation isn't the cause by using\n" "`bin/doom clean` to clear out your *.elc files.\n\n" - "Byte-compile anyway?"))) - (message "Aborting.") - (cl-return-from 'byte-compile))) - (when (and (not recompile-p) - (or (null modules) - (equal modules '(":core")))) - (doom-clean-byte-compiled-files)) - (let (doom-emacs-changed-p - noninteractive) - ;; 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. - (unless (and (doom-initialize-autoloads doom-autoload-file) - (doom-initialize-autoloads doom-package-autoload-file)) - (doom-reload-autoloads)) - (doom-initialize) - (doom-initialize-modules 'force)) - ;; If no targets were supplied, then we use your module list. - (unless modules - (let ((doom-modules-dirs (delete (expand-file-name "modules/" doom-private-dir) - doom-modules-dirs))) - (setq targets - (append (list doom-core-dir) - (delete doom-private-dir (doom-module-load-path)))))) - ;; Assemble el files we want to compile; taking into account that - ;; MODULES may be a list of MODULE/SUBMODULE strings from the command - ;; line. - (let ((target-files (doom-files-in targets :filter #'doom--byte-compile-ignore-file-p :sort nil))) - (when (or (not modules) - (member ":core" modules)) - (push (expand-file-name "init.el" doom-emacs-dir) - target-files)) - (unless target-files - (if targets - (message "Couldn't find any valid targets") - (message "No targets to %scompile" (if recompile-p "re" ""))) - (cl-return-from 'byte-compile)) - (require 'use-package) - (condition-case e - (let ((use-package-defaults use-package-defaults) - (use-package-expand-minimally t) - (load-path load-path) - kill-emacs-hook kill-buffer-query-functions) - ;; 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 (target (cl-delete-duplicates (mapcar #'file-truename target-files) :test #'equal)) - (if (or (not recompile-p) - (let ((elc-file (byte-compile-dest-file target))) - (and (file-exists-p elc-file) - (file-newer-than-file-p target elc-file)))) - (let ((result (if (or (string-match-p "/\\(?:packages\\|doctor\\)\\.el$" target) - (not (doom--file-cookie-p target))) - 'no-byte-compile - (byte-compile-file target))) - (short-name (if (file-in-directory-p target doom-emacs-dir) - (file-relative-name target doom-emacs-dir) - (abbreviate-file-name target)))) - (cl-incf - (cond ((eq result 'no-byte-compile) - (print! (dark (white "⚠ Ignored %s")) short-name) - total-noop) - ((null result) - (print! (red "✕ Failed to compile %s") short-name) - total-fail) - (t - (print! (green "✓ Compiled %s") short-name) - (load target t t) - total-ok)))) - (cl-incf total-noop))) - (print! (bold (color (if (= total-fail 0) 'green 'red) - "%s %d/%d file(s) (%d ignored)")) - (if recompile-p "Recompiled" "Compiled") - total-ok (- (length target-files) total-noop) - total-noop) - (or (= total-fail 0) - (error "Failed to compile some files"))) - ((debug error) - (print! (red "\nThere were breaking errors.\n\n%s") - "Reverting changes...") - (signal 'doom-error (list 'byte-compile e)))))))) + "Byte-compile anyway?")))) + (user-error "Aborting")) + + ;; 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. + (doom-initialize-autoloads doom-autoload-file) + (doom-initialize-autoloads doom-package-autoload-file) + + ;; + (unless target-dirs + (push (doom-glob doom-emacs-dir "init.el") targets) + ;; If no targets were supplied, then we use your module list. + (appendq! target-dirs + (list doom-core-dir) + ;; Omit `doom-private-dir', which is always first + (cl-remove-if-not (lambda (path) (file-in-directory-p path doom-emacs-dir)) + (cdr (doom-module-load-path))))) + + ;; Assemble el files we want to compile; taking into account that MODULES + ;; may be a list of MODULE/SUBMODULE strings from the command line. + (appendq! targets + (doom-files-in target-dirs + :match "\\.el$" + :filter #'doom--byte-compile-ignore-file-p))) + + (unless targets + (print! + (if targets + (warn "Couldn't find any valid targets") + (info "No targets to %scompile" (if recompile-p "re" "")))) + (cl-return nil)) + + (print! + (info (if recompile-p + "Recompiling stale elc files..." + "Byte-compiling your config (may take a while)..."))) + (print-group! + (require 'use-package) + (condition-case e + (let ((total-ok 0) + (total-fail 0) + (total-noop 0) + (use-package-defaults use-package-defaults) + (use-package-expand-minimally t) + kill-emacs-hook kill-buffer-query-functions) + ;; 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) + + (unless recompile-p + (doom-clean-byte-compiled-files)) + + (dolist (target (delete-dups targets)) + (cl-incf + (if (not (or (not recompile-p) + (let ((elc-file (byte-compile-dest-file target))) + (and (file-exists-p elc-file) + (file-newer-than-file-p target elc-file))))) + total-noop + (pcase (if (doom-file-cookie-p target) + (byte-compile-file target) + 'no-byte-compile) + (`no-byte-compile + (print! (info "Ignored %s") (relpath target)) + total-noop) + (`nil + (print! (error "Failed to compile %s") (relpath target)) + total-fail) + (_ + (print! (success "Compiled %s") (relpath target)) + (load target t t) + total-ok))))) + (print! (class (if (= total-fail 0) 'success 'error) + "%s %d/%d file(s) (%d ignored)") + (if recompile-p "Recompiled" "Compiled") + total-ok (- (length targets) total-noop) + total-noop) + t) + ((debug error) + (print! (error "\nThere were breaking errors.\n\n%s") + "Reverting changes...") + (signal 'doom-error (list 'byte-compile e))))))) (defun doom-clean-byte-compiled-files () "Delete all the compiled elc files in your Emacs configuration and private module. This does not include your byte-compiled, third party packages.'" - (cl-loop with default-directory = doom-emacs-dir - for path - in (append (doom-files-in doom-emacs-dir :match "\\.elc$" :depth 0 :sort nil) - (doom-files-in doom-private-dir :match "\\.elc$" :depth 1 :sort nil) - (doom-files-in doom-core-dir :match "\\.elc$" :sort nil) - (doom-files-in doom-modules-dirs :match "\\.elc$" :depth 4 :sort nil)) - for truepath = (file-truename path) - if (file-exists-p path) - do (delete-file path) - and do - (print! (green "✓ Deleted %s") - (if (file-in-directory-p truepath default-directory) - (file-relative-name truepath) - (abbreviate-file-name truepath))) - finally do (print! (bold (green "Everything is clean"))))) + (print! (start "Cleaning .elc files")) + (print-group! + (cl-loop with default-directory = doom-emacs-dir + with success = nil + for path + in (append (doom-glob doom-emacs-dir "*.elc") + (doom-files-in doom-private-dir :match "\\.elc$" :depth 1 :sort nil) + (doom-files-in doom-core-dir :match "\\.elc$" :sort nil) + (doom-files-in doom-modules-dirs :match "\\.elc$" :depth 4 :sort nil)) + if (file-exists-p path) + do (delete-file path) + and do (print! (success "Deleted %s") (relpath path)) + and do (setq success t) + finally do + (print! (if success + (success "All elc files deleted") + (info "No elc files to clean")))))) diff --git a/core/cli/debug.el b/core/cli/debug.el index a66a52507..c8c7b4648 100644 --- a/core/cli/debug.el +++ b/core/cli/debug.el @@ -1,7 +1,26 @@ ;;; core/cli/debug.el -*- lexical-binding: t; -*- -(dispatcher! info (doom/info) - "Output system info in markdown for bug reports.") +(load! "autoload/debug" doom-core-dir) -(dispatcher! (version v) (doom/version) - "Reports the version of Doom and Emacs.") + +;; +;;; Commands + +(def-command! info (&optional format) + "Output system info in markdown for bug reports." + (pcase format + ("json" + (require 'json) + (with-temp-buffer + (insert (json-encode (doom-info))) + (json-pretty-print-buffer) + (print! (buffer-string)))) + ((or "md" "markdown") + (doom/info)) + (_ (doom/info 'raw))) + nil) + +(def-command! (version v) () + "Reports the version of Doom and Emacs." + (doom/version) + nil) diff --git a/core/cli/env.el b/core/cli/env.el index 185ff7e1c..f8a0cf85a 100644 --- a/core/cli/env.el +++ b/core/cli/env.el @@ -1,25 +1,6 @@ ;;; core/cli/env.el -*- lexical-binding: t; -*- -(dispatcher! env - (let ((env-file (abbreviate-file-name doom-env-file))) - (pcase (car args) - ((or "refresh" "re") - (doom-reload-env-file 'force)) - ((or "enable" "auto") - (setenv "DOOMENV" "1") - (print! (green "Enabling auto-reload of %S") env-file) - (doom-reload-env-file 'force) - (print! (green "Done! `doom refresh' will now refresh your envvar file."))) - ("clear" - (setenv "DOOMENV" nil) - (unless (file-exists-p env-file) - (user-error "%S does not exist to be cleared" env-file)) - (delete-file env-file) - (print! (green "Disabled envvar file by deleting %S") env-file)) - (_ - (print! "%s\n\n%s" - (bold (red "No valid subcommand provided.")) - "See `doom help env` to see available commands.")))) +(def-command! env (&optional command) "Manages your envvars file. env [SUBCOMMAND] @@ -38,7 +19,18 @@ on Linux). To generate a file, run `doom env refresh`. If you'd like this file to be auto-reloaded when running `doom refresh`, run `doom env enable` instead (only -needs to be run once).") +needs to be run once)." + (let ((default-directory doom-emacs-dir)) + (pcase command + ("clear" + (unless (file-exists-p doom-env-file) + (user-error! "%S does not exist to be cleared" + (relpath doom-env-file))) + (delete-file doom-env-file) + (print! (success "Successfully deleted %S") + (relpath doom-env-file))) + (_ + (doom-reload-env-file 'force))))) ;; @@ -86,12 +78,12 @@ default, on Linux, this is '$SHELL -ic /usr/bin/env'. Variables in `doom-env-ignored-vars' are removed." (when (or force-p (not (file-exists-p doom-env-file))) (with-temp-file doom-env-file - (message "%s envvars file at %S" + (print! (start "%s envvars file at %S") (if (file-exists-p doom-env-file) "Regenerating" "Generating") - (abbreviate-file-name doom-env-file)) - (let ((process-environment doom-site-process-environment)) + (relpath doom-env-file doom-emacs-dir)) + (let ((process-environment doom--initial-process-environment)) (insert (concat "# -*- mode: dotenv -*-\n" @@ -111,25 +103,31 @@ default, on Linux, this is '$SHELL -ic /usr/bin/env'. Variables in "# To auto-regenerate this file when `doom reload` is run, use `doom env auto' or\n" "# set DOOMENV=1 in your shell environment/config.\n" "# ---------------------------------------------------------------------------\n\n")) - (let ((shell-command-switch doom-env-switches)) - (message "Scraping env from '%s %s %s'" - shell-file-name - shell-command-switch - doom-env-executable) + (let ((shell-command-switch doom-env-switches) + (error-buffer (get-buffer-create "*env errors*"))) + (print! (info "Scraping shell environment with '%s %s %s'") + (filename shell-file-name) + shell-command-switch + (filename doom-env-executable)) (save-excursion - (insert (shell-command-to-string doom-env-executable))) - ;; Remove undesireable variables - (while (re-search-forward "\n\\([^= \n]+\\)=" nil t) - (save-excursion - (let* ((valend (or (save-match-data - (when (re-search-forward "^\\([^= ]+\\)=" nil t) - (line-beginning-position))) - (point-max))) - (var (match-string 1)) - (value (buffer-substring-no-properties (point) (1- valend)))) - (when (cl-loop for regexp in doom-env-ignored-vars - if (string-match-p regexp var) - return t) - (message "Ignoring %s" var) - (delete-region (match-beginning 0) (1- valend)))))) - (print! (green "Envvar successfully generated"))))))) + (shell-command doom-env-executable (current-buffer) error-buffer)) + (print-group! + (let ((errors (with-current-buffer error-buffer (buffer-string)))) + (unless (string-empty-p errors) + (print! (info "Error output:\n\n%s") (indent 4 errors)))) + ;; Remove undesireable variables + (while (re-search-forward "\n\\([^= \n]+\\)=" nil t) + (save-excursion + (let* ((valend (or (save-match-data + (when (re-search-forward "^\\([^= ]+\\)=" nil t) + (line-beginning-position))) + (point-max))) + (var (match-string 1))) + (when (cl-loop for regexp in doom-env-ignored-vars + if (string-match-p regexp var) + return t) + (print! (info "Ignoring %s") var) + (delete-region (match-beginning 0) (1- valend))))))) + (print! (success "Successfully generated %S") + (relpath doom-env-file doom-emacs-dir)) + t))))) diff --git a/core/cli/packages.el b/core/cli/packages.el index 661186a50..78c635eb0 100644 --- a/core/cli/packages.el +++ b/core/cli/packages.el @@ -1,55 +1,47 @@ ;; -*- no-byte-compile: t; -*- ;;; core/cli/packages.el -;; -;;; Helpers - -(defmacro doom--condition-case! (&rest body) - `(condition-case-unless-debug e - (progn ,@body) - ('user-error - (print! (bold (red " NOTICE: %s")) e)) - ('file-error - (print! " %s\n %s" - (bold (red "FILE ERROR: %s" (error-message-string e))) - "Trying again...") - (quiet! (doom-refresh-packages-maybe t)) - ,@body) - ('error - (print! (bold (red " %s %s\n %s")) - "FATAL ERROR: " e - "Run again with the -d flag for details")))) - -(defsubst doom--ensure-autoloads-while (fn) - (doom-reload-doom-autoloads) - (when (funcall fn doom-auto-accept) - (doom-reload-package-autoloads))) +(defmacro doom--ensure-autoloads-while (&rest body) + `(progn + (doom-reload-core-autoloads) + (when (progn ,@body) + (doom-reload-package-autoloads 'force-p)) + t)) ;; ;;; Dispatchers -(dispatcher! (install i) - (doom--ensure-autoloads-while #'doom-packages-install) - "Installs wanted packages that aren't installed. - -Package management in Doom is declarative. A `package!' declaration in an -enabled module or your private packages.el marks a package as 'wanted'.") - -(dispatcher! (update u) - (doom--ensure-autoloads-while #'doom-packages-update) +(def-command! (update u) () "Updates packages. This excludes packages whose `package!' declaration contains a non-nil :freeze -or :ignore property.") +or :ignore property." + (doom--ensure-autoloads-while + (straight-check-all) + (when (doom-packages-update doom-auto-accept) + (doom-packages-rebuild doom-auto-accept) + t))) -(dispatcher! (autoremove r) - (doom--ensure-autoloads-while #'doom-packages-autoremove) - "Removes packages that are no longer needed. +(def-command! (rebuild b) () + "Rebuilds all installed packages. -This includes packages installed with 'M-x package-install' without an -accompanying `package!' declaration in an enabled module's packages.el file or -your private one.") +This ensures that all needed files are symlinked from their package repo and +their elisp files are byte-compiled." + (doom--ensure-autoloads-while + (doom-packages-rebuild doom-auto-accept (member "all" args)))) + +(def-command! (purge p) () + "Deletes any unused packages and package repos. + +You should run this once in a while, as repos tend to build up over time." + (doom--ensure-autoloads-while + (straight-check-all) + (doom-packages-purge doom-auto-accept))) + +;; (def-command! rollback () ; TODO rollback +;; "" +;; (user-error "Not implemented yet, sorry!")) ;; @@ -63,153 +55,212 @@ declaration) or dependency thereof that hasn't already been. Unless AUTO-ACCEPT-P is non-nil, this function will prompt for confirmation with a list of packages that will be installed." - (print! "Looking for packages to install...") - (let ((packages (doom-get-missing-packages))) - (cond ((not packages) - (print! (green "No packages to install!")) - nil) + (print! "> Installing & building packages...") + (print-group! + (let ((n 0)) + (dolist (package (hash-table-keys straight--recipe-cache)) + (straight--with-plist (gethash package straight--recipe-cache) + (local-repo) + (let ((existed-p (file-directory-p (straight--repos-dir package)))) + (condition-case-unless-debug e + (and (straight-use-package (intern package) nil nil " ") + (not existed-p) + (file-directory-p (straight--repos-dir package)) + (cl-incf n)) + (error + (signal 'doom-package-error + (list e (straight--process-get-output)))))))) + (if (= n 0) + (ignore (print! (success "No packages need to be installed"))) + (print! (success "Installed & built %d packages") n) + t)))) - ((not (or auto-accept-p - (y-or-n-p - (format "%s packages will be installed:\n\n%s\n\nProceed?" - (length packages) - (mapconcat - (lambda (pkg) - (format "+ %s (%s)" - (car pkg) - (cond ((doom-package-different-recipe-p (car pkg)) - "new recipe") - ((doom-package-different-backend-p (car pkg)) - (format "%s -> %s" - (doom-package-backend (car pkg) 'noerror) - (doom-package-recipe-backend (car pkg) 'noerror))) - ((plist-get (cdr pkg) :recipe) - "quelpa") - ("elpa")))) - (cl-sort (cl-copy-list packages) #'string-lessp - :key #'car) - "\n"))))) - (user-error "Aborted!")) - ((let (success) - (doom-refresh-packages-maybe doom-debug-mode) - (dolist (pkg packages) - (print! "Installing %s" (car pkg)) - (doom--condition-case! - (let ((result - (or (and (doom-package-installed-p (car pkg)) - (not (doom-package-different-backend-p (car pkg))) - (not (doom-package-different-recipe-p (car pkg))) - 'already-installed) - (and (doom-install-package (car pkg) (cdr pkg)) - (setq success t) - 'success) - 'failure)) - (pin-label - (and (plist-member (cdr pkg) :pin) - (format " [pinned: %s]" (plist-get (cdr pkg) :pin))))) - (print! "%s%s" - (pcase result - (`already-installed (dark (white "⚠ ALREADY INSTALLED"))) - (`success (green "✓ DONE")) - (`failure (red "✕ FAILED"))) - (or pin-label ""))))) - (print! (bold (green "Finished!"))) - (when success - (set-file-times doom-packages-dir) - (doom-delete-autoloads-file doom-package-autoload-file)) - success))))) +(defun doom-packages-rebuild (&optional auto-accept-p all) + "(Re)build all packages." + (print! (start "(Re)building %spackages...") (if all "all " "")) + (print-group! + (let ((n 0)) + (if all + (let ((straight--packages-to-rebuild :all) + (straight--packages-not-to-rebuild (make-hash-table :test #'equal))) + (dolist (package (hash-table-keys straight--recipe-cache)) + (straight-use-package + (intern package) nil (lambda (_) (cl-incf n) nil) " "))) + (let ((straight-check-for-modifications '(find-when-checking))) + (straight-check-all) + (dolist (recipe (hash-table-values straight--recipe-cache)) + (straight--with-plist recipe (package local-repo no-build) + (unless (or no-build (null local-repo)) + ;; REVIEW We do these modification checks manually because + ;; Straight's checks seem to miss stale elc files. Need + ;; more tests to confirm this. + (when (or (gethash package straight--cached-package-modifications) + (file-newer-than-file-p (straight--repos-dir local-repo) + (straight--build-dir package)) + (cl-loop for file + in (doom-files-in (straight--build-dir package) + :match "\\.el$" + :full t) + for elc-file = (byte-compile-dest-file file) + if (and (file-exists-p elc-file) + (file-newer-than-file-p file elc-file)) + return t)) + (print! (info "Rebuilding %s") package) + ;; REVIEW `straight-rebuild-package' alone wasn't enough. Why? + (delete-directory (straight--build-dir package) 'recursive) + (straight-rebuild-package package) + (cl-incf n))))))) + (if (= n 0) + (ignore (print! (success "No packages need rebuilding"))) + (print! (success "Rebuilt %d package(s)" n)) + t)))) + (defun doom-packages-update (&optional auto-accept-p) "Updates packages. Unless AUTO-ACCEPT-P is non-nil, this function will prompt for confirmation with a list of packages that will be updated." - (print! "Looking for outdated packages...") - (let ((packages (cl-sort (cl-copy-list (doom-get-outdated-packages)) #'string-lessp - :key #'car))) - (cond ((not packages) - (print! (green "Everything is up-to-date")) - nil) + (print! (start "Scanning for outdated packages (this may take a while)...")) + (print-group! + ;; REVIEW Does this fail gracefully enough? Is it error tolerant? + ;; TODO Add version-lock checks; don't want to spend all this effort on + ;; packages that shouldn't be updated + (condition-case e + (let (futures) + (dolist (group (seq-partition (hash-table-values straight--repo-cache) 8)) + (push (async-start + `(lambda () + (setq load-path ',load-path + doom-modules ',doom-modules) + (load ,(concat doom-core-dir "core.el")) + (let (packages) + (when (require 'straight nil t) + (dolist (recipe ',group) + (straight--with-plist recipe (package local-repo) + (when (and local-repo (straight--repository-is-available-p recipe)) + (straight-fetch-package package) + ;; REVIEW Isn't there a better way to get this information? Maybe with `vc'? + (let* ((default-directory (straight--repos-dir local-repo)) + (n (string-to-number + (shell-command-to-string "git rev-list --right-only --count HEAD..@'{u}'"))) + (pretime + (string-to-number + (shell-command-to-string "git log -1 --format=%at HEAD"))) + (time + (string-to-number + (shell-command-to-string "git log -1 --format=%at FETCH_HEAD")))) + (when (> n 0) + (push (list n pretime time recipe) + packages))))))) + (nreverse packages)))) + futures)) + (let ((total (length futures)) + (futures (nreverse futures)) + (specs '(t))) + (while futures + (while (not (async-ready (car futures))) + (sleep-for 2) + (print! ".")) + (nconc specs (async-get (pop futures)))) + (terpri) + (if-let (specs (delq nil (cdr specs))) + (if (or auto-accept-p + (y-or-n-p + (format! "%s\n\nThere %s %d package%s available to update. Update them?" + (mapconcat + (lambda (spec) + (cl-destructuring-bind (n pretime time recipe) spec + (straight--with-plist recipe (package) + (format! "+ %-33s %s commit(s) behind %s -> %s" + (yellow package) (yellow n) + (format-time-string "%Y%m%d" pretime) + (format-time-string "%Y%m%d" time))))) + specs + "\n") + (if (cdr specs) "are" "is") + (length specs) + (if (cdr specs) "s" "")))) + (terpri) + (dolist (spec specs t) + (cl-destructuring-bind (n pretime time recipe) spec + (straight--with-plist recipe (local-repo package) + (let ((default-directory (straight--repos-dir local-repo))) + (print! (start "Updating %S") package) + ;; HACK `straight' doesn't assume it would ever be used + ;; non-interactively, but here we are. If the repo is + ;; dirty, the command will lock up, waiting for + ;; interaction that will never come, so discard all local + ;; changes. Doom doesn't want you modifying those anyway. + (and (straight--get-call "git" "reset" "--hard") + (straight--get-call "git" "clean" "-ffd")) + (straight-merge-package package) + ;; HACK `straight-rebuild-package' doesn't pick up that + ;; this package has changed, so we do it manually. Is + ;; there a better way? + (run-hook-with-args 'straight-use-package-pre-build-functions package) + (straight--build-package recipe " ")) + (with-current-buffer (straight--process-get-buffer) + (with-silent-modifications + (erase-buffer)))))) + (print! (info "Aborted update")) + nil) + (print! (success "No packages to update")) + nil))) + (error + (message "Output:\n%s" (straight--process-get-output)) + (signal (car e) (error-message-string e)))))) - ((not (or auto-accept-p - (y-or-n-p - (format "%s packages will be updated:\n\n%s\n\nProceed?" - (length packages) - (let ((max-len - (or (car (sort (mapcar (lambda (it) (length (symbol-name (car it)))) packages) - #'>)) - 10))) - (mapconcat - (lambda (pkg) - (format (format "+ %%-%ds (%%s) %%-%ds -> %%s" - (+ max-len 2) 14) - (symbol-name (car pkg)) - (doom-package-backend (car pkg)) - (package-version-join (cadr pkg)) - (package-version-join (cl-caddr pkg)))) - packages - "\n")))))) - (user-error "Aborted!")) - ((let (success) - (dolist (pkg packages) - (print! "Updating %s" (car pkg)) - (doom--condition-case! - (print! - (let ((result (doom-update-package (car pkg) t))) - (when result (setq success t)) - (color (if result 'green 'red) - (if result "✓ DONE" "✕ FAILED")))))) - (print! (bold (green "Finished!"))) - (when success - (set-file-times doom-packages-dir) - (doom-delete-autoloads-file doom-package-autoload-file)) - success))))) +(defun doom--packages-to-purge () + (let (builds repos) + (dolist (name (straight--directory-files (straight--repos-dir))) + (unless (straight--checkhash name straight--repo-cache) + (push name repos))) + (dolist (name (straight--directory-files (straight--build-dir))) + (unless (gethash name straight--profile-cache) + (push name builds))) + (straight-prune-build-cache) + (list builds repos))) -(defun doom-packages-autoremove (&optional auto-accept-p) - "Auto-removes orphaned packages. +(defun doom-packages-purge (&optional auto-accept-p) + "Auto-removes orphaned packages and repos. An orphaned package is a package that isn't a primary package (i.e. doesn't have a `package!' declaration) or isn't depended on by another primary package. Unless AUTO-ACCEPT-P is non-nil, this function will prompt for confirmation with a list of packages that will be removed." - (print! "Looking for orphaned packages...") - (let ((packages (doom-get-orphaned-packages))) - (cond ((not packages) - (print! (green "No unused packages to remove")) - nil) - - ((not - (or auto-accept-p - (y-or-n-p - (format "%s packages will be deleted:\n\n%s\n\nProceed?" - (length packages) - (mapconcat - (lambda (sym) - (let ((old-backend (doom-package-backend sym 'noerror)) - (new-backend (doom-package-recipe-backend sym 'noerror))) - (format "+ %s (%s)" sym - (cond ((null new-backend) - "removed") - ((eq old-backend new-backend) - (symbol-name new-backend)) - ((format "%s -> %s" old-backend new-backend)))))) - (sort (cl-copy-list packages) #'string-lessp) - "\n"))))) - (user-error "Aborted!")) - - ((let (success) - (dolist (pkg packages) - (doom--condition-case! - (let ((result (doom-delete-package pkg t))) - (if result (setq success t)) - (print! (color (if result 'green 'red) "%s %s") - (if result "✓ Removed" "✕ Failed to remove") - pkg)))) - (print! (bold (green "Finished!"))) - (when success - (set-file-times doom-packages-dir) - (doom-delete-autoloads-file doom-package-autoload-file)) - success))))) + (print! (start "Searching for orphaned packages...")) + (cl-destructuring-bind (builds repos) (doom--packages-to-purge) + (unless (bound-and-true-p package--initialized) + (package-initialize)) + (print-group! + (let ((packages (append builds (mapcar #'car package-alist) nil))) + (if (not packages) + (ignore (print! (success "No orphaned packages to purge"))) + (or auto-accept-p + (y-or-n-p + (format! "\n%s\n\n%d packages are orphaned. Purge them (for the Emperor)?" + (mapconcat (lambda (pkgs) + (mapconcat (lambda (p) (format " + %-20.20s" p)) + pkgs + "")) + (seq-partition (cl-sort (copy-sequence packages) #'string-lessp) + 3) + "\n") + (length packages))) + (user-error "Aborted")) + (let ((n 0)) + (dolist (dir (append (mapcar #'straight--repos-dir repos) + (mapcar #'straight--build-dir builds))) + (print! (info "Deleting %S") (relpath dir (straight--dir))) + (delete-directory dir 'recursive) + (unless (file-directory-p dir) + (cl-incf n))) + (straight-prune-build-cache) + (when (file-directory-p package-user-dir) + (delete-directory package-user-dir t) + t) + (> n 0))))))) diff --git a/core/cli/patch-macos.el b/core/cli/patch-macos.el index 8e8bdfb3d..99dff3f75 100644 --- a/core/cli/patch-macos.el +++ b/core/cli/patch-macos.el @@ -1,9 +1,6 @@ ;;; core/cli/patch-macos.el -*- lexical-binding: t; -*- -(dispatcher! (patch-macos) - (doom-patch-macos (or (member "--undo" args) - (member "-u" args)) - (doom--find-emacsapp-path)) +(def-command! patch-macos () "Patches Emacs.app to respect your shell environment. WARNING: This command is deprecated. Use 'doom env' instead. @@ -30,7 +27,11 @@ It can be undone with the --undo or -u options. Alternatively, you can install the exec-path-from-shell Emacs plugin, which will scrape your shell environment remotely, at startup. However, this can be slow -depending on your shell configuration and isn't always reliable.") +depending on your shell configuration and isn't always reliable." + :hidden t + (doom-patch-macos (or (member "--undo" args) + (member "-u" args)) + (doom--find-emacsapp-path))) ;; diff --git a/core/cli/quickstart.el b/core/cli/quickstart.el index 1e061653a..740371644 100644 --- a/core/cli/quickstart.el +++ b/core/cli/quickstart.el @@ -1,7 +1,12 @@ ;;; core/cli/quickstart.el -*- lexical-binding: t; -*- -(dispatcher! (quickstart qs) (apply #'doom-quickstart args) - "Guides you through setting up Doom for first time use. +(def-command! quickstart (&rest args) ; DEPRECATED + "" + :hidden t + (apply #'doom-cli-install args)) + +(def-command! (install i) () + "A wizard for installing Doom for the first time. This command does the following: @@ -17,89 +22,80 @@ This command is idempotent and safe to reuse. The location of DOOMDIR can be changed with the -p option, or by setting the DOOMDIR environment variable. e.g. - doom -p ~/.config/doom quickstart - DOOMDIR=~/.config/doom doom quickstart + doom -p ~/.config/doom install + DOOMDIR=~/.config/doom doom install -Quickstart understands the following switches: +install understands the following switches: --no-config Don't create DOOMDIR or dummy files therein --no-install Don't auto-install packages --no-env Don't generate an envvars file (see `doom help env`) - --no-fonts Don't install (or prompt to install) all-the-icons fonts") - - -;; -;; Library - -(defun doom-quickstart (&rest args) - "Quickly deploy a private module and setup Doom. - -This deploys a barebones config to `doom-private-dir', installs all missing -packages, prompts to install all-the-icons fonts, generates an env file and -regenerates the autoloads file." - ;; Create `doom-private-dir' - (let ((short-private-dir (abbreviate-file-name doom-private-dir))) + --no-fonts Don't install (or prompt to install) all-the-icons fonts" + (print! (green "Installing Doom Emacs!\n")) + (let ((default-directory (doom-dir "~"))) + ;; Create `doom-private-dir' (if (member "--no-config" args) - (print! (yellow "Not copying private config template, as requested")) - (print! "Creating %s" short-private-dir) - (make-directory doom-private-dir t) - (print! (green "Done!")) + (print! (warn "Not copying private config template, as requested")) + (print! "> Creating %s" (relpath doom-private-dir)) + (make-directory doom-private-dir 'parents) + (print! (success "Created %s") (relpath doom-private-dir)) ;; Create init.el, config.el & packages.el - (dolist (file (list (cons "init.el" - (lambda () - (insert-file-contents (expand-file-name "init.example.el" doom-emacs-dir)))) - (cons "config.el" - (lambda () - (insert (format ";;; %sconfig.el -*- lexical-binding: t; -*-\n\n" - short-private-dir) - ";; Place your private configuration here\n"))) - (cons "packages.el" - (lambda () - (insert (format ";; -*- no-byte-compile: t; -*-\n;;; %spackages.el\n\n" - short-private-dir) - ";;; Examples:\n" - ";; (package! some-package)\n" - ";; (package! another-package :recipe (:fetcher github :repo \"username/repo\"))\n" - ";; (package! builtin-package :disable t)\n"))))) - (cl-destructuring-bind (path . fn) file - (if (file-exists-p! path doom-private-dir) - (print! "%s already exists, skipping" path) - (print! "Creating %s%s" short-private-dir path) - (with-temp-file (expand-file-name path doom-private-dir) - (funcall fn)) - (print! (green "Done!"))))))) + (mapc (lambda (file) + (cl-destructuring-bind (filename . fn) file + (if (file-exists-p! filename doom-private-dir) + (print! (warn "%s already exists, skipping") filename) + (print! (info "Creating %s%s") (relpath doom-private-dir) filename) + (with-temp-file (doom-path doom-private-dir filename) + (funcall fn)) + (print! (success "Done!"))))) + '(("init.el" . + (lambda () + (insert-file-contents (doom-dir "init.example.el")))) + ("config.el" . + (lambda () + (insert! ";;; %sconfig.el -*- lexical-binding: t; -*-\n\n" + ";; Place your private configuration here\n" + ((relpath doom-private-dir))))) + ("packages.el" . + (lambda () + (insert! ";; -*- no-byte-compile: t; -*-\n;;; %spackages.el\n\n" + ";;; Examples:\n" + ";; (package! some-package)\n" + ";; (package! another-package :recipe (:fetcher github :repo \"username/repo\"))\n" + ";; (package! builtin-package :disable t)\n" + ((relpath doom-private-dir)))))))) - ;; In case no init.el was present the first time `doom-initialize-modules' was - ;; called in core.el (e.g. on first install) - (doom-initialize-packages 'force-p) + ;; In case no init.el was present the first time `doom-initialize-modules' was + ;; called in core.el (e.g. on first install) + (doom-initialize-packages 'force-p) - ;; Ask if Emacs.app should be patched - (if (member "--no-env" args) - (print! (yellow "Not generating envvars file, as requested")) - (when (or doom-auto-accept - (y-or-n-p "Generate an env file? (see `doom help env` for details)")) - (doom-reload-env-file 'force-p))) + ;; Ask if Emacs.app should be patched + (if (member "--no-env" args) + (print! (warn "- Not generating envvars file, as requested")) + (when (or doom-auto-accept + (y-or-n-p "Generate an env file? (see `doom help env` for details)")) + (doom-reload-env-file 'force-p))) - ;; Install Doom packages - (if (member "--no-install" args) - (print! (yellow "Not installing plugins, as requested")) - (print! "Installing plugins") - (doom-packages-install doom-auto-accept)) + ;; Install Doom packages + (if (member "--no-install" args) + (print! (warn "- Not installing plugins, as requested")) + (print! "Installing plugins") + (doom-packages-install doom-auto-accept)) - (print! "Regenerating autoloads files") - (doom-reload-autoloads nil 'force-p) + (print! "Regenerating autoloads files") + (doom-reload-autoloads nil 'force-p) - (if (member "--no-fonts" args) - (print! (yellow "Not installing fonts, as requested")) - (when (or doom-auto-accept - (y-or-n-p "Download and install all-the-icon's fonts?")) - (require 'all-the-icons) - (let ((window-system (cond (IS-MAC 'ns) - (IS-LINUX 'x)))) - (all-the-icons-install-fonts 'yes)))) + (if (member "--no-fonts" args) + (print! (warn "- Not installing fonts, as requested")) + (when (or doom-auto-accept + (y-or-n-p "Download and install all-the-icon's fonts?")) + (require 'all-the-icons) + (let ((window-system (cond (IS-MAC 'ns) + (IS-LINUX 'x)))) + (all-the-icons-install-fonts 'yes)))) - (print! (bold (green "\nFinished! Doom is ready to go!\n"))) - (with-temp-buffer - (doom-template-insert "QUICKSTART_INTRO") - (print! (buffer-string)))) + (print! (success "\nFinished! Doom is ready to go!\n")) + (with-temp-buffer + (doom-template-insert "QUICKSTART_INTRO") + (print! (buffer-string))))) diff --git a/core/cli/test.el b/core/cli/test.el index f67953fbc..eb0e79709 100644 --- a/core/cli/test.el +++ b/core/cli/test.el @@ -1,67 +1,73 @@ ;;; core/cli/test.el -*- lexical-binding: t; -*- -(dispatcher! test (doom-run-tests args) +(def-command! test () "Run Doom unit tests.") ;; -;; Library +;;; Library -(defun doom-run-tests (&optional modules) - "Run all loaded tests, specified by MODULES (a list of module cons cells) or -command line args following a double dash (each arg should be in the -'module/submodule' format). +(defun doom-run-tests () + "Discover and load test files, then run all defined suites. -If neither is available, run all tests in all enabled modules." - ;; Core libraries aren't fully loaded in a noninteractive session, so we - ;; reload it with `noninteractive' set to nil to force them to. - (let* ((noninteractive t) - (doom-modules (doom-modules))) - (quiet! (doom-reload-autoloads)) - (let ((target-paths - ;; Convert targets into a list of string paths, pointing to the root - ;; directory of modules - (cond ((stringp (car modules)) ; command line - (save-match-data - (cl-loop for arg in modules - if (string= arg ":core") collect doom-core-dir - else if (string-match-p "/" arg) - nconc (mapcar (apply-partially #'expand-file-name arg) - doom-modules-dirs) - else - nconc (cl-loop for dir in doom-modules-dirs - for path = (expand-file-name arg dir) - if (file-directory-p path) - nconc (doom-files-in path :type 'dirs :depth 1 :full t :sort nil)) - finally do (setq argv nil)))) - - (modules ; cons-cells given to MODULES - (cl-loop for (module . submodule) in modules - if (doom-module-locate-path module submodule) - collect it)) - - ((append (list doom-core-dir) - (doom-module-load-path)))))) - ;; Load all the unit test files... - (require 'buttercup) - (mapc (lambda (file) (load file :noerror (not doom-debug-mode))) - (doom-files-in (mapcar (apply-partially #'expand-file-name "test/") - target-paths) - :match "\\.el$" :full t)) - ;; ... then run them - (when doom-debug-mode - (setq buttercup-stack-frame-style 'pretty)) - (let ((split-width-threshold 0) - (split-height-threshold 0) - (window-min-width 0) - (window-min-height 0)) - (buttercup-run))))) +Takes directories as command line arguments, defaulting to the +current directory." + (let ((dirs nil) + (patterns nil) + (args command-line-args-left) + (doom-modules (doom-modules))) + (doom-initialize-autoloads doom-autoload-file) + (doom-initialize-autoloads doom-package-autoload-file) + (while args + (cond + ;; ((member (car args) '("--traceback")) + ;; (when (not (cdr args)) + ;; (error "Option requires argument: %s" (car args))) + ;; ;; Make sure it's a valid style by trying to format a dummy + ;; ;; frame with it + ;; (buttercup--format-stack-frame '(t myfun 1 2) (intern (cadr args))) + ;; (setq buttercup-stack-frame-style (intern (cadr args))) + ;; (setq args (cddr args))) + ;; ((member (car args) '("-p" "--pattern")) + ;; (when (not (cdr args)) + ;; (error "Option requires argument: %s" (car args))) + ;; (push (cadr args) patterns) + ;; (setq args (cddr args))) + ;; ((member (car args) '("-c" "--no-color")) + ;; (setq buttercup-color nil) + ;; (setq args (cdr args))) + (t + (push (car args) dirs) + (setq args (cdr args))))) + (setq command-line-args-left nil) + (dolist (dir (or dirs '("."))) + (setq dir (if (string= dir "core") + doom-core-dir + (expand-file-name dir doom-modules-dir))) + (let ((test-dir (expand-file-name "test" dir))) + (when (or (string= dir doom-core-dir) + (cl-destructuring-bind (category . module) + (or (doom-module-from-path dir) + (cons nil nil)) + (and category module (doom-module-p category module)))) + (dolist (file (nreverse (doom-glob test-dir "test-*.el"))) + (when (doom-file-cookie-p file) + (load file nil t)))))) + (when patterns + (dolist (spec (buttercup--specs buttercup-suites)) + (let ((spec-full-name (buttercup-spec-full-name spec))) + (unless (cl-dolist (p patterns) + (when (string-match p spec-full-name) + (cl-return t))) + (setf (buttercup-spec-function spec) + (lambda () (signal 'buttercup-pending "SKIPPED"))))))) + (buttercup-run))) ;; ;; Test library -(defmacro insert! (&rest text) +(defmacro insert!! (&rest text) "Insert TEXT in buffer, then move cursor to last {0} marker." `(progn (insert ,@text) diff --git a/core/cli/upgrade.el b/core/cli/upgrade.el index b7664ceb5..7f42d532d 100644 --- a/core/cli/upgrade.el +++ b/core/cli/upgrade.el @@ -1,7 +1,7 @@ ;;; core/cli/upgrade.el -*- lexical-binding: t; -*- -(dispatcher! (upgrade up) (doom-upgrade) - "Checks out the latest Doom on this branch. +(def-command! (upgrade up) () + "Updates Doom and packages. Doing so is equivalent to: @@ -9,7 +9,8 @@ Doing so is equivalent to: git pull bin/doom clean bin/doom refresh - bin/doom update") + bin/doom update" + (doom-upgrade)) ;; @@ -75,7 +76,7 @@ Doing so is equivalent to: (buffer-string))) (unless (equal (vc-git-working-revision doom-emacs-dir) rev) (error "Failed to checkout latest commit.\n\n%s" (buffer-string)))) - (doom-refresh 'force-p) + (doom-cli-refresh 'force-p) (when (doom-packages-update doom-auto-accept) (doom-reload-package-autoloads)) (message "Done! Please restart Emacs for changes to take effect"))) diff --git a/core/core-cli.el b/core/core-cli.el index 7ca20ba45..dc12704a4 100644 --- a/core/core-cli.el +++ b/core/core-cli.el @@ -1,54 +1,82 @@ ;;; -*- lexical-binding: t; no-byte-compile: t; -*- -;; Eagerly load these libraries because this module may be loaded in a session -;; that hasn't been fully initialized (where autoloads files haven't been -;; generated or `load-path' populated). -(load! "autoload/debug") -(load! "autoload/files") -(load! "autoload/message") -(load! "autoload/packages") - - -;; -;; Dispatcher API - (defvar doom-auto-accept (getenv "YES") "If non-nil, Doom will auto-accept any confirmation prompts during batch commands like `doom-packages-install', `doom-packages-update' and `doom-packages-autoremove'.") -(defconst doom--dispatch-command-alist ()) -(defconst doom--dispatch-alias-alist ()) +(defvar doom-cli-pre-execute-hook nil + "TODO") +(defvar doom-cli-post-execute-hook nil + "TODO") + +(defvar doom--cli-commands (make-hash-table :test 'equal)) +(defvar doom--cli-groups (make-hash-table :test 'equal)) +(defvar doom--cli-group nil) + + +;; +;;; Dispatcher API + +(defun doom-file-cookie-p (file) + (with-temp-buffer + (insert-file-contents-literally file nil 0 256) + (if (and (re-search-forward "^;;;###if " nil t) + (<= (line-number-at-pos) 3)) + (let ((load-file-name file)) + (eval (sexp-at-point) t)) + t))) + +(defun doom--dispatch-command (command) + (when (symbolp command) + (setq command (symbol-name command))) + (cl-check-type command string) + (intern-soft + (format "doom-cli-%s" + (if (gethash command doom--cli-commands) + command + (cl-loop for key + being the hash-keys in doom--cli-commands + for aliases = (plist-get (gethash key doom--cli-commands) :aliases) + if (member command aliases) + return key))))) (defun doom--dispatch-format (desc &optional short) (with-temp-buffer (let ((fill-column 72)) - (insert desc) - (goto-char (point-min)) - (while (re-search-forward "\n\n[^ \n]" nil t) - (fill-paragraph))) + (save-excursion + (insert desc) + (while (re-search-backward "\n\n[^ \n]" nil t) + (fill-paragraph)))) (if (not short) (buffer-string) - (goto-char (point-min)) - (buffer-substring-no-properties - (line-beginning-position) - (line-end-position))))) + (buffer-substring (line-beginning-position) + (line-end-position))))) -(defun doom--dispatch-help (&optional command desc &rest args) - "Display help documentation for a dispatcher command. If COMMAND and DESC are +(defun doom--dispatch-help-1 (command) + (cl-destructuring-bind (&key aliases _group) + (gethash command doom--cli-commands) + (print! "%-11s\t%s\t%s" + command (if aliases (string-join aliases ",") "") + (doom--dispatch-format + (documentation (doom--dispatch-command command)) + t)))) + +(defun doom--dispatch-help (&optional fn &rest args) + "Display help documentation for a dispatcher command. If fn and DESC are omitted, show all available commands, their aliases and brief descriptions." - (if command - (princ (doom--dispatch-format desc)) - (print! (bold "%-10s\t%s\t%s" "Command:" "Alias" "Description")) - (dolist (spec (cl-sort doom--dispatch-command-alist #'string-lessp - :key #'car)) - (cl-destructuring-bind (command &key desc _body) spec - (let ((aliases (cl-loop for (alias . cmd) in doom--dispatch-alias-alist - if (eq cmd command) - collect (symbol-name alias)))) - (print! " %-10s\t%s\t%s" - command (if aliases (string-join aliases ",") "") - (doom--dispatch-format desc t))))))) + (if fn + (princ (documentation fn)) + (print! (bold "%-11s\t%s\t%s" "Command:" "Alias" "Description")) + (print-group! + (dolist (group (seq-group-by (lambda (key) (plist-get (gethash key doom--cli-commands) :group)) + (hash-table-keys doom--cli-commands))) + (if (null (car group)) + (mapc #'doom--dispatch-help-1 (cdr group)) + (print! "%-30s\t%s" (bold (car group)) (gethash (car group) doom--cli-groups)) + (print-group! + (mapc #'doom--dispatch-help-1 (cdr group)))) + (terpri))))) (defun doom-dispatch (cmd args &optional show-help) "Parses ARGS and invokes a dispatcher. @@ -59,17 +87,31 @@ If SHOW-HELP is non-nil, show the documentation for said dispatcher." (when args (setq cmd (car args) args (cdr args)))) - (cl-destructuring-bind (command &key desc body) - (let ((sym (intern cmd))) - (or (assq sym doom--dispatch-command-alist) - (assq (cdr (assq sym doom--dispatch-alias-alist)) - doom--dispatch-command-alist) - (user-error "Invalid command: %s" sym))) + (let ((fn (doom--dispatch-command cmd))) + (unless (fboundp fn) + (user-error "%s is not any command *I* know!" fn)) (if show-help - (apply #'doom--dispatch-help command desc args) - (funcall body args)))) + (doom--dispatch-help fn args) + (let ((start-time (current-time))) + (run-hooks 'doom-cli-pre-execute-hook) + (when-let (ret (apply fn args)) + (print! + "\n%s" + (success "Finished! (%.4fs)" + (float-time + (time-subtract (current-time) + start-time)))) + (run-hooks 'doom-cli-post-execute-hook) + ret))))) -(defmacro dispatcher! (command form &optional docstring) +(defmacro def-command-group! (name docstring &rest body) + "TODO" + (declare (indent defun) (doc-string 2)) + `(let ((doom--cli-group ,name)) + (puthash doom--cli-group ,docstring doom--cli-groups) + ,@body)) + +(defmacro def-command! (names arglist docstring &rest body) "Define a dispatcher command. COMMAND is a symbol or a list of symbols representing the aliases for this command. DESC is a string description. The first line should be short (under 60 letters), as it will be displayed for @@ -77,79 +119,38 @@ bin/doom help. BODY will be run when this dispatcher is called." (declare (indent defun) (doc-string 3)) - (cl-destructuring-bind (cmd &rest aliases) - (doom-enlist command) + (let* ((names (mapcar #'symbol-name (doom-enlist names))) + (fn (intern (format "doom-cli-%s" (car names)))) + (plist (cl-loop while (keywordp (car body)) + collect (pop body) + collect (pop body)))) (macroexp-progn - (append - (when aliases - `((dolist (alias ',aliases) - (setf (alist-get alias doom--dispatch-alias-alist) ',cmd)))) - `((setf (alist-get ',cmd doom--dispatch-command-alist) - (list :desc ,docstring - :body (lambda (args) (ignore args) ,form)))))))) + (reverse + `((unless ,(plist-get plist :hidden) + (let ((plist ',plist)) + (setq plist (plist-put plist :aliases ',(cdr names))) + (unless (or (plist-member plist :group) + (null doom--cli-group)) + (plist-put plist :group doom--cli-group)) + (puthash ,(car names) plist doom--cli-commands))) + (defun ,fn ,arglist + ,docstring + ,@body)))))) ;; -;; Dummy dispatch commands +;;; Dispatch commands -;; These are handled by bin/doom, except we still want 'help CMD' to print out -;; documentation for them, so... - -(dispatcher! run :noop - "Run Doom Emacs from bin/doom's parent directory. - -All arguments are passed on to Emacs (except for -p and -e). - - doom run - doom run -nw init.el - -WARNING: this command exists for convenience and testing. Doom will suffer -additional overhead by being started this way. For the best performance, it is -best to run Doom out of ~/.emacs.d and ~/.doom.d.") - -(dispatcher! (doctor doc) :noop - "Checks for issues with your environment & Doom config. - -Use the doctor to diagnose common problems or list missing dependencies in -enabled modules.") - -(dispatcher! (help h) :noop - "Look up additional information about a command.") +;; Eagerly load these libraries because this module may be loaded in a session +;; that hasn't been fully initialized (where autoloads files haven't been +;; generated or `load-path' populated). +(load! "autoload/format") +(load! "autoload/packages") -;; -;; Real dispatch commands - -(load! "cli/autoloads") -(load! "cli/byte-compile") -(load! "cli/debug") -(load! "cli/env") -(load! "cli/packages") -(load! "cli/patch-macos") -(load! "cli/quickstart") -(load! "cli/upgrade") -(load! "cli/test") - - -;; -(defun doom-refresh (&optional force-p) - "Ensure Doom is in a working state by checking autoloads and packages, and -recompiling any changed compiled files. This is the shotgun solution to most -problems with doom." - (when (getenv "DOOMENV") - (doom-reload-env-file 'force)) - (doom-reload-doom-autoloads force-p) - (unwind-protect - (progn - (ignore-errors - (doom-packages-autoremove doom-auto-accept)) - (ignore-errors - (doom-packages-install doom-auto-accept))) - (doom-reload-package-autoloads force-p) - (doom-byte-compile nil 'recompile))) - -(dispatcher! (refresh re) (doom-refresh 'force) - "Refresh Doom. +;; Load all of our subcommands +(def-command! (refresh re) (&optional force-p) + "Ensure Doom is properly set up. This is the equivalent of running autoremove, install, autoloads, then recompile. Run this whenever you: @@ -161,7 +162,68 @@ recompile. Run this whenever you: It will ensure that unneeded packages are removed, all needed packages are installed, autoloads files are up-to-date and no byte-compiled files have gone -stale.") +stale." + (print! (green "Initiating a refresh of Doom Emacs...\n")) + (let (success) + (when (file-exists-p doom-env-file) + (doom-reload-env-file 'force)) + (doom-reload-core-autoloads force-p) + (unwind-protect + (progn + (and (doom-packages-install doom-auto-accept) + (setq success t)) + (and (doom-packages-rebuild doom-auto-accept) + (setq success t)) + (and (doom-packages-purge doom-auto-accept) + (setq success t))) + (doom-reload-package-autoloads (or success force-p)) + (doom-byte-compile nil 'recompile)) + t)) + + +;; Load all of our subcommands +(load! "cli/quickstart") + +(def-command-group! "Diagnostics" + "For troubleshooting and diagnostics" + (def-command! (doctor doc) () + "Checks for issues with your environment & Doom config. + +Use the doctor to diagnose common problems or list missing dependencies in +enabled modules.") + + (load! "cli/debug") + (load! "cli/test")) + +(def-command-group! "Maintenance" + "For managing your config and packages" + (load! "cli/env") + (load! "cli/upgrade") + (load! "cli/packages") + (load! "cli/autoloads") + (load! "cli/patch-macos")) + +(def-command-group! "Byte compilation" + "For byte-compiling Doom and your config" + (load! "cli/byte-compile")) + +(def-command-group! "Utilities" + "Conveniences for interacting with Doom externally" + (def-command! run () + "Run Doom Emacs from bin/doom's parent directory. + +All arguments are passed on to Emacs (except for -p and -e). + + doom run + doom run -nw init.el + +WARNING: this command exists for convenience and testing. Doom will suffer +additional overhead by being started this way. For the best performance, it is +best to run Doom out of ~/.emacs.d and ~/.doom.d.") + + ;; (load! "cli/batch") + ;; (load! "cli/org") + ) (provide 'core-cli) ;;; core-cli.el ends here diff --git a/core/core-modules.el b/core/core-modules.el index 2453f861e..b9c368385 100644 --- a/core/core-modules.el +++ b/core/core-modules.el @@ -11,9 +11,6 @@ doom-modules-dir) "A list of module root directories. Order determines priority.") -(defvar doom-inhibit-module-warnings (not noninteractive) - "If non-nil, don't emit deprecated or missing module warnings at startup.") - (defconst doom-obsolete-modules '((:feature (version-control (:emacs vc) (:ui vc-gutter)) (spellcheck (:tools flyspell)) @@ -50,14 +47,10 @@ syntax-checker modules obsolete. e.g. If :feature version-control is found in your `doom!' block, a warning is emitted before replacing it with :emacs vc and :ui vc-gutter.") -(defvar doom--current-module nil) -(defvar doom--current-flags nil) -(defvar doom--modules-cache ()) +(defvar doom-inhibit-module-warnings (not noninteractive) + "If non-nil, don't emit deprecated or missing module warnings at startup.") - -;; ;;; Custom hooks - (defvar doom-before-init-modules-hook nil "A list of hooks to run before Doom's modules' config.el files are loaded, but after their init.el files are loaded.") @@ -68,8 +61,8 @@ before the user's private module.") (defvaralias 'doom-after-init-modules-hook 'after-init-hook) -(define-obsolete-variable-alias 'doom-post-init-hook 'doom-init-modules-hook "2.1.0") -(define-obsolete-variable-alias 'doom-init-hook 'doom-before-init-modules-hook "2.1.0") +(defvar doom--current-module nil) +(defvar doom--current-flags nil) ;; @@ -79,27 +72,26 @@ before the user's private module.") "Loads the init.el in `doom-private-dir' and sets up hooks for a healthy session of Dooming. Will noop if used more than once, unless FORCE-P is non-nil." - (when (and (or force-p - (not doom-init-modules-p)) - (not (setq doom-modules nil)) - (load! "init" doom-private-dir t)) - (setq doom-init-modules-p t) - (unless (hash-table-p doom-modules) - (setq doom-modules (make-hash-table :test 'equal))) - (maphash (lambda (key plist) - (let ((doom--current-module key) - (doom--current-flags (plist-get plist :flags))) - (load! "init" (plist-get plist :path) t))) - doom-modules) - (run-hook-wrapped 'doom-before-init-modules-hook #'doom-try-run-hook) - (unless noninteractive - (maphash (lambda (key plist) - (let ((doom--current-module key) - (doom--current-flags (plist-get plist :flags))) - (load! "config" (plist-get plist :path) t))) - doom-modules) - (run-hook-wrapped 'doom-init-modules-hook #'doom-try-run-hook) - (load! "config" doom-private-dir t)))) + (when (or force-p (not doom-init-modules-p)) + (setq doom-init-modules-p t + doom-modules nil) + (when (load! "init" doom-private-dir t) + (when doom-modules + (maphash (lambda (key plist) + (let ((doom--current-module key) + (doom--current-flags (plist-get plist :flags))) + (load! "init" (plist-get plist :path) t))) + doom-modules)) + (run-hook-wrapped 'doom-before-init-modules-hook #'doom-try-run-hook) + (unless noninteractive + (when doom-modules + (maphash (lambda (key plist) + (let ((doom--current-module key) + (doom--current-flags (plist-get plist :flags))) + (load! "config" (plist-get plist :path) t))) + doom-modules)) + (run-hook-wrapped 'doom-init-modules-hook #'doom-try-run-hook) + (load! "config" doom-private-dir t))))) ;; @@ -108,12 +100,11 @@ non-nil." (defun doom-module-p (category module &optional flag) "Returns t if CATEGORY MODULE is enabled (ie. present in `doom-modules')." (declare (pure t) (side-effect-free t)) - (when (hash-table-p doom-modules) - (let ((plist (gethash (cons category module) doom-modules))) - (and plist - (or (null flag) - (memq flag (plist-get plist :flags))) - t)))) + (let ((plist (gethash (cons category module) doom-modules))) + (and plist + (or (null flag) + (memq flag (plist-get plist :flags))) + t))) (defun doom-module-get (category module &optional property) "Returns the plist for CATEGORY MODULE. Gets PROPERTY, specifically, if set." @@ -128,7 +119,7 @@ non-nil." of PROPERTY and VALUEs. \(fn CATEGORY MODULE PROPERTY VALUE &rest [PROPERTY VALUE [...]])" - (if-let* ((old-plist (doom-module-get category module))) + (if-let (old-plist (doom-module-get category module)) (progn (when plist (when (cl-oddp (length plist)) @@ -159,9 +150,10 @@ MODULE (symbol). If the category isn't enabled this will always return nil. For finding disabled modules use `doom-module-locate-path'." - (let ((path (doom-module-get category module :path)) - file-name-handler-alist) - (if file (expand-file-name file path) + (let ((path (doom-module-get category module :path))) + (if file + (let (file-name-handler-alist) + (expand-file-name file path)) path))) (defun doom-module-locate-path (category &optional module file) @@ -183,37 +175,42 @@ This doesn't require modules to be enabled. For enabled modules us if (file-exists-p path) return (expand-file-name path))) -(defun doom-module-from-path (&optional path) - "Returns a cons cell (CATEGORY . MODULE) derived from PATH (a file path)." +(defun doom-module-from-path (&optional path enabled-only) + "Returns a cons cell (CATEGORY . MODULE) derived from PATH (a file path). +If ENABLED-ONLY, return nil if the containing module isn't enabled." (or doom--current-module - (let* (file-name-handler-alist - (path (or path (FILE!)))) + (let* ((file-name-handler-alist nil) + (path (file-truename (or path (file!))))) (save-match-data - (setq path (file-truename path)) (when (string-match "/modules/\\([^/]+\\)/\\([^/]+\\)\\(?:/.*\\)?$" path) - (when-let* ((category (match-string 1 path)) - (module (match-string 2 path))) - (cons (doom-keyword-intern category) - (intern module)))))))) + (when-let* ((category (doom-keyword-intern (match-string 1 path))) + (module (intern (match-string 2 path)))) + (and (or (null enabled-only) + (doom-module-p category module)) + (cons category module)))))))) -(defun doom-module-load-path (&optional all-p) - "Return an unsorted list of absolute file paths to activated modules. +(defun doom-module-load-path (&optional module-dirs) + "Return a list of file paths to activated modules. -If ALL-P is non-nil, return paths of possible modules, activated or otherwise." +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)) - (append (if all-p - (doom-files-in doom-modules-dirs + (append (list doom-private-dir) + (if module-dirs + (doom-files-in (if (listp module-dirs) + module-dirs + doom-modules-dirs) :type 'dirs :mindepth 1 - :depth 1 - :full t - :sort nil) + :depth 1) (cl-loop for plist being the hash-values of (doom-modules) collect (plist-get plist :path))) - (list doom-private-dir))) + nil)) (defun doom-modules (&optional refresh-p) - "Minimally initialize `doom-modules' (a hash table) and return it." + "Minimally initialize `doom-modules' (a hash table) and return it. +This value is cached. If REFRESH-P, then don't use the cached value." (or (unless refresh-p doom-modules) (let ((noninteractive t) doom-modules @@ -343,45 +340,43 @@ The overall load order of Doom is as follows: Module load order is determined by your `doom!' block. See `doom-modules-dirs' for a list of all recognized module trees. Order defines precedence (from most to least)." - (if doom--modules-cache - (progn - (setq doom-modules doom--modules-cache) - (doom-log "Using `doom-modules' cache")) - (unless doom-modules - (setq doom-modules - (make-hash-table :test 'equal - :size (if modules (length modules) 150) - :rehash-threshold 1.0))) - (let ((inhibit-message doom-inhibit-module-warnings) - category m) - (while modules - (setq m (pop modules)) - (cond ((keywordp m) (setq category m)) - ((not category) (error "No module category specified for %s" m)) - ((catch 'doom-modules - (let* ((module (if (listp m) (car m) m)) - (flags (if (listp m) (cdr m)))) - (when-let* ((obsolete (assq category doom-obsolete-modules)) - (new (assq module obsolete))) - (let ((newkeys (cdr new))) - (if (null newkeys) - (message "WARNING %s module was removed" key) - (if (cdr newkeys) - (message "WARNING %s module was removed and split into the %s modules" - (list category module) (mapconcat #'prin1-to-string newkeys ", ")) - (message "WARNING %s module was moved to %s" - (list category module) (car newkeys))) - (push category modules) - (dolist (key newkeys) - (push (if flags - (nconc (cdr key) flags) - (cdr key)) - modules) - (push (car key) modules)) - (throw 'doom-modules t)))) - (if-let (path (doom-module-locate-path category module)) - (doom-module-set category module :flags flags :path path) - (message "WARNING Couldn't find the %s %s module" category module))))))))) + (unless (keywordp (car modules)) + (setq modules (eval modules t))) + (unless doom-modules + (setq doom-modules + (make-hash-table :test 'equal + :size (if modules (length modules) 150) + :rehash-threshold 1.0))) + (let ((inhibit-message doom-inhibit-module-warnings) + category m) + (while modules + (setq m (pop modules)) + (cond ((keywordp m) (setq category m)) + ((not category) (error "No module category specified for %s" m)) + ((catch 'doom-modules + (let* ((module (if (listp m) (car m) m)) + (flags (if (listp m) (cdr m)))) + (when-let* ((obsolete (assq category doom-obsolete-modules)) + (new (assq module obsolete))) + (let ((newkeys (cdr new))) + (if (null newkeys) + (message "WARNING %s module was removed" key) + (if (cdr newkeys) + (message "WARNING %s module was removed and split into the %s modules" + (list category module) (mapconcat #'prin1-to-string newkeys ", ")) + (message "WARNING %s module was moved to %s" + (list category module) (car newkeys))) + (push category modules) + (dolist (key newkeys) + (push (if flags + (nconc (cdr key) flags) + (cdr key)) + modules) + (push (car key) modules)) + (throw 'doom-modules t)))) + (if-let (path (doom-module-locate-path category module)) + (doom-module-set category module :flags flags :path path) + (message "WARNING Couldn't find the %s %s module" category module)))))))) (when noninteractive (setq doom-inhibit-module-warnings t)) `(setq doom-modules ',doom-modules)) @@ -505,9 +500,9 @@ CATEGORY and MODULE can be omitted When this macro is used from inside a module (doom--current-flags (memq category doom--current-flags)) ((let ((module-pair (or doom--current-module - (doom-module-from-path (FILE!))))) + (doom-module-from-path (file!))))) (unless module-pair - (error "featurep! call couldn't auto-detect what module its in (from %s)" (FILE!))) + (error "featurep! call couldn't auto-detect what module its in (from %s)" (file!))) (memq category (doom-module-get (car module-pair) (cdr module-pair) :flags))))) t)) diff --git a/core/core-packages.el b/core/core-packages.el index 8346326a2..d7cda7fbf 100644 --- a/core/core-packages.el +++ b/core/core-packages.el @@ -1,5 +1,7 @@ ;;; core/core-packages.el -*- lexical-binding: t; -*- +(require 'core-modules) + ;; Emacs package management is opinionated, and so am I. I've bound together ;; `use-package', `quelpa' and package.el to create my own, rolling-release, ;; lazily-loaded package management system for Emacs. @@ -32,123 +34,164 @@ ;; ;; See core/autoload/packages.el for more functions. +(defvar doom-init-packages-p nil + "If non-nil, Doom's package management system has been initialized.") + (defvar doom-packages () "A list of enabled packages. Each element is a sublist, whose CAR is the package's name as a symbol, and whose CDR is the plist supplied to its `package!' declaration. Set by `doom-initialize-packages'.") -(defvar doom-core-packages - '(persistent-soft use-package quelpa async) +(defvar doom-core-packages '(straight use-package async) "A list of packages that must be installed (and will be auto-installed if missing) and shouldn't be deleted.") +(defvar doom-core-package-sources + '((org-elpa :local-repo nil) + (melpa + :type git :host github + :repo "melpa/melpa" + :no-build t) + (gnu-elpa-mirror + :type git :host github + :repo "emacs-straight/gnu-elpa-mirror" + :no-build t) + (emacsmirror-mirror + :type git :host github + :repo "emacs-straight/emacsmirror-mirror" + :no-build t)) + "A list of recipes for straight's recipe repos.") + (defvar doom-disabled-packages () "A list of packages that should be ignored by `def-package!' and `after!'.") -;;; package.el + +;; +;;; Package managers + +;; Ensure that, if we do need package.el, it is configured correctly. You really +;; shouldn't be using it, but it may be convenient for quick package testing. (setq package--init-file-ensured t - package-user-dir (expand-file-name "elpa" doom-packages-dir) - package-gnupghome-dir (expand-file-name "gpg" doom-packages-dir) package-enable-at-startup nil + package-user-dir doom-elpa-dir + package-gnupghome-dir (expand-file-name "gpg" doom-elpa-dir) ;; I omit Marmalade because its packages are manually submitted rather ;; than pulled, so packages are often out of date with upstream. package-archives - `(("gnu" . "https://elpa.gnu.org/packages/") - ("melpa" . "https://melpa.org/packages/") - ("org" . "https://orgmode.org/elpa/"))) + (let ((proto (if gnutls-verify-error "http" "https"))) + `(("gnu" . ,(concat proto "://elpa.gnu.org/packages/")) + ("melpa" . ,(concat proto "://melpa.org/packages/")) + ("org" . ,(concat proto "://orgmode.org/elpa/"))))) ;; Don't save `package-selected-packages' to `custom-file' -(advice-add #'package--save-selected-packages :override - (lambda (&optional value) (if value (setq package-selected-packages value)))) +(def-advice! doom--package-inhibit-custom-file-a (&optional value) + :override #'package--save-selected-packages + (if value (setq package-selected-packages value))) -(when (or (not gnutls-verify-error) - (not (ignore-errors (gnutls-available-p)))) - (dolist (archive package-archives) - (setcdr archive (replace-regexp-in-string "^https://" "http://" (cdr archive) t nil)))) +;;; straight +(setq straight-cache-autoloads nil ; we already do this, and better. + ;; Doom doesn't encourage you to modify packages in place. Disabling this + ;; makes 'doom refresh' instant (once everything set up), which is much + ;; nicer UX than the several seconds modification checks add. + straight-check-for-modifications nil + ;; We do this ourselves, and a little more comprehensively. + straight-enable-package-integration nil + ;; Before switching to straight, `doom-local-dir' would average out at + ;; around 100mb. Afterwards, at around 1gb. With shallow cloning, that is + ;; reduced to ~400mb. This imposes an isuse with packages that require + ;; their git history for certain things to work (like magit and org), but + ;; we're prepared for that. + straight-vc-git-default-clone-depth 1 + ;; Straight's own emacsmirror mirro is a little smaller and faster. + straight-recipes-emacsmirror-use-mirror t + ;; Prefix declarations are unneeded bulk added to our autoloads file. Best + ;; we just don't have to deal with them at all. + autoload-compute-prefixes nil) -;;; quelpa -(setq quelpa-dir (expand-file-name "quelpa" doom-packages-dir) - quelpa-verbose doom-debug-mode - - ;; Don't track MELPA, we'll use package.el for that - quelpa-checkout-melpa-p nil - quelpa-update-melpa-p nil - quelpa-melpa-recipe-stores nil - quelpa-self-upgrade-p nil) +;; Straight is hardcoded to operate out of ~/.emacs.d/straight. Not on my watch! +(def-advice! doom--straight-use-local-dir-a (orig-fn &rest args) + :around #'straight--emacs-dir + (let ((user-emacs-directory doom-local-dir)) + (apply orig-fn args))) ;; ;;; Bootstrapper (defun doom-initialize-packages (&optional force-p) - "Ensures that Doom's package management system, package.el and quelpa are -initialized, and `doom-packages', `packages-alist' and `quelpa-cache' are -populated, if they aren't already. + "Ensures that Doom's package system and straight.el are initialized. If FORCE-P is non-nil, do it anyway. -If FORCE-P is 'internal, only (re)populate `doom-packages'. -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." - (let ((load-prefer-newer t)) ; reduce stale code issues - ;; package.el and quelpa handle themselves if their state changes during the - ;; current session, but if you change a packages.el file in a module, - ;; there's no non-trivial way to detect that, so to reload only - ;; `doom-packages' pass 'internal as FORCE-P or use `doom/reload-packages'. - (unless (eq force-p 'internal) - ;; `package-alist' - (when (or force-p (not (bound-and-true-p package-alist))) - (doom-ensure-packages-initialized 'force) - (setq load-path (cl-delete-if-not #'file-directory-p load-path))) - ;; `quelpa-cache' - (when (or force-p (not (bound-and-true-p quelpa-cache))) - ;; ensure un-byte-compiled version of quelpa is loaded - (unless (featurep 'quelpa) - (load (locate-library "quelpa.el") nil t t)) - (setq quelpa-initialized-p nil) - (or (quelpa-setup-p) - (error "Could not initialize quelpa")))) - ;; `doom-packages' - (when (or force-p (not doom-packages)) - (setq doom-packages (doom-package-list))))) +This ensure `doom-packages' is populated, if isn't aren't already. Use this +before any of straight's or Doom's package management's API to ensure all the +necessary package metadata is initialized and available for them." + (when (or force-p (not doom-init-packages-p)) + (setq doom-init-packages-p t) + (straight--reset-caches) + (mapc #'straight-use-recipes doom-core-package-sources) + (straight-register-package + `(straight :type git :host github + :repo ,(format "%s/straight.el" straight-repository-user) + :files ("straight*.el") + :branch ,straight-repository-branch)) + (mapc #'straight-use-package doom-core-packages) + (when noninteractive + (add-hook 'kill-emacs-hook #'straight--transaction-finalize)) + (dolist (package (straight--directory-files (straight--build-dir))) + (add-to-list 'load-path (directory-file-name (straight--build-dir package))))) + (when (or force-p (not doom-packages)) + (setq doom-disabled-packages nil + doom-packages (doom-package-list)) + (cl-loop for (pkg . plist) in doom-packages + for ignored = (eval (plist-get plist :ignore) t) + for disabled = (eval (plist-get plist :disable) t) + if disabled + do (add-to-list 'doom-disabled-packages pkg) + else if (not ignored) + do (with-demoted-errors "Package error: %s" + (straight-register-package + (if-let (recipe (plist-get plist :recipe)) + `(,pkg ,@recipe) + pkg)))))) + +(defun doom-ensure-straight () + "Ensure `straight' is installed and was compiled with this version of Emacs." + (defvar bootstrap-version) + (let* ((straight-dir (expand-file-name "straight/" doom-local-dir)) + (bootstrap-file (expand-file-name "repos/straight.el/straight.el" straight-dir)) + (bootstrap-version 5) + ;; Force straight to install into ~/.emacs.d/.local/straight instead of + ;; ~/.emacs.d/straight by pretending `doom-local-dir' is our .emacs.d. + (user-emacs-directory doom-local-dir)) + (cl-block 'straight + ;; Straight will throw `emacs-version-changed' if it's loaded with a + ;; version of Emacs that doesn't match the one it was compiled with. + ;; Getting this error isn't very good UX... + (catch 'emacs-version-changed + (unless (require 'straight nil t) + (unless (file-exists-p bootstrap-file) + (with-current-buffer + (url-retrieve-synchronously + "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el" + 'silent 'inhibit-cookies) + (goto-char (point-max)) + (eval-print-last-sexp))) + (load bootstrap-file nil 'nomessage)) + (cl-return-from 'straight t)) + ;; ...so we transform it into a more graceful error message: + (with-temp-buffer + (insert-file-contents-literally (expand-file-name "build-cache.el" straight-dir)) + (let ((_ (read (current-buffer))) + (last-emacs-version (read (current-buffer)))) + (user-error "Your version of Emacs has changed (from %S to %S). You must rebuild your packages with 'doom rebuild'." + emacs-version last-emacs-version)))))) ;; -;;; Package API +;;; Module package macros -(defun doom-ensure-packages-initialized (&optional force-p) - "Make sure package.el is initialized." - (when (or force-p (not (bound-and-true-p package--initialized))) - (require 'package) - (setq package-activated-list nil - package--initialized nil) - (let (byte-compile-warnings) - (condition-case _ - (package-initialize) - ('error (package-refresh-contents) - (setq doom--refreshed-p t) - (package-initialize)))))) - -(defun doom-ensure-core-packages () - "Make sure `doom-core-packages' are installed." - (when-let (core-packages (cl-remove-if #'package-installed-p doom-core-packages)) - (message "Installing core packages") - (unless doom--refreshed-p - (package-refresh-contents)) - (dolist (package core-packages) - (let ((inhibit-message t)) - (package-install package)) - (if (package-installed-p package) - (message "✓ Installed %s" package) - (error "✕ Couldn't install %s" package))) - (message "Installing core packages...done"))) - - -;; -;; Module package macros - -(cl-defmacro package! (name &rest plist &key built-in recipe pin disable ignore _freeze) +(cl-defmacro package! (name &rest plist &key built-in _recipe disable ignore _freeze) "Declares a package and how to install it (if applicable). This macro is declarative and does not load nor install packages. It is used to @@ -162,9 +205,6 @@ Accepts the following properties: :recipe RECIPE Takes a MELPA-style recipe (see `quelpa-recipe' in `quelpa' for an example); for packages to be installed from external sources. - :pin ARCHIVE-NAME - Instructs ELPA to only look for this package in ARCHIVE-NAME. e.g. \"org\". - Ignored if RECIPE is present. :disable BOOL Do not install or update this package AND disable all of its `def-package!' blocks. @@ -180,14 +220,9 @@ Returns t if package is successfully registered, and nil if it was disabled elsewhere." (declare (indent defun)) (let ((old-plist (cdr (assq name doom-packages)))) - (when recipe - (when (cl-evenp (length recipe)) - (setq plist (plist-put plist :recipe (cons name recipe)))) - (setq pin nil - plist (plist-put plist :pin nil))) (let ((module-list (plist-get old-plist :modules)) (module (or doom--current-module - (let ((file (FILE!))) + (let ((file (file!))) (cond ((file-in-directory-p file doom-private-dir) (list :private)) ((file-in-directory-p file doom-core-dir) @@ -199,7 +234,7 @@ elsewhere." (when built-in (doom-log "Ignoring built-in package %S" name) (when (equal built-in '(quote prefer)) - (setq built-in `(locate-library ,(symbol-name name) nil doom-site-load-path)))) + (setq built-in `(locate-library ,(symbol-name name) nil doom--initial-load-path)))) (setq plist (plist-put plist :ignore (or built-in ignore))) (while plist (unless (null (cadr plist)) @@ -207,22 +242,20 @@ elsewhere." (pop plist) (pop plist)) (setq plist old-plist) + ;; TODO Add `straight-use-package-pre-build-function' support (macroexp-progn - (append (when pin - (doom-log "Pinning package '%s' to '%s'" name pin) - `((setf (alist-get ',name package-pinned-packages) ,pin))) - `((setf (alist-get ',name doom-packages) ',plist)) + (append `((setf (alist-get ',name doom-packages) ',plist)) (when disable - (doom-log "Disabling package '%s'" name) - `((add-to-list 'doom-disabled-packages ',name nil 'eq) + `((doom-log "Disabling package %S" ',name) + (add-to-list 'doom-disabled-packages ',name nil 'eq) nil)))))) (defmacro disable-packages! (&rest packages) "A convenience macro for disabling packages in bulk. Only use this macro in a module's (or your private) packages.el file." (macroexp-progn - (cl-loop for pkg in packages - collect (macroexpand `(package! ,pkg :disable t))))) + (cl-loop for p in packages + collect `(package! ,p :disable t)))) (provide 'core-packages) ;;; core-packages.el ends here diff --git a/core/core.el b/core/core.el index 80ed6d957..9f24e28bb 100644 --- a/core/core.el +++ b/core/core.el @@ -56,7 +56,7 @@ dependencies or long-term shared data. Must end with a slash.") Use this for files that change often, like cache files. Must end with a slash.") -(defvar doom-packages-dir (concat doom-local-dir "packages/") +(defvar doom-elpa-dir (concat doom-local-dir "elpa/") "Where package.el and quelpa plugins (and their caches) are stored. Must end with a slash.") @@ -78,13 +78,13 @@ Defaults to ~/.config/doom, ~/.doom.d or the value of the DOOMDIR envvar; whichever is found first. Must end in a slash.") (defvar doom-autoload-file (concat doom-local-dir "autoloads.el") - "Where `doom-reload-doom-autoloads' stores its core autoloads. + "Where `doom-reload-core-autoloads' stores its core autoloads. This file is responsible for informing Emacs where to find all of Doom's autoloaded core functions (in core/autoload/*.el).") (defvar doom-package-autoload-file (concat doom-local-dir "autoloads.pkg.el") - "Where `doom-reload-package-autoloads' stores its package.el autoloads. + "Where `doom-reload-package-autoloads' stores its package autoloads. This file is compiled from the autoloads files of all installed packages combined.") @@ -404,35 +404,6 @@ Meant to be used with `run-hook-wrapped'." ;; return nil so `run-hook-wrapped' won't short circuit nil) -(defun doom-ensure-same-emacs-version-p () - "Check if the running version of Emacs has changed and set -`doom-emacs-changed-p' if it has." - (if (load doom--last-emacs-file 'noerror 'nomessage 'nosuffix) - (setq doom-emacs-changed-p - (not (equal emacs-version doom--last-emacs-version))) - (with-temp-file doom--last-emacs-file - (princ `(setq doom--last-emacs-version ,(prin1-to-string emacs-version)) - (current-buffer)))) - (cond ((not doom-emacs-changed-p)) - ((y-or-n-p - (format - (concat "Your version of Emacs has changed from %s to %s, which may cause incompatibility\n" - "issues. If you run into errors, run `bin/doom compile :plugins` or reinstall your\n" - "plugins to resolve them.\n\n" - "Continue?") - doom--last-emacs-version - emacs-version)) - (delete-file doom--last-emacs-file)) - (noninteractive (error "Aborting")) - ((kill-emacs)))) - -(defun doom-ensure-core-directories-exist () - "Make sure all Doom's essential local directories (in and including -`doom-local-dir') exist." - (dolist (dir (list doom-local-dir doom-etc-dir doom-cache-dir doom-packages-dir)) - (unless (file-directory-p dir) - (make-directory dir t)))) - (defun doom-display-benchmark-h (&optional return-p) "Display a benchmark, showing number of packages and modules, and how quickly they were loaded at startup. @@ -461,7 +432,9 @@ If RETURN-P, return the message as a string instead of displaying it." "Tries to load FILE (an autoloads file). Return t on success, throws an error in interactive sessions, nil otherwise (but logs a warning)." (condition-case e - (load (file-name-sans-extension file) 'noerror 'nomessage) + (let (command-switch-alist) + (load (if noninteractive file (file-name-sans-extension file)) + 'noerror 'nomessage)) ((debug error) (if noninteractive (message "Autoload file warning: %s -> %s" (car e) (error-message-string e)) @@ -470,7 +443,7 @@ in interactive sessions, nil otherwise (but logs a warning)." (defun doom-load-env-vars (file) "Read and set envvars in FILE." (if (not (file-readable-p file)) - (doom-log "Couldn't read %S envvar file" file) + (signal 'file-error (list "Couldn't read envvar file" file)) (with-temp-buffer (insert-file-contents file) (search-forward "\n\n" nil t) @@ -527,37 +500,45 @@ to least)." load-path doom--initial-load-path process-environment doom--initial-process-environment) - ;; `doom-autoload-file' tells Emacs where to load all its autoloaded - ;; functions from. This includes everything in core/autoload/*.el and all - ;; the autoload files in your enabled modules. - (when (or force-p (not (doom-initialize-autoloads doom-autoload-file))) - (doom-ensure-core-directories-exist) - (doom-ensure-same-emacs-version-p) + ;; `doom-autoload-file' tells Emacs where to load all its functions from. + ;; This includes everything in core/autoload/*.el and autoload files in + ;; enabled modules. + (when (or (not (doom-initialize-autoloads doom-autoload-file)) + force-p) + (dolist (dir (list doom-local-dir doom-etc-dir doom-cache-dir doom-elpa-dir)) + (unless (file-directory-p dir) + (make-directory dir 'parents-too))) + ;; Ensure the package management system (and straight) are ready for + ;; action (and all core packages/repos are installed) (require 'core-packages) - (doom-ensure-packages-initialized force-p) - (doom-ensure-core-packages) + (doom-ensure-straight) + (doom-initialize-packages force-p) (unless (or force-p noninteractive) (user-error "Your doom autoloads are missing! Run `bin/doom refresh' to regenerate them"))) ;; Loads `doom-package-autoload-file', which loads a concatenated package - ;; autoloads file and caches `load-path', `auto-mode-alist', - ;; `Info-directory-list', `doom-disabled-packages' and - ;; `package-activated-list'. A big reduction in startup time. - (let (command-switch-alist) - (unless (or force-p - (doom-initialize-autoloads doom-package-autoload-file) - noninteractive) - (user-error "Your package autoloads are missing! Run `bin/doom refresh' to regenerate them"))) + ;; autoloads file which caches `load-path', `auto-mode-alist', + ;; `Info-directory-list', and `doom-disabled-packages'. A big reduction in + ;; startup time. + (unless (or force-p + (doom-initialize-autoloads doom-package-autoload-file) + noninteractive) + (user-error "Your package autoloads are missing! Run `bin/doom refresh' to regenerate them")) ;; Load shell environment - (unless noninteractive + (when (and (not noninteractive) + (file-exists-p doom-env-file)) (doom-load-env-vars doom-env-file))) ;; In case we want to use package.el's API (with-eval-after-load 'package - (require 'core-packages))) + (require 'core-packages)) + ;; Or straight interactively + (with-eval-after-load 'straight + (require 'core-packages) + (doom-initialize-packages))) ;; diff --git a/core/packages.el b/core/packages.el index a1c5c6d60..e88c26bee 100644 --- a/core/packages.el +++ b/core/packages.el @@ -40,8 +40,5 @@ (package! which-key) (package! hydra) -;; core-packages.el -(package! gnu-elpa-keyring-update) - ;; autoload/debug.el (package! esup) diff --git a/init.example.el b/init.example.el index ea853ade2..73482f49f 100644 --- a/init.example.el +++ b/init.example.el @@ -1,9 +1,9 @@ ;;; init.el -*- lexical-binding: t; -*- -;; Copy this file to ~/.doom.d/init.el or ~/.config/doom/init.el ('doom -;; quickstart' will do this for you). The `doom!' block below controls what -;; modules are enabled and in what order they will be loaded. Remember to run -;; 'doom refresh' after modifying it. +;; Copy this file to ~/.doom.d/init.el or ~/.config/doom/init.el ('doom install' +;; will do this for you). The `doom!' block below controls what modules are +;; enabled and in what order they will be loaded. Remember to run 'doom refresh' +;; after modifying it. ;; ;; More information about these modules (and what flags they support) can be ;; found in modules/README.org. diff --git a/modules/completion/helm/packages.el b/modules/completion/helm/packages.el index 1e8d9d178..2021ecf93 100644 --- a/modules/completion/helm/packages.el +++ b/modules/completion/helm/packages.el @@ -5,7 +5,7 @@ (package! helm-ag) (package! helm-c-yasnippet) (package! helm-company) -(package! helm-describe-modes :recipe (:fetcher github :repo "emacs-helm/helm-describe-modes")) +(package! helm-describe-modes :recipe (:host github :repo "emacs-helm/helm-describe-modes")) (package! helm-projectile) (package! swiper-helm) (when (featurep! +fuzzy) diff --git a/modules/editor/evil/packages.el b/modules/editor/evil/packages.el index 4c345d5e8..3e0e1f8ef 100644 --- a/modules/editor/evil/packages.el +++ b/modules/editor/evil/packages.el @@ -9,14 +9,13 @@ (package! evil-escape) (package! evil-exchange) (package! evil-indent-plus) -(package! evil-numbers :recipe (:fetcher github :repo "janpath/evil-numbers")) +(package! evil-numbers :recipe (:host github :repo "janpath/evil-numbers")) (package! evil-textobj-anyblock) (package! evil-snipe) (package! evil-surround) (package! evil-visualstar) (package! exato) - ;; (when (featurep! +everywhere) ;; `evil-collection-neotree' uses the `neotree-make-executor' macro, but this diff --git a/modules/editor/rotate-text/packages.el b/modules/editor/rotate-text/packages.el index 955e531ed..5d6486109 100644 --- a/modules/editor/rotate-text/packages.el +++ b/modules/editor/rotate-text/packages.el @@ -1,4 +1,4 @@ ;; -*- no-byte-compile: t; -*- ;;; editor/rotate-text/packages.el -(package! rotate-text :recipe (:fetcher github :repo "debug-ito/rotate-text.el")) +(package! rotate-text :recipe (:host github :repo "debug-ito/rotate-text.el")) diff --git a/modules/editor/snippets/packages.el b/modules/editor/snippets/packages.el index 64123c6d3..f0274ca26 100644 --- a/modules/editor/snippets/packages.el +++ b/modules/editor/snippets/packages.el @@ -5,6 +5,6 @@ (package! auto-yasnippet) (package! doom-snippets - :recipe (:fetcher github + :recipe (:host github :repo "hlissner/doom-snippets" :files ("*.el" "snippets"))) diff --git a/modules/lang/cc/packages.el b/modules/lang/cc/packages.el index 5a73da2f9..49f47c91b 100644 --- a/modules/lang/cc/packages.el +++ b/modules/lang/cc/packages.el @@ -10,7 +10,7 @@ (when (package! glsl-mode) (when (featurep! :completion company) - (package! company-glsl :recipe (:fetcher github :repo "Kaali/company-glsl")))) + (package! company-glsl :recipe (:host github :repo "Kaali/company-glsl")))) (if (featurep! +lsp) (package! ccls) diff --git a/modules/lang/ocaml/packages.el b/modules/lang/ocaml/packages.el index 9a5a84a40..28c8f99bc 100644 --- a/modules/lang/ocaml/packages.el +++ b/modules/lang/ocaml/packages.el @@ -16,18 +16,8 @@ ;; by default quelpa generated a version 0pre0.20180929.192844, which got ;; parsed into (0 -1 0 ...), which when compared with version nil (0) in ;; package-installed-p always yielded false - (package! ocamlformat :recipe (:fetcher github :repo "ocaml-ppx/ocamlformat" :files ("emacs/*.el")))) + (package! ocamlformat :recipe + (:host github :repo "ocaml-ppx/ocamlformat" :files ("emacs/*.el")))) -(package! dune :recipe (:fetcher github :repo "ocaml/dune" :files ("editor-integration/emacs/*.el"))) - - -;; (defvar +ocaml-elisp-dir -;; (when (executable-find "opam") -;; (let ((opam-share (ignore-errors (car (process-lines "opam" "config" "var" "share" "--safe"))))) -;; (when (and opam-share (file-directory-p opam-share)) -;; (expand-file-name "emacs/site-lisp" opam-share))))) -;; -;; (defmacro localpackage! (name) -;; `(package! ,name :recipe (:fetcher file :path ,+ocaml-elisp-dir))) -;; -;; (localpackage! opam-site-lisp) +(package! dune :recipe + (:host github :repo "ocaml/dune" :files ("editor-integration/emacs/*.el"))) diff --git a/modules/lang/org/config.el b/modules/lang/org/config.el index 160f5bd7b..e809cda2f 100644 --- a/modules/lang/org/config.el +++ b/modules/lang/org/config.el @@ -882,3 +882,12 @@ compelling reason, so..." (org-clock-load)) :config (add-hook 'kill-emacs-hook #'org-clock-save))) + + +;; HACK A necessary hack because org requires a compilation step after being +;; cloned, and during that compilation a org-version.el is generated with these +;; two functions, which return the output of a 'git describe ...' call in the +;; repo's root. Of course, this command won't work in a sparse clone, and more +;; than that, initiating these compilation step is a hassle, so... +(defun org-release () "") +(defun org-git-version () "") diff --git a/modules/lang/org/packages.el b/modules/lang/org/packages.el index 2697d0896..3a0bfcff0 100644 --- a/modules/lang/org/packages.el +++ b/modules/lang/org/packages.el @@ -1,15 +1,8 @@ ;; -*- no-byte-compile: t; -*- ;;; lang/org/packages.el -;; Prevent built-in Org from playing into the byte-compilation of -;; `org-plus-contrib'. -(when-let (orglib (locate-library "org" nil doom-site-load-path)) - (setq load-path (delete (substring (file-name-directory orglib) 0 -1) - load-path))) -(package! org-plus-contrib) ; install cutting-edge version of org-mode -(package! org :ignore t) ; ignore org on ELPA, if possible - -(package! org-bullets :recipe (:fetcher github :repo "Kaligule/org-bullets")) +(package! org-plus-contrib) ; install cutting-edge version of org-mode +(package! org-bullets :recipe (:host github :repo "Kaligule/org-bullets")) (package! toc-org) (when (featurep! :editor evil) (package! evil-org)) @@ -17,7 +10,7 @@ (package! org-pdfview)) (package! htmlize) (package! ox-clip) -(package! org-yt :recipe (:fetcher github :repo "TobiasZawada/org-yt")) +(package! org-yt :recipe (:host github :repo "TobiasZawada/org-yt")) ;;; Babel (package! ob-async) @@ -28,7 +21,7 @@ (when (featurep! :lang nim) (package! ob-nim)) (when (featurep! :lang racket) - (package! ob-racket :recipe (:fetcher github :repo "DEADB17/ob-racket"))) + (package! ob-racket :recipe (:host github :repo "DEADB17/ob-racket"))) (when (featurep! :lang rest) (package! ob-restclient)) (when (featurep! :lang rust) @@ -37,18 +30,14 @@ ;;; Modules (when (featurep! +dragndrop) (package! org-download)) - (when (featurep! +gnuplot) (package! gnuplot) (package! gnuplot-mode)) - (when (featurep! +ipython) (package! ob-ipython)) - (when (featurep! +pandoc) (package! ox-pandoc)) - (when (featurep! +present) - (package! centered-window :recipe (:fetcher github :repo "anler/centered-window-mode")) + (package! centered-window :recipe (:host github :repo "anler/centered-window-mode")) (package! org-tree-slide) (package! ox-reveal)) diff --git a/modules/lang/php/packages.el b/modules/lang/php/packages.el index 39f874bb5..bcb87db05 100644 --- a/modules/lang/php/packages.el +++ b/modules/lang/php/packages.el @@ -2,13 +2,13 @@ ;;; lang/php/packages.el (package! php-boris) -(package! php-extras :recipe (:fetcher github :repo "arnested/php-extras")) +(package! php-extras :recipe (:host github :repo "arnested/php-extras")) (package! php-mode) (package! php-refactor-mode) (package! phpunit) (when (featurep! +hack) - (package! hack-mode :recipe (:fetcher github :repo "hhvm/hack-mode"))) + (package! hack-mode :recipe (:host github :repo "hhvm/hack-mode"))) (unless (featurep! +lsp) (package! phpactor)) diff --git a/modules/lang/terra/packages.el b/modules/lang/terra/packages.el index 90115f396..4a1a1485b 100644 --- a/modules/lang/terra/packages.el +++ b/modules/lang/terra/packages.el @@ -1,7 +1,8 @@ ;; -*- no-byte-compile: t; -*- ;;; lang/lua/packages.el -(package! terra-mode :recipe (:fetcher github :repo "StanfordLegion/terra-mode")) +(package! terra-mode + :recipe (:host github :repo "StanfordLegion/terra-mode")) (when (featurep! :completion company) (package! company-lua)) diff --git a/modules/tools/magit/config.el b/modules/tools/magit/config.el index 16c549113..e2e36bac3 100644 --- a/modules/tools/magit/config.el +++ b/modules/tools/magit/config.el @@ -17,6 +17,11 @@ It is passed a user and repository name.") (setq transient-levels-file (concat doom-etc-dir "transient/levels") transient-values-file (concat doom-etc-dir "transient/values") transient-history-file (concat doom-etc-dir "transient/history")) + + ;; HACK Magit complains loudly when it can't determine its own version, which + ;; is the case when magit is built through straight. The warning is + ;; harmless, however, so we just need it to shut up. + (advice-add #'magit-version :around #'ignore) :config (setq transient-default-level 5 magit-revision-show-gravatars '("^Author: " . "^Commit: ") diff --git a/modules/tools/pass/packages.el b/modules/tools/pass/packages.el index cb4e52e36..a65e113a8 100644 --- a/modules/tools/pass/packages.el +++ b/modules/tools/pass/packages.el @@ -7,8 +7,8 @@ ;; an older version of `auto-source-pass' is built into Emacs 26+, so we must ;; install the new version directly from the source and with a psuedonym. -(package! auth-source-pass-new - :recipe (auth-source-pass :fetcher github :repo "DamienCassou/auth-password-store")) +(package! auth-source-pass + :recipe (:host github :repo "DamienCassou/auth-password-store")) (when (featurep! :completion ivy) (package! ivy-pass))