Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

712 lines
26 KiB

  1. ;;; epl.el --- Emacs Package Library -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2013-2015 Sebastian Wiesner
  3. ;; Copyright (C) 1985-1986, 1992, 1994-1995, 1999-2015 Free Software
  4. ;; Author: Sebastian Wiesner <swiesner@lunaryorn.com>
  5. ;; Maintainer: Johan Andersson <johan.rejeep@gmail.com>
  6. ;; Sebastian Wiesner <swiesner@lunaryorn.com>
  7. ;; Version: 0.9
  8. ;; Package-Version: 0.9
  9. ;; Package-Commit: fd906d3f92d58ecf24169055744409886ceb06ce
  10. ;; Package-Requires: ((cl-lib "0.3"))
  11. ;; Keywords: convenience
  12. ;; URL: http://github.com/cask/epl
  13. ;; This file is NOT part of GNU Emacs.
  14. ;; This program is free software; you can redistribute it and/or modify
  15. ;; it under the terms of the GNU General Public License as published by
  16. ;; the Free Software Foundation, either version 3 of the License, or
  17. ;; (at your option) any later version.
  18. ;; This program is distributed in the hope that it will be useful,
  19. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. ;; GNU General Public License for more details.
  22. ;; You should have received a copy of the GNU General Public License
  23. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  24. ;;; Commentary:
  25. ;; A package management library for Emacs, based on package.el.
  26. ;; The purpose of this library is to wrap all the quirks and hassle of
  27. ;; package.el into a sane API.
  28. ;; The following functions comprise the public interface of this library:
  29. ;;; Package directory selection
  30. ;; `epl-package-dir' gets the directory of packages.
  31. ;; `epl-default-package-dir' gets the default package directory.
  32. ;; `epl-change-package-dir' changes the directory of packages.
  33. ;;; Package system management
  34. ;; `epl-initialize' initializes the package system and activates all
  35. ;; packages.
  36. ;; `epl-reset' resets the package system.
  37. ;; `epl-refresh' refreshes all package archives.
  38. ;; `epl-add-archive' adds a new package archive.
  39. ;;; Package objects
  40. ;; Struct `epl-requirement' describes a requirement of a package with `name' and
  41. ;; `version' slots.
  42. ;; `epl-requirement-version-string' gets a requirement version as string.
  43. ;; Struct `epl-package' describes an installed or installable package with a
  44. ;; `name' and some internal `description'.
  45. ;; `epl-package-version' gets the version of a package.
  46. ;; `epl-package-version-string' gets the version of a package as string.
  47. ;; `epl-package-summary' gets the summary of a package.
  48. ;; `epl-package-requirements' gets the requirements of a package.
  49. ;; `epl-package-directory' gets the installation directory of a package.
  50. ;; `epl-package-from-buffer' creates a package object for the package contained
  51. ;; in the current buffer.
  52. ;; `epl-package-from-file' creates a package object for a package file, either
  53. ;; plain lisp or tarball.
  54. ;; `epl-package-from-descriptor-file' creates a package object for a package
  55. ;; description (i.e. *-pkg.el) file.
  56. ;;; Package database access
  57. ;; `epl-package-installed-p' determines whether a package is installed, either
  58. ;; built-in or explicitly installed.
  59. ;; `epl-package-outdated-p' determines whether a package is outdated, that is,
  60. ;; whether a package with a higher version number is available.
  61. ;; `epl-built-in-packages', `epl-installed-packages', `epl-outdated-packages'
  62. ;; and `epl-available-packages' get all packages built-in, installed, outdated,
  63. ;; or available for installation respectively.
  64. ;; `epl-find-built-in-package', `epl-find-installed-packages' and
  65. ;; `epl-find-available-packages' find built-in, installed and available packages
  66. ;; by name.
  67. ;; `epl-find-upgrades' finds all upgradable packages.
  68. ;; `epl-built-in-p' return true if package is built-in to Emacs.
  69. ;;; Package operations
  70. ;; `epl-install-file' installs a package file.
  71. ;; `epl-package-install' installs a package.
  72. ;; `epl-package-delete' deletes a package.
  73. ;; `epl-upgrade' upgrades packages.
  74. ;;; Code:
  75. (require 'cl-lib)
  76. (require 'package)
  77. (unless (fboundp #'define-error)
  78. ;; `define-error' for 24.3 and earlier, copied from subr.el
  79. (defun define-error (name message &optional parent)
  80. "Define NAME as a new error signal.
  81. MESSAGE is a string that will be output to the echo area if such an error
  82. is signaled without being caught by a `condition-case'.
  83. PARENT is either a signal or a list of signals from which it inherits.
  84. Defaults to `error'."
  85. (unless parent (setq parent 'error))
  86. (let ((conditions
  87. (if (consp parent)
  88. (apply #'append
  89. (mapcar (lambda (parent)
  90. (cons parent
  91. (or (get parent 'error-conditions)
  92. (error "Unknown signal `%s'" parent))))
  93. parent))
  94. (cons parent (get parent 'error-conditions)))))
  95. (put name 'error-conditions
  96. (delete-dups (copy-sequence (cons name conditions))))
  97. (when message (put name 'error-message message)))))
  98. (defsubst epl--package-desc-p (package)
  99. "Whether PACKAGE is a `package-desc' object.
  100. Like `package-desc-p', but return nil, if `package-desc-p' is not
  101. defined as function."
  102. (and (fboundp 'package-desc-p) (package-desc-p package)))
  103. ;;; EPL errors
  104. (define-error 'epl-error "EPL error")
  105. (define-error 'epl-invalid-package "Invalid EPL package" 'epl-error)
  106. (define-error 'epl-invalid-package-file "Invalid EPL package file"
  107. 'epl-invalid-package)
  108. ;;; Package directory
  109. (defun epl-package-dir ()
  110. "Get the directory of packages."
  111. package-user-dir)
  112. (defun epl-default-package-dir ()
  113. "Get the default directory of packages."
  114. (eval (car (get 'package-user-dir 'standard-value))))
  115. (defun epl-change-package-dir (directory)
  116. "Change the directory of packages to DIRECTORY."
  117. (setq package-user-dir directory)
  118. (epl-initialize))
  119. ;;; Package system management
  120. (defvar epl--load-path-before-initialize nil
  121. "Remember the load path for `epl-reset'.")
  122. (defun epl-initialize (&optional no-activate)
  123. "Load Emacs Lisp packages and activate them.
  124. With NO-ACTIVATE non-nil, do not activate packages."
  125. (setq epl--load-path-before-initialize load-path)
  126. (package-initialize no-activate))
  127. (defalias 'epl-refresh 'package-refresh-contents)
  128. (defun epl-add-archive (name url)
  129. "Add a package archive with NAME and URL."
  130. (add-to-list 'package-archives (cons name url)))
  131. (defun epl-reset ()
  132. "Reset the package system.
  133. Clear the list of installed and available packages, the list of
  134. package archives and reset the package directory."
  135. (setq package-alist nil
  136. package-archives nil
  137. package-archive-contents nil
  138. load-path epl--load-path-before-initialize)
  139. (when (boundp 'package-obsolete-alist) ; Legacy package.el
  140. (setq package-obsolete-alist nil))
  141. (epl-change-package-dir (epl-default-package-dir)))
  142. ;;; Package structures
  143. (cl-defstruct (epl-requirement
  144. (:constructor epl-requirement-create))
  145. "Structure describing a requirement.
  146. Slots:
  147. `name' The name of the required package, as symbol.
  148. `version' The version of the required package, as version list."
  149. name
  150. version)
  151. (defun epl-requirement-version-string (requirement)
  152. "The version of a REQUIREMENT, as string."
  153. (package-version-join (epl-requirement-version requirement)))
  154. (cl-defstruct (epl-package (:constructor epl-package-create))
  155. "Structure representing a package.
  156. Slots:
  157. `name' The package name, as symbol.
  158. `description' The package description.
  159. The format package description varies between package.el
  160. variants. For `package-desc' variants, it is simply the
  161. corresponding `package-desc' object. For legacy variants, it is
  162. a vector `[VERSION REQS DOCSTRING]'.
  163. Do not access `description' directly, but instead use the
  164. `epl-package' accessors."
  165. name
  166. description)
  167. (defmacro epl-package-as-description (var &rest body)
  168. "Cast VAR to a package description in BODY.
  169. VAR is a symbol, bound to an `epl-package' object. This macro
  170. casts this object to the `description' object, and binds the
  171. description to VAR in BODY."
  172. (declare (indent 1))
  173. (unless (symbolp var)
  174. (signal 'wrong-type-argument (list #'symbolp var)))
  175. `(if (epl-package-p ,var)
  176. (let ((,var (epl-package-description ,var)))
  177. ,@body)
  178. (signal 'wrong-type-argument (list #'epl-package-p ,var))))
  179. (defsubst epl-package--package-desc-p (package)
  180. "Whether the description of PACKAGE is a `package-desc'."
  181. (epl--package-desc-p (epl-package-description package)))
  182. (defun epl-package-version (package)
  183. "Get the version of PACKAGE, as version list."
  184. (epl-package-as-description package
  185. (cond
  186. ((fboundp 'package-desc-version) (package-desc-version package))
  187. ;; Legacy
  188. ((fboundp 'package-desc-vers)
  189. (let ((version (package-desc-vers package)))
  190. (if (listp version) version (version-to-list version))))
  191. (:else (error "Cannot get version from %S" package)))))
  192. (defun epl-package-version-string (package)
  193. "Get the version from a PACKAGE, as string."
  194. (package-version-join (epl-package-version package)))
  195. (defun epl-package-summary (package)
  196. "Get the summary of PACKAGE, as string."
  197. (epl-package-as-description package
  198. (cond
  199. ((fboundp 'package-desc-summary) (package-desc-summary package))
  200. ((fboundp 'package-desc-doc) (package-desc-doc package)) ; Legacy
  201. (:else (error "Cannot get summary from %S" package)))))
  202. (defsubst epl-requirement--from-req (req)
  203. "Create a `epl-requirement' from a `package-desc' REQ."
  204. (let ((version (cadr req)))
  205. (epl-requirement-create :name (car req)
  206. :version (if (listp version) version
  207. (version-to-list version)))))
  208. (defun epl-package-requirements (package)
  209. "Get the requirements of PACKAGE.
  210. The requirements are a list of `epl-requirement' objects."
  211. (epl-package-as-description package
  212. (mapcar #'epl-requirement--from-req (package-desc-reqs package))))
  213. (defun epl-package-directory (package)
  214. "Get the directory PACKAGE is installed to.
  215. Return the absolute path of the installation directory of
  216. PACKAGE, or nil, if PACKAGE is not installed."
  217. (cond
  218. ((fboundp 'package-desc-dir)
  219. (package-desc-dir (epl-package-description package)))
  220. ((fboundp 'package--dir)
  221. (package--dir (symbol-name (epl-package-name package))
  222. (epl-package-version-string package)))
  223. (:else (error "Cannot get package directory from %S" package))))
  224. (defun epl-package-->= (pkg1 pkg2)
  225. "Determine whether PKG1 is before PKG2 by version."
  226. (not (version-list-< (epl-package-version pkg1)
  227. (epl-package-version pkg2))))
  228. (defun epl-package--from-package-desc (package-desc)
  229. "Create an `epl-package' from a PACKAGE-DESC.
  230. PACKAGE-DESC is a `package-desc' object, from recent package.el
  231. variants."
  232. (if (and (fboundp 'package-desc-name)
  233. (epl--package-desc-p package-desc))
  234. (epl-package-create :name (package-desc-name package-desc)
  235. :description package-desc)
  236. (signal 'wrong-type-argument (list 'epl--package-desc-p package-desc))))
  237. (defun epl-package--parse-info (info)
  238. "Parse a package.el INFO."
  239. (if (epl--package-desc-p info)
  240. (epl-package--from-package-desc info)
  241. ;; For legacy package.el, info is a vector [NAME REQUIRES DESCRIPTION
  242. ;; VERSION COMMENTARY]. We need to re-shape this vector into the
  243. ;; `package-alist' format [VERSION REQUIRES DESCRIPTION] to attach it to the
  244. ;; new `epl-package'.
  245. (let ((name (intern (aref info 0)))
  246. (info (vector (aref info 3) (aref info 1) (aref info 2))))
  247. (epl-package-create :name name :description info))))
  248. (defun epl-package-from-buffer (&optional buffer)
  249. "Create an `epl-package' object from BUFFER.
  250. BUFFER defaults to the current buffer.
  251. Signal `epl-invalid-package' if the buffer does not contain a
  252. valid package file."
  253. (let ((info (with-current-buffer (or buffer (current-buffer))
  254. (condition-case err
  255. (package-buffer-info)
  256. (error (signal 'epl-invalid-package (cdr err)))))))
  257. (epl-package--parse-info info)))
  258. (defun epl-package-from-lisp-file (file-name)
  259. "Parse the package headers the file at FILE-NAME.
  260. Return an `epl-package' object with the header metadata."
  261. (with-temp-buffer
  262. (insert-file-contents file-name)
  263. (condition-case err
  264. (epl-package-from-buffer (current-buffer))
  265. ;; Attach file names to invalid package errors
  266. (epl-invalid-package
  267. (signal 'epl-invalid-package-file (cons file-name (cdr err))))
  268. ;; Forward other errors
  269. (error (signal (car err) (cdr err))))))
  270. (defun epl-package-from-tar-file (file-name)
  271. "Parse the package tarball at FILE-NAME.
  272. Return a `epl-package' object with the meta data of the tarball
  273. package in FILE-NAME."
  274. (condition-case nil
  275. ;; In legacy package.el, `package-tar-file-info' takes the name of the tar
  276. ;; file to parse as argument. In modern package.el, it has no arguments
  277. ;; and works on the current buffer. Hence, we just try to call the legacy
  278. ;; version, and if that fails because of a mismatch between formal and
  279. ;; actual arguments, we use the modern approach. To avoid spurious
  280. ;; signature warnings by the byte compiler, we suppress warnings when
  281. ;; calling the function.
  282. (epl-package--parse-info (with-no-warnings
  283. (package-tar-file-info file-name)))
  284. (wrong-number-of-arguments
  285. (with-temp-buffer
  286. (insert-file-contents-literally file-name)
  287. ;; Switch to `tar-mode' to enable extraction of the file. Modern
  288. ;; `package-tar-file-info' relies on `tar-mode', and signals an error if
  289. ;; called in a buffer with a different mode.
  290. (tar-mode)
  291. (epl-package--parse-info (with-no-warnings
  292. (package-tar-file-info)))))))
  293. (defun epl-package-from-file (file-name)
  294. "Parse the package at FILE-NAME.
  295. Return an `epl-package' object with the meta data of the package
  296. at FILE-NAME."
  297. (if (string-match-p (rx ".tar" string-end) file-name)
  298. (epl-package-from-tar-file file-name)
  299. (epl-package-from-lisp-file file-name)))
  300. (defun epl-package--parse-descriptor-requirement (requirement)
  301. "Parse a REQUIREMENT in a package descriptor."
  302. ;; This function is only called on legacy package.el. On package-desc
  303. ;; package.el, we just let package.el do the work.
  304. (cl-destructuring-bind (name version-string) requirement
  305. (list name (version-to-list version-string))))
  306. (defun epl-package-from-descriptor-file (descriptor-file)
  307. "Load a `epl-package' from a package DESCRIPTOR-FILE.
  308. A package descriptor is a file defining a new package. Its name
  309. typically ends with -pkg.el."
  310. (with-temp-buffer
  311. (insert-file-contents descriptor-file)
  312. (goto-char (point-min))
  313. (let ((sexp (read (current-buffer))))
  314. (unless (eq (car sexp) 'define-package)
  315. (error "%S is no valid package descriptor" descriptor-file))
  316. (if (and (fboundp 'package-desc-from-define)
  317. (fboundp 'package-desc-name))
  318. ;; In Emacs snapshot, we can conveniently call a function to parse the
  319. ;; descriptor
  320. (let ((desc (apply #'package-desc-from-define (cdr sexp))))
  321. (epl-package-create :name (package-desc-name desc)
  322. :description desc))
  323. ;; In legacy package.el, we must manually deconstruct the descriptor,
  324. ;; because the load function has eval's the descriptor and has a lot of
  325. ;; global side-effects.
  326. (cl-destructuring-bind
  327. (name version-string summary requirements) (cdr sexp)
  328. (epl-package-create
  329. :name (intern name)
  330. :description
  331. (vector (version-to-list version-string)
  332. (mapcar #'epl-package--parse-descriptor-requirement
  333. ;; Strip the leading `quote' from the package list
  334. (cadr requirements))
  335. summary)))))))
  336. ;;; Package database access
  337. (defun epl-package-installed-p (package &optional min-version)
  338. "Determine whether a PACKAGE, of MIN-VERSION or newer, is installed.
  339. PACKAGE is either a package name as symbol, or a package object.
  340. When a explicit MIN-VERSION is provided it overwrites the version of the PACKAGE object."
  341. (let ((name (if (epl-package-p package)
  342. (epl-package-name package)
  343. package))
  344. (min-version (or min-version (and (epl-package-p package)
  345. (epl-package-version package)))))
  346. (package-installed-p name min-version)))
  347. (defun epl--parse-built-in-entry (entry)
  348. "Parse an ENTRY from the list of built-in packages.
  349. Return the corresponding `epl-package' object."
  350. (if (fboundp 'package--from-builtin)
  351. ;; In package-desc package.el, convert the built-in package to a
  352. ;; `package-desc' and convert that to an `epl-package'
  353. (epl-package--from-package-desc (package--from-builtin entry))
  354. (epl-package-create :name (car entry) :description (cdr entry))))
  355. (defun epl-built-in-packages ()
  356. "Get all built-in packages.
  357. Return a list of `epl-package' objects."
  358. ;; This looks mighty strange, but it's the only way to force package.el to
  359. ;; build the list of built-in packages. Without this, `package--builtins'
  360. ;; might be empty.
  361. (package-built-in-p 'foo)
  362. (mapcar #'epl--parse-built-in-entry package--builtins))
  363. (defun epl-find-built-in-package (name)
  364. "Find a built-in package with NAME.
  365. NAME is a package name, as symbol.
  366. Return the built-in package as `epl-package' object, or nil if
  367. there is no built-in package with NAME."
  368. (when (package-built-in-p name)
  369. ;; We must call `package-built-in-p' *before* inspecting
  370. ;; `package--builtins', because otherwise `package--builtins' might be
  371. ;; empty.
  372. (epl--parse-built-in-entry (assq name package--builtins))))
  373. (defun epl-package-outdated-p (package)
  374. "Determine whether a PACKAGE is outdated.
  375. A package is outdated, if there is an available package with a
  376. higher version.
  377. PACKAGE is either a package name as symbol, or a package object.
  378. In the former case, test the installed or built-in package with
  379. the highest version number, in the later case, test the package
  380. object itself.
  381. Return t, if the package is outdated, or nil otherwise."
  382. (let* ((package (if (epl-package-p package)
  383. package
  384. (or (car (epl-find-installed-packages package))
  385. (epl-find-built-in-package package))))
  386. (available (car (epl-find-available-packages
  387. (epl-package-name package)))))
  388. (and package available (version-list-< (epl-package-version package)
  389. (epl-package-version available)))))
  390. (defun epl--parse-package-list-entry (entry)
  391. "Parse a list of packages from ENTRY.
  392. ENTRY is a single entry in a package list, e.g. `package-alist',
  393. `package-archive-contents', etc. Typically it is a cons cell,
  394. but the exact format varies between package.el versions. This
  395. function tries to parse all known variants.
  396. Return a list of `epl-package' objects parsed from ENTRY."
  397. (let ((descriptions (cdr entry)))
  398. (cond
  399. ((listp descriptions)
  400. (sort (mapcar #'epl-package--from-package-desc descriptions)
  401. #'epl-package-->=))
  402. ;; Legacy package.el has just a single package in an entry, which is a
  403. ;; standard description vector
  404. ((vectorp descriptions)
  405. (list (epl-package-create :name (car entry)
  406. :description descriptions)))
  407. (:else (error "Cannot parse entry %S" entry)))))
  408. (defun epl-installed-packages ()
  409. "Get all installed packages.
  410. Return a list of package objects."
  411. (apply #'append (mapcar #'epl--parse-package-list-entry package-alist)))
  412. (defsubst epl--filter-outdated-packages (packages)
  413. "Filter outdated packages from PACKAGES."
  414. (let (res)
  415. (dolist (package packages)
  416. (when (epl-package-outdated-p package)
  417. (push package res)))
  418. (nreverse res)))
  419. (defun epl-outdated-packages ()
  420. "Get all outdated packages, as in `epl-package-outdated-p'.
  421. Return a list of package objects."
  422. (epl--filter-outdated-packages (epl-installed-packages)))
  423. (defsubst epl--find-package-in-list (name list)
  424. "Find a package by NAME in a package LIST.
  425. Return a list of corresponding `epl-package' objects."
  426. (let ((entry (assq name list)))
  427. (when entry
  428. (epl--parse-package-list-entry entry))))
  429. (defun epl-find-installed-package (name)
  430. "Find the latest installed package by NAME.
  431. NAME is a package name, as symbol.
  432. Return the installed package with the highest version number as
  433. `epl-package' object, or nil, if no package with NAME is
  434. installed."
  435. (car (epl-find-installed-packages name)))
  436. (make-obsolete 'epl-find-installed-package 'epl-find-installed-packages "0.7")
  437. (defun epl-find-installed-packages (name)
  438. "Find all installed packages by NAME.
  439. NAME is a package name, as symbol.
  440. Return a list of all installed packages with NAME, sorted by
  441. version number in descending order. Return nil, if there are no
  442. packages with NAME."
  443. (epl--find-package-in-list name package-alist))
  444. (defun epl-available-packages ()
  445. "Get all packages available for installation.
  446. Return a list of package objects."
  447. (apply #'append (mapcar #'epl--parse-package-list-entry
  448. package-archive-contents)))
  449. (defun epl-find-available-packages (name)
  450. "Find available packages for NAME.
  451. NAME is a package name, as symbol.
  452. Return a list of available packages for NAME, sorted by version
  453. number in descending order. Return nil, if there are no packages
  454. for NAME."
  455. (epl--find-package-in-list name package-archive-contents))
  456. (cl-defstruct (epl-upgrade
  457. (:constructor epl-upgrade-create))
  458. "Structure describing an upgradable package.
  459. Slots:
  460. `installed' The installed package
  461. `available' The package available for installation."
  462. installed
  463. available)
  464. (defun epl-find-upgrades (&optional packages)
  465. "Find all upgradable PACKAGES.
  466. PACKAGES is a list of package objects to upgrade, defaulting to
  467. all installed packages.
  468. Return a list of `epl-upgrade' objects describing all upgradable
  469. packages."
  470. (let ((packages (or packages (epl-installed-packages)))
  471. upgrades)
  472. (dolist (pkg packages)
  473. (let* ((version (epl-package-version pkg))
  474. (name (epl-package-name pkg))
  475. ;; Find the latest available package for NAME
  476. (available-pkg (car (epl-find-available-packages name)))
  477. (available-version (when available-pkg
  478. (epl-package-version available-pkg))))
  479. (when (and available-version (version-list-< version available-version))
  480. (push (epl-upgrade-create :installed pkg
  481. :available available-pkg)
  482. upgrades))))
  483. (nreverse upgrades)))
  484. (defalias 'epl-built-in-p 'package-built-in-p)
  485. ;;; Package operations
  486. (defun epl-install-file (file)
  487. "Install a package from FILE, like `package-install-file'."
  488. (interactive (advice-eval-interactive-spec
  489. (cadr (interactive-form #'package-install-file))))
  490. (apply #'package-install-file (list file))
  491. (let ((package (epl-package-from-file file)))
  492. (unless (epl-package--package-desc-p package)
  493. (epl--kill-autoload-buffer package))))
  494. (defun epl--kill-autoload-buffer (package)
  495. "Kill the buffer associated with autoloads for PACKAGE."
  496. (let* ((auto-name (format "%s-autoloads.el" (epl-package-name package)))
  497. (generated-autoload-file (expand-file-name auto-name (epl-package-directory package)))
  498. (buf (find-buffer-visiting generated-autoload-file)))
  499. (when buf (kill-buffer buf))))
  500. (defun epl-package-install (package &optional force)
  501. "Install a PACKAGE.
  502. PACKAGE is a `epl-package' object. If FORCE is given and
  503. non-nil, install PACKAGE, even if it is already installed."
  504. (when (or force (not (epl-package-installed-p package)))
  505. (if (epl-package--package-desc-p package)
  506. (package-install (epl-package-description package))
  507. ;; The legacy API installs by name. We have no control over versioning,
  508. ;; etc.
  509. (package-install (epl-package-name package))
  510. (epl--kill-autoload-buffer package))))
  511. (defun epl-package-delete (package)
  512. "Delete a PACKAGE.
  513. PACKAGE is a `epl-package' object to delete."
  514. ;; package-delete allows for packages being trashed instead of fully deleted.
  515. ;; Let's prevent his silly behavior
  516. (let ((delete-by-moving-to-trash nil))
  517. ;; The byte compiler will warn us that we are calling `package-delete' with
  518. ;; the wrong number of arguments, since it can't infer that we guarantee to
  519. ;; always call the correct version. Thus we suppress all warnings when
  520. ;; calling `package-delete'. I wish there was a more granular way to
  521. ;; disable just that specific warning, but it is what it is.
  522. (if (epl-package--package-desc-p package)
  523. (with-no-warnings
  524. (package-delete (epl-package-description package)))
  525. ;; The legacy API deletes by name (as string!) and version instead by
  526. ;; descriptor. Hence `package-delete' takes two arguments. For some
  527. ;; insane reason, the arguments are strings here!
  528. (let ((name (symbol-name (epl-package-name package)))
  529. (version (epl-package-version-string package)))
  530. (with-no-warnings
  531. (package-delete name version))
  532. ;; Legacy package.el does not remove the deleted package
  533. ;; from the `package-alist', so we do it manually here.
  534. (let ((pkg (assq (epl-package-name package) package-alist)))
  535. (when pkg
  536. (setq package-alist (delq pkg package-alist))))))))
  537. (defun epl-upgrade (&optional packages preserve-obsolete)
  538. "Upgrade PACKAGES.
  539. PACKAGES is a list of package objects to upgrade, defaulting to
  540. all installed packages.
  541. The old versions of the updated packages are deleted, unless
  542. PRESERVE-OBSOLETE is non-nil.
  543. Return a list of all performed upgrades, as a list of
  544. `epl-upgrade' objects."
  545. (let ((upgrades (epl-find-upgrades packages)))
  546. (dolist (upgrade upgrades)
  547. (epl-package-install (epl-upgrade-available upgrade) 'force)
  548. (unless preserve-obsolete
  549. (epl-package-delete (epl-upgrade-installed upgrade))))
  550. upgrades))
  551. (provide 'epl)
  552. ;;; epl.el ends here