aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/package/package-quickstart.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/package/package-quickstart.el')
-rw-r--r--lisp/package/package-quickstart.el151
1 files changed, 151 insertions, 0 deletions
diff --git a/lisp/package/package-quickstart.el b/lisp/package/package-quickstart.el
new file mode 100644
index 00000000000..39e71c6533c
--- /dev/null
+++ b/lisp/package/package-quickstart.el
@@ -0,0 +1,151 @@
1;;; package-quickstart.el --- Accelerating Package Startup -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2025 Philip Kaludercic
4
5;; Author: Philip Kaludercic <philipk@posteo.net>
6
7;; This program is free software; you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
11
12;; This program is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with this program. If not, see <https://www.gnu.org/licenses/>.
19
20;;; Commentary:
21
22;; Activating packages via `package-initialize' is costly: for N installed
23;; packages, it needs to read all N <pkg>-pkg.el files first to decide
24;; which packages to activate, and then again N <pkg>-autoloads.el files.
25;; To speed this up, we precompute a mega-autoloads file which is the
26;; concatenation of all those <pkg>-autoloads.el, so we can activate
27;; all packages by loading this one file (and hence without initializing
28;; package.el).
29
30;; Other than speeding things up, this also offers a bootstrap feature:
31;; it lets us activate packages according to `package-load-list' and
32;; `package-user-dir' even before those vars are set.
33
34;;; Code:
35
36(require 'package-core)
37
38(defcustom package-quickstart nil
39 "Precompute activation actions to speed up startup.
40This requires the use of `package-quickstart-refresh' every time the
41activations need to be changed, such as when `package-load-list' is modified."
42 :type 'boolean
43 :version "27.1"
44 :group 'package)
45
46;;;###autoload
47(defcustom package-quickstart-file
48 (locate-user-emacs-file "package-quickstart.el")
49 "Location of the file used to speed up activation of packages at startup."
50 :type 'file
51 :group 'applications
52 :initialize #'custom-initialize-delay
53 :version "27.1")
54
55(defun package--quickstart-maybe-refresh ()
56 (if package-quickstart
57 ;; FIXME: Delay refresh in case we're installing/deleting
58 ;; several packages!
59 (package-quickstart-refresh)
60 (delete-file (concat package-quickstart-file "c"))
61 (delete-file package-quickstart-file)))
62
63(defvar package--quickstart-dir nil
64 "Set by `package-quickstart-file' to the directory containing it.")
65
66(defun package--quickstart-rel (file)
67 "Return an expr depending on `package--quickstart-dir' which evaluates to FILE.
68
69If FILE is in `package--quickstart-dir', returns an expression that is
70relative to that directory, so if that directory is moved we can still
71find FILE."
72 (if (file-in-directory-p file package--quickstart-dir)
73 `(file-name-concat package--quickstart-dir ,(file-relative-name file package--quickstart-dir))
74 file))
75
76(defun package-quickstart-refresh ()
77 "(Re)Generate the `package-quickstart-file'."
78 (interactive)
79 (package-initialize 'no-activate)
80 (require 'info)
81 (let ((package--quickstart-pkgs ())
82 ;; Pretend we haven't activated anything yet!
83 (package-activated-list ())
84 ;; Make sure we can load this file without load-source-file-function.
85 (coding-system-for-write 'emacs-internal)
86 ;; Ensure that `pp' and `prin1-to-string' calls further down
87 ;; aren't truncated.
88 (print-length nil)
89 (print-level nil)
90 (Info-directory-list '(""))
91 (package--quickstart-dir nil))
92 (dolist (elt package-alist)
93 (condition-case err
94 (package-activate (car elt))
95 ;; Don't let failure of activation of a package arbitrarily stop
96 ;; activation of further packages.
97 (error (message "%s" (error-message-string err)))))
98 (setq package--quickstart-pkgs (nreverse package--quickstart-pkgs))
99 (with-temp-file package-quickstart-file
100 (emacs-lisp-mode) ;For `syntax-ppss'.
101 (insert ";;; Quickstart file to activate all packages at startup -*- lexical-binding:t -*-\n")
102 (insert ";; ¡¡ This file is autogenerated by `package-quickstart-refresh', DO NOT EDIT !!\n\n")
103 (setq package--quickstart-dir
104 (file-name-directory (expand-file-name package-quickstart-file)))
105 (pp '(setq package--quickstart-dir
106 (file-name-directory (expand-file-name load-file-name)))
107 (current-buffer))
108 (dolist (pkg package--quickstart-pkgs)
109 (let* ((file
110 ;; Prefer uncompiled files (and don't accept .so files).
111 (let ((load-suffixes '(".el" ".elc")))
112 (locate-library (package--autoloads-file-name pkg))))
113 (pfile (prin1-to-string (package--quickstart-rel file))))
114 (insert "(let* ((load-file-name " pfile ")\
115\(load-true-file-name load-file-name))\n")
116 (insert-file-contents file)
117 ;; Fixup the special #$ reader form and throw away comments.
118 (while (re-search-forward "#\\$\\|^;\\(.*\n\\)" nil 'move)
119 (unless (ppss-string-terminator (save-match-data (syntax-ppss)))
120 (replace-match (if (match-end 1) "" pfile) t t)))
121 (unless (bolp) (insert "\n"))
122 (insert ")\n")))
123 (pp `(defvar package-activated-list) (current-buffer))
124 (pp `(setq package-activated-list
125 (delete-dups
126 (append ',(mapcar #'package-desc-name package--quickstart-pkgs)
127 package-activated-list)))
128 (current-buffer))
129 (let ((info-dirs
130 (mapcar #'package--quickstart-rel (butlast Info-directory-list))))
131 (when info-dirs
132 (pp `(progn (require 'info)
133 (info-initialize)
134 (setq Info-directory-list
135 (append (list . ,info-dirs) Info-directory-list)))
136 (current-buffer))))
137 ;; Use `\s' instead of a space character, so this code chunk is not
138 ;; mistaken for an actual file-local section of package.el.
139 (insert "
140;; Local\sVariables:
141;; version-control: never
142;; no-update-autoloads: t
143;; byte-compile-warnings: (not make-local)
144;; End:
145"))
146 ;; FIXME: Do it asynchronously in an Emacs subprocess, and
147 ;; don't show the byte-compiler warnings.
148 (byte-compile-file package-quickstart-file)))
149
150(provide 'package-quickstart)
151;;; package-quickstart.el ends here