Rewrite package management to be less hackish (untested)

This commit is contained in:
Henrik Lissner 2017-02-03 07:58:16 -05:00
parent 74aa0ab6a7
commit f2a31e9d87
7 changed files with 535 additions and 266 deletions

View file

@ -9,7 +9,10 @@ update: init.el
@$(EMACS) --batch -l core/core.el -f 'doom/packages-update' @$(EMACS) --batch -l core/core.el -f 'doom/packages-update'
clean: init.el clean: init.el
@$(EMACS) --batch -l core/core.el -f 'doom/packages-clean' @$(EMACS) --batch -l core/core.el -f 'doom/packages-autoremove'
autoloads: init.el
@$(EMACS) --batch -l core/core.el -f 'doom/refresh-autoloads'
compile: init.el clean-elc compile: init.el clean-elc
@$(EMACS) --batch -l core/core.el -f 'doom/byte-compile' @$(EMACS) --batch -l core/core.el -f 'doom/byte-compile'
@ -17,9 +20,6 @@ compile: init.el clean-elc
compile-all: init.el clean-elc compile-all: init.el clean-elc
@$(EMACS) --batch -l core/core.el --eval '(doom/byte-compile t)' @$(EMACS) --batch -l core/core.el --eval '(doom/byte-compile t)'
autoloads: init.el
@$(EMACS) --batch -l core/core.el -f 'doom/refresh-autoloads'
clean-cache: clean-cache:
@$(EMACS) --batch -l core/core.el --eval '(delete-directory doom-cache-dir t)' @$(EMACS) --batch -l core/core.el --eval '(delete-directory doom-cache-dir t)'

View file

