Fix package management

This commit is contained in:
Henrik Lissner 2017-01-31 04:31:14 -05:00
parent 02fef2fded
commit 8075c84882
3 changed files with 292 additions and 191 deletions

View file

@ -1,25 +1,33 @@
EMACS=emacs
CACHE_DIR=""
all: install update
install: autoloads
@$(EMACS) -Q --batch --eval '(setq use-package-always-ensure t)' -l init.el
install: init.el
@$(EMACS) --batch \
--eval '(setq doom-auto-install-p t)' \
-l init.el \
--eval '(message "%s" (if doom--packages "All done!" "Nothing to install"))'
update: autoloads
@$(EMACS) -Q --batch -l init.el -f 'doom/packages-update'
update: init.el
@$(EMACS) --batch -l init.el -f 'doom/packages-update'
autoloads:
@$(EMACS) -Q --batch -l init.el -f 'doom/refresh-autoloads'
autoloads: init.el
@$(EMACS) --batch -l init.el -f 'doom/refresh-autoloads'
compile:
@$(EMACS) -Q --batch -l init.el -f 'doom/byte-compile'
compile: init.el
@$(EMACS) --batch -l init.el -f 'doom/byte-compile'
clean: clean-elc
@$(EMACS) -Q --batch -l init.el -f 'doom/packages-clean'
clean: init.el
@$(EMACS) --batch -l init.el -f 'doom/packages-clean'
clean-cache:
@$(EMACS) --batch -l core/core.el --eval '(delete-directory doom-cache-dir t)'
clean-elc:
@rm -fv init.elc
@find {core,modules} -type f -iname '*.elc' -exec rm \-fv {} \;
init.el:
@[ -f init.el ] || $(error No init.el file, please create one or copy init.example.el)
.PHONY: all

View file

