Ce serveur Gitlab sera éteint le 30 juin 2020, pensez à migrer vos projets vers les serveurs gitlab-research.centralesupelec.fr et gitlab-student.centralesupelec.fr !

el-get.el 32.5 KB
Newer Older
1
;;; el-get.el --- Manage the external elisp bits and pieces you depend upon
2
;;
3
;; Copyright (C) 2010-2011 Dimitri Fontaine
4 5
;;
;; Author: Dimitri Fontaine <dim@tapoueh.org>
Dimitri Fontaine's avatar
Dimitri Fontaine committed
6 7
;; URL: http://www.emacswiki.org/emacs/el-get
;; GIT: https://github.com/dimitri/el-get
8
;; Version: 4.0
9
;; Created: 2010-06-17
10 11
;; Keywords: emacs package elisp install elpa git git-svn bzr cvs svn darcs hg
;;           apt-get fink pacman http http-tar emacswiki
12 13 14 15
;; Licence: WTFPL, grab your copy here: http://sam.zoy.org/wtfpl/
;;
;; This file is NOT part of GNU Emacs.
;;
16 17
;; Install
;;     Please see the README.asciidoc file from the same distribution
18 19 20 21 22 23 24 25 26

;;; Commentary:
;;
;; Version String are now inspired by how Emacs itself numbers its version.
;; First is the major version number, then a dot, then the minor version
;; number.  The minor version number is 0 when still developping the next
;; major version.
;;
;; So 2.0 is a developer release while 2.1 will be the next stable release.
27
;;
28
;; Please note that this versioning policy has been picked while backing
29 30 31
;; 1.2~dev, so 1.0 was a "stable" release in fact.  Ah, history.

