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
;; first some essential variables, used in other parts of the code.
(defgroup el-get nil "el-get customization group"
  :group 'convenience)

(defconst el-get-version "4.0.0" "el-get version number")
152

153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
(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")

;;
;; Now load the rest of the el-get code
;;
174 175 176 177 178 179 180 181 182 183 184 185
(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

(require 'el-get-autoloads)		; to be removed^W cleaned up next
186

187 188 189 190 191
;;
;; 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.
;;
192 193
(defcustom el-get-post-init-hooks nil
  "Hooks to run after a package init.
Dave Abrahams's avatar
Dave Abrahams committed
194
Each hook is a unary function accepting a package"
195 196 197
  :group 'el-get
  :type 'hook)

198 199
(defcustom el-get-post-install-hooks nil
  "Hooks to run after installing a package.
Dave Abrahams's avatar
Dave Abrahams committed
200
Each hook is a unary function accepting a package"
201 202 203 204
  :group 'el-get
  :type 'hook)

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

(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"
213 214 215
  :group 'el-get
  :type 'hook)

216
(defcustom el-get-byte-compile t
217
  "Whether or not to byte-compile packages. Can be used to
218 219 220 221 222 223
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)."
224 225 226
  :group 'el-get
  :type 'boolean)

227 228 229 230 231
(defcustom el-get-verbose nil
  "Non-nil means print messages describing progress of el-get even for fast operations."
  :group 'el-get
  :type 'boolean)

232 233 234 235 236 237 238 239 240
(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)

241 242 243 244 245 246
(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)

247 248 249 250 251
(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."
252 253 254
  :group 'el-get
  :type 'boolean)

255 256
(defvar el-get-next-packages nil
  "List of packages to install next, used when dealing with dependencies.")
257

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

263

264 265 266
;;
;; User Interface, Interactive part
;;
267 268 269 270 271
(defun el-get-version ()
  "Message the current el-get version"
  (interactive)
  (message "el-get version %s" el-get-version))

272
(defun el-get-read-all-recipe-names ()
273 274 275
  "Return the list of all known recipe names.

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

279
(defun el-get-error-unless-package-p (package)
280 281
  "Raise an error if PACKAGE does not name a package that has a valid recipe."
  ;; check for recipe
282 283 284 285 286
  (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))))
287

288
(defun el-get-package-is-installed (package)
289
  "Raise an error if PACKAGE is already installed"
290
  (string= "installed" (el-get-package-status (el-get-as-string package))))
291

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

295 296
Completions are offered from all known package names, after
removing any packages in FILTERED."
297
  (let ((packages   (el-get-read-all-recipe-names)))
298
    (completing-read (format "%s package: " action)
299
		     (set-difference packages filtered :test 'string=) nil t)))
300

301
(defun el-get-read-recipe-name (action)
302 303 304 305
  "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)
306
                   (el-get-read-all-recipe-names) nil))
307 308 309 310 311 312 313

(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")))
314
  (let* ((package-el (concat (el-get-as-string package) ".rcp"))
315 316 317
	 (recipe-file (or
		       ;; If dir was specified, open or create the
		       ;; recipe file in that directory.
318
		       (when dir (expand-file-name package-el dir))
319 320 321 322
		       ;; 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'
323
		       (expand-file-name package-el
324
                                         (car el-get-recipe-path)))))
325 326
    (find-file recipe-file)))

327
(defun el-get-funcall (func fname package)
328
  "`funcal' FUNC for PACKAGE and report about FNAME when `el-get-verbose'"
329
  (when (and func (functionp func))
330 331 332 333 334
      (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))))
335

336

337
(defun el-get-init (package)
338 339 340 341 342
  "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
343
called by `el-get' (usually at startup) for each installed package."
344
  (interactive (list (el-get-read-package-name "Init")))
345
  (el-get-verbose-message "el-get-init: %s" package)
346 347
  (condition-case err
      (let* ((source   (el-get-package-def package))
348
             (method   (el-get-package-method source))
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
             (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)))))

407
        ;; now handle the user configs and :post-init and :after functions
408
        (if (or lazy el-get-is-lazy)
409 410
            (let ((lazy-form
		   `(progn ,(when postinit (list 'funcall postinit))
411
			   (el-get-load-package-user-init-file ,package)
412
			   ,(when after (list 'funcall after)))))
413 414 415 416
              (eval-after-load library lazy-form))

          ;; el-get is not lazy here
          (el-get-funcall postinit "post-init" package)
417
	  (el-get-load-package-user-init-file package)
418 419 420 421 422 423 424
          (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)
425
    (debug error
426
     (el-get-installation-failed package err))))
427

428 429 430 431 432 433 434 435 436 437

(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")))
  (if (el-get-package-is-installed package)
      (message "el-get: `%s' package is already installed" package)

438
    (let* ((packages  (el-get-dependencies (el-get-as-symbol package))))
439 440 441
      (when (cdr packages)
	;; tweak el-get-post-install-hooks to install remaining packages
	;; once the first is installed
442
	(el-get-verbose-message "el-get-install %s: %S" package packages)
443 444
	(setq el-get-next-packages (cdr packages))
	(add-hook 'el-get-post-install-hooks 'el-get-install-next-packages))
445
      (el-get-do-install (car packages)))))
446

447
(defun el-get-install-next-packages (current-package)
448 449
  "Run as part of `el-get-post-init-hooks' when dealing with dependencies."
  (let ((package (pop el-get-next-packages)))
450
    (el-get-verbose-message "el-get-install-next-packages: %s" package)
451 452 453
    (if package
	(if (el-get-package-is-installed package)
	    (message "el-get: `%s' package is already installed" package)
454
	  (el-get-do-install (el-get-as-string package)))
455 456 457
      ;; no more packages to install in the dependency walk, clean up
      (remove-hook 'el-get-post-init-hooks 'el-get-install-next-packages))))

458 459
(defun el-get-post-install-build (package)
  "Function to call after building the package while installing it."
460 461 462 463
  (el-get-invalidate-autoloads package)
  (el-get-init package)
  (el-get-save-package-status package "installed"))

464 465
(defun el-get-post-install (package)
  "Post install PACKAGE. This will get run by a sentinel."
466 467
  (let* ((sync     el-get-default-process-sync)
	 (hooks    (el-get-method (el-get-package-type package) :install-hook))
468
	 (commands (el-get-build-commands package)))
Dave Abrahams's avatar
Dave Abrahams committed
469

470
    ;; post-install is the right place to run install-hook
471
    (run-hook-with-args hooks package)
Dave Abrahams's avatar
Dave Abrahams committed
472

473 474 475
    ;; 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
476
  (run-hook-with-args 'el-get-post-install-hooks package))
477

478
(defun el-get-do-install (package)
479
  "Install any PACKAGE for which you have a recipe."
480
  (el-get-error-unless-package-p package)
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
  (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))))
503

504

505
(defun el-get-post-update (package)
506
  "Post update PACKAGE. This will get run by a sentinel."
507
  (let* ((source   (el-get-package-def package))
508
	 (commands (el-get-build-commands package)))
509
    (el-get-build package commands nil el-get-default-process-sync
510 511 512 513 514
		  (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"))
515
                    (run-hook-with-args 'el-get-post-update-hooks package)))))
516

517 518
(defun el-get-update (package)
  "Update PACKAGE."
519 520
  (interactive
   (list (el-get-read-package-with-status "Update" "required" "installed")))
521 522
  (el-get-error-unless-package-p package)
  (let* ((source   (el-get-package-def package))
523
	 (method   (el-get-package-method source))
524 525 526
	 (update   (el-get-method method :update))
	 (url      (plist-get source :url))
	 (commands (plist-get source :build)))
527
    ;; update the package now
528 529
    (funcall update package url 'el-get-post-update)
    (message "el-get update %s" package)))
530

531
(defun el-get-update-all ()
532
  "Performs update of all installed packages."
533
  (interactive)
534
  (mapc 'el-get-update (el-get-list-package-names-with-status "installed")))
535

536 537 538
(defun el-get-self-update ()
  "Update el-get itself.  The standard recipe takes care of reloading the code."
  (interactive)
539 540 541
  (let ((el-get-default-process-sync t)
	(el-get-dir
	 (expand-file-name ".." (file-name-directory el-get-script))))
542
    (el-get-update "el-get")))
543

544

545
(defun el-get-post-remove (package)
546
  "Run the post-remove hooks for PACKAGE."
547
  (let* ((hooks   (el-get-method (el-get-package-method package) :remove-hook)))
548 549
    (run-hook-with-args hooks package)
    (run-hook-with-args 'el-get-post-remove-hooks package)))
550

551
(defun el-get-remove (package)
552 553 554
  "Remove any PACKAGE that is know to be installed or required."
  (interactive
   (list (el-get-read-package-with-status "Remove" "required" "installed")))
555
  (el-get-error-unless-package-p package)
556

557 558 559 560 561 562 563 564 565
  (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)))
566

567
(defun el-get-cd (package)
Dimitri Fontaine's avatar
Dimitri Fontaine committed
568
  "Open dired in the package directory."
569 570
  (interactive
   (list (el-get-read-package-with-status "cd to" "required" "installed")))
571 572
  (el-get-error-unless-package-p package)
  (dired (el-get-package-directory package)))
Dimitri Fontaine's avatar
Dimitri Fontaine committed
573

574 575 576 577 578
(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
579
	 (filename (or filename (format "%s.rcp" (el-get-source-name source)))))
580 581 582 583 584
    ;; Filepath is dir/file
    (let ((filepath (format "%s/%s" dir filename)))
      (with-temp-file filepath
	(insert (prin1-to-string source))))))

585
(defun el-get-make-recipes (&optional dir)
Dimitri Fontaine's avatar
Dimitri Fontaine committed
586
  "Loop over `el-get-sources' and write a recipe file for each
587 588 589 590 591 592 593 594 595
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))
596
      (el-get-write-recipe r dir)))
597 598
  (dired dir))

599

600 601 602
;;
;; User Interface, Non Interactive part
;;
603 604 605 606 607 608 609 610 611 612 613 614 615
(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))
616 617 618 619
	 (init-deps   (loop for p in to-init
			    append (mapcar 'el-get-as-string
					   (el-get-dependencies
					    (el-get-as-symbol p)))))
620 621
	 (to-install  (if packages
			  (loop for p in packages
622
				unless (member (el-get-as-string p) to-init)
623
				collect (el-get-as-string p))
624 625
			required))
	 done)
626
    (el-get-verbose-message "el-get-init-and-install: install %S" to-install)
627 628 629 630 631 632 633 634
    (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))
635 636

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

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

642 643 644 645 646
* 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
647

648 649
When SYNC is nil (the default), all installations run
concurrently, in the background.
650

651 652
When SYNC is 'sync, each package will be installed synchronously,
and any error will stop it all.
653 654 655 656 657

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.
658 659 660 661

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

664 665 666
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."
667
  (unless (or (null sync)
668
	      (member sync '(sync wait)))
669
    (error "el-get sync parameter should be either nil, sync or wait"))
670

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

674 675 676
  ;; 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))

677 678 679 680 681 682 683
  (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)
684
                        (make-progress-reporter
685
			 "Waiting for `el-get' to complete... "
686
			 0 (- total installed) 0)))
687
         (el-get-default-process-sync sync))
688

689 690
    ;; keep the result of `el-get-init-and-install' to return it even in the
    ;; 'wait case
691
    (prog1
692
	(el-get-init-and-install packages)
693

694
      ;; el-get-install is async, that's now ongoing.
695
      (when progress
696 697 698 699 700 701
        (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)))
        (progress-reporter-done progress))
Dave Abrahams's avatar
Dave Abrahams committed
702

703 704 705
      ;; unless we have autoloads to update, just load them now
      (unless el-get-outdated-autoloads
	(el-get-eval-autoloads)))))
706

707
(provide 'el-get)
708 709

;;; el-get.el ends here
710 711 712 713 714 715 716 717 718 719

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