guix build: Accept multiple '-s' options.
[jackhill/guix/guix.git] / guix / scripts / build.scm
index b25bf50..ba143ad 100644 (file)
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2012, 2013, 2014, 2015, 2016 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2013 Mark H Weaver <mhw@netris.org>
 ;;;
 ;;; This file is part of GNU Guix.
   #:use-module (guix derivations)
   #:use-module (guix packages)
   #:use-module (guix grafts)
+
   #:use-module (guix utils)
+
+  ;; Use the procedure that destructures "NAME-VERSION" forms.
+  #:use-module ((guix build utils)
+                #:select ((package-name->name+version
+                           . hyphen-package-name->name+version)))
+
   #:use-module (guix monads)
   #:use-module (guix gexp)
   #:autoload   (guix http-client) (http-fetch http-get-error?)
   #:use-module (srfi srfi-37)
   #:autoload   (gnu packages) (specification->package %package-module-path)
   #:autoload   (guix download) (download-to-store)
+  #:autoload   (guix git-download) (git-reference?)
+  #:autoload   (guix git) (git-checkout?)
+  #:use-module ((guix status) #:select (with-status-verbosity))
+  #:use-module ((guix progress) #:select (current-terminal-columns))
+  #:use-module ((guix build syscalls) #:select (terminal-columns))
   #:export (%standard-build-options
             set-build-options-from-command-line
             set-build-options-from-command-line*
             options->transformation
             show-transformation-options-help
 
