From f6224b295622d5a7065a107b1323486fa9822387 Mon Sep 17 00:00:00 2001 From: Radon Rosborough Date: Wed, 8 Mar 2017 12:28:40 -0800 Subject: [PATCH] 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)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -- 2.39.2