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 33.4 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
;; 1.2~dev, so 1.0 was a "stable" release in fact.  Ah, history.
30 31 32 33 34
;;
;; In addition to the version, you can also get the exact git revision
;; by running M-x `el-get-self-checksum'. You should provide this
;; checksum when seeking support or reporting a bug, so that the
;; developers will know exactly which version you are using.
35 36

;;; Change Log:
37
;;
38 39
;;  4.0 - WIP - To infinity, and beyond!
;;
40 41 42 43
;;   - code refactoring
;;   - fix dependency tracking at install and init times
;;   - fix autoloading so that it happens before notifying install is done
;;   - add some tests
44
;;   - deprecate package.el from the old days, only include the Emacs24 one
45
;;   - implement :builtin property (useful for dealing with package.el)
46
;;   - fix recipes :build commands, must be either lists of strings or expr
47
;;   - add support for el-get-reload and do that at update time
48
;;   - implement :checksum property for http kinds of files
49
;;   - Add new command el-get-reinstall
50
;;   - implement :checkout property for git packages
51
;;   - implement :shallow property for git packages
52
;;
Dimitri Fontaine's avatar
Dimitri Fontaine committed
53
;;  3.1 - 2011-09-15 - Get a fix
54
;;
Dimitri Fontaine's avatar
Dimitri Fontaine committed
55
;;   - support for package dependencies
56
;;   - rely on package status for `el-get' to install and init them
Dimitri Fontaine's avatar
Dimitri Fontaine committed
57
;;   - M-x el-get-list-packages
58
;;   - support for :branch in git
Dimitri Fontaine's avatar
Dimitri Fontaine committed
59 60
;;   - new recipes, galore
;;   - bug fixes, byte compiling, windows compatibility, etc
61
;;   - recipe files are now *.rcp rather than *.el (el still supported)
62
;;   - el-get-user-package-directory allows to setup init-<package>.el files
Dimitri Fontaine's avatar
Dimitri Fontaine committed
63
;;   - remove M-x el-get-sync, now obsolete
64
;;
Julien Danjou's avatar
Julien Danjou committed
65 66 67 68 69
;;  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
70
;;
71
;;   - Add support for autoloads, per Dave Abrahams
72
;;   - fix 'wait support for http (using sync retrieval)
73
;;   - code cleanup per Dave Abrahams, lots of it
74
;;   - add function M-x el-get-update-all
75
;;   - Implement M-x el-get-make-recipes
76 77
;;   - byte-compile at build time rather than at init time
;;   - and use a "clean room" external emacs -Q for byte compiling
78
;;   - allow to skip autoloads either globally or per-package
79
;;   - better checks and errors for commands used when installing packages
80
;;   - register `el-get-sources' against the custom interface
81
;;   - el-get will now accept a list of sources to install or init
82
;;   - open el-get-{install,init,remove,update} from `el-get-sources' only
83
;;   - add :prepare and :post-init and :lazy, and `el-get-is-lazy'
Dimitri Fontaine's avatar
Dimitri Fontaine committed
84
;;   - add support for :repo for ELPA packages
85
;;
Dimitri Fontaine's avatar
Dimitri Fontaine committed
86
;;  1.1 - 2010-12-20 - Nobody's testing until the release
87 88 89 90
;;
;;   - 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)
91
;;   - Allow to bypass byte compiling entirely with a single global var
92
;;   - Have http local file default to something sane, not package.el
93
;;   - Implement support for svn and darcs too
94
;;   - Still more recipes
Dimitri Fontaine's avatar
Dimitri Fontaine committed
95
;;   - Add support for the `pacman' package manager (ARCH Linux)
96
;;   - Add support for mercurial
97 98
;;   - (el-get 'sync) now really means synchronous, and serialized too
;;   - el-get-start-process-list implements :sync, defaults to nil (async)
99
;;   - Implement a :localname package property to help with some kind of URLs
100
;;   - Add el-get-post-init-hooks
101
;;   - Allow build commands to be evaluated, hence using some emacs variables
102
;;   - Finaly walk the extra mile and remove "required" packages at install
103
;;   - Implement support for the "Do you want to continue" apt-get prompt
104
;;   - implement :before user defined function to run before init
105
;;
106
;;  1.0 - 2010-10-07 - Can I haz your recipes?
107 108 109
;;
;;   - 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
110
;;     to share the recipes is easy.
111
;;   - Add support for emacswiki directly, save from having to enter the URL
112 113 114
;;   - 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.
115
;;   - Fix ELPA remove method, adding a "removed" state too.
116
;;   - Implement CVS login support.
117 118
;;   - Add lots of recipes
;;   - Add support for `system-type' specific build commands
119
;;   - Byte compile files from the load-path entries or :compile files
120 121
;;   - Implement support for git submodules with the command
;;     `git submodule update --init --recursive`
122
;;   - Add catch-all post-install and post-update hooks
123
;;   - Add desktop notification on install/update.
124
;;
125 126 127 128 129 130
;;  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'.
;;
131 132 133
;;  0.8 - 2010-08-23 - listen to the users
;;
;;   - implement :after user defined function to run at the end of init
134
;;   - add CVS support (no login support)
135 136
;;   - improve el-get-build to use async building
;;   - fix el-get-update doing so
137
;;
138 139 140 141
;;  0.7 - 2010-08-23 - archive
;;
;;   - http support is extended to `tar' archives, via the http-tar type
;;
142 143 144 145 146 147
;;  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
;;
148 149 150 151 152 153
;;  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
;;
154 155 156 157 158 159 160 161
;;  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
162

163
;;; Code:
Dimitri Fontaine's avatar
Dimitri Fontaine committed
164

165
;; first some essential variables, used in other parts of the code.
166 167 168
(defgroup el-get nil "el-get customization group"
  :group 'convenience)

Ryan C. Thompson's avatar
Ryan C. Thompson committed
169
(defconst el-get-version "4.0.7" "el-get version number")
170

171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
(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")
188

189 190 191
;;
;; Now load the rest of the el-get code
;;
192 193 194 195 196 197 198 199 200 201
(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
202
(require 'el-get-autoloads)		; manages updating el-get's loaddefs.el
203

204 205 206 207 208
;;
;; 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.
;;
209 210
(defcustom el-get-post-init-hooks nil
  "Hooks to run after a package init.
Dave Abrahams's avatar
Dave Abrahams committed
211
Each hook is a unary function accepting a package"
212 213 214
  :group 'el-get
  :type 'hook)

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

(defcustom el-get-post-update-hooks nil
222
  "Hooks to run after updating a package.
Dave Abrahams's avatar
Dave Abrahams committed
223 224 225
Each hook is a unary function accepting a package"
  :group 'el-get
  :type 'hook)
226 227 228 229

(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"
230 231 232
  :group 'el-get
  :type 'hook)

233
(defcustom el-get-byte-compile t
234
  "Whether or not to byte-compile packages. Can be used to
235 236 237 238 239 240
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)."
241 242 243
  :group 'el-get
  :type 'boolean)

244 245 246 247 248
(defcustom el-get-verbose nil
  "Non-nil means print messages describing progress of el-get even for fast operations."
  :group 'el-get
  :type 'boolean)

249 250 251 252 253 254 255 256 257
(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)

258 259 260 261 262 263
(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)

264 265 266 267 268
(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."
269 270 271
  :group 'el-get
  :type 'boolean)

272 273
(defvar el-get-next-packages nil
  "List of packages to install next, used when dealing with dependencies.")
274

275 276 277 278 279
(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)))

280

281 282 283
;;
;; User Interface, Interactive part
;;
284
;;;###autoload
285 286 287 288 289
(defun el-get-version ()
  "Message the current el-get version"
  (interactive)
  (message "el-get version %s" el-get-version))

290
(defun el-get-read-all-recipe-names ()
291 292 293
  "Return the list of all known recipe names.

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

297
(defun el-get-error-unless-package-p (package)
298 299
  "Raise an error if PACKAGE does not name a package that has a valid recipe."
  ;; check for recipe
300 301 302 303 304
  (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))))
305

306
(defun el-get-package-is-installed (package)
307
  "Raise an error if PACKAGE is already installed"
308
  (string= "installed" (el-get-package-status (el-get-as-string package))))
309

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

313 314
Completions are offered from all known package names, after
removing any packages in FILTERED."
315
  (let ((packages   (el-get-read-all-recipe-names)))
316
    (completing-read (format "%s package: " action)
317
		     (set-difference packages filtered :test 'string=) nil t)))
318

319
(defun el-get-read-recipe-name (action)
320 321 322 323
  "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)
324
                   (el-get-read-all-recipe-names) nil))
325 326 327 328 329 330 331

(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")))
332
  (let* ((package-el (concat (el-get-as-string package) ".rcp"))
333 334 335
	 (recipe-file (or
		       ;; If dir was specified, open or create the
		       ;; recipe file in that directory.
336
		       (when dir (expand-file-name package-el dir))
337 338 339 340
		       ;; 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'
341
		       (expand-file-name package-el
342
                                         (car el-get-recipe-path)))))
343 344
    (find-file recipe-file)))

345
(defun el-get-funcall (func fname package)
346
  "`funcal' FUNC for PACKAGE and report about FNAME when `el-get-verbose'"
347
  (when (and func (functionp func))
348 349 350
      (el-get-verbose-message "el-get: Calling :%s function for package %s"
			      fname package)
      ;; don't forget to make some variables available
351
      (let ((pdir (el-get-package-directory package)))
352
	(funcall func))))
353

354

355
(defun el-get-init (package)
356 357 358 359 360
  "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
361
called by `el-get' (usually at startup) for each installed package."
362
  (interactive (list (el-get-read-package-name "Init")))
363
  (el-get-verbose-message "el-get-init: %s" package)
364 365
  (condition-case err
      (let* ((source   (el-get-package-def package))
366
             (method   (el-get-package-method source))
367 368 369 370 371 372 373 374 375 376 377
             (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))
378 379
             (pdir     (el-get-package-directory package))
             (default-directory pdir))
380

381 382
	;; a builtin package initialisation is about calling recipe and user
	;; code only, no load-path nor byte-compiling support needed here.
383
	(unless (eq method 'builtin)
384
	  ;; append entries to load-path and Info-directory-list
385
	  (unless (member method '(apt-get fink pacman))
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
	    ;; 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))))
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428

        ;; 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)))))

429 430 431
        (el-get-funcall postinit "post-init" package)

        ;; now handle the user configs and :after functions
432
        (if (or lazy el-get-is-lazy)
433
            (let ((lazy-form
434
		   `(progn (el-get-load-package-user-init-file ',package)
435
			   ,(when after (list 'funcall after)))))
436 437 438
              (eval-after-load library lazy-form))

          ;; el-get is not lazy here
439
	  (el-get-load-package-user-init-file package)
440 441 442 443 444 445 446
          (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)
447
    (debug error
448
     (el-get-installation-failed package err))))
449

450 451 452 453 454 455 456

(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")))
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
  (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)))))
472

473 474 475 476 477 478
(defun el-get-reinstall (package)
  "Remove PACKAGE and then install it again."
  (interactive (list (el-get-read-package-name "Reinstall")))
  (el-get-remove package)
  (el-get-install package))

479
(defun el-get-install-next-packages (current-package)
480 481
  "Run as part of `el-get-post-init-hooks' when dealing with dependencies."
  (let ((package (pop el-get-next-packages)))
482
    (el-get-verbose-message "el-get-install-next-packages: %s" package)
483
    (if package
484 485 486
	;; 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))
487 488 489
      ;; no more packages to install in the dependency walk, clean up
      (remove-hook 'el-get-post-init-hooks 'el-get-install-next-packages))))

490 491
(defun el-get-post-install-build (package)
  "Function to call after building the package while installing it."
Dimitri Fontaine's avatar
Dimitri Fontaine committed
492 493 494
  (el-get-save-package-status package "installed")
  (el-get-invalidate-autoloads package)	; that will also update them
  (el-get-init package))
495

496 497
(defun el-get-post-install (package)
  "Post install PACKAGE. This will get run by a sentinel."
498 499 500 501 502 503 504 505
  (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
506 507 508 509
    (when (and checksum (not compute-checksum))
      (error
       "Checksum verification of package %s is not supported with method %s."
       package type))
510 511 512
    (when compute-checksum
      (let ((computed (funcall compute-checksum package)))
	(if checksum
513 514 515 516
	    (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\"."
517
		     checksum computed))
518 519
	  (el-get-verbose-message "el-get: pakage %s checksum is %s."
				  package computed))))
Dave Abrahams's avatar
Dave Abrahams committed
520

521
    ;; post-install is the right place to run install-hook
522
    (run-hook-with-args hooks package)
Dave Abrahams's avatar
Dave Abrahams committed
523

524 525 526
    ;; 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
527
  (run-hook-with-args 'el-get-post-install-hooks package))
528

529
(defun el-get-do-install (package)
530
  "Install any PACKAGE for which you have a recipe."
531
  (el-get-error-unless-package-p package)
532
  (if (string= (el-get-package-status package) "installed")
533
      (el-get-init package)
534 535 536 537
    (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))
538 539 540 541 542 543 544 545 546 547 548
	   (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)))
549

550 551 552
      ;; check we can install the package and save to "required" status
      (el-get-check-init)
      (el-get-save-package-status package "required")
553

554 555 556
      ;; and install the package now, *then* message about it
      (funcall install package url 'el-get-post-install)
      (message "el-get install %s" package))))
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 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596
(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)))))))

