(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'.
-
-The default value uses package.el to install the package."
+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 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
;;
(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\"
`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'.
+
+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 no-prompt)
+(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)
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)
;; 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))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(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
(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))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(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)
(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)
(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)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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