gnu: Add GNU Speex.
[jackhill/guix/guix.git] / guix-package.in
index 4dc6257..58d6c49 100644 (file)
@@ -13,6 +13,7 @@ exec ${GUILE-@GUILE@} -L "@guilemoduledir@" -l "$0"    \
 !#
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2012, 2013 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -36,6 +37,7 @@ exec ${GUILE-@GUILE@} -L "@guilemoduledir@" -l "$0"    \
   #:use-module (guix packages)
   #:use-module (guix utils)
   #:use-module (guix config)
+  #:use-module ((guix build utils) #:select (directory-exists? mkdir-p))
   #:use-module (ice-9 ftw)
   #:use-module (ice-9 format)
   #:use-module (ice-9 match)
@@ -45,12 +47,13 @@ exec ${GUILE-@GUILE@} -L "@guilemoduledir@" -l "$0"    \
   #:use-module (srfi srfi-26)
   #:use-module (srfi srfi-34)
   #:use-module (srfi srfi-37)
-  #:use-module (distro)
-  #:use-module (distro packages guile)
+  #:use-module (gnu packages)
+  #:use-module ((gnu packages base) #:select (guile-final))
+  #:use-module ((gnu packages bootstrap) #:select (%bootstrap-guile))
   #:export (guix-package))
 
 (define %store
-  (open-connection))
+  (make-parameter #f))
 
 \f
 ;;;
@@ -62,7 +65,7 @@ exec ${GUILE-@GUILE@} -L "@guilemoduledir@" -l "$0"    \
          (cut string-append <> "/.guix-profile")))
 
 (define %profile-directory
-  (string-append %state-directory "/profiles/"
+  (string-append (or (getenv "NIX_STATE_DIR") %state-directory) "/profiles/"
                  (or (and=> (getenv "USER")
                             (cut string-append "per-user/" <>))
                      "default")))
@@ -87,13 +90,14 @@ exec ${GUILE-@GUILE@} -L "@guilemoduledir@" -l "$0"    \
     (_
      (error "unsupported manifest format" manifest))))
 
