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

145
;;; Code:
Dimitri Fontaine's avatar
Dimitri Fontaine committed
146

147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
(require 'el-get-core)

(require 'el-get-autoloads)
(require 'el-get-build)
(require 'el-get-byte-compile)
(require 'el-get-core)
(require 'el-get-custom)
(require 'el-get)
(require 'el-get-install)
(require 'el-get-list-depends)
(require 'el-get-list-packages)
(require 'el-get-methods)
(require 'el-get-notify)
(require 'el-get-recipes)
(require 'el-get-status)
162

163 164 165
(defgroup el-get nil "el-get customization group"
  :group 'convenience)

166
(defconst el-get-version "4.0.0" "el-get version number")
167

168 169
(defcustom el-get-post-init-hooks nil
  "Hooks to run after a package init.
Dave Abrahams's avatar
Dave Abrahams committed
170
Each hook is a unary function accepting a package"
171 172 173
  :group 'el-get
  :type 'hook)

174 175
(defcustom el-get-post-install-hooks nil
  "Hooks to run after installing a package.
Dave Abrahams's avatar
Dave Abrahams committed
176
Each hook is a unary function accepting a package"
177 178 179 180
  :group 'el-get
  :type 'hook)

(defcustom el-get-post-update-hooks nil
181
  "Hooks to run after updating a package.
Dave Abrahams's avatar
Dave Abrahams committed
182 183 184
Each hook is a unary function accepting a package"
  :group 'el-get
  :type 'hook)
185 186 187 188

(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"
189 190 191
  :group 'el-get
  :type 'hook)

192
(defcustom el-get-byte-compile t
193
  "Whether or not to byte-compile packages. Can be used to
194 195 196 197 198 199
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)."
200 201 202
  :group 'el-get
  :type 'boolean)

203 204 205 206 207
(defcustom el-get-verbose nil
  "Non-nil means print messages describing progress of el-get even for fast operations."
  :group 'el-get
  :type 'boolean)

208 209 210 211 212 213 214 215 216
(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)

217 218 219 220 221 222
(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)

223 224 225 226 227
(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."
228 229 230
  :group 'el-get
  :type 'boolean)

231 232
(defconst el-get-script (or load-file-name buffer-file-name))

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

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

Dave Abrahams's avatar
Dave Abrahams committed
242 243 244 245
(defvar el-get-autoload-file
  (concat (file-name-as-directory el-get-dir) ".loaddefs.el")
  "Where generated autoloads are saved")

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

249 250 251
;; debian uses ginstall-info and it's compatible to fink's install-info on
;; MacOSX, so:
(defvar el-get-install-info (or (executable-find "ginstall-info")
252
				(executable-find "install-info")))
253

Dimitri Fontaine's avatar
Dimitri Fontaine committed
254

255 256 257 258
(defun el-get-install (package)
  "Cause the named PACKAGE to be installed after all of its
dependencies (if any).

259 260
PACKAGE may be either a string or the corresponding symbol."
  (interactive (list (el-get-read-package-name "Install")))
261 262
  (if (el-get-package-is-installed package)
      (message "el-get: `%s' package is already installed" package)
263

264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
    (condition-case err
	(let* ((psym (el-get-as-symbol package))
	       (pname (symbol-name psym)))

	  ;; don't do anything if it's already installed or in progress
	  (unless (memq (el-get-package-state psym) '(init installing))

	    ;; Remember that we're working on it
	    (el-get-set-package-state psym 'installing)

	    (let ((non-installed-dependencies
		   (remove-if 'el-get-package-initialized-p
			      (el-get-dependencies psym))))

	      ;;
	      ;; demand all non-installed dependencies with appropriate
	      ;; handlers in place to trigger installation of this package
	      ;;
	      (dolist (dep non-installed-dependencies)
		;; set up a handler that will install `package' when all
		;; its dependencies are installed
		(el-get-add-generic-event-task
		 (el-get-event-id dep 'init)
		 `(lambda (data)
		    (el-get-mark-initialized ',dep)
		    (el-get-dependency-installed ',psym ',dep)))

		;; set up a handler that will cancel installation of
		;; `package' if installing the dependency fails
		(el-get-add-generic-event-task
		 (el-get-event-id dep 'error)
		 `(lambda (data)
		    (el-get-set-package-state ',dep (list 'error data))
		    (el-get-dependency-error ',psym ',dep data)))

		(el-get-install dep))

	      (unless non-installed-dependencies
302
		(el-get-do-install psym)))))
303 304
      ((debug error)
       (el-get-installation-failed package err)))))
305

306 307 308 309 310
(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)))

311

312 313 314
;;
;; User Interface, Interactive part
;;
315 316 317 318 319
(defun el-get-version ()
  "Message the current el-get version"
  (interactive)
  (message "el-get version %s" el-get-version))

320
(defun el-get-read-all-recipe-names ()
321 322 323
  "Return the list of all known recipe names.

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

327
(defun el-get-error-unless-package-p (package)
328 329
  "Raise an error if PACKAGE does not name a package that has a valid recipe."
  ;; check for recipe
330 331 332 333 334
  (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))))
335

336
(defun el-get-package-is-installed (package)
337
  "Raise an error if PACKAGE is already installed"
338
  (string= "installed" (el-get-package-status (el-get-as-string package))))
339

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

343 344
Completions are offered from all known package names, after
removing any packages in FILTERED."
345
  (let ((packages   (el-get-read-all-recipe-names)))
346
    (completing-read (format "%s package: " action)
347
		     (set-difference packages filtered :test 'string=) nil t)))
348

349
(defun el-get-read-recipe-name (action)
350 351 352 353
  "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)
354
                   (el-get-read-all-recipe-names) nil))
355 356 357 358 359 360 361

(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")))
362
  (let* ((package-el (concat (el-get-as-string package) ".rcp"))
363 364 365
	 (recipe-file (or
		       ;; If dir was specified, open or create the
		       ;; recipe file in that directory.
366
		       (when dir (expand-file-name package-el dir))
367 368 369 370
		       ;; 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'
371
		       (expand-file-name package-el
372
                                         (car el-get-recipe-path)))))
373 374
    (find-file recipe-file)))

375
(defun el-get-funcall (func fname package)
376
  "`funcal' FUNC for PACKAGE and report about FNAME when `el-get-verbose'"
377
  (when (and func (functionp func))
378 379 380 381 382
      (el-get-verbose-message "el-get: Calling :%s function for package %s"
			      fname package)
      ;; don't forget to make some variables available
      (let (pdir (el-get-package-directory package))
	(funcall func))))
383

384
(defun el-get-init (package)
385 386 387 388 389
  "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
390
called by `el-get' (usually at startup) for each installed package."
391
  (interactive (list (el-get-read-package-name "Init")))
392
  (el-get-verbose-message "el-get-init: %s" package)
393 394
  (condition-case err
      (let* ((source   (el-get-package-def package))
395
             (method   (el-get-package-method source))
396 397 398 399 400 401 402 403 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 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
             (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)))

        ;; append entries to load-path and Info-directory-list
        (unless (member method '(elpa apt-get fink pacman))
          ;; 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)))

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

454
        ;; now handle the user configs and :post-init and :after functions
455
        (if (or lazy el-get-is-lazy)
456 457
            (let ((lazy-form
		   `(progn ,(when postinit (list 'funcall postinit))
458
			   (el-get-load-package-user-init-file ,package)
459
			   ,(when after (list 'funcall after)))))
460 461 462 463
              (eval-after-load library lazy-form))

          ;; el-get is not lazy here
          (el-get-funcall postinit "post-init" package)
464
	  (el-get-load-package-user-init-file package)
465 466 467 468 469 470 471
          (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)
472
    (debug error
473
     (el-get-installation-failed package err))))
474

475 476
(defun el-get-post-install-build (package)
  "Function to call after building the package while installing it."
477 478 479 480
  (el-get-invalidate-autoloads package)
  (el-get-init package)
  (el-get-save-package-status package "installed"))

481 482
(defun el-get-post-install (package)
  "Post install PACKAGE. This will get run by a sentinel."
483 484
  (let* ((sync     el-get-default-process-sync)
	 (hooks    (el-get-method (el-get-package-type package) :install-hook))
485
	 (commands (el-get-build-commands package)))
Dave Abrahams's avatar
Dave Abrahams committed
486

487
    ;; post-install is the right place to run install-hook
488
    (run-hook-with-args hooks package)
Dave Abrahams's avatar
Dave Abrahams committed
489

490 491 492
    ;; 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
493
  (run-hook-with-args 'el-get-post-install-hooks package))
494

495
(defun el-get-do-install (package)
496
  "Install any PACKAGE for which you have a recipe."
497
  (el-get-error-unless-package-p package)
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519
  (if (string= (el-get-package-status package) "installed")
      (el-get-init p)
    (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))
	   (url      (plist-get source :url)))

      (when (string= "installed" status)
	(error "Package %s is already installed." package))

      (when (string= "required" status)
	(message "Package %s failed to install, removing it first." package)
	(el-get-remove package))

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

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

521
(defun el-get-post-update (package)
522
  "Post update PACKAGE. This will get run by a sentinel."
523
  (let* ((source   (el-get-package-def package))
524
	 (commands (el-get-build-commands package)))
525
    (el-get-build package commands nil el-get-default-process-sync
526 527 528 529 530
		  (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"))
531
                    (run-hook-with-args 'el-get-post-update-hooks package)))))
532

533 534
(defun el-get-update (package)
  "Update PACKAGE."
535 536
  (interactive
   (list (el-get-read-package-with-status "Update" "required" "installed")))
537 538
  (el-get-error-unless-package-p package)
  (let* ((source   (el-get-package-def package))
539
	 (method   (el-get-package-method source))
540 541 542
	 (update   (el-get-method method :update))
	 (url      (plist-get source :url))
	 (commands (plist-get source :build)))
543
    ;; update the package now
544 545
    (funcall update package url 'el-get-post-update)
    (message "el-get update %s" package)))
546

547
(defun el-get-update-all ()
548
  "Performs update of all installed packages."
549
  (interactive)
550
  (mapc 'el-get-update (el-get-list-package-names-with-status "installed")))
551

552 553 554
(defun el-get-self-update ()
  "Update el-get itself.  The standard recipe takes care of reloading the code."
  (interactive)
555 556 557
  (let ((el-get-default-process-sync t)
	(el-get-dir
	 (expand-file-name ".." (file-name-directory el-get-script))))
558
    (el-get-update "el-get")))
559

560
(defun el-get-post-remove (package)
561
  "Run the post-remove hooks for PACKAGE."
562
  (let* ((hooks   (el-get-method (el-get-package-method package) :remove-hook)))
563 564
    (run-hook-with-args hooks package)
    (run-hook-with-args 'el-get-post-remove-hooks package)))
565

566
(defun el-get-remove (package)
567 568 569
  "Remove any PACKAGE that is know to be installed or required."
  (interactive
   (list (el-get-read-package-with-status "Remove" "required" "installed")))
570
  (el-get-error-unless-package-p package)
571

572 573 574 575 576 577 578 579 580
  (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)))
581

582
(defun el-get-cd (package)
Dimitri Fontaine's avatar
Dimitri Fontaine committed
583
  "Open dired in the package directory."
584 585
  (interactive
   (list (el-get-read-package-with-status "cd to" "required" "installed")))
586 587
  (el-get-error-unless-package-p package)
  (dired (el-get-package-directory package)))
Dimitri Fontaine's avatar
Dimitri Fontaine committed
588

589 590 591 592 593
(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
594
	 (filename (or filename (format "%s.rcp" (el-get-source-name source)))))
595 596 597 598 599
    ;; Filepath is dir/file
    (let ((filepath (format "%s/%s" dir filename)))
      (with-temp-file filepath
	(insert (prin1-to-string source))))))

600
(defun el-get-make-recipes (&optional dir)
Dimitri Fontaine's avatar
Dimitri Fontaine committed
601
  "Loop over `el-get-sources' and write a recipe file for each
602 603 604 605 606 607 608 609 610
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))
611
      (el-get-write-recipe r dir)))
612 613
  (dired dir))

614

615 616 617
;;
;; User Interface, Non Interactive part
;;
618 619 620 621 622 623 624 625 626 627 628 629 630
(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)
				collect (el-get-as-string p))
			installed))
631 632 633 634
	 (init-deps   (loop for p in to-init
			    append (mapcar 'el-get-as-string
					   (el-get-dependencies
					    (el-get-as-symbol p)))))
635 636
	 (to-install  (if packages
			  (loop for p in packages
637
				unless (member (el-get-as-string p) to-init)
638
				collect (el-get-as-string p))
639 640
			required))
	 done)
641
    (el-get-verbose-message "el-get-init-and-install: install %S" to-install)
642 643 644 645 646 647 648 649
    (el-get-verbose-message "el-get-init-and-install: init %S" to-init)
    (el-get-verbose-message "el-get-init-and-install: deps %S" init-deps)

    (loop for p in to-install do (el-get-install p) collect p into done)
    (loop for p in init-deps  do (el-get-init p)    collect p into done)
    (loop for p in to-init
	  unless (member p done) do (el-get-init p) collect p into done)
    done))
650 651

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

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

657 658 659 660 661
* 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
662

663 664
When SYNC is nil (the default), all installations run
concurrently, in the background.
665

666 667
When SYNC is 'sync, each package will be installed synchronously,
and any error will stop it all.
668 669 670 671 672

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.
673 674 675 676

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

679 680 681
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."
682
  (unless (or (null sync)
683
	      (member sync '(sync wait)))
684
    (error "el-get sync parameter should be either nil, sync or wait"))
685

Dave Abrahams's avatar
Dave Abrahams committed
686 687 688
  ;; If there's no autoload file, everything needs to be regenerated.
  (if (not (file-exists-p el-get-autoload-file)) (el-get-invalidate-autoloads))

689 690 691
  ;; 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))

692 693
  (let ((previously-installing (el-get-currently-installing-packages))
        (progress (and (eq sync 'wait)
694
                        (make-progress-reporter
695
			 "Waiting for `el-get' to complete... "
696
			 0 100 0)))
697
         (el-get-default-process-sync sync))
698

699 700
    ;; keep the result of `el-get-init-and-install' to return it even in the
    ;; 'wait case
701
    (prog1
702 703 704 705
	(let ((packages
	       ;; (el-get 'sync 'a 'b my-package-list)
	       (loop for p in packages when (listp p) append p else collect p)))
	  (el-get-init-and-install packages))
706

707
      ;; el-get-do-install is async, that's now ongoing.
708
      (when progress
709 710
        (let* ((newly-installing
               (set-difference (el-get-currently-installing-packages)
711 712 713 714 715 716
                               previously-installing))
              (still-installing newly-installing))

          (while (> (length still-installing) 0)
            (sleep-for 0.2)
            (setq still-installing (delete-if-not 'el-get-currently-installing-p still-installing))
717 718
            (progress-reporter-update
             progress
719
             (/ (* 100.0 (- newly-installing still-installing)) newly-installing)))
720
        (progress-reporter-done progress)))
Dave Abrahams's avatar
Dave Abrahams committed
721

722 723 724
      ;; unless we have autoloads to update, just load them now
      (unless el-get-outdated-autoloads
	(el-get-eval-autoloads)))))
725

726
(provide 'el-get)
727 728

;;; el-get.el ends here
729 730 731 732 733 734 735 736 737 738

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