@ -1,151 +1,272 @@
;;; packages.el ;;; packages.el
(defvar doom-packages-last-refresh nil
"A timestamp indicating the last time `package-refresh-contents' was run.")
;;;###autoload ;;;###autoload
(defun doom-package-outdated-p (package) (defun doom-refresh-packages ()
"Determine whether PACKAGE (a symbol) is outdated or not. If outdated, returns "Refresh ELPA packages."
a cons cell, whose car is the current version string of PACKAGE (a symbol), and (when (or (not doom-packages-last-refresh)
whose cdr is the latest version of the package. Be sure to run (> (nth 1 (time-since doom-packages-last-refresh)) 3600))
`package-refresh-contents' beforehand, or the return value could be out of (doom-initialize)
date." (package-refresh-contents)
(unless package-selected-packages (setq doom-packages-last-refresh (current-time))))
(doom-initialize))
(when (and (memq package package-selected-packages) ;;;###autoload
(package-installed-p package) (defun doom-package-elpa-p (name)
(quelpa-setup-p)) "Returns non-nil if NAME was a package installed with elpa."
(let* ((pkg-recipe (cdr (assq 'quelpa quelpa-cache))) (doom-initialize)
(cur-desc (cadr (or (assq package package-alist) (and (assq name package-alist)
(assq package package--builtins)))) (not (doom-package-quelpa-p name))))
(cur-version (package-desc-version cur-desc))
(inhibit-message t) ;;;###autoload
new-version) (defun doom-package-quelpa-p (name)
(setq new-version "Returns non-nil if NAME was a package installed with quelpa."
(if pkg-recipe (unless (quelpa-setup-p)
(let ((ver (quelpa-checkout (error "Could not initialize quelpa"))
pkg-recipe (assq name quelpa-cache))
(f-expand (symbol-name package) quelpa-build-dir))))
(or (and ver (version-to-list ver)) cur-version)) ;;;###autoload
(package-desc-version (cadr (assq package package-archive-contents))))) (defun doom-package-outdated-p (name)
(unless (version-list-<= new-version cur-version) "Determine whether NAME (a symbol) is outdated or not. If outdated, returns a
(cons cur-version new-version))))) list, whose car is NAME, and cdr the current version list and latest version
list of the package."
(doom-refresh-packages)
(package-read-all-archive-contents)
(when (assq name package-alist)
(let* ((old-version
(package-desc-version (cadr (or (assq name package-alist)
(assq name package--builtins)))))
(new-version
(cond ((doom-package-quelpa-p name)
(let ((recipe (assq name quelpa-cache))
(dir (f-expand (symbol-name name) quelpa-build-dir))
(inhibit-message t))
(or (quelpa-checkout recipe dir)
old-version)))
((doom-package-elpa-p name)
(package-desc-version (cadr (assq name package-archive-contents)))))))
(unless (version-list-<= new-version old-version)
(cons name old-version new-version)))))
;;;###autoload
(defun doom-get-packages (&optional backend)
"Retrieves a list of explicitly installed packages (i.e. non-dependencies).
Each element is a cons cell, whose car is the package symbol and whose cdr is
the quelpa recipe (if any).
BACKEND can be 'quelpa or 'elpa, and will instruct this function to return only
the packages relevant to that backend."
(doom-initialize)
(unless (quelpa-setup-p)
(error "Could not initialize quelpa"))
(--map (cons it (assq it quelpa-cache))
(-intersection (package--find-non-dependencies)
(append (mapcar 'car doom-packages) doom-protected-packages))))
;;;###autoload
(defun doom-get-outdated-packages ()
"Return a list of packages that are out of date. Each element is a sublist,
containing (list package-symbol current-version-string new-version-string). Can
be fed to `doom/packages-update'."
(-non-nil (--map (doom-package-outdated-p (car it)) (doom-get-packages))))
;;;###autoload
(defun doom-get-orphaned-packages ()
"Return a list of packages that are no longer needed or depended on. Can be
fed to `doom/packages-delete'."
(doom-initialize)
(-difference (package--removable-packages)
doom-protected-packages))
;;;###autoload
(defun doom-get-packages-to-install ()
"Return a list of packages that aren't installed, but need to be. Used by
`doom/packages-install'."
(doom-refresh-self)
(--remove (assq (car it) package-alist)
(append doom-packages (-map 'list doom-protected-packages))))
;;
;; Main functions
;;
(defun doom-install-package (name &optional recipe)
"Installs package NAME with optional quelpa RECIPE (see `quelpa-recipe' for an
example; the package name can be omitted)."
(doom-refresh-packages)
(when (package-installed-p name)
(error "%s is already installed" name))
(cond (recipe (quelpa (plist-get plist :recipe)))
(t (package-install name)))
(add-to-list 'doom-packages (cons name recipe))
(package-installed-p name))
(defun doom-update-package (name)
"Updates package NAME if it is out of date, using quelpa or package.el as
appropriate."
(doom-refresh-packages)
(unless (package-installed-p name)
(error "%s isn't installed" name))
(when (doom-package-outdated-p name)
(let (quelpa-modified-p)
(cond ((doom-package-quelpa-p name)
(let ((quelpa-upgrade-p t))
(quelpa it)
(setq quelpa-modified-p t)))
(t
(let ((desc (cadr (assq name package-alist)))
(archive (cadr (assq name package-archive-contents))))
(package-install-from-archive archive)
(delete-directory (package-desc-dir desc) t))
(package-install name))))
(when quelpa-modified-p
(quelpa-save-cache))
(version-list-=
(package-desc-version (cadr (assq name package-alist)))
(package-desc-version (cadr (assq name package-archive-contents))))))
(defun doom-delete-package (name)
"Uninstalls package NAME if it exists, and clears it from `quelpa-cache'."
(doom-initialize)
(unless (package-installed-p name)
(error "%s isn't installed" name))
(let ((desc (cadr (assq package package-alist))))
(package-delete desc))
(when (and (quelpa-setup-p)
(assq name quelpa-cache))
(setq quelpa-cache (delq name quelpa-cache))
(let ((path (f-expand (symbol-name name) quelpa-build-dir)))
(when (f-exists-p path)
(delete-directory path t)))
(quelpa-save-cache))
(not (package-installed-p name)))
;;
;; Interactive commands
;;
;;;###autoload ;;;###autoload
(defun doom/packages-install () (defun doom/packages-install ()
"Install missing packages."
(interactive) (interactive)
(let ((pkg-n (doom-reload-packages :install))) (let ((packages (doom-get-packages-to-install)))
(if (= pkg-n 0) (cond ((not packages)
(message "Nothing to install") (message "No packages to install!"))
(message "\nInstalled %s packages:\n%s" pkg-n
(mapconcat (lambda (pkg) (concat "+ " (symbol-name pkg))) ((not (y-or-n-p
doom-installed-packages "\n"))))) (format "%s packages will be installed:\n%s\n\nProceed?"
(length packages)
(mapconcat (lambda (pkg) (format "+ %s (%s)"
(symbol-name (car pkg))
(cond ((cdr pkg) "QUELPA")
(t "ELPA"))))
packages "\n"))))
(message "Aborted!"))
(t
(doom-message "Installing %s packages" (length packages))
(dolist (pkg packages)
(condition-case ex
(doom-message "%s %s (%s)"
(let ((plist (cdr pkg)))
(if (doom-install-package (car pkg) (cdr pkg))
"Installed"
"Failed to install"))
pkg
(cond ((cdr pkg) "QUELPA")
(t "ELPA")))
('error
(doom-message "Error installing %s: %s" (car pkg) ex))))
(doom-message "Finished!")))))
;;;###autoload ;;;###autoload
(defun doom/packages-update () (defun doom/packages-update ()
"Update outdated packages. This includes quelpa-installed packages and ELPA
packages. This will delete old versions of packages as well."
(interactive) (interactive)
(message "Refreshing packages...") (let ((packages (doom-get-outdated-packages)))
(doom-initialize t) (cond ((not packages)
(if (not package-alist) (message "Everything is up-to-date"))
(message "No packages are installed")
(require 'quelpa)
(when (quelpa-setup-p)
(setq quelpa-cache (--filter (package-installed-p (car it)) quelpa-cache)))
(let* ((err 0)
(quelpa-packages (-map 'car quelpa-cache))
(elpa-packages (-difference (package--find-non-dependencies) quelpa-packages))
quelpa-modified-p
outdated-packages)
(message "ELPA\n%s\n\nQUELPA\n%s" elpa-packages quelpa-packages)
(dolist (pkg (append quelpa-packages elpa-packages))
(awhen (doom-package-outdated-p pkg)
(push (list pkg it) outdated-packages)))
(message "\nOUTDATED\n%s" outdated-packages)
;; (cond ((not outdated-packages)
;; (message "Everything is up-to-date"))
;; ((not (y-or-n-p ((not (y-or-n-p
;; (format "%s packages will be updated:\n%s\n\nProceed?" (format "%s packages will be updated:\n%s\n\nProceed?"
;; (length outdated-packages) (length packages)
;; (mapconcat (lambda (pkg) (format "%s: %s -> %s" (mapconcat (lambda (pkg) (format "%s: %s -> %s"
;; (car pkg) (car pkg)
;; (car (cdr pkg)) (car (cdr pkg))
;; (cdr (cdr pkg)))) (cdr (cdr pkg))))
;; (--sort (string-lessp (symbol-name (car it)) (--sort (string-lessp (symbol-name (car it))
;; (symbol-name (car other))) (symbol-name (car other)))
;; outdated-packages) ", ")))) outdated-packages) ", "))))
;; (message "Aborted")) (message "Aborted!"))
;; (t (t
;; (dolist (pkg outdated-packages) (dolist (pkg packages)
;; (condition-case ex (condition-case ex
;; (cond ((assq pkg quelpa-outdated-packages) (doom-message "%s %s"
;; (let ((inhibit-message t)) (if (doom-update-package pkg)
;; (quelpa package) "Updated"
;; (setq quelpa-modified-p t))) "Failed to update")
;; ((memq pkg elpa-outdated-packages) pkg)
;; (let ((desc (cadr (assq pkg package-alist))) ('error
;; (archive (cadr (assoc pkg package-archive-contents)))) (doom-message "Error installing %s: %s" pkg ex))))
;; (package-install-from-archive archive)
;; (delete-directory (package-desc-dir desc) t))) (doom-message "Finished!")))))
;; (t (error "Not a valid package")))
;; ('error
;; (setq err (1+ err))
;; (message "ERROR (%s): %s" pkg ex))))))
;; (when quelpa-modified-p
;; (quelpa-save-cache))
;; (if (> err 0)
;; (message "Done, but with %s errors" err)
;; (message "Done"))
)))
;;;###autoload ;;;###autoload
(defun doom/packages-clean () (defun doom/packages-autoremove ()
"Delete packages that are no longer used or depended on."
(interactive) (interactive)
(doom-reload-packages) (let ((packages (doom-get-orphaned-packages)))
(let* ((package-selected-packages (-intersection (package--find-non-dependencies) (cond ((not packages)
(append doom-packages doom-protected-packages))) (message "No unused packages to remove"))
(packages-to-delete (-difference (package--removable-packages) doom-protected-packages))
quelpa-modified-p)
(cond ((not package-selected-packages)
(message "No packages installed!"))
((not packages-to-delete)
(message "No unused packages to remove."))
((not (y-or-n-p ((not (y-or-n-p
(format "%s packages will be deleted:\n%s\n\nProceed?" (format "%s packages will be deleted:\n%s\n\nProceed?"
(length packages-to-delete) (length packages)
(mapconcat 'symbol-name (-sort 'string-lessp packages-to-delete) ", ")))) (mapconcat 'symbol-name (-sort 'string-lessp packages) ", "))))
(message "Aborted.")) (message "Aborted!"))
(t (t
(require 'quelpa) (dolist (pkg packages)
(quelpa-setup-p) (condition-case ex
(dolist (p packages-to-delete) (doom-message "%s %s"
(package-delete (cadr (assq p package-alist)) t) (if (doom-delete-package pkg)
(when (and quelpa-cache (assq p quelpa-cache)) "Deleted"
(setq quelpa-cache (assq-delete-all p quelpa-cache) "Failed to delete")
quelpa-modified-p t))) pkg)
(when quelpa-modified-p ('error
(quelpa-save-cache)))))) (doom-message "Error deleting %s: %s" pkg ex))))
(doom-message "Finished!")))))
;;;###autoload ;;;###autoload
(defun doom/packages-reload () (defalias 'doom/package-install 'package-install)
"Reload `load-path' by scanning all packages. Run this if you ran make update
or make clean outside of Emacs."
(interactive)
(doom-initialize t)
(message "Reloaded %s packages" (length package-alist)))
;;;###autoload ;;;###autoload
(defun doom/packages-delete (&optional package) (defun doom/package-delete (&optional package)
"Attempt to delete PACKAGE. Wraps around `package-delete'." (interactive
(interactive) (list (completing-read "Delete package: " (doom-get-packages))))
(doom-reload-packages) (if (package-installed-p package)
(let* ((pkg (or package (completing-read "Delete package: " doom-packages nil t))) (message "%s %s"
(pkg-desc (cdr (assq pkg package-alist)))) (if (doom-delete-package package)
(unless pkg-desc "Deleted"
(error "Couldn't find the package %s" package)) "Failed to delete")
(package-delete pkg-desc))) pkg)
(message "%s isn't installed" package)))
;;;###autoload
(defun doom/package-update (&optional package)
(interactive
(list (completing-read "Update package: " (doom-get-packages))))
(if (doom-package-outdated-p package)
(message "%s %s"
(if (doom-update-package package)
"Updated"
"Failed to update")
pkg)
(message "%s is up-to-date" package)))

View file

@ -177,7 +177,7 @@
(package! pcre2el :commands rxt-quote-pcre) (package! pcre2el :commands rxt-quote-pcre)
(package! rotate-text (package! rotate-text
:quelpa (:fetcher github :repo "debug-ito/rotate-text.el") :recipe (:fetcher github :repo "debug-ito/rotate-text.el")
:commands (rotate-text rotate-text-backward) :commands (rotate-text rotate-text-backward)
:config (push '("true" "false") rotate-text-words)) :config (push '("true" "false") rotate-text-words))

View file

@ -85,5 +85,158 @@ Examples:
(t (user-error "associate! invalid rules for mode [%s] (in %s) (match %s) (files %s)" (t (user-error "associate! invalid rules for mode [%s] (in %s) (match %s) (files %s)"
mode in match files))))) mode in match files)))))
;; Register keywords for proper indentation (see `map!')
(put ':prefix 'lisp-indent-function 'defun)
(put ':map 'lisp-indent-function 'defun)
(put ':map* 'lisp-indent-function 'defun)
(put ':after 'lisp-indent-function 'defun)
(put ':when 'lisp-indent-function 'defun)
(put ':unless 'lisp-indent-function 'defun)
(put ':leader 'lisp-indent-function 'defun)
(put ':localleader 'lisp-indent-function 'defun)
;;;###autoload
(defmacro map! (&rest rest)
"A nightmare of a key-binding macro that will use `evil-define-key*',
`define-key', `local-set-key' and `global-set-key' depending on context and
plist key flags. It was designed to make binding multiple keys more concise,
like in vim.
If evil isn't loaded, it will ignore evil-specific bindings.
Yes, it tries to do too much. Yes, I only did it to make the \"frontend\" config
that little bit more concise. Yes, I could simply have used the above functions.
But it takes a little insanity to custom write your own emacs.d, so what else
were you expecting?
States
:n normal
:v visual
:i insert
:e emacs
:o operator
:m motion
:r replace
:L local
These can be combined (order doesn't matter), e.g. :nvi will apply to
normal, visual and insert mode. The state resets after the following
key=>def pair.
Capitalize the state flag to make it a local binding.
If omitted, the keybind will be defined globally.
Flags
:unset [KEY] ; unset key
(:map [KEYMAP] [...]) ; inner keybinds are applied to KEYMAP
(:prefix [PREFIX] [...]) ; assign prefix to all inner keybindings
(:after [FEATURE] [...]) ; apply keybinds when [FEATURE] loads
Conditional keybinds
(:when [CONDITION] [...])
(:unless [CONDITION] [...])
Example
(map! :map magit-mode-map
:m \"C-r\" 'do-something ; assign C-r in motion state
:nv \"q\" 'magit-mode-quit-window ; assign to 'q' in normal and visual states
\"C-x C-r\" 'a-global-keybind
(:when IS-MAC
:n \"M-s\" 'some-fn
:i \"M-o\" (lambda (interactive) (message \"Hi\"))))"
(let ((keymaps (if (boundp 'keymaps) keymaps))
(prefix (if (boundp 'prefix) prefix))
(state-map '(("n" . normal)
("v" . visual)
("i" . insert)
("e" . emacs)
("o" . operator)
("m" . motion)
("r" . replace)))
local key def states forms)
(while rest
(setq key (pop rest))
(cond
;; it's a sub expr
((listp key)
(push (macroexpand `(map! ,@key)) forms))
;; it's a flag
((keywordp key)
(when (cond ((eq key :leader)
(push (or +evil-leader ",") rest))
((eq key :localleader)
(push (or +evil-localleader "\\") rest)))
(setq key :prefix))
(pcase key
(:prefix (setq prefix (concat prefix (kbd (pop rest)))))
(:map (setq keymaps (-list (pop rest))))
(:unset `(,(macroexpand `(map! ,(kbd (pop rest))))))
(:after (prog1 `((after! ,(pop rest) ,(macroexpand `(map! ,@rest)))) (setq rest '())))
(:when (prog1 `((if ,(pop rest) ,(macroexpand `(map! ,@rest)))) (setq rest '())))
(:unless (prog1 `((if (not ,(pop rest)) ,(macroexpand `(map! ,@rest)))) (setq rest '())))
(otherwise ; might be a state prefix
(mapc (lambda (letter)
(cond ((assoc letter state-map)
(push (cdr (assoc letter state-map)) states))
((string= letter "L")
(setq local t))
(t (user-error "Invalid mode prefix %s in key %s" letter key))))
(split-string (substring (symbol-name key) 1) "" t))
(unless states
(user-error "Unrecognized keyword %s" key))
(when (assoc "L" states)
(cond ((= (length states) 1)
(user-error "local keybinding for %s must accompany another state" key))
((> (length keymaps) 0)
(user-error "local keybinding for %s cannot accompany a keymap" key)))))))
;; It's a key-def pair
((or (stringp key)
(characterp key)
(vectorp key))
(unwind-protect
(catch 'skip
(when (stringp key)
(setq key (kbd key)))
(when prefix
(setq key (if (vectorp key) (vconcat prefix key) (concat prefix key))))
(unless (> (length rest) 0)
(user-error "Map has no definition for %s" key))
(setq def (pop rest))
(push
(cond ((and keymaps states)
(throw 'skip 'evil)
(macroexp-progn
(mapcar (lambda (keymap) `(evil-define-key* ',states ,keymap ,key ,def))
keymaps)))
(keymaps
(macroexp-progn
(mapcar (lambda (keymap) `(define-key ,keymap ,key ,def))
keymaps)))
(states
(throw 'skip 'evil)
(macroexp-progn
(mapcar (lambda (state)
`(define-key
(evil-state-property ',state ,(if local :local-keymap :keymap) t)
,key ,def))
states)))
(t `(,(if local 'local-set-key 'global-set-key)
,key ,def)))
forms))
(setq states '()
local nil)))
(t (user-error "Invalid key %s" key))))
(macroexp-progn (reverse forms))))
(when (or noninteractive doom-dont-load-p)
(defmacro add-hook! (&rest _))
(defmacro associate! (&rest _))
(defmacro map! (&rest _)))
(provide 'core-lib) (provide 'core-lib)
;;; core-lib.el ends here ;;; core-lib.el ends here

View file

@ -25,26 +25,24 @@ submodule symbol, e.g. 'evil.")
(defvar doom-packages nil (defvar doom-packages nil
"A list of enabled packages.") "A list of enabled packages.")
(defvar doom-protected-packages '(quelpa-use-package f s dash) (defvar doom-protected-packages '(quelpa use-package dash f s)
"A list of packages that shouldn't be deleted.") "A list of packages that shouldn't be deleted.")
(defvar doom-installed-packages nil (defvar doom-init-p nil
"A list of packages that were installed during the current session.")
(defvar doom--init nil
"Non-nil if doom's package system has been initialized or not. It may not be "Non-nil if doom's package system has been initialized or not. It may not be
if you have byte-compiled your configuration (as intended).") if you have byte-compiled your configuration (as intended).")
(defvar doom--auto-install-p nil (defvar doom-dont-load-p nil
"If non-nil, install missing packages. Otherwise, strip :ensure and :quelpa "If non-nil, run DOOM emacs in declarative mode, meaning: don't actually load
from `package!' calls.") anything, just track what should be loaded. Useful for scanning packages and
loaded modules.")
(defvar doom--prefer-el-p (or noninteractive doom--auto-install-p) (defvar doom-prefer-el-p noninteractive
"If non-nil, load uncompiled .el config files.") "If non-nil, load uncompiled .el config files.")
(defvar doom--load-path (append (list doom-core-dir (defvar doom--base-load-path (append (list doom-core-dir
doom-modules-dir) doom-modules-dir)
load-path) load-path)
"A backup of `load-path', used as a bare-bones foundation for "A backup of `load-path', used as a bare-bones foundation for
`doom/packages-reload' or `doom-initialize'.") `doom/packages-reload' or `doom-initialize'.")
@ -86,7 +84,7 @@ byte-compilation."
(error "No namespace specified on `doom!' for %s" p)) (error "No namespace specified on `doom!' for %s" p))
(t (t
(setq doom-enabled-modules (append doom-enabled-modules (list (cons mode p)))))))) (setq doom-enabled-modules (append doom-enabled-modules (list (cons mode p))))))))
`(unless noninteractive `(unless doom-dont-load-p
(let (file-name-handler-alist) (let (file-name-handler-alist)
,@(mapcar (lambda (pkg) `(load! ,(car pkg) ,(cdr pkg))) ,@(mapcar (lambda (pkg) `(load! ,(car pkg) ,(cdr pkg)))
doom-enabled-modules) doom-enabled-modules)
@ -99,56 +97,49 @@ byte-compilation."
;; Prevent any auto-displayed text + benchmarking ;; Prevent any auto-displayed text + benchmarking
(advice-add 'display-startup-echo-area-message :override 'ignore) (advice-add 'display-startup-echo-area-message :override 'ignore)
(message "Loaded %s packages in %s" (message "Loaded %s packages in %s"
(- (length load-path) (length doom--load-path)) (- (length load-path) (length doom--base-load-path))
(emacs-init-time))))) (emacs-init-time)))))
(defun doom-initialize (&optional force-p) (defun doom-initialize (&optional force-p)
"Initialize installed packages (using package.el) and ensure the core packages "Initialize installed packages (using package.el) and ensure the core packages
are installed. If you byte compile core/core.el, calls to `package.el' are are installed. If you byte compile core/core.el, calls to `package.el' are
avoided to speed up startup." avoided to speed up startup."
(unless (or doom--init force-p) (unless (or doom-init-p force-p)
(setq load-path doom--load-path (setq load-path doom--base-load-path
package-activated-list nil) package-activated-list nil)
(package-initialize) (package-initialize)
(unless (and (file-exists-p doom-packages-dir) (unless (and (file-exists-p doom-packages-dir)
(require 'quelpa-use-package nil t)) (require 'use-package nil t)
(require 'quelpa nil t))
(package-refresh-contents) (package-refresh-contents)
;; Ensure core packages are installed ;; Ensure core packages are installed
(mapc 'package-install '(quelpa-use-package dash f s))) (condition-case ex
(unless (require 'quelpa-use-package nil t) (mapc (lambda (pkg)
(delete-directory doom-packages-dir t) (package-install pkg)
(error "There was an error initializing DOOM. Try running it again")) (unless (package-installed-p pkg)
(quelpa-use-package-activate-advice) (error "Couldn't install %s" pkg)))
;; Move :ensure to after conditional properties doom-protected-packages)
(delq :ensure use-package-keywords) ('error
(push :ensure (cdr (memq :unless use-package-keywords))) (delete-directory doom-packages-dir t)
(setq doom--init t))) (error "There was an error initializing DOOM. Try running it again"))))
(defun doom-reload-modules () (require 'quelpa)
"Reload `doom-modules'." (require 'use-package)
(setq doom-modules nil) (advice-add 'package-delete :around 'doom*package-delete)
(let ((doom--prefer-el-p t) ;; Remove package management keywords, I'll deal with the myself
(noninteractive t)) (mapc (lambda (keyword) (setq use-package-keywords (delq keyword use-package-keywords)))
(load (concat doom-emacs-dir "init.el") nil :nomessage :nosuffix))) '(:ensure :pin))
(setq doom-init-p t)))
(defun doom-reload-packages (&optional install-p) (defun doom-reload ()
"Reload `doom-packages'. Returns the difference in packages before and after." "Rereads the Emacs config, reloading `doom-packages' and
`doom-enabled-modules'."
(doom-initialize) (doom-initialize)
(doom-reload-modules) (let ((doom-prefer-el-p t)
(let ((before-packages-n (length package-alist)) (doom-dont-load-p t))
(doom--auto-install-p (and install-p t)) (setq doom-modules nil
(doom--prefer-el-p t) doom-packages nil)
(after-packages-n 0) (load (concat doom-emacs-dir "init.el") :noerror (not doom-debug-mode) :nosuffix)))
noninteractive)
(when doom--auto-install-p
(package-refresh-contents))
(load (f-expand "core.el" doom-core-dir) nil (not doom-debug-mode) :nosuffix)
(mapc (lambda (pkg)
(let ((path (f-expand "packages.el" (doom-module-path (car pkg) (cdr pkg)))))
(when (f-exists-p path)
(load path nil (not doom-debug-mode) :nosuffix))))
doom-enabled-modules)
(- (length package-alist) before-packages-n)))
;; ;;
@ -157,12 +148,14 @@ avoided to speed up startup."
(defvar __DIR__ nil "The directory of the currently loaded file (set by `load!')") (defvar __DIR__ nil "The directory of the currently loaded file (set by `load!')")
(defvar __FILE__ nil "The full path of the currently loaded file (set by `load!')") (defvar __FILE__ nil "The full path of the currently loaded file (set by `load!')")
(defvar __PACKAGE__ nil "The name of the current package.")
(defmacro use-package! (name &rest plist) (defmacro use-package! (name &rest plist)
"A `use-package' wrapper, to adhere to the naming conventions of DOOM emacs "A `use-package' wrapper. It exists so configs can adhere to the naming
and let-bind `package-name' for the containing forms. Note that packages are conventions of DOOM emacs, as well as let-bind `__PACKAGE__' for the containing
deferred by default." forms. This is helpful for macros like `set!' and `add-hook!'. Note that
`(let ((package-name ',name)) packages are deferred by default."
`(let ((__PACKAGE__ ',name))
(use-package ,name ,@plist))) (use-package ,name ,@plist)))
(defmacro package! (name &rest plist) (defmacro package! (name &rest plist)
@ -173,25 +166,23 @@ which is the case if you've byte-compiled DOOM Emacs.
Note that packages are deferred by default." Note that packages are deferred by default."
(declare (indent defun)) (declare (indent defun))
(let ((use-package-always-ensure doom--auto-install-p) (let ((recipe (cadr (memq :recipe plist)))
(recipe (plist-get plist :quelpa))) (pin (cadr (memq :pin plist))))
;; prepend NAME to quelpa recipe, if none is specified, to avoid local (when recipe
;; MELPA lookups by quelpa. (when (= 0 (mod (length recipe) 2))
(when (and recipe (= 0 (mod (length recipe) 2))) (push name recipe))
(push name recipe) (setq plist (use-package-plist-delete plist :recipe)))
(setq plist (plist-put plist :quelpa recipe))) (when pin
(if doom--auto-install-p (add-to-list 'package-pinned-packages (cons package (plist-get plist :pin)))
(unless (package-installed-p name) (setq plist (use-package-plist-delete plist :pin)))
(pushnew name doom-installed-packages)) (pushnew (cons name recipe) doom-packages :key 'car)
(setq plist (use-package-plist-delete plist :ensure)) (unless doom-dont-load-p
(setq plist (use-package-plist-delete plist :quelpa))) `(use-package! ,name ,@plist))))
(pushnew name doom-packages)
(macroexpand `(use-package ,name ,@plist))))
(defmacro require! (feature) (defmacro require! (feature)
"Like `require', but prefers uncompiled files when `doom--prefer-el-p' is "Like `require', but prefers uncompiled files when `doom-prefer-el-p' is
non-nil or in a noninteractive session." non-nil or in a noninteractive session."
(let ((prefer-el-p (or doom--prefer-el-p noninteractive))) (let ((prefer-el-p (or doom-prefer-el-p noninteractive)))
`(require ',feature `(require ',feature
,(locate-file (concat (symbol-name feature) (if prefer-el-p ".el")) ,(locate-file (concat (symbol-name feature) (if prefer-el-p ".el"))
load-path)))) load-path))))
@ -199,7 +190,7 @@ non-nil or in a noninteractive session."
(defmacro load! (file-or-module-sym &optional submodule file) (defmacro load! (file-or-module-sym &optional submodule file)
"Load a module from `doom-modules-dir'. Plays the same role as "Load a module from `doom-modules-dir'. Plays the same role as
`load-relative', but is specific to DOOM emacs modules and submodules. If `load-relative', but is specific to DOOM emacs modules and submodules. If
`doom--prefer-el-p' is non-nil or in an noninteractive session, prefer the `doom-prefer-el-p' is non-nil or in an noninteractive session, prefer the
un-compiled elisp file. un-compiled elisp file.
Examples: Examples:
@ -210,30 +201,31 @@ Examples:
(load! +local-module) (load! +local-module)
Loads +local-module.el relative to `__DIR__' or `doom-core-dir'." Loads +local-module.el relative to `__DIR__' or `doom-core-dir'."
(let ((prefer-el-p (or doom--prefer-el-p noninteractive)) (let ((prefer-el-p (or doom-prefer-el-p noninteractive))
path file) path file)
(cond ((null submodule) (cond ((null submodule)
(setq path __DIR__ (setq path __DIR__
file (concat (if (symbolp file-or-module-sym) file (concat (symbol-name file-or-module-sym) ".el")))
(symbol-name file-or-module-sym)
file-or-module-sym)
".el")))
(t (t
(pushnew (cons file-or-module-sym submodule)
doom-enabled-modules
:test (lambda (x y) (and (eq (car x) (car y))
(eq (cdr x) (cdr y)))))
(setq path (doom-module-path file-or-module-sym submodule) (setq path (doom-module-path file-or-module-sym submodule)
file (or file "config.el")))) file (or file "config.el"))))
(setq path (f-slash path) (setq path (f-slash path)
file (concat path file)) file (concat path file))
`(let ((__FILE__ ,file) `(let ((__FILE__ ,file)
(__DIR__ ,path)) (__DIR__ ,path))
(load ,(if doom--prefer-el-p file (f-no-ext file)) (load ,(if doom-prefer-el-p file (f-no-ext file))
nil (not doom-debug-mode) ,doom--prefer-el-p)))) nil (not doom-debug-mode) ,doom-prefer-el-p))))
(defun doom-module-path (module submodule &optional file) (defun doom-module-path (module submodule &optional file)
"Get the full path to a module: e.g. :lang emacs-lisp maps to "Get the full path to a module: e.g. :lang emacs-lisp maps to
~/.emacs.d/modules/lang/emacs-lisp/. Will append FILE if non-nil." ~/.emacs.d/modules/lang/emacs-lisp/. Will append FILE if non-nil."
(setq module (setq module
(cond ((keywordp module) (substring (symbol-name module) 1)) (cond ((keywordp module) (substring (symbol-name module) 1))
((symbolp module) (symbol-name module)) ((symbolp module) (symbol-name module))
((stringp module) module) ((stringp module) module)
(t (error "Not a valid module name: %s" module)))) (t (error "Not a valid module name: %s" module))))
(when (symbolp submodule) (when (symbolp submodule)
@ -243,9 +235,16 @@ Examples:
;; ;;
;; Defuns ;; Commands
;; ;;
(defun doom/reload ()
"Reload `load-path' by scanning all packages. Run this if you ran make update
or make clean outside of Emacs."
(interactive)
(doom-initialize t)
(message "Reloaded %s packages" (length package-alist)))
(defun doom/refresh-autoloads () (defun doom/refresh-autoloads ()
"Refreshes the autoloads.el file, which tells Emacs where to find all the "Refreshes the autoloads.el file, which tells Emacs where to find all the
autoloaded functions in the modules you use or among the core libraries. autoloaded functions in the modules you use or among the core libraries.
@ -253,8 +252,7 @@ autoloaded functions in the modules you use or among the core libraries.
Rerun this whenever you modify your init.el (or use `make autoloads` from the Rerun this whenever you modify your init.el (or use `make autoloads` from the
command line)." command line)."
(interactive) (interactive)
(unless doom--init (doom-reload)
(doom-reload-modules))
(let ((generated-autoload-file (concat doom-local-dir "autoloads.el")) (let ((generated-autoload-file (concat doom-local-dir "autoloads.el"))
(autoload-files (autoload-files
(append (-flatten (mapcar (lambda (dir) (append (-flatten (mapcar (lambda (dir)
@ -285,20 +283,21 @@ a lot from this.
If COMPREHENSIVE-P or the envar ALL is non-nil, also compile all autoload files. If COMPREHENSIVE-P or the envar ALL is non-nil, also compile all autoload files.
The benefit from this is minimal and may take more time." The benefit from this is minimal and may take more time."
(interactive) (interactive)
(doom-initialize) (doom-reload)
(doom-reload-modules) (let ((targets (append
(let* ((map-fn (lambda (file) (and (f-ext-p file "el") (list (f-expand "init.el" doom-emacs-dir)
(if comprehensive-p (f-expand "core.el" doom-core-dir))
(not (string= (f-base file) "packages")) (f-glob "core-*.el" doom-core-dir)
(string= (f-base file) "config"))))) (-flatten
(targets (append (--map (f--entries (doom-module-path (car it) (cdr it))
(list (f-expand "init.el" doom-emacs-dir) (and (f-ext-p it "el")
(f-expand "core.el" doom-core-dir)) (if comprehensive-p
(f-glob "core-*.el" doom-core-dir) (not (string= (f-base it) "packages"))
(-flatten (--map (f-entries (doom-module-path (car it) (cdr it)) map-fn t) (string= (f-base it) "config")))
doom-enabled-modules)))) t)
(n 0) doom-enabled-modules))))
results) (n 0)
results)
(mapc (lambda (file) (mapc (lambda (file)
(push (cons (f-relative file doom-emacs-dir) (push (cons (f-relative file doom-emacs-dir)
(when (byte-recompile-file file nil 0) (when (byte-recompile-file file nil 0)

View file

@ -101,7 +101,7 @@ disabled.")
;; I modified the built-in `hideshow' package to enable itself when needed. A ;; I modified the built-in `hideshow' package to enable itself when needed. A
;; better, more vim-like code-folding plugin would be the `origami' plugin, but ;; better, more vim-like code-folding plugin would be the `origami' plugin, but
;; until certain breaking bugs are fixed in it, I won't switch over. ;; until certain breaking bugs are fixed in it, I won't switch over.
(package! hideshow :ensure nil ; built-in (use-package! hideshow ; built-in
:commands (hs-minor-mode hs-toggle-hiding hs-already-hidden-p) :commands (hs-minor-mode hs-toggle-hiding hs-already-hidden-p)
:init :init
(defun doom*autoload-hideshow () (defun doom*autoload-hideshow ()
@ -164,7 +164,7 @@ file."
(package! highlight-numbers :commands highlight-numbers-mode) (package! highlight-numbers :commands highlight-numbers-mode)
;; Line highlighting ;; Line highlighting
(package! hl-line :ensure nil ; built-in (use-package! hl-line ; built-in
:config :config
;; stickiness doesn't play nice with emacs 25+ ;; stickiness doesn't play nice with emacs 25+
(setq hl-line-sticky-flag nil (setq hl-line-sticky-flag nil

View file

@ -120,52 +120,48 @@ enable multiple minor modes for the same regexp.")
(setq gc-cons-threshold 339430400 (setq gc-cons-threshold 339430400
gc-cons-percentage 0.6) gc-cons-percentage 0.6)
(eval-when-compile
(unless (file-exists-p doom-packages-dir)
(error "No packages are installed, run 'make install'"))
;; Ensure cache folder exist
(unless (file-exists-p doom-cache-dir)
(make-directory doom-cache-dir t)))
(let (file-name-handler-list) (let (file-name-handler-list)
(eval-and-compile (eval-and-compile
(load (concat doom-core-dir "core-packages") nil :nomessage)) (require 'core-packages (concat doom-core-dir "core-packages")))
(eval-when-compile (eval-when-compile
;; Ensure cache folder exist
(unless (file-exists-p doom-cache-dir)
(make-directory doom-cache-dir t))
(doom-initialize)) (doom-initialize))
(setq load-path (eval-when-compile load-path)) (setq load-path (eval-when-compile load-path))
;;; Essential packages (eval-and-compile
(require 'dash)
(require 'f)
(require 's))
;;; Let 'er rip
(require 'core-lib) (require 'core-lib)
(package! dash :demand t)
(package! s :demand t)
(package! f :demand t)
;;; Helper packages (autoloaded)
(package! async
:commands (async-start
async-start-process
async-byte-recompile-directory))
(defvar pcache-directory (concat doom-cache-dir "pcache/"))
(package! persistent-soft
:commands (persistent-soft-exists-p
persistent-soft-fetch
persistent-soft-flush
persistent-soft-store))
(package! smex :commands smex)
;;; Let 'er rip! (order matters!)
(require 'core-ui) ; draw me like one of your French editors
(require 'core-states) ; TODO
(require 'core-popups) ; taming sudden yet inevitable windows
(require 'core-editor) ; baseline configuration for text editing
(require 'core-projects) ; getting around your projects
(unless (require 'autoloads (concat doom-local-dir "autoloads.el") t) (unless (require 'autoloads (concat doom-local-dir "autoloads.el") t)
(add-hook 'after-init-hook 'doom/refresh-autoloads))) (doom/refresh-autoloads))
(unless noninteractive
(package! anaphora
:commands (awhen aif acond awhile))
(package! async
:commands (async-start
async-start-process
async-byte-recompile-directory))
(package! persistent-soft
:preface (defvar pcache-directory (concat doom-cache-dir "pcache/"))
:commands (persistent-soft-exists-p
persistent-soft-fetch
persistent-soft-flush
persistent-soft-store))
(require! core-set) ; a centralized config system; provides `set!'
(require! core-states) ; TODO
(require! core-ui) ; draw me like one of your French editors
(require! core-popups) ; taming sudden yet inevitable windows
(require! core-editor) ; baseline configuration for text editing
(require! core-projects))) ; making Emacs project-aware
(provide 'core) (provide 'core)
;;; core.el ends here ;;; core.el ends here