Refactor package management system

This commit is contained in:
Henrik Lissner 2017-02-11 00:46:42 -05:00
parent 85d5360c7c
commit 7ef87546cc
10 changed files with 335 additions and 288 deletions

View file

@ -55,6 +55,8 @@ if you have byte-compiled your configuration (as intended).")
'(("gnu" . "http://elpa.gnu.org/packages/")
("melpa" . "http://melpa.org/packages/")
("org" . "http://orgmode.org/elpa/"))
;; I omit Marmalade because its packages are manually submitted rather
;; than pulled, so packages are often out of date with upstream.
use-package-always-defer t
use-package-always-ensure nil
@ -74,49 +76,19 @@ if you have byte-compiled your configuration (as intended).")
;; Bootstrap function
;;
(autoload 'use-package "use-package" nil nil 'macro)
(advice-add 'package-delete :after 'doom*package-delete)
(defmacro @doom (&rest packages)
"DOOM Emacs bootstrap macro. List the modules to load. Benefits from
byte-compilation."
(let (mode)
(dolist (p packages)
(cond ((keywordp p)
(setq mode p))
((not mode)
(error "No namespace specified on `@doom' for %s" p))
((eq p '*)
(let ((mode-name (substring (symbol-name mode) 1)))
(--map (setq doom-modules (append doom-modules (list (cons mode (f-base it)))))
(f-directories (f-expand mode-name doom-modules-dir)))))
(t
(setq doom-modules (append doom-modules (list (cons mode p))))))))
(unless noninteractive
`(let (file-name-handler-alist)
,@(mapcar (lambda (pkg) `(@load ,(car pkg) ,(cdr pkg)))
doom-modules)
(when (display-graphic-p)
(require 'server)
(unless (server-running-p)
(server-start)))
;; Benchmark
(format "Loaded %s packages in %s"
(- (length load-path) (length doom--base-load-path))
(emacs-init-time)))))
(defun doom-initialize (&optional force-p)
"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
avoided to speed up startup."
;; This is called early during Emacs initialization, so we can only use native
;; emacs functions.
(unless (or doom-init-p force-p)
(setq load-path doom--base-load-path
package-activated-list nil)
(package-initialize t)
;; Sure, package-initialize fills the load-path, but it will error out on
;; missing packages. UNACCEPTAABBLLLE!
;; Sure, `package-initialize' fills the load-path, but when NO-ACTIVATE is
;; non-nil, it will error out on missing packages. UNACCEPTAABBLLLE!
(setq load-path (append load-path (directory-files package-user-dir t "^[a-zA-Z0-9]" t)))
;; Ensure cache folder exists
@ -143,99 +115,168 @@ avoided to speed up startup."
;; Remove package management keywords, I'll deal with the myself
(mapc (lambda (keyword) (setq use-package-keywords (delq keyword use-package-keywords)))
'(:ensure :pin))
(setq doom-init-p t)))
(defun doom-initialize-autoloads (&optional force-p)
"Ensures that an autoloads file exists and is loaded."
(unless (or (featurep 'autoloads)
(load doom-autoload-file t t))
(doom/refresh-autoloads)
(unless (file-exists-p doom-autoload-file)
(error "Autoloads file couldn't be generated"))))
(unless (ignore-errors (require 'autoloads doom-autoload-file t))
(unless noninteractive
(doom/reload-autoloads)
(unless (file-exists-p doom-autoload-file)
(error "Autoloads file couldn't be generated")))))
(defun doom-initialize-packages (&optional force-p)
"Parses your Emacs config to keep track of packages declared with `@package'
in `doom-packages' and enabled modules in `doom-modules'."
(doom-initialize force-p)
(when (or force-p (not doom-modules) (not doom-packages))
(setq doom-modules nil)
(let ((noninteractive t))
(mapc (lambda (file) (load file nil :nomessage))
(list (f-expand "packages.el" doom-core-dir)
(f-expand "init.el" doom-emacs-dir)))
;; Look up packages.el for enabled modules
(mapc (lambda (file) (load file :noerror :nomessage))
(--map (doom-module-path (car it) (cdr it) "packages.el")
(doom-module-pairs))))))
(defun doom-module-path (module submodule &optional file)
"Get the full path to a module: e.g. :lang emacs-lisp maps to
~/.emacs.d/modules/lang/emacs-lisp/ and will append FILE if non-nil."
(unless (keywordp module)
(error "Expected a keyword, got %s" module))
(unless (symbolp submodule)
(error "Expected a symbol, got %s" submodule))
(let ((module-name (substring (symbol-name module) 1))
(submodule-name (symbol-name submodule)))
(f-expand (concat module-name "/" submodule-name "/" file)
doom-modules-dir)))
(defun doom-module-pairs ()
"TODO"
(let (pairs module)
(dolist (modules doom-modules)
(setq module (car modules))
(dolist (submodule (cdr modules))
(push (cons module submodule) pairs)))
pairs))
(defun doom-module-loaded-p (module submodule)
"TODO"
(memq submodule (cdr (assq module doom-modules))))
(defun doom-enable-module (module submodule &optional force-p)
(unless (or force-p (doom-module-loaded-p module submodule))
(let ((sublist (assq module doom-modules)))
(if sublist
(setf sublist (cons sublist submodule))
(push (list module submodule) doom-modules)))))
(defun doom-enable-modules (modules)
"TODO"
(let (mode)
(dolist (m modules)
(cond ((keywordp m)
(setq mode m))
((not mode)
(error "No namespace specified on `@doom' for %s" m))
((eq m '*)
(let ((mode-str (substring (symbol-name mode) 1)))
(doom-enable-modules
(cons mode
(--map (intern (f-base it))
(f-directories
(f-expand mode-str doom-modules-dir)))))))
(t
(doom-enable-module mode m))))
doom-modules))
;;
;; Macros
;;
(defvar __PACKAGE__ nil "The name of the current package.")
(autoload 'use-package "use-package" nil nil 'macro)
(defalias '@use-package 'use-package
(defmacro @doom (&rest modules)
"DOOM Emacs bootstrap macro. List the modules to load. Benefits from
byte-compilation."
(doom-enable-modules modules)
(unless noninteractive
`(let (file-name-handler-alist)
,@(mapcar (lambda (pkg)
`(@require ,(car pkg) ,(cdr pkg) t))
(doom-module-pairs))
(when (display-graphic-p)
(require 'server)
(unless (server-running-p)
(server-start)))
;; Benchmark
(format "Loaded %s packages in %s"
(- (length load-path) (length doom--base-load-path))
(emacs-init-time)))))
(defalias '@def-package 'use-package
"A `use-package' alias. It exists so DOOM configs adhere to the naming
conventions of DOOM emacs. Note that packages are deferred by default.
conventions of DOOM emacs. Note that packages are deferred by default.")
By DOOM conventions, using this instead of `@package' means you are configuring
a package regardless of whether it's installed or not, while `@package' is used
to declare how to install/setup a package.")
(defmacro @load (filesym &optional path noerror)
"TODO"
(let ((path (or (and path (eval path)) __DIR__))
file)
(unless path
(error "Could not find %s" filesym))
(setq file (f-expand (concat (symbol-name filesym) ".el") path))
(if (f-exists-p file)
`(let ((__FILE__ ,file)
(__DIR__ ,path))
(load ,(f-no-ext file) ,noerror (not doom-debug-mode)))
(unless noerror
(error "Could not @load file %s" file)))))
(defmacro @require (module submodule &optional reload-p)
"Like `require', but for doom modules."
(unless noninteractive
(let ((loaded-p (doom-module-loaded-p module submodule)))
(when (or reload-p (not loaded-p))
(unless loaded-p
(doom-enable-module module submodule t))
`(@load config ,(doom-module-path module submodule) t)))))
;;
;; Declarative macros
;;
(defmacro @package (name &rest plist)
"Declares a package. This does not load nor install them explicitly.
If used in `doom-core-dir', this is a wrapper for `@use-package' (all packages
are deferred by default), and takes the same arguments as `use-package'.
this macro serves a purely declarative purpose, and are run to build
`doom-packages', so that functions like `doom/packages-install' can operate on
them.
If used outside of `doom-core-dir' (i.e. in packages.el files within modules),
this macro serves a purely declarative purpose and doesn't call `@use-package'.
These calls are parsed by `doom-read-packages' to build `doom-packages'.
Adds a few custom properties in either case:
Accepts the following properties:
:recipe RECIPE Takes a MELPA-style recipe (see `quelpa-recipe' 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\".
:needs FEATURE Don't install this package if FEATURE isn't available. Can be a
(:module . submodule) cons pair."
ARCHIVE-NAME. e.g. \"org\"."
(declare (indent defun))
(mapc (lambda (key) (setq plist (use-package-plist-delete plist key)))
'(:recipe :pin :needs))
`(let ((__PACKAGE__ ',name))
(@use-package ,name ,@plist)))
(let ((pkg-recipe (plist-get plist :recipe))
(pkg-pin (plist-get plist :pin)))
(when (= 0 (mod (length pkg-recipe) 2))
(plist-put plist :recipe (cons name pkg-recipe)))
`(add-to-list 'doom-packages ',(cons name plist) t)))
(defmacro @load (module &optional submodule file)
"Load a module from `doom-modules-dir' when both MODULE and SUBMODULE is
provided (both symbols). If FILE is non-nil, append it to the resulting path. If
SUBMODULE is nil, MODULE is loaded relative to the current file (see `__DIR__').
When SUBMODULE is nil, FILE isn't used.
Examples:
(@load :lang emacs-lisp)
Loads modules/lang/emacs-lisp/FILE.el (defaults to config.el).
(@load +local-module)
Loads +local-module.el relative to `__DIR__' or `doom-core-dir'."
(let (path file)
(cond ((null submodule)
(setq path __DIR__
file (concat (symbol-name module) ".el")))
(t
(cl-pushnew (cons module submodule)
doom-modules
:test (lambda (x y) (and (eq (car x) (car y))
(eq (cdr x) (cdr y)))))
(setq path (doom-module-path module submodule)
file (or file "config.el"))))
(setq path (f-slash path)
file (concat path file))
`(let ((__FILE__ ,file)
(__DIR__ ,path))
(load ,(f-no-ext file) nil (not doom-debug-mode)))))
(defun doom-module-path (module submodule &optional file)
"Get the full path to a module: e.g. :lang emacs-lisp maps to
~/.emacs.d/modules/lang/emacs-lisp/ and will append FILE if non-nil."
(setq module
(cond ((keywordp module) (substring (symbol-name module) 1))
((symbolp module) (symbol-name module))
((stringp module) module)
(t (error "Not a valid module name: %s" module))))
(when (symbolp submodule)
(setq submodule (symbol-name submodule)))
(f-expand (concat module "/" submodule "/" file)
doom-modules-dir))
(defmacro @depends-on (module submodule)
"Declares that this module depends on another. MODULE is a keyword, and
SUBMODULE is a symbol."
(doom-enable-module ,module ',submodule)
`(@load packages ,(doom-module-path module submodule) t))
;;
@ -243,16 +284,13 @@ Examples:
;;
(defun doom/reload ()
"Reload `load-path', `doom-modules' and `doom-packages' by
reinitializing doom and parsing config files for `@package' and `@doom' calls.
There are few reasons to use this."
"Reload `load-path' by reinitializing package.el and reloading autoloads."
(interactive)
(doom-initialize t)
(doom-read-packages t)
(doom-initialize-autoloads)
(doom/reload-autoloads)
(message "Reloaded %s packages" (length package-alist)))
(defun doom/refresh-autoloads ()
(defun doom/reload-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, e.g.
core/autoload/*.el.
@ -262,6 +300,7 @@ In modules, checks modules/*/autoload.el and modules/*/autoload/*.el.
Rerun this whenever init.el is modified. You can also use `make autoloads` from
the commandline."
(interactive)
(doom-initialize-packages noninteractive)
(let ((generated-autoload-file doom-autoload-file)
autoload-files)
(setq autoload-files
@ -272,26 +311,29 @@ the commandline."
(and (f-directory-p auto-dir)
(f-glob "*.el" auto-dir))))
(--map (doom-module-path (car it) (cdr it))
doom-modules)))
(doom-module-pairs))))
(f-glob "autoload/*.el" doom-core-dir)))
(when (f-exists-p generated-autoload-file)
(f-delete generated-autoload-file)
(message "Deleted old autoloads.el"))
(dolist (file autoload-files)
(update-file-autoloads file)
(@quiet (update-file-autoloads file))
(message "Scanned %s" (f-relative file doom-emacs-dir)))
(with-current-buffer (get-file-buffer generated-autoload-file)
(save-buffer)
(eval-buffer))
(message "Done!")))
(condition-case ex
(with-current-buffer (get-file-buffer generated-autoload-file)
(save-buffer)
(eval-buffer)
(message "Done!"))
('error (error "Couldn't evaluate autoloads.el: %s" (cadr ex))))))
(defun doom/byte-compile (&optional simple-p)
(defun doom/recompile (&optional simple-p)
"Byte (re)compile the important files in your emacs configuration (init.el &
core/*.el). DOOM Emacs was designed to benefit from this.
If SIMPLE-P is nil, also byte-compile modules/*/*/*.el (except for packages.el).
There should be a measurable benefit from this, but it may take a while."
(interactive)
(doom-initialize-packages t)
(let ((targets
(append (list (f-expand "init.el" doom-emacs-dir)
(f-expand "core.el" doom-core-dir))
@ -303,7 +345,7 @@ There should be a measurable benefit from this, but it may take a while."
(or (string= (f-base it) "config")
(string-prefix-p "+" (f-base it))))
t)
doom-modules)))))
(doom-module-pairs))))))
(n 0)
results)
(dolist (file targets)
@ -318,5 +360,12 @@ There should be a measurable benefit from this, but it may take a while."
(mapconcat (lambda (file) (concat "+ " (if (cdr file) "SUCCESS" "FAIL") ": " (car file)))
(reverse results) "\n")))))
;;
;; Package.el modifications
;;
(advice-add 'package-delete :after 'doom*package-delete)
(provide 'core-packages)
;;; core-packages.el ends here