-(define (latest-profile-number profile)
-  "Return the identifying number of the latest generation of PROFILE.
-PROFILE is the name of the symlink to the current generation."
-  (define %profile-rx
-    (make-regexp (string-append "^" (regexp-quote (basename profile))
-                                "-([0-9]+)")))
+(define (profile-regexp profile)
+  "Return a regular expression that matches PROFILE's name and number."
+  (make-regexp (string-append "^" (regexp-quote (basename profile))
+                              "-([0-9]+)")))
 
+(define (profile-numbers profile)
+  "Return the list of generation numbers of PROFILE, or '(0) if no
+former profiles were found."
   (define* (scandir name #:optional (select? (const #t))
                     (entry<? (@ (ice-9 i18n) string-locale<?)))
     ;; XXX: Bug-fix version introduced in Guile v2.0.6-62-g139ce19.
@@ -129,22 +133,37 @@ PROFILE is the name of the symlink to the current generation."
              (sort files entry<?))))
 
   (match (scandir (dirname profile)
-                  (cut regexp-exec %profile-rx <>))
+                  (cute regexp-exec (profile-regexp profile) <>))
     (#f                                         ; no profile directory
-     0)
+     '(0))
     (()                                         ; no profiles
-     0)
+     '(0))
     ((profiles ...)                             ; former profiles around
-     (let ((numbers (map (compose string->number
-                                  (cut match:substring <> 1)
-                                  (cut regexp-exec %profile-rx <>))
-                         profiles)))
-       (fold (lambda (number highest)
-               (if (> number highest)
-                   number
-                   highest))
-             0
-             numbers)))))
+     (map (compose string->number
+                   (cut match:substring <> 1)
+                   (cute regexp-exec (profile-regexp profile) <>))
+          profiles))))
+
+(define (latest-profile-number profile)
+  "Return the identifying number of the latest generation of PROFILE.
+PROFILE is the name of the symlink to the current generation."
+  (fold (lambda (number highest)
+          (if (> number highest)
+              number
+              highest))
+        0
+        (profile-numbers profile)))
+
+(define (previous-profile-number profile number)
+  "Return the number of the generation before generation NUMBER of
+PROFILE, or 0 if none exists.  It could be NUMBER - 1, but it's not the
+case when generations have been deleted (there are \"holes\")."
+  (fold (lambda (candidate highest)
+          (if (and (< candidate number) (> candidate highest))
+              candidate
+              highest))
+        0
+        (profile-numbers profile)))
 
 (define (profile-derivation store packages)
   "Return a derivation that builds a profile (a user environment) with
@@ -177,6 +196,40 @@ all of PACKAGES, a list of name/version/output/path tuples."
                                      packages)
                                 #:modules '((guix build union))))
 
+(define (profile-number profile)
+  "Return PROFILE's number or 0.  An absolute file name must be used."
+  (or (and=> (false-if-exception (regexp-exec (profile-regexp profile)
+                                              (basename (readlink profile))))
+             (compose string->number (cut match:substring <> 1)))
+      0))
+
+(define (roll-back profile)
+  "Roll back to the previous generation of PROFILE."
+  ;; XXX: Get the previous generation number from the manifest?
+  (let* ((number           (profile-number profile))
+         (previous-number  (previous-profile-number profile number))
+         (previous-profile (format #f "~a/~a-~a-link"
+                                   (dirname profile) profile
+                                   previous-number))
+         (manifest         (string-append previous-profile "/manifest")))
+
+    (define (switch-link)
+      ;; Atomically switch PROFILE to the previous profile.
+      (let ((pivot (string-append previous-profile ".new")))
+        (format #t (_ "switching from generation ~a to ~a~%")
+                number previous-number)
+        (symlink previous-profile pivot)
+        (rename-file pivot profile)))
+
+    (cond ((zero? number)
+           (format (current-error-port)
+                   (_ "error: `~a' is not a valid profile~%")
+                   profile))
+          ((or (zero? previous-number)
+               (not (file-exists? previous-profile)))
+           (leave (_ "error: no previous profile; not rolling back~%")))
+          (else (switch-link)))))
+
 \f
 ;;;
 ;;; Command-line options.
@@ -195,13 +248,15 @@ Install, remove, or upgrade PACKAGES in a single transaction.\n"))
   -r, --remove=PACKAGE   remove PACKAGE"))
   (display (_ "
   -u, --upgrade=REGEXP   upgrade all the installed packages matching REGEXP"))
+  (display (_ "
+      --roll-back        roll back to the previous generation"))
   (newline)
   (display (_ "
   -p, --profile=PROFILE  use PROFILE instead of the user's default profile"))
   (display (_ "
   -n, --dry-run          show what would be done without actually doing it"))
   (display (_ "
-  -b, --bootstrap        use the bootstrap Guile to build the profile"))
+      --bootstrap        use the bootstrap Guile to build the profile"))
   (display (_ "
       --verbose          produce verbose output"))
   (newline)
@@ -235,6 +290,9 @@ Install, remove, or upgrade PACKAGES in a single transaction.\n"))
         (option '(#\r "remove") #t #f
                 (lambda (opt name arg result)
                   (alist-cons 'remove arg result)))
+        (option '("roll-back") #f #f
+                (lambda (opt name arg result)
+                  (alist-cons 'roll-back? #t result)))
         (option '(#\p "profile") #t #f
                 (lambda (opt name arg result)
                   (alist-cons 'profile arg
@@ -242,7 +300,7 @@ Install, remove, or upgrade PACKAGES in a single transaction.\n"))
         (option '(#\n "dry-run") #f #f
                 (lambda (opt name arg result)
                   (alist-cons 'dry-run? #t result)))
-        (option '(#\b "bootstrap") #f #f
+        (option '("bootstrap") #f #f
                 (lambda (opt name arg result)
                   (alist-cons 'bootstrap? #t result)))
         (option '("verbose") #f #f
@@ -272,16 +330,22 @@ Install, remove, or upgrade PACKAGES in a single transaction.\n"))
                  (alist-cons 'argument arg result))
                %default-options))
 
+  (define (guile-missing?)
+    ;; Return #t if %GUILE-FOR-BUILD is not available yet.
+    (let ((out (derivation-path->output-path (%guile-for-build))))
+      (not (valid-path? (%store) out))))
+
   (define (show-what-to-build drv dry-run?)
     ;; Show what will/would be built in realizing the derivations listed
     ;; in DRV.
     (let* ((req  (append-map (lambda (drv-path)
                                (let ((d (call-with-input-file drv-path
                                           read-derivation)))
-                                 (derivation-prerequisites-to-build %store d)))
+                                 (derivation-prerequisites-to-build
+                                  (%store) d)))
                              drv))
            (req* (delete-duplicates
-                  (append (remove (compose (cut valid-path? %store <>)
+                  (append (remove (compose (cute valid-path? (%store) <>)
                                            derivation-path->output-path)
                                   drv)
                           (map derivation-input-path req)))))
@@ -324,84 +388,126 @@ Install, remove, or upgrade PACKAGES in a single transaction.\n"))
         (()
          (leave (_ "~a: package not found~%") request)))))
 
+  (define (ensure-default-profile)
+    ;; Ensure the default profile symlink and directory exist.
+
+    ;; Create ~/.guix-profile if it doesn't exist yet.
+    (when (and %user-environment-directory
+               %current-profile
+               (not (false-if-exception
+                     (lstat %user-environment-directory))))
+      (symlink %current-profile %user-environment-directory))
+
+    ;; Attempt to create /…/profiles/per-user/$USER if needed.
+    (unless (directory-exists? %profile-directory)
+      (catch 'system-error
+        (lambda ()
+          (mkdir-p %profile-directory))
+        (lambda args
+          ;; Often, we cannot create %PROFILE-DIRECTORY because its
+          ;; parent directory is root-owned and we're running
+          ;; unprivileged.
+          (format (current-error-port)
+                  (_ "error: while creating directory `~a': ~a~%")
+                  %profile-directory
+                  (strerror (system-error-errno args)))
+          (format (current-error-port)
+                  (_ "Please create the `~a' directory, with you as the owner.~%")
+                  %profile-directory)
+          (exit 1)))))
+
   (define (process-actions opts)
     ;; Process any install/remove/upgrade action from OPTS.
-    (let* ((dry-run? (assoc-ref opts 'dry-run?))
-           (verbose? (assoc-ref opts 'verbose?))
-           (profile  (assoc-ref opts 'profile))
-           (install  (filter-map (match-lambda
-                                  (('install . (? store-path?))
-                                   #f)
-                                  (('install . package)
-                                   (find-package package))
-                                  (_ #f))
-                                 opts))
-           (drv      (filter-map (match-lambda
-                                  ((name version sub-drv
-                                         (? package? package))
-                                   (package-derivation %store package))
-                                  (_ #f))
-                                 install))
-           (install* (append
-                      (filter-map (match-lambda
-                                   (('install . (? store-path? path))
-                                    (let-values (((name version)
-                                                  (package-name->name+version
-                                                   (store-path-package-name
-                                                    path))))
-                                     `(,name ,version #f ,path)))
-                                   (_ #f))
-                                  opts)
-                      (map (lambda (tuple drv)
-                             (match tuple
-                               ((name version sub-drv _)
-                                (let ((output-path
-                                       (derivation-path->output-path
-                                        drv sub-drv)))
-                                  `(,name ,version ,sub-drv ,output-path)))))
-                           install drv)))
-           (remove   (filter-map (match-lambda
-                                  (('remove . package)
-                                   package)
-                                  (_ #f))
-                                 opts))
-           (packages (append install*
-                             (fold (lambda (package result)
-                                     (match package
-                                       ((name _ ...)
-                                        (alist-delete name result))))
-                                   (fold alist-delete
-                                         (manifest-packages
-                                          (profile-manifest profile))
-                                         remove)
-                                   install*))))
-
-      (show-what-to-build drv dry-run?)
-
-      (or dry-run?
-          (and (build-derivations %store drv)
-               (let* ((prof-drv (profile-derivation %store packages))
-                      (prof     (derivation-path->output-path prof-drv))
-                      (old-drv  (profile-derivation
-                                 %store (manifest-packages
-                                         (profile-manifest profile))))
-                      (old-prof (derivation-path->output-path old-drv))
-                      (number   (latest-profile-number profile))
-                      (name     (format #f "~a/~a-~a-link"
-                                        (dirname profile)
-                                        (basename profile) (+ 1 number))))
-                 (if (string=? old-prof prof)
-                     (format (current-error-port) (_ "nothing to be done~%"))
-                     (and (parameterize ((current-build-output-port
-                                          (if verbose?
-                                              (current-error-port)
-                                              (%make-void-port "w"))))
-                            (build-derivations %store (list prof-drv)))
-                          (begin
-                            (symlink prof name)
-                            (when (file-exists? profile)
-                              (delete-file profile))
-                            (symlink name profile)))))))))
+
+    (define dry-run? (assoc-ref opts 'dry-run?))
+    (define verbose? (assoc-ref opts 'verbose?))
+    (define profile  (assoc-ref opts 'profile))
+
+    ;; First roll back if asked to.
+    (if (and (assoc-ref opts 'roll-back?) (not dry-run?))
+        (begin
+          (roll-back profile)
+          (process-actions (alist-delete 'roll-back? opts)))
+        (let* ((install  (filter-map (match-lambda
+                                      (('install . (? store-path?))
+                                       #f)
+                                      (('install . package)
+                                       (find-package package))
+                                      (_ #f))
+                                     opts))
+               (drv      (filter-map (match-lambda
+                                      ((name version sub-drv
+                                             (? package? package))
+                                       (package-derivation (%store) package))
+                                      (_ #f))
+                                     install))
+               (install* (append
+                          (filter-map (match-lambda
+                                       (('install . (? store-path? path))
+                                        (let-values (((name version)
+                                                      (package-name->name+version
+                                                       (store-path-package-name
+                                                        path))))
+                                          `(,name ,version #f ,path)))
+                                       (_ #f))
+                                      opts)
+                          (map (lambda (tuple drv)
+                                 (match tuple
+                                   ((name version sub-drv _)
+                                    (let ((output-path
+                                           (derivation-path->output-path
+                                            drv sub-drv)))
+                                      `(,name ,version ,sub-drv ,output-path)))))
+                               install drv)))
+               (remove   (filter-map (match-lambda
+                                      (('remove . package)
+                                       package)
+                                      (_ #f))
+                                     opts))
+               (packages (append install*
+                                 (fold (lambda (package result)
+                                         (match package
+                                           ((name _ ...)
+                                            (alist-delete name result))))
+                                       (fold alist-delete
+                                             (manifest-packages
+                                              (profile-manifest profile))
+                                             remove)
+                                       install*))))
+
+          (when (equal? profile %current-profile)
+            (ensure-default-profile))
+
+          (show-what-to-build drv dry-run?)
+
+          (or dry-run?
+              (and (build-derivations (%store) drv)
+                   (let* ((prof-drv (profile-derivation (%store) packages))
+                          (prof     (derivation-path->output-path prof-drv))
+                          (old-drv  (profile-derivation
+                                     (%store) (manifest-packages
+                                               (profile-manifest profile))))
+                          (old-prof (derivation-path->output-path old-drv))
+                          (number   (latest-profile-number profile))
+                          (name     (format #f "~a/~a-~a-link"
+                                            (dirname profile)
+                                            (basename profile) (+ 1 number))))
+                     (if (string=? old-prof prof)
+                         (when (or (pair? install) (pair? remove))
+                           (format (current-error-port)
+                                   (_ "nothing to be done~%")))
+                         (and (parameterize ((current-build-output-port
+                                              ;; Output something when Guile
+                                              ;; needs to be built.
+                                              (if (or verbose? (guile-missing?))
+                                                  (current-error-port)
+                                                  (%make-void-port "w"))))
+                                (build-derivations (%store) (list prof-drv)))
+                              (begin
+                                (symlink prof name)
+                                (when (file-exists? profile)
+                                  (delete-file profile))
+                                (symlink name profile))))))))))
 
   (define (process-query opts)
     ;; Process any query specified by OPTS.  Return #t when a query was
@@ -432,9 +538,10 @@ Install, remove, or upgrade PACKAGES in a single transaction.\n"))
                                     (cons p r))))
                             '())))
            (for-each (lambda (p)
-                       (format #t "~a\t~a\t~a~%"
+                       (format #t "~a\t~a\t~a\t~a~%"
                                (package-name p)
                                (package-version p)
+                               (string-join (package-outputs p) ",")
                                (location->string (package-location p))))
                      (sort available
                            (lambda (p1 p2)
@@ -449,16 +556,12 @@ Install, remove, or upgrade PACKAGES in a single transaction.\n"))
   (setvbuf (current-error-port) _IOLBF)
 
   (let ((opts (parse-options)))
-    (with-error-handling
-      (or (process-query opts)
-          (parameterize ((%guile-for-build
-                          (package-derivation %store
-                                              (if (assoc-ref opts 'bootstrap?)
-                                                  (@@ (distro packages base)
-                                                      %bootstrap-guile)
-                                                  guile-2.0))))
-            (process-actions opts))))))
-
-;; Local Variables:
-;; eval: (put 'guard 'scheme-indent-function 1)
-;; End:
+    (parameterize ((%store (open-connection)))
+      (with-error-handling
+        (or (process-query opts)
+            (parameterize ((%guile-for-build
+                            (package-derivation (%store)
+                                                (if (assoc-ref opts 'bootstrap?)
+                                                    %bootstrap-guile
+                                                    guile-final))))
+              (process-actions opts)))))))