;;; Change Log:
32
;;
33 34
;;  4.0 - WIP - To infinity, and beyond!
;;
35 36 37 38
;;   - code refactoring
;;   - fix dependency tracking at install and init times
;;   - fix autoloading so that it happens before notifying install is done
;;   - add some tests
39
;;   - deprecate package.el from the old days, only include the Emacs24 one
40
;;   - implement :builtin property (useful for dealing with package.el)
41
;;   - fix recipes :build commands, must be either lists of strings or expr
42
;;   - add support for el-get-reload and do that at update time
43
;;   - implement :checksum property for http kinds of files
44
;;
Dimitri Fontaine's avatar
Dimitri Fontaine committed
45
;;  3.1 - 2011-09-15 - Get a fix
46
;;
Dimitri Fontaine's avatar
Dimitri Fontaine committed
47
;;   - support for package dependencies
48
;;   - rely on package status for `el-get' to install and init them
Dimitri Fontaine's avatar
Dimitri Fontaine committed
49
;;   - M-x el-get-list-packages
50
;;   - support for :branch in git
Dimitri Fontaine's avatar
Dimitri Fontaine committed
51 52
;;   - new recipes, galore
;;   - bug fixes, byte compiling, windows compatibility, etc
53
;;   - recipe files are now *.rcp rather than *.el (el still supported)
54
;;   - el-get-user-package-directory allows to setup init-<package>.el files
Dimitri Fontaine's avatar
Dimitri Fontaine committed
55
;;   - remove M-x el-get-sync, now obsolete
56
;;
Julien Danjou's avatar
Julien Danjou committed
57 58 59 60 61
;;  2.2 - 2011-05-26 - Fix the merge
;;
;;   - Revert changes introduced by a merge done by mistake
;;
;;  2.1 - 2011-05-25 - Still growing, getting lazy
62
;;
63
;;   - Add support for autoloads, per Dave Abrahams
64
;;   - fix 'wait support for http (using sync retrieval)
65
;;   - code cleanup per Dave Abrahams, lots of it
66
;;   - add function M-x el-get-update-all
67
;;   - Implement M-x el-get-make-recipes
68 69
;;   - byte-compile at build time rather than at init time
;;   - and use a "clean room" external emacs -Q for byte compiling
70
;;   - allow to skip autoloads either globally or per-package
71
;;   - better checks and errors for commands used when installing packages
72
;;   - register `el-get-sources' against the custom interface
73
;;   - el-get will now accept a list of sources to install or init
74
;;   - open el-get-{install,init,remove,update} from `el-get-sources' only
75
;;   - add :prepare and :post-init and :lazy, and `el-get-is-lazy'
Dimitri Fontaine's avatar
Dimitri Fontaine committed
76
;;   - add support for :repo for ELPA packages
77
;;
Dimitri Fontaine's avatar
Dimitri Fontaine committed
78
;;  1.1 - 2010-12-20 - Nobody's testing until the release
79 80 81 82
;;
;;   - Adapt to package.el from Emacs24 by using relative symlinks to ELPA
;;     packages ((package-user-dir) is "~/.emacs.d/elpa" now, so needs to
;;     get expanded at least)
83
;;   - Allow to bypass byte compiling entirely with a single global var
84
;;   - Have http local file default to something sane, not package.el
85
;;   - Implement support for svn and darcs too
86
;;   - Still more recipes
Dimitri Fontaine's avatar
Dimitri Fontaine committed
87
;;   - Add support for the `pacman' package manager (ARCH Linux)
88
;;   - Add support for mercurial
89 90
;;   - (el-get 'sync) now really means synchronous, and serialized too
;;   - el-get-start-process-list implements :sync, defaults to nil (async)
91
;;   - Implement a :localname package property to help with some kind of URLs
92
;;   - Add el-get-post-init-hooks
93
;;   - Allow build commands to be evaluated, hence using some emacs variables
94
;;   - Finaly walk the extra mile and remove "required" packages at install
95
;;   - Implement support for the "Do you want to continue" apt-get prompt
96
;;   - implement :before user defined function to run before init
97
;;
98
;;  1.0 - 2010-10-07 - Can I haz your recipes?
99 100 101
;;
;;   - Implement el-get recipes so that el-get-sources can be a simple list
;;     of symbols. Now that there's an authoritative git repository, where
102
;;     to share the recipes is easy.
103
;;   - Add support for emacswiki directly, save from having to enter the URL
104 105 106
;;   - Implement package status on-disk saving so that installing over a
;;     previously failed install is in theory possible. Currently `el-get'
;;     will refrain from removing your package automatically, though.
107
;;   - Fix ELPA remove method, adding a "removed" state too.
108
;;   - Implement CVS login support.
109 110
;;   - Add lots of recipes
;;   - Add support for `system-type' specific build commands
111
;;   - Byte compile files from the load-path entries or :compile files
112 113
;;   - Implement support for git submodules with the command
;;     `git submodule update --init --recursive`
114
;;   - Add catch-all post-install and post-update hooks
115
;;   - Add desktop notification on install/update.
116
;;
117 118 119 120 121 122
;;  0.9 - 2010-08-24 - build me a shell
;;
;;   - have `el-get-build' use start-process-shell-command so that you can
;;     pass-in shell commands. Beware of poor command argument "parsing"
;;     though, done with a simple `split-string'.
;;
123 124 125
;;  0.8 - 2010-08-23 - listen to the users
;;
;;   - implement :after user defined function to run at the end of init
126
;;   - add CVS support (no login support)
127 128
;;   - improve el-get-build to use async building
;;   - fix el-get-update doing so
129
;;
130 131 132 133
;;  0.7 - 2010-08-23 - archive
;;
;;   - http support is extended to `tar' archives, via the http-tar type
;;
134 135 136 137 138 139
;;  0.6 - 2010-08-12 - towards a stable version
;;
;;   - fix when asynchronous http support call post-install-fun
;;   - fix el-get-remove calling convention
;;   - add support for bzr, thanks to Kevin Fletcher
;;
140 141 142 143 144 145
;;  0.5 - 2010-08-06 - release early, fix often
;;
;;   - fix apt-get and fink install hooks to call el-get-dpkg-symlink
;;   - fix elpa and http support to follow the new call convention
;;   - use asynchronous url-retrieve facility so that http is async too
;;
146 147 148 149 150 151 152 153
;;  0.4 - 2010-08-04 - foxy release
;;
;;   - support asynchronous processes for system commands
;;       apt-get, fink, git and git-svn are run in background
;;   - support `sudo' password prompts (classic and ubuntu variants)
;;   - fix fink support
;;   - ELPA support is an option so that you can install ELPA from el-get
;;   - implement el-get-rmdir
154

155
;;; Code:
Dimitri Fontaine's avatar
Dimitri Fontaine committed
156

157
;; first some essential variables, used in other parts of the code.
158 159 160
(defgroup el-get nil "el-get customization group"
  :group 'convenience)

Dimitri Fontaine's avatar
Dimitri Fontaine committed
161
(defconst el-get-version "4.0.6" "el-get version number")
162

163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
(defconst el-get-script (or load-file-name buffer-file-name))

(defcustom el-get-dir "~/.emacs.d/el-get/"
  "Path where to install the packages."
  :group 'el-get
  :type 'directory)

(defcustom el-get-status-file
  (concat (file-name-as-directory el-get-dir) ".status.el")
  "Define where to store and read the package statuses")

(defvar el-get-autoload-file
  (concat (file-name-as-directory el-get-dir) ".loaddefs.el")
  "Where generated autoloads are saved")

(defvar el-get-emacs (concat invocation-directory invocation-name)
  "Where to find the currently running emacs, a facility for :build commands")
180

181 182 183
;;
;; Now load the rest of the el-get code
;;
184 185 186 187 188 189 190 191 192 193
(require 'el-get-core)			; core facilities used everywhere
(require 'el-get-custom)		; user tweaks and `el-get-sources'
(require 'el-get-methods)		; support for `el-get-methods', backends
(require 'el-get-recipes)		; support for dealing with recipes
(require 'el-get-status)		; support for dealing with status
(require 'el-get-build)			; building packages
(require 'el-get-byte-compile)		; byte compiling in a subprocess
(require 'el-get-dependencies)		; topological-sort of package dep graph
(require 'el-get-notify)		; notification support (dbus, growl...)
(require 'el-get-list-packages)		; menu and `el-get-describe' facilities
194
(require 'el-get-autoloads)		; manages updating el-get's loaddefs.el
195

196 197 198 199 200
;;
;; And then define some more code-level customs.  They stay here so that
;; it's easier for elisp programmers to find them and know about them.  If
;; that is too lame of an excuse, let's move them to el-get-custom.el.
;;
201 202
(defcustom el-get-post-init-hooks nil
  "Hooks to run after a package init.
Dave Abrahams's avatar
Dave Abrahams committed
203
Each hook is a unary function accepting a package"
204 205 206
  :group 'el-get
  :type 'hook)

207 208
(defcustom el-get-post-install-hooks nil
  "Hooks to run after installing a package.
Dave Abrahams's avatar
Dave Abrahams committed
209
Each hook is a unary function accepting a package"
210 211 212 213
  :group 'el-get
  :type 'hook)

(defcustom el-get-post-update-hooks nil
214
  "Hooks to run after updating a package.
Dave Abrahams's avatar
Dave Abrahams committed
215 216 217
Each hook is a unary function accepting a package"
  :group 'el-get
  :type 'hook)
218 219 220 221

(defcustom el-get-post-error-hooks nil
  "Hooks to run after package installation fails.
Each hook is a binay function accepting a package and error data"
222 223 224
  :group 'el-get
  :type 'hook)

225
(defcustom el-get-byte-compile t
226
  "Whether or not to byte-compile packages. Can be used to
227 228 229 230 231 232
disable byte-compilation globally, unless this process is not
controlled by `el-get' itself.

The cases when `el-get' loses control are with \"advanced\"
packaging systems (apt-get, fink, pacman, elpa) or when the
recipe contains a :build rule (using a Makefile for example)."
233 234 235
  :group 'el-get
  :type 'boolean)

236 237 238 239 240
(defcustom el-get-verbose nil
  "Non-nil means print messages describing progress of el-get even for fast operations."
  :group 'el-get
  :type 'boolean)

241 242 243 244 245 246 247 248 249
(defcustom el-get-byte-compile-at-init nil
  "Whether or not to byte-compile packages at init.

Turn this to t if you happen to update your packages from under
`el-get', e.g. doing `cd el-get-dir/package && git pull`
directly."
  :group 'el-get
  :type 'boolean)

250 251 252 253 254 255
(defcustom el-get-generate-autoloads t
  "Whether or not to generate autoloads for packages. Can be used
to disable autoloads globally."
  :group 'el-get
  :type 'boolean)

256 257 258 259 260
(defcustom el-get-is-lazy nil
  "Whether or not to defer evaluation of :post-init and :after
functions until libraries are required.  Will also have el-get
skip the :load and :features properties when set.  See :lazy to
force their evaluation on some packages only."
261 262 263
  :group 'el-get
  :type 'boolean)

264 265
(defvar el-get-next-packages nil
  "List of packages to install next, used when dealing with dependencies.")
266

267 268 269 270 271
(defun el-get-installation-failed (package signal-data)
  "Run all the failure hooks for PACKAGE and `signal' the car and cdr of SIGNAL-DATA."
  (run-hook-with-args 'el-get-post-error-hooks package signal-data)
  (signal (car signal-data) (cdr signal-data)))

272

273 274 275
;;
;; User Interface, Interactive part
;;
276
;;;###autoload
277 278 279 280 281
(defun el-get-version ()
  "Message the current el-get version"
  (interactive)
  (message "el-get version %s" el-get-version))

282
(defun el-get-read-all-recipe-names ()
283 284 285
  "Return the list of all known recipe names.

This is useful to use for providing completion candidates for
286 287
package names."
  (mapcar 'el-get-source-name (el-get-read-all-recipes)))
288

289
(defun el-get-error-unless-package-p (package)
290 291
  "Raise an error if PACKAGE does not name a package that has a valid recipe."
  ;; check for recipe
292 293 294 295 296
  (let ((recipe (el-get-package-def package)))
    (unless recipe
      (error "el-get: package `%s' has no recipe" package))
    (unless (plist-member recipe :type)
      (error "el-get: package `%s' has incomplete recipe (no :type)" package))))
297

298
(defun el-get-package-is-installed (package)
299
  "Raise an error if PACKAGE is already installed"
300
  (string= "installed" (el-get-package-status (el-get-as-string package))))
301

302
(defun el-get-read-package-name (action &optional filtered)
303 304
  "Ask user for a package name in minibuffer, with completion.

305 306
Completions are offered from all known package names, after
removing any packages in FILTERED."
307
  (let ((packages   (el-get-read-all-recipe-names)))
308
    (completing-read (format "%s package: " action)
309
		     (set-difference packages filtered :test 'string=) nil t)))
310

311
(defun el-get-read-recipe-name (action)
312 313 314 315
  "Ask user for a recipe name, with completion from the list of known recipe files.

This function does not deal with `el-get-sources' at all."
  (completing-read (format "%s recipe: " action)
316
                   (el-get-read-all-recipe-names) nil))
317 318 319 320 321 322 323

(defun el-get-find-recipe-file (package &optional dir)
  "Find recipe file for PACKAGE.

If no recipe file exists for PACKAGE, create a new one in DIR,
which defaults to the first element in `el-get-recipe-path'."
  (interactive (list (el-get-read-recipe-name "Find or create")))
324
  (let* ((package-el (concat (el-get-as-string package) ".rcp"))
325 326 327
	 (recipe-file (or
		       ;; If dir was specified, open or create the
		       ;; recipe file in that directory.
328
		       (when dir (expand-file-name package-el dir))
329 330 331 332
		       ;; Next, try to find an existing recipe file anywhere.
		       (el-get-recipe-filename package)
		       ;; Lastly, create a new recipe file in the first
		       ;; directory in `el-get-recipe-path'
333
		       (expand-file-name package-el
334
                                         (car el-get-recipe-path)))))
335 336
    (find-file recipe-file)))

337
(defun el-get-funcall (func fname package)
338
  "`funcal' FUNC for PACKAGE and report about FNAME when `el-get-verbose'"
339
  (when (and func (functionp func))
340 341 342
      (el-get-verbose-message "el-get: Calling :%s function for package %s"
			      fname package)
      ;; don't forget to make some variables available
343
      (let ((pdir (el-get-package-directory package)))
344
	(funcall func))))
345

346

347
(defun el-get-init (package)
348 349 350 351 352
  "Make the named PACKAGE available for use.

Add PACKAGE's directory (or `:load-path' if specified) to the
`load-path', add any its `:info' directory to
`Info-directory-list', and `require' its `:features'.  Will be
353
called by `el-get' (usually at startup) for each installed package."
354
  (interactive (list (el-get-read-package-name "Init")))
355
  (el-get-verbose-message "el-get-init: %s" package)
356 357
  (condition-case err
      (let* ((source   (el-get-package-def package))
358
             (method   (el-get-package-method source))
359 360 361 362 363 364 365 366 367 368 369 370 371
             (loads    (el-get-as-list (plist-get source :load)))
             (autoloads (plist-get source :autoloads))
             (feats    (el-get-as-list (plist-get source :features)))
             (el-path  (el-get-as-list (el-get-load-path package)))
             (lazy     (plist-get source :lazy))
             (prepare  (plist-get source :prepare))
             (before   (plist-get source :before))
             (postinit (plist-get source :post-init))
             (after    (plist-get source :after))
             (pkgname  (plist-get source :pkgname))
             (library  (or (plist-get source :library) pkgname package))
             (pdir     (el-get-package-directory package)))

372 373
	;; a builtin package initialisation is about calling recipe and user
	;; code only, no load-path nor byte-compiling support needed here.
374
	(unless (eq method 'builtin)
375
	  ;; append entries to load-path and Info-directory-list
376
	  (unless (member method '(apt-get fink pacman))
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
	    ;; append entries to load-path
	    (dolist (path el-path)
	      (el-get-add-path-to-list package 'load-path path))
	    ;;  and Info-directory-list
	    (el-get-install-or-init-info package 'init))

	  (when el-get-byte-compile-at-init
	    ;; If the package has been updated outside el-get, the .el files will be
	    ;; out of date, so just check if we need to recompile them.
	    ;;
	    ;; when using el-get-update to update packages, though, there's no
	    ;; need to byte compile at init.
	    (el-get-byte-compile package))

	  ;; load any autoloads file if needed
	  (unless (eq autoloads t)
	    (dolist (file (el-get-as-list autoloads))
	      (el-get-load-fast file))))
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419

        ;; first, the :prepare function, usually defined in the recipe
        (el-get-funcall prepare "prepare" package)

        ;; now call the :before user function
        (el-get-funcall before "before" package)

        ;; loads and feature are skipped when el-get-is-lazy
        (unless (or lazy el-get-is-lazy)
          ;; loads
          (dolist (file loads)
            (let ((pfile (concat pdir file)))
              (unless (file-exists-p pfile)
                (error "el-get could not find file '%s'" pfile))
              (el-get-verbose-message "el-get: load '%s'" pfile)
              (el-get-load-fast pfile)))

          ;; features, only ELPA will handle them on its own
          (unless (eq method 'elpa)
            ;; if a feature is provided, require it now
            (dolist (feat feats)
              (let ((feature (el-get-as-symbol feat)))
                (el-get-verbose-message "require '%s" feature)
                (require feature)))))

420 421 422
        (el-get-funcall postinit "post-init" package)

        ;; now handle the user configs and :after functions
423
        (if (or lazy el-get-is-lazy)
424
            (let ((lazy-form
425
		   `(progn (el-get-load-package-user-init-file ',package)
426
			   ,(when after (list 'funcall after)))))
427 428 429
              (eval-after-load library lazy-form))

          ;; el-get is not lazy here
430
	  (el-get-load-package-user-init-file package)
431 432 433 434 435 436 437
          (el-get-funcall after "after" package))

        ;; and call the global init hooks
        (run-hook-with-args 'el-get-post-init-hooks package)

        ;; return the package
        package)
438
    (debug error
439
     (el-get-installation-failed package err))))
440

441 442 443 444 445 446 447

(defun el-get-install (package)
  "Cause the named PACKAGE to be installed after all of its
dependencies (if any).

PACKAGE may be either a string or the corresponding symbol."
  (interactive (list (el-get-read-package-name "Install")))
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
  (let ((packages  (el-get-dependencies (el-get-as-symbol package))))
    (when (cdr packages)
      ;; tweak el-get-post-install-hooks to install remaining packages
      ;; once the first is installed
      (el-get-verbose-message "el-get-install %s: %S" package packages)
      (setq el-get-next-packages (cdr packages))
      (add-hook 'el-get-post-install-hooks 'el-get-install-next-packages))

    (let ((package (car packages)))
      (if (not (el-get-package-is-installed package))
	  (el-get-do-install package)
	;; if package is already installed, skip to the next
	(message "el-get: `%s' package is already installed" package)
	(el-get-init package)
	(el-get-install-next-packages package)))))
463

464
(defun el-get-install-next-packages (current-package)
465 466
  "Run as part of `el-get-post-init-hooks' when dealing with dependencies."
  (let ((package (pop el-get-next-packages)))
467
    (el-get-verbose-message "el-get-install-next-packages: %s" package)
468
    (if package
469 470 471
	;; el-get-do-install will either init the package, installing it
	;; first only when necessary to do so
	(el-get-do-install (el-get-as-string package))
472 473 474
      ;; no more packages to install in the dependency walk, clean up
      (remove-hook 'el-get-post-init-hooks 'el-get-install-next-packages))))

475 476
(defun el-get-post-install-build (package)
  "Function to call after building the package while installing it."
Dimitri Fontaine's avatar
Dimitri Fontaine committed
477 478 479
  (el-get-save-package-status package "installed")
  (el-get-invalidate-autoloads package)	; that will also update them
  (el-get-init package))
480

481 482
(defun el-get-post-install (package)
  "Post install PACKAGE. This will get run by a sentinel."
483 484 485 486 487 488 489 490
  (let* ((sync             el-get-default-process-sync)
	 (type             (el-get-package-type package))
	 (hooks            (el-get-method type :install-hook))
	 (commands         (el-get-build-commands package))
	 (checksum         (plist-get (el-get-package-def package) :checksum))
	 (compute-checksum (el-get-method type :compute-checksum)))

    ;; check the checksum of the package here, as early as possible
491 492 493 494
    (when (and checksum (not compute-checksum))
      (error
       "Checksum verification of package %s is not supported with method %s."
       package type))
495 496 497
    (when compute-checksum
      (let ((computed (funcall compute-checksum package)))
	(if checksum
498 499 500 501
	    (if (equal computed (el-get-as-string checksum))
		(el-get-verbose-message "el-get: package %s passed checksum with \"%s\"."
					package computed)
	      (error "Checksum verification failed. Required: \"%s\", actual: \"%s\"."
502
		     checksum computed))
503 504
	  (el-get-verbose-message "el-get: pakage %s checksum is %s."
				  package computed))))
Dave Abrahams's avatar
Dave Abrahams committed
505

506
    ;; post-install is the right place to run install-hook
507
    (run-hook-with-args hooks package)
Dave Abrahams's avatar
Dave Abrahams committed
508

509 510 511
    ;; el-get-post-build will care about autoloads and initializing the
    ;; package, and will change the status to "installed"
    (el-get-build package commands nil sync 'el-get-post-install-build))
Julien Danjou's avatar
Julien Danjou committed
512
  (run-hook-with-args 'el-get-post-install-hooks package))
513

514
(defun el-get-do-install (package)
515
  "Install any PACKAGE for which you have a recipe."
516
  (el-get-error-unless-package-p package)
517
  (if (string= (el-get-package-status package) "installed")
518
      (el-get-init package)
519 520 521 522
    (let* ((status   (el-get-read-package-status package))
	   (source   (el-get-package-def package))
	   (method   (el-get-package-method source))
	   (install  (el-get-method method :install))
523 524 525 526 527 528 529 530 531 532 533
	   (url      (plist-get source :url))
           (pdir     (el-get-package-directory package)))

      (cond ((string= "installed" status)
             (error "Package %s is already installed." package))
            ((string= "required" status)
             (message "Package %s failed to install, removing it first." package)
             (el-get-remove package))
            ((file-exists-p pdir)
             (message "Package %s has an install dir but is not known to be installed. Removing it so we can install a known version." package)
             (el-get-remove package)))
534

535 536 537
      ;; check we can install the package and save to "required" status
      (el-get-check-init)
      (el-get-save-package-status package "required")
538

539 540 541
      ;; and install the package now, *then* message about it
      (funcall install package url 'el-get-post-install)
      (message "el-get install %s" package))))
542

543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
(defun el-get-reload (package)
  "Reload PACKAGE."
  (interactive
   (list (el-get-read-package-with-status "Update" "installed")))
  (el-get-verbose-message "el-get-reload: %s" package)
  (let* ((all-features features)
         (package-features (el-get-package-features package))
         (package-files (el-get-package-files package))
         (other-features
	  (remove-if (lambda (x) (memq x package-features)) all-features)))
    (unwind-protect
        (progn
          ;; We cannot let-bind `features' here, becauses the changes
          ;; made by `el-get-init' must persist.
          (setq features other-features)
          ;; Reload all loaded files in package dir if they still
          ;; exist.
          (loop for file in package-files
                do (load file 'noerror))
          ;; Redo package initialization
          (el-get-init package)
          ;; Reload all features provided by the package. This ensures
          ;; that autoloaded packages (which normally don't load
          ;; anything until one of their entry points is called) are
          ;; forced to reload immediately if they were already loaded.
          (loop for f in package-features
                do (require f nil 'noerror)))
      ;; We have to add all the removed features back in no matter
      ;; what, or else we would be lying about what has been loaded.
      ;; This covers the corner case where an updated package no
      ;; longer provides a certain feature. Technically that feature
      ;; is still provided, so not adding it back would be wrong.
      (let ((missing-features
             (remove-if (lambda (x) (memq x features)) package-features)))
        (when missing-features
          (warn "Adding %S back onto features, because the reloaded package did not provide them."
                missing-features)
          (setq features (append missing-features features)))))))

582

583
(defun el-get-post-update (package)
584
  "Post update PACKAGE. This will get run by a sentinel."
585
  (let* ((source   (el-get-package-def package))
586
	 (commands (el-get-build-commands package)))
587
    (el-get-build package commands nil el-get-default-process-sync
588 589 590 591 592
		  (lambda (package)
		    (el-get-init package)
		    ;; fix trailing failed installs
		    (when (string= (el-get-read-package-status package) "required")
		      (el-get-save-package-status package "installed"))
593
                    (el-get-reload package)
594
                    (run-hook-with-args 'el-get-post-update-hooks package)))))
595

596 597
(defun el-get-update (package)
  "Update PACKAGE."
598 599
  (interactive
   (list (el-get-read-package-with-status "Update" "required" "installed")))
600 601
  (el-get-error-unless-package-p package)
  (let* ((source   (el-get-package-def package))
602
	 (method   (el-get-package-method source))
603 604 605
	 (update   (el-get-method method :update))
	 (url      (plist-get source :url))
	 (commands (plist-get source :build)))
606
    ;; update the package now
Gergely Risko's avatar
Gergely Risko committed
607
    (if (plist-get source :checksum)
608
	(error "el-get: remove checksum from package %s to update it." package)
Gergely Risko's avatar
Gergely Risko committed
609 610
      (funcall update package url 'el-get-post-update)
      (message "el-get update %s" package))))
611

612
;;;###autoload
613
(defun el-get-update-all ()
614
  "Performs update of all installed packages."
615
  (interactive)
616
  (mapc 'el-get-update (el-get-list-package-names-with-status "installed")))
617

618
;;;###autoload
619 620 621
(defun el-get-self-update ()
  "Update el-get itself.  The standard recipe takes care of reloading the code."
  (interactive)
622 623 624
  (let ((el-get-default-process-sync t)
	(el-get-dir
	 (expand-file-name ".." (file-name-directory el-get-script))))
625
    (el-get-update "el-get")))
626

627

628
(defun el-get-post-remove (package)
629
  "Run the post-remove hooks for PACKAGE."
630
  (let* ((hooks   (el-get-method (el-get-package-method package) :remove-hook)))
631 632
    (run-hook-with-args hooks package)
    (run-hook-with-args 'el-get-post-remove-hooks package)))
633

634
(defun el-get-remove (package)
635 636 637
  "Remove any PACKAGE that is know to be installed or required."
  (interactive
   (list (el-get-read-package-with-status "Remove" "required" "installed")))
638
  (el-get-error-unless-package-p package)
639

640 641 642 643 644 645 646 647 648
  (let* ((source   (el-get-package-def package))
	 (method   (el-get-package-method source))
	 (remove   (el-get-method method :remove))
	 (url      (plist-get source :url)))
    ;; remove the package now
    (el-get-remove-autoloads package)
    (funcall remove package url 'el-get-post-remove)
    (el-get-save-package-status package "removed")
    (message "el-get remove %s" package)))
649

650
;;;###autoload
651
(defun el-get-cd (package)
Dimitri Fontaine's avatar
Dimitri Fontaine committed
652
  "Open dired in the package directory."
653 654
  (interactive
   (list (el-get-read-package-with-status "cd to" "required" "installed")))
655 656
  (el-get-error-unless-package-p package)
  (dired (el-get-package-directory package)))
Dimitri Fontaine's avatar
Dimitri Fontaine committed
657

658 659 660 661 662
(defun el-get-write-recipe (source dir &optional filename)
  "Given an SOURCE entry, write it to FILENAME"
  (let* (;; Replace a package name with its definition
	 (source (if (symbolp source) (el-get-read-recipe source) source))
	 ;; Autogenerate filename if unspecified
663
	 (filename (or filename (format "%s.rcp" (el-get-source-name source)))))
664 665 666 667 668
    ;; Filepath is dir/file
    (let ((filepath (format "%s/%s" dir filename)))
      (with-temp-file filepath
	(insert (prin1-to-string source))))))

669
;;;###autoload
670
(defun el-get-make-recipes (&optional dir)
Dimitri Fontaine's avatar
Dimitri Fontaine committed
671
  "Loop over `el-get-sources' and write a recipe file for each
672 673 674 675 676 677 678 679 680
entry which is not a symbol and is not already a known recipe."
  (interactive "Dsave recipes in directory: ")
  (let* ((all (mapcar 'el-get-source-name (el-get-read-all-recipes)))
	 (new (loop for r in el-get-sources
		    when (and (not (symbolp r))
			      (not (member (el-get-source-name r) all)))
		    collect r)))
    (dolist (r new)
      (message "el-get: preparing recipe file for %s" (el-get-source-name r))
681
      (el-get-write-recipe r dir)))
682 683
  (dired dir))

684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
;;;###autoload
(defun el-get-checksum (package)
  "Compute the checksum of the given package, and put it in the kill-ring"
  (interactive
   (list (el-get-read-package-with-status "Checksum" "installed")))
  (let* ((type             (el-get-package-type package))
	 (checksum         (plist-get (el-get-package-def package) :checksum))
	 (compute-checksum (el-get-method type :compute-checksum)))
    (when (and checksum (not compute-checksum))
      (error "package method %s does not support checksums" type))
    (when compute-checksum
      (let ((checksum (funcall compute-checksum package)))
	(message "Checksum for package %s is: %s" package checksum)
	(kill-new checksum)))))

699

700 701 702
;;
;; User Interface, Non Interactive part
;;
703 704 705 706 707 708 709 710 711 712 713
(defun el-get-init-and-install (&optional packages)
  "Install \"required\" packages, init \"installed\" packages.

When PACKAGES is non-nil, only process entries from this list.
Those packages from the list we don't know the status of are
considered \"required\"."
  (let* ((required    (el-get-list-package-names-with-status "required"))
	 (installed   (el-get-list-package-names-with-status "installed"))
	 (to-init     (if packages
			  (loop for p in packages
				when (member (el-get-as-string p) installed)
714 715 716
				collect p)
			(mapcar 'el-get-as-symbol installed)))
	 (init-deps   (el-get-dependencies to-init))
717 718
	 (to-install  (if packages
			  (loop for p in packages
719 720 721 722
				unless (member p init-deps)
				collect p)
			(mapcar 'el-get-as-symbol required)))
	 (install-deps (el-get-dependencies to-install))
723
	 done)
724 725 726 727 728
    (el-get-verbose-message "el-get-init-and-install: install %S" install-deps)
    (el-get-verbose-message "el-get-init-and-install: init %S" init-deps)

    (loop for p in install-deps do (el-get-do-install p) collect p into done)
    (loop for p in init-deps    do (el-get-init p)       collect p into done)
729
    done))
730 731

(defun el-get (&optional sync &rest packages)
732
  "Ensure that packages have been downloaded once and init them as needed.
733 734

This will not update the sources by using `apt-get install' or
735
`git pull', but it will ensure that:
736

737 738 739 740 741
* the packages have been installed
* load-path is set so their elisp files can be found
* Info-directory-list is set so their info files can be found
* Autoloads have been prepared and evaluated for each package
* Any post-installation setup (e.g. `(require 'feature)') happens
742

743 744
When SYNC is nil (the default), all installations run
concurrently, in the background.
745

746 747
When SYNC is 'sync, each package will be installed synchronously,
and any error will stop it all.
748 749 750 751 752

When SYNC is 'wait, then `el-get' will enter a wait-loop and only
let you use Emacs once it has finished with its job. That's
useful an option to use in your `user-init-file'. Note that each
package in the list gets installed in parallel with this option.
753 754 755 756

Please note that the `el-get-init' part of `el-get' is always
done synchronously, so you will have to wait here. There's
`byte-compile' support though, and the packages you use are
757 758
welcome to use `autoload' too.

759 760 761
PACKAGES is expected to be a list of packages you want to install
or init.  When PACKAGES is omited (the default), the list of
already installed packages is considered."
762
  (unless (or (null sync)
763
	      (member sync '(sync wait)))
764
    (error "el-get sync parameter should be either nil, sync or wait"))
765

Dave Abrahams's avatar
Dave Abrahams committed
766
  ;; If there's no autoload file, everything needs to be regenerated.
767
  (unless (file-exists-p el-get-autoload-file) (el-get-invalidate-autoloads))
Dave Abrahams's avatar
Dave Abrahams committed
768

769 770 771
  ;; Autoloads path are relative to el-get-dir, so add it to load-path
  (add-to-list 'load-path (file-name-as-directory el-get-dir))

772 773 774 775 776 777 778
  (let* ((packages
	  ;; (el-get 'sync 'a 'b my-package-list)
	  (loop for p in packages when (listp p) append p else collect p))
	 (p-status    (el-get-read-all-packages-status))
         (total       (length packages))
         (installed   (el-get-count-packages-with-status packages "installed"))
         (progress (and (eq sync 'wait)
779
                        (make-progress-reporter
780
			 "Waiting for `el-get' to complete... "
781
			 0 (- total installed) 0)))
782
         (el-get-default-process-sync sync))
783

784 785
    ;; keep the result of `el-get-init-and-install' to return it even in the
    ;; 'wait case
786
    (prog1
787
	(el-get-init-and-install (mapcar 'el-get-as-symbol packages))
788

789
      ;; el-get-install is async, that's now ongoing.
790
      (when progress
791 792 793 794 795
        (while (> (- total installed) 0)
          (sleep-for 0.2)
          ;; don't forget to account for installation failure
          (setq installed (el-get-count-packages-with-status packages "installed" "required"))
          (progress-reporter-update progress (- total installed)))
796 797 798 799
        (progress-reporter-done progress))

      ;; now is a good time to care about autoloads
      (el-get-eval-autoloads))))
800

801
(provide 'el-get)
802 803

;;; el-get.el ends here
804 805 806 807 808 809 810 811 812 813


;; Local Variables:
;; eval: (require 'whitespace)
;; whitespace-line-column:80
;; whitespace-style:(face trailing lines-tail)
;; eval: (set-face-attribute 'whitespace-tab nil :background "red1" :foreground "yellow" :weight 'bold)
;; eval: (set-face-attribute 'whitespace-line nil :background "red1" :foreground "yellow" :weight 'bold)
;; eval: (whitespace-mode)
;; End: