💥 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
This commit is contained in:
Henrik Lissner 2019-07-21 15:39:45 +02:00
parent 492f2dea1e
commit b90dede1ab
No known key found for this signature in database
GPG key ID: 5F6C0EA160557395
35 changed files with 1542 additions and 1771 deletions

View file

@ -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

View file

@ -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**

View file

@ -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)))

View file

@ -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")))

View file

@ -16,44 +16,35 @@ 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
(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))
(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
: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))
@ -64,17 +55,15 @@ ready to be pasted in a bug report on github."
(if flags
`(,(cdr key) ,@flags)
(cdr key))))
"n/a")
(or (ignore-errors
'("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")
;; abbreviate $HOME to hide username
(mapcar #'abbreviate-file-name exec-path))))
'("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...")
(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 "<details>\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</details>"))
(if noninteractive
(print! (doom-info))
(kill-new (doom-info))
(message "Done! Copied to clipboard.")))
(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

View file

@ -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

View file

@ -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)

View file

@ -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)))
;;;###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)))
;;;###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)))
;;;###autoload
(defun doom-package-psuedo-name (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))
;;;###autoload
(defun doom-package-backend (package &optional noerror)
"Return backend that PACKAGE was installed with.
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))
(let ((plist (cdr (assq package doom-packages))))
(if prop
(if (plist-member plist prop)
(plist-get plist prop)
nil-value)
nil-value))
plist)))
;;;###autoload
(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-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-build-time (package)
"TODO"
(car (gethash (symbol-name package) straight--build-cache)))
;;;###autoload
(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)))
(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,23 +199,28 @@ 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))
(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
@ -332,7 +229,7 @@ files."
((file-in-directory-p file doom-core-dir)
'((:core)))
((doom-module-from-path file)))))
doom-packages))))))
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)

View file

@ -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)
(if noninteractive
(add-hook 'doom-cli-post-execute-hook #'doom--warn-refresh-session)
(dolist (file files)
(load-file (byte-compile-dest-file file)))
(add-hook 'kill-emacs-hook #'doom--warn-refresh-session)))
(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,82 +72,69 @@ 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)
(let ((n 0))
(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))))))
(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 (targets enabled-targets)
(goto-char (point-max))
(search-backward ";;;***" nil t)
(save-excursion (insert "\n"))
(dolist (path targets)
(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)
(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))
@ -167,20 +144,21 @@ even if it doesn't need reloading!"
((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)
`(: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
(cl-destructuring-bind (_ _name arglist &rest body) sexp
(let ((docstring (if (stringp (car body))
(pop body)
"No documentation.")))
(push (if member-p
(appendq!
forms
(list (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
@ -200,38 +178,46 @@ even if it doesn't need reloading!"
collect (car arg) into syms
finally return (if syms `((ignore ,@syms))))))
('error
(message "Ignoring autodef %s (%s)"
name e)
(print! "- Ignoring autodef %s (%s)" name e)
nil)))
forms)
(push `(put ',name 'doom-module ',origin) forms))))
`(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 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))))
(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)))))
(if forms
(member-p (push sexp forms)))))
forms))
(defun doom--generate-autodefs (targets enabled-targets)
(goto-char (point-max))
(search-backward ";;;***" nil t)
(save-excursion (insert "\n"))
(dolist (path targets)
(insert
(with-temp-buffer
(insert-file-contents path)
(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,61 +227,77 @@ 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)))))
;; 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)
(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)
(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)))
(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)
(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-doom-autoloads)
(prin1 `(setq doom--modules-cache ',doom-modules) (current-buffer))
(doom--generate-header 'doom-reload-core-autoloads)
(save-excursion
(doom--generate-autoloads (reverse enabled-targets)))
(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-autoloads)
(print! (green "Expanded module autoload paths")))
(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 (reverse targets) enabled-targets)
(print! (green "Generated autodefs")))
(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! (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)
(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")))
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 "(let ((load-file-name " (prin1-to-string (abbreviate-file-name file)) "))\n")
(insert-file-contents file)
(when (save-excursion
(and (re-search-forward "\\_<load-file-name\\_>" 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"))
(insert ")\n"))))
(message "Couldn't find package desc for %s" (car spec)))))
(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)))
(print! (start "Checking package autoloads file"))
(print-group!
(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 (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! (green "Doom package autoloads is up-to-date"))
(ignore
(print! (success "Skipping package autoloads, they are up-to-date"))
(doom-initialize-autoloads doom-package-autoload-file))
(let (case-fold-search)
(doom-delete-autoloads-file 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! (green "✓ Cached package state"))
(print! (success "Cached package state"))
;; Concatenate the autoloads of all installed packages.
(doom--generate-package-autoloads)
(print! (green "✓ Package autoloads included")))
(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! (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! (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))

View file

@ -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))
(let (target-dirs)
(dolist (module (delete-dups modules))
(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)))
(":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 targets))
(push module target-dirs))
((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
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,47 +86,50 @@ 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)
"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.
(unless (and (doom-initialize-autoloads doom-autoload-file)
(doom-initialize-autoloads doom-package-autoload-file))
(doom-reload-autoloads))
(doom-initialize)
(doom-initialize-modules 'force))
(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.
(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
(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
(message "Couldn't find any valid targets")
(message "No targets to %scompile" (if recompile-p "re" "")))
(cl-return-from 'byte-compile))
(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 ((use-package-defaults use-package-defaults)
(let ((total-ok 0)
(total-fail 0)
(total-noop 0)
(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.
@ -134,57 +141,58 @@ If RECOMPILE-P is non-nil, only recompile out-of-date files."
(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)
(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))))
(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)
(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)
((null result)
(print! (red "✕ Failed to compile %s") short-name)
(`nil
(print! (error "Failed to compile %s") (relpath target))
total-fail)
(t
(print! (green "✓ Compiled %s") short-name)
(_
(print! (success "Compiled %s") (relpath target))
(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)"))
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 target-files) total-noop)
total-ok (- (length targets) total-noop)
total-noop)
(or (= total-fail 0)
(error "Failed to compile some files")))
t)
((debug error)
(print! (red "\nThere were breaking errors.\n\n%s")
(print! (error "\nThere were breaking errors.\n\n%s")
"Reverting changes...")
(signal 'doom-error (list 'byte-compile e))))))))
(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.'"
(print! (start "Cleaning .elc files"))
(print-group!
(cl-loop with default-directory = doom-emacs-dir
with success = nil
for path
in (append (doom-files-in doom-emacs-dir :match "\\.elc$" :depth 0 :sort nil)
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))
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")))))
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"))))))

View file

@ -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)

View file

@ -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,13 +103,18 @@ 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
(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
doom-env-executable)
(filename doom-env-executable))
(save-excursion
(insert (shell-command-to-string doom-env-executable)))
(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
@ -125,11 +122,12 @@ default, on Linux, this is '$SHELL -ic /usr/bin/env'. Variables in
(when (re-search-forward "^\\([^= ]+\\)=" nil t)
(line-beginning-position)))
(point-max)))
(var (match-string 1))
(value (buffer-substring-no-properties (point) (1- valend))))
(var (match-string 1)))
(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")))))))
(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)))))

View file

@ -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
;; "<Not implemented yet>"
;; (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)
((not (or auto-accept-p
(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 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)))
(format! "%s\n\nThere %s %d package%s available to update. Update them?"
(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!"))
(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))))))
((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-autoremove (&optional auto-accept-p)
"Auto-removes orphaned packages.
(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-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
(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 "%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)))))
(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)))))))

View file

@ -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)))
;;

View file

@ -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,58 +22,49 @@ 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."
--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'
(let ((short-private-dir (abbreviate-file-name 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"
(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 (expand-file-name "init.example.el" doom-emacs-dir))))
(cons "config.el"
(insert-file-contents (doom-dir "init.example.el"))))
("config.el" .
(lambda ()
(insert (format ";;; %sconfig.el -*- lexical-binding: t; -*-\n\n"
short-private-dir)
";; Place your private configuration here\n")))
(cons "packages.el"
(insert! ";;; %sconfig.el -*- lexical-binding: t; -*-\n\n"
";; Place your private configuration here\n"
((relpath doom-private-dir)))))
("packages.el" .
(lambda ()
(insert (format ";; -*- no-byte-compile: t; -*-\n;;; %spackages.el\n\n"
short-private-dir)
(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")))))
(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!")))))))
";; (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)
@ -76,14 +72,14 @@ regenerates the autoloads file."
;; Ask if Emacs.app should be patched
(if (member "--no-env" args)
(print! (yellow "Not generating envvars file, as requested"))
(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! (warn "- Not installing plugins, as requested"))
(print! "Installing plugins")
(doom-packages-install doom-auto-accept))
@ -91,7 +87,7 @@ regenerates the autoloads file."
(doom-reload-autoloads nil 'force-p)
(if (member "--no-fonts" args)
(print! (yellow "Not installing fonts, as requested"))
(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)
@ -99,7 +95,7 @@ regenerates the autoloads file."
(IS-LINUX 'x))))
(all-the-icons-install-fonts 'yes))))
(print! (bold (green "\nFinished! Doom is ready to go!\n")))
(print! (success "\nFinished! Doom is ready to go!\n"))
(with-temp-buffer
(doom-template-insert "QUICKSTART_INTRO")
(print! (buffer-string))))
(print! (buffer-string)))))

View file

@ -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)
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)))
(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)))))
(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)

View file

@ -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")))

View file

@ -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))
(save-excursion
(insert desc)
(goto-char (point-min))
(while (re-search-forward "\n\n[^ \n]" nil t)
(fill-paragraph)))
(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)
(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
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"
(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 desc t)))))))
(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 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

View file

@ -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)))
(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)
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)
doom-modules))
(run-hook-wrapped 'doom-init-modules-hook #'doom-try-run-hook)
(load! "config" doom-private-dir t))))
(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))))
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,10 +340,8 @@ 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 (keywordp (car modules))
(setq modules (eval modules t)))
(unless doom-modules
(setq doom-modules
(make-hash-table :test 'equal
@ -381,7 +376,7 @@ to least)."
(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)))))))))
(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))

View file

@ -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'
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-packages (doom-package-list)))))
(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

View file

@ -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)
;; 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")))
(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)))
;;

View file

@ -40,8 +40,5 @@
(package! which-key)
(package! hydra)
;; core-packages.el
(package! gnu-elpa-keyring-update)
;; autoload/debug.el
(package! esup)

View file

@ -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.

View file

@ -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)

View file

@ -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

View file

@ -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"))

View file

@ -5,6 +5,6 @@
(package! auto-yasnippet)
(package! doom-snippets
:recipe (:fetcher github
:recipe (:host github
:repo "hlissner/doom-snippets"
:files ("*.el" "snippets")))

View file

@ -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)

View file

@ -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")))

View file

@ -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 () "")

View file

@ -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-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))

View file

@ -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))

View file

@ -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))

View file

@ -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: ")

View file

@ -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))