summaryrefslogtreecommitdiff
path: root/guix
diff options
context:
space:
mode:
authorJan Nieuwenhuizen <janneke@gnu.org>2018-10-21 23:18:19 +0200
committerJan Nieuwenhuizen <janneke@gnu.org>2018-10-21 23:19:35 +0200
commitcf7658f7cb5de0e17f4801faa84c378a4b40033e (patch)
tree646fa120d67bb41868a543461700e62aa170b2c0 /guix
parent09c5a5680a06011f985a84aa26fb890b3be453bd (diff)
parentffddb42d6c510456997ee6de1c1b8026c9ce6d14 (diff)
downloadguix-patches-cf7658f7cb5de0e17f4801faa84c378a4b40033e.tar
guix-patches-cf7658f7cb5de0e17f4801faa84c378a4b40033e.tar.gz
Merge branch 'core-updates' into core-updates-next
Diffstat (limited to 'guix')
-rw-r--r--guix/build-system/glib-or-gtk.scm2
-rw-r--r--guix/build-system/haskell.scm32
-rw-r--r--guix/build-system/python.scm2
-rw-r--r--guix/build/download.scm33
-rw-r--r--guix/build/haskell-build-system.scm32
-rw-r--r--guix/build/java-utils.scm10
-rw-r--r--guix/build/lisp-utils.scm7
-rw-r--r--guix/build/store-copy.scm23
-rw-r--r--guix/channels.scm16
-rw-r--r--guix/gexp.scm8
-rw-r--r--guix/git-download.scm15
-rw-r--r--guix/gnupg.scm2
-rw-r--r--guix/import/json.scm3
-rw-r--r--guix/import/pypi.scm2
-rw-r--r--guix/import/stackage.scm11
-rw-r--r--guix/inferior.scm366
-rw-r--r--guix/profiles.scm114
-rw-r--r--guix/progress.scm119
-rw-r--r--guix/scripts.scm4
-rw-r--r--guix/scripts/build.scm22
-rw-r--r--guix/scripts/describe.scm2
-rw-r--r--guix/scripts/environment.scm117
-rw-r--r--guix/scripts/import/cran.scm2
-rw-r--r--guix/scripts/pack.scm150
-rw-r--r--guix/scripts/package.scm66
-rw-r--r--guix/scripts/perform-download.scm17
-rw-r--r--guix/scripts/pull.scm155
-rwxr-xr-xguix/scripts/substitute.scm44
-rw-r--r--guix/scripts/system.scm37
-rw-r--r--guix/self.scm9
-rw-r--r--guix/serialization.scm3
-rw-r--r--guix/status.scm605
-rw-r--r--guix/store.scm94
-rw-r--r--guix/store/database.scm44
-rw-r--r--guix/tests.scm37
-rw-r--r--guix/ui.scm122
36 files changed, 1777 insertions, 550 deletions
diff --git a/guix/build-system/glib-or-gtk.scm b/guix/build-system/glib-or-gtk.scm
index 621e68e0ab..fcd92f2334 100644
--- a/guix/build-system/glib-or-gtk.scm
+++ b/guix/build-system/glib-or-gtk.scm
@@ -112,7 +112,7 @@
(configure-flags ''())
;; Disable icon theme cache generation.
(make-flags ''("gtk_update_icon_cache=true"))
- (out-of-source? #t)
+ (out-of-source? #f)
(tests? #t)
(test-target "check")
(parallel-build? #t)
diff --git a/guix/build-system/haskell.scm b/guix/build-system/haskell.scm
index 1cb734631c..1ec11c71d8 100644
--- a/guix/build-system/haskell.scm
+++ b/guix/build-system/haskell.scm
@@ -21,6 +21,7 @@
#:use-module (guix utils)
#:use-module (guix packages)
#:use-module (guix derivations)
+ #:use-module (guix download)
#:use-module (guix search-paths)
#:use-module (guix build-system)
#:use-module (guix build-system gnu)
@@ -48,14 +49,35 @@
(let ((haskell (resolve-interface '(gnu packages haskell))))
(module-ref haskell 'ghc)))
+(define (source-url->revision-url url revision)
+ "Convert URL (a Hackage source URL) to the URL for the Cabal file at
+version REVISION."
+ (let* ((last-slash (string-rindex url #\/))
+ (next-slash (string-rindex url #\/ 0 last-slash)))
+ (string-append (substring url 0 next-slash)
+ (substring url last-slash (- (string-length url)
+ (string-length ".tar.gz")))
+ "/revision/" revision ".cabal")))
+
(define* (lower name
#:key source inputs native-inputs outputs system target
(haskell (default-haskell))
+ cabal-revision
#:allow-other-keys
#:rest arguments)
"Return a bag for NAME."
(define private-keywords
- '(#:target #:haskell #:inputs #:native-inputs))
+ '(#:target #:haskell #:cabal-revision #:inputs #:native-inputs))
+
+ (define (cabal-revision->origin cabal-revision)
+ (match cabal-revision
+ ((revision hash)
+ (origin
+ (method url-fetch)
+ (uri (source-url->revision-url (origin-uri source) revision))
+ (sha256 (base32 hash))
+ (file-name (string-append name "-" revision ".cabal"))))
+ (#f #f)))
(and (not target) ;XXX: no cross-compilation
(bag
@@ -64,6 +86,9 @@
(host-inputs `(,@(if source
`(("source" ,source))
'())
+ ,@(match (cabal-revision->origin cabal-revision)
+ (#f '())
+ (revision `(("cabal-revision" ,revision))))
,@inputs
;; Keep the standard inputs of 'gnu-build-system'.
@@ -103,6 +128,11 @@ provides a 'Setup.hs' file as its build system."
source)
(source
source))
+ #:cabal-revision ,(match (assoc-ref inputs
+ "cabal-revision")
+ (((? derivation? revision))
+ (derivation->output-path revision))
+ (revision revision))
#:configure-flags ,configure-flags
#:haddock-flags ,haddock-flags
#:system ,system
diff --git a/guix/build-system/python.scm b/guix/build-system/python.scm
index ffed837313..b753940bad 100644
--- a/guix/build-system/python.scm
+++ b/guix/build-system/python.scm
@@ -50,7 +50,7 @@
"Return a URI string for the Python package hosted on the Python Package
Index (PyPI) corresponding to NAME and VERSION. EXTENSION is the file name
extension, such as '.tar.gz'."
- (string-append "https://pypi.io/packages/source/"
+ (string-append "https://pypi.org/packages/source/"
(string-take name 1) "/" name "/"
name "-" version extension))
diff --git a/guix/build/download.scm b/guix/build/download.scm
index 315a3554ec..54163849a2 100644
--- a/guix/build/download.scm
+++ b/guix/build/download.scm
@@ -115,7 +115,7 @@ and 'guix publish', something like
(string-drop path 33)
path)))
-(define* (ftp-fetch uri file #:key timeout)
+(define* (ftp-fetch uri file #:key timeout print-build-trace?)
"Fetch data from URI and write it to FILE. Return FILE on success. Bail
out if the connection could not be established in less than TIMEOUT seconds."
(let* ((conn (match (and=> (uri-userinfo uri)
@@ -136,12 +136,17 @@ out if the connection could not be established in less than TIMEOUT seconds."
(lambda (out)
(dump-port* in out
#:buffer-size %http-receive-buffer-size
- #:reporter (progress-reporter/file
- (uri-abbreviation uri) size))))
-
- (ftp-close conn))
- (newline)
- file)
+ #:reporter
+ (if print-build-trace?
+ (progress-reporter/trace
+ file (uri->string uri) size)
+ (progress-reporter/file
+ (uri-abbreviation uri) size)))))
+
+ (ftp-close conn)
+ (unless print-build-trace?
+ (newline))
+ file))
;; Autoload GnuTLS so that this module can be used even when GnuTLS is
;; not available. At compile time, this yields "possibly unbound
@@ -723,7 +728,8 @@ Return a list of URIs."
#:key
(timeout 10) (verify-certificate? #t)
(mirrors '()) (content-addressed-mirrors '())
- (hashes '()))
+ (hashes '())
+ print-build-trace?)
"Fetch FILE from URL; URL may be either a single string, or a list of
string denoting alternate URLs for FILE. Return #f on failure, and FILE
on success.
@@ -759,13 +765,18 @@ otherwise simply ignore them."
(lambda (output)
(dump-port* port output
#:buffer-size %http-receive-buffer-size
- #:reporter (progress-reporter/file
- (uri-abbreviation uri) size))
+ #:reporter (if print-build-trace?
+ (progress-reporter/trace
+ file (uri->string uri) size)
+ (progress-reporter/file
+ (uri-abbreviation uri) size)))
(newline)))
file)))
((ftp)
(false-if-exception* (ftp-fetch uri file
- #:timeout timeout)))
+ #:timeout timeout
+ #:print-build-trace?
+ print-build-trace?)))
(else
(format #t "skipping URI with unsupported scheme: ~s~%"
uri)
diff --git a/guix/build/haskell-build-system.scm b/guix/build/haskell-build-system.scm
index 5a72d22842..7b556f6431 100644
--- a/guix/build/haskell-build-system.scm
+++ b/guix/build/haskell-build-system.scm
@@ -3,6 +3,7 @@
;;; Copyright © 2015 Eric Bavier <bavier@member.fsf.org>
;;; Copyright © 2015 Paul van der Walt <paul@denknerd.org>
;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
+;;; Copyright © 2018 Alex Vong <alexvong1995@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -28,6 +29,7 @@
#:use-module (ice-9 regex)
#:use-module (ice-9 match)
#:use-module (ice-9 vlist)
+ #:use-module (ice-9 ftw)
#:export (%standard-phases
haskell-build))
@@ -77,6 +79,7 @@ and parameters ~s~%"
(doc (assoc-ref outputs "doc"))
(lib (assoc-ref outputs "lib"))
(bin (assoc-ref outputs "bin"))
+ (name-version (strip-store-file-name out))
(input-dirs (match inputs
(((_ . dir) ...)
dir)
@@ -87,7 +90,7 @@ and parameters ~s~%"
`(,(string-append "--bindir=" (or bin out) "/bin"))
`(,(string-append
"--docdir=" (or doc out)
- "/share/doc/" (package-name-version out)))
+ "/share/doc/" name-version))
'("--libsubdir=$compiler/$pkg-$version")
`(,(string-append "--package-db=" %tmp-db-dir))
'("--global")
@@ -126,12 +129,6 @@ and parameters ~s~%"
"Install a given Haskell package."
(run-setuphs "copy" '()))
-(define (package-name-version store-dir)
- "Given a store directory STORE-DIR return 'name-version' of the package."
- (let* ((base (basename store-dir)))
- (string-drop base
- (+ 1 (string-index base #\-)))))
-
(define (grep rx port)
"Given a regular-expression RX including a group, read from PORT until the
first match and return the content of the group."
@@ -146,7 +143,7 @@ first match and return the content of the group."
(define* (setup-compiler #:key system inputs outputs #:allow-other-keys)
"Setup the compiler environment."
(let* ((haskell (assoc-ref inputs "haskell"))
- (name-version (package-name-version haskell)))
+ (name-version (strip-store-file-name haskell)))
(cond
((string-match "ghc" name-version)
(make-ghc-package-database system inputs outputs))
@@ -163,6 +160,7 @@ first match and return the content of the group."
(define (make-ghc-package-database system inputs outputs)
"Generate the GHC package database."
(let* ((haskell (assoc-ref inputs "haskell"))
+ (name-version (strip-store-file-name haskell))
(input-dirs (match inputs
(((_ . dir) ...)
dir)
@@ -170,7 +168,7 @@ first match and return the content of the group."
;; Silence 'find-files' (see 'evaluate-search-paths')
(conf-dirs (with-null-error-port
(search-path-as-list
- `(,(string-append "lib/" (package-name-version haskell)))
+ `(,(string-append "lib/" name-version))
input-dirs #:pattern ".*\\.conf.d$")))
(conf-files (append-map (cut find-files <> "\\.conf$") conf-dirs)))
(mkdir-p %tmp-db-dir)
@@ -230,9 +228,10 @@ given Haskell package."
(let* ((out (assoc-ref outputs "out"))
(haskell (assoc-ref inputs "haskell"))
+ (name-verion (strip-store-file-name haskell))
(lib (string-append out "/lib"))
- (config-dir (string-append lib "/"
- (package-name-version haskell)
+ (config-dir (string-append lib
+ "/" name-verion
"/" name ".conf.d"))
(id-rx (make-regexp "^id: *(.*)$"))
(config-file (string-append out "/" name ".conf"))
@@ -266,8 +265,19 @@ given Haskell package."
(run-setuphs "haddock" haddock-flags))
#t)
+(define* (patch-cabal-file #:key cabal-revision #:allow-other-keys)
+ (when cabal-revision
+ ;; Cabal requires there to be a single file with the suffix ".cabal".
+ (match (scandir "." (cut string-suffix? ".cabal" <>))
+ ((original)
+ (format #t "replacing ~s with ~s~%" original cabal-revision)
+ (copy-file cabal-revision original))
+ (_ (error "Could not find a Cabal file to patch."))))
+ #t)
+
(define %standard-phases
(modify-phases gnu:%standard-phases
+ (add-after 'unpack 'patch-cabal-file patch-cabal-file)
(delete 'bootstrap)
(add-before 'configure 'setup-compiler setup-compiler)
(add-before 'install 'haddock haddock)
diff --git a/guix/build/java-utils.scm b/guix/build/java-utils.scm
index 128be1edeb..8200638bee 100644
--- a/guix/build/java-utils.scm
+++ b/guix/build/java-utils.scm
@@ -1,6 +1,7 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2016 Hartmut Goebel <h.goebel@crazy-compilers.com>
;;; Copyright © 2016 Ricardo Wurmus <rekado@elephly.net>
+;;; Copyright © 2018 Alex Vong <alexvong1995@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -23,12 +24,6 @@
install-jars
install-javadoc))
-;; Copied from haskell-build-system.scm
-(define (package-name-version store-dir)
- "Given a store directory STORE-DIR return 'name-version' of the package."
- (let* ((base (basename store-dir)))
- (string-drop base (+ 1 (string-index base #\-)))))
-
(define* (ant-build-javadoc #:key (target "javadoc") (make-flags '())
#:allow-other-keys)
(apply invoke `("ant" ,target ,@make-flags)))
@@ -48,8 +43,9 @@ is used in case the build.xml does not include an install target."
install javadocs when this is not done by the install target."
(lambda* (#:key outputs #:allow-other-keys)
(let* ((out (assoc-ref outputs "out"))
+ (name-version (strip-store-file-name out))
(docs (string-append (or (assoc-ref outputs "doc") out)
- "/share/doc/" (package-name-version out) "/")))
+ "/share/doc/" name-version "/")))
(mkdir-p docs)
(copy-recursively apidoc-directory docs)
#t)))
diff --git a/guix/build/lisp-utils.scm b/guix/build/lisp-utils.scm
index 6470cfec97..97bc6197a3 100644
--- a/guix/build/lisp-utils.scm
+++ b/guix/build/lisp-utils.scm
@@ -84,11 +84,12 @@
(define (normalize-dependency dependency)
"Normalize the name of DEPENDENCY. Handles dependency definitions of the
dependency-def form described by
-<https://common-lisp.net/project/asdf/asdf.html#The-defsystem-grammar>."
+<https://common-lisp.net/project/asdf/asdf.html#The-defsystem-grammar>.
+Assume that any symbols in DEPENDENCY will be in upper-case."
(match dependency
- ((':version name rest ...)
+ ((':VERSION name rest ...)
`(:version ,(normalize-string name) ,@rest))
- ((':feature feature-specification dependency-specification)
+ ((':FEATURE feature-specification dependency-specification)
`(:feature
,feature-specification
,(normalize-dependency dependency-specification)))
diff --git a/guix/build/store-copy.scm b/guix/build/store-copy.scm
index 2d9590d16f..64ade7885c 100644
--- a/guix/build/store-copy.scm
+++ b/guix/build/store-copy.scm
@@ -19,6 +19,7 @@
(define-module (guix build store-copy)
#:use-module (guix build utils)
#:use-module (guix sets)
+ #:use-module (guix progress)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-26)
@@ -167,7 +168,8 @@ REFERENCE-GRAPHS, a list of reference-graph files."
(reduce + 0 (map file-size items)))
-(define* (populate-store reference-graphs target)
+(define* (populate-store reference-graphs target
+ #:key (log-port (current-error-port)))
"Populate the store under directory TARGET with the items specified in
REFERENCE-GRAPHS, a list of reference-graph files."
(define store
@@ -183,9 +185,20 @@ REFERENCE-GRAPHS, a list of reference-graph files."
(mkdir-p store)
(chmod store #o1775)
- (for-each (lambda (thing)
- (copy-recursively thing
- (string-append target thing)))
- (things-to-copy)))
+
+ (let* ((things (things-to-copy))
+ (len (length things))
+ (progress (progress-reporter/bar len
+ (format #f "copying ~a store items"
+ len)
+ log-port)))
+ (call-with-progress-reporter progress
+ (lambda (report)
+ (for-each (lambda (thing)
+ (copy-recursively thing
+ (string-append target thing)
+ #:log (%make-void-port "w"))
+ (report))
+ things)))))
;;; store-copy.scm ends here
diff --git a/guix/channels.scm b/guix/channels.scm
index 2e7bffae9f..82389eb583 100644
--- a/guix/channels.scm
+++ b/guix/channels.scm
@@ -47,9 +47,9 @@
channel-instance-checkout
latest-channel-instances
- channel-instance-derivations
latest-channel-derivation
- channel-instances->manifest))
+ channel-instances->manifest
+ channel-instances->derivation))
;;; Commentary:
;;;
@@ -294,13 +294,17 @@ channel instances."
(zip instances derivations))))
(return (manifest entries))))
+(define (channel-instances->derivation instances)
+ "Return the derivation of the profile containing INSTANCES, a list of
+channel instances."
+ (mlet %store-monad ((manifest (channel-instances->manifest instances)))
+ (profile-derivation manifest)))
+
(define latest-channel-instances*
(store-lift latest-channel-instances))
(define* (latest-channel-derivation #:optional (channels %default-channels))
"Return as a monadic value the derivation that builds the profile for the
latest instances of CHANNELS."
- (mlet* %store-monad ((instances ((store-lift latest-channel-instances)
- channels))
- (manifest (channel-instances->manifest instances)))
- (profile-derivation manifest)))
+ (mlet %store-monad ((instances (latest-channel-instances* channels)))
+ (channel-instances->derivation instances)))
diff --git a/guix/gexp.scm b/guix/gexp.scm
index 770b79e012..f7def5862b 100644
--- a/guix/gexp.scm
+++ b/guix/gexp.scm
@@ -438,6 +438,14 @@ This is the declarative counterpart of 'gexp->file'."
(base file-append-base) ;<package> | <derivation> | ...
(suffix file-append-suffix)) ;list of strings
+(define (write-file-append file port)
+ (match file
+ (($ <file-append> base suffix)
+ (format port "#<file-append ~s ~s>" base
+ (string-join suffix)))))
+
+(set-record-type-printer! <file-append> write-file-append)
+
(define (file-append base . suffix)
"Return a <file-append> object that expands to the concatenation of BASE and
SUFFIX."
diff --git a/guix/git-download.scm b/guix/git-download.scm
index 24cf11be5e..fa94fad8f8 100644
--- a/guix/git-download.scm
+++ b/guix/git-download.scm
@@ -156,22 +156,23 @@ HASH-ALGO (a symbol). Use NAME as the file name, or a generic name if #f."
The result is similar to that of the 'git ls-files' command, except that it
also includes directories, not just regular files. The returned file names
are relative to DIRECTORY, which is not necessarily the root of the checkout."
- (let* ((directory (canonicalize-path directory))
+ (let* (;; 'repository-working-directory' always returns a trailing "/",
+ ;; so add one here to ease the comparisons below.
+ (directory (string-append (canonicalize-path directory) "/"))
(dot-git (repository-discover directory))
- (top (dirname dot-git))
(repository (repository-open dot-git))
+ ;; XXX: This procedure is mistakenly private in Guile-Git 0.1.0.
+ (workdir ((@@ (git repository) repository-working-directory)
+ repository))
(head (repository-head repository))
(oid (reference-target head))
(commit (commit-lookup repository oid))
(tree (commit-tree commit))
(files (tree-list tree)))
(repository-close! repository)
- (if (string=? top directory)
+ (if (string=? workdir directory)
files
- (let ((relative (string-append
- (string-drop directory
- (+ 1 (string-length top)))
- "/")))
+ (let ((relative (string-drop directory (string-length workdir))))
(filter-map (lambda (file)
(and (string-prefix? relative file)
(string-drop file (string-length relative))))
diff --git a/guix/gnupg.scm b/guix/gnupg.scm
index b30ce461b4..40feb44561 100644
--- a/guix/gnupg.scm
+++ b/guix/gnupg.scm
@@ -57,7 +57,7 @@
(define %openpgp-key-server
;; The default key server. Note that keys.gnupg.net appears to be
;; unreliable.
- (make-parameter "pgp.mit.edu"))
+ (make-parameter "pool.sks-keyservers.net"))
(define* (gnupg-verify sig file
#:optional (keyring (current-keyring)))
diff --git a/guix/import/json.scm b/guix/import/json.scm
index 4f96a513df..81ea5e7b31 100644
--- a/guix/import/json.scm
+++ b/guix/import/json.scm
@@ -47,4 +47,5 @@ the query."
(define (json-fetch-alist url)
"Return an alist representation of the JSON resource URL, or #f if URL
returns 403 or 404."
- (hash-table->alist (json-fetch url)))
+ (and=> (json-fetch url)
+ hash-table->alist))
diff --git a/guix/import/pypi.scm b/guix/import/pypi.scm
index 87b047bdac..3a20fc4b9b 100644
--- a/guix/import/pypi.scm
+++ b/guix/import/pypi.scm
@@ -330,7 +330,7 @@ VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE."
(define (pypi-url? url)
(or (string-prefix? "https://pypi.org/" url)
(string-prefix? "https://pypi.python.org/" url)
- (string-prefix? "https://pypi.io/packages" url)))
+ (string-prefix? "https://pypi.org/packages" url)))
(let ((source-url (and=> (package-source package) origin-uri))
(fetch-method (and=> (package-source package) origin-method)))
diff --git a/guix/import/stackage.scm b/guix/import/stackage.scm
index afd5d997ae..1c1e73a723 100644
--- a/guix/import/stackage.scm
+++ b/guix/import/stackage.scm
@@ -43,15 +43,12 @@
(define (lts-info-ghc-version lts-info)
"Retruns the version of the GHC compiler contained in LTS-INFO."
- (match lts-info
- ((("snapshot" ("ghc" . version) _ _) _) version)
- (_ #f)))
+ (and=> (assoc-ref lts-info "snapshot")
+ (cut assoc-ref <> "ghc")))
(define (lts-info-packages lts-info)
- "Returns the alist of packages contained in LTS-INFO."
- (match lts-info
- ((("packages" pkg ...) . _) pkg)
- (_ '())))
+ "Retruns the alist of packages contained in LTS-INFO."
+ (or (assoc-ref lts-info "packages") '()))
(define (leave-with-message fmt . args)
(raise (condition (&message (message (apply format #f fmt args))))))
diff --git a/guix/inferior.scm b/guix/inferior.scm
index af37233a03..1dbb9e1699 100644
--- a/guix/inferior.scm
+++ b/guix/inferior.scm
@@ -19,24 +19,68 @@
(define-module (guix inferior)
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-9 gnu)
- #:use-module ((guix utils) #:select (source-properties->location))
+ #:use-module ((guix utils)
+ #:select (%current-system
+ source-properties->location
+ call-with-temporary-directory
+ version>? version-prefix?
+ cache-directory))
+ #:use-module ((guix store)
+ #:select (nix-server-socket
+ nix-server-major-version
+ nix-server-minor-version
+ store-lift))
+ #:use-module ((guix derivations)
+ #:select (read-derivation-from-file))
+ #:use-module (guix gexp)
+ #:use-module (guix search-paths)
+ #:use-module (guix profiles)
+ #:use-module (guix channels)
+ #:use-module (guix monads)
+ #:use-module (guix store)
+ #:use-module (guix derivations)
+ #:use-module (guix base32)
+ #:use-module (gcrypt hash)
+ #:autoload (guix cache) (maybe-remove-expired-cache-entries)
+ #:autoload (guix ui) (show-what-to-build*)
+ #:autoload (guix build utils) (mkdir-p)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
+ #:autoload (ice-9 ftw) (scandir)
#:use-module (ice-9 match)
#:use-module (ice-9 popen)
+ #:use-module (ice-9 vlist)
+ #:use-module (ice-9 binary-ports)
+ #:use-module ((rnrs bytevectors) #:select (string->utf8))
#:export (inferior?
open-inferior
close-inferior
inferior-eval
inferior-object?
+ inferior-packages
+ lookup-inferior-packages
+
inferior-package?
inferior-package-name
inferior-package-version
-
- inferior-packages
inferior-package-synopsis
inferior-package-description
inferior-package-home-page
- inferior-package-location))
+ inferior-package-location
+ inferior-package-inputs
+ inferior-package-native-inputs
+ inferior-package-propagated-inputs
+ inferior-package-transitive-propagated-inputs
+ inferior-package-native-search-paths
+ inferior-package-transitive-native-search-paths
+ inferior-package-search-paths
+ inferior-package-derivation
+
+ inferior-package->manifest-entry
+
+ %inferior-cache-directory
+ inferior-for-channels))
;;; Commentary:
;;;
@@ -48,11 +92,13 @@
;; Inferior Guix process.
(define-record-type <inferior>
- (inferior pid socket version)
+ (inferior pid socket version packages table)
inferior?
(pid inferior-pid)
(socket inferior-socket)
- (version inferior-version)) ;REPL protocol version
+ (version inferior-version) ;REPL protocol version
+ (packages inferior-package-promise) ;promise of inferior packages
+ (table inferior-package-table)) ;promise of vhash
(define (inferior-pipe directory command)
"Return an input/output pipe on the Guix instance in DIRECTORY. This runs
@@ -96,9 +142,12 @@ equivalent. Return #f if the inferior could not be launched."
(match (read pipe)
(('repl-version 0 rest ...)
- (let ((result (inferior 'pipe pipe (cons 0 rest))))
+ (letrec ((result (inferior 'pipe pipe (cons 0 rest)
+ (delay (%inferior-packages result))
+ (delay (%inferior-package-table result)))))
(inferior-eval '(use-modules (guix)) result)
(inferior-eval '(use-modules (gnu)) result)
+ (inferior-eval '(use-modules (ice-9 match)) result)
(inferior-eval '(define %package-table (make-hash-table))
result)
result))
@@ -123,8 +172,7 @@ equivalent. Return #f if the inferior could not be launched."
(set-record-type-printer! <inferior-object> write-inferior-object)
-(define (inferior-eval exp inferior)
- "Evaluate EXP in INFERIOR."
+(define (read-inferior-response inferior)
(define sexp->object
(match-lambda
(('value value)
@@ -132,14 +180,21 @@ equivalent. Return #f if the inferior could not be launched."
(('non-self-quoting address string)
(inferior-object address string))))
- (write exp (inferior-socket inferior))
- (newline (inferior-socket inferior))
(match (read (inferior-socket inferior))
(('values objects ...)
(apply values (map sexp->object objects)))
(('exception key objects ...)
(apply throw key (map sexp->object objects)))))
+(define (send-inferior-request exp inferior)
+ (write exp (inferior-socket inferior))
+ (newline (inferior-socket inferior)))
+
+(define (inferior-eval exp inferior)
+ "Evaluate EXP in INFERIOR."
+ (send-inferior-request exp inferior)
+ (read-inferior-response inferior))
+
;;;
;;; Inferior packages.
@@ -162,8 +217,8 @@ equivalent. Return #f if the inferior could not be launched."
(set-record-type-printer! <inferior-package> write-inferior-package)
-(define (inferior-packages inferior)
- "Return the list of packages known to INFERIOR."
+(define (%inferior-packages inferior)
+ "Compute the list of inferior packages from INFERIOR."
(let ((result (inferior-eval
'(fold-packages (lambda (package result)
(let ((id (object-address package)))
@@ -179,6 +234,33 @@ equivalent. Return #f if the inferior could not be launched."
(inferior-package inferior name version id)))
result)))
+(define (inferior-packages inferior)
+ "Return the list of packages known to INFERIOR."
+ (force (inferior-package-promise inferior)))
+
+(define (%inferior-package-table inferior)
+ "Compute a package lookup table for INFERIOR."
+ (fold (lambda (package table)
+ (vhash-cons (inferior-package-name package) package
+ table))
+ vlist-null
+ (inferior-packages inferior)))
+
+(define* (lookup-inferior-packages inferior name #:optional version)
+ "Return the sorted list of inferior packages matching NAME in INFERIOR, with
+highest version numbers first. If VERSION is true, return only packages with
+a version number prefixed by VERSION."
+ ;; This is the counterpart of 'find-packages-by-name'.
+ (sort (filter (lambda (package)
+ (or (not version)
+ (version-prefix? version
+ (inferior-package-version package))))
+ (vhash-fold* cons '() name
+ (force (inferior-package-table inferior))))
+ (lambda (p1 p2)
+ (version>? (inferior-package-version p1)
+ (inferior-package-version p2)))))
+
(define (inferior-package-field package getter)
"Return the field of PACKAGE, an inferior package, accessed with GETTER."
(let ((inferior (inferior-package-inferior package))
@@ -216,3 +298,261 @@ record."
(location->source-properties
loc)))
package-location))))
+
+(define (inferior-package-input-field package field)
+ "Return the input field FIELD (e.g., 'native-inputs') of PACKAGE, an
+inferior package."
+ (define field*
+ `(compose (lambda (inputs)
+ (map (match-lambda
+ ;; XXX: Origins are not handled.
+ ((label (? package? package) rest ...)
+ (let ((id (object-address package)))
+ (hashv-set! %package-table id package)
+ `(,label (package ,id
+ ,(package-name package)
+ ,(package-version package))
+ ,@rest)))
+ (x
+ x))
+ inputs))
+ ,field))
+
+ (define inputs
+ (inferior-package-field package field*))
+
+ (define inferior
+ (inferior-package-inferior package))
+
+ (map (match-lambda
+ ((label ('package id name version) . rest)
+ ;; XXX: eq?-ness of inferior packages is not preserved here.
+ `(,label ,(inferior-package inferior name version id)
+ ,@rest))
+ (x x))
+ inputs))
+
+(define inferior-package-inputs
+ (cut inferior-package-input-field <> 'package-inputs))
+
+(define inferior-package-native-inputs
+ (cut inferior-package-input-field <> 'package-native-inputs))
+
+(define inferior-package-propagated-inputs
+ (cut inferior-package-input-field <> 'package-propagated-inputs))
+
+(define inferior-package-transitive-propagated-inputs
+ (cut inferior-package-input-field <> 'package-transitive-propagated-inputs))
+
+(define (%inferior-package-search-paths package field)
+ "Return the list of search path specificiations of PACKAGE, an inferior
+package."
+ (define paths
+ (inferior-package-field package
+ `(compose (lambda (paths)
+ (map (@ (guix search-paths)
+ search-path-specification->sexp)
+ paths))
+ ,field)))
+
+ (map sexp->search-path-specification paths))
+
+(define inferior-package-native-search-paths
+ (cut %inferior-package-search-paths <> 'package-native-search-paths))
+
+(define inferior-package-search-paths
+ (cut %inferior-package-search-paths <> 'package-search-paths))
+
+(define inferior-package-transitive-native-search-paths
+ (cut %inferior-package-search-paths <> 'package-transitive-native-search-paths))
+
+(define (proxy client backend) ;adapted from (guix ssh)
+ "Proxy communication between CLIENT and BACKEND until CLIENT closes the
+connection, at which point CLIENT is closed (both CLIENT and BACKEND must be
+input/output ports.)"
+ (define (select* read write except)
+ ;; This is a workaround for <https://bugs.gnu.org/30365> in Guile < 2.2.4:
+ ;; since 'select' sometimes returns non-empty sets for no good reason,
+ ;; call 'select' a second time with a zero timeout to filter out incorrect
+ ;; replies.
+ (match (select read write except)
+ ((read write except)
+ (select read write except 0))))
+
+ ;; Use buffered ports so that 'get-bytevector-some' returns up to the
+ ;; whole buffer like read(2) would--see <https://bugs.gnu.org/30066>.
+ (setvbuf client _IOFBF 65536)
+ (setvbuf backend _IOFBF 65536)
+
+ (let loop ()
+ (match (select* (list client backend) '() '())
+ ((reads () ())
+ (when (memq client reads)
+ (match (get-bytevector-some client)
+ ((? eof-object?)
+ (close-port client))
+ (bv
+ (put-bytevector backend bv)
+ (force-output backend))))
+ (when (memq backend reads)
+ (match (get-bytevector-some backend)
+ (bv
+ (put-bytevector client bv)
+ (force-output client))))
+ (unless (port-closed? client)
+ (loop))))))
+
+(define* (inferior-package-derivation store package
+ #:optional
+ (system (%current-system))
+ #:key target)
+ "Return the derivation for PACKAGE, an inferior package, built for SYSTEM
+and cross-built for TARGET if TARGET is true. The inferior corresponding to
+PACKAGE must be live."
+ ;; Create a named socket in /tmp and let the inferior of PACKAGE connect to
+ ;; it and use it as its store. This ensures the inferior uses the same
+ ;; store, with the same options, the same per-session GC roots, etc.
+ (call-with-temporary-directory
+ (lambda (directory)
+ (chmod directory #o700)
+ (let* ((name (string-append directory "/inferior"))
+ (socket (socket AF_UNIX SOCK_STREAM 0))
+ (inferior (inferior-package-inferior package))
+ (major (nix-server-major-version store))
+ (minor (nix-server-minor-version store))
+ (proto (logior major minor)))
+ (bind socket AF_UNIX name)
+ (listen socket 1024)
+ (send-inferior-request
+ `(let ((socket (socket AF_UNIX SOCK_STREAM 0)))
+ (connect socket AF_UNIX ,name)
+
+ ;; 'port->connection' appeared in June 2018 and we can hardly
+ ;; emulate it on older versions. Thus fall back to
+ ;; 'open-connection', at the risk of talking to the wrong daemon or
+ ;; having our build result reclaimed (XXX).
+ (let* ((store (if (defined? 'port->connection)
+ (port->connection socket #:version ,proto)
+ (open-connection)))
+ (package (hashv-ref %package-table
+ ,(inferior-package-id package)))
+ (drv ,(if target
+ `(package-cross-derivation store package
+ ,target
+ ,system)
+ `(package-derivation store package
+ ,system))))
+ (close-connection store)
+ (close-port socket)
+ (derivation-file-name drv)))
+ inferior)
+ (match (accept socket)
+ ((client . address)
+ (proxy client (nix-server-socket store))))
+ (close-port socket)
+ (read-derivation-from-file (read-inferior-response inferior))))))
+
+(define inferior-package->derivation
+ (store-lift inferior-package-derivation))
+
+(define-gexp-compiler (package-compiler (package <inferior-package>) system
+ target)
+ ;; Compile PACKAGE for SYSTEM, optionally cross-building for TARGET.
+ (inferior-package->derivation package system #:target target))
+
+
+;;;
+;;; Manifest entries.
+;;;
+
+(define* (inferior-package->manifest-entry package
+ #:optional (output "out")
+ #:key (parent (delay #f))
+ (properties '()))
+ "Return a manifest entry for the OUTPUT of package PACKAGE."
+ ;; For each dependency, keep a promise pointing to its "parent" entry.
+ (letrec* ((deps (map (match-lambda
+ ((label package)
+ (inferior-package->manifest-entry package
+ #:parent (delay entry)))
+ ((label package output)
+ (inferior-package->manifest-entry package output
+ #:parent (delay entry))))
+ (inferior-package-propagated-inputs package)))
+ (entry (manifest-entry
+ (name (inferior-package-name package))
+ (version (inferior-package-version package))
+ (output output)
+ (item package)
+ (dependencies (delete-duplicates deps))
+ (search-paths
+ (inferior-package-transitive-native-search-paths package))
+ (parent parent)
+ (properties properties))))
+ entry))
+
+
+;;;
+;;; Cached inferiors.
+;;;
+
+(define %inferior-cache-directory
+ ;; Directory for cached inferiors (GC roots).
+ (make-parameter (string-append (cache-directory #:ensure? #f)
+ "/inferiors")))
+
+(define* (inferior-for-channels channels
+ #:key
+ (cache-directory (%inferior-cache-directory))
+ (ttl (* 3600 24 30)))
+ "Return an inferior for CHANNELS, a list of channels. Use the cache at
+CACHE-DIRECTORY, where entries can be reclaimed after TTL seconds. This
+procedure opens a new connection to the build daemon.
+
+This is a convenience procedure that people may use in manifests passed to
+'guix package -m', for instance."
+ (with-store store
+ (let ()
+ (define instances
+ (latest-channel-instances store channels))
+
+ (define key
+ (bytevector->base32-string
+ (sha256
+ (string->utf8
+ (string-concatenate (map channel-instance-commit instances))))))
+
+ (define cached
+ (string-append cache-directory "/" key))
+
+ (define (base32-encoded-sha256? str)
+ (= (string-length str) 52))
+
+ (define (cache-entries directory)
+ (map (lambda (file)
+ (string-append directory "/" file))
+ (scandir directory base32-encoded-sha256?)))
+
+ (define symlink*
+ (lift2 symlink %store-monad))
+
+ (define add-indirect-root*
+ (store-lift add-indirect-root))
+
+ (mkdir-p cache-directory)
+ (maybe-remove-expired-cache-entries cache-directory
+ cache-entries
+ #:entry-expiration
+ (file-expiration-time ttl))
+
+ (if (file-exists? cached)
+ (open-inferior cached)
+ (run-with-store store
+ (mlet %store-monad ((profile
+ (channel-instances->derivation instances)))
+ (mbegin %store-monad
+ (show-what-to-build* (list profile))
+ (built-derivations (list profile))
+ (symlink* (derivation->output-path profile) cached)
+ (add-indirect-root* cached)
+ (return (open-inferior cached)))))))))
diff --git a/guix/profiles.scm b/guix/profiles.scm
index 8acfcff8c1..89e92ea2ba 100644
--- a/guix/profiles.scm
+++ b/guix/profiles.scm
@@ -28,7 +28,8 @@
#:use-module ((guix config) #:select (%state-directory))
#:use-module ((guix utils) #:hide (package-name->name+version))
#:use-module ((guix build utils)
- #:select (package-name->name+version))
+ #:select (package-name->name+version mkdir-p))
+ #:use-module (guix i18n)
#:use-module (guix records)
#:use-module (guix packages)
#:use-module (guix derivations)
@@ -127,6 +128,7 @@
%user-profile-directory
%profile-directory
%current-profile
+ ensure-profile-directory
canonicalize-profile
user-friendly-profile))
@@ -314,12 +316,31 @@ file name."
"Return a list of manifest entries, one for each item listed in PACKAGES.
Elements of PACKAGES can be either package objects or package/string tuples
denoting a specific output of a package."
+ (define inferiors-loaded?
+ ;; This hack allows us to provide seamless integration for inferior
+ ;; packages while not having a hard dependency on (guix inferior).
+ (resolve-module '(guix inferior) #f #f #:ensure #f))
+
+ (define (inferior->entry)
+ (module-ref (resolve-interface '(guix inferior))
+ 'inferior-package->manifest-entry))
+
(manifest
(map (match-lambda
- ((package output)
- (package->manifest-entry package output))
- ((? package? package)
- (package->manifest-entry package)))
+ ((package output)
+ (package->manifest-entry package output))
+ ((? package? package)
+ (package->manifest-entry package))
+ ((thing output)
+ (if inferiors-loaded?
+ ((inferior->entry) thing output)
+ (throw 'wrong-type-arg 'packages->manifest
+ "Wrong package object: ~S" (list thing) (list thing))))
+ (thing
+ (if inferiors-loaded?
+ ((inferior->entry) thing)
+ (throw 'wrong-type-arg 'packages->manifest
+ "Wrong package object: ~S" (list thing) (list thing)))))
packages)))
(define (manifest->gexp manifest)
@@ -1230,7 +1251,7 @@ the entries in MANIFEST."
(define config.scm
(scheme-file "config.scm"
#~(begin
- (define-module (guix config)
+ (define-module #$'(guix config) ;placate Geiser
#:export (%libz))
(define %libz
@@ -1591,28 +1612,73 @@ because the NUMBER is zero.)"
;; coexist with Nix profiles.
(string-append %profile-directory "/guix-profile"))
-(define (canonicalize-profile profile)
- "If PROFILE is %USER-PROFILE-DIRECTORY, return %CURRENT-PROFILE. Otherwise
-return PROFILE unchanged. The goal is to treat '-p ~/.guix-profile' as if
-'-p' was omitted." ; see <http://bugs.gnu.org/17939>
+(define (ensure-profile-directory)
+ "Attempt to create /…/profiles/per-user/$USER if needed."
+ (let ((s (stat %profile-directory #f)))
+ (unless (and s (eq? 'directory (stat:type s)))
+ (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.
+ (raise (condition
+ (&message
+ (message
+ (format #f
+ (G_ "while creating directory `~a': ~a")
+ %profile-directory
+ (strerror (system-error-errno args)))))
+ (&fix-hint
+ (hint
+ (format #f (G_ "Please create the @file{~a} directory, \
+with you as the owner.")
+ %profile-directory))))))))
+
+ ;; Bail out if it's not owned by the user.
+ (unless (or (not s) (= (stat:uid s) (getuid)))
+ (raise (condition
+ (&message
+ (message
+ (format #f (G_ "directory `~a' is not owned by you")
+ %profile-directory)))
+ (&fix-hint
+ (hint
+ (format #f (G_ "Please change the owner of @file{~a} \
+to user ~s.")
+ %profile-directory (or (getenv "USER")
+ (getenv "LOGNAME")
+ (getuid))))))))))
- ;; Trim trailing slashes so that the basename comparison below works as
- ;; intended.
+(define (canonicalize-profile profile)
+ "If PROFILE points to a profile in %PROFILE-DIRECTORY, return that.
+Otherwise return PROFILE unchanged. The goal is to treat '-p ~/.guix-profile'
+as if '-p' was omitted." ; see <http://bugs.gnu.org/17939>
+ ;; Trim trailing slashes so 'readlink' can do its job.
(let ((profile (string-trim-right profile #\/)))
- (if (and %user-profile-directory
- (string=? (canonicalize-path (dirname profile))
- (dirname %user-profile-directory))
- (string=? (basename profile) (basename %user-profile-directory)))
- %current-profile
- profile)))
+ (catch 'system-error
+ (lambda ()
+ (let ((target (readlink profile)))
+ (if (string=? (dirname target) %profile-directory)
+ target
+ profile)))
+ (const profile))))
+
+(define %known-shorthand-profiles
+ ;; Known shorthand forms for profiles that the user manipulates.
+ (list (string-append (config-directory #:ensure? #f) "/current")
+ %user-profile-directory))
(define (user-friendly-profile profile)
- "Return either ~/.guix-profile if that's what PROFILE refers to, directly or
-indirectly, or PROFILE."
- (if (and %user-profile-directory
- (false-if-exception
- (string=? (readlink %user-profile-directory) profile)))
- %user-profile-directory
+ "Return either ~/.guix-profile or ~/.config/guix/current if that's what
+PROFILE refers to, directly or indirectly, or PROFILE."
+ (or (find (lambda (shorthand)
+ (and shorthand
+ (let ((target (false-if-exception
+ (readlink shorthand))))
+ (and target (string=? target profile)))))
+ %known-shorthand-profiles)
profile))
;;; profiles.scm ends here
diff --git a/guix/progress.scm b/guix/progress.scm
index c9c3cd12a0..9da667a027 100644
--- a/guix/progress.scm
+++ b/guix/progress.scm
@@ -1,7 +1,7 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017 Sou Bunnbu <iyzsong@gmail.com>
;;; Copyright © 2015 Steve Sprang <scs@stevesprang.com>
-;;; Copyright © 2017 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2017, 2018 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -38,7 +38,11 @@
progress-reporter/silent
progress-reporter/file
progress-reporter/bar
+ progress-reporter/trace
+ display-download-progress
+ erase-current-line
+ progress-bar
byte-count->string
current-terminal-columns
@@ -70,11 +74,11 @@ stopped."
(($ <progress-reporter> start report stop)
(start))))
-(define (progress-reporter-report! reporter)
+(define (progress-reporter-report! reporter . args)
"Low-level procedure to lead REPORTER to emit a report."
(match reporter
(($ <progress-reporter> start report stop)
- (report))))
+ (apply report args))))
(define (stop-progress-reporter! reporter)
"Low-level procedure to stop REPORTER."
@@ -183,6 +187,46 @@ width of the bar is BAR-WIDTH."
move the cursor to the beginning of the line."
(display "\r\x1b[K" port))
+(define* (display-download-progress file size
+ #:key
+ start-time (transferred 0)
+ (log-port (current-error-port)))
+ "Write the progress report to LOG-PORT. Use START-TIME (a SRFI-19 time
+object) and TRANSFERRED (a total number of bytes) to determine the
+throughput."
+ (define elapsed
+ (duration->seconds
+ (time-difference (current-time time-monotonic) start-time)))
+ (if (number? size)
+ (let* ((% (* 100.0 (/ transferred size)))
+ (throughput (/ transferred elapsed))
+ (left (format #f " ~a ~a" file
+ (byte-count->string size)))
+ (right (format #f "~a/s ~a ~a~6,1f%"
+ (byte-count->string throughput)
+ (seconds->string elapsed)
+ (progress-bar %) %)))
+ (erase-current-line log-port)
+ (display (string-pad-middle left right
+ (current-terminal-columns))
+ log-port)
+ (force-output log-port))
+ (let* ((throughput (/ transferred elapsed))
+ (left (format #f " ~a" file))
+ (right (format #f "~a/s ~a | ~a transferred"
+ (byte-count->string throughput)
+ (seconds->string elapsed)
+ (byte-count->string transferred))))
+ (erase-current-line log-port)
+ (display (string-pad-middle left right
+ (current-terminal-columns))
+ log-port)
+ (force-output log-port))))
+
+(define %progress-interval
+ ;; Default interval between subsequent outputs for rate-limited displays.
+ (make-time time-monotonic 200000000 0))
+
(define* (progress-reporter/file file size
#:optional (log-port (current-output-port))
#:key (abbreviation basename))
@@ -192,44 +236,16 @@ ABBREVIATION used to shorten FILE for display."
(let ((start-time (current-time time-monotonic))
(transferred 0))
(define (render)
- "Write the progress report to LOG-PORT."
- (define elapsed
- (duration->seconds
- (time-difference (current-time time-monotonic) start-time)))
- (if (number? size)
- (let* ((% (* 100.0 (/ transferred size)))
- (throughput (/ transferred elapsed))
- (left (format #f " ~a ~a"
- (abbreviation file)
- (byte-count->string size)))
- (right (format #f "~a/s ~a ~a~6,1f%"
- (byte-count->string throughput)
- (seconds->string elapsed)
- (progress-bar %) %)))
- (erase-current-line log-port)
- (display (string-pad-middle left right
- (current-terminal-columns))
- log-port)
- (force-output log-port))
- (let* ((throughput (/ transferred elapsed))
- (left (format #f " ~a"
- (abbreviation file)))
- (right (format #f "~a/s ~a | ~a transferred"
- (byte-count->string throughput)
- (seconds->string elapsed)
- (byte-count->string transferred))))
- (erase-current-line log-port)
- (display (string-pad-middle left right
- (current-terminal-columns))
- log-port)
- (force-output log-port))))
+ (display-download-progress (abbreviation file) size
+ #:start-time start-time
+ #:transferred transferred
+ #:log-port log-port))
(progress-reporter
(start render)
;; Report the progress every 300ms or longer.
(report
- (let ((rate-limited-render
- (rate-limited render (make-time time-monotonic 300000000 0))))
+ (let ((rate-limited-render (rate-limited render %progress-interval)))
(lambda (value)
(set! transferred value)
(rate-limited-render))))
@@ -269,6 +285,37 @@ tasks is performed. Write PREFIX at the beginning of the line."
(newline port))
(force-output port)))))
+(define* (progress-reporter/trace file url size
+ #:optional (log-port (current-output-port)))
+ "Like 'progress-reporter/file', but instead of returning human-readable
+progress reports, write \"build trace\" lines to be processed elsewhere."
+ (define total 0) ;bytes transferred
+
+ (define (report-progress transferred)
+ (define message
+ (format #f "@ download-progress ~a ~a ~a ~a~%"
+ file url (or size "-") transferred))
+
+ (display message log-port) ;should be atomic
+ (flush-output-port log-port))
+
+ (progress-reporter
+ (start (lambda ()
+ (set! total 0)
+ (display (format #f "@ download-started ~a ~a ~a~%"
+ file url (or size "-"))
+ log-port)))
+ (report (let ((report (rate-limited report-progress %progress-interval)))
+ (lambda (transferred)
+ (set! total transferred)
+ (report transferred))))
+ (stop (lambda ()
+ (let ((size (or size total)))
+ (report-progress size)
+ (display (format #f "@ download-succeeded ~a ~a ~a~%"
+ file url size)
+ log-port))))))
+
;; TODO: replace '(@ (guix build utils) dump-port))'.
(define* (dump-port* in out
#:key (buffer-size 16384)
diff --git a/guix/scripts.scm b/guix/scripts.scm
index 4cbbbeb96f..98751bc812 100644
--- a/guix/scripts.scm
+++ b/guix/scripts.scm
@@ -26,6 +26,7 @@
#:use-module (guix monads)
#:use-module (guix packages)
#:use-module (guix derivations)
+ #:use-module ((guix profiles) #:select (%profile-directory))
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-19)
#:use-module (srfi srfi-37)
@@ -169,8 +170,7 @@ Show what and how will/would be built."
(define age
(match (false-if-not-found
- (lstat (string-append (config-directory #:ensure? #f)
- "/current")))
+ (lstat (string-append %profile-directory "/current-guix")))
(#f #f)
(stat (- (time-second (current-time time-utc))
(stat:mtime stat)))))
diff --git a/guix/scripts/build.scm b/guix/scripts/build.scm
index 9d38610633..13978abb77 100644
--- a/guix/scripts/build.scm
+++ b/guix/scripts/build.scm
@@ -45,6 +45,9 @@
#:use-module (srfi srfi-37)
#:autoload (gnu packages) (specification->package %package-module-path)
#:autoload (guix download) (download-to-store)
+ #:use-module (guix status)
+ #: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*
@@ -390,6 +393,10 @@ 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?)
+ #:print-extended-build-trace?
+ (assoc-ref opts 'print-extended-build-trace?)
+ #:multiplexed-build-output?
+ (assoc-ref opts 'multiplexed-build-output?)
#:verbosity (assoc-ref opts 'verbosity)))
(define set-build-options-from-command-line*
@@ -499,6 +506,8 @@ options handled by 'set-build-options-from-command-line', and listed in
(substitutes? . #t)
(build-hook? . #t)
(print-build-trace? . #t)
+ (print-extended-build-trace? . #t)
+ (multiplexed-build-output? . #t)
(verbosity . 0)))
(define (show-help)
@@ -617,7 +626,7 @@ 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))
+ (unless (or (derivation? x) (file-like? x) (gexp? x) (procedure? x))
(leave (G_ "~s: not something we can build~%") x)))
(define (ensure-list x)
@@ -694,6 +703,10 @@ package '~a' has no source~%")
(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)))))
((? gexp? gexp)
(list (run-with-store store
(mbegin %store-monad
@@ -733,11 +746,12 @@ needed."
;; Set the build options before we do anything else.
(set-build-options-from-command-line store opts)
- (parameterize ((current-build-output-port
+ (parameterize ((current-terminal-columns (terminal-columns))
+ (current-build-output-port
(if quiet?
(%make-void-port "w")
- (build-output-port #:verbose? #t
- #:port (duplicate-port (current-error-port) "w")))))
+ (build-event-output-port
+ (build-status-updater print-build-event)))))
(let* ((mode (assoc-ref opts 'build-mode))
(drv (options->derivations store opts))
(urls (map (cut string-append <> "/log")
diff --git a/guix/scripts/describe.scm b/guix/scripts/describe.scm
index c1a20fe26c..e59502076c 100644
--- a/guix/scripts/describe.scm
+++ b/guix/scripts/describe.scm
@@ -158,4 +158,4 @@ in the format specified by FMT."
(#f
(display-checkout-info format))
(profile
- (display-profile-info profile format))))))
+ (display-profile-info (canonicalize-profile profile) format))))))
diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm
index 1c04800e42..5965e3426e 100644
--- a/guix/scripts/environment.scm
+++ b/guix/scripts/environment.scm
@@ -21,6 +21,7 @@
(define-module (guix scripts environment)
#:use-module (guix ui)
#:use-module (guix store)
+ #:use-module (guix status)
#:use-module (guix grafts)
#:use-module (guix derivations)
#:use-module (guix packages)
@@ -173,6 +174,9 @@ COMMAND or an interactive shell in that environment.\n"))
(substitutes? . #t)
(build-hook? . #t)
(graft? . #t)
+ (print-build-trace? . #t)
+ (print-extended-build-trace? . #t)
+ (multiplexed-build-output? . #t)
(verbosity . 0)))
(define (tag-package-arg opts arg)
@@ -661,59 +665,60 @@ message if any test fails."
(leave (G_ "'--user' cannot be used without '--container'~%")))
(with-store store
- (set-build-options-from-command-line store opts)
-
- ;; Use the bootstrap Guile when requested.
- (parameterize ((%graft? (assoc-ref opts 'graft?))
- (%guile-for-build
- (package-derivation
- store
- (if bootstrap?
- %bootstrap-guile
- (canonical-package guile-2.2)))))
- (run-with-store store
- ;; Containers need a Bourne shell at /bin/sh.
- (mlet* %store-monad ((bash (environment-bash container?
- bootstrap?
- system))
- (prof-drv (manifest->derivation
- manifest system bootstrap?))
- (profile -> (derivation->output-path prof-drv))
- (gc-root -> (assoc-ref opts 'gc-root)))
-
- ;; First build the inputs. This is necessary even for
- ;; --search-paths. Additionally, we might need to build bash for
- ;; a container.
- (mbegin %store-monad
- (build-environment (if (derivation? bash)
- (list prof-drv bash)
- (list prof-drv))
- opts)
- (mwhen gc-root
- (register-gc-root profile gc-root))
-
- (cond
- ((assoc-ref opts 'dry-run?)
- (return #t))
- ((assoc-ref opts 'search-paths)
- (show-search-paths profile manifest #:pure? pure?)
- (return #t))
- (container?
- (let ((bash-binary
- (if bootstrap?
- bash
- (string-append (derivation->output-path bash)
- "/bin/sh"))))
- (launch-environment/container #:command command
- #:bash bash-binary
- #:user user
- #:user-mappings mappings
- #:profile profile
- #:manifest manifest
- #:link-profile? link-prof?
- #:network? network?)))
- (else
- (return
- (exit/status
- (launch-environment/fork command profile manifest
- #:pure? pure?)))))))))))))
+ (with-status-report print-build-event
+ (set-build-options-from-command-line store opts)
+
+ ;; Use the bootstrap Guile when requested.
+ (parameterize ((%graft? (assoc-ref opts 'graft?))
+ (%guile-for-build
+ (package-derivation
+ store
+ (if bootstrap?
+ %bootstrap-guile
+ (canonical-package guile-2.2)))))
+ (run-with-store store
+ ;; Containers need a Bourne shell at /bin/sh.
+ (mlet* %store-monad ((bash (environment-bash container?
+ bootstrap?
+ system))
+ (prof-drv (manifest->derivation
+ manifest system bootstrap?))
+ (profile -> (derivation->output-path prof-drv))
+ (gc-root -> (assoc-ref opts 'gc-root)))
+
+ ;; First build the inputs. This is necessary even for
+ ;; --search-paths. Additionally, we might need to build bash for
+ ;; a container.
+ (mbegin %store-monad
+ (build-environment (if (derivation? bash)
+ (list prof-drv bash)
+ (list prof-drv))
+ opts)
+ (mwhen gc-root
+ (register-gc-root profile gc-root))
+
+ (cond
+ ((assoc-ref opts 'dry-run?)
+ (return #t))
+ ((assoc-ref opts 'search-paths)
+ (show-search-paths profile manifest #:pure? pure?)
+ (return #t))
+ (container?
+ (let ((bash-binary
+ (if bootstrap?
+ bash
+ (string-append (derivation->output-path bash)
+ "/bin/sh"))))
+ (launch-environment/container #:command command
+ #:bash bash-binary
+ #:user user
+ #:user-mappings mappings
+ #:profile profile
+ #:manifest manifest
+ #:link-profile? link-prof?
+ #:network? network?)))
+ (else
+ (return
+ (exit/status
+ (launch-environment/fork command profile manifest
+ #:pure? pure?))))))))))))))
diff --git a/guix/scripts/import/cran.scm b/guix/scripts/import/cran.scm
index 30ae6d4342..794fb710cd 100644
--- a/guix/scripts/import/cran.scm
+++ b/guix/scripts/import/cran.scm
@@ -47,6 +47,8 @@ Import and convert the CRAN package for PACKAGE-NAME.\n"))
(display (G_ "
-h, --help display this help and exit"))
(display (G_ "
+ -r, --recursive import packages recursively"))
+ (display (G_ "
-V, --version display version information and exit"))
(newline)
(show-bug-report-information))
diff --git a/guix/scripts/pack.scm b/guix/scripts/pack.scm
index 28462d9b8d..13aa8923cd 100644
--- a/guix/scripts/pack.scm
+++ b/guix/scripts/pack.scm
@@ -25,6 +25,7 @@
#:use-module (guix gexp)
#:use-module (guix utils)
#:use-module (guix store)
+ #:use-module (guix status)
#:use-module (guix grafts)
#:use-module (guix monads)
#:use-module (guix modules)
@@ -197,8 +198,11 @@ added to the pack."
(with-directory-excursion %root
(exit
(zero? (apply system* "tar"
- "-I"
- (string-join '#+(compressor-command compressor))
+ #+@(if (compressor-command compressor)
+ #~("-I"
+ (string-join
+ '#+(compressor-command compressor)))
+ #~())
"--format=gnu"
;; Avoid non-determinism in the archive. Use
@@ -538,6 +542,9 @@ please email '~a'~%")
(substitutes? . #t)
(build-hook? . #t)
(graft? . #t)
+ (print-build-trace? . #t)
+ (print-extended-build-trace? . #t)
+ (multiplexed-build-output? . #t)
(verbosity . 0)
(symlinks . ())
(compressor . ,(first %compressors))))
@@ -684,72 +691,73 @@ Create a bundle of PACKAGE.\n"))
(with-error-handling
(with-store store
- ;; Set the build options before we do anything else.
- (set-build-options-from-command-line store opts)
-
- (parameterize ((%graft? (assoc-ref opts 'graft?))
- (%guile-for-build (package-derivation
- store
- (if (assoc-ref opts 'bootstrap?)
- %bootstrap-guile
- (canonical-package guile-2.2))
- (assoc-ref opts 'system)
- #:graft? (assoc-ref opts 'graft?))))
- (let* ((dry-run? (assoc-ref opts 'dry-run?))
- (relocatable? (assoc-ref opts 'relocatable?))
- (manifest (let ((manifest (manifest-from-args store opts)))
- ;; Note: We cannot honor '--bootstrap' here because
- ;; 'glibc-bootstrap' lacks 'libc.a'.
- (if relocatable?
- (map-manifest-entries wrapped-package manifest)
- manifest)))
- (pack-format (assoc-ref opts 'format))
- (name (string-append (symbol->string pack-format)
- "-pack"))
- (target (assoc-ref opts 'target))
- (bootstrap? (assoc-ref opts 'bootstrap?))
- (compressor (if bootstrap?
- bootstrap-xz
- (assoc-ref opts 'compressor)))
- (archiver (if (equal? pack-format 'squashfs)
- squashfs-tools-next
- (if bootstrap?
- %bootstrap-coreutils&co
- tar)))
- (symlinks (assoc-ref opts 'symlinks))
- (build-image (match (assq-ref %formats pack-format)
- ((? procedure? proc) proc)
- (#f
- (leave (G_ "~a: unknown pack format~%")
- pack-format))))
- (localstatedir? (assoc-ref opts 'localstatedir?)))
- (run-with-store store
- (mlet* %store-monad ((profile (profile-derivation
- manifest
- #:relative-symlinks? relocatable?
- #:hooks (if bootstrap?
- '()
- %default-profile-hooks)
- #:locales? (not bootstrap?)
- #:target target))
- (drv (build-image name profile
- #:target
- target
- #:compressor
- compressor
- #:symlinks
- symlinks
- #:localstatedir?
- localstatedir?
- #:archiver
- archiver)))
- (mbegin %store-monad
- (show-what-to-build* (list drv)
- #:use-substitutes?
- (assoc-ref opts 'substitutes?)
- #:dry-run? dry-run?)
- (munless dry-run?
- (built-derivations (list drv))
- (return (format #t "~a~%"
- (derivation->output-path drv))))))
- #:system (assoc-ref opts 'system)))))))
+ (with-status-report print-build-event
+ ;; Set the build options before we do anything else.
+ (set-build-options-from-command-line store opts)
+
+ (parameterize ((%graft? (assoc-ref opts 'graft?))
+ (%guile-for-build (package-derivation
+ store
+ (if (assoc-ref opts 'bootstrap?)
+ %bootstrap-guile
+ (canonical-package guile-2.2))
+ (assoc-ref opts 'system)
+ #:graft? (assoc-ref opts 'graft?))))
+ (let* ((dry-run? (assoc-ref opts 'dry-run?))
+ (relocatable? (assoc-ref opts 'relocatable?))
+ (manifest (let ((manifest (manifest-from-args store opts)))
+ ;; Note: We cannot honor '--bootstrap' here because
+ ;; 'glibc-bootstrap' lacks 'libc.a'.
+ (if relocatable?
+ (map-manifest-entries wrapped-package manifest)
+ manifest)))
+ (pack-format (assoc-ref opts 'format))
+ (name (string-append (symbol->string pack-format)
+ "-pack"))
+ (target (assoc-ref opts 'target))
+ (bootstrap? (assoc-ref opts 'bootstrap?))
+ (compressor (if bootstrap?
+ bootstrap-xz
+ (assoc-ref opts 'compressor)))
+ (archiver (if (equal? pack-format 'squashfs)
+ squashfs-tools-next
+ (if bootstrap?
+ %bootstrap-coreutils&co
+ tar)))
+ (symlinks (assoc-ref opts 'symlinks))
+ (build-image (match (assq-ref %formats pack-format)
+ ((? procedure? proc) proc)
+ (#f
+ (leave (G_ "~a: unknown pack format~%")
+ pack-format))))
+ (localstatedir? (assoc-ref opts 'localstatedir?)))
+ (run-with-store store
+ (mlet* %store-monad ((profile (profile-derivation
+ manifest
+ #:relative-symlinks? relocatable?
+ #:hooks (if bootstrap?
+ '()
+ %default-profile-hooks)
+ #:locales? (not bootstrap?)
+ #:target target))
+ (drv (build-image name profile
+ #:target
+ target
+ #:compressor
+ compressor
+ #:symlinks
+ symlinks
+ #:localstatedir?
+ localstatedir?
+ #:archiver
+ archiver)))
+ (mbegin %store-monad
+ (show-what-to-build* (list drv)
+ #:use-substitutes?
+ (assoc-ref opts 'substitutes?)
+ #:dry-run? dry-run?)
+ (munless dry-run?
+ (built-derivations (list drv))
+ (return (format #t "~a~%"
+ (derivation->output-path drv))))))
+ #:system (assoc-ref opts 'system))))))))
diff --git a/guix/scripts/package.scm b/guix/scripts/package.scm
index c3ed2ac935..5d146b8427 100644
--- a/guix/scripts/package.scm
+++ b/guix/scripts/package.scm
@@ -24,6 +24,7 @@
(define-module (guix scripts package)
#:use-module (guix ui)
+ #:use-module (guix status)
#:use-module (guix store)
#:use-module (guix grafts)
#:use-module (guix derivations)
@@ -67,50 +68,14 @@
(define (ensure-default-profile)
"Ensure the default profile symlink and directory exist and are writable."
-
- (define (rtfm)
- (format (current-error-port)
- (G_ "Try \"info '(guix) Invoking guix package'\" for \
-more information.~%"))
- (exit 1))
+ (ensure-profile-directory)
;; Create ~/.guix-profile if it doesn't exist yet.
(when (and %user-profile-directory
%current-profile
(not (false-if-exception
(lstat %user-profile-directory))))
- (symlink %current-profile %user-profile-directory))
-
- (let ((s (stat %profile-directory #f)))
- ;; Attempt to create /…/profiles/per-user/$USER if needed.
- (unless (and s (eq? 'directory (stat:type s)))
- (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)
- (G_ "error: while creating directory `~a': ~a~%")
- %profile-directory
- (strerror (system-error-errno args)))
- (format (current-error-port)
- (G_ "Please create the `~a' directory, with you as the owner.~%")
- %profile-directory)
- (rtfm))))
-
- ;; Bail out if it's not owned by the user.
- (unless (or (not s) (= (stat:uid s) (getuid)))
- (format (current-error-port)
- (G_ "error: directory `~a' is not owned by you~%")
- %profile-directory)
- (format (current-error-port)
- (G_ "Please change the owner of `~a' to user ~s.~%")
- %profile-directory (or (getenv "USER")
- (getenv "LOGNAME")
- (getuid)))
- (rtfm))))
+ (symlink %current-profile %user-profile-directory)))
(define (delete-generations store profile generations)
"Delete GENERATIONS from PROFILE.
@@ -330,7 +295,9 @@ ENTRIES, a list of manifest entries, in the context of PROFILE."
(graft? . #t)
(substitutes? . #t)
(build-hook? . #t)
- (print-build-trace? . #t)))
+ (print-build-trace? . #t)
+ (print-extended-build-trace? . #t)
+ (multiplexed-build-output? . #t)))
(define (show-help)
(display (G_ "Usage: guix package [OPTION]...
@@ -941,15 +908,12 @@ processed, #f otherwise."
(or (process-query opts)
(parameterize ((%store (open-connection))
(%graft? (assoc-ref opts 'graft?)))
- (set-build-options-from-command-line (%store) opts)
-
- (parameterize ((%guile-for-build
- (package-derivation
- (%store)
- (if (assoc-ref opts 'bootstrap?)
- %bootstrap-guile
- (canonical-package guile-2.2))))
- (current-build-output-port
- (build-output-port #:verbose? verbose?
- #:port (duplicate-port (current-error-port) "w"))))
- (process-actions (%store) opts))))))
+ (with-status-report print-build-event/quiet
+ (set-build-options-from-command-line (%store) opts)
+ (parameterize ((%guile-for-build
+ (package-derivation
+ (%store)
+ (if (assoc-ref opts 'bootstrap?)
+ %bootstrap-guile
+ (canonical-package guile-2.2)))))
+ (process-actions (%store) opts)))))))
diff --git a/guix/scripts/perform-download.scm b/guix/scripts/perform-download.scm
index 18e2fc92f2..df787a9940 100644
--- a/guix/scripts/perform-download.scm
+++ b/guix/scripts/perform-download.scm
@@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2016, 2017 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2016, 2017, 2018 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -41,7 +41,8 @@
(module-use! module (resolve-interface '(guix base32)))
module))
-(define* (perform-download drv #:optional output)
+(define* (perform-download drv #:optional output
+ #:key print-build-trace?)
"Perform the download described by DRV, a fixed-output derivation, to
OUTPUT.
@@ -67,6 +68,7 @@ actual output is different from that when we're doing a 'bmCheck' or
;; We're invoked by the daemon, which gives us write access to OUTPUT.
(when (url-fetch url output
+ #:print-build-trace? print-build-trace?
#:mirrors (if mirrors
(call-with-input-file mirrors read)
'())
@@ -98,6 +100,11 @@ allows us to sidestep bootstrapping problems, such downloading the source code
of GnuTLS over HTTPS, before we have built GnuTLS. See
<http://bugs.gnu.org/22774>."
+ (define print-build-trace?
+ (match (getenv "_NIX_OPTIONS")
+ (#f #f)
+ (str (string-contains str "print-extended-build-trace=1"))))
+
;; This program must be invoked by guix-daemon under an unprivileged UID to
;; prevent things downloading from 'file:///etc/shadow' or arbitrary code
;; execution via the content-addressed mirror procedures. (That means we
@@ -107,10 +114,12 @@ of GnuTLS over HTTPS, before we have built GnuTLS. See
(((? derivation-path? drv) (? store-path? output))
(assert-low-privileges)
(perform-download (read-derivation-from-file drv)
- output))
+ output
+ #:print-build-trace? print-build-trace?))
(((? derivation-path? drv)) ;backward compatibility
(assert-low-privileges)
- (perform-download (read-derivation-from-file drv)))
+ (perform-download (read-derivation-from-file drv)
+ #:print-build-trace? print-build-trace?))
(("--version")
(show-version-and-exit))
(x
diff --git a/guix/scripts/pull.scm b/guix/scripts/pull.scm
index 10e1a99e54..188237aa90 100644
--- a/guix/scripts/pull.scm
+++ b/guix/scripts/pull.scm
@@ -20,6 +20,7 @@
(define-module (guix scripts pull)
#:use-module (guix ui)
#:use-module (guix utils)
+ #:use-module (guix status)
#:use-module (guix scripts)
#:use-module (guix store)
#:use-module (guix config)
@@ -61,6 +62,9 @@
`((system . ,(%current-system))
(substitutes? . #t)
(build-hook? . #t)
+ (print-build-trace? . #t)
+ (print-extended-build-trace? . #t)
+ (multiplexed-build-output? . #t)
(graft? . #t)
(verbosity . 0)))
@@ -180,9 +184,25 @@ Download and deploy the latest version of Guix.\n"))
(define (honor-x509-certificates store)
"Use the right X.509 certificates for Git checkouts over HTTPS."
- (let ((file (getenv "SSL_CERT_FILE"))
+ ;; On distros such as CentOS 7, /etc/ssl/certs contains only a couple of
+ ;; files (instead of all the certificates) among which "ca-bundle.crt". On
+ ;; other distros /etc/ssl/certs usually contains the whole set of
+ ;; certificates along with "ca-certificates.crt". Try to choose the right
+ ;; one.
+ (let ((file (letrec-syntax ((choose
+ (syntax-rules ()
+ ((_ file rest ...)
+ (let ((f file))
+ (if (and f (file-exists? f))
+ f
+ (choose rest ...))))
+ ((_)
+ #f))))
+ (choose (getenv "SSL_CERT_FILE")
+ "/etc/ssl/certs/ca-certificates.crt"
+ "/etc/ssl/certs/ca-bundle.crt")))
(directory (or (getenv "SSL_CERT_DIR") "/etc/ssl/certs")))
- (if (or (and file (file-exists? file))
+ (if (or file
(and=> (stat directory #f)
(lambda (st)
(> (stat:nlink st) 2))))
@@ -208,6 +228,60 @@ Download and deploy the latest version of Guix.\n"))
;;;
+;;; Profile.
+;;;
+
+(define %current-profile
+ ;; The "real" profile under /var/guix.
+ (string-append %profile-directory "/current-guix"))
+
+(define %user-profile-directory
+ ;; The user-friendly name of %CURRENT-PROFILE.
+ (string-append (config-directory #:ensure? #f) "/current"))
+
+(define (migrate-generations profile directory)
+ "Migrate the generations of PROFILE to DIRECTORY."
+ (format (current-error-port)
+ (G_ "Migrating profile generations to '~a'...~%")
+ %profile-directory)
+ (let ((current (generation-number profile)))
+ (for-each (lambda (generation)
+ (let ((source (generation-file-name profile generation))
+ (target (string-append directory "/current-guix-"
+ (number->string generation)
+ "-link")))
+ ;; Note: Don't use 'rename-file' as SOURCE and TARGET might
+ ;; live on different file systems.
+ (symlink (readlink source) target)
+ (delete-file source)))
+ (profile-generations profile))
+ (symlink (string-append "current-guix-"
+ (number->string current) "-link")
+ (string-append directory "/current-guix"))))
+
+(define (ensure-default-profile)
+ (ensure-profile-directory)
+
+ ;; In 0.15.0+ we'd create ~/.config/guix/current-[0-9]*-link symlinks. Move
+ ;; them to %PROFILE-DIRECTORY.
+ (unless (string=? %profile-directory
+ (dirname (canonicalize-profile %user-profile-directory)))
+ (migrate-generations %user-profile-directory %profile-directory))
+
+ ;; Make sure ~/.config/guix/current points to /var/guix/profiles/….
+ (let ((link %user-profile-directory))
+ (unless (equal? (false-if-exception (readlink link))
+ %current-profile)
+ (catch 'system-error
+ (lambda ()
+ (false-if-exception (delete-file link))
+ (symlink %current-profile link))
+ (lambda args
+ (leave (G_ "while creating symlink '~a': ~a~%")
+ link (strerror (system-error-errno args))))))))
+
+
+;;;
;;; Queries.
;;;
@@ -322,11 +396,8 @@ and ALIST2 differ, display HEADING upfront."
(display-new/upgraded-packages (package-alist gen1)
(package-alist gen2)))
-(define (process-query opts)
- "Process any query specified by OPTS."
- (define profile
- (string-append (config-directory) "/current"))
-
+(define (process-query opts profile)
+ "Process any query on PROFILE specified by OPTS."
(match (assoc-ref opts 'query)
(('list-generations pattern)
(define (list-generations profile numbers)
@@ -422,45 +493,45 @@ Use '~/.config/guix/channels.scm' instead."))
(list %default-options)))
(cache (string-append (cache-directory) "/pull"))
(channels (channel-list opts))
- (profile (or (assoc-ref opts 'profile)
- (string-append (config-directory) "/current"))))
-
+ (profile (or (assoc-ref opts 'profile) %current-profile)))
+ (ensure-default-profile)
(cond ((assoc-ref opts 'query)
- (process-query opts))
+ (process-query opts profile))
((assoc-ref opts 'dry-run?)
#t) ;XXX: not very useful
(else
(with-store store
- (parameterize ((%graft? (assoc-ref opts 'graft?))
- (%repository-cache-directory cache))
- (set-build-options-from-command-line store opts)
- (honor-x509-certificates store)
-
- (let ((instances (latest-channel-instances store channels)))
- (format (current-error-port)
- (N_ "Building from this channel:~%"
- "Building from these channels:~%"
- (length instances)))
- (for-each (lambda (instance)
- (let ((channel
- (channel-instance-channel instance)))
- (format (current-error-port)
- " ~10a~a\t~a~%"
- (channel-name channel)
- (channel-url channel)
- (string-take
- (channel-instance-commit instance)
- 7))))
- instances)
- (parameterize ((%guile-for-build
- (package-derivation
- store
- (if (assoc-ref opts 'bootstrap?)
- %bootstrap-guile
- (canonical-package guile-2.2)))))
- (run-with-store store
- (build-and-install instances profile
- #:verbose?
- (assoc-ref opts 'verbose?)))))))))))))
+ (with-status-report print-build-event
+ (parameterize ((%graft? (assoc-ref opts 'graft?))
+ (%repository-cache-directory cache))
+ (set-build-options-from-command-line store opts)
+ (honor-x509-certificates store)
+
+ (let ((instances (latest-channel-instances store channels)))
+ (format (current-error-port)
+ (N_ "Building from this channel:~%"
+ "Building from these channels:~%"
+ (length instances)))
+ (for-each (lambda (instance)
+ (let ((channel
+ (channel-instance-channel instance)))
+ (format (current-error-port)
+ " ~10a~a\t~a~%"
+ (channel-name channel)
+ (channel-url channel)
+ (string-take
+ (channel-instance-commit instance)
+ 7))))
+ instances)
+ (parameterize ((%guile-for-build
+ (package-derivation
+ store
+ (if (assoc-ref opts 'bootstrap?)
+ %bootstrap-guile
+ (canonical-package guile-2.2)))))
+ (run-with-store store
+ (build-and-install instances profile
+ #:verbose?
+ (assoc-ref opts 'verbose?))))))))))))))
;;; pull.scm ends here
diff --git a/guix/scripts/substitute.scm b/guix/scripts/substitute.scm
index 6d31dfdaa4..eb82224016 100755
--- a/guix/scripts/substitute.scm
+++ b/guix/scripts/substitute.scm
@@ -837,8 +837,17 @@ REPORTER, which should be a <progress-reporter> object."
(make-custom-binary-input-port "progress-port-proc"
read! #f #f
(lambda ()
- (close-connection port)
- (stop)))))))
+ ;; XXX: Kludge! When used through
+ ;; 'decompressed-port', this port ends
+ ;; up being closed twice: once in a
+ ;; child process early on, and at the
+ ;; end in the parent process. Ignore
+ ;; the early close so we don't output
+ ;; a spurious "download-succeeded"
+ ;; trace.
+ (unless (zero? total)
+ (stop))
+ (close-port port)))))))
(define-syntax with-networking
(syntax-rules ()
@@ -930,7 +939,7 @@ authorized substitutes."
(error "unknown `--query' command" wtf))))
(define* (process-substitution store-item destination
- #:key cache-urls acl)
+ #:key cache-urls acl print-build-trace?)
"Substitute STORE-ITEM (a store file name) from CACHE-URLS, and write it to
DESTINATION as a nar file. Verify the substitute against ACL."
(let* ((narinfo (lookup-narinfo cache-urls store-item
@@ -943,8 +952,10 @@ DESTINATION as a nar file. Verify the substitute against ACL."
;; Tell the daemon what the expected hash of the Nar itself is.
(format #t "~a~%" (narinfo-hash narinfo))
- (format (current-error-port)
- (G_ "Downloading ~a...~%") (uri->string uri))
+ (unless print-build-trace?
+ (format (current-error-port)
+ (G_ "Downloading ~a...~%") (uri->string uri)))
+
(let*-values (((raw download-size)
;; Note that Hydra currently generates Nars on the fly
;; and doesn't specify a Content-Length, so
@@ -955,10 +966,15 @@ DESTINATION as a nar file. Verify the substitute against ACL."
(dl-size (or download-size
(and (equal? comp "none")
(narinfo-size narinfo))))
- (reporter (progress-reporter/file
- (uri->string uri) dl-size
- (current-error-port)
- #:abbreviation nar-uri-abbreviation)))
+ (reporter (if print-build-trace?
+ (progress-reporter/trace
+ destination
+ (uri->string uri) dl-size
+ (current-error-port))
+ (progress-reporter/file
+ (uri->string uri) dl-size
+ (current-error-port)
+ #:abbreviation nar-uri-abbreviation))))
(progress-report-port reporter raw)))
((input pids)
;; NOTE: This 'progress' port of current process will be
@@ -1058,6 +1074,13 @@ default value."
(define (guix-substitute . args)
"Implement the build daemon's substituter protocol."
+ (define print-build-trace?
+ (match (or (find-daemon-option "untrusted-print-extended-build-trace")
+ (find-daemon-option "print-extended-build-trace"))
+ (#f #f)
+ ((= string->number number) (> number 0))
+ (_ #f)))
+
(mkdir-p %narinfo-cache-directory)
(maybe-remove-expired-cache-entries %narinfo-cache-directory
cached-narinfo-files
@@ -1111,7 +1134,8 @@ default value."
(parameterize ((current-terminal-columns (client-terminal-columns)))
(process-substitution store-path destination
#:cache-urls (substitute-urls)
- #:acl (current-acl))))
+ #:acl (current-acl)
+ #:print-build-trace? print-build-trace?)))
((or ("-V") ("--version"))
(show-version-and-exit "guix substitute"))
(("--help")
diff --git a/guix/scripts/system.scm b/guix/scripts/system.scm
index 69bd05b516..f9af38b7c5 100644
--- a/guix/scripts/system.scm
+++ b/guix/scripts/system.scm
@@ -23,6 +23,7 @@
(define-module (guix scripts system)
#:use-module (guix config)
#:use-module (guix ui)
+ #:use-module (guix status)
#:use-module (guix store)
#:autoload (guix store database) (register-path)
#:use-module (guix grafts)
@@ -310,9 +311,9 @@ names of services to load (upgrade), and the list of names of services to
unload."
(match (current-services)
((services ...)
- (let-values (((to-unload to-load)
+ (let-values (((to-unload to-restart)
(shepherd-service-upgrade services new-services)))
- (mproc to-load
+ (mproc to-restart
(map (compose first live-service-provision)
to-unload))))
(#f
@@ -335,25 +336,32 @@ bring the system down."
;; Arrange to simply emit a warning if the service upgrade fails.
(with-shepherd-error-handling
(call-with-service-upgrade-info new-services
- (lambda (to-load to-unload)
+ (lambda (to-restart to-unload)
(for-each (lambda (unload)
(info (G_ "unloading service '~a'...~%") unload)
(unload-service unload))
to-unload)
(with-monad %store-monad
- (munless (null? to-load)
- (let ((to-load-names (map shepherd-service-canonical-name to-load))
- (to-start (filter shepherd-service-auto-start? to-load)))
- (info (G_ "loading new services:~{ ~a~}...~%") to-load-names)
+ (munless (null? new-services)
+ (let ((new-service-names (map shepherd-service-canonical-name new-services))
+ (to-restart-names (map shepherd-service-canonical-name to-restart))
+ (to-start (filter shepherd-service-auto-start? new-services)))
+ (info (G_ "loading new services:~{ ~a~}...~%") new-service-names)
+ (unless (null? to-restart-names)
+ ;; Listing TO-RESTART-NAMES in the message below wouldn't help
+ ;; because many essential services cannot be meaningfully
+ ;; restarted. See <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=22039#30>.
+ (format #t (G_ "To complete the upgrade, run 'herd restart SERVICE' to stop,
+upgrade, and restart each service that was not automatically restarted.\n")))
(mlet %store-monad ((files (mapm %store-monad
(compose lower-object
shepherd-service-file)
- to-load)))
+ new-services)))
;; Here we assume that FILES are exactly those that were computed
;; as part of the derivation that built OS, which is normally the
;; case.
- (load-services (map derivation->output-path files))
+ (load-services/safe (map derivation->output-path files))
(for-each start-service
(map shepherd-service-canonical-name to-start))
@@ -1072,6 +1080,9 @@ Some ACTIONS support additional ARGS.\n"))
`((system . ,(%current-system))
(substitutes? . #t)
(build-hook? . #t)
+ (print-build-trace? . #t)
+ (print-extended-build-trace? . #t)
+ (multiplexed-build-output? . #t)
(graft? . #t)
(verbosity . 0)
(file-system-type . "ext4")
@@ -1246,9 +1257,11 @@ argument list and OPTS is the option alist."
parse-sub-command))
(args (option-arguments opts))
(command (assoc-ref opts 'action)))
- (parameterize ((%graft? (assoc-ref opts 'graft?))
- (current-terminal-columns (terminal-columns)))
- (process-command command args opts)))))
+ (parameterize ((%graft? (assoc-ref opts 'graft?)))
+ (with-status-report (if (memq command '(init reconfigure))
+ print-build-event/quiet
+ print-build-event)
+ (process-command command args opts))))))
;;; Local Variables:
;;; eval: (put 'call-with-service-upgrade-info 'scheme-indent-function 1)
diff --git a/guix/self.scm b/guix/self.scm
index 733c4a2cc9..3e29c9a42a 100644
--- a/guix/self.scm
+++ b/guix/self.scm
@@ -904,7 +904,11 @@ is not supported."
version))
(define guile
- (guile-for-build guile-version))
+ ;; When PULL-VERSION >= 1, produce a self-contained Guix and use Guile 2.2
+ ;; unconditionally.
+ (guile-for-build (if (>= pull-version 1)
+ "2.2"
+ guile-version)))
(mbegin %store-monad
(set-guile-for-build guile)
@@ -913,7 +917,8 @@ is not supported."
#:name (string-append "guix-"
(shorten version))
#:pull-version pull-version
- #:guile-version guile-version
+ #:guile-version (if (>= pull-version 1)
+ "2.2" guile-version)
#:guile-for-build guile)))
(if guix
(lower-object guix)
diff --git a/guix/serialization.scm b/guix/serialization.scm
index 129374f541..87ad7eeec0 100644
--- a/guix/serialization.scm
+++ b/guix/serialization.scm
@@ -301,8 +301,7 @@ result of 'lstat'; exclude entries for which SELECT? does not return true."
(filter-map (lambda (base)
(let ((file (string-append directory
"/" base)))
- (and (not (member base '("." "..")))
- (select? file (lstat file))
+ (and (select? file (lstat file))
base)))
basenames))
diff --git a/guix/status.scm b/guix/status.scm
new file mode 100644
index 0000000000..ffa9d9e93c
--- /dev/null
+++ b/guix/status.scm
@@ -0,0 +1,605 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2017, 2018 Ludovic Courtès <ludo@gnu.org>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix status)
+ #:use-module (guix records)
+ #:use-module (guix i18n)
+ #:use-module ((guix ui) #:select (colorize-string))
+ #:use-module (guix progress)
+ #:autoload (guix build syscalls) (terminal-columns)
+ #:use-module ((guix build download)
+ #:select (nar-uri-abbreviation))
+ #:use-module (guix store)
+ #:use-module (guix derivations)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-19)
+ #:use-module (srfi srfi-26)
+ #:use-module (ice-9 regex)
+ #:use-module (ice-9 match)
+ #:use-module (ice-9 format)
+ #:use-module (ice-9 binary-ports)
+ #:autoload (ice-9 rdelim) (read-string)
+ #:use-module (rnrs bytevectors)
+ #:use-module ((system foreign)
+ #:select (bytevector->pointer pointer->bytevector))
+ #:export (build-event-output-port
+ compute-status
+
+ build-status
+ build-status?
+ build-status-building
+ build-status-downloading
+ build-status-builds-completed
+ build-status-downloads-completed
+
+ download?
+ download
+ download-item
+ download-uri
+ download-size
+ download-start
+ download-end
+ download-transferred
+
+ build-status-updater
+ print-build-event
+ print-build-event/quiet
+ print-build-status
+
+ with-status-report))
+
+;;; Commentary:
+;;;
+;;; This module provides facilities to track the status of ongoing builds and
+;;; downloads in a given session, as well as tools to report about the current
+;;; status to user interfaces. It does so by analyzing the output of
+;;; 'current-build-output-port'. The build status is maintained in a
+;;; <build-status> record.
+;;;
+;;; Code:
+
+
+;;;
+;;; Build status tracking.
+;;;
+
+;; Builds and substitutions performed by the daemon.
+(define-record-type* <build-status> build-status make-build-status
+ build-status?
+ (building build-status-building ;list of drv
+ (default '()))
+ (downloading build-status-downloading ;list of <download>
+ (default '()))
+ (builds-completed build-status-builds-completed ;list of drv
+ (default '()))
+ (downloads-completed build-status-downloads-completed ;list of store items
+ (default '())))
+
+;; On-going or completed downloads. Downloads can be stem from substitutes
+;; and from "builtin:download" fixed-output derivations.
+(define-record-type <download>
+ (%download item uri size start end transferred)
+ download?
+ (item download-item) ;store item
+ (uri download-uri) ;string | #f
+ (size download-size) ;integer | #f
+ (start download-start) ;<time>
+ (end download-end) ;#f | <time>
+ (transferred download-transferred)) ;integer
+
+(define* (download item uri
+ #:key size
+ (start (current-time time-monotonic)) end
+ (transferred 0))
+ "Return a new download."
+ (%download item uri size start end transferred))
+
+(define (matching-download item)
+ "Return a predicate that matches downloads of ITEM."
+ (lambda (download)
+ (string=? item (download-item download))))
+
+(define* (compute-status event status
+ #:key
+ (current-time current-time)
+ (derivation-path->output-path
+ derivation-path->output-path))
+ "Given EVENT, a tuple like (build-started \"/gnu/store/...-foo.drv\" ...),
+compute a new status based on STATUS."
+ (match event
+ (('build-started drv _ ...)
+ (build-status
+ (inherit status)
+ (building (cons drv (build-status-building status)))))
+ (((or 'build-succeeded 'build-failed) drv _ ...)
+ (build-status
+ (inherit status)
+ (building (delete drv (build-status-building status)))
+ (builds-completed (cons drv (build-status-builds-completed status)))))
+
+ ;; Note: Ignore 'substituter-started' and 'substituter-succeeded' because
+ ;; they're not as informative as 'download-started' and
+ ;; 'download-succeeded'.
+
+ (('download-started item uri (= string->number size))
+ ;; This is presumably a fixed-output derivation so move it from
+ ;; 'building' to 'downloading'. XXX: This doesn't work in 'check' mode
+ ;; because ITEM is different from DRV's output.
+ (build-status
+ (inherit status)
+ (building (remove (lambda (drv)
+ (equal? (false-if-exception
+ (derivation-path->output-path drv))
+ item))
+ (build-status-building status)))
+ (downloading (cons (download item uri #:size size
+ #:start (current-time time-monotonic))
+ (build-status-downloading status)))))
+ (('download-succeeded item uri (= string->number size))
+ (let ((current (find (matching-download item)
+ (build-status-downloading status))))
+ (build-status
+ (inherit status)
+ (downloading (delq current (build-status-downloading status)))
+ (downloads-completed
+ (cons (download item uri
+ #:size size
+ #:start (download-start current)
+ #:transferred size
+ #:end (current-time time-monotonic))
+ (build-status-downloads-completed status))))))
+ (('substituter-succeeded item _ ...)
+ (match (find (matching-download item)
+ (build-status-downloading status))
+ (#f
+ ;; Presumably we already got a 'download-succeeded' event for ITEM,
+ ;; everything is fine.
+ status)
+ (current
+ ;; Maybe the build process didn't emit a 'download-succeeded' event
+ ;; for ITEM, so remove CURRENT from the queue now.
+ (build-status
+ (inherit status)
+ (downloading (delq current (build-status-downloading status)))
+ (downloads-completed
+ (cons (download item (download-uri current)
+ #:size (download-size current)
+ #:start (download-start current)
+ #:transferred (download-size current)
+ #:end (current-time time-monotonic))
+ (build-status-downloads-completed status)))))))
+ (('download-progress item uri
+ (= string->number size)
+ (= string->number transferred))
+ (let ((downloads (remove (matching-download item)
+ (build-status-downloading status)))
+ (current (find (matching-download item)
+ (build-status-downloading status))))
+ (build-status
+ (inherit status)
+ (downloading (cons (download item uri
+ #:size size
+ #:start
+ (or (and current
+ (download-start current))
+ (current-time time-monotonic))
+ #:transferred transferred)
+ downloads)))))
+ (_
+ status)))
+
+(define (simultaneous-jobs status)
+ "Return the number of on-going builds and downloads for STATUS."
+ (+ (length (build-status-building status))
+ (length (build-status-downloading status))))
+
+
+;;;
+;;; Rendering.
+;;;
+
+(define (extended-build-trace-supported?)
+ "Return true if the currently used store is known to support \"extended
+build traces\" such as \"@ download-progress\" traces."
+ ;; Support for extended build traces was added in protocol version #x162.
+ (and (current-store-protocol-version)
+ (>= (current-store-protocol-version) #x162)))
+
+(define (multiplexed-output-supported?)
+ "Return true if the daemon supports \"multiplexed output\"--i.e., \"@
+build-log\" traces."
+ (and (current-store-protocol-version)
+ (>= (current-store-protocol-version) #x163)))
+
+(define spin!
+ (let ((steps (circular-list "\\" "|" "/" "-")))
+ (lambda (port)
+ "Display a spinner on PORT."
+ (match steps
+ ((first . rest)
+ (set! steps rest)
+ (display "\r\x1b[K" port)
+ (display first port)
+ (force-output port))))))
+
+(define (color-output? port)
+ "Return true if we should write colored output to PORT."
+ (and (not (getenv "INSIDE_EMACS"))
+ (not (getenv "NO_COLOR"))
+ (isatty? port)))
+
+(define-syntax color-rules
+ (syntax-rules ()
+ "Return a procedure that colorizes the string it is passed according to
+the given rules. Each rule has the form:
+
+ (REGEXP COLOR1 COLOR2 ...)
+
+where COLOR1 specifies how to colorize the first submatch of REGEXP, and so
+on."
+ ((_ (regexp colors ...) rest ...)
+ (let ((next (color-rules rest ...))
+ (rx (make-regexp regexp)))
+ (lambda (str)
+ (if (string-index str #\nul)
+ str
+ (match (regexp-exec rx str)
+ (#f (next str))
+ (m (let loop ((n 1)
+ (c '(colors ...))
+ (result '()))
+ (match c
+ (()
+ (string-concatenate-reverse result))
+ ((first . tail)
+ (loop (+ n 1) tail
+ (cons (colorize-string (match:substring m n)
+ first)
+ result)))))))))))
+ ((_)
+ (lambda (str)
+ str))))
+
+(define colorize-log-line
+ ;; Take a string and return a possibly colorized string according to the
+ ;; rules below.
+ (color-rules
+ ("^(phase)(.*)(succeeded after)(.*)(seconds)(.*)"
+ GREEN BOLD GREEN RESET GREEN BLUE)
+ ("^(phase)(.*)(failed after)(.*)(seconds)(.*)"
+ RED BLUE RED BLUE RED BLUE)
+ ("^(.*)(error|fail|failed|\\<FAIL|FAILED)([[:blank:]]*)(:)(.*)"
+ RESET RED BOLD BOLD BOLD)
+ ("^(.*)(warning)([[:blank:]]*)(:)(.*)"
+ RESET MAGENTA BOLD BOLD BOLD)))
+
+(define* (print-build-event event old-status status
+ #:optional (port (current-error-port))
+ #:key
+ (colorize? (color-output? port))
+ (print-log? #t))
+ "Print information about EVENT and STATUS to PORT. When COLORIZE? is true,
+produce colorful output. When PRINT-LOG? is true, display the build log in
+addition to build events."
+ (define info
+ (if colorize?
+ (cut colorize-string <> 'BOLD)
+ identity))
+
+ (define success
+ (if colorize?
+ (cut colorize-string <> 'GREEN 'BOLD)
+ identity))
+
+ (define failure
+ (if colorize?
+ (cut colorize-string <> 'RED 'BOLD)
+ identity))
+
+ (define print-log-line
+ (if print-log?
+ (if colorize?
+ (lambda (line)
+ (display (colorize-log-line line) port))
+ (cut display <> port))
+ (lambda (line)
+ (spin! port))))
+
+ (unless print-log?
+ (display "\r" port)) ;erase the spinner
+ (match event
+ (('build-started drv . _)
+ (format port (info (G_ "building ~a...")) drv)
+ (newline port))
+ (('build-succeeded drv . _)
+ (when (or print-log? (not (extended-build-trace-supported?)))
+ (format port (success (G_ "successfully built ~a")) drv)
+ (newline port))
+ (match (build-status-building status)
+ (() #t)
+ (ongoing ;when max-jobs > 1
+ (format port
+ (N_ "The following build is still in progress:~%~{ ~a~%~}~%"
+ "The following builds are still in progress:~%~{ ~a~%~}~%"
+ (length ongoing))
+ ongoing))))
+ (('build-failed drv . _)
+ (format port (failure (G_ "build of ~a failed")) drv)
+ (newline port)
+ (match (derivation-log-file drv)
+ (#f
+ (format port (failure (G_ "Could not find build log for '~a'."))
+ drv))
+ (log
+ (format port (info (G_ "View build log at '~a'.")) log)))
+ (newline port))
+ (('substituter-started item _ ...)
+ (when (or print-log? (not (extended-build-trace-supported?)))
+ (format port (info (G_ "substituting ~a...")) item)
+ (newline port)))
+ (('download-started item uri _ ...)
+ (format port (info (G_ "downloading from ~a...")) uri)
+ (newline port))
+ (('download-progress item uri
+ (= string->number size)
+ (= string->number transferred))
+ ;; Print a progress bar, but only if there's only one on-going
+ ;; job--otherwise the output would be intermingled.
+ (when (= 1 (simultaneous-jobs status))
+ (match (find (matching-download item)
+ (build-status-downloading status))
+ (#f #f) ;shouldn't happen!
+ (download
+ ;; XXX: It would be nice to memoize the abbreviation.
+ (let ((uri (if (string-contains uri "/nar/")
+ (nar-uri-abbreviation uri)
+ (basename uri))))
+ (display-download-progress uri size
+ #:start-time
+ (download-start download)
+ #:transferred transferred))))))
+ (('substituter-succeeded item _ ...)
+ ;; If there are no jobs running, we already reported download completion
+ ;; so there's nothing left to do.
+ (unless (and (zero? (simultaneous-jobs status))
+ (extended-build-trace-supported?))
+ (format port (success (G_ "substitution of ~a complete")) item)
+ (newline port)))
+ (('substituter-failed item _ ...)
+ (format port (failure (G_ "substitution of ~a failed")) item)
+ (newline port))
+ (('hash-mismatch item algo expected actual _ ...)
+ ;; TRANSLATORS: The final string looks like "sha256 hash mismatch for
+ ;; /gnu/store/…-sth:", where "sha256" is the hash algorithm.
+ (format port (failure (G_ "~a hash mismatch for ~a:")) algo item)
+ (newline port)
+ (format port (info (G_ "\
+ expected hash: ~a
+ actual hash: ~a~%"))
+ expected actual))
+ (('build-log pid line)
+ (if (multiplexed-output-supported?)
+ (if (not pid)
+ (begin
+ ;; LINE comes from the daemon, not from builders. Let it
+ ;; through.
+ (display line port)
+ (force-output port))
+ (print-log-line line))
+ (cond ((string-prefix? "substitute: " line)
+ ;; The daemon prefixes early messages coming with 'guix
+ ;; substitute' with "substitute:". These are useful ("updating
+ ;; substitutes from URL"), so let them through.
+ (display line port)
+ (force-output port))
+ ((string-prefix? "waiting for locks" line)
+ ;; This is when a derivation is already being built and we're just
+ ;; waiting for the build to complete.
+ (display (info (string-trim-right line)) port)
+ (newline))
+ (else
+ (print-log-line line)))))
+ (_
+ event)))
+
+(define* (print-build-event/quiet event old-status status
+ #:optional
+ (port (current-error-port))
+ #:key
+ (colorize? (color-output? port)))
+ (print-build-event event old-status status port
+ #:colorize? colorize?
+ #:print-log? #f))
+
+(define* (build-status-updater #:optional (on-change (const #t)))
+ "Return a procedure that can be passed to 'build-event-output-port'. That
+procedure computes the new build status upon each event and calls ON-CHANGE:
+
+ (ON-CHANGE event status new-status)
+
+ON-CHANGE can display the build status, build events, etc."
+ (lambda (event status)
+ (let ((new (compute-status event status)))
+ (on-change event status new)
+ new)))
+
+
+;;;
+;;; Build port.
+;;;
+
+(define (maybe-utf8->string bv)
+ "Attempt to decode BV as UTF-8 string and return it. Gracefully handle the
+case where BV does not contain only valid UTF-8."
+ (catch 'decoding-error
+ (lambda ()
+ (utf8->string bv))
+ (lambda _
+ ;; This is the sledgehammer but it's the only safe way we have to
+ ;; properly handle this. It's expensive but it's rarely needed.
+ (let ((port (open-bytevector-input-port bv)))
+ (set-port-encoding! port "UTF-8")
+ (set-port-conversion-strategy! port 'substitute)
+ (let ((str (read-string port)))
+ (close-port port)
+ str)))))
+
+(define (bytevector-index bv number offset count)
+ "Search for NUMBER in BV starting from OFFSET and reading up to COUNT bytes;
+return the offset where NUMBER first occurs or #f if it could not be found."
+ (let loop ((offset offset)
+ (count count))
+ (cond ((zero? count) #f)
+ ((= (bytevector-u8-ref bv offset) number) offset)
+ (else (loop (+ 1 offset) (- count 1))))))
+
+(define (split-lines str)
+ "Split STR into lines in a way that preserves newline characters."
+ (let loop ((str str)
+ (result '()))
+ (if (string-null? str)
+ (reverse result)
+ (match (string-index str #\newline)
+ (#f
+ (loop "" (cons str result)))
+ (index
+ (loop (string-drop str (+ index 1))
+ (cons (string-take str (+ index 1)) result)))))))
+
+(define* (build-event-output-port proc #:optional (seed (build-status)))
+ "Return an output port for use as 'current-build-output-port' that calls
+PROC with its current state value, initialized with SEED, on every build
+event. Build events passed to PROC are tuples corresponding to the \"build
+traces\" produced by the daemon:
+
+ (build-started \"/gnu/store/...-foo.drv\" ...)
+ (substituter-started \"/gnu/store/...-foo\" ...)
+
+and so on.
+
+The second return value is a thunk to retrieve the current state."
+ (define %fragments
+ ;; Line fragments received so far.
+ '())
+
+ (define %state
+ ;; Current state for PROC.
+ seed)
+
+ ;; When true, this represents the current state while reading a
+ ;; "@ build-log" trace: the current builder PID, the previously-read
+ ;; bytevectors, and the number of bytes that remain to be read.
+ (define %build-output-pid #f)
+ (define %build-output '())
+ (define %build-output-left #f)
+
+ (define (process-line line)
+ (cond ((string-prefix? "@ " line)
+ (match (string-tokenize (string-drop line 2))
+ (("build-log" (= string->number pid) (= string->number len))
+ (set! %build-output-pid pid)
+ (set! %build-output '())
+ (set! %build-output-left len))
+ (((= string->symbol event-name) args ...)
+ (set! %state
+ (proc (cons event-name args)
+ %state)))))
+ (else
+ (set! %state (proc (list 'build-log #f line)
+ %state)))))
+
+ (define (process-build-output pid output)
+ ;; Transform OUTPUT in 'build-log' events or download events as generated
+ ;; by extended build traces.
+ (define (line->event line)
+ (match (and (string-prefix? "@ " line)
+ (string-tokenize (string-drop line 2)))
+ ((type . args)
+ (if (or (string-prefix? "download-" type)
+ (string=? "build-remote" type))
+ (cons (string->symbol type) args)
+ `(build-log ,pid ,line)))
+ (_
+ `(build-log ,pid ,line))))
+
+ (let* ((lines (split-lines output))
+ (events (map line->event lines)))
+ (set! %state (fold proc %state events))))
+
+ (define (bytevector-range bv offset count)
+ (let ((ptr (bytevector->pointer bv offset)))
+ (pointer->bytevector ptr count)))
+
+ (define (write! bv offset count)
+ (if %build-output-pid
+ (let ((keep (min count %build-output-left)))
+ (set! %build-output
+ (let ((bv* (make-bytevector keep)))
+ (bytevector-copy! bv offset bv* 0 keep)
+ (cons bv* %build-output)))
+ (set! %build-output-left
+ (- %build-output-left keep))
+
+ (when (zero? %build-output-left)
+ (process-build-output %build-output-pid
+ (string-concatenate-reverse
+ (map maybe-utf8->string %build-output))) ;XXX
+ (set! %build-output '())
+ (set! %build-output-pid #f))
+ keep)
+ (match (bytevector-index bv (char->integer #\newline)
+ offset count)
+ ((? integer? cr)
+ (let* ((tail (maybe-utf8->string
+ (bytevector-range bv offset (- cr -1 offset))))
+ (line (string-concatenate-reverse
+ (cons tail %fragments))))
+ (process-line line)
+ (set! %fragments '())
+ (- cr -1 offset)))
+ (#f
+ (unless (zero? count)
+ (let ((str (maybe-utf8->string
+ (bytevector-range bv offset count))))
+ (set! %fragments (cons str %fragments))))
+ count))))
+
+ (define port
+ (make-custom-binary-output-port "filtering-input-port"
+ write!
+ #f #f
+ #f))
+
+ ;; The build port actually receives Unicode strings.
+ (set-port-encoding! port "UTF-8")
+ (cond-expand
+ ((and guile-2 (not guile-2.2)) #t)
+ (else (setvbuf port 'line)))
+ (values port (lambda () %state)))
+
+(define (call-with-status-report on-event thunk)
+ (parameterize ((current-terminal-columns (terminal-columns))
+ (current-build-output-port
+ (build-event-output-port (build-status-updater on-event))))
+ (thunk)))
+
+(define-syntax-rule (with-status-report on-event exp ...)
+ "Set up build status reporting to the user using the ON-EVENT procedure;
+evaluate EXP... in that context."
+ (call-with-status-report on-event (lambda () exp ...)))
diff --git a/guix/store.scm b/guix/store.scm
index f88cdefe87..b1bdbf3813 100644
--- a/guix/store.scm
+++ b/guix/store.scm
@@ -50,9 +50,11 @@
%default-substitute-urls
nix-server?
+ nix-server-version
nix-server-major-version
nix-server-minor-version
nix-server-socket
+ current-store-protocol-version ;for internal use
&nix-error nix-error?
&nix-connection-error nix-connection-error?
@@ -150,9 +152,10 @@
store-path-package-name
store-path-hash-part
direct-store-path
+ derivation-log-file
log-file))
-(define %protocol-version #x161)
+(define %protocol-version #x163)
(define %worker-magic-1 #x6e697863) ; "nixc"
(define %worker-magic-2 #x6478696f) ; "dxio"
@@ -161,6 +164,8 @@
(logand magic #xff00))
(define (protocol-minor magic)
(logand magic #x00ff))
+(define (protocol-version major minor)
+ (logior major minor))
(define-syntax define-enumerate-type
(syntax-rules ()
@@ -540,6 +545,11 @@ connection. Use with care."
(make-hash-table 100)
(make-hash-table 100))))
+(define (nix-server-version store)
+ "Return the protocol version of STORE as an integer."
+ (protocol-version (nix-server-major-version store)
+ (nix-server-minor-version store)))
+
(define (write-buffered-output server)
"Flush SERVER's output port."
(force-output (nix-server-output-port server))
@@ -556,10 +566,20 @@ automatically close the store when the dynamic extent of EXP is left."
(dynamic-wind
(const #f)
(lambda ()
- exp ...)
+ (parameterize ((current-store-protocol-version
+ (nix-server-version store)))
+ exp) ...)
(lambda ()
(false-if-exception (close-connection store))))))
+(define current-store-protocol-version
+ ;; Protocol version of the store currently used. XXX: This is a hack to
+ ;; communicate the protocol version to the build output port. It's a hack
+ ;; because it could be inaccurrate, for instance if there's code that
+ ;; manipulates several store connections at once; it works well for the
+ ;; purposes of (guix status) though.
+ (make-parameter #f))
+
(define current-build-output-port
;; The port where build output is sent.
(make-parameter (current-error-port)))
@@ -682,6 +702,22 @@ encoding conversion errors."
(build-verbosity 0)
(log-type 0)
(print-build-trace #t)
+
+ ;; When true, provide machine-readable "build
+ ;; traces" for use by (guix status). Old clients
+ ;; are unable to make sense, which is why it's
+ ;; disabled by default.
+ print-extended-build-trace?
+
+ ;; When true, the daemon prefixes builder output
+ ;; with "@ build-log" traces so we can
+ ;; distinguish it from daemon output, and we can
+ ;; distinguish each builder's output
+ ;; (PRINT-BUILD-TRACE must be true as well.) The
+ ;; latter is particularly useful when
+ ;; MAX-BUILD-JOBS > 1.
+ multiplexed-build-output?
+
build-cores
(use-substitutes? #t)
@@ -725,7 +761,16 @@ encoding conversion errors."
(when (>= (nix-server-minor-version server) 10)
(send (boolean use-substitutes?)))
(when (>= (nix-server-minor-version server) 12)
- (let ((pairs `(,@(if timeout
+ (let ((pairs `(;; This option is honored by 'guix substitute' et al.
+ ,@(if print-build-trace
+ `(("print-extended-build-trace"
+ . ,(if print-extended-build-trace? "1" "0")))
+ '())
+ ,@(if multiplexed-build-output?
+ `(("multiplexed-build-output"
+ . ,(if multiplexed-build-output? "true" "false")))
+ '())
+ ,@(if timeout
`(("build-timeout" . ,(number->string timeout)))
'())
,@(if max-silent-time
@@ -1064,13 +1109,15 @@ an arbitrary directory layout in the store without creating a derivation."
outputs, and return when the worker is done building them. Elements of THINGS
that are not derivations can only be substituted and not built locally.
Return #t on success."
- (if (>= (nix-server-minor-version store) 15)
- (build store things mode)
- (if (= mode (build-mode normal))
- (build/old store things)
- (raise (condition (&nix-protocol-error
- (message "unsupported build mode")
- (status 1)))))))))
+ (parameterize ((current-store-protocol-version
+ (nix-server-version store)))
+ (if (>= (nix-server-minor-version store) 15)
+ (build store things mode)
+ (if (= mode (build-mode normal))
+ (build/old store things)
+ (raise (condition (&nix-protocol-error
+ (message "unsupported build mode")
+ (status 1))))))))))
(define-operation (add-temp-root (store-path path))
"Make PATH a temporary root for the duration of the current session.
@@ -1673,21 +1720,26 @@ syntactically valid store path."
(and (string-every %nix-base32-charset hash)
hash))))))
+(define (derivation-log-file drv)
+ "Return the build log file for DRV, a derivation file name, or #f if it
+could not be found."
+ (let* ((base (basename drv))
+ (log (string-append (dirname %state-directory) ; XXX
+ "/log/guix/drvs/"
+ (string-take base 2) "/"
+ (string-drop base 2)))
+ (log.gz (string-append log ".gz"))
+ (log.bz2 (string-append log ".bz2")))
+ (cond ((file-exists? log.gz) log.gz)
+ ((file-exists? log.bz2) log.bz2)
+ ((file-exists? log) log)
+ (else #f))))
+
(define (log-file store file)
"Return the build log file for FILE, or #f if none could be found. FILE
must be an absolute store file name, or a derivation file name."
(cond ((derivation-path? file)
- (let* ((base (basename file))
- (log (string-append (dirname %state-directory) ; XXX
- "/log/guix/drvs/"
- (string-take base 2) "/"
- (string-drop base 2)))
- (log.gz (string-append log ".gz"))
- (log.bz2 (string-append log ".bz2")))
- (cond ((file-exists? log.gz) log.gz)
- ((file-exists? log.bz2) log.bz2)
- ((file-exists? log) log)
- (else #f))))
+ (derivation-log-file file))
(else
(match (valid-derivers store file)
((derivers ...)
diff --git a/guix/store/database.scm b/guix/store/database.scm
index 0879a95d0b..341276bc30 100644
--- a/guix/store/database.scm
+++ b/guix/store/database.scm
@@ -23,6 +23,7 @@
#:use-module (guix serialization)
#:use-module (guix store deduplication)
#:use-module (guix base16)
+ #:use-module (guix progress)
#:use-module (guix build syscalls)
#:use-module ((guix build utils)
#:select (mkdir-p executable-file?))
@@ -234,7 +235,8 @@ be used internally by the daemon's build hook."
#:prefix prefix #:state-directory state-directory
#:deduplicate? deduplicate?
#:reset-timestamps? reset-timestamps?
- #:schema schema))
+ #:schema schema
+ #:log-port (%make-void-port "w")))
(define %epoch
;; When it all began.
@@ -245,12 +247,14 @@ be used internally by the daemon's build hook."
(deduplicate? #t)
(reset-timestamps? #t)
registration-time
- (schema (sql-schema)))
+ (schema (sql-schema))
+ (log-port (current-error-port)))
"Register all of ITEMS, a list of <store-info> records as returned by
'read-reference-graph', in the database under PREFIX/STATE-DIRECTORY. ITEMS
must be in topological order (with leaves first.) If the database is
initially empty, apply SCHEMA to initialize it. REGISTRATION-TIME must be the
-registration time to be recorded in the database; #f means \"now\"."
+registration time to be recorded in the database; #f means \"now\".
+Write a progress report to LOG-PORT."
;; Priority for options: first what is given, then environment variables,
;; then defaults. %state-directory, %store-directory, and
@@ -286,20 +290,32 @@ registration time to be recorded in the database; #f means \"now\"."
(define real-file-name
(string-append store-dir "/" (basename (store-info-item item))))
- (let-values (((hash nar-size) (nar-sha256 real-file-name)))
+ ;; When TO-REGISTER is already registered, skip it. This makes a
+ ;; significant differences when 'register-closures' is called
+ ;; consecutively for overlapping closures such as 'system' and 'bootcfg'.
+ (unless (path-id db to-register)
(when reset-timestamps?
(reset-timestamps real-file-name))
- (sqlite-register db #:path to-register
- #:references (store-info-references item)
- #:deriver (store-info-deriver item)
- #:hash (string-append "sha256:"
- (bytevector->base16-string hash))
- #:nar-size nar-size
- #:time registration-time)
- (when deduplicate?
- (deduplicate real-file-name hash #:store store-dir))))
+ (let-values (((hash nar-size) (nar-sha256 real-file-name)))
+ (sqlite-register db #:path to-register
+ #:references (store-info-references item)
+ #:deriver (store-info-deriver item)
+ #:hash (string-append "sha256:"
+ (bytevector->base16-string hash))
+ #:nar-size nar-size
+ #:time registration-time)
+ (when deduplicate?
+ (deduplicate real-file-name hash #:store store-dir)))))
(mkdir-p db-dir)
(parameterize ((sql-schema schema))
(with-database (string-append db-dir "/db.sqlite") db
- (for-each (cut register db <>) items))))
+ (let* ((prefix (format #f "registering ~a items" (length items)))
+ (progress (progress-reporter/bar (length items)
+ prefix log-port)))
+ (call-with-progress-reporter progress
+ (lambda (report)
+ (for-each (lambda (item)
+ (register db item)
+ (report))
+ items)))))))
diff --git a/guix/tests.scm b/guix/tests.scm
index 06e9f8da0b..bcf9b990e5 100644
--- a/guix/tests.scm
+++ b/guix/tests.scm
@@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2013, 2014, 2015, 2016, 2017 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -17,6 +17,7 @@
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (guix tests)
+ #:use-module ((guix config) #:select (%storedir %localstatedir))
#:use-module (guix store)
#:use-module (guix derivations)
#:use-module (guix packages)
@@ -30,6 +31,7 @@
#:use-module (ice-9 binary-ports)
#:use-module (web uri)
#:export (open-connection-for-tests
+ with-external-store
random-text
random-bytevector
file=?
@@ -74,6 +76,39 @@
store)))
+(define (call-with-external-store proc)
+ "Call PROC with an open connection to the external store or #f it there is
+no external store to talk to."
+ (parameterize ((%daemon-socket-uri
+ (string-append %localstatedir
+ "/guix/daemon-socket/socket"))
+ (%store-prefix %storedir))
+ (define store
+ (catch #t
+ (lambda ()
+ (open-connection))
+ (const #f)))
+
+ (dynamic-wind
+ (const #t)
+ (lambda ()
+ ;; Since we're using a different store we must clear the
+ ;; package-derivation cache.
+ (hash-clear! (@@ (guix packages) %derivation-cache))
+
+ (proc store))
+ (lambda ()
+ (when store
+ (close-connection store))))))
+
+(define-syntax-rule (with-external-store store exp ...)
+ "Evaluate EXP with STORE bound to the external store rather than the
+temporary test store, or #f if there is no external store to talk to.
+
+This is meant to be used for tests that need to build packages that would be
+too expensive to build entirely in the test store."
+ (call-with-external-store (lambda (store) exp ...)))
+
(define (random-seed)
(or (and=> (getenv "GUIX_TESTS_RANDOM_SEED")
number->string)
diff --git a/guix/ui.scm b/guix/ui.scm
index c55ae7e2f8..96f403acf5 100644
--- a/guix/ui.scm
+++ b/guix/ui.scm
@@ -119,7 +119,7 @@
warning
info
guix-main
- build-output-port))
+ colorize-string))
;;; Commentary:
;;;
@@ -1676,124 +1676,4 @@ be reset such that subsequent output will not have any colors in effect."
str
(color 'RESET)))
-(define* (build-output-port #:key
- (colorize? #t)
- verbose?
- (port (current-error-port)))
- "Return a soft port that processes build output. By default it colorizes
-phase announcements and replaces any other output with a spinner."
- (define spun? #f)
- (define spin!
- (let ((steps (circular-list "\\" "|" "/" "-")))
- (lambda ()
- (match steps
- ((first . rest)
- (set! steps rest)
- (set! spun? #t) ; remember to erase spinner
- first)))))
-
- (define use-color?
- (and colorize?
- (not (or (getenv "NO_COLOR")
- (getenv "INSIDE_EMACS")
- (not (isatty? port))))))
-
- (define handle-string
- (let* ((proc (if use-color?
- colorize-string
- (lambda (s . _) s)))
- (rules `(("^(@ build-started) (.*) (.*)"
- #:transform
- ,(lambda (m)
- (string-append
- (proc "Building " 'BLUE 'BOLD)
- (match:substring m 2) "\n")))
- ,(if verbose?
- ;; Err on the side of caution: show everything, even
- ;; if it might be redundant.
- `("^(@ build-failed)(.+)"
- #:transform
- ,(lambda (m)
- (string-append
- (proc "Build failed: " 'RED 'BOLD)
- (match:substring m 2))))
- ;; Show only that the build failed.
- `("^(@ build-failed)(.+) -.*"
- #:transform
- ,(lambda (m)
- (string-append
- (proc "Build failed: " 'RED 'BOLD)
- (match:substring m 2)
- "\n"))))
- ;; NOTE: this line contains "\n" characters.
- ("^(sha256 hash mismatch for output path)(.*)"
- RED BLACK)
- ("^(@ build-succeeded) (.*) (.*)"
- #:transform
- ,(lambda (m)
- (string-append
- (proc "Built " 'GREEN 'BOLD)
- (match:substring m 2) "\n")))
- ("^(@ substituter-started) (.*) (.*)"
- #:transform
- ,(lambda (m)
- (string-append
- (proc "Substituting " 'BLUE 'BOLD)
- (match:substring m 2) "\n")))
- ("^(@ substituter-failed) (.*) (.*) (.*)"
- #:transform
- ,(lambda (m)
- (string-append
- (proc "Substituter failed: " 'RED 'BOLD)
- (match:substring m 2) "\n"
- (match:substring m 3) ": "
- (match:substring m 4) "\n")))
- ("^(@ substituter-succeeded) (.*)"
- #:transform
- ,(lambda (m)
- (string-append
- (proc "Substituted " 'GREEN 'BOLD)
- (match:substring m 2) "\n")))
- ("^(starting phase )(.*)"
- BLUE GREEN)
- ("^(phase)(.*)(succeeded after)(.*)(seconds)(.*)"
- GREEN BLUE GREEN BLUE GREEN BLUE)
- ("^(phase)(.*)(failed after)(.*)(seconds)(.*)"
- RED BLUE RED BLUE RED BLUE))))
- (lambda (str)
- (let ((processed
- (any (match-lambda
- ((pattern #:transform transform)
- (and=> (string-match pattern str)
- transform))
- ((pattern . colors)
- (and=> (string-match pattern str)
- (lambda (m)
- (let ((substrings
- (map (cut match:substring m <>)
- (iota (- (match:count m) 1) 1))))
- (string-join (map proc substrings colors) ""))))))
- rules)))
- (when spun?
- (display (string #\backspace) port))
- (if processed
- (begin
- (display processed port)
- (set! spun? #f))
- ;; Print unprocessed line, or replace with spinner
- (display (if verbose? str (spin!)) port))))))
- (make-soft-port
- (vector
- ;; procedure accepting one character for output
- (cut write <> port)
- ;; procedure accepting a string for output
- handle-string
- ;; thunk for flushing output
- (lambda () (force-output port))
- ;; thunk for getting one character
- (const #t)
- ;; thunk for closing port (not by garbage collection)
- (lambda () (close port)))
- "w"))
-
;;; ui.scm ends here