@ -16,18 +16,26 @@
;; used previously. package.el and quelpa are much more stable.
;; 4. No external dependencies (e.g. Cask) for plugin management.
(defvar doom-init nil
(defvar doom--init nil
"Whether doom's package system has been initialized or not. It may not be if
you have byte-compiled your configuration (as intended).")
(defvar doom-packages '(quelpa-use-package)
"List of enabled packages.")
(defvar doom-packages (list (cons 'quelpa-use-package nil))
"List of explicitly installed packages (not dependencies).")
(defvar doom-modules nil
"List of installed modules.")
"List of enabled modules; each are cons cells whose car is the module's name
symbol and cdr is the submodule's name as a symbol.")
(defvar doom--load-path load-path
"A backup of `load-path' before it was initialized.")
(defvar doom-auto-install-p nil
"")
(defvar doom--load-path (append (list doom-core-dir
doom-modules-dir
doom-local-dir)
load-path)
"A backup of `load-path', used as a bare-bones foundation for
`doom/packages-reload' or `doom-initialize'.")
(setq load-prefer-newer nil
package--init-file-ensured t
@ -41,6 +49,7 @@ you have byte-compiled your configuration (as intended).")
use-package-always-defer t
use-package-always-ensure nil
use-package-debug doom-debug-mode
use-package-verbose doom-debug-mode
quelpa-checkout-melpa-p nil
quelpa-update-melpa-p nil
quelpa-use-package-inhibit-loading-quelpa t
@ -48,120 +57,208 @@ you have byte-compiled your configuration (as intended).")
;;
;; Library
;; Bootstrap function
;;
(defun doom-package-init (&optional force-p)
"Initialize DOOM, its essential packages and package.el. This must be used on
first run. If you byte compile core/core.el, this file is avoided to speed up
startup. Returns `load-path'."
(when (or (not doom-init) force-p)
(autoload 'use-package "quelpa-use-package" nil t)
(defun doom-initialize (&optional force-p)
"Initialize installed packages (using package.el). This must be used on first
run, as it will prepare Emacs to auto-install all missing packages (otherwise
you'll get errors). If you byte compile core/core.el, calls to `package.el' are
avoided to speed up startup."
(unless (or doom--init force-p)
(setq load-path doom--load-path
package-activated-list nil)
(package-initialize)
(when (or (not (file-exists-p package-user-dir)) force-p)
(package-refresh-contents))
(unless package-archive-contents
(package-read-all-archive-contents))
(unless (package-installed-p 'quelpa-use-package)
(package-install 'quelpa-use-package t))
(package-refresh-contents)
(package-install 'quelpa-use-package t)
(setq doom-auto-install-p (not noninteractive)))
(unless (featurep 'quelpa-use-package)
(require 'quelpa-use-package)
(setq use-package-always-ensure t)
(quelpa-use-package-activate-advice)
(setq use-package-expand-minimally t)
;; Move :ensure to after conditional properties
(delq :ensure use-package-keywords)
(push :ensure (cdr (memq :unless use-package-keywords))))
(setq doom--init t)))
(add-to-list 'load-path doom-local-dir)
(add-to-list 'load-path doom-modules-dir)
(add-to-list 'load-path doom-core-dir)
(setq doom-init t)))
;;
;; Macros
;;
(defmacro package! (name &rest rest)
"Declare a package. Wraps around `use-package', and takes the same arguments
that it does. NOTE: Packages are deferred by default."
(defvar doom--packages nil
"List of packages explicitly installed during this session.")
(defmacro package! (name &rest plist)
"Uses `quelpa' and `use-package' to ensure PACKAGES are installed and
available. If `doom-auto-install-p' is nil, then strip out :ensure and :quelpa
properties, which is the case if you've byte-compiled DOOM Emacs.
It takes the same arguments as `use-package'.
Each element in PACKAGES can be a symbol or a list, whose car is the package
symbol and cdr is a plist. The plist accepts any argument `quelpa-use-package'
uses."
(declare (indent defun))
(add-to-list 'doom-packages name)
;; If `use-package-always-ensure' is nil, then remove any possibility of an
;; installation by package.el or quelpa.
(unless use-package-always-ensure
(when (and (plist-member rest :ensure)
(plist-get rest :ensure))
(setq rest (plist-put rest :ensure nil)))
(when (plist-member rest :quelpa)
(use-package-plist-delete rest :quelpa)))
(macroexpand-all `(use-package ,name ,@rest)))
(let ((use-package-always-ensure doom-auto-install-p)
recipe)
(when (plist-member plist :quelpa)
(setq recipe (plist-get plist :quelpa))
;; prepend NAME to quelpa recipe, if none is specified, to avoid local
;; MELPA lookups by quelpa.
(when (= 0 (mod (length recipe) 2))
(push name recipe)
(plist-put plist :quelpa (append (list name) recipe))))
(if (and doom-auto-install-p
(not (bound-and-true-p byte-compile-current-file)))
(unless (package-installed-p name)
(add-to-list 'doom--packages name))
(use-package-plist-delete plist :ensure)
(use-package-plist-delete plist :quelpa))
`(progn
(add-to-list 'doom-packages '(,name ,@recipe))
,(macroexpand-all `(use-package ,name ,@plist)))))
(defmacro load! (file-or-module-sym &optional submodule noerror)
(defmacro load! (file-or-module-sym &optional submodule)
"Load a module from `doom-modules-dir'. Plays the same role as
`load-relative', but is specific to DOOM emacs modules and submodules.
Examples:
(load! :lang emacs-lisp) loads modules/lang/emacs-lisp/{packages,config}.el
(load! +local-module) if called from ./config.el, loads ./+local-module.el
Note: requires that config.el be loaded with `load!'"
(let* ((module-name (symbol-name file-or-module-sym))
(module (if (and submodule (string-prefix-p ":" module-name)) (substring module-name 1) module-name)))
(if submodule
(let ((path (concat doom-modules-dir module "/" (symbol-name submodule) "/")))
(macroexp-progn
(mapcar (lambda (file)
(when (file-exists-p (concat path file ".el"))
(doom--load path file (not doom-debug-mode))))
(append (list "packages")
(unless noninteractive (list "config"))))))
(let ((path (concat (f-dirname load-file-name) "/")))
(doom--load path module (not doom-debug-mode))))))
;; Note: requires that the calling module be loaded with `load!'
(load! +local-module) if called from ./config.el, loads ./+local-module.el"
(let ((module-name (if (symbolp file-or-module-sym)
(symbol-name file-or-module-sym)
file-or-module-sym))
submodule-name
path file)
(cond ((null submodule)
(setq path (f-dirname load-file-name)
file (list module-name)))
(t
(when (string-prefix-p ":" module-name)
(setq module-name (substring module-name 1)))
(setq path (f-expand (concat module-name "/" (symbol-name submodule))
doom-modules-dir)
file (if doom-auto-install-p "packages.el" "config.el"))))
(setq path (f-slash path)
file (concat path file))
(when (file-exists-p file)
`(let ((__FILE__ ,file)
(__DIR__ ,path))
(load __FILE__ nil :noerror noninteractive noninteractive)))))
(defvar __DIR__ nil "The directory of the currently loaded file (with `load!')")
(defvar __FILE__ nil "The full path of the currently loaded file (with `load!')")
(defun doom--load (path file &optional noerror)
`(let ((__FILE__ ,(concat path file (if noninteractive ".el")))
(__DIR__ ,path))
(load __FILE__ nil ,noerror noninteractive noninteractive)))
(defun doom-package-outdated-p (package &optional inhibit-refresh-p)
"Determine whether PACKAGE (a symbol) is outdated or not. If INHIBIT-REFRESH-P
is non-nil, don't run `package-refresh-contents' (which is slow, but useful if
you intend to use this method for batch processing -- be sure to run
`package-refresh-contents' beforehand however)."
(unless inhibit-refresh-p
(package-refresh-contents))
(when (and (package-installed-p package)
(cadr (assq package package-archive-contents)))
(let* ((newest-desc (cadr (assq package package-archive-contents)))
(installed-desc (cadr (or (assq package package-alist)
;;
;; Commands
;;
(defun doom-package-outdated-p (package)
"Determine whether PACKAGE (a symbol) is outdated or not. Be sure to run
`package-refresh-contents' beforehand, or the return value could be out of
date."
(let ((pkg (assq package doom-packages)))
(when (and pkg (package-installed-p package))
(let* ((pkg-recipe (cdr pkg))
(cur-desc (cadr (or (assq package package-alist)
(assq package package--builtins))))
(newest-version (package-desc-version newest-desc))
(installed-version (package-desc-version installed-desc)))
(not (version-list-<= newest-version installed-version)))))
(cur-version (package-desc-version cur-desc))
(inhibit-message t)
new-version)
(setq new-version
(if pkg-recipe
(let ((ver (quelpa-checkout
pkg-recipe
(f-expand (symbol-name package) quelpa-build-dir))))
(or (and ver (version-to-list ver)) cur-version))
(package-desc-version (cadr (assq package package-archive-contents)))))
(not (version-list-<= new-version cur-version))))))
(defun doom/packages-reload ()
"Reload `load-path' by scanning all packages. Run this if you ran make update
or make clean outside of Emacs."
(interactive)
(setq load-path doom--load-path)
(doom-package-init t)
(doom-initialize t)
(when (called-interactively-p 'interactive)
(message "Reloaded %s packages" (length package-alist))))
(defun doom/packages-install ()
"Install missing packages."
(interactive)
(let ((doom-auto-install-p t))
(load (concat doom-emacs-dir "init.el"))))
(defun doom/packages-update ()
"Update outdated packages. This includes quelpa itself, quelpa-installed
packages, and ELPA packages. This will delete old versions of packages as well."
(interactive)
(doom-package-init t)
(quelpa-upgrade) ; upgrade quelpa + quelpa-installed packages
(mapc (lambda (package)
(package-refresh-contents)
(package-read-all-archive-contents)
;; first, upgrade quelpa + quelpa-installed packages
(require 'quelpa)
(let ((n 0)
(err 0)
(quelpa-upgrade-p t)
quelpa-verbose)
(when (quelpa-setup-p)
(setq quelpa-cache (--filter (package-installed-p (car it)) quelpa-cache))
(dolist (package quelpa-cache)
(condition-case ex
(let ((desc (cadr (assq package package-alist))))
(delete-directory (package-desc-dir desc) t)
(package-install-from-archive (cadr (assoc package package-archive-contents))))
('error (message "ERROR: %s" ex)))) ;; TODO
(-uniq (--filter (or (assq it quelpa-cache)
(doom-package-outdated-p it t))
(package--find-non-dependencies)))))
(let ((old-version (ignore-errors
(package-desc-version
(cadr (or (assq (car package) package-alist)
(assq (car package) package--builtins))))))
new-version)
(when (doom-package-outdated-p (car package))
(setq n (1+ n))
(let ((inhibit-message t))
(quelpa package))
(setq new-version (package-desc-version
(cadr (or (assq (car package) package-alist)
(assq (car package) package--builtins)))))
(when noninteractive
(message "Updating %s (%s -> %s) (quelpa)" (car package)
(mapconcat 'number-to-string old-version ".")
(mapconcat 'number-to-string new-version ".")))))
('error
(setq err (1+ err))
(message "ERROR (quelpa): %s" ex)))))
;; ...then update elpa packages
(mapc (lambda (package)
(when noninteractive (message "Updating %s (elpa)" package))
(condition-case ex
(let ((desc (cadr (assq package package-alist)))
(archive (cadr (assoc package package-archive-contents))))
(setq n (1+ n))
(package-install-from-archive archive)
(delete-directory (package-desc-dir desc) t))
('error
(setq err (1+ err))
(message "ERROR (elpa): %s" ex)))) ;; TODO real error string
(-uniq (--filter (and (not (assq it quelpa-cache))
(doom-package-outdated-p it))
(package--find-non-dependencies))))
(when noninteractive
(message (if (= n 0)
"Everything is up-to-date"
"Updated %s packages") n)
(when (> err 0)
(message "There were %s errors" err)))))
(defun doom/packages-clean ()
"Delete packages that are no longer used or referred to."
(interactive)
(doom-package-init t)
(let* ((package-selected-packages (-intersection (package--find-non-dependencies) doom-packages))
(let* ((package-selected-packages (-intersection (package--find-non-dependencies)
(mapcar 'car doom-packages)))
(packages-to-delete (package--removable-packages))
quelpa-modified-p)
(cond ((not package-selected-packages)
@ -174,62 +271,63 @@ packages, and ELPA packages. This will delete old versions of packages as well."
(mapconcat 'symbol-name (reverse packages-to-delete) ", "))))
(message "Aborted."))
(t
(dolist (package packages-to-delete)
(package-delete package t)
(when (assq package quelpa-cache)
(setq quelpa-cache (assq-delete-all package quelpa-cache)
(require 'quelpa)
(quelpa-setup-p)
(dolist (p packages-to-delete)
(package-delete (cadr (assq p package-alist)) t)
(when (and quelpa-cache (assq p quelpa-cache))
(setq quelpa-cache (assq-delete-all p quelpa-cache)
quelpa-modified-p t)))
(when quelpa-modified-p
(quelpa-save-cache))))))
(defun doom/byte-compile (&optional comprehensive-p)
"Byte (re)compile the important files in your emacs configuration. DOOM Emacs
was designed to benefit a lot from this. If COMPREHENSIVE-P is non-nil, compile
config.el and autoload.el files as well -- the performance benefit from this is
minor and may take a while.
No need to recompile any of these files so long as `auto-compile-mode' is on in
`emacs-lisp-mode', which, if you're using the provided emacs-lisp module, should
be the case."
"Byte (re)compile the important files in your emacs configuration (init.el and
core/*.el). If COMPREHENSIVE-P is non-nil, also compile config.el files in
modules. DOOM Emacs was designed to benefit a lot from this."
(interactive)
(let (use-package-always-ensure)
(doom-package-init)
(let (use-package-always-ensure
file-name-handler-alist)
(mapc 'byte-compile-file
(append (list (expand-file-name "init.el" doom-emacs-dir)
(expand-file-name "core.el" doom-core-dir))
(reverse
(f-glob "core-*.el" doom-core-dir))
(f-glob "*/*/packages.el" doom-modules-dir)
(append (list (f-expand "init.el" doom-emacs-dir)
(f-expand "core.el" doom-core-dir))
(reverse (f-glob "core-*.el" doom-core-dir))
(when comprehensive-p
(f-glob "*/*/config.el" doom-modules-dir))
(when comprehensive-p
(f-glob "*/*/autoload.el" doom-modules-dir))))))
(f-glob "*/*/config.el" doom-modules-dir))))))
(defun doom/refresh-autoloads ()
"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.
Rerun this whever 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)."
(interactive)
(let ((generated-autoload-file (concat doom-local-dir "autoloads.el"))
(interactive-p (called-interactively-p 'interactive))
(autoload-files
(append (-filter 'file-exists-p
(mapcar (lambda (m)
(f-expand
(format "%s/%s/autoload.el" (substring (symbol-name (car m)) 1) (cdr m))
(append
(-flatten (mapcar (lambda (m)
(let* ((dir (f-expand (format "%s/%s"
(substring (symbol-name (car m)) 1)
(cdr m))
doom-modules-dir))
(auto-dir (f-expand "autoload" dir))
(auto-file (f-expand "autoload.el" dir)))
(cond ((f-directory-p auto-dir)
(f-glob "*.el" auto-dir))
((f-exists-p auto-file)
auto-file))))
doom-modules))
(f-glob "autoload/*.el" doom-core-dir))))
(when (file-exists-p generated-autoload-file)
(when (f-exists-p generated-autoload-file)
(delete-file generated-autoload-file)
(when interactive-p (message "Deleted old autoloads.el")))
(when noninteractive (message "Deleted old autoloads.el")))
(dolist (file autoload-files)
(update-file-autoloads file t)
(unless interactive-p
(update-file-autoloads file)
(when noninteractive
(message "Detected: %s" (f-relative file doom-emacs-dir))))
(when interactive-p (message "Done!"))
(with-current-buffer (get-file-buffer generated-autoload-file)
(save-buffer))
(when noninteractive (message "Done!"))
(with-demoted-errors "WARNING: %s"
(load generated-autoload-file nil t))))

View file

@ -21,6 +21,10 @@
(defvar doom-version "2.0.0"
"Current version of DOOM emacs")
(defvar doom-debug-mode nil
"If non-nil, all loading functions will be verbose and `use-package-debug'
will be set.")
(defvar doom-emacs-dir user-emacs-directory
"The path to this emacs.d directory")
@ -115,20 +119,24 @@ enable multiple minor modes for the same regexp.")
(setq gc-cons-threshold 339430400
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)
(eval-and-compile
(load (concat doom-core-dir "core-packages") nil t))
(load (concat doom-core-dir "core-packages") nil :nomessage))
(eval-when-compile
;; Ensure cache folder exists
(unless (file-exists-p doom-temp-dir)
(make-directory doom-temp-dir t))
(doom-package-init))
(setq load-path (eval-when-compile load-path)
custom-theme-load-path (append (list doom-themes-dir) custom-theme-load-path))
(doom-initialize))
(setq load-path (eval-when-compile load-path))
;;; Essential packages
(require 'core-lib)
(package! dash :demand t)
(package! s :demand t)
(package! f :demand t)
@ -139,67 +147,54 @@ enable multiple minor modes for the same regexp.")
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)
:init (defvar pcache-directory (concat doom-temp-dir "/pcache/")))
persistent-soft-store))
(package! smex :commands smex)
(unless (require 'autoloads nil t)
(add-hook 'after-init-hook 'doom/refresh-autoloads))
;;; Let 'er rip! (order matters!)
;; (require 'core-set) ; configuration management system
;; (require 'core-popups) ; taming sudden yet inevitable windows
(require 'core-evil) ; come to the dark side, we have cookies
;; (require 'core-project)
;; (require 'core-os)
;; (require 'core-ui)
;; (require 'core-modeline)
;; (require 'core-editor)
;; (require 'core-completion)
(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) ; getting around your projects
;; (require 'core-workspaces) ; TODO
;; (require 'core-completion) ; TODO company & auto-complete, for the lazy typist
;; (require 'core-evil)
;; (require 'core-jump)
;; (require 'core-repl)
;; (require 'core-snippets)
;; (require 'core-syntax-checking)
;; (require 'core-vcs)
;; (require 'core-workspaces)
(unless (require 'autoloads nil t)
(doom/refresh-autoloads t)))
;; (require 'core-syntax-checking))
)
;;;
;;
(defmacro doom! (&rest packages)
(let (paths)
(dolist (p packages)
(if (memq p '(:apps :emacs :lang :lib :private :ui))
(setq mode p)
(unless mode
(error "No namespace specified on `doom!' for %s" p))
(pushnew (format "%s%s/%s/" doom-modules-dir (substring (symbol-name mode) 1) (symbol-name p))
paths)))
"DOOM Emacs bootstrap macro. List the modules to load. Benefits from
byte-compilation."
`(let (file-name-handler-alist)
(unless noninteractive
(load "~/.emacs.local.el" t t))
(with-demoted-errors "ERROR: %s"
,@(mapcar
(lambda (path)
(macroexp-progn
(mapcar (lambda (file)
(when noninteractive (setq file (file-name-sans-extension file)))
`(load ,(expand-file-name file path) t t (not noninteractive)))
(append (list "packages.el")
(unless noninteractive (list "config.el"))))))
paths))
,@(mapcar (lambda (pkg)
`(progn
(add-to-list 'doom-modules (cons ,(car pkg) ',(cdr pkg)))
,(macroexpand `(load! ,(car pkg) ,(cdr pkg)))))
(let (pkgs mode)
(dolist (p packages)
(cond ((string-prefix-p ":" (symbol-name p))
(setq mode p))
((not mode)
(error "No namespace specified on `doom!' for %s" p))
(t
(setq pkgs (append pkgs (list (cons mode p)))))))
pkgs))
(unless noninteractive
(require 'my-bindings)
(require 'my-commands)
(when (display-graphic-p)
(require 'server)
(unless (server-running-p)
@ -207,7 +202,7 @@ enable multiple minor modes for the same regexp.")
;; Prevent any auto-displayed text + benchmarking
(advice-add 'display-startup-echo-area-message :override 'ignore)
(message "")))))
(message ""))))
(provide 'core)
;;; core.el ends here