-            guix-build))
+            guix-build
+            register-root
+            register-root*))
 
 (define %default-log-urls
   ;; Default base URLs for build logs.
-  '("http://hydra.gnu.org/log"))
+  '("http://ci.guix.info/log"))
 
 ;; XXX: The following procedure cannot be in (guix store) because of the
 ;; dependency on (guix derivations).
 found.  Return #f if no build log was found."
   (define (valid-url? url)
     ;; Probe URL and return #t if it is accessible.
-    (guard (c ((http-get-error? c) #f))
-      (close-port (http-fetch url #:buffered? #f))
-      #t))
+    (catch #t
+      (lambda ()
+        (guard (c ((http-get-error? c) #f))
+          (close-port (http-fetch url #:buffered? #f))
+          #t))
+      (match-lambda*
+        (('getaddrinfo-error . _)
+         #f)
+        (('tls-certificate-error args ...)
+         (report-error (G_ "cannot access build log at '~a':~%") url)
+         (print-exception (current-error-port) #f
+                          'tls-certificate-error args)
+         (exit 1))
+        ((key . args)
+         (apply throw key args)))))
 
   (define (find-url file)
     (let ((base (basename file)))
@@ -77,7 +103,7 @@ found.  Return #f if no build log was found."
              ;; Usually we'll have more luck with the output file name since
              ;; the deriver that was used by the server could be different, so
              ;; try one of the output file names.
-             (let ((drv (call-with-input-file file read-derivation)))
+             (let ((drv (read-derivation-from-file file)))
                (or (find-url (derivation->output-path drv))
                    (find-url file))))
            (lambda args
@@ -90,8 +116,10 @@ found.  Return #f if no build log was found."
 
 (define (register-root store paths root)
   "Register ROOT as an indirect GC root for all of PATHS."
-  (let* ((root (string-append (canonicalize-path (dirname root))
-                              "/" root)))
+  (let* ((root (if (string-prefix? "/" root)
+                   root
+                   (string-append (canonicalize-path (dirname root))
+                                  "/" (basename root)))))
     (catch 'system-error
       (lambda ()
         (match paths
@@ -109,40 +137,51 @@ found.  Return #f if no build log was found."
                  0
                  paths))))
       (lambda args
-        (leave (_ "failed to create GC root `~a': ~a~%")
+        (leave (G_ "failed to create GC root `~a': ~a~%")
                root (strerror (system-error-errno args)))))))
 
-(define (package-with-source store p uri)
+(define register-root*
+  (store-lift register-root))
+
+(define (numeric-extension? file-name)
+  "Return true if FILE-NAME ends with digits."
+  (string-every char-set:hex-digit (file-extension file-name)))
+
+(define (tarball-base-name file-name)
+  "Return the \"base\" of FILE-NAME, removing '.tar.gz' or similar
+extensions."
+  ;; TODO: Factorize.
+  (cond ((not (file-extension file-name))
+         file-name)
+        ((numeric-extension? file-name)
+         file-name)
+        ((string=? (file-extension file-name) "tar")
+         (file-sans-extension file-name))
+        ((file-extension file-name)
+         =>
+         (match-lambda
+           ("scm" file-name)
+           (else  (tarball-base-name (file-sans-extension file-name)))))
+        (else
+         file-name)))
+
+(define* (package-with-source store p uri #:optional version)
   "Return a package based on P but with its source taken from URI.  Extract
 the new package's version number from URI."
-  (define (numeric-extension? file-name)
-    ;; Return true if FILE-NAME ends with digits.
-    (string-every char-set:hex-digit (file-extension file-name)))
-
-  (define (tarball-base-name file-name)
-    ;; Return the "base" of FILE-NAME, removing '.tar.gz' or similar
-    ;; extensions.
-    ;; TODO: Factorize.
-    (cond ((not (file-extension file-name))
-           file-name)
-          ((numeric-extension? file-name)
-           file-name)
-          ((string=? (file-extension file-name) "tar")
-           (file-sans-extension file-name))
-          ((file-extension file-name)
-           (tarball-base-name (file-sans-extension file-name)))
-          (else
-           file-name)))
-
   (let ((base (tarball-base-name (basename uri))))
-    (let-values (((name version)
-                  (package-name->name+version base)))
+    (let-values (((_ version*)
+                  (hyphen-package-name->name+version base)))
       (package (inherit p)
-               (version (or version (package-version p)))
+               (version (or version version*
+                            (package-version p)))
 
                ;; Use #:recursive? #t to allow for directories.
                (source (download-to-store store uri
-                                          #:recursive? #t))))))
+                                          #:recursive? #t))
+
+               ;; Override the replacement, otherwise '--with-source' would
+               ;; have no effect.
+               (replacement #f)))))
 
 \f
 ;;;
@@ -154,8 +193,23 @@ the new package's version number from URI."
 matching URIs given in SOURCES."
   (define new-sources
     (map (lambda (uri)
-           (cons (package-name->name+version (basename uri))
-                 uri))
+           (match (string-index uri #\=)
+             (#f
+              ;; Determine the package name and version from URI.
+              (call-with-values
+                  (lambda ()
+                    (hyphen-package-name->name+version
+                     (tarball-base-name (basename uri))))
+                (lambda (name version)
+                  (list name version uri))))
+             (index
+              ;; What's before INDEX is a "PKG@VER" or "PKG" spec.
+              (call-with-values
+                  (lambda ()
+                    (package-name->name+version (string-take uri index)))
+                (lambda (name version)
+                  (list name version
+                        (string-drop uri (+ 1 index))))))))
          sources))
 
   (lambda (store obj)
@@ -163,53 +217,168 @@ matching URIs given in SOURCES."
                (result   '()))
       (match obj
         ((? package? p)
-         (let ((source (assoc-ref sources (package-name p))))
-           (if source
-               (package-with-source store p source)
-               p)))
+         (match (assoc-ref sources (package-name p))
+           ((version source)
+            (package-with-source store p source version))
+           (#f
+            p)))
         (_
          obj)))))
 
-(define (transform-package-inputs replacement-specs)
-  "Return a procedure that, when passed a package, replaces its direct
-dependencies according to REPLACEMENT-SPECS.  REPLACEMENT-SPECS is a list of
-strings like \"guile=guile@2.1\" meaning that, any direct dependency on a
-package called \"guile\" must be replaced with a dependency on a version 2.1
-of \"guile\"."
+(define (evaluate-replacement-specs specs proc)
+  "Parse SPECS, a list of strings like \"guile=guile@2.1\" and return a list
+of package spec/procedure pairs as expected by 'package-input-rewriting/spec'.
+PROC is called with the package to be replaced and its replacement according
+to SPECS.  Raise an error if an element of SPECS uses invalid syntax, or if a
+package it refers to could not be found."
   (define not-equal
     (char-set-complement (char-set #\=)))
 
+  (map (lambda (spec)
+         (match (string-tokenize spec not-equal)
+           ((spec new)
+            (cons spec
+                  (let ((new (specification->package new)))
+                    (lambda (old)
+                      (proc old new)))))
+           (x
+            (leave (G_ "invalid replacement specification: ~s~%") spec))))
+       specs))
+
+(define (transform-package-inputs replacement-specs)
+  "Return a procedure that, when passed a package, replaces its direct
+dependencies according to REPLACEMENT-SPECS.  REPLACEMENT-SPECS is a list of
+strings like \"guile=guile@2.1\" meaning that, any dependency on a package
+called \"guile\" must be replaced with a dependency on a version 2.1 of
+\"guile\"."
+  (let* ((replacements (evaluate-replacement-specs replacement-specs
+                                                   (lambda (old new)
+                                                     new)))
+         (rewrite      (package-input-rewriting/spec replacements)))
+    (lambda (store obj)
+      (if (package? obj)
+          (rewrite obj)
+          obj))))
+
+(define (transform-package-inputs/graft replacement-specs)
+  "Return a procedure that, when passed a package, replaces its direct
+dependencies according to REPLACEMENT-SPECS.  REPLACEMENT-SPECS is a list of
+strings like \"gnutls=gnutls@3.5.4\" meaning that packages are built using the
+current 'gnutls' package, after which version 3.5.4 is grafted onto them."
+  (define (set-replacement old new)
+    (package (inherit old) (replacement new)))
+
+  (let* ((replacements (evaluate-replacement-specs replacement-specs
+                                                   set-replacement))
+         (rewrite      (package-input-rewriting/spec replacements)))
+    (lambda (store obj)
+      (if (package? obj)
+          (rewrite obj)
+          obj))))
+
+(define %not-equal
+  (char-set-complement (char-set #\=)))
+
+(define (package-git-url package)
+  "Return the URL of the Git repository for package, or raise an error if
+the source of PACKAGE is not fetched from a Git repository."
+  (let ((source (package-source package)))
+    (cond ((and (origin? source)
+                (git-reference? (origin-uri source)))
+           (git-reference-url (origin-uri source)))
+          ((git-checkout? source)
+           (git-checkout-url source))
+          (else
+           (leave (G_ "the source of ~a is not a Git reference~%")
+                  (package-full-name package))))))
+
+(define (evaluate-git-replacement-specs specs proc)
+  "Parse SPECS, a list of strings like \"guile=stable-2.2\", and return a list
+of package pairs, where (PROC PACKAGE URL BRANCH-OR-COMMIT) returns the
+replacement package.  Raise an error if an element of SPECS uses invalid
+syntax, or if a package it refers to could not be found."
+  (map (lambda (spec)
+         (match (string-tokenize spec %not-equal)
+           ((spec branch-or-commit)
+            (define (replace old)
+              (let* ((source (package-source old))
+                     (url    (package-git-url old)))
+                (proc old url branch-or-commit)))
+
+            (cons spec replace))
+           (x
+            (leave (G_ "invalid replacement specification: ~s~%") spec))))
+       specs))
+
+(define (transform-package-source-branch replacement-specs)
+  "Return a procedure that, when passed a package, replaces its direct
+dependencies according to REPLACEMENT-SPECS.  REPLACEMENT-SPECS is a list of
+strings like \"guile-next=stable-3.0\" meaning that packages are built using
+'guile-next' from the latest commit on its 'stable-3.0' branch."
+  (define (replace old url branch)
+    (package
+      (inherit old)
+      (version (string-append "git." (string-map (match-lambda
+                                                   (#\/ #\-)
+                                                   (chr chr))
+                                                 branch)))
+      (source (git-checkout (url url) (branch branch)
+                            (recursive? #t)))))
+
+  (let* ((replacements (evaluate-git-replacement-specs replacement-specs
+                                                       replace))
+         (rewrite      (package-input-rewriting/spec replacements)))
+    (lambda (store obj)
+      (if (package? obj)
+          (rewrite obj)
+          obj))))
+
+(define (transform-package-source-commit replacement-specs)
+  "Return a procedure that, when passed a package, replaces its direct
+dependencies according to REPLACEMENT-SPECS.  REPLACEMENT-SPECS is a list of
+strings like \"guile-next=cabba9e\" meaning that packages are built using
+'guile-next' from commit 'cabba9e'."
+  (define (replace old url commit)
+    (package
+      (inherit old)
+      (version (string-append "git."
+                              (if (< (string-length commit) 7)
+                                  commit
+                                  (string-take commit 7))))
+      (source (git-checkout (url url) (commit commit)
+                            (recursive? #t)))))
+
+  (let* ((replacements (evaluate-git-replacement-specs replacement-specs
+                                                       replace))
+         (rewrite      (package-input-rewriting/spec replacements)))
+    (lambda (store obj)
+      (if (package? obj)
+          (rewrite obj)
+          obj))))
+
+(define (transform-package-source-git-url replacement-specs)
+  "Return a procedure that, when passed a package, replaces its dependencies
+according to REPLACEMENT-SPECS.  REPLACEMENT-SPECS is a list of strings like
+\"guile-json=https://gitthing.com/…\" meaning that packages are built using
+a checkout of the Git repository at the given URL."
   (define replacements
-    ;; List of name/package pairs.
     (map (lambda (spec)
-           (match (string-tokenize spec not-equal)
-             ((old new)
-              (cons old (specification->package new)))
-             (_
-              (leave (_ "invalid replacement specification: ~s~%") spec))))
+           (match (string-tokenize spec %not-equal)
+             ((spec url)
+              (cons spec
+                    (lambda (old)
+                      (package
+                        (inherit old)
+                        (source (git-checkout (url url)
+                                              (recursive? #t)))))))))
          replacement-specs))
 
-  (define (rewrite input)
-    (match input
-      ((label (? package? package) outputs ...)
-       (match (assoc-ref replacements (package-name package))
-         (#f  (cons* label (replace package) outputs))
-         (new (cons* label new outputs))))
-      (_
-       input)))
-
-  (define replace
-    (memoize                                      ;XXX: use eq?
-     (lambda (p)
-       (package
-         (inherit p)
-         (inputs (map rewrite (package-inputs p)))
-         (native-inputs (map rewrite (package-native-inputs p)))
-         (propagated-inputs (map rewrite (package-propagated-inputs p)))))))
+  (define rewrite
+    (package-input-rewriting/spec replacements))
 
   (lambda (store obj)
     (if (package? obj)
-        (replace obj)
+        (rewrite obj)
         obj)))
 
 (define %transformations
@@ -218,51 +387,80 @@ of \"guile\"."
   ;; procedure; it is called with two arguments: the store, and a list of
   ;; things to build.
   `((with-source . ,transform-package-source)
-    (with-input  . ,transform-package-inputs)))
+    (with-input  . ,transform-package-inputs)
+    (with-graft  . ,transform-package-inputs/graft)
+    (with-branch . ,transform-package-source-branch)
+    (with-commit . ,transform-package-source-commit)
+    (with-git-url . ,transform-package-source-git-url)))
 
 (define %transformation-options
   ;; The command-line interface to the above transformations.
-  (list (option '("with-source") #t #f
-                (lambda (opt name arg result . rest)
-                  (apply values
-                         (cons (alist-cons 'with-source arg result)
-                               rest))))
-        (option '("with-input") #t #f
-                (lambda (opt name arg result . rest)
-                  (apply values
-                         (cons (alist-cons 'with-input arg result)
-                               rest))))))
+  (let ((parser (lambda (symbol)
+                  (lambda (opt name arg result . rest)
+                    (apply values
+                           (alist-cons symbol arg result)
+                           rest)))))
+    (list (option '("with-source") #t #f
+                  (parser 'with-source))
+          (option '("with-input") #t #f
+                  (parser 'with-input))
+          (option '("with-graft") #t #f
+                  (parser 'with-graft))
+          (option '("with-branch") #t #f
+                  (parser 'with-branch))
+          (option '("with-commit") #t #f
+                  (parser 'with-commit))
+          (option '("with-git-url") #t #f
+                  (parser 'with-git-url)))))
 
 (define (show-transformation-options-help)
-  (display (_ "
+  (display (G_ "
       --with-source=SOURCE
                          use SOURCE when building the corresponding package"))
-  (display (_ "
+  (display (G_ "
       --with-input=PACKAGE=REPLACEMENT
-                         replace dependency PACKAGE by REPLACEMENT")))
+                         replace dependency PACKAGE by REPLACEMENT"))
+  (display (G_ "
+      --with-graft=PACKAGE=REPLACEMENT
+                         graft REPLACEMENT on packages that refer to PACKAGE"))
+  (display (G_ "
+      --with-branch=PACKAGE=BRANCH
+                         build PACKAGE from the latest commit of BRANCH"))
+  (display (G_ "
+      --with-commit=PACKAGE=COMMIT
+                         build PACKAGE from COMMIT"))
+  (display (G_ "
+      --with-git-url=PACKAGE=URL
+                         build PACKAGE from the repository at URL")))
 
 
 (define (options->transformation opts)
   "Return a procedure that, when passed an object to build (package,
 derivation, etc.), applies the transformations specified by OPTS."
   (define applicable
-    ;; List of applicable transformations as symbol/procedure pairs.
+    ;; List of applicable transformations as symbol/procedure pairs in the
+    ;; order in which they appear on the command line.
     (filter-map (match-lambda
-                  ((key . transform)
-                   (match (filter-map (match-lambda
-                                        ((k . arg)
-                                         (and (eq? k key) arg)))
-                                      opts)
-                     (()   #f)
-                     (args (cons key (transform args))))))
-                %transformations))
+                  ((key . value)
+                   (match (any (match-lambda
+                                 ((k . proc)
+                                  (and (eq? k key) proc)))
+                               %transformations)
+                     (#f
+                      #f)
+                     (transform
+                      ;; XXX: We used to pass TRANSFORM a list of several
+                      ;; arguments, but we now pass only one, assuming that
+                      ;; transform composes well.
+                      (cons key (transform (list value)))))))
+                (reverse opts)))
 
   (lambda (store obj)
     (fold (match-lambda*
             (((name . transform) obj)
              (let ((new (transform store obj)))
                (when (eq? new obj)
-                 (warning (_ "transformation '~a' had no effect on ~a~%")
+                 (warning (G_ "transformation '~a' had no effect on ~a~%")
                           name
                           (if (package? obj)
                               (package-full-name obj)
@@ -280,38 +478,38 @@ derivation, etc.), applies the transformations specified by OPTS."
   "Display on the current output port help about the standard command-line
 options handled by 'set-build-options-from-command-line', and listed in
 '%standard-build-options'."
-  (display (_ "
+  (display (G_ "
   -L, --load-path=DIR    prepend DIR to the package module search path"))
-  (display (_ "
+  (display (G_ "
   -K, --keep-failed      keep build tree of failed builds"))
-  (display (_ "
+  (display (G_ "
   -k, --keep-going       keep going when some of the derivations fail"))
-  (display (_ "
+  (display (G_ "
   -n, --dry-run          do not build the derivations"))
-  (display (_ "
+  (display (G_ "
       --fallback         fall back to building when the substituter fails"))
-  (display (_ "
+  (display (G_ "
       --no-substitutes   build instead of resorting to pre-built substitutes"))
-  (display (_ "
+  (display (G_ "
       --substitute-urls=URLS
                          fetch substitute from URLS if they are authorized"))
-  (display (_ "
+  (display (G_ "
       --no-grafts        do not graft packages"))
-  (display (_ "
+  (display (G_ "
       --no-build-hook    do not attempt to offload builds via the build hook"))
-  (display (_ "
+  (display (G_ "
       --max-silent-time=SECONDS
                          mark the build as failed after SECONDS of silence"))
-  (display (_ "
+  (display (G_ "
       --timeout=SECONDS  mark the build as failed after SECONDS of activity"))
-  (display (_ "
-      --verbosity=LEVEL  use the given verbosity LEVEL"))
-  (display (_ "
+  (display (G_ "
       --rounds=N         build N times in a row to detect non-determinism"))
-  (display (_ "
+  (display (G_ "
   -c, --cores=N          allow the use of up to N CPU cores for the build"))
-  (display (_ "
-  -M, --max-jobs=N       allow at most N build jobs")))
+  (display (G_ "
+  -M, --max-jobs=N       allow at most N build jobs"))
+  (display (G_ "
+      --debug=LEVEL      produce debugging output at LEVEL")))
 
 (define (set-build-options-from-command-line store opts)
   "Given OPTS, an alist as returned by 'args-fold' given
@@ -321,8 +519,8 @@ options handled by 'set-build-options-from-command-line', and listed in
                      #:keep-failed? (assoc-ref opts 'keep-failed?)
                      #:keep-going? (assoc-ref opts 'keep-going?)
                      #:rounds (assoc-ref opts 'rounds)
-                     #:build-cores (or (assoc-ref opts 'cores) 0)
-                     #:max-build-jobs (or (assoc-ref opts 'max-jobs) 1)
+                     #:build-cores (assoc-ref opts 'cores)
+                     #:max-build-jobs (assoc-ref opts 'max-jobs)
                      #:fallback? (assoc-ref opts 'fallback?)
                      #:use-substitutes? (assoc-ref opts 'substitutes?)
                      #:substitute-urls (assoc-ref opts 'substitute-urls)
@@ -330,7 +528,11 @@ options handled by 'set-build-options-from-command-line', and listed in
                      #:max-silent-time (assoc-ref opts 'max-silent-time)
                      #:timeout (assoc-ref opts 'timeout)
                      #:print-build-trace (assoc-ref opts 'print-build-trace?)
-                     #:verbosity (assoc-ref opts 'verbosity)))
+                     #:print-extended-build-trace?
+                     (assoc-ref opts 'print-extended-build-trace?)
+                     #:multiplexed-build-output?
+                     (assoc-ref opts 'multiplexed-build-output?)
+                     #:verbosity (assoc-ref opts 'debug)))
 
 (define set-build-options-from-command-line*
   (store-lift set-build-options-from-command-line))
@@ -404,26 +606,26 @@ options handled by 'set-build-options-from-command-line', and listed in
                   (apply values
                          (alist-cons 'timeout (string->number* arg) result)
                          rest)))
-        (option '("verbosity") #t #f
+        (option '("debug") #t #f
                 (lambda (opt name arg result . rest)
-                  (let ((level (string->number arg)))
+                  (let ((level (string->number* arg)))
                     (apply values
-                           (alist-cons 'verbosity level
-                                       (alist-delete 'verbosity result))
+                           (alist-cons 'debug level
+                                       (alist-delete 'debug result))
                            rest))))
         (option '(#\c "cores") #t #f
                 (lambda (opt name arg result . rest)
                   (let ((c (false-if-exception (string->number arg))))
                     (if c
                         (apply values (alist-cons 'cores c result) rest)
-                        (leave (_ "not a number: '~a' option argument: ~a~%")
+                        (leave (G_ "not a number: '~a' option argument: ~a~%")
                                name arg)))))
         (option '(#\M "max-jobs") #t #f
                 (lambda (opt name arg result . rest)
                   (let ((c (false-if-exception (string->number arg))))
                     (if c
                         (apply values (alist-cons 'max-jobs c result) rest)
-                        (leave (_ "not a number: '~a' option argument: ~a~%")
+                        (leave (G_ "not a number: '~a' option argument: ~a~%")
                                name arg)))))))
 
 \f
@@ -433,51 +635,56 @@ options handled by 'set-build-options-from-command-line', and listed in
 
 (define %default-options
   ;; Alist of default option values.
-  `((system . ,(%current-system))
-    (build-mode . ,(build-mode normal))
+  `((build-mode . ,(build-mode normal))
     (graft? . #t)
     (substitutes? . #t)
     (build-hook? . #t)
     (print-build-trace? . #t)
-    (max-silent-time . 3600)
-    (verbosity . 0)))
+    (print-extended-build-trace? . #t)
+    (multiplexed-build-output? . #t)
+    (verbosity . 2)
+    (debug . 0)))
 
 (define (show-help)
-  (display (_ "Usage: guix build [OPTION]... PACKAGE-OR-DERIVATION...
+  (display (G_ "Usage: guix build [OPTION]... PACKAGE-OR-DERIVATION...
 Build the given PACKAGE-OR-DERIVATION and return their output paths.\n"))
-  (display (_ "
+  (display (G_ "
   -e, --expression=EXPR  build the package or derivation EXPR evaluates to"))
-  (display (_ "
+  (display (G_ "
   -f, --file=FILE        build the package or derivation that the code within
                          FILE evaluates to"))
-  (display (_ "
+  (display (G_ "
   -S, --source           build the packages' source derivations"))
-  (display (_ "
+  (display (G_ "
       --sources[=TYPE]   build source derivations; TYPE may optionally be one
                          of \"package\", \"all\" (default), or \"transitive\""))
-  (display (_ "
+  (display (G_ "
   -s, --system=SYSTEM    attempt to build for SYSTEM--e.g., \"i686-linux\""))
-  (display (_ "
+  (display (G_ "
       --target=TRIPLET   cross-build for TRIPLET--e.g., \"armel-linux-gnu\""))
-  (display (_ "
+  (display (G_ "
   -d, --derivations      return the derivation paths of the given packages"))
-  (display (_ "
+  (display (G_ "
       --check            rebuild items to check for non-determinism issues"))
-  (display (_ "
+  (display (G_ "
+      --repair           repair the specified items"))
+  (display (G_ "
   -r, --root=FILE        make FILE a symlink to the result, and register it
                          as a garbage collector root"))
-  (display (_ "
+  (display (G_ "
+  -v, --verbosity=LEVEL  use the given verbosity LEVEL"))
+  (display (G_ "
   -q, --quiet            do not show the build log"))
-  (display (_ "
+  (display (G_ "
       --log-file         return the log file names for the given derivations"))
   (newline)
   (show-build-options-help)
   (newline)
   (show-transformation-options-help)
   (newline)
-  (display (_ "
+  (display (G_ "
   -h, --help             display this help and exit"))
-  (display (_ "
+  (display (G_ "
   -V, --version          display version information and exit"))
   (newline)
   (show-bug-report-information))
@@ -504,7 +711,7 @@ Build the given PACKAGE-OR-DERIVATION and return their output paths.\n"))
                      ("transitive"
                       (alist-cons 'source package-transitive-sources result))
                      (else
-                      (leave (_ "invalid argument: '~a' option argument: ~a, ~
+                      (leave (G_ "invalid argument: '~a' option argument: ~a, ~
 must be one of 'package', 'all', or 'transitive'~%")
                              name arg)))))
          (option '("check") #f #f
@@ -513,10 +720,15 @@ must be one of 'package', 'all', or 'transitive'~%")
                           (alist-cons 'build-mode (build-mode check)
                                       result)
                           rest)))
+         (option '("repair") #f #f
+                 (lambda (opt name arg result . rest)
+                   (apply values
+                          (alist-cons 'build-mode (build-mode repair)
+                                      result)
+                          rest)))
          (option '(#\s "system") #t #f
                  (lambda (opt name arg result)
-                   (alist-cons 'system arg
-                               (alist-delete 'system result eq?))))
+                   (alist-cons 'system arg result)))
          (option '("target") #t #f
                  (lambda (opt name arg result)
                    (alist-cons 'target arg
@@ -532,13 +744,19 @@ must be one of 'package', 'all', or 'transitive'~%")
                    (alist-cons 'file arg result)))
          (option '(#\n "dry-run") #f #f
                  (lambda (opt name arg result)
-                   (alist-cons 'dry-run? #t result)))
+                   (alist-cons 'dry-run? #t (alist-cons 'graft? #f result))))
          (option '(#\r "root") #t #f
                  (lambda (opt name arg result)
                    (alist-cons 'gc-root arg result)))
+         (option '(#\v "verbosity") #t #f
+                 (lambda (opt name arg result)
+                   (let ((level (string->number* arg)))
+                     (alist-cons 'verbosity level
+                                 (alist-delete 'verbosity result)))))
          (option '(#\q "quiet") #f #f
                  (lambda (opt name arg result)
-                   (alist-cons 'quiet? #t result)))
+                   (alist-cons 'verbosity 0
+                               (alist-delete 'verbosity result))))
          (option '("log-file") #f #f
                  (lambda (opt name arg result)
                    (alist-cons 'log-file? #t result)))
@@ -550,8 +768,8 @@ must be one of 'package', 'all', or 'transitive'~%")
   "Read the arguments from OPTS and return a list of high-level objects to
 build---packages, gexps, derivations, and so on."
   (define (validate-type x)
-    (unless (or (package? x) (derivation? x) (gexp? x) (procedure? x))
-      (leave (_ "~s: not something we can build~%") x)))
+    (unless (or (derivation? x) (file-like? x) (gexp? x) (procedure? x))
+      (leave (G_ "~s: not something we can build~%") x)))
 
   (define (ensure-list x)
     (let ((lst (match x
@@ -563,7 +781,7 @@ build---packages, gexps, derivations, and so on."
   (append-map (match-lambda
                 (('argument . (? string? spec))
                  (cond ((derivation-path? spec)
-                        (list (call-with-input-file spec read-derivation)))
+                        (list (read-derivation-from-file spec)))
                        ((store-path? spec)
                         ;; Nothing to do; maybe for --log-file.
                         '())
@@ -591,38 +809,71 @@ build."
        (cut package-cross-derivation <> <> triplet <>))))
 
   (define src    (assoc-ref opts 'source))
-  (define system (assoc-ref opts 'system))
   (define graft? (assoc-ref opts 'graft?))
-
-  (parameterize ((%graft? graft?))
-    (append-map (match-lambda
-                  ((? package? p)
-                   (let ((p (or (and graft? (package-replacement p)) p)))
-                     (match src
-                       (#f
-                        (list (package->derivation store p system)))
-                       (#t
-                        (let ((s (package-source p)))
-                          (list (package-source-derivation store s))))
-                       (proc
-                        (map (cut package-source-derivation store <>)
-                             (proc p))))))
-                  ((? derivation? drv)
-                   (list drv))
-                  ((? procedure? proc)
-                   (list (run-with-store store
-                           (mbegin %store-monad
-                             (set-guile-for-build (default-guile))
-                             (proc))
-                           #:system system)))
-                  ((? gexp? gexp)
-                   (list (run-with-store store
-                           (mbegin %store-monad
-                             (set-guile-for-build (default-guile))
-                             (gexp->derivation "gexp" gexp
-                                               #:system system))))))
-                (map (cut transform store <>)
-                     (options->things-to-build opts)))))
+  (define systems
+    (match (filter-map (match-lambda
+                         (('system . system) system)
+                         (_ #f))
+                       opts)
+      (()      (list (%current-system)))
+      (systems systems)))
+
+  (define things-to-build
+    (map (cut transform store <>)
+         (options->things-to-build opts)))
+
+  (define (compute-derivation obj system)
+    ;; Compute the derivation of OBJ for SYSTEM.
+    (match obj
+      ((? package? p)
+       (let ((p (or (and graft? (package-replacement p)) p)))
+         (match src
+           (#f
+            (list (package->derivation store p system)))
+           (#t
+            (match (package-source p)
+              (#f
+               (format (current-error-port)
+                       (G_ "~a: warning: \
+package '~a' has no source~%")
+                       (location->string (package-location p))
+                       (package-name p))
+               '())
+              (s
+               (list (package-source-derivation store s)))))
+           (proc
+            (map (cut package-source-derivation store <>)
+                 (proc p))))))
+      ((? derivation? drv)
+       (list drv))
+      ((? procedure? proc)
+       (list (run-with-store store
+               (mbegin %store-monad
+                 (set-guile-for-build (default-guile))
+                 (proc))
+               #:system system)))
+      ((? file-like? obj)
+       (list (run-with-store store
+               (lower-object obj system
+                             #:target (assoc-ref opts 'target))
+               #:system system)))
+      ((? gexp? gexp)
+       (list (run-with-store store
+               (mbegin %store-monad
+                 (set-guile-for-build (default-guile))
+                 (gexp->derivation "gexp" gexp
+                                   #:system system))
+               #:system system)))))
+
+  ;; We may get 'unbound-variable' errors while evaluating the 'inputs' fields
+  ;; of user packages.  Since 'guix build' is the primary tool for people
+  ;; testing new packages, report such errors gracefully.
+  (with-unbound-variable-handling
+   (parameterize ((%graft? graft?))
+     (append-map (lambda (system)
+                   (append-map (cut compute-derivation <> system)
+                               things-to-build))
+                 systems))))
 
 (define (show-build-log store file urls)
   "Show the build log for FILE, falling back to remote logs from URLS if
@@ -631,7 +882,7 @@ needed."
                  (log-url store file #:base-urls urls))))
     (if log
         (format #t "~a~%" log)
-        (leave (_ "no build log for '~a'~%") file))))
+        (leave (G_ "no build log for '~a'~%") file))))
 
 \f
 ;;;
@@ -643,62 +894,59 @@ needed."
     (parse-command-line args %options
                         (list %default-options)))
 
-  (define quiet?
-    (assoc-ref opts 'quiet?))
-
   (with-error-handling
     ;; Ask for absolute file names so that .drv file names passed from the
     ;; user to 'read-derivation' are absolute when it returns.
     (with-fluids ((%file-port-name-canonicalization 'absolute))
-      (with-store store
-        ;; Set the build options before we do anything else.
-        (set-build-options-from-command-line store opts)
-
-        (parameterize ((current-build-output-port (if quiet?
-                                                      (%make-void-port "w")
-                                                      (current-error-port))))
-          (let* ((mode  (assoc-ref opts 'build-mode))
-                 (drv   (options->derivations store opts))
-                 (urls  (map (cut string-append <> "/log")
-                             (if (assoc-ref opts 'substitutes?)
-                                 (or (assoc-ref opts 'substitute-urls)
-                                     ;; XXX: This does not necessarily match the
-                                     ;; daemon's substitute URLs.
-                                     %default-substitute-urls)
-                                 '())))
-                 (items (filter-map (match-lambda
-                                      (('argument . (? store-path? file))
-                                       file)
-                                      (_ #f))
-                                    opts))
-                 (roots (filter-map (match-lambda
-                                      (('gc-root . root) root)
-                                      (_ #f))
-                                    opts)))
-
-            (unless (assoc-ref opts 'log-file?)
-              (show-what-to-build store drv
-                                  #:use-substitutes?
-                                  (assoc-ref opts 'substitutes?)
-                                  #:dry-run? (assoc-ref opts 'dry-run?)
-                                  #:mode mode))
-
-            (cond ((assoc-ref opts 'log-file?)
-                   (for-each (cut show-build-log store <> urls)
-                             (delete-duplicates
-                              (append (map derivation-file-name drv)
-                                      items))))
-                  ((assoc-ref opts 'derivations-only?)
-                   (format #t "~{~a~%~}" (map derivation-file-name drv))
-                   (for-each (cut register-root store <> <>)
-                             (map (compose list derivation-file-name) drv)
-                             roots))
-                  ((not (assoc-ref opts 'dry-run?))
-                   (and (build-derivations store drv mode)
-                        (for-each show-derivation-outputs drv)
-                        (for-each (cut register-root store <> <>)
-                                  (map (lambda (drv)
-                                         (map cdr
-                                              (derivation->output-paths drv)))
-                                       drv)
-                                  roots))))))))))
+      (with-status-verbosity (assoc-ref opts 'verbosity)
+        (with-store store
+          ;; Set the build options before we do anything else.
+          (set-build-options-from-command-line store opts)
+
+          (parameterize ((current-terminal-columns (terminal-columns)))
+            (let* ((mode  (assoc-ref opts 'build-mode))
+                   (drv   (options->derivations store opts))
+                   (urls  (map (cut string-append <> "/log")
+                               (if (assoc-ref opts 'substitutes?)
+                                   (or (assoc-ref opts 'substitute-urls)
+                                       ;; XXX: This does not necessarily match the
+                                       ;; daemon's substitute URLs.
+                                       %default-substitute-urls)
+                                   '())))
+                   (items (filter-map (match-lambda
+                                        (('argument . (? store-path? file))
+                                         file)
+                                        (_ #f))
+                                      opts))
+                   (roots (filter-map (match-lambda
+                                        (('gc-root . root) root)
+                                        (_ #f))
+                                      opts)))
+
+              (unless (or (assoc-ref opts 'log-file?)
+                          (assoc-ref opts 'derivations-only?))
+                (show-what-to-build store drv
+                                    #:use-substitutes?
+                                    (assoc-ref opts 'substitutes?)
+                                    #:dry-run? (assoc-ref opts 'dry-run?)
+                                    #:mode mode))
+
+              (cond ((assoc-ref opts 'log-file?)
+                     (for-each (cut show-build-log store <> urls)
+                               (delete-duplicates
+                                (append (map derivation-file-name drv)
+                                        items))))
+                    ((assoc-ref opts 'derivations-only?)
+                     (format #t "~{~a~%~}" (map derivation-file-name drv))
+                     (for-each (cut register-root store <> <>)
+                               (map (compose list derivation-file-name) drv)
+                               roots))
+                    ((not (assoc-ref opts 'dry-run?))
+                     (and (build-derivations store drv mode)
+                          (for-each show-derivation-outputs drv)
+                          (for-each (cut register-root store <> <>)
+                                    (map (lambda (drv)
+                                           (map cdr
+                                                (derivation->output-paths drv)))
+                                         drv)
+                                    roots)))))))))))