diff options
Diffstat (limited to 'lisp/package/package-vc.el')
| -rw-r--r-- | lisp/package/package-vc.el | 1003 |
1 files changed, 1003 insertions, 0 deletions
diff --git a/lisp/package/package-vc.el b/lisp/package/package-vc.el new file mode 100644 index 00000000000..d40c7efb670 --- /dev/null +++ b/lisp/package/package-vc.el | |||
| @@ -0,0 +1,1003 @@ | |||
| 1 | ;;; package-vc.el --- Manage packages from VC checkouts -*- lexical-binding: t; -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2022-2025 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Philip Kaludercic <philipk@posteo.net> | ||
| 6 | ;; Maintainer: Philip Kaludercic <philipk@posteo.net> | ||
| 7 | ;; Keywords: tools | ||
| 8 | |||
| 9 | ;; This file is part of GNU Emacs. | ||
| 10 | |||
| 11 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 12 | ;; it under the terms of the GNU General Public License as published by | ||
| 13 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 14 | ;; (at your option) any later version. | ||
| 15 | |||
| 16 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 19 | ;; GNU General Public License for more details. | ||
| 20 | |||
| 21 | ;; You should have received a copy of the GNU General Public License | ||
| 22 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | ||
| 23 | |||
| 24 | ;;; Commentary: | ||
| 25 | |||
| 26 | ;; While packages managed by package.el use tarballs for distributing | ||
| 27 | ;; the source code, this extension allows for packages to be fetched | ||
| 28 | ;; and upgraded directly from a version control system. | ||
| 29 | ;; | ||
| 30 | ;; To install a package from source use `package-vc-install'. If you | ||
| 31 | ;; aren't interested in activating a package, you can use | ||
| 32 | ;; `package-vc-checkout' instead, which will prompt you for a target | ||
| 33 | ;; directory. If you wish to reuse an existing checkout, the command | ||
| 34 | ;; `package-vc-install-from-checkout' will create a symbolic link and | ||
| 35 | ;; prepare the package. | ||
| 36 | ;; | ||
| 37 | ;; If you make local changes that you wish to share with an upstream | ||
| 38 | ;; maintainer, the command `package-vc-prepare-patch' can prepare | ||
| 39 | ;; these as patches to send via Email. | ||
| 40 | |||
| 41 | ;;; TODO: | ||
| 42 | |||
| 43 | ;; - Allow maintaining patches that are ported back onto regular | ||
| 44 | ;; packages and maintained between versions. | ||
| 45 | |||
| 46 | ;;; Code: | ||
| 47 | |||
| 48 | (eval-when-compile (require 'rx)) | ||
| 49 | (eval-when-compile (require 'map)) | ||
| 50 | (eval-when-compile (require 'cl-lib)) | ||
| 51 | (require 'package-elpa) | ||
| 52 | (require 'package-misc) | ||
| 53 | (require 'package-install) | ||
| 54 | (require 'lisp-mnt) | ||
| 55 | (require 'vc) | ||
| 56 | (require 'seq) | ||
| 57 | |||
| 58 | (defgroup package-vc nil | ||
| 59 | "Manage packages from VC checkouts." | ||
| 60 | :group 'package | ||
| 61 | :link '(custom-manual "(emacs) Fetching Package Sources") | ||
| 62 | :prefix "package-vc-" | ||
| 63 | :version "29.1") | ||
| 64 | |||
| 65 | (defconst package-vc--elpa-packages-version 1 | ||
| 66 | "Version number of the package specification format understood by package-vc.") | ||
| 67 | |||
| 68 | (define-obsolete-variable-alias | ||
| 69 | 'package-vc-heuristic-alist | ||
| 70 | 'vc-clone-heuristic-alist "31.1") | ||
| 71 | |||
| 72 | (defcustom package-vc-default-backend 'Git | ||
| 73 | "Default VC backend to use for cloning package repositories. | ||
| 74 | `package-vc-install' uses this backend when you specify neither | ||
| 75 | the backend nor a repository URL that's recognized via | ||
| 76 | `vc-clone-heuristic-alist'. | ||
| 77 | |||
| 78 | The value must be a member of `vc-handled-backends' that supports | ||
| 79 | the `clone' VC function." | ||
| 80 | :type vc-cloneable-backends-custom-type | ||
| 81 | :version "29.1") | ||
| 82 | |||
| 83 | (defcustom package-vc-register-as-project t | ||
| 84 | "Non-nil means that packages should be registered as projects." | ||
| 85 | :type 'boolean | ||
| 86 | :version "30.1") | ||
| 87 | |||
| 88 | (defvar package-vc-selected-packages) ; pacify byte-compiler | ||
| 89 | |||
| 90 | ;;;###autoload | ||
| 91 | (defun package-vc-install-selected-packages () | ||
| 92 | "Ensure packages specified in `package-vc-selected-packages' are installed." | ||
| 93 | (interactive) | ||
| 94 | (pcase-dolist (`(,name . ,spec) package-vc-selected-packages) | ||
| 95 | (when (stringp name) | ||
| 96 | (setq name (intern name))) | ||
| 97 | (let ((pkg-descs (assoc name package-alist #'string=))) | ||
| 98 | (unless (seq-some #'package-vc-p (cdr pkg-descs)) | ||
| 99 | (cond | ||
| 100 | ((null spec) | ||
| 101 | (package-vc-install name)) | ||
| 102 | ((stringp spec) | ||
| 103 | (package-vc-install name spec)) | ||
| 104 | ((listp spec) | ||
| 105 | (package-vc--archives-initialize) | ||
| 106 | (package-vc--unpack | ||
| 107 | (or (cadr (assoc name package-archive-contents)) | ||
| 108 | (package-desc-create :name name :kind 'vc)) | ||
| 109 | spec))))))) | ||
| 110 | |||
| 111 | |||
| 112 | (defcustom package-vc-selected-packages nil | ||
| 113 | "List of packages to install from their VCS repositories. | ||
| 114 | Each element is of the form (NAME . SPEC), where NAME is a symbol | ||
| 115 | designating the package and SPEC is one of: | ||
| 116 | |||
| 117 | - nil, if any package version can be installed; | ||
| 118 | - a version string, if that specific revision is to be installed; | ||
| 119 | - a property list, describing a package specification. For possible | ||
| 120 | values, see the subsection \"Specifying Package Sources\" in the | ||
| 121 | Info node `(emacs)Fetching Package Sources'. | ||
| 122 | |||
| 123 | The command `package-vc-install' updates the value of this user | ||
| 124 | option to store package specifications for packages that are not | ||
| 125 | specified in any archive." | ||
| 126 | :type '(alist :tag "List of packages you want to be installed" | ||
| 127 | :key-type (symbol :tag "Package") | ||
| 128 | :value-type | ||
| 129 | (choice (const :tag "Any revision" nil) | ||
| 130 | (string :tag "Specific revision") | ||
| 131 | (plist :options ((:url string) | ||
| 132 | (:branch string) | ||
| 133 | (:lisp-dir string) | ||
| 134 | (:main-file string) | ||
| 135 | (:doc string) | ||
| 136 | (:vc-backend symbol))))) | ||
| 137 | :version "29.1") | ||
| 138 | |||
| 139 | (defvar package-vc--archive-spec-alists nil | ||
| 140 | "List of package specifications for each archive. | ||
| 141 | The list maps each package name, as a string, to a plist as | ||
| 142 | specified in `package-vc-selected-packages'.") | ||
| 143 | |||
| 144 | (defvar package-vc--archive-data-alist nil | ||
| 145 | "List of package specification metadata for archives. | ||
| 146 | Each element of the list has the form (ARCHIVE . PLIST), where | ||
| 147 | PLIST keys are one of: | ||
| 148 | |||
| 149 | `:version' (integer) | ||
| 150 | Indicates the version of the file formatting, to be compared | ||
| 151 | with `package-vc--elpa-packages-version'. | ||
| 152 | |||
| 153 | `:vc-backend' (symbol) | ||
| 154 | A symbol of the default VC backend to use if a package specification | ||
| 155 | does not indicate a backend. The value ought to be a member of | ||
| 156 | `vc-handled-backends'. If omitted, `vc-clone' will fall back on | ||
| 157 | `package-vc-default-backend'. | ||
| 158 | |||
| 159 | All other values are ignored.") | ||
| 160 | |||
| 161 | (defun package-vc--desc->spec (pkg-desc &optional name) | ||
| 162 | "Retrieve the package specification for PKG-DESC. | ||
| 163 | The optional argument NAME can be used to override the default | ||
| 164 | name for PKG-DESC." | ||
| 165 | (alist-get | ||
| 166 | (setq name (or name (package-desc-name pkg-desc))) | ||
| 167 | (if (and (package-desc-archive pkg-desc) | ||
| 168 | (not (alist-get name package-vc-selected-packages | ||
| 169 | nil nil #'string=))) | ||
| 170 | (alist-get (intern (package-desc-archive pkg-desc)) | ||
| 171 | package-vc--archive-spec-alists) | ||
| 172 | ;; Consult both our local list of package specifications, as well | ||
| 173 | ;; as the lists provided by the archives. | ||
| 174 | (apply #'append (cons package-vc-selected-packages | ||
| 175 | (mapcar #'cdr package-vc--archive-spec-alists)))) | ||
| 176 | '() nil #'string=)) | ||
| 177 | |||
| 178 | (defun package-vc--read-archive-data (archive) | ||
| 179 | "Update `package-vc--archive-spec-alists' for ARCHIVE. | ||
| 180 | This function is meant to be used as a hook for `package-read-archive-hook'." | ||
| 181 | (let ((contents-file (expand-file-name | ||
| 182 | (format "archives/%s/elpa-packages.eld" archive) | ||
| 183 | package-user-dir))) | ||
| 184 | (when (file-exists-p contents-file) | ||
| 185 | (with-temp-buffer | ||
| 186 | (let ((coding-system-for-read 'utf-8)) | ||
| 187 | (insert-file-contents contents-file) | ||
| 188 | ;; The response from the server is expected to have the form | ||
| 189 | ;; | ||
| 190 | ;; ((("foo" :url "..." ...) ...) | ||
| 191 | ;; :version 1 | ||
| 192 | ;; :default-vc Git) | ||
| 193 | (let ((spec (read (current-buffer)))) | ||
| 194 | (when (eq package-vc--elpa-packages-version | ||
| 195 | (plist-get (cdr spec) :version)) | ||
| 196 | (setf (alist-get (intern archive) package-vc--archive-spec-alists) | ||
| 197 | (car spec))) | ||
| 198 | (setf (alist-get (intern archive) package-vc--archive-data-alist) | ||
| 199 | (cdr spec)) | ||
| 200 | (when-let* ((default-vc (plist-get (cdr spec) :default-vc)) | ||
| 201 | ((not (memq default-vc vc-handled-backends)))) | ||
| 202 | (warn "Archive `%S' expects missing VC backend %S" | ||
| 203 | archive (plist-get (cdr spec) :default-vc))))))))) | ||
| 204 | |||
| 205 | (defun package-vc--download-and-read-archives (&optional async) | ||
| 206 | "Download specifications of all `package-archives' and read them. | ||
| 207 | Populate `package-vc--archive-spec-alists' with the result. | ||
| 208 | |||
| 209 | If optional argument ASYNC is non-nil, perform the downloads | ||
| 210 | asynchronously." | ||
| 211 | (dolist (archive package-archives) | ||
| 212 | (condition-case err | ||
| 213 | (package--download-one-archive archive "elpa-packages.eld" async) | ||
| 214 | (error (message "Failed to download `%s' archive: %S" (car archive) err))))) | ||
| 215 | |||
| 216 | (add-hook 'package-read-archive-hook #'package-vc--read-archive-data 20) | ||
| 217 | |||
| 218 | (defun package-vc-commit (pkg-desc) | ||
| 219 | "Return the last commit of a development package PKG-DESC." | ||
| 220 | (cl-assert (package-vc-p pkg-desc)) | ||
| 221 | ;; FIXME: vc should be extended to allow querying the commit of a | ||
| 222 | ;; directory (as is possible when dealing with git repositories). | ||
| 223 | ;; This should be a fallback option. | ||
| 224 | (cl-loop with dir = (let ((pkg-spec (package-vc--desc->spec pkg-desc))) | ||
| 225 | (or (plist-get pkg-spec :lisp-dir) | ||
| 226 | (package-desc-dir pkg-desc))) | ||
| 227 | for file in (directory-files dir t "\\.el\\'" t) | ||
| 228 | when (vc-working-revision file) return it | ||
| 229 | finally return "unknown")) | ||
| 230 | |||
| 231 | (defun package-vc--version (pkg) | ||
| 232 | "Return the version number for the VC package PKG." | ||
| 233 | (cl-assert (package-vc-p pkg)) | ||
| 234 | (if-let* ((main-file (package-vc--main-file pkg))) | ||
| 235 | (with-temp-buffer | ||
| 236 | (insert-file-contents main-file) | ||
| 237 | (package-strip-rcs-id | ||
| 238 | (or (lm-header "package-version") | ||
| 239 | (lm-header "version") | ||
| 240 | "0"))) | ||
| 241 | "0")) | ||
| 242 | |||
| 243 | (defun package-vc--main-file (pkg-desc) | ||
| 244 | "Return the name of the main file for PKG-DESC." | ||
| 245 | (cl-assert (package-vc-p pkg-desc)) | ||
| 246 | (let* ((pkg-spec (package-vc--desc->spec pkg-desc)) | ||
| 247 | (name (symbol-name (package-desc-name pkg-desc))) | ||
| 248 | (directory (expand-file-name | ||
| 249 | (or (plist-get pkg-spec :lisp-dir) ".") | ||
| 250 | (or (package-desc-dir pkg-desc) | ||
| 251 | (expand-file-name name package-user-dir)))) | ||
| 252 | (file (expand-file-name | ||
| 253 | (or (plist-get pkg-spec :main-file) | ||
| 254 | (concat name ".el")) | ||
| 255 | directory))) | ||
| 256 | (if (file-exists-p file) file | ||
| 257 | ;; The following heuristic is only necessary when fetching a | ||
| 258 | ;; repository with URL that would break the above assumptions. | ||
| 259 | ;; Concrete example: https://github.com/sachac/waveform-el does | ||
| 260 | ;; not have a file waveform-el.el, but a file waveform.el, so we | ||
| 261 | ;; try and find the closest match. | ||
| 262 | (let ((distance most-positive-fixnum) (best nil)) | ||
| 263 | (dolist (alt (directory-files directory t "\\.el\\'" t)) | ||
| 264 | (let ((sd (string-distance file alt))) | ||
| 265 | (when (and (not (string-match-p (rx (or (: "-autoloads.el") | ||
| 266 | (: "-pkg.el")) | ||
| 267 | eos) | ||
| 268 | alt)) | ||
| 269 | (< sd distance)) | ||
| 270 | (when (< sd distance) | ||
| 271 | (setq distance (string-distance file alt) | ||
| 272 | best alt))))) | ||
| 273 | best)))) | ||
| 274 | |||
| 275 | (defun package-vc--generate-description-file (pkg-desc pkg-file) | ||
| 276 | "Generate a package description file for PKG-DESC and write it to PKG-FILE." | ||
| 277 | (let ((name (package-desc-name pkg-desc))) | ||
| 278 | (when (equal (package-desc-summary pkg-desc) package--default-summary) | ||
| 279 | ;; We unset the package description if it is just the default | ||
| 280 | ;; summary, so that the following heuristic can take effect. | ||
| 281 | (setf (package-desc-summary pkg-desc) nil)) | ||
| 282 | ;; Infer the package description if missing. | ||
| 283 | (unless (package-desc-summary pkg-desc) | ||
| 284 | (setf (package-desc-summary pkg-desc) | ||
| 285 | (let ((main-file (package-vc--main-file pkg-desc))) | ||
| 286 | (or (package-desc-summary pkg-desc) | ||
| 287 | (and-let* ((pkg (cadr (assq name package-archive-contents)))) | ||
| 288 | (package-desc-summary pkg)) | ||
| 289 | (and main-file (file-exists-p main-file) | ||
| 290 | (lm-summary main-file)) | ||
| 291 | package--default-summary)))) | ||
| 292 | (let ((print-level nil) | ||
| 293 | (print-quoted t) | ||
| 294 | (print-length nil)) | ||
| 295 | (write-region | ||
| 296 | (concat | ||
| 297 | ";;; Generated package description from " | ||
| 298 | (replace-regexp-in-string | ||
| 299 | "-pkg\\.el\\'" ".el" | ||
| 300 | (file-name-nondirectory pkg-file)) | ||
| 301 | " -*- no-byte-compile: t -*-\n" | ||
| 302 | (prin1-to-string | ||
| 303 | (nconc | ||
| 304 | (list 'define-package | ||
| 305 | (symbol-name name) | ||
| 306 | (package-vc--version pkg-desc) | ||
| 307 | (package-desc-summary pkg-desc) | ||
| 308 | (let ((requires (package-desc-reqs pkg-desc))) | ||
| 309 | (list 'quote | ||
| 310 | ;; Turn version lists into string form. | ||
| 311 | (mapcar | ||
| 312 | (lambda (elt) | ||
| 313 | (list (car elt) | ||
| 314 | (package-version-join (cadr elt)))) | ||
| 315 | requires)))) | ||
| 316 | (list :kind 'vc) | ||
| 317 | (package--alist-to-plist-args | ||
| 318 | (let ((extras (copy-alist (package-desc-extras pkg-desc)))) | ||
| 319 | (setf (alist-get :commit extras) | ||
| 320 | (package-vc-commit pkg-desc)) | ||
| 321 | extras) | ||
| 322 | ))) | ||
| 323 | "\n") | ||
| 324 | nil pkg-file nil 'silent)))) | ||
| 325 | |||
| 326 | (defcustom package-vc-allow-build-commands nil | ||
| 327 | "Whether to run extra build commands when installing VC packages. | ||
| 328 | |||
| 329 | Some packages specify \"make\" targets or other shell commands | ||
| 330 | that should run prior to building the package, by including the | ||
| 331 | :make or :shell-command keywords in their specification. By | ||
| 332 | default, Emacs ignores these keywords when installing and | ||
| 333 | upgrading VC packages, but if the value is a list of package | ||
| 334 | names (symbols), the build commands will be run for those | ||
| 335 | packages. If the value is t, always respect :make and | ||
| 336 | :shell-command keywords. | ||
| 337 | |||
| 338 | It may be necessary to run :make and :shell-command arguments in | ||
| 339 | order to initialize a package or build its documentation, but | ||
| 340 | please be careful when changing this option, as installing and | ||
| 341 | updating a package can run potentially harmful code. | ||
| 342 | |||
| 343 | This applies to package specifications that come from your | ||
| 344 | configured package archives, as well as from entries in | ||
| 345 | `package-vc-selected-packages' and specifications that you give | ||
| 346 | to `package-vc-install' directly." | ||
| 347 | :type '(choice (const :tag "Run for all packages" t) | ||
| 348 | (repeat :tag "Run only for selected packages" (symbol :tag "Package name")) | ||
| 349 | (const :tag "Never run" nil)) | ||
| 350 | :version "30.1") | ||
| 351 | |||
| 352 | (defun package-vc--make (pkg-spec pkg-desc) | ||
| 353 | "Process :make and :shell-command in PKG-SPEC. | ||
| 354 | PKG-DESC is the package descriptor for the package that is being | ||
| 355 | prepared." | ||
| 356 | (let ((target (plist-get pkg-spec :make)) | ||
| 357 | (cmd (plist-get pkg-spec :shell-command)) | ||
| 358 | (buf (format " *package-vc make %s*" (package-desc-name pkg-desc)))) | ||
| 359 | (when (or cmd target) | ||
| 360 | (with-current-buffer (get-buffer-create buf) | ||
| 361 | (erase-buffer) | ||
| 362 | (when (and cmd (/= 0 (call-process shell-file-name nil t nil shell-command-switch cmd))) | ||
| 363 | (warn "Failed to run %s, see buffer %S" cmd (buffer-name))) | ||
| 364 | (when (and target (/= 0 (apply #'call-process "make" nil t nil (if (consp target) target (list target))))) | ||
| 365 | (warn "Failed to make %s, see buffer %S" target (buffer-name))))))) | ||
| 366 | |||
| 367 | (declare-function org-export-to-file "ox" (backend file)) | ||
| 368 | |||
| 369 | (defun package-vc--build-documentation (pkg-desc file) | ||
| 370 | "Build documentation for package PKG-DESC from documentation source in FILE. | ||
| 371 | FILE can be an Org file, indicated by its \".org\" extension, | ||
| 372 | otherwise it's assumed to be an Info file." | ||
| 373 | (let* ((pkg-name (package-desc-name pkg-desc)) | ||
| 374 | (default-directory (package-desc-dir pkg-desc)) | ||
| 375 | (docs-directory (file-name-directory (expand-file-name file))) | ||
| 376 | (output (expand-file-name (format "%s.info" pkg-name))) | ||
| 377 | (log-buffer (get-buffer-create (format " *package-vc doc: %s*" pkg-name))) | ||
| 378 | clean-up) | ||
| 379 | (with-current-buffer log-buffer | ||
| 380 | (erase-buffer)) | ||
| 381 | (condition-case err | ||
| 382 | (progn | ||
| 383 | (when (string-match-p "\\.org\\'" file) | ||
| 384 | (require 'ox) | ||
| 385 | (require 'ox-texinfo) | ||
| 386 | (with-temp-buffer | ||
| 387 | (insert-file-contents file) | ||
| 388 | (setq file (make-temp-file "ox-texinfo-")) | ||
| 389 | (let ((default-directory docs-directory)) | ||
| 390 | (org-export-to-file 'texinfo file)) | ||
| 391 | (setq clean-up t))) | ||
| 392 | (cond | ||
| 393 | ((/= 0 (call-process "makeinfo" nil log-buffer nil | ||
| 394 | "-I" docs-directory | ||
| 395 | "--no-split" file | ||
| 396 | "-o" output)) | ||
| 397 | (message "Failed to build manual %s, see buffer %S" | ||
| 398 | file (buffer-name))) | ||
| 399 | ((/= 0 (call-process "install-info" nil log-buffer nil | ||
| 400 | output (expand-file-name "dir"))) | ||
| 401 | (message "Failed to install manual %s, see buffer %S" | ||
| 402 | output (buffer-name))) | ||
| 403 | ((kill-buffer log-buffer)))) | ||
| 404 | (error (with-current-buffer log-buffer | ||
| 405 | (insert (error-message-string err))) | ||
| 406 | (message "Failed to export org manual for %s, see buffer %S" pkg-name log-buffer))) | ||
| 407 | (when clean-up | ||
| 408 | (delete-file file)))) | ||
| 409 | |||
| 410 | (defun package-vc-install-dependencies (deps) | ||
| 411 | "Install missing dependencies according to DEPS. | ||
| 412 | |||
| 413 | DEPS is a list of elements (PACKAGE VERSION-LIST), where | ||
| 414 | PACKAGE is a package name and VERSION-LIST is the required | ||
| 415 | version of that package. | ||
| 416 | |||
| 417 | Return a list of dependencies that couldn't be met (or nil, when | ||
| 418 | this function successfully installs all given dependencies)." | ||
| 419 | (let ((to-install '()) (missing '())) | ||
| 420 | (cl-labels ((search (pkg) | ||
| 421 | "Attempt to find all dependencies for PKG." | ||
| 422 | (cond | ||
| 423 | ((assq (car pkg) to-install)) ;inhibit cycles | ||
| 424 | ((package-installed-p (car pkg) (cadr pkg))) | ||
| 425 | ((let* ((pac package-archive-contents) | ||
| 426 | (desc (cadr (assoc (car pkg) pac)))) | ||
| 427 | (if desc | ||
| 428 | (let ((reqs (package-desc-reqs desc))) | ||
| 429 | (push desc to-install) | ||
| 430 | (mapc #'search reqs)) | ||
| 431 | (push pkg missing)))))) | ||
| 432 | (version-order (a b) | ||
| 433 | "Predicate to sort packages in order." | ||
| 434 | (version-list-< | ||
| 435 | (package-desc-version b) | ||
| 436 | (package-desc-version a))) | ||
| 437 | (duplicate-p (a b) | ||
| 438 | "Are A and B the same package?" | ||
| 439 | (eq (package-desc-name a) (package-desc-name b))) | ||
| 440 | (depends-on-p (target package) | ||
| 441 | "Does PACKAGE depend on TARGET?" | ||
| 442 | (or (eq target package) | ||
| 443 | (let* ((pac package-archive-contents) | ||
| 444 | (desc (cadr (assoc package pac)))) | ||
| 445 | (and desc (seq-some | ||
| 446 | (apply-partially #'depends-on-p target) | ||
| 447 | (mapcar #'car (package-desc-reqs desc))))))) | ||
| 448 | (dependent-order (a b) | ||
| 449 | (let ((desc-a (package-desc-name a)) | ||
| 450 | (desc-b (package-desc-name b))) | ||
| 451 | (depends-on-p desc-a desc-b)))) | ||
| 452 | (mapc #'search deps) | ||
| 453 | (cl-callf sort to-install #'version-order) | ||
| 454 | (cl-callf seq-uniq to-install #'duplicate-p) | ||
| 455 | (cl-callf sort to-install #'dependent-order)) | ||
| 456 | (mapc #'package-install-from-archive to-install) | ||
| 457 | missing)) | ||
| 458 | |||
| 459 | (defun package-vc--unpack-1 (pkg-desc pkg-dir) | ||
| 460 | "Prepare PKG-DESC that is already checked-out in PKG-DIR. | ||
| 461 | This includes downloading missing dependencies, generating | ||
| 462 | autoloads, generating a package description file (used to | ||
| 463 | identify a package as a VC package later on), building | ||
| 464 | documentation and marking the package as installed." | ||
| 465 | (let* ((pkg-spec (package-vc--desc->spec pkg-desc)) | ||
| 466 | (lisp-dir (plist-get pkg-spec :lisp-dir)) | ||
| 467 | (lisp-path (expand-file-name (or lisp-dir ".") pkg-dir)) | ||
| 468 | missing) | ||
| 469 | |||
| 470 | ;; In case the package was installed directly from source, the | ||
| 471 | ;; dependency list wasn't know beforehand, and they might have | ||
| 472 | ;; to be installed explicitly. | ||
| 473 | (let ((ignored-files | ||
| 474 | (if (plist-get pkg-spec :ignored-files) | ||
| 475 | (mapconcat | ||
| 476 | (lambda (ignore) | ||
| 477 | (wildcard-to-regexp | ||
| 478 | (if (string-match-p "\\`/" ignore) | ||
| 479 | (concat pkg-dir ignore) | ||
| 480 | (concat "*/" ignore)))) | ||
| 481 | (plist-get pkg-spec :ignored-files) | ||
| 482 | "\\|") | ||
| 483 | regexp-unmatchable)) | ||
| 484 | (deps '())) | ||
| 485 | (dolist (file (directory-files lisp-path t "\\.el\\'" t)) | ||
| 486 | (unless (string-match-p ignored-files file) | ||
| 487 | (with-temp-buffer | ||
| 488 | (insert-file-contents file) | ||
| 489 | (when-let* ((require-lines (lm-header-multiline "package-requires"))) | ||
| 490 | (setq deps | ||
| 491 | (nconc deps | ||
| 492 | (lm--prepare-package-dependencies | ||
| 493 | (package-read-from-string | ||
| 494 | (mapconcat (function identity) | ||
| 495 | require-lines " "))))))))) | ||
| 496 | (dolist (dep deps) | ||
| 497 | (cl-callf version-to-list (cadr dep))) | ||
| 498 | (setf (package-desc-reqs pkg-desc) deps) | ||
| 499 | (setf missing (package-vc-install-dependencies (delete-dups deps))) | ||
| 500 | (setf missing (delq (assq (package-desc-name pkg-desc) | ||
| 501 | missing) | ||
| 502 | missing))) | ||
| 503 | |||
| 504 | (let ((default-directory (file-name-as-directory pkg-dir)) | ||
| 505 | (pkg-file (expand-file-name (package--description-file pkg-dir) pkg-dir))) | ||
| 506 | ;; Generate autoloads | ||
| 507 | (let* ((name (package-desc-name pkg-desc)) | ||
| 508 | (auto-name (format "%s-autoloads.el" name))) | ||
| 509 | (package-generate-autoloads name lisp-path) | ||
| 510 | (when lisp-dir | ||
| 511 | (write-region | ||
| 512 | (with-temp-buffer | ||
| 513 | (insert ";; Autoload indirection for package-vc\n\n") | ||
| 514 | (prin1 `(load (expand-file-name | ||
| 515 | ,(expand-file-name auto-name lisp-dir) | ||
| 516 | (or (and load-file-name | ||
| 517 | (file-name-directory load-file-name)) | ||
| 518 | (car load-path)))) | ||
| 519 | (current-buffer)) | ||
| 520 | (buffer-string)) | ||
| 521 | nil (expand-file-name auto-name pkg-dir)))) | ||
| 522 | |||
| 523 | ;; Generate package file | ||
| 524 | (package-vc--generate-description-file pkg-desc pkg-file) | ||
| 525 | |||
| 526 | ;; Process :make and :shell-command arguments before building documentation | ||
| 527 | (when (or (eq package-vc-allow-build-commands t) | ||
| 528 | (memq (package-desc-name pkg-desc) | ||
| 529 | package-vc-allow-build-commands)) | ||
| 530 | (package-vc--make pkg-spec pkg-desc)) | ||
| 531 | |||
| 532 | ;; Detect a manual | ||
| 533 | (when (executable-find "install-info") | ||
| 534 | (dolist (doc-file (ensure-list (plist-get pkg-spec :doc))) | ||
| 535 | (package-vc--build-documentation pkg-desc doc-file)))) | ||
| 536 | |||
| 537 | ;; Remove any previous instance of PKG-DESC from `package-alist' | ||
| 538 | (let ((pkgs (assq (package-desc-name pkg-desc) package-alist))) | ||
| 539 | (when pkgs | ||
| 540 | (setf (cdr pkgs) (seq-remove #'package-vc-p (cdr pkgs))))) | ||
| 541 | |||
| 542 | ;; Update package-alist. | ||
| 543 | (let ((new-desc (package-load-descriptor pkg-dir))) | ||
| 544 | ;; Activation has to be done before compilation, so that if we're | ||
| 545 | ;; upgrading and macros have changed we load the new definitions | ||
| 546 | ;; before compiling. | ||
| 547 | (when (package-activate-1 new-desc :reload :deps) | ||
| 548 | ;; FIXME: Compilation should be done as a separate, optional, step. | ||
| 549 | ;; E.g. for multi-package installs, we should first install all packages | ||
| 550 | ;; and then compile them. | ||
| 551 | (package--compile | ||
| 552 | (if lisp-dir | ||
| 553 | ;; In case we are installing a package from a local | ||
| 554 | ;; checkout, we want to compile the checkout, not the | ||
| 555 | ;; redirection! | ||
| 556 | (package-desc-create :dir lisp-dir) | ||
| 557 | new-desc)) | ||
| 558 | |||
| 559 | (when package-native-compile | ||
| 560 | (package--native-compile-async new-desc)) | ||
| 561 | ;; After compilation, load again any files loaded by | ||
| 562 | ;; `activate-1', so that we use the byte-compiled definitions. | ||
| 563 | (package--reload-previously-loaded new-desc))) | ||
| 564 | |||
| 565 | ;; Mark package as selected | ||
| 566 | (let ((name (package-desc-name pkg-desc))) | ||
| 567 | (unless (memq name package-selected-packages) | ||
| 568 | (package--save-selected-packages | ||
| 569 | (cons name package-selected-packages)))) | ||
| 570 | |||
| 571 | (package--quickstart-maybe-refresh) | ||
| 572 | |||
| 573 | ;; Confirm that the installation was successful | ||
| 574 | (let ((main-file (package-vc--main-file pkg-desc))) | ||
| 575 | (message "VC package `%s' installed (Version %s, Revision %S).%s" | ||
| 576 | (package-desc-name pkg-desc) | ||
| 577 | (lm-with-file main-file | ||
| 578 | (package-strip-rcs-id | ||
| 579 | (or (lm-header "package-version") | ||
| 580 | (lm-header "version")))) | ||
| 581 | (vc-working-revision main-file) | ||
| 582 | (if missing | ||
| 583 | (format | ||
| 584 | " Failed to install the following dependencies: %s" | ||
| 585 | (mapconcat | ||
| 586 | (lambda (p) | ||
| 587 | (format "%s (%s)" (car p) (cadr p))) | ||
| 588 | missing ", ")) | ||
| 589 | ""))) | ||
| 590 | t)) | ||
| 591 | |||
| 592 | (declare-function project-remember-projects-under "project" (dir &optional recursive)) | ||
| 593 | |||
| 594 | (defun package-vc--clone (pkg-desc pkg-spec dir rev) | ||
| 595 | "Clone the package PKG-DESC whose spec is PKG-SPEC into the directory DIR. | ||
| 596 | REV specifies a specific revision to checkout. This overrides the `:branch' | ||
| 597 | attribute in PKG-SPEC." | ||
| 598 | (pcase-let* ((name (package-desc-name pkg-desc)) | ||
| 599 | ((map :url :branch) pkg-spec)) | ||
| 600 | |||
| 601 | ;; Clone the repository into `repo-dir' if necessary | ||
| 602 | (unless (file-exists-p dir) | ||
| 603 | (make-directory (file-name-directory dir) t) | ||
| 604 | (let ((backend (or (plist-get pkg-spec :vc-backend) | ||
| 605 | (vc-guess-url-backend url) | ||
| 606 | (plist-get (alist-get (package-desc-archive pkg-desc) | ||
| 607 | package-vc--archive-data-alist | ||
| 608 | nil nil #'string=) | ||
| 609 | :vc-backend) | ||
| 610 | package-vc-default-backend))) | ||
| 611 | (unless (vc-clone url backend dir | ||
| 612 | (or (and (not (eq rev :last-release)) rev) branch)) | ||
| 613 | (error "Failed to clone %s from %s" name url)))) | ||
| 614 | |||
| 615 | (when package-vc-register-as-project | ||
| 616 | (let ((default-directory dir)) | ||
| 617 | (require 'project) | ||
| 618 | (project-remember-projects-under dir))) | ||
| 619 | |||
| 620 | ;; Check out the latest release if requested | ||
| 621 | (when (eq rev :last-release) | ||
| 622 | (if-let* ((release-rev (package-vc--release-rev pkg-desc))) | ||
| 623 | (vc-retrieve-tag dir release-rev) | ||
| 624 | (message "No release revision was found, continuing..."))))) | ||
| 625 | |||
| 626 | (defvar package-vc-non-code-file-names | ||
| 627 | '(".dir-locals.el" ".dir-locals-2.el") | ||
| 628 | "List of file names that do not contain Emacs Lisp code. | ||
| 629 | This list is used by `package-vc--unpack' to better check if the | ||
| 630 | user is fetching code from a repository that does not contain any | ||
| 631 | Emacs Lisp files.") | ||
| 632 | |||
| 633 | (defun package-vc--unpack (pkg-desc pkg-spec &optional rev) | ||
| 634 | "Install the package described by PKG-DESC. | ||
| 635 | PKG-SPEC is a package specification, a property list describing | ||
| 636 | how to fetch and build the package. See `package-vc--archive-spec-alists' | ||
| 637 | for details. The optional argument REV specifies a specific revision to | ||
| 638 | checkout. This overrides the `:branch' attribute in PKG-SPEC." | ||
| 639 | (unless (eq (package-desc-kind pkg-desc) 'vc) | ||
| 640 | (let ((copy (copy-package-desc pkg-desc))) | ||
| 641 | (setf (package-desc-kind copy) 'vc | ||
| 642 | pkg-desc copy))) | ||
| 643 | (pcase-let* (((map :lisp-dir) pkg-spec) | ||
| 644 | (name (package-desc-name pkg-desc)) | ||
| 645 | (dirname (package-desc-full-name pkg-desc)) | ||
| 646 | (pkg-dir (file-name-as-directory (expand-file-name dirname package-user-dir)))) | ||
| 647 | (when (string-empty-p name) | ||
| 648 | (user-error "Empty package name")) | ||
| 649 | (setf (package-desc-dir pkg-desc) pkg-dir) | ||
| 650 | (when (file-exists-p pkg-dir) | ||
| 651 | (if (yes-or-no-p (format "Overwrite previous checkout for package `%s'?" name)) | ||
| 652 | (package--delete-directory pkg-dir) | ||
| 653 | (error "There already exists a checkout for %s" name))) | ||
| 654 | (package-vc--clone pkg-desc pkg-spec pkg-dir rev) | ||
| 655 | (when (directory-empty-p pkg-dir) | ||
| 656 | (delete-directory pkg-dir) | ||
| 657 | (error "Empty checkout for %s" name)) | ||
| 658 | (unless (seq-remove | ||
| 659 | (lambda (file) | ||
| 660 | (member (file-name-nondirectory file) package-vc-non-code-file-names)) | ||
| 661 | (directory-files-recursively pkg-dir "\\.el\\'" nil)) | ||
| 662 | (when (yes-or-no-p (format "No Emacs Lisp files found when fetching \"%s\", \ | ||
| 663 | abort installation?" name)) | ||
| 664 | (delete-directory pkg-dir t) | ||
| 665 | (user-error "Installation aborted"))) | ||
| 666 | |||
| 667 | ;; When nothing is specified about a `lisp-dir', then should | ||
| 668 | ;; heuristically check if there is a sub-directory with lisp | ||
| 669 | ;; files. These are conventionally just called "lisp" or "src". | ||
| 670 | ;; If this directory exists and contains non-zero number of lisp | ||
| 671 | ;; files, we will use that instead of `pkg-dir'. | ||
| 672 | (catch 'done | ||
| 673 | (dolist (name '("lisp" "src")) | ||
| 674 | (when-let* (((null lisp-dir)) | ||
| 675 | (dir (expand-file-name name pkg-dir)) | ||
| 676 | ((file-directory-p dir)) | ||
| 677 | ((directory-files dir nil "\\`[^.].+\\.el\\'" t 1))) | ||
| 678 | ;; We won't use `dir', since dir is an absolute path and we | ||
| 679 | ;; don't want `lisp-dir' to depend on the current location of | ||
| 680 | ;; the package installation, ie. to break if moved around the | ||
| 681 | ;; file system or between installations. | ||
| 682 | (throw 'done (setq lisp-dir name))))) | ||
| 683 | |||
| 684 | ;; Ensure we have a copy of the package specification | ||
| 685 | (unless (seq-some (lambda (alist) (equal (alist-get name (cdr alist)) pkg-spec)) | ||
| 686 | package-vc--archive-spec-alists) | ||
| 687 | (customize-save-variable | ||
| 688 | 'package-vc-selected-packages | ||
| 689 | (cons (cons name pkg-spec) | ||
| 690 | (seq-remove (lambda (spec) (string= name (car spec))) | ||
| 691 | package-vc-selected-packages)))) | ||
| 692 | |||
| 693 | (package-vc--unpack-1 pkg-desc pkg-dir))) | ||
| 694 | |||
| 695 | (defun package-vc--read-package-name (prompt &optional allow-url installed) | ||
| 696 | "Query the user for a VC package and return a name with PROMPT. | ||
| 697 | If the optional argument ALLOW-URL is non-nil, the user is also | ||
| 698 | allowed to specify a non-package name. If the optional argument | ||
| 699 | INSTALLED is non-nil, the selection will be filtered down to | ||
| 700 | VC packages that have already been installed." | ||
| 701 | (package-vc--archives-initialize) | ||
| 702 | (completing-read prompt (if installed package-alist package-archive-contents) | ||
| 703 | (if installed | ||
| 704 | (lambda (pkg) (package-vc-p (cadr pkg))) | ||
| 705 | (lambda (pkg) | ||
| 706 | (or (package-vc--desc->spec (cadr pkg)) | ||
| 707 | ;; If we have no explicit VC data, we can try a kind of | ||
| 708 | ;; heuristic and use the URL header, that might already be | ||
| 709 | ;; pointing towards a repository, and use that as a backup | ||
| 710 | (and-let* ((extras (package-desc-extras (cadr pkg))) | ||
| 711 | (url (alist-get :url extras)) | ||
| 712 | ((vc-guess-url-backend url))))))) | ||
| 713 | (not allow-url))) | ||
| 714 | |||
| 715 | (defun package-vc--read-package-desc (prompt &optional installed) | ||
| 716 | "Query the user for a VC package and return a description with PROMPT. | ||
| 717 | If the optional argument INSTALLED is non-nil, the selection will | ||
| 718 | be filtered down to VC packages that have already been | ||
| 719 | installed, and the package description will be that of an | ||
| 720 | installed package." | ||
| 721 | (cadr (assoc (package-vc--read-package-name prompt nil installed) | ||
| 722 | (if installed package-alist package-archive-contents) | ||
| 723 | #'string=))) | ||
| 724 | |||
| 725 | ;;;###autoload | ||
| 726 | (defun package-vc-upgrade-all () | ||
| 727 | "Upgrade all installed VC packages. | ||
| 728 | |||
| 729 | This may fail if the local VCS state of one of the packages | ||
| 730 | conflicts with its remote repository state." | ||
| 731 | (interactive) | ||
| 732 | (dolist (package package-alist) | ||
| 733 | (dolist (pkg-desc (cdr package)) | ||
| 734 | (when (package-vc-p pkg-desc) | ||
| 735 | (package-vc-upgrade pkg-desc)))) | ||
| 736 | (message "Done upgrading packages.")) | ||
| 737 | |||
| 738 | (declare-function vc-dir-prepare-status-buffer "vc-dir" | ||
| 739 | (bname dir backend &optional create-new)) | ||
| 740 | |||
| 741 | ;;;###autoload | ||
| 742 | (defun package-vc-upgrade (pkg-desc) | ||
| 743 | "Upgrade the package described by PKG-DESC from package's VC repository. | ||
| 744 | |||
| 745 | This may fail if the local VCS state of the package conflicts | ||
| 746 | with the remote repository state." | ||
| 747 | (interactive (list (package-vc--read-package-desc "Upgrade VC package: " t))) | ||
| 748 | ;; HACK: To run `package-vc--unpack-1' after checking out the new | ||
| 749 | ;; revision, we insert a hook into `vc-post-command-functions', and | ||
| 750 | ;; remove it right after it ran. To avoid running the hook multiple | ||
| 751 | ;; times or even for the wrong repository (as `vc-pull' is often | ||
| 752 | ;; asynchronous), we extract the relevant arguments using a pseudo | ||
| 753 | ;; filter for `vc-filter-command-function', executed only for the | ||
| 754 | ;; side effect, and store them in the lexical scope. When the hook | ||
| 755 | ;; is run, we check if the arguments are the same (`eq') as the ones | ||
| 756 | ;; previously extracted, and only in that case will be call | ||
| 757 | ;; `package-vc--unpack-1'. Ugh... | ||
| 758 | ;; | ||
| 759 | ;; If there is a better way to do this, it should be done. | ||
| 760 | (cl-assert (package-vc-p pkg-desc)) | ||
| 761 | (letrec ((pkg-dir (package-desc-dir pkg-desc)) | ||
| 762 | (vc-flags) | ||
| 763 | (vc-filter-command-function | ||
| 764 | (lambda (command file-or-list flags) | ||
| 765 | (setq vc-flags flags) | ||
| 766 | (list command file-or-list flags))) | ||
| 767 | (post-upgrade | ||
| 768 | (lambda (_command _file-or-list flags) | ||
| 769 | (when (and (file-equal-p pkg-dir default-directory) | ||
| 770 | (eq flags vc-flags)) | ||
| 771 | (unwind-protect | ||
| 772 | (with-demoted-errors "Failed to activate: %S" | ||
| 773 | (package-vc--unpack-1 pkg-desc pkg-dir)) | ||
| 774 | (remove-hook 'vc-post-command-functions post-upgrade)))))) | ||
| 775 | (add-hook 'vc-post-command-functions post-upgrade) | ||
| 776 | (with-demoted-errors "Failed to fetch: %S" | ||
| 777 | (require 'vc-dir) | ||
| 778 | (with-current-buffer (vc-dir-prepare-status-buffer | ||
| 779 | (format " *package-vc-dir: %s*" pkg-dir) | ||
| 780 | pkg-dir (vc-responsible-backend pkg-dir)) | ||
| 781 | (vc-pull))))) | ||
| 782 | |||
| 783 | (defun package-vc--archives-initialize () | ||
| 784 | "Initialize package.el and fetch package specifications." | ||
| 785 | (package--archives-initialize) | ||
| 786 | (unless package-vc--archive-data-alist | ||
| 787 | (package-vc--download-and-read-archives))) | ||
| 788 | |||
| 789 | (defun package-vc--release-rev (pkg-desc) | ||
| 790 | "Return the latest revision that bumps the \"Version\" tag for PKG-DESC. | ||
| 791 | If no such revision can be found, return nil." | ||
| 792 | (with-current-buffer (find-file-noselect (package-vc--main-file pkg-desc)) | ||
| 793 | (vc-buffer-sync) | ||
| 794 | (save-excursion | ||
| 795 | (goto-char (point-min)) | ||
| 796 | (let ((case-fold-search t)) | ||
| 797 | (when (cond | ||
| 798 | ((re-search-forward | ||
| 799 | (concat (lm-get-header-re "package-version") ".*$") | ||
| 800 | (lm-code-start) t)) | ||
| 801 | ((re-search-forward | ||
| 802 | (concat (lm-get-header-re "version") ".*$") | ||
| 803 | (lm-code-start) t))) | ||
| 804 | (ignore-error vc-not-supported | ||
| 805 | (vc-call-backend (vc-backend (buffer-file-name)) | ||
| 806 | 'last-change | ||
| 807 | (buffer-file-name) | ||
| 808 | (line-number-at-pos nil t)))))))) | ||
| 809 | |||
| 810 | ;;;###autoload | ||
| 811 | (defun package-vc-install (package &optional rev backend name) | ||
| 812 | "Fetch a package described by PACKAGE and set it up for use with Emacs. | ||
| 813 | |||
| 814 | PACKAGE specifies which package to install, where to find its | ||
| 815 | source repository and how to build it. | ||
| 816 | |||
| 817 | If PACKAGE is a symbol, install the package with that name | ||
| 818 | according to metadata that package archives provide for it. This | ||
| 819 | is the simplest way to call this function, but it only works if | ||
| 820 | the package you want to install is listed in a package archive | ||
| 821 | you have configured. | ||
| 822 | |||
| 823 | If PACKAGE is a string, it specifies the URL of the package | ||
| 824 | repository. In this case, optional argument BACKEND specifies | ||
| 825 | the VC backend to use for cloning the repository; if it's nil, | ||
| 826 | this function tries to infer which backend to use according to | ||
| 827 | the value of `vc-clone-heuristic-alist' and if that fails it | ||
| 828 | uses `package-vc-default-backend'. Optional argument NAME | ||
| 829 | specifies the package name in this case; if it's nil, this | ||
| 830 | package uses `file-name-base' on the URL to obtain the package | ||
| 831 | name, otherwise NAME is the package name as a symbol. | ||
| 832 | |||
| 833 | PACKAGE can also be a cons cell (PNAME . SPEC) where PNAME is the | ||
| 834 | package name as a symbol, and SPEC is a plist that specifies how | ||
| 835 | to fetch and build the package. For possible values, see the | ||
| 836 | subsection \"Specifying Package Sources\" in the Info | ||
| 837 | node `(emacs)Fetching Package Sources'. | ||
| 838 | |||
| 839 | By default, this function installs the last revision of the | ||
| 840 | package available from its repository. If REV is a string, it | ||
| 841 | describes the revision to install, as interpreted by the relevant | ||
| 842 | VC backend. The special value `:last-release' (interactively, | ||
| 843 | the prefix argument), says to use the commit of the latest | ||
| 844 | release, if it exists. The last release is the latest revision | ||
| 845 | which changed the \"Version:\" header of the package's main Lisp | ||
| 846 | file. | ||
| 847 | |||
| 848 | If you use this function to install a package that you also have | ||
| 849 | installed from a package archive, the version this function | ||
| 850 | installs takes precedence." | ||
| 851 | (interactive | ||
| 852 | (progn | ||
| 853 | ;; Initialize the package system to get the list of package | ||
| 854 | ;; symbols for completion. | ||
| 855 | (package-vc--archives-initialize) | ||
| 856 | (let* ((name-or-url (package-vc--read-package-name | ||
| 857 | "Fetch and install package: " t)) | ||
| 858 | (name (file-name-base (directory-file-name name-or-url)))) | ||
| 859 | (when (string-empty-p name) | ||
| 860 | (user-error "Empty package name")) | ||
| 861 | (list name-or-url | ||
| 862 | (and current-prefix-arg :last-release) | ||
| 863 | nil | ||
| 864 | (intern (string-remove-prefix "emacs-" name)))))) | ||
| 865 | (package-vc--archives-initialize) | ||
| 866 | (cond | ||
| 867 | ((null package) | ||
| 868 | (signal 'wrong-type-argument nil)) | ||
| 869 | ((consp package) | ||
| 870 | (package-vc--unpack | ||
| 871 | (package-desc-create :name (car package) | ||
| 872 | :kind 'vc) | ||
| 873 | (cdr package) | ||
| 874 | rev)) | ||
| 875 | ((and-let* (((stringp package)) | ||
| 876 | (backend (or backend (vc-guess-url-backend package)))) | ||
| 877 | (package-vc--unpack | ||
| 878 | (package-desc-create | ||
| 879 | :name (or name (intern (file-name-base package))) | ||
| 880 | :kind 'vc) | ||
| 881 | (list :vc-backend backend :url package) | ||
| 882 | rev))) | ||
| 883 | ((and-let* ((desc (assoc package package-archive-contents #'string=))) | ||
| 884 | (package-vc--unpack | ||
| 885 | (cadr desc) | ||
| 886 | (or (package-vc--desc->spec (cadr desc)) | ||
| 887 | (and-let* ((extras (package-desc-extras (cadr desc))) | ||
| 888 | (url (alist-get :url extras)) | ||
| 889 | (backend (vc-guess-url-backend url))) | ||
| 890 | (list :vc-backend backend :url url)) | ||
| 891 | (user-error "Package `%s' has no VC data" package)) | ||
| 892 | rev))) | ||
| 893 | ((user-error "Unknown package to fetch: %s" package)))) | ||
| 894 | |||
| 895 | ;;;###autoload | ||
| 896 | (defun package-vc-checkout (pkg-desc directory &optional rev) | ||
| 897 | "Clone the sources for PKG-DESC into DIRECTORY and visit that directory. | ||
| 898 | Unlike `package-vc-install', this does not yet set up the package | ||
| 899 | for use with Emacs; use `package-vc-install-from-checkout' for | ||
| 900 | setting the package up after this function finishes. Optional | ||
| 901 | argument REV means to clone a specific version of the package; it | ||
| 902 | defaults to the last version available from the package's | ||
| 903 | repository. If REV has the special value | ||
| 904 | `:last-release' (interactively, the prefix argument), that stands | ||
| 905 | for the last released version of the package." | ||
| 906 | (interactive | ||
| 907 | (let* ((name (package-vc--read-package-name "Fetch package source: "))) | ||
| 908 | (list (cadr (assoc name package-archive-contents #'string=)) | ||
| 909 | (read-directory-name "Clone into new or empty directory: " nil nil | ||
| 910 | (lambda (dir) (or (not (file-exists-p dir)) | ||
| 911 | (directory-empty-p dir)))) | ||
| 912 | (and current-prefix-arg :last-release)))) | ||
| 913 | (package-vc--archives-initialize) | ||
| 914 | (let ((pkg-spec (or (package-vc--desc->spec pkg-desc) | ||
| 915 | (and-let* ((extras (package-desc-extras pkg-desc)) | ||
| 916 | (url (alist-get :url extras)) | ||
| 917 | (backend (vc-guess-url-backend url))) | ||
| 918 | (list :vc-backend backend :url url)) | ||
| 919 | (user-error "Package `%s' has no VC data" | ||
| 920 | (package-desc-name pkg-desc))))) | ||
| 921 | (package-vc--clone pkg-desc pkg-spec directory rev) | ||
| 922 | (find-file directory))) | ||
| 923 | |||
| 924 | ;;;###autoload | ||
| 925 | (defun package-vc-install-from-checkout (dir &optional name interactive) | ||
| 926 | "Install the package NAME from its source directory DIR. | ||
| 927 | NAME defaults to the base name of DIR. Interactively, prompt the user | ||
| 928 | for DIR, which should be a directory under version control, typically | ||
| 929 | one created by `package-vc-checkout'. If invoked interactively with a | ||
| 930 | prefix argument, prompt the user for the NAME of the package to set up. | ||
| 931 | If the optional argument INTERACTIVE is non-nil (as happens | ||
| 932 | interactively), DIR must be an absolute file name." | ||
| 933 | (interactive (let ((dir (expand-file-name (read-directory-name "Directory: ")))) | ||
| 934 | (list dir (and current-prefix-arg | ||
| 935 | (let ((base (file-name-base | ||
| 936 | (directory-file-name | ||
| 937 | dir)))) | ||
| 938 | (read-string | ||
| 939 | (format-prompt "Package name" base) | ||
| 940 | nil nil base))) | ||
| 941 | :interactive))) | ||
| 942 | (package-vc--archives-initialize) | ||
| 943 | (let* ((dir (if interactive dir (expand-file-name dir))) ;avoid double expansion | ||
| 944 | (name (or name (file-name-base (directory-file-name dir)))) | ||
| 945 | (pkg-dir (expand-file-name name package-user-dir)) | ||
| 946 | (package-vc-selected-packages | ||
| 947 | (cons (list name :lisp-dir dir) | ||
| 948 | package-vc-selected-packages))) | ||
| 949 | (when (file-exists-p pkg-dir) | ||
| 950 | (if (yes-or-no-p (format "Overwrite previous checkout for package `%s'?" name)) | ||
| 951 | (package--delete-directory pkg-dir) | ||
| 952 | (error "There already exists a checkout for %s" name))) | ||
| 953 | (make-directory pkg-dir t) | ||
| 954 | (package-vc--unpack-1 | ||
| 955 | (package-desc-create | ||
| 956 | :name (intern name) | ||
| 957 | :dir pkg-dir | ||
| 958 | :kind 'vc) | ||
| 959 | (file-name-as-directory pkg-dir)))) | ||
| 960 | |||
| 961 | ;;;###autoload | ||
| 962 | (defun package-vc-rebuild (pkg-desc) | ||
| 963 | "Rebuild the installation for package given by PKG-DESC. | ||
| 964 | Rebuilding an installation means scraping for new autoload | ||
| 965 | cookies, re-compiling Emacs Lisp files, building and installing | ||
| 966 | any documentation, downloading any missing dependencies. This | ||
| 967 | command does not fetch new revisions from a remote server. That | ||
| 968 | is the responsibility of `package-vc-upgrade'. Interactively, | ||
| 969 | prompt for the name of the package to rebuild." | ||
| 970 | (interactive (list (package-vc--read-package-desc "Rebuild package: " t))) | ||
| 971 | (package-vc--unpack-1 pkg-desc (package-desc-dir pkg-desc))) | ||
| 972 | |||
| 973 | ;;;###autoload | ||
| 974 | (defun package-vc-prepare-patch (pkg-desc subject revisions) | ||
| 975 | "Email patches for REVISIONS to maintainer of package PKG-DESC using SUBJECT. | ||
| 976 | |||
| 977 | PKG-DESC is a package descriptor and SUBJECT is the subject of | ||
| 978 | the message. | ||
| 979 | |||
| 980 | Interactively, prompt for PKG-DESC, SUBJECT, and REVISIONS. When | ||
| 981 | invoked with a numerical prefix argument, use the last N | ||
| 982 | revisions. When invoked interactively in a Log View buffer with | ||
| 983 | marked revisions, use those. | ||
| 984 | |||
| 985 | See also `vc-prepare-patch'." | ||
| 986 | (interactive | ||
| 987 | (list (package-vc--read-package-desc "Package to prepare a patch for: " t) | ||
| 988 | (and (not vc-prepare-patches-separately) | ||
| 989 | (read-string "Subject: " "[PATCH] " nil nil t)) | ||
| 990 | (vc-prepare-patch-prompt-revisions))) | ||
| 991 | (let ((default-directory (package-desc-dir pkg-desc))) | ||
| 992 | (vc-prepare-patch (package-maintainers pkg-desc t) | ||
| 993 | subject revisions))) | ||
| 994 | |||
| 995 | (defun package-vc-log-incoming (pkg-desc) | ||
| 996 | "Call `vc-log-incoming' for the package PKG-DESC." | ||
| 997 | (interactive | ||
| 998 | (list (package-vc--read-package-desc "Incoming log for package: " t))) | ||
| 999 | (let ((default-directory (package-desc-dir pkg-desc))) | ||
| 1000 | (call-interactively #'vc-log-incoming))) | ||
| 1001 | |||
| 1002 | (provide 'package-vc) | ||
| 1003 | ;;; package-vc.el ends here | ||