597

598
(defun el-get-post-update (package)
599
  "Post update PACKAGE. This will get run by a sentinel."
600
  (let* ((source   (el-get-package-def package))
601
	 (commands (el-get-build-commands package)))
602
    (el-get-build package commands nil el-get-default-process-sync
603 604 605 606 607
		  (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"))
608
                    (el-get-reload package)
609
                    (run-hook-with-args 'el-get-post-update-hooks package)))))
610

611 612
(defun el-get-update (package)
  "Update PACKAGE."
613 614
  (interactive
   (list (el-get-read-package-with-status "Update" "required" "installed")))
615 616
  (el-get-error-unless-package-p package)
  (let* ((source   (el-get-package-def package))
617
	 (method   (el-get-package-method source))
618 619 620
	 (update   (el-get-method method :update))
	 (url      (plist-get source :url))
	 (commands (plist-get source :build)))
621
    ;; update the package now
Gergely Risko's avatar
Gergely Risko committed
622
    (if (plist-get source :checksum)
623
	(error "el-get: remove checksum from package %s to update it." package)
Gergely Risko's avatar
Gergely Risko committed
624 625
      (funcall update package url 'el-get-post-update)
      (message "el-get update %s" package))))
626

627
;;;###autoload
628
(defun el-get-update-all ()
629
  "Performs update of all installed packages."
630
  (interactive)
631
  (mapc 'el-get-update (el-get-list-package-names-with-status "installed")))
632

633
;;;###autoload
634 635 636
(defun el-get-self-update ()
  "Update el-get itself.  The standard recipe takes care of reloading the code."
  (interactive)
637 638 639
  (let ((el-get-default-process-sync t)
	(el-get-dir
	 (expand-file-name ".." (file-name-directory el-get-script))))
640
    (el-get-update "el-get")))
641

642

643
(defun el-get-post-remove (package)
644
  "Run the post-remove hooks for PACKAGE."
645
  (let* ((hooks   (el-get-method (el-get-package-method package) :remove-hook)))
646 647
    (run-hook-with-args hooks package)
    (run-hook-with-args 'el-get-post-remove-hooks package)))
648

649
(defun el-get-remove (package)
650 651 652
  "Remove any PACKAGE that is know to be installed or required."
  (interactive
   (list (el-get-read-package-with-status "Remove" "required" "installed")))
653
  (el-get-error-unless-package-p package)
654

655 656 657 658 659 660 661 662 663
  (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)))
664

665
;;;###autoload
666
(defun el-get-cd (package)
Dimitri Fontaine's avatar
Dimitri Fontaine committed
667
  "Open dired in the package directory."
668 669
  (interactive
   (list (el-get-read-package-with-status "cd to" "required" "installed")))
670 671
  (el-get-error-unless-package-p package)
  (dired (el-get-package-directory package)))
Dimitri Fontaine's avatar
Dimitri Fontaine committed
672

673 674 675 676 677
(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
678
	 (filename (or filename (format "%s.rcp" (el-get-source-name source)))))
679 680 681 682 683
    ;; Filepath is dir/file
    (let ((filepath (format "%s/%s" dir filename)))
      (with-temp-file filepath
	(insert (prin1-to-string source))))))

684
;;;###autoload
685
(defun el-get-make-recipes (&optional dir)
Dimitri Fontaine's avatar
Dimitri Fontaine committed
686
  "Loop over `el-get-sources' and write a recipe file for each
687 688 689 690 691 692 693 694 695
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))
696
      (el-get-write-recipe r dir)))
697 698
  (dired dir))

699 700 701 702 703 704 705 706 707 708 709 710
;;;###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)))
711 712 713
        (message "Checksum for package %s is: %s. It has been copied to the kill-ring."
                 package checksum)
        (kill-new checksum)))))
