summaryrefslogtreecommitdiff
path: root/lisp/emacs-lisp/package.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/emacs-lisp/package.el')
-rw-r--r--lisp/emacs-lisp/package.el285
1 files changed, 176 insertions, 109 deletions
diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el
index c56502236ee..94d98178c4d 100644
--- a/lisp/emacs-lisp/package.el
+++ b/lisp/emacs-lisp/package.el
@@ -101,7 +101,7 @@
;; Michael Olson <mwolson@member.fsf.org>
;; Sebastian Tennant <sebyte@smolny.plus.com>
;; Stefan Monnier <monnier@iro.umontreal.ca>
-;; Vinicius Jose Latorre <viniciusjl@ig.com.br>
+;; Vinicius Jose Latorre <viniciusjl.gnu@gmail.com>
;; Phil Hagelberg <phil@hagelb.org>
;;; ToDo:
@@ -143,8 +143,8 @@
;;; Code:
+(require 'cl-lib)
(eval-when-compile (require 'subr-x))
-(eval-when-compile (require 'cl-lib))
(eval-when-compile (require 'epg)) ;For setf accessors.
(require 'seq)
@@ -161,29 +161,34 @@
;;; Customization options
;;;###autoload
(defcustom package-enable-at-startup t
- "Whether to activate installed packages when Emacs starts.
-If non-nil, packages are activated after reading the init file
-and before `after-init-hook'. Activation is not done if
-`user-init-file' is nil (e.g. Emacs was started with \"-q\").
+ "Whether to make installed packages available when Emacs starts.
+If non-nil, packages are made available before reading the init
+file (but after reading the early init file). This means that if
+you wish to set this variable, you must do so in the early init
+file. Regardless of the value of this variable, packages are not
+made available if `user-init-file' is nil (e.g. Emacs was started
+with \"-q\").
Even if the value is nil, you can type \\[package-initialize] to
-activate the package system at any time."
+make installed packages available at any time, or you can
+call (package-initialize) in your init-file."
:type 'boolean
:version "24.1")
(defcustom package-load-list '(all)
- "List of packages for `package-initialize' to load.
+ "List of packages for `package-initialize' to make available.
Each element in this list should be a list (NAME VERSION), or the
-symbol `all'. The symbol `all' says to load the latest installed
-versions of all packages not specified by other elements.
+symbol `all'. The symbol `all' says to make available the latest
+installed versions of all packages not specified by other
+elements.
For an element (NAME VERSION), NAME is a package name (a symbol).
VERSION should be t, a string, or nil.
-If VERSION is t, the most recent version is activated.
-If VERSION is a string, only that version is ever loaded.
+If VERSION is t, the most recent version is made available.
+If VERSION is a string, only that version is ever made available.
Any other version, even if newer, is silently ignored.
Hence, the package is \"held\" at that version.
-If VERSION is nil, the package is not loaded (it is \"disabled\")."
+If VERSION is nil, the package is not made available (it is \"disabled\")."
:type '(repeat (choice (const all)
(list :tag "Specific package"
(symbol :tag "Package name")
@@ -676,6 +681,9 @@ PKG-DESC is a `package-desc' object."
(defvar Info-directory-list)
(declare-function info-initialize "info" ())
+(defvar package--quickstart-pkgs t
+ "If set to a list, we're computing the set of pkgs to activate.")
+
(defun package--load-files-for-activation (pkg-desc reload)
"Load files for activating a package given by PKG-DESC.
Load the autoloads file, and ensure `load-path' is setup. If
@@ -718,7 +726,10 @@ correspond to previously loaded files (those returned by
(message "Unable to activate package `%s'.\nRequired package `%s-%s' is unavailable"
name (car req) (package-version-join (cadr req)))
(throw 'exit nil))))
- (package--load-files-for-activation pkg-desc reload)
+ (if (listp package--quickstart-pkgs)
+ ;; We're only collecting the set of packages to activate!
+ (push pkg-desc package--quickstart-pkgs)
+ (package--load-files-for-activation pkg-desc reload))
;; Add info node.
(when (file-exists-p (expand-file-name "dir" pkg-dir))
;; FIXME: not the friendliest, but simple.
@@ -961,17 +972,12 @@ This assumes that `pkg-desc' has already been activated with
(defun package-read-from-string (str)
"Read a Lisp expression from STR.
Signal an error if the entire string was not used."
- (let* ((read-data (read-from-string str))
- (more-left
- (condition-case nil
- ;; The call to `ignore' suppresses a compiler warning.
- (progn (ignore (read-from-string
- (substring str (cdr read-data))))
- t)
- (end-of-file nil))))
- (if more-left
- (error "Can't read whole string")
- (car read-data))))
+ (pcase-let ((`(,expr . ,offset) (read-from-string str)))
+ (condition-case ()
+ ;; The call to `ignore' suppresses a compiler warning.
+ (progn (ignore (read-from-string str offset))
+ (error "Can't read whole string"))
+ (end-of-file expr))))
(defun package--prepare-dependencies (deps)
"Turn DEPS into an acceptable list of dependencies.
@@ -1436,45 +1442,61 @@ If successful, set `package-archive-contents'."
;; available on disk.
(defvar package--initialized nil)
-(defvar package--init-file-ensured nil
- "Whether we know the init file has package-initialize.")
-
;;;###autoload
(defun package-initialize (&optional no-activate)
"Load Emacs Lisp packages, and activate them.
The variable `package-load-list' controls which packages to load.
If optional arg NO-ACTIVATE is non-nil, don't activate packages.
-If `user-init-file' does not mention `(package-initialize)', add
-it to the file.
If called as part of loading `user-init-file', set
`package-enable-at-startup' to nil, to prevent accidentally
loading packages twice.
+
It is not necessary to adjust `load-path' or `require' the
individual packages after calling `package-initialize' -- this is
-taken care of by `package-initialize'."
+taken care of by `package-initialize'.
+
+If `package-initialize' is called twice during Emacs startup,
+signal a warning, since this is a bad idea except in highly
+advanced use cases. To suppress the warning, remove the
+superfluous call to `package-initialize' from your init-file. If
+you have code which must run before `package-initialize', put
+that code in the early init-file."
(interactive)
+ (when (and package--initialized (not after-init-time))
+ (lwarn '(package reinitialization) :warning
+ "Unnecessary call to `package-initialize' in init file"))
(setq package-alist nil)
- (if after-init-time
- (package--ensure-init-file)
- ;; If `package-initialize' is before we finished loading the init
- ;; file, it's obvious we don't need to ensure-init.
- (setq package--init-file-ensured t
- ;; And likely we don't need to run it again after init.
- package-enable-at-startup nil))
+ (setq package-enable-at-startup nil)
(package-load-all-descriptors)
(package-read-all-archive-contents)
+ (setq package--initialized t)
(unless no-activate
+ (package-activate-all))
+ ;; This uses `package--mapc' so it must be called after
+ ;; `package--initialized' is t.
+ (package--build-compatibility-table))
+
+(defvar package-quickstart-file)
+
+;;;###autoload
+(defun package-activate-all ()
+ "Activate all installed packages.
+The variable `package-load-list' controls which packages to load."
+ (setq package-enable-at-startup nil)
+ (if (file-readable-p package-quickstart-file)
+ ;; Skip load-source-file-function which would slow us down by a factor
+ ;; 2 (this assumes we were careful to save this file so it doesn't need
+ ;; any decoding).
+ (let ((load-source-file-function nil))
+ (load package-quickstart-file))
+ (unless package--initialized
+ (package-initialize t))
(dolist (elt package-alist)
(condition-case err
(package-activate (car elt))
;; Don't let failure of activation of a package arbitrarily stop
;; activation of further packages.
- (error (message "%s" (error-message-string err))))))
- (setq package--initialized t)
- ;; This uses `package--mapc' so it must be called after
- ;; `package--initialized' is t.
- (package--build-compatibility-table))
-
+ (error (message "%s" (error-message-string err)))))))
;;;; Populating `package-archive-contents' from archives
;; This subsection populates the variables listed above from the
@@ -1856,18 +1878,26 @@ If PACKAGE is a symbol, it is the package name and MIN-VERSION
should be a version list.
If PACKAGE is a `package-desc' object, MIN-VERSION is ignored."
- (unless package--initialized (error "package.el is not yet initialized!"))
- (if (package-desc-p package)
- (let ((dir (package-desc-dir package)))
+ (cond
+ ((package-desc-p package)
+ (let ((dir (package-desc-dir package)))
(and (stringp dir)
- (file-exists-p dir)))
+ (file-exists-p dir))))
+ ((and (not package--initialized)
+ (null min-version)
+ package-activated-list)
+ ;; We used the quickstart: make it possible to use package-installed-p
+ ;; even before package is fully initialized.
+ (memq package package-activated-list))
+ ((not package--initialized) (error "package.el is not yet initialized!"))
+ (t
(or
(let ((pkg-descs (cdr (assq package package-alist))))
(and pkg-descs
(version-list-<= min-version
(package-desc-version (car pkg-descs)))))
;; Also check built-in packages.
- (package-built-in-p package min-version))))
+ (package-built-in-p package min-version)))))
(defun package-download-transaction (packages)
"Download and install all the packages in PACKAGES.
@@ -1877,64 +1907,6 @@ PACKAGES are satisfied, i.e. that PACKAGES is computed
using `package-compute-transaction'."
(mapc #'package-install-from-archive packages))
-(defun package--ensure-init-file ()
- "Ensure that the user's init file has `package-initialize'.
-`package-initialize' doesn't have to be called, as long as it is
-present somewhere in the file, even as a comment. If it is not,
-add a call to it along with some explanatory comments."
- ;; Don't mess with the init-file from "emacs -Q".
- (when (and (stringp user-init-file)
- (not package--init-file-ensured)
- (file-readable-p user-init-file)
- (file-writable-p user-init-file))
- (let* ((buffer (find-buffer-visiting user-init-file))
- buffer-name
- (contains-init
- (if buffer
- (with-current-buffer buffer
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (re-search-forward "(package-initialize\\_>" nil 'noerror))))
- ;; Don't visit the file if we don't have to.
- (with-temp-buffer
- (insert-file-contents user-init-file)
- (goto-char (point-min))
- (re-search-forward "(package-initialize\\_>" nil 'noerror)))))
- (unless contains-init
- (with-current-buffer (or buffer
- (let ((delay-mode-hooks t)
- (find-file-visit-truename t))
- (find-file-noselect user-init-file)))
- (when buffer
- (setq buffer-name (buffer-file-name))
- (set-visited-file-name (file-chase-links user-init-file)))
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (while (and (looking-at-p "[[:blank:]]*\\(;\\|$\\)")
- (not (eobp)))
- (forward-line 1))
- (insert
- "\n"
- ";; Added by Package.el. This must come before configurations of\n"
- ";; installed packages. Don't delete this line. If you don't want it,\n"
- ";; just comment it out by adding a semicolon to the start of the line.\n"
- ";; You may delete these explanatory comments.\n"
- "(package-initialize)\n")
- (unless (looking-at-p "$")
- (insert "\n"))
- (let ((file-precious-flag t))
- (save-buffer))
- (if buffer
- (progn
- (set-visited-file-name buffer-name)
- (set-buffer-modified-p nil))
- (kill-buffer (current-buffer)))))))))
- (setq package--init-file-ensured t))
-
;;;###autoload
(defun package-install (pkg &optional dont-select)
"Install the package PKG.
@@ -1976,7 +1948,9 @@ to install it but still mark it as selected."
(package-compute-transaction (list pkg)
(package-desc-reqs pkg)))
(package-compute-transaction () (list (list pkg))))))
- (package-download-transaction transaction)
+ (progn
+ (package-download-transaction transaction)
+ (package--quickstart-maybe-refresh))
(message "`%s' is already installed" name))))
(defun package-strip-rcs-id (str)
@@ -2148,7 +2122,9 @@ If NOSAVE is non-nil, the package is not removed from
(delete pkg-desc pkgs)
(unless (cdr pkgs)
(setq package-alist (delq pkgs package-alist))))
- (message "Package `%s' deleted." (package-desc-full-name pkg-desc))))))
+ (package--quickstart-maybe-refresh)
+ (message "Package `%s' deleted."
+ (package-desc-full-name pkg-desc))))))
;;;###autoload
(defun package-reinstall (pkg)
@@ -3473,6 +3449,97 @@ The list is displayed in a buffer named `*Packages*'."
(interactive)
(list-packages t))
+;;;; Quickstart: precompute activation actions for faster start up.
+
+;; Activating packages via `package-initialize' is costly: for N installed
+;; packages, it needs to read all N <pkg>-pkg.el files first to decide
+;; which packages to activate, and then again N <pkg>-autoloads.el files.
+;; To speed this up, we precompute a mega-autoloads file which is the
+;; concatenation of all those <pkg>-autoloads.el, so we can activate
+;; all packages by loading this one file (and hence without initializing
+;; package.el).
+
+;; Other than speeding things up, this also offers a bootstrap feature:
+;; it lets us activate packages according to `package-load-list' and
+;; `package-user-dir' even before those vars are set.
+
+(defcustom package-quickstart nil
+ "Precompute activation actions to speed up startup.
+This requires the use of `package-quickstart-refresh' every time the
+activations need to be changed, such as when `package-load-list' is modified."
+ :type 'boolean
+ :version "27.1")
+
+(defcustom package-quickstart-file
+ (locate-user-emacs-file "package-quickstart.el")
+ "Location of the file used to speed up activation of packages at startup."
+ :type 'file
+ :version "27.1")
+
+(defun package--quickstart-maybe-refresh ()
+ (if package-quickstart
+ ;; FIXME: Delay refresh in case we're installing/deleting
+ ;; several packages!
+ (package-quickstart-refresh)
+ (delete-file package-quickstart-file)))
+
+(defun package-quickstart-refresh ()
+ "(Re)Generate the `package-quickstart-file'."
+ (interactive)
+ (package-initialize 'no-activate)
+ (require 'info)
+ (let ((package--quickstart-pkgs ())
+ ;; Pretend we haven't activated anything yet!
+ (package-activated-list ())
+ ;; Make sure we can load this file without load-source-file-function.
+ (coding-system-for-write 'emacs-internal)
+ (Info-directory-list '("")))
+ (dolist (elt package-alist)
+ (condition-case err
+ (package-activate (car elt))
+ ;; Don't let failure of activation of a package arbitrarily stop
+ ;; activation of further packages.
+ (error (message "%s" (error-message-string err)))))
+ (setq package--quickstart-pkgs (nreverse package--quickstart-pkgs))
+ (with-temp-file package-quickstart-file
+ (emacs-lisp-mode) ;For `syntax-ppss'.
+ (insert ";;; Quickstart file to activate all packages at startup -*- lexical-binding:t -*-\n")
+ (insert ";; ¡¡ This file is autogenerated by `package-quickstart-refresh', DO NOT EDIT !!\n\n")
+ (dolist (pkg package--quickstart-pkgs)
+ (let* ((file
+ ;; Prefer uncompiled files (and don't accept .so files).
+ (let ((load-suffixes '(".el" ".elc")))
+ (locate-library (package--autoloads-file-name pkg))))
+ (pfile (prin1-to-string file)))
+ (insert "(let ((load-file-name " pfile "))\n")
+ (insert-file-contents file)
+ ;; Fixup the special #$ reader form and throw away comments.
+ (while (re-search-forward "#\\$\\|^;\\(.*\n\\)" nil 'move)
+ (unless (nth 8 (syntax-ppss))
+ (replace-match (if (match-end 1) "" pfile) t t)))
+ (unless (bolp) (insert "\n"))
+ (insert ")\n")))
+ (pp `(setq package-activated-list
+ (append ',(mapcar #'package-desc-name package--quickstart-pkgs)
+ package-activated-list))
+ (current-buffer))
+ (let ((info-dirs (butlast Info-directory-list)))
+ (when info-dirs
+ (pp `(progn (require 'info)
+ (info-initialize)
+ (setq Info-directory-list
+ (append ',info-dirs Info-directory-list)))
+ (current-buffer))))
+ ;; Use `\s' instead of a space character, so this code chunk is not
+ ;; mistaken for an actual file-local section of package.el.
+ (insert "
+;; Local\sVariables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+"))))
+
(provide 'package)
;;; package.el ends here