From 18ae1ec3ecfe22d55d6cdf595a442afebbc5595a Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Mon, 18 May 2020 22:10:20 +0200 Subject: guix hash, guix download: Add '--hash'. * guix/scripts/download.scm (%default-options): Add 'hash-algorithm'. (show-help, %options): Add "--hash". (guix-download): Honor it. * guix/scripts/hash.scm (%default-options): Add 'hash-algorithm'. (show-help, %options): Add "--hash". (guix-hash): Honor it. * tests/guix-hash.sh: Test '-H sha512'. * doc/guix.texi (Invoking guix download): Document it. (Invoking guix hash): Document it. --- guix/scripts/download.scm | 14 ++++++++++++-- guix/scripts/hash.scm | 21 +++++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) (limited to 'guix') diff --git a/guix/scripts/download.scm b/guix/scripts/download.scm index 22cd75ea0b..b4446c2e2f 100644 --- a/guix/scripts/download.scm +++ b/guix/scripts/download.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2012, 2013, 2015, 2016, 2017 Ludovic Courtès +;;; Copyright © 2012, 2013, 2015, 2016, 2017, 2020 Ludovic Courtès ;;; ;;; This file is part of GNU Guix. ;;; @@ -77,6 +77,7 @@ (define %default-options ;; Alist of default option values. `((format . ,bytevector->nix-base32-string) + (hash-algorithm . ,(hash-algorithm sha256)) (verify-certificate? . #t) (download-proc . ,download-to-store*))) @@ -89,6 +90,8 @@ Supported formats: 'nix-base32' (default), 'base32', and 'base16' ('hex' and 'hexadecimal' can be used as well).\n")) (format #t (G_ " -f, --format=FMT write the hash in the given format")) + (format #t (G_ " + -H, --hash=ALGORITHM use the given hash ALGORITHM")) (format #t (G_ " --no-check-certificate do not validate the certificate of HTTPS servers ")) @@ -119,6 +122,13 @@ Supported formats: 'nix-base32' (default), 'base32', and 'base16' (alist-cons 'format fmt-proc (alist-delete 'format result)))) + (option '(#\H "hash") #t #f + (lambda (opt name arg result) + (match (lookup-hash-algorithm (string->symbol arg)) + (#f + (leave (G_ "~a: unknown hash algorithm~%") arg)) + (algo + (alist-cons 'hash-algorithm algo result))))) (option '("no-check-certificate") #f #f (lambda (opt name arg result) (alist-cons 'verify-certificate? #f result))) @@ -175,7 +185,7 @@ Supported formats: 'nix-base32' (default), 'base32', and 'base16' (or path (leave (G_ "~a: download failed~%") arg)) - port-sha256)) + (cute port-hash (assoc-ref opts 'hash-algorithm) <>))) (fmt (assq-ref opts 'format))) (format #t "~a~%~a~%" path (fmt hash)) #t))) diff --git a/guix/scripts/hash.scm b/guix/scripts/hash.scm index b8b2158195..cfc4420260 100644 --- a/guix/scripts/hash.scm +++ b/guix/scripts/hash.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2012, 2013, 2014, 2016, 2017 Ludovic Courtès +;;; Copyright © 2012, 2013, 2014, 2016, 2017, 2020 Ludovic Courtès ;;; Copyright © 2013 Nikita Karetnikov ;;; Copyright © 2016 Jan Nieuwenhuizen ;;; Copyright © 2018 Tim Gesthuizen @@ -42,7 +42,8 @@ (define %default-options ;; Alist of default option values. - `((format . ,bytevector->nix-base32-string))) + `((format . ,bytevector->nix-base32-string) + (hash-algorithm . ,(hash-algorithm sha256)))) (define (show-help) (display (G_ "Usage: guix hash [OPTION] FILE @@ -53,6 +54,8 @@ and 'hexadecimal' can be used as well).\n")) (format #t (G_ " -x, --exclude-vcs exclude version control directories")) (format #t (G_ " + -H, --hash=ALGORITHM use the given hash ALGORITHM")) + (format #t (G_ " -f, --format=FMT write the hash in the given format")) (format #t (G_ " -r, --recursive compute the hash on FILE recursively")) @@ -69,6 +72,13 @@ and 'hexadecimal' can be used as well).\n")) (list (option '(#\x "exclude-vcs") #f #f (lambda (opt name arg result) (alist-cons 'exclude-vcs? #t result))) + (option '(#\H "hash") #t #f + (lambda (opt name arg result) + (match (lookup-hash-algorithm (string->symbol arg)) + (#f + (leave (G_ "~a: unknown hash algorithm~%") arg)) + (algo + (alist-cons 'hash-algorithm algo result))))) (option '(#\f "format") #t #f (lambda (opt name arg result) (define fmt-proc @@ -139,8 +149,11 @@ and 'hexadecimal' can be used as well).\n")) (force-output port) (get-hash)) (match file - ("-" (port-sha256 (current-input-port))) - (_ (call-with-input-file file port-sha256)))))) + ("-" (port-hash (assoc-ref opts 'hash-algorithm) + (current-input-port))) + (_ (call-with-input-file file + (cute port-hash (assoc-ref opts 'hash-algorithm) + <>))))))) (match args ((file) -- cgit v1.2.3 From 0e4e9c8e7655f3e2a9c2615923768d211ac02a3e Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Mon, 18 May 2020 22:25:13 +0200 Subject: guix hash, guix download: Support base64 format. * guix/scripts/download.scm (show-help, %options): Support "base64" format. * guix/scripts/hash.scm (show-help, %options): Likewise. * tests/guix-hash.sh: Test it. * doc/guix.texi (Invoking guix hash): Document it. --- doc/guix.texi | 2 +- guix/scripts/download.scm | 12 ++++++++---- guix/scripts/hash.scm | 14 +++++++++----- tests/guix-hash.sh | 1 + 4 files changed, 19 insertions(+), 10 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index 197207697e..01dab5b72c 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -9076,7 +9076,7 @@ Reference Manual}). @itemx -f @var{fmt} Write the hash in the format specified by @var{fmt}. -Supported formats: @code{nix-base32}, @code{base32}, @code{base16} +Supported formats: @code{base64}, @code{nix-base32}, @code{base32}, @code{base16} (@code{hex} and @code{hexadecimal} can be used as well). If the @option{--format} option is not specified, @command{guix hash} diff --git a/guix/scripts/download.scm b/guix/scripts/download.scm index b4446c2e2f..589f62da9d 100644 --- a/guix/scripts/download.scm +++ b/guix/scripts/download.scm @@ -23,6 +23,7 @@ #:use-module (gcrypt hash) #:use-module (guix base16) #:use-module (guix base32) + #:autoload (guix base64) (base64-encode) #:use-module ((guix download) #:hide (url-fetch)) #:use-module ((guix build download) #:select (url-fetch)) @@ -84,10 +85,11 @@ (define (show-help) (display (G_ "Usage: guix download [OPTION] URL Download the file at URL to the store or to the given file, and print its -file name and the hash of its contents. - -Supported formats: 'nix-base32' (default), 'base32', and 'base16' -('hex' and 'hexadecimal' can be used as well).\n")) +file name and the hash of its contents.\n")) + (newline) + (display (G_ "\ +Supported formats: 'base64', 'nix-base32' (default), 'base32', +and 'base16' ('hex' and 'hexadecimal' can be used as well).\n")) (format #t (G_ " -f, --format=FMT write the hash in the given format")) (format #t (G_ " @@ -111,6 +113,8 @@ Supported formats: 'nix-base32' (default), 'base32', and 'base16' (lambda (opt name arg result) (define fmt-proc (match arg + ("base64" + base64-encode) ("nix-base32" bytevector->nix-base32-string) ("base32" diff --git a/guix/scripts/hash.scm b/guix/scripts/hash.scm index cfc4420260..9b4f419a24 100644 --- a/guix/scripts/hash.scm +++ b/guix/scripts/hash.scm @@ -20,12 +20,13 @@ ;;; along with GNU Guix. If not, see . (define-module (guix scripts hash) - #:use-module (guix base32) #:use-module (gcrypt hash) #:use-module (guix serialization) #:use-module (guix ui) #:use-module (guix scripts) #:use-module (guix base16) + #:use-module (guix base32) + #:autoload (guix base64) (base64-encode) #:use-module (ice-9 binary-ports) #:use-module (rnrs files) #:use-module (ice-9 match) @@ -47,10 +48,11 @@ (define (show-help) (display (G_ "Usage: guix hash [OPTION] FILE -Return the cryptographic hash of FILE. - -Supported formats: 'nix-base32' (default), 'base32', and 'base16' ('hex' -and 'hexadecimal' can be used as well).\n")) +Return the cryptographic hash of FILE.\n")) + (newline) + (display (G_ "\ +Supported formats: 'base64', 'nix-base32' (default), 'base32', +and 'base16' ('hex' and 'hexadecimal' can be used as well).\n")) (format #t (G_ " -x, --exclude-vcs exclude version control directories")) (format #t (G_ " @@ -83,6 +85,8 @@ and 'hexadecimal' can be used as well).\n")) (lambda (opt name arg result) (define fmt-proc (match arg + ("base64" + base64-encode) ("nix-base32" bytevector->nix-base32-string) ("base32" diff --git a/tests/guix-hash.sh b/tests/guix-hash.sh index 1c595b49ed..3538b9aeda 100644 --- a/tests/guix-hash.sh +++ b/tests/guix-hash.sh @@ -32,6 +32,7 @@ test `guix hash -f nix-base32 /dev/null` = 0mdqa9w1p6cmli6976v4wi0sw9r4p5prkj7lz test `guix hash -f hex /dev/null` = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 test `guix hash -f base32 /dev/null` = 4oymiquy7qobjgx36tejs35zeqt24qpemsnzgtfeswmrw6csxbkq test `guix hash -H sha512 -f hex /dev/null` = cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e +test `guix hash -H sha1 -f base64 /dev/null` = "2jmj7l5rSw0yVb/vlWAYkK/YBwk=" if guix hash -H abcd1234 /dev/null; then false; else true; fi -- cgit v1.2.3 From 56f7ca6e7c8b5eadeee48b00bcbd78f9fa9e5f43 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Mon, 18 May 2020 23:21:36 +0200 Subject: packages: Add 'base64' macro. * guix/packages.scm (define-compile-time-decoder): New macro. (base32): Redefine in terms of it. (base64): New macro. --- guix/packages.scm | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) (limited to 'guix') diff --git a/guix/packages.scm b/guix/packages.scm index 3fff50a6e8..c1c4805ae9 100644 --- a/guix/packages.scm +++ b/guix/packages.scm @@ -28,6 +28,7 @@ #:use-module (guix monads) #:use-module (guix gexp) #:use-module (guix base32) + #:autoload (guix base64) (base64-decode) #:use-module (guix grafts) #:use-module (guix derivations) #:use-module (guix memoization) @@ -62,6 +63,7 @@ origin-snippet origin-modules base32 + base64 package package? @@ -197,19 +199,24 @@ (set-record-type-printer! print-origin) -(define-syntax base32 - (lambda (s) - "Return the bytevector corresponding to the given Nix-base32 +(define-syntax-rule (define-compile-time-decoder name string->bytevector) + "Define NAME as a macro that runs STRING->BYTEVECTOR at macro expansion time +if possible." + (define-syntax name + (lambda (s) + "Return the bytevector corresponding to the given textual representation." - (syntax-case s () - ((_ str) - (string? (syntax->datum #'str)) - ;; A literal string: do the conversion at expansion time. - (with-syntax ((bv (nix-base32-string->bytevector - (syntax->datum #'str)))) - #''bv)) - ((_ str) - #'(nix-base32-string->bytevector str))))) + (syntax-case s () + ((_ str) + (string? (syntax->datum #'str)) + ;; A literal string: do the conversion at expansion time. + (with-syntax ((bv (string->bytevector (syntax->datum #'str)))) + #''bv)) + ((_ str) + #'(string->bytevector str)))))) + +(define-compile-time-decoder base32 nix-base32-string->bytevector) +(define-compile-time-decoder base64 base64-decode) (define (origin-actual-file-name origin) "Return the file name of ORIGIN, either its 'file-name' field or the file -- cgit v1.2.3 From ce0be5675b702b2ff89aed1772ebb42af4150243 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Tue, 19 May 2020 15:55:08 +0200 Subject: packages: Introduce and use it in . * guix/packages.scm (): New record type. (define-content-hash-constructor, build-content-hash) (content-hash): New macros. (print-content-hash): New procedure. (): Rename constructor to '%origin'. [sha256]: Remove field. [hash]: New field. Adjust users. (origin-compatibility-helper, origin): New macros. (origin-sha256): New deprecated procedure. (origin->derivation): Adjust accordingly. * tests/packages.scm ("package-source-derivation, origin, sha512"): New test. * guix/tests.scm: Hide (gcrypt hash) 'sha256' for proper syntax matching. * tests/challenge.scm: Add #:prefix for (gcrypt hash) and adjust users. * tests/derivations.scm: Likewise. * tests/store.scm: Likewise. * tests/graph.scm ("bag DAG, including origins"): Provide 'sha256' field with the right length. * gnu/packages/aspell.scm (aspell-dictionary) (aspell-dict-ca, aspell-dict-it): Use 'hash' and 'content-hash' for proper syntax matching. * gnu/packages/bash.scm (bash-patch): Rename 'sha256' to 'sha256-bv'. * gnu/packages/bootstrap.scm (bootstrap-executable): Rename 'sha256' to 'bv'. * gnu/packages/readline.scm (readline-patch): Likewise. * gnu/packages/virtualization.scm (qemu-patch): Rename 'sha256' to 'sha256-bv'. * guix/import/utils.scm: Hide (gcrypt hash) 'sha256'. --- doc/guix.texi | 34 ++++++++++- gnu/packages/aspell.scm | 8 +-- gnu/packages/bash.scm | 8 +-- gnu/packages/bootstrap.scm | 6 +- gnu/packages/readline.scm | 8 +-- gnu/packages/virtualization.scm | 4 +- guix/import/utils.scm | 2 +- guix/packages.scm | 126 ++++++++++++++++++++++++++++++++++++---- guix/tests.scm | 2 +- tests/challenge.scm | 6 +- tests/derivations.scm | 32 +++++----- tests/graph.scm | 6 +- tests/packages.scm | 28 ++++++++- tests/store.scm | 8 +-- 14 files changed, 220 insertions(+), 58 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index 01dab5b72c..c1e23b5ef3 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -5966,9 +5966,13 @@ specified in the @code{uri} field as a @code{git-reference} object; a @end table @item @code{sha256} -A bytevector containing the SHA-256 hash of the source. Typically the -@code{base32} form is used here to generate the bytevector from a -base-32 string. +A bytevector containing the SHA-256 hash of the source. This is +equivalent to providing a @code{content-hash} SHA256 object in the +@code{hash} field described below. + +@item @code{hash} +The @code{content-hash} object of the source---see below for how to use +@code{content-hash}. You can obtain this information using @code{guix download} (@pxref{Invoking guix download}) or @code{guix hash} (@pxref{Invoking @@ -6013,6 +6017,30 @@ this is @code{#f}, a sensible default is used. @end table @end deftp +@deftp {Data Type} content-hash @var{value} [@var{algorithm}] +Construct a content hash object for the given @var{algorithm}, and with +@var{value} as its hash value. When @var{algorithm} is omitted, assume +it is @code{sha256}. + +@var{value} can be a literal string, in which case it is base32-decoded, +or it can be a bytevector. + +The following forms are all equivalent: + +@lisp +(content-hash "05zxkyz9bv3j9h0xyid1rhvh3klhsmrpkf3bcs6frvlgyr2gwilj") +(content-hash "05zxkyz9bv3j9h0xyid1rhvh3klhsmrpkf3bcs6frvlgyr2gwilj" + sha256) +(content-hash (base32 + "05zxkyz9bv3j9h0xyid1rhvh3klhsmrpkf3bcs6frvlgyr2gwilj")) +(content-hash (base64 "kkb+RPaP7uyMZmu4eXPVkM4BN8yhRd8BTHLslb6f/Rc=") + sha256) +@end lisp + +Technically, @code{content-hash} is currently implemented as a macro. +It performs sanity checks at macro-expansion time, when possible, such +as ensuring that @var{value} has the right size for @var{algorithm}. +@end deftp @node Build Systems @section Build Systems diff --git a/gnu/packages/aspell.scm b/gnu/packages/aspell.scm index 7550736c40..22256f750b 100644 --- a/gnu/packages/aspell.scm +++ b/gnu/packages/aspell.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2013, 2014, 2015, 2017, 2018, 2019 Ludovic Courtès +;;; Copyright © 2013, 2014, 2015, 2017, 2018, 2019, 2020 Ludovic Courtès ;;; Copyright © 2015, 2016 Alex Kost ;;; Copyright © 2016 John Darrington ;;; Copyright © 2016, 2017, 2019 Efraim Flashner @@ -111,7 +111,7 @@ dictionaries, including personal ones.") (uri (string-append "mirror://gnu/aspell/dict/" dict-name "/" prefix dict-name "-" version ".tar.bz2")) - (sha256 sha256))) + (hash (content-hash sha256)))) (build-system gnu-build-system) (arguments `(#:phases @@ -163,7 +163,7 @@ dictionaries, including personal ones.") (method url-fetch) (uri (string-append "https://www.softcatala.org/pub/softcatala/aspell/" version "/aspell6-ca-" version ".tar.bz2")) - (sha256 sha256))) + (hash (content-hash sha256)))) (home-page "https://www.softcatala.org/pub/softcatala/aspell/")))) (define-public aspell-dict-de @@ -264,7 +264,7 @@ dictionaries, including personal ones.") (uri (string-append "mirror://sourceforge/linguistico/" "Dizionario%20italiano%20per%20Aspell/" version "/" "aspell6-it-" version ".tar.bz2")) - (sha256 sha256))) + (hash (content-hash sha256)))) (home-page "http://linguistico.sourceforge.net/pages/dizionario_italiano.html")))) diff --git a/gnu/packages/bash.scm b/gnu/packages/bash.scm index 1b342827c5..311e07a944 100644 --- a/gnu/packages/bash.scm +++ b/gnu/packages/bash.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2019 Ludovic Courtès +;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2020 Ludovic Courtès ;;; Copyright © 2014, 2015, 2018 Mark H Weaver ;;; Copyright © 2015, 2017 Leo Famulari ;;; Copyright © 2016, 2017, 2018, 2019 Efraim Flashner @@ -48,12 +48,12 @@ "Return the URL of Bash patch number SEQNO." (format #f "mirror://gnu/bash/bash-5.0-patches/bash50-~3,'0d" seqno)) -(define (bash-patch seqno sha256) - "Return the origin of Bash patch SEQNO, with expected hash SHA256" +(define (bash-patch seqno sha256-bv) + "Return the origin of Bash patch SEQNO, with expected hash SHA256-BV." (origin (method url-fetch) (uri (patch-url seqno)) - (sha256 sha256))) + (sha256 sha256-bv))) (define-syntax-rule (patch-series (seqno hash) ...) (list (bash-patch seqno (base32 hash)) diff --git a/gnu/packages/bootstrap.scm b/gnu/packages/bootstrap.scm index f58ce2de93..a3ecb6e692 100644 --- a/gnu/packages/bootstrap.scm +++ b/gnu/packages/bootstrap.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès +;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès ;;; Copyright © 2014, 2015, 2018, 2019 Mark H Weaver ;;; Copyright © 2017, 2020 Efraim Flashner ;;; Copyright © 2018, 2020 Jan (janneke) Nieuwenhuizen @@ -151,14 +151,14 @@ built for SYSTEM." (format #f (G_ "could not find bootstrap binary '~a' \ for system '~a'") program system)))))) - ((sha256) + ((bv) (origin (method url-fetch/executable) (uri (map (cute string-append <> (bootstrap-executable-file-name system program)) %bootstrap-executable-base-urls)) (file-name program) - (sha256 sha256))))))) + (hash (content-hash bv sha256)))))))) ;;; diff --git a/gnu/packages/readline.scm b/gnu/packages/readline.scm index 5f61dcb735..8a36883347 100644 --- a/gnu/packages/readline.scm +++ b/gnu/packages/readline.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2012, 2013, 2014 Ludovic Courtès +;;; Copyright © 2012, 2013, 2014, 2020 Ludovic Courtès ;;; Copyright © 2016, 2019 Efraim Flashner ;;; Copyright © 2016 Jan Nieuwenhuizen ;;; Copyright © 2018 Tobias Geerinckx-Rice @@ -35,12 +35,12 @@ (format #f "mirror://gnu/readline/readline-~a-patches/readline~a-~3,'0d" version (string-join (string-split version #\.) "") seqno)) -(define (readline-patch version seqno sha256) - "Return the origin of Readline patch SEQNO, with expected hash SHA256" +(define (readline-patch version seqno sha256-bv) + "Return the origin of Readline patch SEQNO, with expected hash SHA256-BV" (origin (method url-fetch) (uri (patch-url version seqno)) - (sha256 sha256))) + (sha256 sha256-bv))) (define-syntax-rule (patch-series version (seqno hash) ...) (list (readline-patch version seqno (base32 hash)) diff --git a/gnu/packages/virtualization.scm b/gnu/packages/virtualization.scm index e0b9a21e72..32113a0f2c 100644 --- a/gnu/packages/virtualization.scm +++ b/gnu/packages/virtualization.scm @@ -104,14 +104,14 @@ #:use-module (srfi srfi-1) #:use-module (ice-9 match)) -(define (qemu-patch commit file-name sha256) +(define (qemu-patch commit file-name sha256-bv) "Return an origin for COMMIT." (origin (method url-fetch) (uri (string-append "http://git.qemu.org/?p=qemu.git;a=commitdiff_plain;h=" commit)) - (sha256 sha256) + (hash (content-hash sha256-bv sha256)) (file-name file-name))) (define-public qemu diff --git a/guix/import/utils.scm b/guix/import/utils.scm index 3809c3d074..0cfa1f8321 100644 --- a/guix/import/utils.scm +++ b/guix/import/utils.scm @@ -24,7 +24,7 @@ (define-module (guix import utils) #:use-module (guix base32) #:use-module ((guix build download) #:prefix build:) - #:use-module (gcrypt hash) + #:use-module ((gcrypt hash) #:hide (sha256)) #:use-module (guix http-client) #:use-module ((guix licenses) #:prefix license:) #:use-module (guix utils) diff --git a/guix/packages.scm b/guix/packages.scm index c1c4805ae9..3d9988d836 100644 --- a/guix/packages.scm +++ b/guix/packages.scm @@ -35,6 +35,8 @@ #:use-module (guix build-system) #:use-module (guix search-paths) #:use-module (guix sets) + #:use-module (guix deprecation) + #:use-module (guix i18n) #:use-module (ice-9 match) #:use-module (ice-9 vlist) #:use-module (ice-9 regex) @@ -44,16 +46,23 @@ #:use-module (srfi srfi-26) #:use-module (srfi srfi-34) #:use-module (srfi srfi-35) + #:use-module (rnrs bytevectors) #:use-module (web uri) #:re-export (%current-system %current-target-system search-path-specification) ;for convenience - #:export (origin + #:export (content-hash + content-hash? + content-hash-algorithm + content-hash-value + + origin origin? this-origin origin-uri origin-method - origin-sha256 + origin-hash + origin-sha256 ;deprecated origin-file-name origin-actual-file-name origin-patches @@ -157,15 +166,79 @@ ;;; ;;; Code: +;; Crytographic content hash. +(define-immutable-record-type + (%content-hash algorithm value) + content-hash? + (algorithm content-hash-algorithm) ;symbol + (value content-hash-value)) ;bytevector + +(define-syntax-rule (define-content-hash-constructor name + (algorithm size) ...) + "Define NAME as a constructor that ensures that (1) its +second argument is among the listed ALGORITHM, and (2), when possible, that +its first argument has the right size for the chosen algorithm." + (define-syntax name + (lambda (s) + (syntax-case s (algorithm ...) + ((_ bv algorithm) + (let ((bv* (syntax->datum #'bv))) + (when (and (bytevector? bv*) + (not (= size (bytevector-length bv*)))) + (syntax-violation 'content-hash "invalid content hash length" s)) + #'(%content-hash 'algorithm bv))) + ...)))) + +(define-content-hash-constructor build-content-hash + (sha256 32) + (sha512 64)) + +(define-syntax content-hash + (lambda (s) + "Return a content hash with the given parameters. The default hash +algorithm is sha256. If the first argument is a literal string, it is decoded +as base32. Otherwise, it must be a bytevector." + ;; What we'd really want here is something like C++ 'constexpr'. + (syntax-case s () + ((_ str) + (string? (syntax->datum #'str)) + #'(content-hash str sha256)) + ((_ str algorithm) + (string? (syntax->datum #'str)) + (with-syntax ((bv (base32 (syntax->datum #'str)))) + #'(content-hash bv algorithm))) + ((_ (id str) algorithm) + (and (string? (syntax->datum #'str)) + (free-identifier=? #'id #'base32)) + (with-syntax ((bv (nix-base32-string->bytevector (syntax->datum #'str)))) + #'(content-hash bv algorithm))) + ((_ (id str) algorithm) + (and (string? (syntax->datum #'str)) + (free-identifier=? #'id #'base64)) + (with-syntax ((bv (base64-decode (syntax->datum #'str)))) + #'(content-hash bv algorithm))) + ((_ bv) + #'(content-hash bv sha256)) + ((_ bv hash) + #'(build-content-hash bv hash))))) + +(define (print-content-hash hash port) + (format port "#" + (content-hash-algorithm hash) + (bytevector->nix-base32-string (content-hash-value hash)))) + +(set-record-type-printer! print-content-hash) + + ;; The source of a package, such as a tarball URL and fetcher---called ;; "origin" to avoid name clash with `package-source', `source', etc. (define-record-type* - origin make-origin + %origin make-origin origin? this-origin (uri origin-uri) ; string (method origin-method) ; procedure - (sha256 origin-sha256) ; bytevector + (hash origin-hash) ; (file-name origin-file-name (default #f)) ; optional file name ;; Patches are delayed so that the 'search-patch' calls are made lazily, @@ -188,12 +261,37 @@ (patch-guile origin-patch-guile ; package or #f (default #f))) +(define-syntax origin-compatibility-helper + (syntax-rules (sha256) + ((_ () (fields ...)) + (%origin fields ...)) + ((_ ((sha256 exp) rest ...) (others ...)) + (%origin others ... + (hash (content-hash exp sha256)) + rest ...)) + ((_ (field rest ...) (others ...)) + (origin-compatibility-helper (rest ...) + (others ... field))))) + +(define-syntax-rule (origin fields ...) + "Build an record, automatically converting 'sha256' field +specifications to 'hash'." + (origin-compatibility-helper (fields ...) ())) + +(define-deprecated (origin-sha256 origin) + origin-hash + (let ((hash (origin-hash origin))) + (unless (eq? (content-hash-algorithm hash) 'sha256) + (raise (condition (&message + (message (G_ "no SHA256 hash for origin")))))) + (content-hash-value hash))) + (define (print-origin origin port) "Write a concise representation of ORIGIN to PORT." (match origin - (($ uri method sha256 file-name patches) + (($ uri method hash file-name patches) (simple-format port "#" - uri (bytevector->base32-string sha256) + uri hash (force patches) (number->string (object-address origin) 16))))) @@ -238,6 +336,7 @@ name of its URI." ;; git, svn, cvs, etc. reference #f)))) + (define %supported-systems ;; This is the list of system types that are supported. By default, we ;; expect all packages to build successfully here. @@ -1388,14 +1487,19 @@ unless you know what you are doing." #:optional (system (%current-system))) "Return the derivation corresponding to ORIGIN." (match origin - (($ uri method sha256 name (= force ()) #f) + (($ uri method hash name (= force ()) #f) ;; No patches, no snippet: this is a fixed-output derivation. - (method uri 'sha256 sha256 name #:system system)) - (($ uri method sha256 name (= force (patches ...)) snippet + (method uri + (content-hash-algorithm hash) + (content-hash-value hash) + name #:system system)) + (($ uri method hash name (= force (patches ...)) snippet (flags ...) inputs (modules ...) guile-for-build) ;; Patches and/or a snippet. - (mlet %store-monad ((source (method uri 'sha256 sha256 name - #:system system)) + (mlet %store-monad ((source (method uri + (content-hash-algorithm hash) + (content-hash-value hash) + name #:system system)) (guile (package->derivation (or guile-for-build (default-guile)) system diff --git a/guix/tests.scm b/guix/tests.scm index 95a7d7c4b8..3ccf049a7d 100644 --- a/guix/tests.scm +++ b/guix/tests.scm @@ -26,7 +26,7 @@ #:use-module (guix monads) #:use-module ((guix utils) #:select (substitute-keyword-arguments)) #:use-module ((guix build utils) #:select (mkdir-p)) - #:use-module (gcrypt hash) + #:use-module ((gcrypt hash) #:hide (sha256)) #:use-module (guix build-system gnu) #:use-module (gnu packages base) #:use-module (gnu packages bootstrap) diff --git a/tests/challenge.scm b/tests/challenge.scm index bb5633a3eb..9c6d6e0d58 100644 --- a/tests/challenge.scm +++ b/tests/challenge.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2015, 2017, 2019 Ludovic Courtès +;;; Copyright © 2015, 2017, 2019, 2020 Ludovic Courtès ;;; ;;; This file is part of GNU Guix. ;;; @@ -19,7 +19,7 @@ (define-module (test-challenge) #:use-module (guix tests) #:use-module (guix tests http) - #:use-module (gcrypt hash) + #:use-module ((gcrypt hash) #:prefix gcrypt:) #:use-module (guix store) #:use-module (guix monads) #:use-module (guix derivations) @@ -135,7 +135,7 @@ (mlet* %store-monad ((drv (gexp->derivation "something" #~(list #$output #$text))) (out -> (derivation->output-path drv)) - (hash -> (sha256 #vu8()))) + (hash -> (gcrypt:sha256 #vu8()))) (with-derivation-narinfo* drv (sha256 => hash) (>>= (compare-contents (list out) (%test-substitute-urls)) (match-lambda diff --git a/tests/derivations.scm b/tests/derivations.scm index a409fa99f0..9f1104a887 100644 --- a/tests/derivations.scm +++ b/tests/derivations.scm @@ -23,7 +23,7 @@ #:use-module (guix grafts) #:use-module (guix store) #:use-module (guix utils) - #:use-module (gcrypt hash) + #:use-module ((gcrypt hash) #:prefix gcrypt:) #:use-module (guix base32) #:use-module (guix tests) #:use-module (guix tests http) @@ -215,7 +215,7 @@ #:env-vars `(("url" . ,(object->string (%local-url)))) #:hash-algo 'sha256 - #:hash (sha256 (string->utf8 text))))) + #:hash (gcrypt:sha256 (string->utf8 text))))) (and (build-derivations %store (list drv)) (string=? (call-with-input-file (derivation->output-path drv) get-string-all) @@ -230,7 +230,7 @@ #:env-vars `(("url" . ,(object->string (%local-url)))) #:hash-algo 'sha256 - #:hash (sha256 (random-bytevector 100))))) ;wrong + #:hash (gcrypt:sha256 (random-bytevector 100))))) ;wrong (guard (c ((store-protocol-error? c) (string-contains (store-protocol-error-message c) "failed"))) (build-derivations %store (list drv)) @@ -245,7 +245,7 @@ #:env-vars `(("url" . ,(object->string (%local-url)))) #:hash-algo 'sha256 - #:hash (sha256 (random-bytevector 100))))) + #:hash (gcrypt:sha256 (random-bytevector 100))))) (guard (c ((store-protocol-error? c) (string-contains (store-protocol-error-message (pk c)) "failed"))) (build-derivations %store (list drv)) @@ -273,7 +273,7 @@ #:env-vars `(("url" . ,(object->string (%local-url)))) #:hash-algo 'sha256 - #:hash (sha256 (string->utf8 text))))) + #:hash (gcrypt:sha256 (string->utf8 text))))) (and (with-http-server `((200 ,text)) (build-derivations %store (list drv))) (with-http-server `((200 ,text)) @@ -317,7 +317,7 @@ (test-assert "fixed-output-derivation?" (let* ((builder (add-text-to-store %store "my-fixed-builder.sh" "echo -n hello > $out" '())) - (hash (sha256 (string->utf8 "hello"))) + (hash (gcrypt:sha256 (string->utf8 "hello"))) (drv (derivation %store "fixed" %bash `(,builder) #:sources (list builder) @@ -329,10 +329,10 @@ (map (lambda (hash-algorithm) (let* ((builder (add-text-to-store %store "my-fixed-builder.sh" "echo -n hello > $out" '())) - (sha256 (sha256 (string->utf8 "hello"))) - (hash (bytevector-hash + (sha256 (gcrypt:sha256 (string->utf8 "hello"))) + (hash (gcrypt:bytevector-hash (string->utf8 "hello") - (lookup-hash-algorithm hash-algorithm))) + (gcrypt:lookup-hash-algorithm hash-algorithm))) (drv (derivation %store (string-append "fixed-" (symbol->string hash-algorithm)) @@ -353,7 +353,7 @@ "echo -n hello > $out" '())) (builder2 (add-text-to-store %store "fixed-builder2.sh" "echo hey; echo -n hello > $out" '())) - (hash (sha256 (string->utf8 "hello"))) + (hash (gcrypt:sha256 (string->utf8 "hello"))) (drv1 (derivation %store "fixed" %bash `(,builder1) #:hash hash #:hash-algo 'sha256)) @@ -368,7 +368,7 @@ (test-assert "fixed-output derivation, recursive" (let* ((builder (add-text-to-store %store "my-fixed-builder.sh" "echo -n hello > $out" '())) - (hash (sha256 (string->utf8 "hello"))) + (hash (gcrypt:sha256 (string->utf8 "hello"))) (drv (derivation %store "fixed-rec" %bash `(,builder) #:sources (list builder) @@ -390,7 +390,7 @@ "echo -n hello > $out" '())) (builder2 (add-text-to-store %store "fixed-builder2.sh" "echo hey; echo -n hello > $out" '())) - (hash (sha256 (string->utf8 "hello"))) + (hash (gcrypt:sha256 (string->utf8 "hello"))) (fixed1 (derivation %store "fixed" %bash `(,builder1) #:hash hash #:hash-algo 'sha256)) @@ -427,7 +427,7 @@ "echo -n hello > $out" '())) (builder2 (add-text-to-store %store "fixed-builder2.sh" "echo hey; echo -n hello > $out" '())) - (hash (sha256 (string->utf8 "hello"))) + (hash (gcrypt:sha256 (string->utf8 "hello"))) (fixed1 (derivation %store "fixed" %bash `(,builder1) #:hash hash #:hash-algo 'sha256)) @@ -680,7 +680,7 @@ (let* ((value (getenv "GUIX_STATE_DIRECTORY")) (drv (derivation %store "leaked-env-vars" %bash '("-c" "echo -n $GUIX_STATE_DIRECTORY > $out") - #:hash (sha256 (string->utf8 value)) + #:hash (gcrypt:sha256 (string->utf8 value)) #:hash-algo 'sha256 #:sources (list %bash) #:leaked-env-vars '("GUIX_STATE_DIRECTORY")))) @@ -1106,7 +1106,7 @@ (builder2 '(call-with-output-file (pk 'difference-here! %output) (lambda (p) (write "hello" p)))) - (hash (sha256 (string->utf8 "hello"))) + (hash (gcrypt:sha256 (string->utf8 "hello"))) (input1 (build-expression->derivation %store "fixed" builder1 #:hash hash #:hash-algo 'sha256)) @@ -1127,7 +1127,7 @@ (builder2 '(call-with-output-file (pk 'difference-here! %output) (lambda (p) (write "hello" p)))) - (hash (sha256 (string->utf8 "hello"))) + (hash (gcrypt:sha256 (string->utf8 "hello"))) (input1 (build-expression->derivation %store "fixed" builder1 #:hash hash #:hash-algo 'sha256)) diff --git a/tests/graph.scm b/tests/graph.scm index 136260c7d1..0663d13b49 100644 --- a/tests/graph.scm +++ b/tests/graph.scm @@ -162,7 +162,11 @@ edges." (let-values (((backend nodes+edges) (make-recording-backend))) (let* ((m (lambda* (uri hash-type hash name #:key system) (text-file "foo-1.2.3.tar.gz" "This is a fake!"))) - (o (origin (method m) (uri "the-uri") (sha256 #vu8(0 1 2)))) + (o (origin + (method m) (uri "the-uri") + (sha256 + (base32 + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")))) (p (dummy-package "p" (source o)))) (run-with-store %store (export-graph (list p) 'port diff --git a/tests/packages.scm b/tests/packages.scm index c528d2080c..4935d4503e 100644 --- a/tests/packages.scm +++ b/tests/packages.scm @@ -29,7 +29,7 @@ #:renamer (lambda (name) (cond ((eq? name 'location) 'make-location) (else name)))) - #:use-module (gcrypt hash) + #:use-module ((gcrypt hash) #:hide (sha256)) #:use-module (guix derivations) #:use-module (guix packages) #:use-module (guix grafts) @@ -51,6 +51,7 @@ #:use-module (srfi srfi-34) #:use-module (srfi srfi-35) #:use-module (srfi srfi-64) + #:use-module (rnrs bytevectors) #:use-module (rnrs io ports) #:use-module (ice-9 vlist) #:use-module (ice-9 regex) @@ -497,6 +498,31 @@ (search-path %load-path "guix/base32.scm") get-bytevector-all))))) +(test-equal "package-source-derivation, origin, sha512" + "hello" + (let* ((bash (search-bootstrap-binary "bash" (%current-system))) + (builder (add-text-to-store %store "my-fixed-builder.sh" + "echo -n hello > $out" '())) + (method (lambda* (url hash-algo hash #:optional name + #:rest rest) + (and (eq? hash-algo 'sha512) + (raw-derivation name bash (list builder) + #:sources (list builder) + #:hash hash + #:hash-algo hash-algo)))) + (source (origin + (method method) + (uri "unused://") + (file-name "origin-sha512") + (hash (content-hash + (bytevector-hash (string->utf8 "hello") + (hash-algorithm sha512)) + sha512)))) + (drv (package-source-derivation %store source)) + (output (derivation->output-path drv))) + (build-derivations %store (list drv)) + (call-with-input-file output get-string-all))) + (unless (network-reachable?) (test-skip 1)) (test-equal "package-source-derivation, snippet" "OK" diff --git a/tests/store.scm b/tests/store.scm index f007846dc1..06f7939657 100644 --- a/tests/store.scm +++ b/tests/store.scm @@ -22,7 +22,7 @@ #:use-module (guix store) #:use-module (guix utils) #:use-module (guix monads) - #:use-module (gcrypt hash) + #:use-module ((gcrypt hash) #:prefix gcrypt:) #:use-module (guix base32) #:use-module (guix packages) #:use-module (guix derivations) @@ -321,7 +321,7 @@ #:env-vars `(("t2" . ,t2)))) (o (derivation->output-path d))) (with-derivation-narinfo d - (sha256 => (sha256 (string->utf8 t2))) + (sha256 => (gcrypt:sha256 (string->utf8 t2))) (references => (list t2)) (equal? (references/substitutes s (list o t3 t2 t1)) @@ -940,7 +940,7 @@ (foldm %store-monad (lambda (item result) (define ref-hash - (let-values (((port get) (open-sha256-port))) + (let-values (((port get) (gcrypt:open-sha256-port))) (write-file item port) (close-port port) (get))) @@ -1144,7 +1144,7 @@ (info (query-path-info %store item))) (and (equal? (path-info-references info) (list ref)) (equal? (path-info-hash info) - (sha256 + (gcrypt:sha256 (string->utf8 (call-with-output-string (cut write-file item <>)))))))) -- cgit v1.2.3 From feea1d0e62fdd09c4b209c549d65fe93e506d690 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 22 May 2020 00:23:15 +0200 Subject: lint: archival: Use 'origin-hash'. * guix/lint.scm (check-archival): Use 'origin-hash' instead of 'origin-sha256', removing hard-coded "sha256". --- guix/lint.scm | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'guix') diff --git a/guix/lint.scm b/guix/lint.scm index e192f292a4..6271894360 100644 --- a/guix/lint.scm +++ b/guix/lint.scm @@ -1154,15 +1154,18 @@ try again later") ((? origin? origin) ;; Since "save" origins are not supported for non-VCS source, all ;; we can do is tell whether a given tarball is available or not. - (if (origin-sha256 origin) ;XXX: for ungoogled-chromium - (match (lookup-content (origin-sha256 origin) "sha256") - (#f - (list (make-warning package - (G_ "source not archived on Software \ + (if (origin-hash origin) ;XXX: for ungoogled-chromium + (let ((hash (origin-hash origin))) + (match (lookup-content (content-hash-value hash) + (symbol->string + (content-hash-algorithm hash))) + (#f + (list (make-warning package + (G_ "source not archived on Software \ Heritage") - #:field 'source))) - ((? content?) - '())) + #:field 'source))) + ((? content?) + '()))) '())))) (match-lambda* ((key url method response) -- cgit v1.2.3 From c7d2dd69004b020de5d86898d2497ab3c8435c37 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 22 May 2020 00:24:35 +0200 Subject: upstream: Use 'origin-hash'. * guix/upstream.scm (update-package-source): Use 'origin-hash' instead of 'origin-sha256'. --- guix/upstream.scm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'guix') diff --git a/guix/upstream.scm b/guix/upstream.scm index c11de0b25b..67d0eeefbb 100644 --- a/guix/upstream.scm +++ b/guix/upstream.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès +;;; Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès ;;; Copyright © 2015 Alex Kost ;;; Copyright © 2019 Ricardo Wurmus ;;; @@ -441,7 +441,8 @@ new version string if an update was made, and #f otherwise." (if version-loc (let* ((loc (package-location package)) (old-version (package-version package)) - (old-hash (origin-sha256 (package-source package))) + (old-hash (content-hash-value + (origin-hash (package-source package)))) (old-url (match (origin-uri (package-source package)) ((? string? url) url) (_ #f))) -- cgit v1.2.3 From 881eaae1abe39ea324ea9c757e84d15eb30f869f Mon Sep 17 00:00:00 2001 From: zimoun Date: Thu, 21 May 2020 23:43:06 +0200 Subject: guix package: Support multiple profiles with '--list-installed'. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * guix/scripts/package.scm (process-query): List installed multiple profiles. * tests/guix-package-net.sh: Test it. Signed-off-by: Ludovic Courtès --- guix/scripts/package.scm | 20 +++++++++++--------- tests/guix-package-net.sh | 12 ++++++++++++ 2 files changed, 23 insertions(+), 9 deletions(-) (limited to 'guix') diff --git a/guix/scripts/package.scm b/guix/scripts/package.scm index a69efa365e..1246147798 100644 --- a/guix/scripts/package.scm +++ b/guix/scripts/package.scm @@ -675,12 +675,13 @@ doesn't need it." (define (process-query opts) "Process any query specified by OPTS. Return #t when a query was actually processed, #f otherwise." - (let* ((profiles (match (filter-map (match-lambda - (('profile . p) p) - (_ #f)) - opts) - (() (list %current-profile)) - (lst (reverse lst)))) + (let* ((profiles (delete-duplicates + (match (filter-map (match-lambda + (('profile . p) p) + (_ #f)) + opts) + (() (list %current-profile)) + (lst (reverse lst))))) (profile (match profiles ((head tail ...) head)))) (match (assoc-ref opts 'query) @@ -718,7 +719,8 @@ processed, #f otherwise." (('list-installed regexp) (let* ((regexp (and regexp (make-regexp* regexp regexp/icase))) - (manifest (profile-manifest profile)) + (manifest (concatenate-manifests + (map profile-manifest profiles))) (installed (manifest-entries manifest))) (leave-on-EPIPE (for-each (match-lambda @@ -729,8 +731,8 @@ processed, #f otherwise." name (or version "?") output path)))) ;; Show most recently installed packages last. - (reverse installed))) - #t)) + (reverse installed)))) + #t) (('list-available regexp) (let* ((regexp (and regexp (make-regexp* regexp regexp/icase))) diff --git a/tests/guix-package-net.sh b/tests/guix-package-net.sh index 48a94865e1..3876701fa2 100644 --- a/tests/guix-package-net.sh +++ b/tests/guix-package-net.sh @@ -1,6 +1,7 @@ # GNU Guix --- Functional package management for GNU # Copyright © 2012, 2013, 2014, 2015, 2017, 2019 Ludovic Courtès # Copyright © 2013 Nikita Karetnikov +# Copyright © 2020 Simon Tournier # # This file is part of GNU Guix. # @@ -78,6 +79,17 @@ esac test "`guix package -p "$profile" -I 'g.*e' | cut -f1`" = "guile-bootstrap" +guix package --bootstrap -p "$profile_alt" -i gcc-bootstrap +installed="`guix package -p "$profile" -p "$profile_alt" -I | cut -f1 | xargs echo | sort`" +case "x$installed" in + "gcc-bootstrap guile-bootstrap make-boot0") + true;; + "*") + false;; +esac +test "`guix package -p "$profile_alt" -p "$profile" -I | wc -l`" = "3" +rm "$profile_alt" + # List generations. test "`guix package -p "$profile" -l | cut -f1 | grep guile | head -n1`" \ = " guile-bootstrap" -- cgit v1.2.3 From c098c11be8eb9e0c12be42640721e3cb21c37628 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 20 May 2020 13:01:26 +0200 Subject: git: Add 'commit-relation'. * guix/git.scm (commit-relation): New procedure. * tests/git.scm ("commit-relation"): New test. --- guix/git.scm | 16 ++++++++++++++++ tests/git.scm | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/git.scm b/guix/git.scm index 92121156cf..249d622756 100644 --- a/guix/git.scm +++ b/guix/git.scm @@ -43,6 +43,7 @@ url+commit->name latest-repository-commit commit-difference + commit-relation git-checkout git-checkout? @@ -405,6 +406,21 @@ that of OLD." (cons head result) (set-insert head visited))))))) +(define (commit-relation old new) + "Return a symbol denoting the relation between OLD and NEW, two commit +objects: 'ancestor (meaning that OLD is an ancestor of NEW), 'descendant, or +'unrelated, or 'self (OLD and NEW are the same commit)." + (if (eq? old new) + 'self + (let ((newest (commit-closure new))) + (if (set-contains? newest old) + 'ancestor + (let* ((seen (list->setq (commit-parents new))) + (oldest (commit-closure old seen))) + (if (set-contains? oldest new) + 'descendant + 'unrelated)))))) + ;;; ;;; Checkouts. diff --git a/tests/git.scm b/tests/git.scm index 052f8a79c4..4a806abcc3 100644 --- a/tests/git.scm +++ b/tests/git.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2019 Ludovic Courtès +;;; Copyright © 2019, 2020 Ludovic Courtès ;;; ;;; This file is part of GNU Guix. ;;; @@ -122,4 +122,44 @@ (lset= eq? (commit-difference commit4 commit1 (list commit5)) (list commit2 commit3 commit4))))))) +(unless (which (git-command)) (test-skip 1)) +(test-equal "commit-relation" + '(self ;master3 master3 + ancestor ;master1 master3 + descendant ;master3 master1 + unrelated ;master2 branch1 + unrelated ;branch1 master2 + ancestor ;branch1 merge + descendant ;merge branch1 + ancestor ;master1 merge + descendant) ;merge master1 + (with-temporary-git-repository directory + '((add "a.txt" "A") + (commit "first commit") + (branch "hack") + (checkout "hack") + (add "1.txt" "1") + (commit "branch commit") + (checkout "master") + (add "b.txt" "B") + (commit "second commit") + (add "c.txt" "C") + (commit "third commit") + (merge "hack" "merge")) + (with-repository directory repository + (let ((master1 (find-commit repository "first")) + (master2 (find-commit repository "second")) + (master3 (find-commit repository "third")) + (branch1 (find-commit repository "branch")) + (merge (find-commit repository "merge"))) + (list (commit-relation master3 master3) + (commit-relation master1 master3) + (commit-relation master3 master1) + (commit-relation master2 branch1) + (commit-relation branch1 master2) + (commit-relation branch1 merge) + (commit-relation merge branch1) + (commit-relation master1 merge) + (commit-relation merge master1)))))) + (test-end "git") -- cgit v1.2.3 From 9b049de84ed101e2c0a5d071e76f424b3bc46bd9 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 20 May 2020 15:55:37 +0200 Subject: channels: 'latest-channel-instances' doesn't leak internal state. * guix/channels.scm (latest-channel-instances): Remove 'previous-channels' argument. Introduce 'loop' and use it. --- guix/channels.scm | 69 +++++++++++++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 35 deletions(-) (limited to 'guix') diff --git a/guix/channels.scm b/guix/channels.scm index f0174de767..e0a7a84f55 100644 --- a/guix/channels.scm +++ b/guix/channels.scm @@ -231,10 +231,9 @@ result is unspecified." #:select? (negate dot-git?)))) (channel-instance channel commit checkout)))) -(define* (latest-channel-instances store channels #:optional (previous-channels '())) +(define* (latest-channel-instances store channels) "Return a list of channel instances corresponding to the latest checkouts of -CHANNELS and the channels on which they depend. PREVIOUS-CHANNELS is a list -of previously processed channels." +CHANNELS and the channels on which they depend." ;; Only process channels that are unique, or that are more specific than a ;; previous channel specification. (define (ignore? channel others) @@ -245,38 +244,38 @@ of previously processed channels." (not (or (channel-commit a) (channel-commit b)))))))) - ;; Accumulate a list of instances. A list of processed channels is also - ;; accumulated to decide on duplicate channel specifications. - (define-values (resulting-channels instances) - (fold2 (lambda (channel previous-channels instances) - (if (ignore? channel previous-channels) - (values previous-channels instances) - (begin - (format (current-error-port) - (G_ "Updating channel '~a' from Git repository at '~a'...~%") - (channel-name channel) - (channel-url channel)) - (let ((instance (latest-channel-instance store channel))) - (let-values (((new-instances new-channels) - (latest-channel-instances - store - (channel-instance-dependencies instance) - previous-channels))) - (values (append (cons channel new-channels) - previous-channels) - (append (cons instance new-instances) - instances))))))) - previous-channels - '() ;instances - channels)) - - (let ((instance-name (compose channel-name channel-instance-channel))) - ;; Remove all earlier channel specifications if they are followed by a - ;; more specific one. - (values (delete-duplicates instances - (lambda (a b) - (eq? (instance-name a) (instance-name b)))) - resulting-channels))) + (let loop ((channels channels) + (previous-channels '())) + ;; Accumulate a list of instances. A list of processed channels is also + ;; accumulated to decide on duplicate channel specifications. + (define-values (resulting-channels instances) + (fold2 (lambda (channel previous-channels instances) + (if (ignore? channel previous-channels) + (values previous-channels instances) + (begin + (format (current-error-port) + (G_ "Updating channel '~a' from Git repository at '~a'...~%") + (channel-name channel) + (channel-url channel)) + (let ((instance (latest-channel-instance store channel))) + (let-values (((new-instances new-channels) + (loop (channel-instance-dependencies instance) + previous-channels))) + (values (append (cons channel new-channels) + previous-channels) + (append (cons instance new-instances) + instances))))))) + previous-channels + '() ;instances + channels)) + + (let ((instance-name (compose channel-name channel-instance-channel))) + ;; Remove all earlier channel specifications if they are followed by a + ;; more specific one. + (values (delete-duplicates instances + (lambda (a b) + (eq? (instance-name a) (instance-name b)))) + resulting-channels)))) (define* (checkout->channel-instance checkout #:key commit -- cgit v1.2.3 From 8d1d56578aa95118650ed2197bfb7fac40f4218a Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 20 May 2020 17:57:54 +0200 Subject: git: 'update-cached-checkout' returns the commit relation. * guix/git.scm (update-cached-checkout): Add #:starting-commit parameter. Call 'commit-relation' when #:starting-commit is true. Always return the relation or #f as the third value. (latest-repository-commit): Adjust accordingly. * guix/import/opam.scm (get-opam-repository): Likewise. * tests/channels.scm ("latest-channel-instances includes channel dependencies") ("latest-channel-instances excludes duplicate channel dependencies"): Update mock of 'update-cached-checkout' accordingly. --- guix/channels.scm | 2 +- guix/git.scm | 22 +++++++++++++++++----- guix/import/opam.scm | 2 +- tests/channels.scm | 12 ++++++------ 4 files changed, 25 insertions(+), 13 deletions(-) (limited to 'guix') diff --git a/guix/channels.scm b/guix/channels.scm index e0a7a84f55..75b767a94c 100644 --- a/guix/channels.scm +++ b/guix/channels.scm @@ -218,7 +218,7 @@ result is unspecified." (and (string=? (basename file) ".git") (eq? 'directory (stat:type stat)))) - (let-values (((checkout commit) + (let-values (((checkout commit relation) (update-cached-checkout (channel-url channel) #:ref (channel-reference channel)))) (when (guix-channel? channel) diff --git a/guix/git.scm b/guix/git.scm index 249d622756..ab3b5075b1 100644 --- a/guix/git.scm +++ b/guix/git.scm @@ -262,14 +262,16 @@ definitely available in REPOSITORY, false otherwise." #:key (ref '(branch . "master")) recursive? + starting-commit (log-port (%make-void-port "w")) (cache-directory (url-cache-directory url (%repository-cache-directory) #:recursive? recursive?))) - "Update the cached checkout of URL to REF in CACHE-DIRECTORY. Return two + "Update the cached checkout of URL to REF in CACHE-DIRECTORY. Return three values: the cache directory name, and the SHA1 commit (a string) corresponding -to REF. +to REF, and the relation of the new commit relative to STARTING-COMMIT (if +provided) as returned by 'commit-relation'. REF is pair whose key is [branch | commit | tag | tag-or-commit ] and value the associated data: [ | | | ]. @@ -302,7 +304,17 @@ When RECURSIVE? is true, check out submodules as well, if any." (remote-fetch (remote-lookup repository "origin")))) (when recursive? (update-submodules repository #:log-port log-port)) - (let ((oid (switch-to-ref repository canonical-ref))) + + ;; Note: call 'commit-relation' from here because it's more efficient + ;; than letting users re-open the checkout later on. + (let* ((oid (switch-to-ref repository canonical-ref)) + (new (and starting-commit + (commit-lookup repository oid))) + (old (and starting-commit + (commit-lookup repository + (string->oid starting-commit)))) + (relation (and starting-commit + (commit-relation old new)))) ;; Reclaim file descriptors and memory mappings associated with ;; REPOSITORY as soon as possible. @@ -310,7 +322,7 @@ When RECURSIVE? is true, check out submodules as well, if any." 'repository-close!) (repository-close! repository)) - (values cache-directory (oid->string oid)))))) + (values cache-directory (oid->string oid) relation))))) (define* (latest-repository-commit store url #:key @@ -343,7 +355,7 @@ Log progress and checkout info to LOG-PORT." (format log-port "updating checkout of '~a'...~%" url) (let*-values - (((checkout commit) + (((checkout commit _) (update-cached-checkout url #:recursive? recursive? #:ref ref diff --git a/guix/import/opam.scm b/guix/import/opam.scm index ae7df8a8b5..9cda3da006 100644 --- a/guix/import/opam.scm +++ b/guix/import/opam.scm @@ -115,7 +115,7 @@ (define (get-opam-repository) "Update or fetch the latest version of the opam repository and return the path to the repository." - (receive (location commit) + (receive (location commit _) (update-cached-checkout "https://github.com/ocaml/opam-repository") location)) diff --git a/tests/channels.scm b/tests/channels.scm index 910088ba15..3578b57204 100644 --- a/tests/channels.scm +++ b/tests/channels.scm @@ -136,11 +136,11 @@ (url "test"))) (test-dir (channel-instance-checkout instance--simple))) (mock ((guix git) update-cached-checkout - (lambda* (url #:key ref) + (lambda* (url #:key ref starting-commit) (match url - ("test" (values test-dir "caf3cabba9e")) + ("test" (values test-dir "caf3cabba9e" #f)) (_ (values (channel-instance-checkout instance--no-deps) - "abcde1234"))))) + "abcde1234" #f))))) (with-store store (let ((instances (latest-channel-instances store (list channel)))) (and (eq? 2 (length instances)) @@ -155,11 +155,11 @@ (url "test"))) (test-dir (channel-instance-checkout instance--with-dupes))) (mock ((guix git) update-cached-checkout - (lambda* (url #:key ref) + (lambda* (url #:key ref starting-commit) (match url - ("test" (values test-dir "caf3cabba9e")) + ("test" (values test-dir "caf3cabba9e" #f)) (_ (values (channel-instance-checkout instance--no-deps) - "abcde1234"))))) + "abcde1234" #f))))) (with-store store (let ((instances (latest-channel-instances store (list channel)))) (and (= 2 (length instances)) -- cgit v1.2.3 From 872898f768ae6d3b41eb93c5e183624bd1d157ff Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 20 May 2020 22:15:54 +0200 Subject: channels: 'latest-channel-instances' guards against non-forward updates. * guix/channels.scm (latest-channel-instance): Add #:starting-commit and pass it to 'update-cached-checkout'. Return the commit relation as a second value. (ensure-forward-channel-update): New procedure. (latest-channel-instances): Add #:current-channels and #:validate-pull. [current-commit]: New procedure. Pass #:starting-commit to 'latest-channel-instance'. When the returned relation is true, call VALIDATE-PULL. (latest-channel-derivation): Add #:current-channels and #:validate-pull. Pass them to 'latest-channel-instances*'. * tests/channels.scm ("latest-channel-instances #:validate-pull"): New test. --- guix/channels.scm | 91 +++++++++++++++++++++++++++++++++++++++++++++++------- tests/channels.scm | 35 +++++++++++++++++++++ 2 files changed, 115 insertions(+), 11 deletions(-) (limited to 'guix') diff --git a/guix/channels.scm b/guix/channels.scm index 75b767a94c..70e2d7f07c 100644 --- a/guix/channels.scm +++ b/guix/channels.scm @@ -73,6 +73,7 @@ channel-instances->manifest %channel-profile-hooks channel-instances->derivation + ensure-forward-channel-update profile-channels @@ -212,15 +213,18 @@ result is unspecified." (loop rest))))) (define* (latest-channel-instance store channel - #:key (patches %patches)) - "Return the latest channel instance for CHANNEL." + #:key (patches %patches) + starting-commit) + "Return two values: the latest channel instance for CHANNEL, and its +relation to STARTING-COMMIT when provided." (define (dot-git? file stat) (and (string=? (basename file) ".git") (eq? 'directory (stat:type stat)))) (let-values (((checkout commit relation) (update-cached-checkout (channel-url channel) - #:ref (channel-reference channel)))) + #:ref (channel-reference channel) + #:starting-commit starting-commit))) (when (guix-channel? channel) ;; Apply the relevant subset of PATCHES directly in CHECKOUT. This is ;; safe to do because 'switch-to-ref' eventually does a hard reset. @@ -229,11 +233,51 @@ result is unspecified." (let* ((name (url+commit->name (channel-url channel) commit)) (checkout (add-to-store store name #t "sha256" checkout #:select? (negate dot-git?)))) - (channel-instance channel commit checkout)))) - -(define* (latest-channel-instances store channels) + (values (channel-instance channel commit checkout) + relation)))) + +(define (ensure-forward-channel-update channel start instance relation) + "Raise an error if RELATION is not 'ancestor, meaning that START is not an +ancestor of the commit in INSTANCE, unless CHANNEL specifies a commit. + +This procedure implements a channel update policy meant to be used as a +#:validate-pull argument." + (match relation + ('ancestor #t) + ('self #t) + (_ + (raise (apply make-compound-condition + (condition + (&message (message + (format #f (G_ "\ +aborting update of channel '~a' to commit ~a, which is not a descendant of ~a") + (channel-name channel) + (channel-instance-commit instance) + start)))) + + ;; Don't show the hint when the user explicitly specified a + ;; commit in CHANNEL. + (if (channel-commit channel) + '() + (list (condition + (&fix-hint + (hint (G_ "This could indicate that the channel has +been tampered with and is trying to force a roll-back, preventing you from +getting the latest updates. If you think this is not the case, explicitly +allow non-forward updates."))))))))))) + +(define* (latest-channel-instances store channels + #:key + (current-channels '()) + (validate-pull + ensure-forward-channel-update)) "Return a list of channel instances corresponding to the latest checkouts of -CHANNELS and the channels on which they depend." +CHANNELS and the channels on which they depend. + +CURRENT-CHANNELS is the list of currently used channels. It is compared +against the newly-fetched instances of CHANNELS, and VALIDATE-PULL is called +for each channel update and can choose to emit warnings or raise an error, +depending on the policy it implements." ;; Only process channels that are unique, or that are more specific than a ;; previous channel specification. (define (ignore? channel others) @@ -244,6 +288,13 @@ CHANNELS and the channels on which they depend." (not (or (channel-commit a) (channel-commit b)))))))) + (define (current-commit name) + ;; Return the current commit for channel NAME. + (any (lambda (channel) + (and (eq? (channel-name channel) name) + (channel-commit channel))) + current-channels)) + (let loop ((channels channels) (previous-channels '())) ;; Accumulate a list of instances. A list of processed channels is also @@ -257,7 +308,15 @@ CHANNELS and the channels on which they depend." (G_ "Updating channel '~a' from Git repository at '~a'...~%") (channel-name channel) (channel-url channel)) - (let ((instance (latest-channel-instance store channel))) + (let*-values (((current) + (current-commit (channel-name channel))) + ((instance relation) + (latest-channel-instance store channel + #:starting-commit + current))) + (when relation + (validate-pull channel current instance relation)) + (let-values (((new-instances new-channels) (loop (channel-instance-dependencies instance) previous-channels))) @@ -617,10 +676,20 @@ channel instances." (define latest-channel-instances* (store-lift latest-channel-instances)) -(define* (latest-channel-derivation #:optional (channels %default-channels)) +(define* (latest-channel-derivation #:optional (channels %default-channels) + #:key + (current-channels '()) + (validate-pull + ensure-forward-channel-update)) "Return as a monadic value the derivation that builds the profile for the -latest instances of CHANNELS." - (mlet %store-monad ((instances (latest-channel-instances* channels))) +latest instances of CHANNELS. CURRENT-CHANNELS and VALIDATE-PULL are passed +to 'latest-channel-instances'." + (mlet %store-monad ((instances + (latest-channel-instances* channels + #:current-channels + current-channels + #:validate-pull + validate-pull))) (channel-instances->derivation instances))) (define (profile-channels profile) diff --git a/tests/channels.scm b/tests/channels.scm index 3578b57204..3b141428c8 100644 --- a/tests/channels.scm +++ b/tests/channels.scm @@ -37,6 +37,7 @@ #:use-module (srfi srfi-34) #:use-module (srfi srfi-35) #:use-module (srfi srfi-64) + #:use-module (ice-9 control) #:use-module (ice-9 match)) (test-begin "channels") @@ -178,6 +179,40 @@ "abc1234"))) instances))))))) +(unless (which (git-command)) (test-skip 1)) +(test-equal "latest-channel-instances #:validate-pull" + 'descendant + + ;; Make sure the #:validate-pull procedure receives the right values. + (let/ec return + (with-temporary-git-repository directory + '((add "a.txt" "A") + (commit "first commit") + (add "b.scm" "#t") + (commit "second commit")) + (with-repository directory repository + (let* ((commit1 (find-commit repository "first")) + (commit2 (find-commit repository "second")) + (spec (channel (url (string-append "file://" directory)) + (name 'foo))) + (new (channel (inherit spec) + (commit (oid->string (commit-id commit2))))) + (old (channel (inherit spec) + (commit (oid->string (commit-id commit1)))))) + (define (validate-pull channel current instance relation) + (return (and (eq? channel old) + (string=? (oid->string (commit-id commit2)) + current) + (string=? (oid->string (commit-id commit1)) + (channel-instance-commit instance)) + relation))) + + (with-store store + ;; Attempt a downgrade from NEW to OLD. + (latest-channel-instances store (list old) + #:current-channels (list new) + #:validate-pull validate-pull))))))) + (test-assert "channel-instances->manifest" ;; Compute the manifest for a graph of instances and make sure we get a ;; derivation graph that mirrors the instance graph. This test also ensures -- cgit v1.2.3 From 9744cc7b4636fafb772c94adb8f05961b5b39f16 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 20 May 2020 23:18:09 +0200 Subject: pull: Protect against downgrade attacks. * guix/scripts/pull.scm (%default-options): Add 'validate-pull'. (%options, show-help): Add '--allow-downgrades'. (warn-about-backward-updates): New procedure. (guix-pull): Pass #:current-channels and #:validate-pull to 'latest-channel-instances'. * guix/channels.scm (ensure-forward-channel-update): Add hint for when (channel-commit channel) is true. * doc/guix.texi (Invoking guix pull): Document '--allow-downgrades'. --- doc/guix.texi | 15 +++++++++++++++ guix/channels.scm | 36 ++++++++++++++++++++---------------- guix/scripts/pull.scm | 35 ++++++++++++++++++++++++++++++++--- 3 files changed, 67 insertions(+), 19 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index aa2b316c90..3d1b097447 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -3900,6 +3900,21 @@ Use @var{profile} instead of @file{~/.config/guix/current}. Show which channel commit(s) would be used and what would be built or substituted but do not actually do it. +@item --allow-downgrades +Allow pulling older or unrelated revisions of channels than those +currently in use. + +@cindex downgrade attacks, protection against +By default, @command{guix pull} protects against so-called ``downgrade +attacks'' whereby the Git repository of a channel would be reset to an +earlier or unrelated revision of itself, potentially leading you to +install older, known-vulnerable versions of software packages. + +@quotation Note +Make sure you understand its security implications before using +@option{--allow-downgrades}. +@end quotation + @item --system=@var{system} @itemx -s @var{system} Attempt to build for @var{system}---e.g., @code{i686-linux}---instead of diff --git a/guix/channels.scm b/guix/channels.scm index 70e2d7f07c..84c47fc0d0 100644 --- a/guix/channels.scm +++ b/guix/channels.scm @@ -246,25 +246,29 @@ This procedure implements a channel update policy meant to be used as a ('ancestor #t) ('self #t) (_ - (raise (apply make-compound-condition - (condition - (&message (message - (format #f (G_ "\ + (raise (make-compound-condition + (condition + (&message (message + (format #f (G_ "\ aborting update of channel '~a' to commit ~a, which is not a descendant of ~a") - (channel-name channel) - (channel-instance-commit instance) - start)))) - - ;; Don't show the hint when the user explicitly specified a - ;; commit in CHANNEL. - (if (channel-commit channel) - '() - (list (condition - (&fix-hint - (hint (G_ "This could indicate that the channel has + (channel-name channel) + (channel-instance-commit instance) + start)))) + + ;; If the user asked for a specific commit, they might want + ;; that to happen nevertheless, so tell them about the + ;; relevant 'guix pull' option. + (if (channel-commit channel) + (condition + (&fix-hint + (hint (G_ "Use @option{--allow-downgrades} to force +this downgrade.")))) + (condition + (&fix-hint + (hint (G_ "This could indicate that the channel has been tampered with and is trying to force a roll-back, preventing you from getting the latest updates. If you think this is not the case, explicitly -allow non-forward updates."))))))))))) +allow non-forward updates.")))))))))) (define* (latest-channel-instances store channels #:key diff --git a/guix/scripts/pull.scm b/guix/scripts/pull.scm index dfe7ee7ad5..c386d81b8e 100644 --- a/guix/scripts/pull.scm +++ b/guix/scripts/pull.scm @@ -81,7 +81,8 @@ (multiplexed-build-output? . #t) (graft? . #t) (debug . 0) - (verbosity . 1))) + (verbosity . 1) + (validate-pull . ,ensure-forward-channel-update))) (define (show-help) (display (G_ "Usage: guix pull [OPTION]... @@ -94,6 +95,8 @@ Download and deploy the latest version of Guix.\n")) --commit=COMMIT download the specified COMMIT")) (display (G_ " --branch=BRANCH download the tip of the specified BRANCH")) + (display (G_ " + --allow-downgrades allow downgrades to earlier channel revisions")) (display (G_ " -N, --news display news compared to the previous generation")) (display (G_ " @@ -158,6 +161,10 @@ Download and deploy the latest version of Guix.\n")) (option '("branch") #t #f (lambda (opt name arg result) (alist-cons 'ref `(branch . ,arg) result))) + (option '("allow-downgrades") #f #f + (lambda (opt name arg result) + (alist-cons 'validate-pull warn-about-backward-updates + result))) (option '(#\p "profile") #t #f (lambda (opt name arg result) (alist-cons 'profile (canonicalize-profile arg) @@ -188,6 +195,21 @@ Download and deploy the latest version of Guix.\n")) %standard-build-options)) +(define (warn-about-backward-updates channel start instance relation) + "Warn about non-forward updates of CHANNEL from START to INSTANCE, without +aborting." + (match relation + ((or 'ancestor 'self) + #t) + ('descendant + (warning (G_ "rolling back channel '~a' from ~a to ~a~%") + (channel-name channel) start + (channel-instance-commit instance))) + ('unrelated + (warning (G_ "moving channel '~a' from ~a to unrelated commit ~a~%") + (channel-name channel) start + (channel-instance-commit instance))))) + (define* (display-profile-news profile #:key concise? current-is-newer?) "Display what's up in PROFILE--new packages, and all that. If @@ -749,7 +771,9 @@ Use '~/.config/guix/channels.scm' instead.")) (substitutes? (assoc-ref opts 'substitutes?)) (dry-run? (assoc-ref opts 'dry-run?)) (channels (channel-list opts)) - (profile (or (assoc-ref opts 'profile) %current-profile))) + (profile (or (assoc-ref opts 'profile) %current-profile)) + (current-channels (profile-channels profile)) + (validate-pull (assoc-ref opts 'validate-pull))) (cond ((assoc-ref opts 'query) (process-query opts profile)) ((assoc-ref opts 'generation) @@ -766,7 +790,12 @@ Use '~/.config/guix/channels.scm' instead.")) (ensure-default-profile) (honor-x509-certificates store) - (let ((instances (latest-channel-instances store channels))) + (let ((instances + (latest-channel-instances store channels + #:current-channels + current-channels + #:validate-pull + validate-pull))) (format (current-error-port) (N_ "Building from this channel:~%" "Building from these channels:~%" -- cgit v1.2.3