Function: package-isolate

package-isolate is an interactive and byte-compiled function defined in package.el.gz.

Signature

(package-isolate PACKAGES &optional TEMP-INIT)

Documentation

Start an uncustomized Emacs and only load a set of PACKAGES.

Interactively, prompt for PACKAGES to load, which should be specified separated by commas. If called from Lisp, PACKAGES should be a list of package-desc objects to load. If an element of PACKAGES is not installed, it will be fetched, but not activated in the current session. If TEMP-INIT is non-nil, or when invoked with a prefix argument, the Emacs user directory is set to a temporary directory. This command is intended for testing Emacs and/or the packages in a clean environment.

Probably introduced at or before Emacs version 30.1.

Key Bindings

Source Code

;; Defined in /usr/src/emacs/lisp/emacs-lisp/package.el.gz
(defun package-isolate (packages &optional temp-init)
  "Start an uncustomized Emacs and only load a set of PACKAGES.
Interactively, prompt for PACKAGES to load, which should be specified
separated by commas.  If called from Lisp, PACKAGES should be a list of
`package-desc' objects to load.  If an element of PACKAGES is not
installed, it will be fetched, but not activated in the current session.
If TEMP-INIT is non-nil, or when invoked with a prefix argument, the
Emacs user directory is set to a temporary directory.  This command is
intended for testing Emacs and/or the packages in a clean environment."
  (interactive
   (cl-loop for p in
	    (cl-loop with installed = (cl-loop for p in (package--alist)
                                               append (cdr p))
		     for p in (package--archive-contents)
		     append (cl-loop
                             for desc in (cdr p)
			     unless (cl-loop
                                     for idesc in installed thereis
                                     (and (string= (package-desc-name idesc)
						   (package-desc-name desc))
					  (equal (package-desc-version idesc)
						 (package-desc-version desc))
                                          (eq (package-desc-kind idesc)
					      (package-desc-kind desc))))
			     collect desc)
		     into descs
		     finally return (nconc installed descs))
	    unless (package-built-in-p p)
	    collect (cons (package-desc-full-name p) p) into table
	    finally return
	    (list
             (cl-loop for c in
                      (completing-read-multiple
                       "Packages to isolate: " table
                       nil t)
		      collect (alist-get c table nil nil #'string=))
             current-prefix-arg)))
  (let* ((name (concat "package-isolate-"
                       (mapconcat #'package-desc-full-name packages ",")))
         (all-packages
          (delete-dups
           (nconc
            (mapcan
             (pcase-lambda (`(,name ,vers))
               (and (not (eq name 'emacs))
                    (not (cl-find name packages :key #'package-desc-name))
                    (if-let* ((desc (package-get-descriptor
                                     name t
                                     (lambda (desc)
                                       (version-list-<= vers (package-desc-version desc))))))
                        (list desc)
                      (error "Failed to find package: (%s %S)" name vers))))
             (package--dependencies packages))
            packages)))
         (all-packages (seq-remove #'package-built-in-p all-packages))
         (package-alist (copy-tree package-alist t))
         (temp-install-dir nil) initial-scratch-message load-list)
    (when-let* ((missing (seq-remove #'package-installed-p all-packages))
                (package-user-dir (make-temp-file "package-isolate" t)))
      (setq temp-install-dir (list package-user-dir))
      ;; We bind `package-activate-1' to prevent activating the package
      ;; in `package-unpack' for this session.
      (cl-letf (((symbol-function #'package-activate-1) #'ignore))
        (package-download-transaction missing)))
    (with-temp-buffer
      (insert ";; This is an isolated testing environment, with these packages enabled:\n\n")
      (dolist (package all-packages)
        (push (list (package-desc-name package)
                    (package-version-join (package-desc-version package)))
              load-list)
        (insert ";; - " (package-desc-full-name package))
        (unless (memq package packages)
          (insert ", dependency"))
        (unless (package-installed-p package)
          (insert ", downloaded"))
        (insert "\n"))
      (insert "\n")
      (setq initial-scratch-message (buffer-string)))
    (apply #'start-process (concat "*" name "*") nil
           (list (expand-file-name invocation-name invocation-directory)
                 "--quick" "--debug-init"
                 "--init-directory" (if temp-init
                                        (make-temp-file name t)
                                      user-emacs-directory)
                 (format "--eval=%S"
                         `(progn
                            (setq initial-scratch-message ,initial-scratch-message)

                            (require 'package)
                            ,@(mapcar
                               (lambda (dir)
                                 `(add-to-list 'package-directory-list ,dir))
                               (append (list package-user-dir)
                                       temp-install-dir
                                       package-directory-list))
                            (setq package-load-list ',load-list)
                            (package--activate-all)))))))