714

715 716 717 718 719 720 721
(defun el-get-self-checksum ()
  "Compute the checksum of the running version of el-get itself.

Also put the checksum in the kill-ring."
  (interactive)
  (el-get-checksum 'el-get))

722

723 724 725
;;
;; User Interface, Non Interactive part
;;
726 727 728 729 730 731 732 733 734 735 736
(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)
737 738 739
				collect p)
			(mapcar 'el-get-as-symbol installed)))
	 (init-deps   (el-get-dependencies to-init))
740 741
	 (to-install  (if packages
			  (loop for p in packages
742 743 744 745
				unless (member p init-deps)
				collect p)
			(mapcar 'el-get-as-symbol required)))
	 (install-deps (el-get-dependencies to-install))
746
	 done)
747 748 749 750 751
    (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)
752
    done))
753 754

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

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

760 761 762 763 764
* 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
765

766 767
When SYNC is nil (the default), all installations run
concurrently, in the background.
768

769 770
When SYNC is 'sync, each package will be installed synchronously,
and any error will stop it all.
771 772 773 774 775

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.
776 777 778 779

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
780 781
welcome to use `autoload' too.

782 783 784
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."
785
  (unless (or (null sync)
786
	      (member sync '(sync wait)))
787
    (error "el-get sync parameter should be either nil, sync or wait"))
788

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

792 793 794
  ;; 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))

795 796 797 798 799 800 801
  (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)
802
                        (make-progress-reporter
803
			 "Waiting for `el-get' to complete... "
804
			 0 (- total installed) 0)))
805
         (el-get-default-process-sync sync))
806

807 808
    ;; keep the result of `el-get-init-and-install' to return it even in the
    ;; 'wait case
809
    (prog1
810
	(el-get-init-and-install (mapcar 'el-get-as-symbol packages))
811

812
      ;; el-get-install is async, that's now ongoing.
813
      (when progress
814 815 816 817 818
        (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)))
819 820 821 822
        (progress-reporter-done progress))

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

824
(provide 'el-get)
825 826

;;; el-get.el ends here
827 828 829 830 831 832 833 834 835 836


;; 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: