From 4e6115214b24b5e52fddcb53040045890d01f59a Mon Sep 17 00:00:00 2001 From: Radon Rosborough Date: Wed, 8 Mar 2017 11:17:33 -0800 Subject: [PATCH 1/7] Extend capabilities of use-package-ensure-function Modify the expected API of `use-package-ensure-function' so that it is passed three arguments: the name of the package declared in the `use-package' form; the argument passed to `:ensure'; and the current `state' plist created by previous handlers. (Previously, it was only given a single argument, which was the argument passed to `:ensure', or the name of the package declared in the `use-package' form, if the former was `t'. This allows for more flexibility in the capabilities of the `use-package-ensure-function' implementation. For example, its behavior can change depending on the values of other keywords, if those keywords modify the `state' plist appropriately. --- lisp/use-package/use-package.el | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/lisp/use-package/use-package.el b/lisp/use-package/use-package.el index f933ce2d936..2ef697201f8 100644 --- a/lisp/use-package/use-package.el +++ b/lisp/use-package/use-package.el @@ -566,24 +566,26 @@ 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 &optional no-refresh) + (let ((package (or (and (eq ensure t) (use-package-as-symbol name)) + ensure))) + (when package + (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 name ensure state t))))))) (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)))) + (ensure-form `(,use-package-ensure-function + ',name ',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 From f6224b295622d5a7065a107b1323486fa9822387 Mon Sep 17 00:00:00 2001 From: Radon Rosborough Date: Wed, 8 Mar 2017 12:28:40 -0800 Subject: [PATCH 2/7] First cut at :defer-install keyword This new keyword, if provided along with a non-nil value, causes the action of :ensure to be deferred until "necessary". Package installation can be triggered by the user calling the new interactive function `use-package-install-deferred-package', or by the feature declared by the `use-package' form being required. This latter behavior seems to be the simplest way to make sure that package installation actually takes place when it needs to, but it requires that an advice be added to `require', which may be considered overly intrusive. (Also, it's generally considered bad practice for functions in Emacs to put advice on other functions in Emacs.) Thus it may make sense to add an option or function to explicitly enable this behavior, if there does not turn out to be a better way to accomplish deferred installation. Documentation has not been updated to reflect :defer-install yet. --- lisp/use-package/use-package.el | 85 +++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/lisp/use-package/use-package.el b/lisp/use-package/use-package.el index 2ef697201f8..3d5c99919d6 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 @@ -550,6 +551,75 @@ manually updated package." (push pin-form body)) ; or else wait until runtime. body)) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;;; :defer-install +;; + +(defvar use-package--deferred-packages (make-hash-table) + "Hash mapping packages to forms which install them. +If `use-package' needs to install one of the named packages, it +will evaluate the corresponding form to do so. + +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'. The values are unevaluated Lisp forms. ") + +(defun use-package-install-deferred-package + (name &optional no-prompt) + "Install a package whose installation has been deferred. +NAME should be a symbol naming a package (actually, a feature). +The user is prompted for confirmation first, unless NO-PROMPT is +non-nil." + (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) + 'no-prompt) + (user-error "No packages with deferred installation")))) + (when (or no-prompt + (y-or-n-p (format "Install package %S? " name))) + (eval (gethash name use-package--deferred-packages)) + (let ((features nil)) + (maphash (lambda (feature package) + (when (eq package name) + (push feature features))) + use-package--deferred-features) + (dolist (feature features) + (remhash feature use-package--deferred-features))) + (remhash name use-package--deferred-packages))) + +(defun use-package--require-advice (require feature &optional + filename noerror) + "Advice for `require' to support `:defer-install'. +If there is a package with deferred installation enabled that is +expected to provide the requested feature, that package is +installed first (if the user confirms it) and then the `require' +proceeds." + (when (gethash feature use-package--deferred-packages) + (use-package-install-deferred-package feature)) + (funcall require feature filename noerror)) + +(advice-add #'require :around #'use-package--require-advice) + +(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 + (plist-put state :defer-install defer))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; :ensure @@ -585,14 +655,21 @@ manually updated package." (defun use-package-handler/:ensure (name keyword ensure rest state) (let* ((body (use-package-process-keywords name rest state)) (ensure-form `(,use-package-ensure-function - ',name ',ensure ',state))) + ',name ',ensure ',state)) + (defer-install (plist-get state :defer-install))) ;; 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 + (defer-install + (push + `(puthash ',name ',ensure-form + use-package--deferred-packages) + body)) + ((bound-and-true-p byte-compile-current-file) + (eval ensure-form)) ; Eval when byte-compiling, + (t (push ensure-form body))) ; or else wait until runtime. body)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; From a233f01ff64a3cb4002c3913883035bb1b299208 Mon Sep 17 00:00:00 2001 From: Radon Rosborough Date: Wed, 8 Mar 2017 18:16:31 -0800 Subject: [PATCH 3/7] Update docstring for use-package-ensure-function Now it properly reflects the API changes recently made. --- lisp/use-package/use-package.el | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lisp/use-package/use-package.el b/lisp/use-package/use-package.el index 2ef697201f8..13ba63446c1 100644 --- a/lisp/use-package/use-package.el +++ b/lisp/use-package/use-package.el @@ -184,8 +184,12 @@ 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. +This function is called with three arguments: the name of the +package declared in the `use-package' form; the argument passed +to `:ensure'; and the current `state' plist created by previous +handlers. 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'. The default value uses package.el to install the package." :type '(choice (const :tag "package.el" use-package-ensure-elpa) From 855a2afbe3167fe6bb9f1993fb825fd180605dd3 Mon Sep 17 00:00:00 2001 From: Radon Rosborough Date: Wed, 8 Mar 2017 19:00:37 -0800 Subject: [PATCH 4/7] Improve deferred installation mechanism This time around, I've gotten rid of the advice on `require' (that was never going to work) and instead made `use-package' try to handle loading the package at the appropriate time. In particular, when deferred installation is active, all the autoloads generated by `use-package' are not regular autoloads, but regular functions that will install the relevant package, require the relevant feature, and only then call the newly defined (autoloaded) function. Some smarter logic has been added to make sure things like `:demand' play nicely with the autoloading system; see the extensive comment in `use-package-handler/:defer-install' for more information on how that works. There was a section in `use-package-install-deferred-package' which referred to a nonexistent variable `use-package--deferred-features'; that has been removed. There is now, in addition to `use-package-ensure-function', a new variable called `use-package-pre-ensure-function'. This is intended for use by package managers which, unlike package.el, activate autoloads package-by-package instead of all at once. Even if a package is marked for deferred installation, the user would likely want its autoloads activated immediately *if* it was already installed. The logic for doing that can now be put in `use-package-pre-ensure-function'. --- lisp/use-package/use-package.el | 108 ++++++++++++++++++++++---------- 1 file changed, 76 insertions(+), 32 deletions(-) diff --git a/lisp/use-package/use-package.el b/lisp/use-package/use-package.el index 6695c19af64..8902b406712 100644 --- a/lisp/use-package/use-package.el +++ b/lisp/use-package/use-package.el @@ -197,6 +197,22 @@ The default value uses package.el to install the package." (function :tag "Custom")) :group 'use-package) +(defcustom use-package-pre-ensure-function 'ignore + "Function that is called upon installation deferral. +It is called with the same arguments as +`use-package-ensure-function', 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) @@ -596,33 +612,23 @@ non-nil." (when (or no-prompt (y-or-n-p (format "Install package %S? " name))) (eval (gethash name use-package--deferred-packages)) - (let ((features nil)) - (maphash (lambda (feature package) - (when (eq package name) - (push feature features))) - use-package--deferred-features) - (dolist (feature features) - (remhash feature use-package--deferred-features))) (remhash name use-package--deferred-packages))) -(defun use-package--require-advice (require feature &optional - filename noerror) - "Advice for `require' to support `:defer-install'. -If there is a package with deferred installation enabled that is -expected to provide the requested feature, that package is -installed first (if the user confirms it) and then the `require' -proceeds." - (when (gethash feature use-package--deferred-packages) - (use-package-install-deferred-package feature)) - (funcall require feature filename noerror)) - -(advice-add #'require :around #'use-package--require-advice) - (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 - (plist-put state :defer-install defer))) + ;; 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 `:ensure', and the second is `:defer'.) + (plist-put state :defer-install (when defer :defer-install)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; @@ -657,7 +663,17 @@ proceeds." (use-package-ensure-elpa name ensure state t))))))) (defun use-package-handler/:ensure (name keyword ensure rest state) - (let* ((body (use-package-process-keywords name rest state)) + (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))) + (pre-ensure-form `(,use-package-pre-ensure-function + ',name ',ensure ',state)) (ensure-form `(,use-package-ensure-function ',name ',ensure ',state)) (defer-install (plist-get state :defer-install))) @@ -670,7 +686,8 @@ proceeds." (push `(puthash ',name ',ensure-form use-package--deferred-packages) - body)) + body) + (push pre-ensure-form body)) ((bound-and-true-p byte-compile-current-file) (eval ensure-form)) ; Eval when byte-compiling, (t (push ensure-form body))) ; or else wait until runtime. @@ -1047,6 +1064,19 @@ 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." + `(defun ,command (&rest args) + (if (bound-and-true-p use-package--recursive-autoload) + (use-package-error + (format "Autoloading failed to define function %S" + command)) + (use-package-install-deferred-package ',package-name) + (require ',package-name) + (let ((use-package--recursive-autoload t)) + (funcall ',command args))))) + (defun use-package-handler/:defer (name keyword arg rest state) (let ((body (use-package-process-keywords name rest (plist-put state :deferred t))) @@ -1061,15 +1091,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))) @@ -1155,6 +1194,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 'no-prompt)) (use-package--with-elapsed-timer (format "Loading package %s" name) (if use-package-expand-minimally From 57e38152e143ef9c447c015c09e7f375737f4b8e Mon Sep 17 00:00:00 2001 From: Radon Rosborough Date: Wed, 8 Mar 2017 20:05:15 -0800 Subject: [PATCH 5/7] Get :defer-install completely working, in theory * A quoting error has been fixed in `use-package-handler/:defer'. * `use-package-install-deferred-package' has been updated to return t if the package was actually installed, and nil otherwise. * The fake autoloads generated during deferred installation are doctored so Emacs does not think they were defined in the user's init-file. * The docstrings of the fake autoloads have been improved. * Arguments and interactivity are now correctly passed to the autoloaded function. * The autoload now skips requiring the feature and calling the original function if the user declines to install the package. This prevents unprofessional errors. --- lisp/use-package/use-package.el | 40 +++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/lisp/use-package/use-package.el b/lisp/use-package/use-package.el index 8902b406712..94f5229eca8 100644 --- a/lisp/use-package/use-package.el +++ b/lisp/use-package/use-package.el @@ -594,7 +594,7 @@ the value of `:ensure'. The values are unevaluated Lisp forms. ") "Install a package whose installation has been deferred. NAME should be a symbol naming a package (actually, a feature). The user is prompted for confirmation first, unless NO-PROMPT is -non-nil." +non-nil. Return t if the package is installed, nil otherwise." (interactive (let ((packages nil)) (maphash (lambda (package info) @@ -612,7 +612,8 @@ non-nil." (when (or no-prompt (y-or-n-p (format "Install package %S? " name))) (eval (gethash name use-package--deferred-packages)) - (remhash name use-package--deferred-packages))) + (remhash name use-package--deferred-packages) + t)) (defalias 'use-package-normalize/:defer-install 'use-package-normalize-test) @@ -1067,15 +1068,30 @@ deferred until the prefix key sequence is pressed." (defun use-package--autoload-with-deferred-install (command package-name) "Return a form defining an autoload supporting deferred install." - `(defun ,command (&rest args) - (if (bound-and-true-p use-package--recursive-autoload) - (use-package-error - (format "Autoloading failed to define function %S" - command)) - (use-package-install-deferred-package ',package-name) - (require ',package-name) - (let ((use-package--recursive-autoload t)) - (funcall ',command args))))) + `(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) + (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 @@ -1103,7 +1119,7 @@ deferred until the prefix key sequence is pressed." ;; information. ,(if (eq (plist-get state :defer-install) :ensure) (use-package--autoload-with-deferred-install - ',command ',name) + command name) `(autoload #',command ,name-string nil t)))) (when (bound-and-true-p byte-compile-current-file) `((eval-when-compile From ecc5fddda43aa9163c65e4f1cfe2cd538198b07a Mon Sep 17 00:00:00 2001 From: Radon Rosborough Date: Sat, 18 Mar 2017 19:34:28 -0700 Subject: [PATCH 6/7] Various improvements for deferred installation --- lisp/use-package/use-package.el | 161 +++++++++++++++++++++----------- 1 file changed, 109 insertions(+), 52 deletions(-) diff --git a/lisp/use-package/use-package.el b/lisp/use-package/use-package.el index 94f5229eca8..0c2ee5b8b5d 100644 --- a/lisp/use-package/use-package.el +++ b/lisp/use-package/use-package.el @@ -185,21 +185,43 @@ 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 three arguments: the name of the +This function is called with four arguments: the name of the package declared in the `use-package' form; the argument passed -to `:ensure'; and the current `state' plist created by previous -handlers. 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'. +to `:ensure'; the current `state' plist created by previous +handlers; and a keyword indicating the context in which the +installation is occurring. -The default value uses package.el to install the package." +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 with the same arguments as +It is called immediately with the same arguments as `use-package-ensure-function', 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 @@ -577,9 +599,7 @@ manually updated package." ;; (defvar use-package--deferred-packages (make-hash-table) - "Hash mapping packages to forms which install them. -If `use-package' needs to install one of the named packages, it -will evaluate the corresponding form to do so. + "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\" @@ -587,14 +607,29 @@ 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'. The values are unevaluated Lisp forms. ") +the value of `:ensure'. -(defun use-package-install-deferred-package - (name &optional no-prompt) +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). -The user is prompted for confirmation first, unless NO-PROMPT is -non-nil. Return t if the package is installed, nil otherwise." +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) @@ -607,13 +642,16 @@ non-nil. Return t if the package is installed, nil otherwise." packages nil 'require-match) - 'no-prompt) + :interactive) (user-error "No packages with deferred installation")))) - (when (or no-prompt - (y-or-n-p (format "Install package %S? " name))) - (eval (gethash name use-package--deferred-packages)) - (remhash name use-package--deferred-packages) - t)) + (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) @@ -628,7 +666,7 @@ non-nil. Return t if the package is installed, nil otherwise." ;; 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 `:ensure', and the second is `:defer'.) + ;; first marker is `:defer-install', and the second is `:ensure'.) (plist-put state :defer-install (when defer :defer-install)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -647,21 +685,28 @@ non-nil. Return t if the package is installed, nil otherwise." (concat ":ensure wants an optional package name " "(an unquoted symbol name)"))))))) -(defun use-package-ensure-elpa (name ensure state &optional no-refresh) - (let ((package (or (and (eq ensure t) (use-package-as-symbol name)) +(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) - (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) + (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?" name)))) (progn - (package-refresh-contents) - (use-package-ensure-elpa name ensure state t))))))) + (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 @@ -672,26 +717,28 @@ non-nil. Return t if the package is installed, nil otherwise." (if (eq (plist-get state :defer-install) :defer-install) (plist-put state :defer-install :ensure) - state))) - (pre-ensure-form `(,use-package-pre-ensure-function - ',name ',ensure ',state)) - (ensure-form `(,use-package-ensure-function - ',name ',ensure ',state)) - (defer-install (plist-get state :defer-install))) + 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. (cond - (defer-install - (push - `(puthash ',name ',ensure-form - use-package--deferred-packages) - body) - (push pre-ensure-form body)) + ((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 ensure-form)) ; Eval when byte-compiling, - (t (push ensure-form body))) ; or else wait until runtime. + ;; 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)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1079,7 +1126,8 @@ deferred until the prefix key sequence is pressed." (use-package-error (format "Autoloading failed to define function %S" command)) - (when (use-package-install-deferred-package ',package-name) + (when (use-package-install-deferred-package + ',package-name :autoload) (require ',package-name) (let ((use-package--recursive-autoload t)) (if (called-interactively-p 'any) @@ -1136,14 +1184,19 @@ deferred until the prefix key sequence is pressed." (defalias 'use-package-normalize/:after 'use-package-normalize-symlist) -(defun use-package-require-after-load (features name) +(defun use-package-require-after-load + (features name &optional deferred-install) "Return form for after any of FEATURES require NAME." `(progn ,@(mapcar (lambda (feat) `(eval-after-load (quote ,feat) - (quote (require (quote ,name) nil t)))) + ,(macroexp-progn + `(,@(when deferred-install + `((use-package-install-deferred-package + ',name :after))) + '(require ',name nil t))))) features))) (defun use-package-handler/:after (name keyword arg rest state) @@ -1152,7 +1205,11 @@ deferred until the prefix key sequence is pressed." (name-string (use-package-as-string name))) (use-package-concat (when arg - (list (use-package-require-after-load arg name))) + (list (use-package-require-after-load + ;; Here we are checking the marker value for deferred + ;; installation set in `use-package-handler/:ensure'. + ;; See also `use-package-handler/:defer-install'. + arg name (eq (plist-get state :defer-install) :ensure)))) body))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1214,7 +1271,7 @@ deferred until the prefix key sequence is pressed." ;; 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 'no-prompt)) + (use-package-install-deferred-package name 'no-prompt :config)) (use-package--with-elapsed-timer (format "Loading package %s" name) (if use-package-expand-minimally From b2e674de0a30c5961e7e31a8658b3eba9a229ba1 Mon Sep 17 00:00:00 2001 From: Radon Rosborough Date: Sat, 18 Mar 2017 20:00:53 -0700 Subject: [PATCH 7/7] Update docstring, installation prompt message --- lisp/use-package/use-package.el | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lisp/use-package/use-package.el b/lisp/use-package/use-package.el index 0c2ee5b8b5d..45215eccdaa 100644 --- a/lisp/use-package/use-package.el +++ b/lisp/use-package/use-package.el @@ -221,16 +221,16 @@ with the appropriate value." (defcustom use-package-pre-ensure-function 'ignore "Function that is called upon installation deferral. -It is called immediately with the same arguments as -`use-package-ensure-function', 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." +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) @@ -695,7 +695,7 @@ If the package is installed, its entry is removed from ;; Contexts in which the confirmation prompt is ;; bypassed. (member context '(:byte-compile :ensure :config)) - (y-or-n-p (format "Install package %S?" name)))) + (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))