summaryrefslogtreecommitdiff
path: root/lisp/emacs-lisp/package-vc.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/emacs-lisp/package-vc.el')
-rw-r--r--lisp/emacs-lisp/package-vc.el352
1 files changed, 235 insertions, 117 deletions
diff --git a/lisp/emacs-lisp/package-vc.el b/lisp/emacs-lisp/package-vc.el
index 5c5486de290..ef056c7909b 100644
--- a/lisp/emacs-lisp/package-vc.el
+++ b/lisp/emacs-lisp/package-vc.el
@@ -62,6 +62,18 @@
(defconst package-vc--elpa-packages-version 1
"Version number of the package specification format understood by package-vc.")
+(defconst package-vc--backend-type
+ `(choice :convert-widget
+ ,(lambda (widget)
+ (let (opts)
+ (dolist (be vc-handled-backends)
+ (when (or (vc-find-backend-function be 'clone)
+ (alist-get 'clone (get be 'vc-functions)))
+ (push (widget-convert (list 'const be)) opts)))
+ (widget-put widget :args opts))
+ widget))
+ "The type of VC backends that support cloning package VCS repositories.")
+
(defcustom package-vc-heuristic-alist
`((,(rx bos "http" (? "s") "://"
(or (: (? "www.") "github.com"
@@ -94,24 +106,34 @@
(+ (or alnum "-" "." "_")) (? "/")))
eos)
. Bzr))
- "Heuristic mapping URL regular expressions to VC backends."
+ "Alist mapping repository URLs to VC backends.
+`package-vc-install' consults this alist to determine the VC
+backend from the repository URL when you call it without
+specifying a backend. Each element of the alist has the form
+\(URL-REGEXP . BACKEND). `package-vc-install' will use BACKEND of
+the first association for which the URL of the repository matches
+the URL-REGEXP of the association. If no match is found,
+`package-vc-install' uses `package-vc-default-backend' instead."
:type `(alist :key-type (regexp :tag "Regular expression matching URLs")
- :value-type (choice :tag "VC Backend"
- ,@(mapcar (lambda (b) `(const ,b))
- vc-handled-backends)))
+ :value-type ,package-vc--backend-type)
:version "29.1")
(defcustom package-vc-default-backend 'Git
- "Default VC backend used when cloning a package repository.
-If no repository type was specified or could be guessed by
-`package-vc-heuristic-alist', this is the default VC backend
-used as fallback. The value must be a member of
-`vc-handled-backends' and the named backend must implement
-the `clone' function."
- :type `(choice ,@(mapcar (lambda (b) (list 'const b))
- vc-handled-backends))
+ "Default VC backend to use for cloning package repositories.
+`package-vc-install' uses this backend when you specify neither
+the backend nor a repository URL that's recognized via
+`package-vc-heuristic-alist'.
+
+The value must be a member of `vc-handled-backends' that supports
+the `clone' VC function."
+ :type package-vc--backend-type
:version "29.1")
+(defcustom package-vc-register-as-project t
+ "Non-nil means that packages should be registered as projects."
+ :type 'boolean
+ :version "30.1")
+
(defvar package-vc-selected-packages) ; pacify byte-compiler
;;;###autoload
@@ -135,20 +157,21 @@ the `clone' function."
(package-desc-create :name name :kind 'vc))
spec)))))))
-(defcustom package-vc-selected-packages '()
- "List of packages that must be installed.
-Each member of the list is of the form (NAME . SPEC), where NAME
-is a symbol designating the package and SPEC is one of:
+
+(defcustom package-vc-selected-packages nil
+ "List of packages to install from their VCS repositories.
+Each element is of the form (NAME . SPEC), where NAME is a symbol
+designating the package and SPEC is one of:
- nil, if any package version can be installed;
- a version string, if that specific revision is to be installed;
-- a property list, describing a package specification. For more
- details, please consult the subsection \"Specifying Package
- Sources\" in the Info node `(emacs)Fetching Package Sources'.
+- a property list, describing a package specification. For possible
+ values, see the subsection \"Specifying Package Sources\" in the
+ Info node `(emacs)Fetching Package Sources'.
-This user option will be automatically updated to store package
-specifications for packages that are not specified in any
-archive."
+The command `package-vc-install' updates the value of this user
+option to store package specifications for packages that are not
+specified in any archive."
:type '(alist :tag "List of packages you want to be installed"
:key-type (symbol :tag "Package")
:value-type
@@ -339,6 +362,47 @@ asynchronously."
"\n")
nil pkg-file nil 'silent))))
+(defcustom package-vc-allow-build-commands nil
+ "Whether to run extra build commands when installing VC packages.
+
+Some packages specify \"make\" targets or other shell commands
+that should run prior to building the package, by including the
+:make or :shell-command keywords in their specification. By
+default, Emacs ignores these keywords when installing and
+upgrading VC packages, but if the value is a list of package
+names (symbols), the build commands will be run for those
+packages. If the value is t, always respect :make and
+:shell-command keywords.
+
+It may be necessary to run :make and :shell-command arguments in
+order to initialize a package or build its documentation, but
+please be careful when changing this option, as installing and
+updating a package can run potentially harmful code.
+
+This applies to package specifications that come from your
+configured package archives, as well as from entries in
+`package-vc-selected-packages' and specifications that you give
+to `package-vc-install' directly."
+ :type '(choice (const :tag "Run for all packages" t)
+ (repeat :tag "Run only for selected packages" (symbol :tag "Package name"))
+ (const :tag "Never run" nil))
+ :version "30.1")
+
+(defun package-vc--make (pkg-spec pkg-desc)
+ "Process :make and :shell-command in PKG-SPEC.
+PKG-DESC is the package descriptor for the package that is being
+prepared."
+ (let ((target (plist-get pkg-spec :make))
+ (cmd (plist-get pkg-spec :shell-command))
+ (buf (format " *package-vc make %s*" (package-desc-name pkg-desc))))
+ (when (or cmd target)
+ (with-current-buffer (get-buffer-create buf)
+ (erase-buffer)
+ (when (and cmd (/= 0 (call-process shell-file-name nil t nil shell-command-switch cmd)))
+ (warn "Failed to run %s, see buffer %S" cmd (buffer-name)))
+ (when (and target (/= 0 (apply #'call-process "make" nil t nil (if (consp target) target (list target)))))
+ (warn "Failed to make %s, see buffer %S" target (buffer-name)))))))
+
(declare-function org-export-to-file "ox" (backend file))
(defun package-vc--build-documentation (pkg-desc file)
@@ -349,42 +413,48 @@ otherwise it's assumed to be an Info file."
(default-directory (package-desc-dir pkg-desc))
(docs-directory (file-name-directory (expand-file-name file)))
(output (expand-file-name (format "%s.info" pkg-name)))
+ (log-buffer (get-buffer-create (format " *package-vc doc: %s*" pkg-name)))
clean-up)
- (when (string-match-p "\\.org\\'" file)
- (require 'ox)
- (require 'ox-texinfo)
- (with-temp-buffer
- (insert-file-contents file)
- (setq file (make-temp-file "ox-texinfo-"))
- (let ((default-directory docs-directory))
- (org-export-to-file 'texinfo file))
- (setq clean-up t)))
- (with-current-buffer (get-buffer-create " *package-vc doc*")
- (erase-buffer)
- (cond
- ((/= 0 (call-process "makeinfo" nil t nil
- "-I" docs-directory
- "--no-split" file
- "-o" output))
- (message "Failed to build manual %s, see buffer %S"
- file (buffer-name)))
- ((/= 0 (call-process "install-info" nil t nil
- output (expand-file-name "dir")))
- (message "Failed to install manual %s, see buffer %S"
- output (buffer-name)))
- ((kill-buffer))))
+ (with-current-buffer log-buffer
+ (erase-buffer))
+ (condition-case err
+ (progn
+ (when (string-match-p "\\.org\\'" file)
+ (require 'ox)
+ (require 'ox-texinfo)
+ (with-temp-buffer
+ (insert-file-contents file)
+ (setq file (make-temp-file "ox-texinfo-"))
+ (let ((default-directory docs-directory))
+ (org-export-to-file 'texinfo file))
+ (setq clean-up t)))
+ (cond
+ ((/= 0 (call-process "makeinfo" nil log-buffer nil
+ "-I" docs-directory
+ "--no-split" file
+ "-o" output))
+ (message "Failed to build manual %s, see buffer %S"
+ file (buffer-name)))
+ ((/= 0 (call-process "install-info" nil log-buffer nil
+ output (expand-file-name "dir")))
+ (message "Failed to install manual %s, see buffer %S"
+ output (buffer-name)))
+ ((kill-buffer log-buffer))))
+ (error (with-current-buffer log-buffer
+ (insert (error-message-string err)))
+ (message "Failed to export org manual for %s, see buffer %S" pkg-name log-buffer)))
(when clean-up
(delete-file file))))
-(defun package-vc-install-dependencies (requirements)
- "Install missing dependencies, and return missing ones.
-The return value will be nil if everything was found, or a list
-of (NAME VERSION) pairs of all packages that couldn't be found.
+(defun package-vc-install-dependencies (deps)
+ "Install missing dependencies according to DEPS.
+
+DEPS is a list of elements (PACKAGE VERSION-LIST), where
+PACKAGE is a package name and VERSION-LIST is the required
+version of that package.
-REQUIREMENTS should be a list of additional requirements; each
-element in this list should have the form (PACKAGE VERSION-LIST),
-where PACKAGE is a package name and VERSION-LIST is the required
-version of that package."
+Return a list of dependencies that couldn't be met (or nil, when
+this function successfully installs all given dependencies)."
(let ((to-install '()) (missing '()))
(cl-labels ((search (pkg)
"Attempt to find all dependencies for PKG."
@@ -418,7 +488,7 @@ version of that package."
(let ((desc-a (package-desc-name a))
(desc-b (package-desc-name b)))
(depends-on-p desc-a desc-b))))
- (mapc #'search requirements)
+ (mapc #'search deps)
(cl-callf sort to-install #'version-order)
(cl-callf seq-uniq to-install #'duplicate-p)
(cl-callf sort to-install #'dependent-order))
@@ -431,42 +501,51 @@ This includes downloading missing dependencies, generating
autoloads, generating a package description file (used to
identify a package as a VC package later on), building
documentation and marking the package as installed."
- (let (missing)
- ;; Remove any previous instance of PKG-DESC from `package-alist'
- (let ((pkgs (assq (package-desc-name pkg-desc) package-alist)))
- (when pkgs
- (setf (cdr pkgs) (seq-remove #'package-vc-p (cdr pkgs)))))
+ (let* ((pkg-spec (package-vc--desc->spec pkg-desc))
+ (lisp-dir (plist-get pkg-spec :lisp-dir))
+ (lisp-path (file-name-concat pkg-dir lisp-dir))
+ missing)
;; In case the package was installed directly from source, the
;; dependency list wasn't know beforehand, and they might have
;; to be installed explicitly.
- (let ((deps '()))
- (dolist (file (directory-files pkg-dir t "\\.el\\'" t))
- (with-temp-buffer
- (insert-file-contents file)
- (when-let* ((require-lines (lm-header-multiline "package-requires")))
- (thread-last
- (mapconcat #'identity require-lines " ")
- package-read-from-string
- package--prepare-dependencies
- (nconc deps)
- (setq deps)))))
+ (let ((ignored-files
+ (if (plist-get pkg-spec :ignored-files)
+ (mapconcat
+ (lambda (ignore)
+ (wildcard-to-regexp
+ (if (string-match-p "\\`/" ignore)
+ (concat pkg-dir ignore)
+ (concat "*/" ignore))))
+ (plist-get pkg-spec :ignored-files)
+ "\\|")
+ regexp-unmatchable))
+ (deps '()))
+ (dolist (file (directory-files lisp-path t "\\.el\\'" t))
+ (unless (string-match-p ignored-files file)
+ (with-temp-buffer
+ (insert-file-contents file)
+ (when-let* ((require-lines (lm-header-multiline "package-requires")))
+ (thread-last
+ (mapconcat #'identity require-lines " ")
+ package-read-from-string
+ lm--prepare-package-dependencies
+ (nconc deps)
+ (setq deps))))))
(dolist (dep deps)
(cl-callf version-to-list (cadr dep)))
+ (setf (package-desc-reqs pkg-desc) deps)
(setf missing (package-vc-install-dependencies (delete-dups deps)))
(setf missing (delq (assq (package-desc-name pkg-desc)
missing)
missing)))
(let ((default-directory (file-name-as-directory pkg-dir))
- (pkg-file (expand-file-name (package--description-file pkg-dir) pkg-dir))
- (pkg-spec (package-vc--desc->spec pkg-desc)))
+ (pkg-file (expand-file-name (package--description-file pkg-dir) pkg-dir)))
;; Generate autoloads
(let* ((name (package-desc-name pkg-desc))
- (auto-name (format "%s-autoloads.el" name))
- (lisp-dir (plist-get pkg-spec :lisp-dir)))
- (package-generate-autoloads
- name (file-name-concat pkg-dir lisp-dir))
+ (auto-name (format "%s-autoloads.el" name)))
+ (package-generate-autoloads name lisp-path)
(when lisp-dir
(write-region
(with-temp-buffer
@@ -483,11 +562,22 @@ documentation and marking the package as installed."
;; Generate package file
(package-vc--generate-description-file pkg-desc pkg-file)
+ ;; Process :make and :shell-command arguments before building documentation
+ (when (or (eq package-vc-allow-build-commands t)
+ (memq (package-desc-name pkg-desc)
+ package-vc-allow-build-commands))
+ (package-vc--make pkg-spec pkg-desc))
+
;; Detect a manual
(when (executable-find "install-info")
(dolist (doc-file (ensure-list (plist-get pkg-spec :doc)))
(package-vc--build-documentation pkg-desc doc-file))))
+ ;; Remove any previous instance of PKG-DESC from `package-alist'
+ (let ((pkgs (assq (package-desc-name pkg-desc) package-alist)))
+ (when pkgs
+ (setf (cdr pkgs) (seq-remove #'package-vc-p (cdr pkgs)))))
+
;; Update package-alist.
(let ((new-desc (package-load-descriptor pkg-dir)))
;; Activation has to be done before compilation, so that if we're
@@ -538,6 +628,8 @@ and return nil if it cannot reasonably guess."
(and url (alist-get url package-vc-heuristic-alist
nil nil #'string-match-p)))
+(declare-function project-remember-projects-under "project" (dir &optional recursive))
+
(defun package-vc--clone (pkg-desc pkg-spec dir rev)
"Clone the package PKG-DESC whose spec is PKG-SPEC into the directory DIR.
REV specifies a specific revision to checkout. This overrides the `:branch'
@@ -559,6 +651,11 @@ attribute in PKG-SPEC."
(or (and (not (eq rev :last-release)) rev) branch))
(error "Failed to clone %s from %s" name url))))
+ (when package-vc-register-as-project
+ (let ((default-directory dir))
+ (require 'project)
+ (project-remember-projects-under dir)))
+
;; Check out the latest release if requested
(when (eq rev :last-release)
(if-let ((release-rev (package-vc--release-rev pkg-desc)))
@@ -666,7 +763,10 @@ installed package."
;;;###autoload
(defun package-vc-upgrade-all ()
- "Attempt to upgrade all installed VC packages."
+ "Upgrade all installed VC packages.
+
+This may fail if the local VCS state of one of the packages
+conflicts with its remote repository state."
(interactive)
(dolist (package package-alist)
(dolist (pkg-desc (cdr package))
@@ -676,7 +776,10 @@ installed package."
;;;###autoload
(defun package-vc-upgrade (pkg-desc)
- "Attempt to upgrade the package PKG-DESC."
+ "Upgrade the package described by PKG-DESC from package's VC repository.
+
+This may fail if the local VCS state of the package conflicts
+with the remote repository state."
(interactive (list (package-vc--read-package-desc "Upgrade VC package: " t)))
;; HACK: To run `package-vc--unpack-1' after checking out the new
;; revision, we insert a hook into `vc-post-command-functions', and
@@ -739,34 +842,45 @@ If no such revision can be found, return nil."
;;;###autoload
(defun package-vc-install (package &optional rev backend name)
- "Fetch a PACKAGE and set it up for using with Emacs.
-
-If PACKAGE is a string containing an URL, download the package
-from the repository at that URL; the function will try to guess
-the name of the package from the URL. This can be overridden by
-passing the optional argument NAME. If PACKAGE is a cons-cell,
-it should have the form (NAME . SPEC), where NAME is a symbol
-indicating the package name and SPEC is a plist as described in
-`package-vc-selected-packages'. Otherwise PACKAGE should be a
-symbol whose name is the package name, and the URL for the
-package will be taken from the package's metadata.
+ "Fetch a package described by PACKAGE and set it up for use with Emacs.
+
+PACKAGE specifies which package to install, where to find its
+source repository and how to build it.
+
+If PACKAGE is a symbol, install the package with that name
+according to metadata that package archives provide for it. This
+is the simplest way to call this function, but it only works if
+the package you want to install is listed in a package archive
+you have configured.
+
+If PACKAGE is a string, it specifies the URL of the package
+repository. In this case, optional argument BACKEND specifies
+the VC backend to use for cloning the repository; if it's nil,
+this function tries to infer which backend to use according to
+the value of `package-vc-heuristic-alist' and if that fails it
+uses `package-vc-default-backend'. Optional argument NAME
+specifies the package name in this case; if it's nil, this
+package uses `file-name-base' on the URL to obtain the package
+name, otherwise NAME is the package name as a symbol.
+
+PACKAGE can also be a cons cell (PNAME . SPEC) where PNAME is the
+package name as a symbol, and SPEC is a plist that specifies how
+to fetch and build the package. For possible values, see the
+subsection \"Specifying Package Sources\" in the Info
+node `(emacs)Fetching Package Sources'.
By default, this function installs the last revision of the
package available from its repository. If REV is a string, it
-describes the revision to install, as interpreted by the VC
-backend. The special value `:last-release' (interactively, the
-prefix argument), will use the commit of the latest release, if
-it exists. The last release is the latest revision which changed
-the \"Version:\" header of the package's main Lisp file.
-
-Optional argument BACKEND specifies the VC backend to use for cloning
-the package's repository; this is only possible if NAME-OR-URL is a URL,
-a string. If BACKEND is omitted or nil, the function
-uses `package-vc-heuristic-alist' to guess the backend.
-Note that by default, a VC package will be prioritized over a
-regular package, but it will not remove a VC package.
-
-\(fn PACKAGE &optional REV BACKEND)"
+describes the revision to install, as interpreted by the relevant
+VC backend. The special value `:last-release' (interactively,
+the prefix argument), says to use the commit of the latest
+release, if it exists. The last release is the latest revision
+which changed the \"Version:\" header of the package's main Lisp
+file.
+
+If you use this function to install a package that you also have
+installed from a package archive, the version this function
+installs takes precedence."
(interactive
(progn
;; Initialize the package system to get the list of package
@@ -829,7 +943,6 @@ for the last released version of the package."
(lambda (dir) (or (not (file-exists-p dir))
(directory-empty-p dir))))
(and current-prefix-arg :last-release))))
- (setf directory (expand-file-name directory))
(package-vc--archives-initialize)
(let ((pkg-spec (or (package-vc--desc->spec pkg-desc)
(and-let* ((extras (package-desc-extras pkg-desc))
@@ -842,18 +955,19 @@ for the last released version of the package."
(find-file directory)))
;;;###autoload
-(defun package-vc-install-from-checkout (dir name)
- "Set up the package NAME in DIR by linking it into the ELPA directory.
+(defun package-vc-install-from-checkout (dir &optional name)
+ "Install the package NAME from its source directory DIR.
+NAME defaults to the base name of DIR.
Interactively, prompt the user for DIR, which should be a directory
under version control, typically one created by `package-vc-checkout'.
If invoked interactively with a prefix argument, prompt the user
-for the NAME of the package to set up. Otherwise infer the package
-name from the base name of DIR."
- (interactive (let ((dir (read-directory-name "Directory: ")))
- (list dir
- (if current-prefix-arg
- (read-string "Package name: ")
- (file-name-base (directory-file-name dir))))))
+for the NAME of the package to set up."
+ (interactive (let* ((dir (read-directory-name "Directory: "))
+ (base (file-name-base (directory-file-name dir))))
+ (list dir (and current-prefix-arg
+ (read-string
+ (format-prompt "Package name" base)
+ nil nil base)))))
(unless (vc-responsible-backend dir)
(user-error "Directory %S is not under version control" dir))
(package-vc--archives-initialize)
@@ -885,13 +999,17 @@ prompt for the name of the package to rebuild."
;;;###autoload
(defun package-vc-prepare-patch (pkg-desc subject revisions)
- "Send patch for REVISIONS to maintainer of the package PKG using SUBJECT.
-The function uses `vc-prepare-patch', passing SUBJECT and
-REVISIONS directly. PKG-DESC must be a package description.
+ "Email patches for REVISIONS to maintainer of package PKG-DESC using SUBJECT.
+
+PKG-DESC is a package descriptor and SUBJECT is the subject of
+the message.
+
Interactively, prompt for PKG-DESC, SUBJECT, and REVISIONS. When
invoked with a numerical prefix argument, use the last N
revisions. When invoked interactively in a Log View buffer with
-marked revisions, use those."
+marked revisions, use those.
+
+See also `vc-prepare-patch'."
(interactive
(list (package-vc--read-package-desc "Package to prepare a patch for: " t)
(and (not vc-prepare-patches-separately)