diff options
author | John Wiegley <johnw@newartisans.com> | 2017-03-19 00:12:06 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-03-19 00:12:06 -0700 |
commit | bd2afa53c7580d23ed8008267b80e1834b6e6600 (patch) | |
tree | 87ac7202ca16b5547e24e8f4395d5ea23c7f6966 /lisp/use-package | |
parent | 45442561d3ec095b7fa244d3b2c80b9847baee99 (diff) | |
parent | 249de4b44defa24da051a990bb5ac012f4050734 (diff) | |
download | emacs-bd2afa53c7580d23ed8008267b80e1834b6e6600.tar.gz emacs-bd2afa53c7580d23ed8008267b80e1834b6e6600.tar.bz2 emacs-bd2afa53c7580d23ed8008267b80e1834b6e6600.zip |
Merge pull request from raxod502/defer-install
[RFC] Support for deferred installation
GitHub-reference: https://github.com/jwiegley/use-package/issues/433
Diffstat (limited to 'lisp/use-package')
-rw-r--r-- | lisp/use-package/use-package.el | 267 |
1 files changed, 233 insertions, 34 deletions
diff --git a/lisp/use-package/use-package.el b/lisp/use-package/use-package.el index 99646e2690a..2943dafe6b0 100644 --- a/lisp/use-package/use-package.el +++ b/lisp/use-package/use-package.el @@ -133,6 +133,7 @@ the user specified." '(:disabled :preface :pin + :defer-install :ensure :if :when @@ -184,14 +185,56 @@ Must be set before loading use-package." (defcustom use-package-ensure-function 'use-package-ensure-elpa "Function that ensures a package is installed. -This function is called with one argument, the package name as a -symbol, by the `:ensure' keyword. - -The default value uses package.el to install the package." +This function is called with four arguments: the name of the +package declared in the `use-package' form; the argument passed +to `:ensure'; the current `state' plist created by previous +handlers; and a keyword indicating the context in which the +installation is occurring. + +Note that this function is called whenever `:ensure' is provided, +even if it is nil. It is up to the function to decide on the +semantics of the various values for `:ensure'. + +This function should return non-nil if the package is installed. + +The default value uses package.el to install the package. + +Possible values for the context keyword are: + +:byte-compile - package installed during byte-compilation +:ensure - package installed normally by :ensure +:autoload - deferred installation triggered by an autoloaded + function +:after - deferred installation triggered by the loading of a + feature listed in the :after declaration +:config - deferred installation was specified at the same time + as :demand, so the installation was triggered + immediately +:unknown - context not provided + +Note that third-party code can provide other values for the +context keyword by calling `use-package-install-deferred-package' +with the appropriate value." :type '(choice (const :tag "package.el" use-package-ensure-elpa) (function :tag "Custom")) :group 'use-package) +(defcustom use-package-pre-ensure-function 'ignore + "Function that is called upon installation deferral. +It is called immediately with the first three arguments that +would be passed to `use-package-ensure-function' (the context +keyword is omitted), but only if installation has been deferred. +It is intended for package managers other than package.el which +might want to activate the autoloads of a package immediately, if +it's installed, but otherwise defer installation until later (if +`:defer-install' is specified). The reason it is set to `ignore' +by default is that package.el activates the autoloads for all +known packages at initialization time, rather than one by one +when the packages are actually requested." + :type '(choice (const :tag "None" ignore) + (function :tag "Custom")) + :group 'use-package) + (defcustom use-package-defaults '((:config '(t) t) (:ensure use-package-always-ensure use-package-always-ensure) @@ -574,6 +617,82 @@ manually updated package." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; +;;; :defer-install +;; + +(defvar use-package--deferred-packages (make-hash-table) + "Hash mapping packages to data about their installation. + +The keys are not actually symbols naming packages, but rather +symbols naming the features which are the names of \"packages\" +required by `use-package' forms. Since +`use-package-ensure-function' could be set to anything, it is +actually impossible for `use-package' to determine what package +is supposed to provide the feature being ensured just based on +the value of `:ensure'. + +Each value is a cons, with the car being the the value passed to +`:ensure' and the cdr being the `state' plist. See +`use-package-install-deferred-package' for information about how +these values are used to call `use-package-ensure-function'.") + +(defun use-package-install-deferred-package (name &optional context) + "Install a package whose installation has been deferred. +NAME should be a symbol naming a package (actually, a feature). +This is done by calling `use-package-ensure-function' is called +with four arguments: the key (NAME) and the two elements of the +cons in `use-package--deferred-packages' (the value passed to +`:ensure', and the `state' plist), and a keyword providing +information about the context in which the installation is +happening. (This defaults to `:unknown' but can be overridden by +providing CONTEXT.) + +Return t if the package is installed, nil otherwise. (This is +determined by the return value of `use-package-ensure-function'.) +If the package is installed, its entry is removed from +`use-package--deferred-packages'. If the package has no entry in +`use-package--deferred-packages', do nothing and return t." + (interactive + (let ((packages nil)) + (maphash (lambda (package info) + (push package packages)) + use-package--deferred-packages) + (if packages + (list + (completing-read + "Select package: " + packages + nil + 'require-match) + :interactive) + (user-error "No packages with deferred installation")))) + (let ((spec (gethash name use-package--deferred-packages))) + (if spec + (when (funcall use-package-ensure-function + name (car spec) (cdr spec) + (or context :unknown)) + (remhash name use-package--deferred-packages) + t) + t))) + +(defalias 'use-package-normalize/:defer-install 'use-package-normalize-test) + +(defun use-package-handler/:defer-install (name keyword defer rest state) + (use-package-process-keywords name rest + ;; Just specifying `:defer-install' does not do anything; this + ;; sets up a marker so that if `:ensure' is specified as well then + ;; it knows to set up deferred installation. But then later, when + ;; `:config' is processed, it might turn out that `:demand' was + ;; specified as well, and the deferred installation needs to be + ;; run immediately. For this we need to know if the deferred + ;; installation was actually set up or not, so we need to set one + ;; marker value in `:defer-install', and then change it to a + ;; different value in `:ensure', if the first one is present. (The + ;; first marker is `:defer-install', and the second is `:ensure'.) + (plist-put state :defer-install (when defer :defer-install)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ;;; :ensure ;; (defvar package-archive-contents) @@ -588,31 +707,60 @@ manually updated package." (concat ":ensure wants an optional package name " "(an unquoted symbol name)"))))))) -(defun use-package-ensure-elpa (package &optional no-refresh) - (require 'package) - (if (package-installed-p package) - t - (if (and (not no-refresh) - (assoc package (bound-and-true-p package-pinned-packages))) - (package-read-all-archive-contents)) - (if (or (assoc package package-archive-contents) no-refresh) - (package-install package) - (progn - (package-refresh-contents) - (use-package-ensure-elpa package t))))) +(defun use-package-ensure-elpa (name ensure state context &optional no-refresh) + (let ((package (or (when (eq ensure t) (use-package-as-symbol name)) + ensure))) + (when package + (require 'package) + (or (package-installed-p package) + (not (or + ;; Contexts in which the confirmation prompt is + ;; bypassed. + (member context '(:byte-compile :ensure :config)) + (y-or-n-p (format "Install package %S?" package)))) + (progn + (when (assoc package (bound-and-true-p package-pinned-packages)) + (package-read-all-archive-contents)) + (if (assoc package package-archive-contents) + (progn (package-install package) t) + (progn + (package-refresh-contents) + (when (assoc package (bound-and-true-p + package-pinned-packages)) + (package-read-all-archive-contents)) + (package-install package)))))))) (defun use-package-handler/:ensure (name keyword ensure rest state) - (let* ((body (use-package-process-keywords name rest state)) - (package-name (or (and (eq ensure t) (use-package-as-symbol name)) ensure)) - (ensure-form (when package-name - `(,use-package-ensure-function ',package-name)))) + (let* ((body (use-package-process-keywords name rest + ;; Here we are conditionally updating the marker + ;; value for deferred installation; this will be + ;; checked later by `:config'. For more information + ;; see `use-package-handler/:defer-install'. + (if (eq (plist-get state :defer-install) + :defer-install) + (plist-put state :defer-install :ensure) + state)))) ;; We want to avoid installing packages when the `use-package' ;; macro is being macro-expanded by elisp completion (see ;; `lisp--local-variables'), but still do install packages when ;; byte-compiling to avoid requiring `package' at runtime. - (if (bound-and-true-p byte-compile-current-file) - (eval ensure-form) ; Eval when byte-compiling, - (push ensure-form body)) ; or else wait until runtime. + (cond + ((plist-get state :defer-install) + (push + `(puthash ',name '(,ensure . ,state) + use-package--deferred-packages) + body) + (push `(,use-package-pre-ensure-function + ',name ',ensure ',state) + body)) + ((bound-and-true-p byte-compile-current-file) + ;; Eval when byte-compiling, + (funcall use-package-ensure-function + name ensure state :byte-compile)) + ;; or else wait until runtime. + (t (push `(,use-package-ensure-function + ',name ',ensure ',state :ensure) + body))) body)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1007,6 +1155,35 @@ deferred until the prefix key sequence is pressed." (defalias 'use-package-normalize/:defer 'use-package-normalize-predicate) +(defun use-package--autoload-with-deferred-install + (command package-name) + "Return a form defining an autoload supporting deferred install." + `(let* ((load-list-item '(defun . ,command)) + (already-loaded (member load-list-item current-load-list))) + (defun ,command (&rest args) + "[Arg list not available until function definition is loaded.] + +\(fn ...)" + (interactive) + (if (bound-and-true-p use-package--recursive-autoload) + (use-package-error + (format "Autoloading failed to define function %S" + command)) + (when (use-package-install-deferred-package + ',package-name :autoload) + (require ',package-name) + (let ((use-package--recursive-autoload t)) + (if (called-interactively-p 'any) + (call-interactively ',command) + (apply ',command args)))))) + ;; This prevents the user's init-file from being recorded as the + ;; definition location for the function before it is actually + ;; loaded. (Our goal is to leave the `current-load-list' + ;; unchanged, so we only remove the entry for this function if it + ;; was not already present.) + (unless already-loaded + (setq current-load-list (remove load-list-item current-load-list))))) + (defun use-package-handler/:defer (name keyword arg rest state) (let ((body (use-package-process-keywords name rest (plist-put state :deferred t))) @@ -1021,15 +1198,24 @@ deferred until the prefix key sequence is pressed." ;; keep the byte-compiler happy. (apply #'nconc - (mapcar #'(lambda (command) - (when (not (stringp command)) - (append - `((unless (fboundp ',command) - (autoload #',command ,name-string nil t))) - (when (bound-and-true-p byte-compile-current-file) - `((eval-when-compile - (declare-function ,command ,name-string))))))) - (delete-dups (plist-get state :commands)))) + (mapcar + #'(lambda (command) + (when (not (stringp command)) + (append + `((unless (fboundp ',command) + ;; Here we are checking the marker value set in + ;; `use-package-handler/:ensure' to see if deferred + ;; installation is actually happening. See + ;; `use-package-handler/:defer-install' for more + ;; information. + ,(if (eq (plist-get state :defer-install) :ensure) + (use-package--autoload-with-deferred-install + command name) + `(autoload #',command ,name-string nil t)))) + (when (bound-and-true-p byte-compile-current-file) + `((eval-when-compile + (declare-function ,command ,name-string))))))) + (delete-dups (plist-get state :commands)))) body))) @@ -1041,7 +1227,8 @@ deferred until the prefix key sequence is pressed." (defalias 'use-package-normalize/:after 'use-package-normalize-recursive-symlist) -(defun use-package-require-after-load (features) +(defun use-package-require-after-load + (features) "Return form for after any of FEATURES require NAME." (pcase features ((and (pred symbolp) feat) @@ -1076,7 +1263,14 @@ deferred until the prefix key sequence is pressed." (use-package-concat (when arg (list (funcall (use-package-require-after-load arg) - `(require (quote ,name) nil t)))) + (macroexp-progn + ;; Here we are checking the marker value for deferred + ;; installation set in `use-package-handler/:ensure'. + ;; See also `use-package-handler/:defer-install'. + `(,@(when (eq (plist-get state :defer-install) :ensure) + `((use-package-install-deferred-package + 'name :after))) + '(require (quote ,name) nil t)))))) body))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1134,6 +1328,11 @@ deferred until the prefix key sequence is pressed." (unless (or (null config-body) (equal config-body '(t))) `((eval-after-load ,(if (symbolp name) `',name name) ',(macroexp-progn config-body)))) + ;; Here we are checking the marker value for deferred + ;; installation set in `use-package-handler/:ensure'. See also + ;; `use-package-handler/:defer-install'. + (when (eq (plist-get state :defer-install) :ensure) + (use-package-install-deferred-package name :config)) (use-package--with-elapsed-timer (format "Loading package %s" name) (if use-package-expand-minimally |