summaryrefslogtreecommitdiff
path: root/guix
diff options
context:
space:
mode:
authorEfraim Flashner <efraim@flashner.co.il>2021-12-19 15:15:11 +0200
committerEfraim Flashner <efraim@flashner.co.il>2021-12-19 15:15:11 +0200
commit6ccf8ea81f95963c0b7f945648106576008ee105 (patch)
treef39f596e6c3e98ff1e9f1de0ad41c977e9dd37c1 /guix
parentfcaed5b81e893f34d77527fbef389ca628ca882d (diff)
parent9f916d14765b00309c742fcbff0cfabdd10dcf05 (diff)
Merge branch 'master' into core-updates
Diffstat (limited to 'guix')
-rw-r--r--guix/build-system/asdf.scm3
-rw-r--r--guix/build-system/cmake.scm2
-rw-r--r--guix/build-system/glib-or-gtk.scm7
-rw-r--r--guix/build-system/gnu.scm4
-rw-r--r--guix/build-system/go.scm120
-rw-r--r--guix/build-system/haskell.scm11
-rw-r--r--guix/build-system/julia.scm8
-rw-r--r--guix/build-system/linux-module.scm33
-rw-r--r--guix/build-system/meson.scm14
-rw-r--r--guix/build-system/r.scm2
-rw-r--r--guix/build-system/renpy.scm1
-rw-r--r--guix/build-system/trivial.scm2
-rw-r--r--guix/build/download.scm26
-rw-r--r--guix/build/emacs-build-system.scm7
-rw-r--r--guix/build/emacs-utils.scm2
-rw-r--r--guix/build/glib-or-gtk-build-system.scm168
-rw-r--r--guix/build/go-build-system.scm6
-rw-r--r--guix/build/haskell-build-system.scm46
-rw-r--r--guix/build/julia-build-system.scm84
-rw-r--r--guix/build/linux-module-build-system.scm35
-rw-r--r--guix/build/meson-build-system.scm18
-rw-r--r--guix/build/minetest-build-system.scm25
-rw-r--r--guix/build/po.scm117
-rw-r--r--guix/build/qt-utils.scm8
-rw-r--r--guix/build/renpy-build-system.scm2
-rw-r--r--guix/build/syscalls.scm97
-rw-r--r--guix/build/union.scm7
-rw-r--r--guix/cache.scm10
-rw-r--r--guix/channels.scm9
-rw-r--r--guix/cpio.scm21
-rw-r--r--guix/derivations.scm31
-rw-r--r--guix/diagnostics.scm38
-rw-r--r--guix/discovery.scm32
-rw-r--r--guix/docker.scm9
-rw-r--r--guix/download.scm45
-rw-r--r--guix/extracting-download.scm179
-rw-r--r--guix/gexp.scm20
-rw-r--r--guix/git.scm92
-rw-r--r--guix/gnupg.scm31
-rw-r--r--guix/graph.scm45
-rw-r--r--guix/import/cran.scm110
-rw-r--r--guix/import/crate.scm8
-rw-r--r--guix/import/egg.scm37
-rw-r--r--guix/import/elpa.scm24
-rw-r--r--guix/import/git.scm225
-rw-r--r--guix/import/gnu.scm3
-rw-r--r--guix/import/go.scm13
-rw-r--r--guix/import/hackage.scm20
-rw-r--r--guix/import/minetest.scm75
-rw-r--r--guix/import/opam.scm8
-rw-r--r--guix/import/print.scm85
-rw-r--r--guix/import/pypi.scm85
-rw-r--r--guix/import/stackage.scm111
-rw-r--r--guix/import/texlive.scm264
-rw-r--r--guix/import/utils.scm17
-rw-r--r--guix/inferior.scm17
-rw-r--r--guix/licenses.scm2
-rw-r--r--guix/lint.scm32
-rw-r--r--guix/narinfo.scm4
-rw-r--r--guix/packages.scm152
-rw-r--r--guix/profiles.scm178
-rw-r--r--guix/progress.scm26
-rw-r--r--guix/scripts/challenge.scm97
-rw-r--r--guix/scripts/environment.scm480
-rw-r--r--guix/scripts/graph.scm11
-rw-r--r--guix/scripts/hash.scm107
-rw-r--r--guix/scripts/home.scm538
-rw-r--r--guix/scripts/home/import.scm186
-rw-r--r--guix/scripts/import/cran.scm35
-rw-r--r--guix/scripts/import/egg.scm34
-rw-r--r--guix/scripts/import/pypi.scm32
-rw-r--r--guix/scripts/import/texlive.scm16
-rw-r--r--guix/scripts/offload.scm55
-rw-r--r--guix/scripts/package.scm47
-rw-r--r--guix/scripts/publish.scm82
-rw-r--r--guix/scripts/refresh.scm52
-rw-r--r--guix/scripts/shell.scm399
-rw-r--r--guix/scripts/style.scm58
-rw-r--r--guix/scripts/system.scm17
-rw-r--r--guix/self.scm115
-rw-r--r--guix/status.scm2
-rw-r--r--guix/store.scm22
-rw-r--r--guix/store/deduplication.scm69
-rw-r--r--guix/substitutes.scm6
-rw-r--r--guix/svn-download.scm31
-rw-r--r--guix/swh.scm63
-rw-r--r--guix/tests.scm30
-rw-r--r--guix/tests/git.scm7
-rw-r--r--guix/ui.scm33
-rw-r--r--guix/utils.scm6
90 files changed, 4353 insertions, 1190 deletions
diff --git a/guix/build-system/asdf.scm b/guix/build-system/asdf.scm
index f043e6a7a2..a0f4634db0 100644
--- a/guix/build-system/asdf.scm
+++ b/guix/build-system/asdf.scm
@@ -151,7 +151,8 @@ set up using CL source package conventions."
name))
(define (has-from-build-system? pkg)
- (eq? from-build-system (package-build-system pkg)))
+ (and (package? pkg)
+ (eq? from-build-system (package-build-system pkg))))
(define (find-input-package pkg)
(let* ((name (package-name pkg))
diff --git a/guix/build-system/cmake.scm b/guix/build-system/cmake.scm
index d500eccfde..2056c04153 100644
--- a/guix/build-system/cmake.scm
+++ b/guix/build-system/cmake.scm
@@ -158,6 +158,7 @@ provides a 'CMakeLists.txt' file as its build system."
(gexp->derivation name build
#:system system
#:target #f
+ #:graft? #f
#:substitutable? substitutable?
#:guile-for-build guile)))
@@ -248,6 +249,7 @@ build system."
(gexp->derivation name builder
#:system system
#:target target
+ #:graft? #f
#:substitutable? substitutable?
#:guile-for-build guile)))
diff --git a/guix/build-system/glib-or-gtk.scm b/guix/build-system/glib-or-gtk.scm
index ec491ff0bd..aa9703829b 100644
--- a/guix/build-system/glib-or-gtk.scm
+++ b/guix/build-system/glib-or-gtk.scm
@@ -26,6 +26,8 @@
#:use-module (guix monads)
#:use-module (guix derivations)
#:use-module (guix search-paths)
+ #:use-module ((guix build glib-or-gtk-build-system)
+ #:select (%gdk-pixbuf-loaders-cache-file))
#:use-module (guix build-system)
#:use-module (guix build-system gnu)
#:use-module (guix packages)
@@ -33,7 +35,8 @@
#:export (%glib-or-gtk-build-system-modules
glib-or-gtk-build
glib-or-gtk-cross-build
- glib-or-gtk-build-system))
+ glib-or-gtk-build-system)
+ #:re-export (%gdk-pixbuf-loaders-cache-file)) ;for convenience
;; Commentary:
;;
@@ -186,6 +189,7 @@
(gexp->derivation name build
#:system system
#:target #f
+ #:graft? #f
#:allowed-references allowed-references
#:disallowed-references disallowed-references
#:guile-for-build guile)))
@@ -279,6 +283,7 @@
(gexp->derivation name builder
#:system system
#:target target
+ #:graft? #f
#:modules imported-modules
#:allowed-references allowed-references
#:disallowed-references disallowed-references
diff --git a/guix/build-system/gnu.scm b/guix/build-system/gnu.scm
index ea91be5bcd..651415098e 100644
--- a/guix/build-system/gnu.scm
+++ b/guix/build-system/gnu.scm
@@ -423,9 +423,12 @@ are allowed to refer to."
(mlet %store-monad ((guile (package->derivation (or guile (default-guile))
system #:graft? #f)))
+ ;; Note: Always pass #:graft? #f. Without it, ALLOWED-REFERENCES &
+ ;; co. would be interpreted as referring to grafted packages.
(gexp->derivation name builder
#:system system
#:target #f
+ #:graft? #f
#:substitutable? substitutable?
#:allowed-references allowed-references
#:disallowed-references disallowed-references
@@ -560,6 +563,7 @@ platform."
(gexp->derivation name builder
#:system system
#:target target
+ #:graft? #f
#:modules imported-modules
#:substitutable? substitutable?
#:allowed-references allowed-references
diff --git a/guix/build-system/go.scm b/guix/build-system/go.scm
index b62f2a897b..18824c79d9 100644
--- a/guix/build-system/go.scm
+++ b/guix/build-system/go.scm
@@ -166,8 +166,8 @@ commit hash and its date rather than a proper release tag."
(tests? #t)
(allow-go-reference? #f)
(system (%current-system))
- (goarch (first (go-target (%current-system))))
- (goos (last (go-target (%current-system))))
+ (goarch #f)
+ (goos #f)
(guile #f)
(imported-modules %go-build-system-modules)
(modules '((guix build go-build-system)
@@ -201,11 +201,11 @@ commit hash and its date rather than a proper release tag."
#:system system
#:guile-for-build guile)))
-(define* (go-cross-build store name
+(define* (go-cross-build name
#:key
- target native-drvs target-drvs
- (phases '(@ (guix build go-build-system)
- %standard-phases))
+ source target
+ build-inputs target-inputs host-inputs
+ (phases '%standard-phases)
(outputs '("out"))
(search-paths '())
(native-search-paths '())
@@ -213,7 +213,7 @@ commit hash and its date rather than a proper release tag."
(import-path "")
(unpack-path "")
(build-flags ''())
- (tests? #f) ; nothing can be done
+ (tests? #f) ; nothing can be done
(allow-go-reference? #f)
(system (%current-system))
(goarch (first (go-target target)))
@@ -225,73 +225,53 @@ commit hash and its date rather than a proper release tag."
(guix build utils))))
"Cross-build NAME using GO, where TARGET is a GNU triplet and with INPUTS."
(define builder
- `(begin
- (use-modules ,@modules)
- (let ()
- (define %build-host-inputs
- ',(map (match-lambda
- ((name (? derivation? drv) sub ...)
- `(,name . ,(apply derivation->output-path drv sub)))
- ((name path)
- `(,name . ,path)))
- native-drvs))
+ #~(begin
+ (use-modules #$@(sexp->gexp modules))
- (define %build-target-inputs
- ',(map (match-lambda
- ((name (? derivation? drv) sub ...)
- `(,name . ,(apply derivation->output-path drv sub)))
- ((name (? package? pkg) sub ...)
- (let ((drv (package-cross-derivation store pkg
- target system)))
- `(,name . ,(apply derivation->output-path drv sub))))
- ((name path)
- `(,name . ,path)))
- target-drvs))
+ (define %build-host-inputs
+ #+(input-tuples->gexp build-inputs))
- (go-build #:name ,name
- #:source ,(match (assoc-ref native-drvs "source")
- (((? derivation? source))
- (derivation->output-path source))
- ((source)
- source)
- (source
- source))
- #:system ,system
- #:phases ,phases
- #:outputs %outputs
- #:target ,target
- #:goarch ,goarch
- #:goos ,goos
- #:inputs %build-target-inputs
- #:native-inputs %build-host-inputs
- #:search-paths ',(map search-path-specification->sexp
- search-paths)
- #:native-search-paths ',(map
- search-path-specification->sexp
- native-search-paths)
- #:install-source? ,install-source?
- #:import-path ,import-path
- #:unpack-path ,unpack-path
- #:build-flags ,build-flags
- #:tests? ,tests?
- #:allow-go-reference? ,allow-go-reference?
- #:inputs %build-inputs))))
+ (define %build-target-inputs
+ (append #$(input-tuples->gexp host-inputs)
+ #+(input-tuples->gexp target-inputs)))
+
+ (define %build-inputs
+ (append %build-host-inputs %build-target-inputs))
- (define guile-for-build
- (match guile
- ((? package?)
- (package-derivation store guile system #:graft? #f))
- (#f ; the default
- (let* ((distro (resolve-interface '(gnu packages commencement)))
- (guile (module-ref distro 'guile-final)))
- (package-derivation store guile system #:graft? #f)))))
+ (define %outputs
+ #$(outputs->gexp outputs))
- (build-expression->derivation store name builder
- #:system system
- #:inputs (append native-drvs target-drvs)
- #:outputs outputs
- #:modules imported-modules
- #:guile-for-build guile-for-build))
+ (go-build #:name #$name
+ #:source #+source
+ #:system #$system
+ #:phases #$phases
+ #:outputs %outputs
+ #:target #$target
+ #:goarch #$goarch
+ #:goos #$goos
+ #:inputs %build-target-inputs
+ #:native-inputs %build-host-inputs
+ #:search-paths '#$(map search-path-specification->sexp
+ search-paths)
+ #:native-search-paths '#$(map
+ search-path-specification->sexp
+ native-search-paths)
+ #:install-source? #$install-source?
+ #:import-path #$import-path
+ #:unpack-path #$unpack-path
+ #:build-flags #$build-flags
+ #:tests? #$tests?
+ #:allow-go-reference? #$allow-go-reference?
+ #:inputs %build-inputs)))
+
+ (mlet %store-monad ((guile (package->derivation (or guile (default-guile))
+ system #:graft? #f)))
+ (gexp->derivation name builder
+ #:system system
+ #:target target
+ #:graft? #f
+ #:substitutable? substitutable?
+ #:guile-for-build guile)))
(define go-build-system
(build-system
diff --git a/guix/build-system/haskell.scm b/guix/build-system/haskell.scm
index 3770304745..dc83512d30 100644
--- a/guix/build-system/haskell.scm
+++ b/guix/build-system/haskell.scm
@@ -3,6 +3,7 @@
;;; Copyright © 2020 Timothy Sample <samplet@ngyro.com>
;;; Copyright © 2020 Simon Tournier <zimon.toutoune@gmail.com>
;;; Copyright © 2021 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -32,7 +33,9 @@
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-26)
- #:export (%haskell-build-system-modules
+ #:export (hackage-uri
+
+ %haskell-build-system-modules
haskell-build
haskell-build-system))
@@ -43,6 +46,12 @@
;;
;; Code:
+(define (hackage-uri name version)
+ "Return a URI string for the Haskell package hosted on Hackage corresponding
+to NAME and VERSION."
+ (string-append "https://hackage.haskell.org/package/" name "/"
+ name "-" version ".tar.gz"))
+
(define %haskell-build-system-modules
;; Build-side modules imported by default.
`((guix build haskell-build-system)
diff --git a/guix/build-system/julia.scm b/guix/build-system/julia.scm
index 5b824d7f0a..6261f8a55a 100644
--- a/guix/build-system/julia.scm
+++ b/guix/build-system/julia.scm
@@ -1,6 +1,8 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2019 Nicolò Balzarotti <nicolo@nixo.xyz>
;;; Copyright © 2021 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2021 Jean-Baptiste Volatier <jbv@pm.me>
+;;; Copyright © 2021 Simon Tournier <zimon.toutoune@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -78,12 +80,14 @@
(define* (julia-build name inputs
#:key source
(tests? #t)
+ (parallel-tests? #t)
(phases '%standard-phases)
(outputs '("out"))
(search-paths '())
(system (%current-system))
(guile #f)
(julia-package-name #f)
+ (julia-package-uuid #f)
(imported-modules %julia-build-system-modules)
(modules '((guix build julia-build-system)
(guix build utils))))
@@ -96,13 +100,15 @@
#:source #+source
#:system #$system
#:tests? #$tests?
+ #:parallel-tests? #$parallel-tests?
#:phases #$phases
#:outputs #$(outputs->gexp outputs)
#:search-paths '#$(sexp->gexp
(map search-path-specification->sexp
search-paths))
#:inputs #$(input-tuples->gexp inputs)
- #:julia-package-name #$julia-package-name))))
+ #:julia-package-name #$julia-package-name
+ #:julia-package-uuid #$julia-package-uuid))))
(mlet %store-monad ((guile (package->derivation (or guile (default-guile))
system #:graft? #f)))
diff --git a/guix/build-system/linux-module.scm b/guix/build-system/linux-module.scm
index 7bafee5a7a..57fce8e96e 100644
--- a/guix/build-system/linux-module.scm
+++ b/guix/build-system/linux-module.scm
@@ -2,6 +2,7 @@
;;; Copyright © 2019 Danny Milosavljevic <dannym@scratchpost.org>
;;; Copyright © 2020 Mathieu Othacehe <m.othacehe@gmail.com>
;;; Copyright © 2021 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2021 Tobias Geerinckx-Rice <me@tobias.gr>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -158,6 +159,7 @@
(phases '%standard-phases)
(outputs '("out"))
(make-flags ''())
+ (parallel-build? #t)
(system (%current-system))
(source-directory ".")
(guile #f)
@@ -171,20 +173,22 @@
(with-imported-modules imported-modules
#~(begin
(use-modules #$@(sexp->gexp modules))
- (linux-module-build #:name #$name
- #:source #+source
- #:source-directory #$source-directory
- #:search-paths '#$(sexp->gexp
- (map search-path-specification->sexp
- search-paths))
- #:phases #$phases
- #:system #$system
- #:target #$target
- #:arch #$(system->arch (or target system))
- #:tests? #$tests?
- #:outputs #$(outputs->gexp outputs)
- #:make-flags #$make-flags
- #:inputs #$(input-tuples->gexp inputs)))))
+ #$(with-build-variables inputs outputs
+ #~(linux-module-build #:name #$name
+ #:source #+source
+ #:source-directory #$source-directory
+ #:search-paths '#$(sexp->gexp
+ (map search-path-specification->sexp
+ search-paths))
+ #:phases #$phases
+ #:system #$system
+ #:target #$target
+ #:arch #$(system->arch (or target system))
+ #:tests? #$tests?
+ #:outputs #$(outputs->gexp outputs)
+ #:make-flags #$make-flags
+ #:parallel-build? #$parallel-build?
+ #:inputs #$(input-tuples->gexp inputs))))))
(mlet %store-monad ((guile (package->derivation (or guile (default-guile))
system #:graft? #f)))
@@ -201,6 +205,7 @@
(guile #f)
(outputs '("out"))
(make-flags ''())
+ (parallel-build? #t)
(search-paths '())
(native-search-paths '())
(tests? #f)
diff --git a/guix/build-system/meson.scm b/guix/build-system/meson.scm
index dcad3f322d..ba7441a3eb 100644
--- a/guix/build-system/meson.scm
+++ b/guix/build-system/meson.scm
@@ -169,7 +169,7 @@ TRIPLET."
(search-paths '())
(build-type "debugoptimized")
(tests? #t)
- (test-target "test")
+ (test-options ''())
(glib-or-gtk? #f)
(parallel-build? #t)
(parallel-tests? #f)
@@ -218,7 +218,7 @@ has a 'meson.build' file."
configure-flags)
#:build-type #$build-type
#:tests? #$tests?
- #:test-target #$test-target
+ #:test-options #$(sexp->gexp test-options)
#:parallel-build? #$parallel-build?
#:parallel-tests? #$parallel-tests?
#:validate-runpath? #$validate-runpath?
@@ -233,6 +233,7 @@ has a 'meson.build' file."
(gexp->derivation name builder
#:system system
#:target #f
+ #:graft? #f
#:substitutable? substitutable?
#:allowed-references allowed-references
#:disallowed-references disallowed-references
@@ -250,7 +251,7 @@ has a 'meson.build' file."
(build-type "debugoptimized")
(tests? #f)
- (test-target "test")
+ (test-options ''())
(glib-or-gtk? #f)
(parallel-build? #t)
(parallel-tests? #f)
@@ -280,7 +281,7 @@ SOURCE has a 'meson.build' file."
(if (null? target-inputs)
(input-tuples->gexp host-inputs)
#~(append #$(input-tuples->gexp host-inputs)
- #+(input-tuples->gexp target-inputs))))
+ #+(input-tuples->gexp target-inputs))))
(define builder
(with-imported-modules imported-modules
#~(begin
@@ -305,7 +306,7 @@ SOURCE has a 'meson.build' file."
#:native-inputs #+(input-tuples->gexp build-inputs)
#:search-paths '#$(sexp->gexp
(map search-path-specification->sexp
- search-paths))
+ search-paths))
#:native-search-paths '#$(sexp->gexp
(map search-path-specification->sexp
native-search-paths))
@@ -317,7 +318,7 @@ SOURCE has a 'meson.build' file."
configure-flags))
#:build-type #$build-type
#:tests? #$tests?
- #:test-target #$test-target
+ #:test-options #$(sexp->gexp test-options)
#:parallel-build? #$parallel-build?
#:parallel-tests? #$parallel-tests?
#:validate-runpath? #$validate-runpath?
@@ -332,6 +333,7 @@ SOURCE has a 'meson.build' file."
(gexp->derivation name builder
#:system system
#:target target
+ #:graft? #f
#:substitutable? substitutable?
#:allowed-references allowed-references
#:disallowed-references disallowed-references
diff --git a/guix/build-system/r.scm b/guix/build-system/r.scm
index be6a600c28..2c82390ba6 100644
--- a/guix/build-system/r.scm
+++ b/guix/build-system/r.scm
@@ -61,7 +61,7 @@ release corresponding to NAME and VERSION."
"/src/contrib/"
name "_" version ".tar.gz")
;; TODO: use %bioconductor-version from (guix import cran)
- (string-append "https://bioconductor.org/packages/3.13"
+ (string-append "https://bioconductor.org/packages/3.14"
type-url-part
"/src/contrib/"
name "_" version ".tar.gz"))))
diff --git a/guix/build-system/renpy.scm b/guix/build-system/renpy.scm
index 0ee73ec969..f1070951ee 100644
--- a/guix/build-system/renpy.scm
+++ b/guix/build-system/renpy.scm
@@ -1,6 +1,7 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2021 Leo Prikler <leo.prikler@student.tugraz.at>
;;; Copyright © 2021 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2021 Liliana Marie Prikler <liliana.prikler@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
diff --git a/guix/build-system/trivial.scm b/guix/build-system/trivial.scm
index cd35c846ce..378ae481b9 100644
--- a/guix/build-system/trivial.scm
+++ b/guix/build-system/trivial.scm
@@ -61,6 +61,7 @@ ignored."
(gexp->derivation name (with-build-variables inputs outputs builder)
#:system system
#:target #f
+ #:graft? #f
#:modules modules
#:allowed-references allowed-references
#:guile-for-build guile)))
@@ -85,6 +86,7 @@ ignored."
builder)
#:system system
#:target target
+ #:graft? #f
#:modules modules
#:allowed-references allowed-references
#:guile-for-build guile)))
diff --git a/guix/build/download.scm b/guix/build/download.scm
index c8ddadfdd4..7c310e94f1 100644
--- a/guix/build/download.scm
+++ b/guix/build/download.scm
@@ -36,7 +36,7 @@
#:use-module (srfi srfi-26)
#:autoload (ice-9 ftw) (scandir)
#:autoload (guix base16) (bytevector->base16-string)
- #:autoload (guix swh) (swh-download-directory)
+ #:autoload (guix swh) (swh-download-directory %verify-swh-certificate?)
#:use-module (ice-9 match)
#:use-module (ice-9 format)
#:export (open-socket-for-uri
@@ -646,6 +646,8 @@ and write the output to FILE."
#:verify-certificate?
verify-certificate?
#:timeout timeout)))
+ (format #t "Retrieving Disarchive spec from ~a ...~%"
+ (uri->string uri))
(let ((specification (read port)))
(close-port port)
specification))))
@@ -674,10 +676,23 @@ and write the output to FILE."
(match (fetch-specification uris)
(#f (format #t "could not find its Disarchive specification~%")
#f)
- (spec (parameterize ((%disarchive-log-port (current-output-port)))
+ (spec (parameterize ((%disarchive-log-port (current-output-port))
+ (%verify-swh-certificate? verify-certificate?))
(false-if-exception*
(disarchive-assemble spec file #:resolver resolve))))))))
+(define (internet-archive-uri uri)
+ "Return a URI corresponding to an Internet Archive backup of URI, or #f if
+URI does not denote a Web URI."
+ (and (memq (uri-scheme uri) '(http https))
+ (let* ((now (time-utc->date (current-time time-utc)))
+ (date (date->string now "~Y~m~d~H~M~S")))
+ ;; Note: the date in the URL can be anything and web.archive.org
+ ;; automatically redirects to the closest date.
+ (build-uri 'https #:host "web.archive.org"
+ #:path (string-append "/web/" date "/"
+ (uri->string uri))))))
+
(define* (url-fetch url file
#:key
(timeout 10) (verify-certificate? #t)
@@ -769,7 +784,12 @@ otherwise simply ignore them."
(setvbuf (current-error-port) 'line)
- (let try ((uri (append uri content-addressed-uris)))
+ (let try ((uri (append uri content-addressed-uris
+ (match uri
+ ((first . _)
+ (or (and=> (internet-archive-uri first) list)
+ '()))
+ (() '())))))
(match uri
((uri tail ...)
(or (fetch uri file)
diff --git a/guix/build/emacs-build-system.scm b/guix/build/emacs-build-system.scm
index ba2c1b4aad..ab77e57f33 100644
--- a/guix/build/emacs-build-system.scm
+++ b/guix/build/emacs-build-system.scm
@@ -53,8 +53,7 @@
;; These are the default inclusion/exclusion regexps for the install phase.
(define %default-include '("^[^/]*\\.el$" "^[^/]*\\.info$" "^doc/.*\\.info$"))
-(define %default-exclude '("^\\.dir-locals\\.el$" "-pkg\\.el$"
- "^[^/]*tests?\\.el$"))
+(define %default-exclude '("^\\.dir-locals\\.el$" "^[^/]*tests?\\.el$"))
(define gnu:unpack (assoc-ref gnu:%standard-phases 'unpack))
@@ -111,7 +110,7 @@ environment variable\n" source-directory))
(define* (build #:key outputs inputs #:allow-other-keys)
"Compile .el files."
- (let* ((emacs (string-append (assoc-ref inputs "emacs") "/bin/emacs"))
+ (let* ((emacs (search-input-file inputs "/bin/emacs"))
(out (assoc-ref outputs "out")))
(setenv "SHELL" "sh")
(parameterize ((%emacs emacs))
@@ -220,7 +219,7 @@ parallel. PARALLEL-TESTS? is ignored when using a non-make TEST-COMMAND."
(define* (make-autoloads #:key outputs inputs #:allow-other-keys)
"Generate the autoloads file."
- (let* ((emacs (string-append (assoc-ref inputs "emacs") "/bin/emacs"))
+ (let* ((emacs (search-input-file inputs "/bin/emacs"))
(out (assoc-ref outputs "out"))
(elpa-name-ver (store-directory->elpa-name-version out))
(elpa-name (package-name->name+version elpa-name-ver))
diff --git a/guix/build/emacs-utils.scm b/guix/build/emacs-utils.scm
index 5f7ba71244..64ef40e25a 100644
--- a/guix/build/emacs-utils.scm
+++ b/guix/build/emacs-utils.scm
@@ -2,7 +2,7 @@
;;; Copyright © 2014, 2018 Mark H Weaver <mhw@netris.org>
;;; Copyright © 2014 Alex Kost <alezost@gmail.com>
;;; Copyright © 2018, 2020 Maxim Cournoyer <maxim.cournoyer@gmail.com>
-;;; Copyright © 2019 Leo Prikler <leo.prikler@student.tugraz.at>
+;;; Copyright © 2019 Liliana Marie Prikler <liliana.prikler@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
diff --git a/guix/build/glib-or-gtk-build-system.scm b/guix/build/glib-or-gtk-build-system.scm
index 8d3c3684d3..475a94ae4f 100644
--- a/guix/build/glib-or-gtk-build-system.scm
+++ b/guix/build/glib-or-gtk-build-system.scm
@@ -3,6 +3,7 @@
;;; Copyright © 2014, 2015 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2018 Mark H Weaver <mhw@netris.org>
;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -28,6 +29,8 @@
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-26)
#:export (%standard-phases
+ %gdk-pixbuf-loaders-cache-file
+ generate-gdk-pixbuf-loaders-cache
glib-or-gtk-build))
;; Commentary:
@@ -51,12 +54,24 @@
"Check for the existence of \"libdir/gtk-v.0\" in INPUTS. Return a list
with all found directories."
(let* ((version
- (if (string-match "gtk\\+-3"
- (or (assoc-ref inputs "gtk+")
- (assoc-ref inputs "source")
- "gtk+-3")) ; we default to version 3
- "3.0"
- "2.0"))
+ (cond
+ ((string-match "gtk-4"
+ (or (assoc-ref inputs "gtk")
+ (assoc-ref inputs "source")
+ ""))
+ "4.0")
+ ((string-match "gtk\\+-3"
+ (or (assoc-ref inputs "gtk+")
+ (assoc-ref inputs "source")
+ ""))
+ "3.0")
+ ((string-match "gtk\\+-2"
+ (or (assoc-ref inputs "gtk+")
+ (assoc-ref inputs "source")
+ ""))
+ "2.0")
+ (else
+ "4.0"))) ; We default to version 4.0.
(gtk-module
(lambda (input prev)
(let* ((in (match input
@@ -144,68 +159,34 @@ add a dependency of that output on GLib and GTK+."
(define (sh) (force %sh))
(define handle-output
(match-lambda
- ((output . directory)
- (unless (member output glib-or-gtk-wrap-excluded-outputs)
- (let* ((bindir (string-append directory "/bin"))
- (libexecdir (string-append directory "/libexec"))
- (bin-list (filter (negate wrapped-program?)
- (append (find-files bindir ".*")
- (find-files libexecdir ".*"))))
- (datadirs (data-directories
- (alist-cons output directory inputs)))
- (gtk-mod-dirs (gtk-module-directories
- (alist-cons output directory inputs)))
- (gio-mod-dirs (gio-module-directories
- (alist-cons output directory inputs)))
- (data-env-var
- (if (not (null? datadirs))
- `("XDG_DATA_DIRS" ":" prefix ,datadirs)
- #f))
- (gtk-mod-env-var
- (if (not (null? gtk-mod-dirs))
- `("GTK_PATH" ":" prefix ,gtk-mod-dirs)
- #f))
- (gio-mod-env-var
- (if (not (null? gio-mod-dirs))
- `("GIO_EXTRA_MODULES" ":" prefix ,gio-mod-dirs)
- #f)))
- (cond
- ((and data-env-var gtk-mod-env-var gio-mod-env-var)
- (for-each (cut wrap-program <> #:sh (sh)
- data-env-var
- gtk-mod-env-var
- gio-mod-env-var)
- bin-list))
- ((and data-env-var gtk-mod-env-var (not gio-mod-env-var))
- (for-each (cut wrap-program <> #:sh (sh)
- data-env-var
- gtk-mod-env-var)
- bin-list))
- ((and data-env-var (not gtk-mod-env-var) gio-mod-env-var)
- (for-each (cut wrap-program <> #:sh (sh)
- data-env-var
- gio-mod-env-var)
- bin-list))
- ((and (not data-env-var) gtk-mod-env-var gio-mod-env-var)
- (for-each (cut wrap-program <> #:sh (sh)
- gio-mod-env-var
- gtk-mod-env-var)
- bin-list))
- ((and data-env-var (not gtk-mod-env-var) (not gio-mod-env-var))
- (for-each (cut wrap-program <> #:sh (sh)
- data-env-var)
- bin-list))
- ((and (not data-env-var) gtk-mod-env-var (not gio-mod-env-var))
- (for-each (cut wrap-program <> #:sh (sh)
- gtk-mod-env-var)
- bin-list))
- ((and (not data-env-var) (not gtk-mod-env-var) gio-mod-env-var)
- (for-each (cut wrap-program <> #:sh (sh)
- gio-mod-env-var)
- bin-list))))))))
-
- (for-each handle-output outputs)
- #t)
+ ((output . directory)
+ (unless (member output glib-or-gtk-wrap-excluded-outputs)
+ (let* ((bindir (string-append directory "/bin"))
+ (libexecdir (string-append directory "/libexec"))
+ (bin-list (filter (negate wrapped-program?)
+ (append (find-files bindir ".*")
+ (find-files libexecdir ".*"))))
+ (datadirs (data-directories
+ (alist-cons output directory inputs)))
+ (gtk-mod-dirs (gtk-module-directories
+ (alist-cons output directory inputs)))
+ (gio-mod-dirs (gio-module-directories
+ (alist-cons output directory inputs)))
+ (env-vars `(,@(if (not (null? datadirs))
+ (list `("XDG_DATA_DIRS" ":" prefix ,datadirs))
+ '())
+ ,@(if (not (null? gtk-mod-dirs))
+ (list `("GTK_PATH" ":" prefix ,gtk-mod-dirs))
+ '())
+ ,@(if (not (null? gio-mod-dirs))
+ (list `("GIO_EXTRA_MODULES" ":"
+ prefix ,gio-mod-dirs))
+ '()))))
+ (for-each (lambda (program)
+ (apply wrap-program program #:sh (sh) env-vars))
+ bin-list))))))
+
+ (for-each handle-output outputs))
(define* (compile-glib-schemas #:key outputs #:allow-other-keys)
"Implement phase \"glib-or-gtk-compile-schemas\": compile \"glib\" schemas
@@ -218,11 +199,58 @@ if needed."
(not (file-exists?
(string-append schemasdir "/gschemas.compiled"))))
(invoke "glib-compile-schemas" schemasdir)))))
- outputs)
- #t)
+ outputs))
+
+;; This file is to be generated by the
+;; `generate-gdk-pixbuf-loaders-cache' build phase defined below.
+(define %gdk-pixbuf-loaders-cache-file
+ "lib/gdk-pixbuf-2.0/2.10.0/loaders.cache")
+
+(define (generate-gdk-pixbuf-loaders-cache directories outputs)
+ "Generate the loaders.cache file used by gdk-pixbuf to locate the available
+loaders among DIRECTORIES, and set the GDK_PIXBUF_MODULE_FILE environment
+variable. The cache file is installed under OUTPUTS. Return the first cache
+file name if one was created else #f."
+ (let* ((loaders (append-map
+ (cut find-files <> "^libpixbufloader-.*\\.so$")
+ directories))
+ (outputs* (map (cut string-append <> "/"
+ %gdk-pixbuf-loaders-cache-file)
+ outputs))
+ (loaders.cache (first outputs*))
+ (loaders.cache-copies (cdr outputs*)))
+ (if (not (null? loaders))
+ (begin
+ (mkdir-p (dirname loaders.cache))
+ (setenv "GDK_PIXBUF_MODULE_FILE" loaders.cache)
+ (apply invoke "gdk-pixbuf-query-loaders" "--update-cache" loaders)
+ (for-each (lambda (f)
+ (mkdir-p (dirname f))
+ (copy-file loaders.cache f))
+ loaders.cache-copies)
+ loaders.cache)
+ #f)))
+
+(define* (generate-gdk-pixbuf-loaders-cache-file #:key inputs outputs
+ #:allow-other-keys)
+ "Build phase that Wraps the GENERATE-GDK-PIXBUF-LOADERS-CACHE procedure."
+ ;; Conditionally compute the cache file if the gdk-pixbuf command is
+ ;; available on PATH (it comes with gdk-pixbuf).
+ (when (which "gdk-pixbuf-query-loaders")
+ (let ((loaders.cache (generate-gdk-pixbuf-loaders-cache
+ (map cdr inputs)
+ (filter-map identity
+ (list
+ (assoc-ref outputs "out")
+ (assoc-ref outputs "bin")
+ (assoc-ref outputs "lib"))))))
+ (when loaders.cache
+ (format #t "GDK_PIXBUF_MODULE_FILE set to `~a'~%" loaders.cache)))))
(define %standard-phases
(modify-phases gnu:%standard-phases
+ (add-after 'unpack 'generate-gdk-pixbuf-loaders-cache-file
+ generate-gdk-pixbuf-loaders-cache-file)
(add-after 'install 'glib-or-gtk-compile-schemas compile-glib-schemas)
(add-after 'install 'glib-or-gtk-wrap wrap-all-programs)))
diff --git a/guix/build/go-build-system.scm b/guix/build/go-build-system.scm
index 645d2fe680..4768ee8562 100644
--- a/guix/build/go-build-system.scm
+++ b/guix/build/go-build-system.scm
@@ -152,8 +152,10 @@ dependencies, so it should be self-contained."
;; Make sure we're building for the correct architecture and OS targets
;; that Guix targets.
- (setenv "GOARCH" goarch)
- (setenv "GOOS" goos)
+ (setenv "GOARCH" (or goarch
+ (getenv "GOHOSTARCH")))
+ (setenv "GOOS" (or goos
+ (getenv "GOHOSTOS")))
(match goarch
("arm"
(setenv "GOARM" "7"))
diff --git a/guix/build/haskell-build-system.scm b/guix/build/haskell-build-system.scm
index 28253ce2f0..ef6cb316ee 100644
--- a/guix/build/haskell-build-system.scm
+++ b/guix/build/haskell-build-system.scm
@@ -4,6 +4,7 @@
;;; Copyright © 2015 Paul van der Walt <paul@denknerd.org>
;;; Copyright © 2018, 2020 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2018 Alex Vong <alexvong1995@gmail.com>
+;;; Copyright © 2021 John Kehayias <john.kehayias@protonmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -63,13 +64,14 @@
((file-exists? "Setup.lhs")
"Setup.lhs")
(else
- #f))))
+ #f)))
+ (pkgdb (string-append "-package-db=" %tmp-db-dir)))
(if setup-file
(begin
(format #t "running \"runhaskell Setup.hs\" with command ~s \
and parameters ~s~%"
command params)
- (apply invoke "runhaskell" setup-file command params))
+ (apply invoke "runhaskell" pkgdb setup-file command params))
(error "no Setup.hs nor Setup.lhs found"))))
(define* (configure #:key outputs inputs tests? (configure-flags '())
@@ -141,17 +143,6 @@ and parameters ~s~%"
(find-files lib "\\.a$"))))
#t)
-(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."
- (let ((line (read-line port)))
- (if (eof-object? line)
- #f
- (let ((rx-result (regexp-exec rx line)))
- (if rx-result
- (match:substring rx-result 1)
- (grep rx port))))))
-
(define* (setup-compiler #:key system inputs outputs #:allow-other-keys)
"Setup the compiler environment."
(let* ((haskell (assoc-ref inputs "haskell"))
@@ -173,15 +164,8 @@ first match and return the content of the group."
"Generate the GHC package database."
(let* ((haskell (assoc-ref inputs "haskell"))
(name-version (strip-store-file-name haskell))
- (input-dirs (match inputs
- (((_ . dir) ...)
- dir)
- (_ '())))
;; Silence 'find-files' (see 'evaluate-search-paths')
- (conf-dirs (with-null-error-port
- (search-path-as-list
- `(,(string-append "lib/" name-version))
- input-dirs #:pattern ".*\\.conf.d$")))
+ (conf-dirs (search-path-as-string->list (getenv "GHC_PACKAGE_PATH")))
(conf-files (append-map (cut find-files <> "\\.conf$") conf-dirs)))
(mkdir-p %tmp-db-dir)
(for-each (lambda (file)
@@ -233,6 +217,8 @@ given Haskell package."
(if (not (vhash-assoc id seen))
(let ((dep-conf (string-append src "/" id ".conf"))
(dep-conf* (string-append dest "/" id ".conf")))
+ (when (not (file-exists? dep-conf))
+ (error (format #f "File ~a does not exist. This usually means the dependency ~a is missing. Was checking conf-file ~a." dep-conf id conf-file)))
(copy-file dep-conf dep-conf*) ;XXX: maybe symlink instead?
(loop (vhash-cons id #t seen)
(append lst (conf-depends dep-conf))))
@@ -241,12 +227,13 @@ given Haskell package."
(let* ((out (assoc-ref outputs "out"))
(doc (assoc-ref outputs "doc"))
(haskell (assoc-ref inputs "haskell"))
- (name-verion (strip-store-file-name haskell))
+ (name-version (strip-store-file-name haskell))
+ (version (last (string-split name-version #\-)))
(lib (string-append (or (assoc-ref outputs "lib") out) "/lib"))
(config-dir (string-append lib
- "/" name-verion
+ "/ghc-" version
"/" name ".conf.d"))
- (id-rx (make-regexp "^id: *(.*)$"))
+ (id-rx (make-regexp "^id:[ \n\t]+([^ \t\n]+)$" regexp/newline))
(config-file (string-append out "/" name ".conf"))
(params
(list (string-append "--gen-pkg-config=" config-file))))
@@ -254,8 +241,15 @@ given Haskell package."
;; The conf file is created only when there is a library to register.
(when (file-exists? config-file)
(mkdir-p config-dir)
- (let ((config-file-name+id
- (call-with-ascii-input-file config-file (cut grep id-rx <>))))
+ (let* ((contents (call-with-input-file config-file read-string))
+ (config-file-name+id (match:substring (first (list-matches id-rx contents)) 1)))
+
+ (when (or
+ (and
+ (string? config-file-name+id)
+ (string-null? config-file-name+id))
+ (not config-file-name+id))
+ (error (format #f "The package id for ~a is empty. This is a bug." config-file)))
;; Remove reference to "doc" output from "lib" (or "out") by rewriting the
;; "haddock-interfaces" field and removing the optional "haddock-html"
diff --git a/guix/build/julia-build-system.scm b/guix/build/julia-build-system.scm
index d74acf2a05..b4e0044567 100644
--- a/guix/build/julia-build-system.scm
+++ b/guix/build/julia-build-system.scm
@@ -1,5 +1,7 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2019, 2020 Nicolò Balzarotti <nicolo@nixo.xyz>
+;;; Copyright © 2021 Jean-Baptiste Volatier <jbv@pm.me>
+;;; Copyright © 2021 Simon Tournier <zimon.toutoune@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -20,9 +22,11 @@
(define-module (guix build julia-build-system)
#:use-module ((guix build gnu-build-system) #:prefix gnu:)
#:use-module (guix build utils)
+ #:use-module (rnrs io ports)
#:use-module (ice-9 match)
#:use-module (ice-9 regex)
#:use-module (ice-9 rdelim)
+ #:use-module (ice-9 popen)
#:export (%standard-phases
julia-create-package-toml
julia-build))
@@ -37,7 +41,7 @@
(invoke "julia" "-e" code))
;; subpath where we store the package content
-(define %package-path "/share/julia/packages/")
+(define %package-path "/share/julia/loadpath/")
(define (project.toml->name file)
"Look for Julia package name in the TOML file FILE (usually named
@@ -51,6 +55,18 @@ Project.toml)."
(if m (match:substring m 1)
(loop (read-line in 'concat)))))))))
+(define (project.toml->uuid file)
+ "Look for Julia package uuid in the TOML file FILE (usually named
+Project.toml)."
+ (call-with-input-file file
+ (lambda (in)
+ (let loop ((line (read-line in 'concat)))
+ (if (eof-object? line)
+ #f
+ (let ((m (string-match "uuid\\s*=\\s*\"(.*)\"" line)))
+ (if m (match:substring m 1)
+ (loop (read-line in 'concat)))))))))
+
(define* (install #:key source inputs outputs julia-package-name
#:allow-other-keys)
(let* ((out (assoc-ref outputs "out"))
@@ -59,8 +75,7 @@ Project.toml)."
julia-package-name
(project.toml->name "Project.toml")))))
(mkdir-p package-dir)
- (copy-recursively (getcwd) package-dir))
- #t)
+ (copy-recursively (getcwd) package-dir)))
(define* (precompile #:key source inputs outputs julia-package-name
#:allow-other-keys)
@@ -73,7 +88,7 @@ Project.toml)."
(setenv "JULIA_DEPOT_PATH" builddir)
;; Add new package dir to the load path.
(setenv "JULIA_LOAD_PATH"
- (string-append builddir "packages/" ":"
+ (string-append builddir "loadpath/" ":"
(or (getenv "JULIA_LOAD_PATH")
"")))
;; Actual precompilation:
@@ -84,27 +99,63 @@ Project.toml)."
;; element of DEPOT_PATH. Once the cache file exists, this hack is not
;; needed anymore (like in the check phase). If the user install new
;; packages, those will be installed and precompiled in the home dir.
- (string-append "pushfirst!(DEPOT_PATH, pop!(DEPOT_PATH)); using " package)))
- #t)
+ (string-append "pushfirst!(DEPOT_PATH, pop!(DEPOT_PATH)); using "
+ package))))
(define* (check #:key tests? source inputs outputs julia-package-name
- #:allow-other-keys)
+ parallel-tests? #:allow-other-keys)
(when tests?
(let* ((out (assoc-ref outputs "out"))
(package (or julia-package-name (project.toml->name "Project.toml")))
- (builddir (string-append out "/share/julia/")))
+ (builddir (string-append out "/share/julia/"))
+ (job-count (if parallel-tests?
+ (parallel-job-count)
+ 1))
+ ;; The --proc argument of Julia *adds* extra processors rather than
+ ;; specify the exact count to use, so zero must be specified to
+ ;; disable parallel processing...
+ (additional-procs (max 0 (1- job-count))))
;; With a patch, SOURCE_DATE_EPOCH is honored
(setenv "SOURCE_DATE_EPOCH" "1")
(setenv "JULIA_DEPOT_PATH" builddir)
(setenv "JULIA_LOAD_PATH"
- (string-append builddir "packages/" ":"
+ (string-append builddir "loadpath/" ":"
(or (getenv "JULIA_LOAD_PATH")
"")))
+ (setenv "JULIA_CPU_THREADS" (number->string job-count))
(setenv "HOME" "/tmp")
- (invoke "julia" "--depwarn=yes"
- (string-append builddir "packages/"
- package "/test/runtests.jl"))))
- #t)
+ (apply invoke "julia"
+ `("--depwarn=yes"
+ ,@(if parallel-tests?
+ ;; XXX: ... but '--procs' doesn't accept 0 as a valid
+ ;; value, so just omit the argument entirely.
+ (list (string-append "--procs="
+ (number->string additional-procs)))
+ '())
+ ,(string-append builddir "loadpath/"
+ package "/test/runtests.jl"))))))
+
+(define* (link-depot #:key source inputs outputs
+ julia-package-name julia-package-uuid #:allow-other-keys)
+ (let* ((out (assoc-ref outputs "out"))
+ (package-name (or
+ julia-package-name
+ (project.toml->name "Project.toml")))
+ (package-dir (string-append out %package-path package-name))
+ (uuid (or julia-package-uuid (project.toml->uuid "Project.toml")))
+ (pipe (open-pipe* OPEN_READ "julia" "-e"
+ (format #f "using Pkg;
+println(Base.version_slug(Base.UUID(\"~a\"),
+ Base.SHA1(Pkg.GitTools.tree_hash(\".\"))))" uuid)))
+ (slug (string-trim-right (get-string-all pipe))))
+ ;; When installing a package, julia looks first at in the JULIA_DEPOT_PATH
+ ;; for a path like packages/PACKAGE/XXXX
+ ;; Where XXXX is a slug encoding the package UUID and SHA1 of the files
+ ;; Here we create a link with the correct path to enable julia to find the
+ ;; package
+ (mkdir-p (string-append out "/share/julia/packages/" package-name))
+ (symlink package-dir (string-append out "/share/julia/packages/"
+ package-name "/" slug))))
(define (julia-create-package-toml outputs source
name uuid version
@@ -130,14 +181,14 @@ version = \"" version "\"
(display (string-append (car (car dep)) " = \"" (cdr (car dep)) "\"\n")
f))
deps))
- (close-port f))
- #t)
+ (close-port f)))
(define %standard-phases
(modify-phases gnu:%standard-phases
(delete 'check) ; tests must be run after installation
(replace 'install install)
(add-after 'install 'precompile precompile)
+ (add-after 'unpack 'link-depot link-depot)
(add-after 'install 'check check)
;; TODO: In the future we could add a "system-image-generation" phase
;; where we use PackageCompiler.jl to speed up package loading times
@@ -146,11 +197,12 @@ version = \"" version "\"
(delete 'patch-usr-bin-file)
(delete 'build)))
-(define* (julia-build #:key inputs julia-package-name
+(define* (julia-build #:key inputs julia-package-name julia-package-uuid
(phases %standard-phases)
#:allow-other-keys #:rest args)
"Build the given Julia package, applying all of PHASES in order."
(apply gnu:gnu-build
#:inputs inputs #:phases phases
#:julia-package-name julia-package-name
+ #:julia-package-uuid julia-package-uuid
args))
diff --git a/guix/build/linux-module-build-system.scm b/guix/build/linux-module-build-system.scm
index 729ab6154f..18ccf7cd8b 100644
--- a/guix/build/linux-module-build-system.scm
+++ b/guix/build/linux-module-build-system.scm
@@ -1,6 +1,7 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2019 Danny Milosavljevic <dannym@scratchpost.org>
;;; Copyright © 2020 Mathieu Othacehe <m.othacehe@gmail.com>
+;;; Copyright © 2021 Tobias Geerinckx-Rice <me@tobias.gr>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -33,7 +34,7 @@
;;
;; Code:
-;; Copied from make-linux-libre's "configure" phase.
+;; Similar to make-linux-libre's "configure" phase.
(define* (configure #:key inputs target arch #:allow-other-keys)
(setenv "KCONFIG_NOTIMESTAMP" "1")
(setenv "KBUILD_BUILD_TIMESTAMP" (getenv "SOURCE_DATE_EPOCH"))
@@ -42,23 +43,28 @@
(format #t "`ARCH' set to `~a'~%" (getenv "ARCH"))
(when target
+ ;; TODO? (setenv "EXTRA_VERSION" ,extra-version)
+ ;; TODO? kernel ".config".
(setenv "CROSS_COMPILE" (string-append target "-"))
(format #t "`CROSS_COMPILE' set to `~a'~%"
- (getenv "CROSS_COMPILE")))
- ; TODO: (setenv "EXTRA_VERSION" ,extra-version)
- ; TODO: kernel ".config".
- #t)
+ (getenv "CROSS_COMPILE"))))
-(define* (build #:key inputs make-flags (source-directory ".") #:allow-other-keys)
+(define* (build #:key (make-flags '()) (parallel-build? #t)
+ (source-directory ".")
+ inputs
+ #:allow-other-keys)
(apply invoke "make" "-C"
(string-append (assoc-ref inputs "linux-module-builder")
"/lib/modules/build")
- (string-append "M=" (getcwd) "/" source-directory)
- (or make-flags '())))
+ (string-append "M=" (canonicalize-path source-directory))
+ `(,@(if parallel-build?
+ `("-j" ,(number->string (parallel-job-count)))
+ '())
+ ,@make-flags)))
-;; This block was copied from make-linux-libre--only took the "modules_install"
-;; part.
-(define* (install #:key make-flags (source-directory ".")
+;; Similar to the "modules_install" part of make-linux-libre.
+(define* (install #:key (make-flags '()) (parallel-build? #t)
+ (source-directory ".")
inputs native-inputs outputs
#:allow-other-keys)
(let* ((out (assoc-ref outputs "out"))
@@ -68,7 +74,7 @@
(apply invoke "make" "-C"
(string-append (assoc-ref inputs "linux-module-builder")
"/lib/modules/build")
- (string-append "M=" (getcwd) "/" source-directory)
+ (string-append "M=" (canonicalize-path source-directory))
;; Disable depmod because the Guix system's module directory
;; is an union of potentially multiple packages. It is not
;; possible to use depmod to usefully calculate a dependency
@@ -79,7 +85,10 @@
(string-append "INSTALL_MOD_PATH=" out)
"INSTALL_MOD_STRIP=1"
"modules_install"
- (or make-flags '()))))
+ `(,@(if parallel-build?
+ `("-j" ,(number->string (parallel-job-count)))
+ '())
+ ,@make-flags))))
(define %standard-phases
(modify-phases gnu:%standard-phases
diff --git a/guix/build/meson-build-system.scm b/guix/build/meson-build-system.scm
index cc2ba83889..61ce45367d 100644
--- a/guix/build/meson-build-system.scm
+++ b/guix/build/meson-build-system.scm
@@ -2,6 +2,7 @@
;;; Copyright © 2017 Peter Mikkelsen <petermikkelsen10@gmail.com>
;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2018 Marius Bakke <mbakke@fastmail.com>
+;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -63,16 +64,17 @@
(number->string (parallel-job-count))
"1")))
-(define* (check #:key test-target parallel-tests? tests?
+(define* (check #:key tests? test-options parallel-tests?
#:allow-other-keys)
- (setenv "MESON_TESTTHREADS"
- (if parallel-tests?
- (number->string (parallel-job-count))
- "1"))
(if tests?
- (invoke "ninja" test-target)
- (format #t "test suite not run~%"))
- #t)
+ (begin
+ (setenv "MESON_TESTTHREADS"
+ (if parallel-tests?
+ (number->string (parallel-job-count))
+ "1"))
+ ;; Always provide "-t 0" to disable the 30 s default timeout.
+ (apply invoke "meson" "test" "--print-errorlogs" "-t" "0" test-options))
+ (format #t "test suite not run~%")))
(define* (install #:rest args)
(invoke "ninja" "install"))
diff --git a/guix/build/minetest-build-system.scm b/guix/build/minetest-build-system.scm
index 5d7d8d2292..4a7a87ab83 100644
--- a/guix/build/minetest-build-system.scm
+++ b/guix/build/minetest-build-system.scm
@@ -23,6 +23,7 @@
#:use-module (ice-9 rdelim)
#:use-module (ice-9 receive)
#:use-module (ice-9 regex)
+ #:use-module (ice-9 exceptions)
#:use-module ((guix build gnu-build-system) #:prefix gnu:)
#:use-module ((guix build copy-build-system) #:prefix copy:)
#:export (%standard-phases
@@ -40,7 +41,7 @@
;; See <https://github.com/minetest/minetest/blob/master/doc/lua_api.txt>
;; for an incomple list of files that can be found in mods.
#:include ("mod.conf" "modpack.conf" "settingtypes.txt" "depends.txt"
- "description.txt")
+ "description.txt" "config.txt" "_config.txt")
#:include-regexp (".lua$" ".png$" ".ogg$" ".obj$" ".b3d$" ".tr$"
".mts$"))))
@@ -190,20 +191,24 @@ auth_backend = sqlite3
(define (stop? line)
(and (string? line)
(string-contains line "ACTION[Server]: singleplayer [127.0.0.1] joins game.")))
- (let loop ()
- (match (read-line port)
- ((? error? line)
- (error "minetest raised an error: ~a" line))
- ((? stop?)
+ (let loop ((has-errors? #f))
+ (match `(,(read-line port) ,has-errors?)
+ (((? error? line) _)
+ (display line)
+ (newline)
+ (loop #t))
+ (((? stop?) #f)
(kill pid SIGINT)
(close-port port)
(waitpid pid))
- ((? string? line)
+ (((? eof-object?) #f)
+ (error "minetest didn't start"))
+ (((or (? stop?) (? eof-object?)) #t)
+ (error "minetest raised an error"))
+ (((? string? line) has-error?)
(display line)
(newline)
- (loop))
- ((? eof-object?)
- (error "minetest didn't start"))))))))
+ (loop has-error?))))))))
(define %standard-phases
(modify-phases gnu:%standard-phases
diff --git a/guix/build/po.scm b/guix/build/po.scm
index eb9690ad1a..7f88164cd8 100644
--- a/guix/build/po.scm
+++ b/guix/build/po.scm
@@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2019 Julien Lepiller <julien@lepiller.eu>
+;;; Copyright © 2019, 2021 Julien Lepiller <julien@lepiller.eu>
;;; Copyright © 2020 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
@@ -20,17 +20,23 @@
(define-module (guix build po)
#:use-module (ice-9 match)
#:use-module (ice-9 peg)
+ #:use-module (ice-9 regex)
#:use-module (ice-9 textual-ports)
- #:export (read-po-file))
+ #:use-module (ice-9 vlist)
+ #:use-module (srfi srfi-1)
+ #:export (read-po-file
+ translate-cross-references))
;; A small parser for po files
-(define-peg-pattern po-file body (* (or comment entry whitespace)))
+(define-peg-pattern po-file body (* (or entry whitespace)))
(define-peg-pattern whitespace body (or " " "\t" "\n"))
(define-peg-pattern comment-chr body (range #\space #\頋))
(define-peg-pattern comment none (and "#" (* comment-chr) "\n"))
+(define-peg-pattern flags all (and (ignore "#, ") (* comment-chr) (ignore "\n")))
(define-peg-pattern entry all
- (and (ignore (* whitespace)) (ignore "msgid ") msgid
- (ignore (* whitespace)) (ignore "msgstr ") msgstr))
+ (and (* (or flags comment (ignore (* whitespace))))
+ (ignore "msgid ") msgid (ignore (* whitespace))
+ (ignore "msgstr ") msgstr))
(define-peg-pattern escape body (or "\\\\" "\\\"" "\\n"))
(define-peg-pattern str-chr body (or " " "!" (and (ignore "\\") "\"")
"\\n" (and (ignore "\\") "\\")
@@ -53,7 +59,24 @@
(append (list "\n" prefix) result)))))))
(define (parse-tree->assoc parse-tree)
- "Converts a po PARSE-TREE to an association list."
+ "Converts a po PARSE-TREE to an association list, where the key is the msgid
+and the value is the msgstr. The result only contains non fuzzy strings."
+ (define (comments->flags comments)
+ (match comments
+ (('flags flags)
+ (map (lambda (flag) (string->symbol (string-trim-both flag #\space)))
+ (string-split flags #\,)))
+ ((? list? comments)
+ (fold
+ (lambda (comment res)
+ (match comment
+ ((? string? _) res)
+ (flags
+ (append (comments->flags flags)
+ res))))
+ '()
+ comments))))
+
(match parse-tree
(() '())
((entry . parse-tree)
@@ -66,10 +89,22 @@
;; empty msgstr
(('entry ('msgid msgid) 'msgstr)
(parse-tree->assoc parse-tree))
+ (('entry _ ('msgid msgid) 'msgstr)
+ (parse-tree->assoc parse-tree))
+ (('entry ('msgid msgid) ('msgstr msgstr))
+ (acons (interpret-newline-escape msgid)
+ (interpret-newline-escape msgstr)
+ (parse-tree->assoc parse-tree)))
(('entry ('msgid msgid) ('msgstr msgstr))
(acons (interpret-newline-escape msgid)
(interpret-newline-escape msgstr)
- (parse-tree->assoc parse-tree)))))))
+ (parse-tree->assoc parse-tree)))
+ (('entry comments ('msgid msgid) ('msgstr msgstr))
+ (if (member 'fuzzy (comments->flags comments))
+ (parse-tree->assoc parse-tree)
+ (acons (interpret-newline-escape msgid)
+ (interpret-newline-escape msgstr)
+ (parse-tree->assoc parse-tree))))))))
(define (read-po-file port)
"Read a .po file from PORT and return an alist of msgid and msgstr."
@@ -77,3 +112,71 @@
po-file
(get-string-all port)))))
(parse-tree->assoc tree)))
+
+(define (canonicalize-whitespace str)
+ "Change whitespace (newlines, etc.) in STR to @code{#\\space}."
+ (string-map (lambda (chr)
+ (if (char-set-contains? char-set:whitespace chr)
+ #\space
+ chr))
+ str))
+
+(define xref-regexp
+ ;; Texinfo cross-reference regexp.
+ (make-regexp "@(px|x)?ref\\{([^,}]+)"))
+
+(define (translate-cross-references texi pofile)
+ "Translate the cross-references that appear in @var{texi}, the initial
+translation of a Texinfo file, using the msgid/msgstr pairs from @var{pofile}."
+ (define translations
+ (call-with-input-file pofile read-po-file))
+
+ (define content
+ (call-with-input-file texi get-string-all))
+
+ (define matches
+ (list-matches xref-regexp content))
+
+ (define translation-map
+ (fold (match-lambda*
+ (((msgid . str) result)
+ (vhash-cons msgid str result)))
+ vlist-null
+ translations))
+
+ (define translated
+ ;; Iterate over MATCHES and replace cross-references with their
+ ;; translation found in TRANSLATION-MAP. (We can't use
+ ;; 'substitute*' because matches can span multiple lines.)
+ (let loop ((matches matches)
+ (offset 0)
+ (result '()))
+ (match matches
+ (()
+ (string-concatenate-reverse
+ (cons (string-drop content offset) result)))
+ ((head . tail)
+ (let ((prefix (match:substring head 1))
+ (ref (canonicalize-whitespace (match:substring head 2))))
+ (define translated
+ (string-append "@" (or prefix "")
+ "ref{"
+ (match (vhash-assoc ref translation-map)
+ (#f ref)
+ ((_ . str) str))))
+
+ (loop tail
+ (match:end head)
+ (append (list translated
+ (string-take
+ (string-drop content offset)
+ (- (match:start head) offset)))
+ result)))))))
+
+ (format (current-error-port)
+ "translated ~a cross-references in '~a'~%"
+ (length matches) texi)
+
+ (call-with-output-file texi
+ (lambda (port)
+ (display translated port))))
diff --git a/guix/build/qt-utils.scm b/guix/build/qt-utils.scm
index 97145a116d..b9c5a76f34 100644
--- a/guix/build/qt-utils.scm
+++ b/guix/build/qt-utils.scm
@@ -4,6 +4,7 @@
;;; Copyright © 2020 Jakub Kądziołka <kuba@kadziolka.net>
;;; Copyright © 2021 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
+;;; Copyright © 2021 Brendan Tildesley <mail@brendan.scot>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -109,7 +110,7 @@
(define* (wrap-qt-program program-name #:key (sh (which "bash")) inputs output
(qt-wrap-excluded-inputs %qt-wrap-excluded-inputs))
- "Wrap the specified programm (which must reside in the OUTPUT's \"/bin\"
+ "Wrap the specified program (which must reside in the OUTPUT's \"/bin\"
directory) with suitably set environment variables.
This is like qt-build-systems's phase \"qt-wrap\", but only the named program
@@ -134,7 +135,10 @@ add a dependency of that output on Qt."
(define (find-files-to-wrap output-dir)
(append-map
(lambda (dir)
- (if (directory-exists? dir) (find-files dir ".*") (list)))
+ (if (directory-exists? dir)
+ (find-files dir (lambda (file stat)
+ (not (wrapped-program? file))))
+ (list)))
(list (string-append output-dir "/bin")
(string-append output-dir "/sbin")
(string-append output-dir "/libexec")
diff --git a/guix/build/renpy-build-system.scm b/guix/build/renpy-build-system.scm
index 66683971c5..e4a88456be 100644
--- a/guix/build/renpy-build-system.scm
+++ b/guix/build/renpy-build-system.scm
@@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2021 Leo Prikler <leo.prikler@student.tugraz.at>
+;;; Copyright © 2021 Liliana Marie Prikler <liliana.prikler@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
diff --git a/guix/build/syscalls.scm b/guix/build/syscalls.scm
index ac1b0c2eea..45f95c509d 100644
--- a/guix/build/syscalls.scm
+++ b/guix/build/syscalls.scm
@@ -7,6 +7,7 @@
;;; Copyright © 2020 Julien Lepiller <julien@lepiller.eu>
;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
;;; Copyright © 2021 Chris Marusich <cmmarusich@gmail.com>
+;;; Copyright © 2021 Tobias Geerinckx-Rice <me@tobias.gr>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -56,6 +57,9 @@
restart-on-EINTR
+ device-number
+ device-number->major+minor
+
mount?
mount-device-number
mount-source
@@ -67,6 +71,11 @@
mounts
mount-points
+ SWAP_FLAG_PREFER
+ SWAP_FLAG_PRIO_MASK
+ SWAP_FLAG_PRIO_SHIFT
+ SWAP_FLAG_DISCARD
+
swapon
swapoff
@@ -116,6 +125,8 @@
with-file-lock
with-file-lock/no-wait
+ set-child-subreaper!
+
set-thread-name
thread-name
@@ -176,6 +187,8 @@
terminal-window-size
terminal-columns
terminal-rows
+ openpty
+ login-tty
utmpx?
utmpx-login-type
@@ -418,15 +431,21 @@ expansion-time error is raised if FIELD does not exist in TYPE."
"Evaluate EXPR and restart upon EINTR. Return the value of EXPR."
(call-with-restart-on-EINTR (lambda () expr)))
-(define (syscall->procedure return-type name argument-types)
+(define* (syscall->procedure return-type name argument-types
+ #:key library)
"Return a procedure that wraps the C function NAME using the dynamic FFI,
-and that returns two values: NAME's return value, and errno.
+and that returns two values: NAME's return value, and errno. When LIBRARY is
+specified, look up NAME in that library rather than in the global symbol name
+space.
If an error occurs while creating the binding, defer the error report until
the returned procedure is called."
(catch #t
(lambda ()
- (let ((ptr (dynamic-func name (dynamic-link))))
+ (let ((ptr (dynamic-func name
+ (if library
+ (dynamic-link library)
+ (dynamic-link)))))
;; The #:return-errno? facility was introduced in Guile 2.0.12.
(pointer->procedure return-type ptr argument-types
#:return-errno? #t)))
@@ -450,6 +469,29 @@ the returned procedure is called."
;;;
+;;; Block devices.
+;;;
+
+;; Convert between major:minor pairs and packed ‘device number’ representation.
+;; XXX These aren't syscalls, but if you squint very hard they are part of the
+;; FFI or however you want to justify me not finding a better fit… :-)
+(define (device-number major minor) ; see glibc's <sys/sysmacros.h>
+ "Return the device number for the device with MAJOR and MINOR, for use as
+the last argument of `mknod'."
+ (logior (ash (logand #x00000fff major) 8)
+ (ash (logand #xfffff000 major) 32)
+ (logand #x000000ff minor)
+ (ash (logand #xffffff00 minor) 12)))
+
+(define (device-number->major+minor device) ; see glibc's <sys/sysmacros.h>
+ "Return two values: the major and minor device numbers that make up DEVICE."
+ (values (logior (ash (logand #x00000000000fff00 device) -8)
+ (ash (logand #xfffff00000000000 device) -32))
+ (logior (logand #x00000000000000ff device)
+ (ash (logand #x00000ffffff00000 device) -12))))
+
+
+;;;
;;; File systems.
;;;
@@ -628,7 +670,7 @@ current process."
(define (string->device-number str)
(match (string-split str #\:)
(((= string->number major) (= string->number minor))
- (+ (* major 256) minor))))
+ (device-number major minor))))
(call-with-input-file "/proc/self/mountinfo"
(lambda (port)
@@ -650,6 +692,13 @@ current process."
"Return the mounts points for currently mounted file systems."
(map mount-point (mounts)))
+;; Pulled from glibc's sysdeps/unix/sysv/linux/sys/swap.h
+
+(define SWAP_FLAG_PREFER #x8000) ;; Set if swap priority is specified.
+(define SWAP_FLAG_PRIO_MASK #x7fff)
+(define SWAP_FLAG_PRIO_SHIFT 0)
+(define SWAP_FLAG_DISCARD #x10000) ;; Discard swap cluster after use.
+
(define swapon
(let ((proc (syscall->procedure int "swapon" (list '* int))))
(lambda* (device #:optional (flags 0))
@@ -1386,6 +1435,11 @@ handler if the lock is already held by another process."
(define PR_SET_NAME 15) ;<linux/prctl.h>
(define PR_GET_NAME 16)
+(define PR_SET_CHILD_SUBREAPER 36)
+
+(define (set-child-subreaper!)
+ "Set the CHILD_SUBREAPER capability for the current process."
+ (%prctl PR_SET_CHILD_SUBREAPER 1 0 0 0))
(define %max-thread-name-length
;; Maximum length in bytes of the process name, including the terminating
@@ -2259,6 +2313,41 @@ PORT, trying to guess a reasonable value if all else fails. The result is
always a positive integer."
(terminal-dimension window-size-rows port (const 25)))
+(define openpty
+ (let ((proc (syscall->procedure int "openpty" '(* * * * *)
+ #:library "libutil")))
+ (lambda ()
+ "Return two file descriptors: one for the pseudo-terminal control side,
+and one for the controlled side."
+ (let ((head (make-bytevector (sizeof int)))
+ (inferior (make-bytevector (sizeof int))))
+ (let-values (((ret err)
+ (proc (bytevector->pointer head)
+ (bytevector->pointer inferior)
+ %null-pointer %null-pointer %null-pointer)))
+ (unless (zero? ret)
+ (throw 'system-error "openpty" "~A"
+ (list (strerror err))
+ (list err))))
+
+ (let ((* (lambda (bv)
+ (bytevector-sint-ref bv 0 (native-endianness)
+ (sizeof int)))))
+ (values (* head) (* inferior)))))))
+
+(define login-tty
+ (let* ((proc (syscall->procedure int "login_tty" (list int)
+ #:library "libutil")))
+ (lambda (fd)
+ "Make FD the controlling terminal of the current process (with the
+TIOCSCTTY ioctl), redirect standard input, standard output and standard error
+output to this terminal, and close FD."
+ (let-values (((ret err) (proc fd)))
+ (unless (zero? ret)
+ (throw 'system-error "login-pty" "~A"
+ (list (strerror err))
+ (list err)))))))
+
;;;
;;; utmpx.
diff --git a/guix/build/union.scm b/guix/build/union.scm
index 961ac3298b..bf75c67c52 100644
--- a/guix/build/union.scm
+++ b/guix/build/union.scm
@@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2012, 2013, 2014, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2012, 2013, 2014, 2016, 2017, 2018, 2019, 2021 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2014 Mark H Weaver <mhw@netris.org>
;;; Copyright © 2017 Huang Ying <huang.ying.caritas@gmail.com>
;;;
@@ -99,8 +99,9 @@ identical, #f otherwise."
;; a warning doesn't make sense. For example, "icon-theme.cache" is
;; regenerated by a profile hook which shadows the file provided by
;; individual packages, and "gschemas.compiled" is made available to
- ;; applications via 'glib-or-gtk-build-system'.
- '("icon-theme.cache" "gschemas.compiled"))
+ ;; applications via 'glib-or-gtk-build-system'; "etc/ld.so.cache" is created
+ ;; for most packages.
+ '("icon-theme.cache" "gschemas.compiled" "ld.so.cache"))
(define (warn-about-collision files)
"Handle the collision among FILES by emitting a warning and choosing the
diff --git a/guix/cache.scm b/guix/cache.scm
index 0401a9d428..51009809bd 100644
--- a/guix/cache.scm
+++ b/guix/cache.scm
@@ -101,7 +101,13 @@ CLEANUP-PERIOD denotes the minimum time between two cache cleanups."
#:now now
#:entry-expiration entry-expiration
#:delete-entry delete-entry)
- (call-with-output-file expiry-file
- (cute write (time-second now) <>))))
+ (catch 'system-error
+ (lambda ()
+ (call-with-output-file expiry-file
+ (cute write (time-second now) <>)))
+ (lambda args
+ ;; ENOENT means CACHE does not exist.
+ (unless (= ENOENT (system-error-errno args))
+ (apply throw args))))))
;;; cache.scm ends here
diff --git a/guix/channels.scm b/guix/channels.scm
index 476d62e1f4..e4e0428eb5 100644
--- a/guix/channels.scm
+++ b/guix/channels.scm
@@ -1089,8 +1089,13 @@ cannot be found."
(if (channel-news-entry-commit entry)
entry
(let* ((tag (channel-news-entry-tag entry))
- (reference (string-append "refs/tags/" tag))
- (oid (reference-name->oid repository reference)))
+ (reference (reference-lookup repository
+ (string-append "refs/tags/" tag)))
+ (target (reference-target reference))
+ (oid (let ((obj (object-lookup repository target)))
+ (if (= OBJ-TAG (object-type obj)) ;annotated tag?
+ (tag-target-id (tag-lookup repository target))
+ target))))
(channel-news-entry (oid->string oid) tag
(channel-news-entry-title entry)
(channel-news-entry-body entry)))))
diff --git a/guix/cpio.scm b/guix/cpio.scm
index 8038a11f3c..d4a7d5f1e0 100644
--- a/guix/cpio.scm
+++ b/guix/cpio.scm
@@ -18,6 +18,8 @@
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (guix cpio)
+ #:use-module ((guix build syscalls) #:select (device-number
+ device-number->major+minor))
#:use-module ((guix build utils) #:select (dump-port))
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-11)
@@ -129,8 +131,8 @@
(nlink 1) (mtime 0) (size 0)
(dev 0) (rdev 0) (name-size 0))
"Return a new cpio file header."
- (let-values (((major minor) (device->major+minor dev))
- ((rmajor rminor) (device->major+minor rdev)))
+ (let-values (((major minor) (device-number->major+minor dev))
+ ((rmajor rminor) (device-number->major+minor rdev)))
(%make-cpio-header MAGIC
inode mode uid gid
nlink mtime
@@ -154,21 +156,6 @@ denotes, similar to 'stat:type'."
(else
(error "unsupported file type" mode)))))
-(define (device-number major minor) ; see glibc's <sys/sysmacros.h>
- "Return the device number for the device with MAJOR and MINOR, for use as
-the last argument of `mknod'."
- (logior (ash (logand #x00000fff major) 8)
- (ash (logand #xfffff000 major) 32)
- (logand #x000000ff minor)
- (ash (logand #xffffff00 minor) 12)))
-
-(define (device->major+minor device) ; see glibc's <sys/sysmacros.h>
- "Return two values: the major and minor device numbers that make up DEVICE."
- (values (logior (ash (logand #x00000000000fff00 device) -8)
- (ash (logand #xfffff00000000000 device) -32))
- (logior (logand #x00000000000000ff device)
- (ash (logand #x00000ffffff00000 device) -12))))
-
(define* (file->cpio-header file #:optional (file-name file)
#:key (stat lstat))
"Return a cpio header corresponding to the info returned by STAT for FILE,
diff --git a/guix/derivations.scm b/guix/derivations.scm
index 33f4dc5d9d..f77ea179f4 100644
--- a/guix/derivations.scm
+++ b/guix/derivations.scm
@@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2012-2021 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2016, 2017 Mathieu Lirzin <mthl@gnu.org>
;;;
;;; This file is part of GNU Guix.
@@ -1243,20 +1243,21 @@ MODULES are compiled."
(hash-set! %module-cache key result)
result)))
-(define* (build-expression->derivation store name exp ;deprecated
- #:key
- (system (%current-system))
- (inputs '())
- (outputs '("out"))
- hash hash-algo recursive?
- (env-vars '())
- (modules '())
- guile-for-build
- references-graphs
- allowed-references
- disallowed-references
- local-build? (substitutable? #t)
- (properties '()))
+(define-deprecated (build-expression->derivation store name exp
+ #:key
+ (system (%current-system))
+ (inputs '())
+ (outputs '("out"))
+ hash hash-algo recursive?
+ (env-vars '())
+ (modules '())
+ guile-for-build
+ references-graphs
+ allowed-references
+ disallowed-references
+ local-build? (substitutable? #t)
+ (properties '()))
+ gexp->derivation ;unbound, but that's okay
"Return a derivation that executes Scheme expression EXP as a builder
for derivation NAME. INPUTS must be a list of (NAME DRV-PATH SUB-DRV)
tuples; when SUB-DRV is omitted, \"out\" is assumed. MODULES is a list
diff --git a/guix/diagnostics.scm b/guix/diagnostics.scm
index 6a792febd4..337a73c1a2 100644
--- a/guix/diagnostics.scm
+++ b/guix/diagnostics.scm
@@ -54,7 +54,9 @@
condition-fix-hint
guix-warning-port
- program-name))
+ program-name
+
+ define-with-syntax-properties))
;;; Commentary:
;;;
@@ -331,3 +333,37 @@ number of arguments in ARGS matches the escapes in FORMAT."
(define program-name
;; Name of the command-line program currently executing, or #f.
(make-parameter #f))
+
+
+(define-syntax define-with-syntax-properties
+ (lambda (x)
+ "Define BINDING to be a syntax form replacing each VALUE-IDENTIFIER and
+SYNTAX-PROPERTIES-IDENTIFIER in body by the syntax and syntax-properties,
+respectively, of each ensuing syntax object."
+ (syntax-case x ()
+ ((_ (binding (value-identifier syntax-properties-identifier)
+ ...)
+ body ...)
+ (and (and-map identifier? #'(value-identifier ...))
+ (and-map identifier? #'(syntax-properties-identifier ...)))
+ #'(define-syntax binding
+ (lambda (y)
+ (with-ellipsis :::
+ (syntax-case y ()
+ ((_ value-identifier ...)
+ (with-syntax ((syntax-properties-identifier
+ #`'#,(datum->syntax y
+ (syntax-source
+ #'value-identifier)))
+ ...)
+ #'(begin body ...)))
+ (_
+ (syntax-violation #f (format #f
+ "Expected (~a~{ ~a~})"
+ 'binding
+ '(value-identifier ...))
+ y)))))))
+ (_
+ (syntax-violation #f "Expected a definition of the form \
+(define-with-syntax-properties (binding (value syntax-properties) \
+...) body ...)" x)))))
diff --git a/guix/discovery.scm b/guix/discovery.scm
index b84b9ff370..81d4ca600f 100644
--- a/guix/discovery.scm
+++ b/guix/discovery.scm
@@ -107,19 +107,25 @@ name and the exception key and arguments."
(define prefix-len
(string-length directory))
- (filter-map (lambda (file)
- (let* ((relative (string-drop file prefix-len))
- (module (file-name->module-name relative)))
- (catch #t
- (lambda ()
- (resolve-interface module))
- (lambda args
- ;; Report the error, but keep going.
- (warn file module args)
- #f))))
- (scheme-files (if sub-directory
- (string-append directory "/" sub-directory)
- directory))))
+ ;; Hide Guile warnings such as "source file [...] newer than compiled" when
+ ;; loading user code, unless we're hacking on Guix proper. See
+ ;; <https://issues.guix.gnu.org/43747>.
+ (parameterize ((current-warning-port (if (getenv "GUIX_UNINSTALLED")
+ (current-warning-port)
+ (%make-void-port "w"))))
+ (filter-map (lambda (file)
+ (let* ((relative (string-drop file prefix-len))
+ (module (file-name->module-name relative)))
+ (catch #t
+ (lambda ()
+ (resolve-interface module))
+ (lambda args
+ ;; Report the error, but keep going.
+ (warn file module args)
+ #f))))
+ (scheme-files (if sub-directory
+ (string-append directory "/" sub-directory)
+ directory)))))
(define* (scheme-modules* directory #:optional sub-directory)
"Return the list of module names found under SUB-DIRECTORY in DIRECTORY.
diff --git a/guix/docker.scm b/guix/docker.scm
index a6f73d423c..5e6460f43f 100644
--- a/guix/docker.scm
+++ b/guix/docker.scm
@@ -214,10 +214,11 @@ SRFI-19 time-utc object, as the creation time in metadata."
(else
(error "unsupported system"
system)))))))
- (cond* ("x86_64" "amd64")
- ("i686" "386")
- ("arm" "arm")
- ("mips64" "mips64le")))))
+ (cond* ("x86_64" "amd64")
+ ("i686" "386")
+ ("arm" "arm")
+ ("aarch64" "arm64")
+ ("mips64" "mips64le")))))
;; Make sure we start with a fresh, empty working directory.
(mkdir directory)
(with-directory-excursion directory
diff --git a/guix/download.scm b/guix/download.scm
index 85b97a4766..4e219c9f49 100644
--- a/guix/download.scm
+++ b/guix/download.scm
@@ -36,6 +36,7 @@
#:use-module (srfi srfi-26)
#:export (%mirrors
%disarchive-mirrors
+ %download-fallback-test
(url-fetch* . url-fetch)
url-fetch/executable
url-fetch/tarbomb
@@ -399,14 +400,23 @@
(plain-file "content-addressed-mirrors"
(object->string %content-addressed-mirrors)))
+(define %no-mirrors-file
+ ;; File specifying an empty list of mirrors, for fallback tests.
+ (plain-file "no-content-addressed-mirrors" (object->string ''())))
+
(define %disarchive-mirrors
;; TODO: Eventually turn into a procedure that takes a hash algorithm
;; (symbol) and hash (bytevector).
- '("https://disarchive.ngyro.com/"))
+ '("https://disarchive.guix.gnu.org/"
+ "https://disarchive.ngyro.com/"))
(define %disarchive-mirror-file
(plain-file "disarchive-mirrors" (object->string %disarchive-mirrors)))
+(define %no-disarchive-mirrors-file
+ ;; File specifying an empty list of Disarchive mirrors, for fallback tests.
+ (plain-file "no-disarchive-mirrors" (object->string '())))
+
(define built-in-builders*
(store-lift built-in-builders))
@@ -455,6 +465,24 @@ download by itself using its own dependencies."
;; for that built-in is widespread.
#:local-build? #t)))
+(define %download-fallback-test
+ ;; Define whether to test one of the download fallback mechanism. Possible
+ ;; values are:
+ ;;
+ ;; - #f, to use the normal download methods, not trying to exercise the
+ ;; fallback mechanism;
+ ;;
+ ;; - 'none, to disable all the fallback mechanisms;
+ ;;
+ ;; - 'content-addressed-mirrors, to purposefully attempt to download from
+ ;; a content-addressed mirror;
+ ;;
+ ;; - 'disarchive-mirrors, to download from Disarchive + Software Heritage.
+ ;;
+ ;; This is meant to be used for testing purposes.
+ (make-parameter (and=> (getenv "GUIX_DOWNLOAD_FALLBACK_TEST")
+ string->symbol)))
+
(define* (url-fetch* url hash-algo hash
#:optional name
#:key (system (%current-system))
@@ -490,7 +518,10 @@ name in the store."
(unless (member "download" builtins)
(error "'guix-daemon' is too old, please upgrade" builtins))
- (built-in-download (or name file-name) url
+ (built-in-download (or name file-name)
+ (match (%download-fallback-test)
+ ((or #f 'none) url)
+ (_ "https://example.org/does-not-exist"))
#:guile guile
#:system system
#:hash-algo hash-algo
@@ -498,9 +529,15 @@ name in the store."
#:executable? executable?
#:mirrors %mirror-file
#:content-addressed-mirrors
- %content-addressed-mirror-file
+ (match (%download-fallback-test)
+ ((or #f 'content-addressed-mirrors)
+ %content-addressed-mirror-file)
+ (_ %no-mirrors-file))
#:disarchive-mirrors
- %disarchive-mirror-file)))))
+ (match (%download-fallback-test)
+ ((or #f 'disarchive-mirrors)
+ %disarchive-mirror-file)
+ (_ %no-disarchive-mirrors-file)))))))
(define* (url-fetch/executable url hash-algo hash
#:optional name
diff --git a/guix/extracting-download.scm b/guix/extracting-download.scm
new file mode 100644
index 0000000000..4b7dcc7e83
--- /dev/null
+++ b/guix/extracting-download.scm
@@ -0,0 +1,179 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2017 Mathieu Lirzin <mthl@gnu.org>
+;;; Copyright © 2017 Christopher Baines <mail@cbaines.net>
+;;; Copyright © 2020 Jakub Kądziołka <kuba@kadziolka.net>
+;;; Copyright © 2020 Hartmut Goebel <h.goebel@crazy-compilers.com>
+;;;
+;;; 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 extracting-download)
+ #:use-module (ice-9 match)
+ #:use-module (ice-9 popen)
+ #:use-module ((guix build download) #:prefix build:)
+ #:use-module ((guix build utils) #:hide (delete))
+ #:use-module (guix gexp)
+ #:use-module (guix modules)
+ #:use-module (guix monads)
+ #:use-module (guix packages) ;; for %current-system
+ #:use-module (guix store)
+ #:use-module (guix utils)
+ #:use-module (srfi srfi-26)
+ #:export (http-fetch/extract
+ download-to-store/extract))
+
+;;;
+;;; Produce fixed-output derivations with data extracted from n archive
+;;; fetched over HTTP or FTP.
+;;;
+;;; This is meant to be used for package repositories where the actual source
+;;; archive is packed into another archive, eventually carrying meta-data.
+;;; Using this derivation saves both storing the outer archive and extracting
+;;; the actual one at build time. The hash is calculated on the actual
+;;; archive to ease validating the stored file.
+;;;
+
+(define* (http-fetch/extract url filename-to-extract hash-algo hash
+ #:optional name
+ #:key (system (%current-system)) (guile (default-guile)))
+ "Return a fixed-output derivation that fetches an archive at URL, and
+extracts FILE_TO_EXTRACT from the archive. The FILE_TO_EXTRACT is expected to
+have hash HASH of type HASH-ALGO (a symbol). By default, the file name is the
+base name of URL; optionally, NAME can specify a different file name."
+ (define file-name
+ (match url
+ ((head _ ...)
+ (basename head))
+ (_
+ (basename url))))
+
+ (define guile-zlib
+ (module-ref (resolve-interface '(gnu packages guile)) 'guile-zlib))
+
+ (define guile-json
+ (module-ref (resolve-interface '(gnu packages guile)) 'guile-json-4))
+
+ (define gnutls
+ (module-ref (resolve-interface '(gnu packages tls)) 'gnutls))
+
+ (define inputs
+ `(("tar" ,(module-ref (resolve-interface '(gnu packages base))
+ 'tar))))
+
+ (define config.scm
+ (scheme-file "config.scm"
+ #~(begin
+ (define-module (guix config)
+ #:export (%system))
+
+ (define %system
+ #$(%current-system)))))
+
+ (define modules
+ (cons `((guix config) => ,config.scm)
+ (delete '(guix config)
+ (source-module-closure '((guix build download)
+ (guix build utils)
+ (guix utils)
+ (web uri))))))
+
+ (define build
+ (with-imported-modules modules
+ (with-extensions (list guile-json gnutls ;for (guix swh)
+ guile-zlib)
+ #~(begin
+ (use-modules (guix build download)
+ (guix build utils)
+ (guix utils)
+ (web uri)
+ (ice-9 match)
+ (ice-9 popen))
+ ;; The code below expects tar to be in $PATH.
+ (set-path-environment-variable "PATH" '("bin")
+ (match '#+inputs
+ (((names dirs outputs ...) ...)
+ dirs)))
+
+ (setvbuf (current-output-port) 'line)
+ (setvbuf (current-error-port) 'line)
+
+ (call-with-temporary-directory
+ (lambda (directory)
+ ;; TODO: Support different archive types, based on content-type
+ ;; or archive name extention.
+ (let* ((file-to-extract (getenv "extract filename"))
+ (port (http-fetch (string->uri (getenv "download url"))
+ #:verify-certificate? #f))
+ (tar (open-pipe* OPEN_WRITE "tar" "-C" directory
+ "-xf" "-" file-to-extract)))
+ (dump-port port tar)
+ (close-port port)
+ (let ((status (close-pipe tar)))
+ (unless (zero? status)
+ (error "tar extraction failure" status)))
+ (copy-file (string-append directory "/"
+ (getenv "extract filename"))
+ #$output))))))))
+
+ (mlet %store-monad ((guile (package->derivation guile system)))
+ (gexp->derivation (or name file-name) build
+
+ ;; Use environment variables and a fixed script name so
+ ;; there's only one script in store for all the
+ ;; downloads.
+ #:script-name "extract-download"
+ #:env-vars
+ `(("download url" . ,url)
+ ("extract filename" . ,filename-to-extract))
+ #:leaked-env-vars '("http_proxy" "https_proxy"
+ "LC_ALL" "LC_MESSAGES" "LANG"
+ "COLUMNS")
+ #:system system
+ #:local-build? #t ; don't offload download
+ #:hash-algo hash-algo
+ #:hash hash
+ #:guile-for-build guile)))
+
+
+(define* (download-to-store/extract store url filename-to-extract
+ #:optional (name (basename url))
+ #:key (log (current-error-port))
+ (verify-certificate? #t))
+ "Download an archive from URL, and extracts FILE_TO_EXTRACT from the archive
+to STORE, either under NAME or URL's basename if omitted. Write progress
+reports to LOG. VERIFY-CERTIFICATE? determines whether or not to validate
+HTTPS server certificates."
+ (call-with-temporary-output-file
+ (lambda (temp port)
+ (let ((result
+ (parameterize ((current-output-port log))
+ (build:url-fetch url temp
+ ;;#:mirrors %mirrors
+ #:verify-certificate?
+ verify-certificate?))))
+ (close port)
+ (and result
+ (call-with-temporary-output-file
+ (lambda (contents port)
+ (let ((tar (open-pipe* OPEN_READ
+ "tar" ;"--auto-compress"
+ "-xf" temp "--to-stdout" filename-to-extract)))
+ (dump-port tar port)
+ (close-port port)
+ (let ((status (close-pipe tar)))
+ (unless (zero? status)
+ (error "tar extraction failure" status)))
+ (add-to-store store name #f "sha256" contents)))))))))
diff --git a/guix/gexp.scm b/guix/gexp.scm
index ff5ede2857..01dca902f7 100644
--- a/guix/gexp.scm
+++ b/guix/gexp.scm
@@ -685,7 +685,8 @@ SUFFIX."
expander => (lambda (obj lowered output)
(match obj
(($ <file-append> base suffix)
- (let* ((expand (lookup-expander base))
+ (let* ((expand (or (lookup-expander base)
+ (lookup-expander lowered)))
(base (expand base lowered output)))
(string-append base (string-concatenate suffix)))))))
@@ -923,9 +924,8 @@ corresponding <derivation-input> or store item."
(match graphs
(((file-names . inputs) ...)
- (mlet %store-monad ((inputs (without-grafting
- (lower-inputs (map tuple->gexp-input inputs)
- system target))))
+ (mlet %store-monad ((inputs (lower-inputs (map tuple->gexp-input inputs)
+ system target)))
(return (map cons file-names inputs))))))
(define* (lower-references lst #:key system target)
@@ -938,15 +938,13 @@ names and file names suitable for the #:allowed-references argument to
((? string? output)
(return output))
(($ <gexp-input> thing output native?)
- (mlet %store-monad ((drv (without-grafting
- (lower-object thing system
- #:target (if native?
- #f target)))))
+ (mlet %store-monad ((drv (lower-object thing system
+ #:target (if native?
+ #f target))))
(return (derivation->output-path drv output))))
(thing
- (mlet %store-monad ((drv (without-grafting
- (lower-object thing system
- #:target target))))
+ (mlet %store-monad ((drv (lower-object thing system
+ #:target target)))
(return (derivation->output-path drv))))))
(mapm/accumulate-builds lower lst)))
diff --git a/guix/git.scm b/guix/git.scm
index acc48fd12f..dc2ca1be84 100644
--- a/guix/git.scm
+++ b/guix/git.scm
@@ -34,8 +34,9 @@
#:use-module (guix records)
#:use-module (guix gexp)
#:use-module (guix sets)
- #:use-module ((guix diagnostics) #:select (leave))
+ #:use-module ((guix diagnostics) #:select (leave warning))
#:use-module (guix progress)
+ #:autoload (guix swh) (swh-download commit-id?)
#:use-module (rnrs bytevectors)
#:use-module (ice-9 format)
#:use-module (ice-9 match)
@@ -57,6 +58,8 @@
commit-difference
commit-relation
+ remote-refs
+
git-checkout
git-checkout?
git-checkout-url
@@ -180,6 +183,13 @@ the 'SSL_CERT_FILE' and 'SSL_CERT_DIR' environment variables."
(lambda args
(make-fetch-options auth-method)))))
+(define GITERR_HTTP
+ ;; Guile-Git <= 0.5.2 lacks this constant.
+ (let ((errors (resolve-interface '(git errors))))
+ (if (module-defined? errors 'GITERR_HTTP)
+ (module-ref errors 'GITERR_HTTP)
+ 34)))
+
(define (clone* url directory)
"Clone git repository at URL into DIRECTORY. Upon failure,
make sure no empty directory is left behind."
@@ -332,7 +342,8 @@ dynamic extent of EXP."
"Return true if REF, a reference such as '(commit . \"cabba9e\"), is
definitely available in REPOSITORY, false otherwise."
(match ref
- (('commit . commit)
+ ((or ('commit . commit)
+ ('tag-or-commit . (? commit-id? commit)))
(let ((len (string-length commit))
(oid (string->oid commit)))
(false-if-git-not-found
@@ -342,6 +353,42 @@ definitely available in REPOSITORY, false otherwise."
(_
#f)))
+(define (clone-from-swh url tag-or-commit output)
+ "Attempt to clone TAG-OR-COMMIT (a string), which originates from URL, using
+a copy archived at Software Heritage."
+ (call-with-temporary-directory
+ (lambda (bare)
+ (and (swh-download url tag-or-commit bare
+ #:archive-type 'git-bare)
+ (let ((repository (clone* bare output)))
+ (remote-set-url! repository "origin" url)
+ repository)))))
+
+(define (clone/swh-fallback url ref cache-directory)
+ "Like 'clone', but fallback to Software Heritage if the repository cannot be
+found at URL."
+ (define (inaccessible-url-error? err)
+ (let ((class (git-error-class err))
+ (code (git-error-code err)))
+ (or (= class GITERR_HTTP) ;404 or similar
+ (= class GITERR_NET)))) ;unknown host, etc.
+
+ (catch 'git-error
+ (lambda ()
+ (clone* url cache-directory))
+ (lambda (key err)
+ (match ref
+ (((or 'commit 'tag-or-commit) . commit)
+ (if (inaccessible-url-error? err)
+ (or (clone-from-swh url commit cache-directory)
+ (begin
+ (warning (G_ "revision ~a of ~a \
+could not be fetched from Software Heritage~%")
+ commit url)
+ (throw key err)))
+ (throw key err)))
+ (_ (throw key err))))))
+
(define cached-checkout-expiration
;; Return the expiration time procedure for a cached checkout.
;; TODO: Honor $GUIX_GIT_CACHE_EXPIRATION.
@@ -408,7 +455,7 @@ it unchanged."
(let* ((cache-exists? (openable-repository? cache-directory))
(repository (if cache-exists?
(repository-open cache-directory)
- (clone* url cache-directory))))
+ (clone/swh-fallback url ref cache-directory))))
;; Only fetch remote if it has not been cloned just before.
(when (and cache-exists?
(not (reference-available? repository ref)))
@@ -571,6 +618,45 @@ objects: 'ancestor (meaning that OLD is an ancestor of NEW), 'descendant, or
(if (set-contains? oldest new)
'descendant
'unrelated))))))
+
+;;
+;;; Remote operations.
+;;;
+
+(define* (remote-refs url #:key tags?)
+ "Return the list of references advertised at Git repository URL. If TAGS?
+is true, limit to only refs/tags."
+ (define (ref? ref)
+ ;; Like `git ls-remote --refs', only show actual references.
+ (and (string-prefix? "refs/" ref)
+ (not (string-suffix? "^{}" ref))))
+
+ (define (tag? ref)
+ (string-prefix? "refs/tags/" ref))
+
+ (define (include? ref)
+ (and (ref? ref)
+ (or (not tags?) (tag? ref))))
+
+ (define (remote-head->ref remote)
+ (let ((name (remote-head-name remote)))
+ (and (include? name)
+ name)))
+
+ (with-libgit2
+ (call-with-temporary-directory
+ (lambda (cache-directory)
+ (let* ((repository (repository-init cache-directory))
+ ;; Create an in-memory remote so we don't touch disk.
+ (remote (remote-create-anonymous repository url)))
+ (remote-connect remote)
+
+ (let* ((remote-heads (remote-ls remote))
+ (refs (filter-map remote-head->ref remote-heads)))
+ ;; Wait until we're finished with the repository before closing it.
+ (remote-disconnect remote)
+ (repository-close! repository)
+ refs))))))
;;;
diff --git a/guix/gnupg.scm b/guix/gnupg.scm
index 5fae24b325..088bebc0de 100644
--- a/guix/gnupg.scm
+++ b/guix/gnupg.scm
@@ -2,6 +2,7 @@
;;; Copyright © 2010, 2011, 2013, 2014, 2016, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
;;; Copyright © 2020 Tobias Geerinckx-Rice <me@tobias.gr>
+;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -56,9 +57,9 @@
"/gpg/trustedkeys.kbx")))
(define %openpgp-key-server
- ;; The default key server. Note that keys.gnupg.net appears to be
- ;; unreliable.
- (make-parameter "pool.sks-keyservers.net"))
+ ;; The default key server. It defaults to #f, which causes GnuPG to use the
+ ;; one it is configured with.
+ (make-parameter #f))
;; Regexps for status lines. See file `doc/DETAILS' in GnuPG.
@@ -182,22 +183,26 @@ missing key or its key id if the fingerprint is unavailable."
(_ #f)))
status))
-(define* (gnupg-receive-keys fingerprint/key-id server
- #:optional (keyring (current-keyring)))
- "Download FINGERPRINT/KEY-ID from SERVER, a key server, and add it to
-KEYRING."
+(define* (gnupg-receive-keys fingerprint/key-id
+ #:key server (keyring (current-keyring)))
+ "Download FINGERPRINT/KEY-ID from SERVER if specified, otherwise from
+GnuPG's default/configured one. The key is added to KEYRING."
(unless (file-exists? keyring)
(mkdir-p (dirname keyring))
- (call-with-output-file keyring (const #t))) ;create an empty keybox
+ (call-with-output-file keyring (const #t))) ;create an empty keybox
- (zero? (system* (%gpg-command) "--keyserver" server
- "--no-default-keyring" "--keyring" keyring
- "--recv-keys" fingerprint/key-id)))
+ (zero? (apply system*
+ `(,(%gpg-command)
+ ,@(if server
+ (list "--keyserver" server)
+ '())
+ "--no-default-keyring" "--keyring" ,keyring
+ "--recv-keys" ,fingerprint/key-id))))
(define* (gnupg-verify* sig file
#:key
(key-download 'interactive)
- (server (%openpgp-key-server))
+ server
(keyring (current-keyring)))
"Like `gnupg-verify', but try downloading the public key if it's missing.
Return two values: 'valid-signature and a fingerprint/name pair upon success,
@@ -215,7 +220,7 @@ fingerprint/user name pair on success and #f otherwise."
(let ((missing (gnupg-status-missing-key? status)))
(define (download-and-try-again)
;; Download the missing key and try again.
- (if (gnupg-receive-keys missing server keyring)
+ (if (gnupg-receive-keys missing #:server server #:keyring keyring)
(match (gnupg-status-good-signature?
(gnupg-verify sig file keyring))
(#f
diff --git a/guix/graph.scm b/guix/graph.scm
index 0d4cd83667..3a1cab244b 100644
--- a/guix/graph.scm
+++ b/guix/graph.scm
@@ -337,11 +337,12 @@ nodeArray.push(nodes[\"~a\"]);~%"
(define* (export-graph sinks port
#:key
- reverse-edges? node-type
+ reverse-edges? node-type (max-depth +inf.0)
(backend %graphviz-backend))
"Write to PORT the representation of the DAG with the given SINKS, using the
given BACKEND. Use NODE-TYPE to traverse the DAG. When REVERSE-EDGES? is
-true, draw reverse arrows."
+true, draw reverse arrows. Do not represent nodes whose distance to one of
+the SINKS is greater than MAX-DEPTH."
(match backend
(($ <graph-backend> _ _ emit-prologue emit-epilogue emit-node emit-edge)
(emit-prologue (node-type-name node-type) port)
@@ -349,6 +350,7 @@ true, draw reverse arrows."
(match node-type
(($ <node-type> node-identifier node-label node-edges)
(let loop ((nodes sinks)
+ (depths (make-list (length sinks) 0))
(visited (set)))
(match nodes
(()
@@ -356,20 +358,29 @@ true, draw reverse arrows."
(emit-epilogue port)
(store-return #t)))
((head . tail)
- (mlet %store-monad ((id (node-identifier head)))
- (if (set-contains? visited id)
- (loop tail visited)
- (mlet* %store-monad ((dependencies (node-edges head))
- (ids (mapm %store-monad
- node-identifier
- dependencies)))
- (emit-node id (node-label head) port)
- (for-each (lambda (dependency dependency-id)
- (if reverse-edges?
- (emit-edge dependency-id id port)
- (emit-edge id dependency-id port)))
- dependencies ids)
- (loop (append dependencies tail)
- (set-insert id visited)))))))))))))
+ (match depths
+ ((depth . depths)
+ (mlet %store-monad ((id (node-identifier head)))
+ (if (set-contains? visited id)
+ (loop tail depths visited)
+ (mlet* %store-monad ((dependencies
+ (if (= depth max-depth)
+ (return '())
+ (node-edges head)))
+ (ids
+ (mapm %store-monad
+ node-identifier
+ dependencies)))
+ (emit-node id (node-label head) port)
+ (for-each (lambda (dependency dependency-id)
+ (if reverse-edges?
+ (emit-edge dependency-id id port)
+ (emit-edge id dependency-id port)))
+ dependencies ids)
+ (loop (append dependencies tail)
+ (append (make-list (length dependencies)
+ (+ 1 depth))
+ depths)
+ (set-insert id visited)))))))))))))))
;;; graph.scm ends here
diff --git a/guix/import/cran.scm b/guix/import/cran.scm
index 510882bc00..1389576cad 100644
--- a/guix/import/cran.scm
+++ b/guix/import/cran.scm
@@ -34,6 +34,8 @@
#:use-module (web uri)
#:use-module (guix memoization)
#:use-module (guix http-client)
+ #:use-module (guix diagnostics)
+ #:use-module (guix i18n)
#:use-module (gcrypt hash)
#:use-module (guix store)
#:use-module ((guix serialization) #:select (write-file))
@@ -153,9 +155,9 @@ package definition."
(define %cran-canonical-url "https://cran.r-project.org/package=")
(define %bioconductor-url "https://bioconductor.org/packages/")
-;; The latest Bioconductor release is 3.13. Bioconductor packages should be
+;; The latest Bioconductor release is 3.14. Bioconductor packages should be
;; updated together.
-(define %bioconductor-version "3.13")
+(define %bioconductor-version "3.14")
(define* (bioconductor-packages-list-url #:optional type)
(string-append "https://bioconductor.org/packages/"
@@ -171,11 +173,11 @@ package definition."
release."
(let ((url (string->uri (bioconductor-packages-list-url type))))
(guard (c ((http-get-error? c)
- (format (current-error-port)
- "error: failed to retrieve list of packages from ~s: ~a (~s)~%"
- (uri->string (http-get-error-uri c))
- (http-get-error-code c)
- (http-get-error-reason c))
+ (warning (G_ "failed to retrieve list of packages \
+from ~a: ~a (~a)~%")
+ (uri->string (http-get-error-uri c))
+ (http-get-error-code c)
+ (http-get-error-reason c))
#f))
;; Split the big list on empty lines, then turn each chunk into an
;; alist of attributes.
@@ -227,27 +229,61 @@ bioconductor package NAME, or #F if the package is unknown."
(let ((store-directory
(add-to-store store (basename url) #t "sha256" dir)))
(values store-directory changeset)))))))
- (else (download-to-store store url)))))))
-
-(define (fetch-description repository name)
+ (else
+ (match url
+ ((? string?)
+ (download-to-store store url))
+ ((urls ...)
+ ;; Try all the URLs. A use case where this is useful is when one
+ ;; of the URLs is the /Archive CRAN URL.
+ (any (cut download-to-store store <>) urls)))))))))
+
+(define (fetch-description-from-tarball url)
+ "Fetch the tarball at URL, extra its 'DESCRIPTION' file, parse it, and
+return the resulting alist."
+ (match (download url)
+ (#f #f)
+ (tarball
+ (call-with-temporary-directory
+ (lambda (dir)
+ (parameterize ((current-error-port (%make-void-port "rw+"))
+ (current-output-port (%make-void-port "rw+")))
+ (and (zero? (system* "tar" "--wildcards" "-x"
+ "--strip-components=1"
+ "-C" dir
+ "-f" tarball "*/DESCRIPTION"))
+ (description->alist
+ (call-with-input-file (string-append dir "/DESCRIPTION")
+ read-string)))))))))
+
+(define* (fetch-description repository name #:optional version)
"Return an alist of the contents of the DESCRIPTION file for the R package
-NAME in the given REPOSITORY, or #f in case of failure. NAME is
+NAME at VERSION in the given REPOSITORY, or #f in case of failure. NAME is
case-sensitive."
(case repository
((cran)
- (let ((url (string-append %cran-url name "/DESCRIPTION")))
- (guard (c ((http-get-error? c)
- (format (current-error-port)
- "error: failed to retrieve package information \
-from ~s: ~a (~s)~%"
- (uri->string (http-get-error-uri c))
- (http-get-error-code c)
- (http-get-error-reason c))
- #f))
- (let* ((port (http-fetch url))
- (result (description->alist (read-string port))))
- (close-port port)
- result))))
+ (guard (c ((http-get-error? c)
+ (warning (G_ "failed to retrieve package information \
+from ~a: ~a (~a)~%")
+ (uri->string (http-get-error-uri c))
+ (http-get-error-code c)
+ (http-get-error-reason c))
+ #f))
+ ;; When VERSION is true, we have to download the tarball to get at its
+ ;; 'DESCRIPTION' file; only the latest one is directly accessible over
+ ;; HTTP.
+ (if version
+ (let ((urls (list (string-append "mirror://cran/src/contrib/"
+ name "_" version ".tar.gz")
+ (string-append "mirror://cran/src/contrib/Archive/"
+ name "/"
+ name "_" version ".tar.gz"))))
+ (fetch-description-from-tarball urls))
+ (let* ((url (string-append %cran-url name "/DESCRIPTION"))
+ (port (http-fetch url))
+ (result (description->alist (read-string port))))
+ (close-port port)
+ result))))
((bioconductor)
;; Currently, the bioconductor project does not offer a way to access a
;; package's DESCRIPTION file over HTTP, so we determine the version,
@@ -256,22 +292,13 @@ from ~s: ~a (~s)~%"
(and (latest-bioconductor-package-version name) #t)
(and (latest-bioconductor-package-version name 'annotation) 'annotation)
(and (latest-bioconductor-package-version name 'experiment) 'experiment)))
+ ;; TODO: Honor VERSION.
(version (latest-bioconductor-package-version name type))
(url (car (bioconductor-uri name version type)))
- (tarball (download url)))
- (call-with-temporary-directory
- (lambda (dir)
- (parameterize ((current-error-port (%make-void-port "rw+"))
- (current-output-port (%make-void-port "rw+")))
- (and (zero? (system* "tar" "--wildcards" "-x"
- "--strip-components=1"
- "-C" dir
- "-f" tarball "*/DESCRIPTION"))
- (and=> (description->alist (with-input-from-file
- (string-append dir "/DESCRIPTION") read-string))
- (lambda (meta)
- (if (boolean? type) meta
- (cons `(bioconductor-type . ,type) meta))))))))))
+ (meta (fetch-description-from-tarball url)))
+ (if (boolean? type)
+ meta
+ (cons `(bioconductor-type . ,type) meta))))
((git)
(and (string-prefix? "http" name)
;; Download the git repository at "NAME"
@@ -484,7 +511,7 @@ from the alist META, which was derived from the R package's DESCRIPTION file."
((bioconductor)
(list (assoc-ref meta 'bioconductor-type)))
(else '())))
- ((url rest ...) url)
+ ((urls ...) urls)
((? string? url) url)
(_ #f)))))
(git? (assoc-ref meta 'git))
@@ -591,7 +618,7 @@ from the alist META, which was derived from the R package's DESCRIPTION file."
(lambda* (package-name #:key (repo 'cran) version)
"Fetch the metadata for PACKAGE-NAME from REPO and return the `package'
s-expression corresponding to that package, or #f on failure."
- (let ((description (fetch-description repo package-name)))
+ (let ((description (fetch-description repo package-name version)))
(if description
(description->package repo description)
(case repo
@@ -609,8 +636,9 @@ s-expression corresponding to that package, or #f on failure."
(&message
(message "couldn't find meta-data for R package")))))))))))
-(define* (cran-recursive-import package-name #:key (repo 'cran))
+(define* (cran-recursive-import package-name #:key (repo 'cran) version)
(recursive-import package-name
+ #:version version
#:repo repo
#:repo->guix-package cran->guix-package
#:guix-name cran-guix-name))
diff --git a/guix/import/crate.scm b/guix/import/crate.scm
index 287ffd2536..c76d7e9c1a 100644
--- a/guix/import/crate.scm
+++ b/guix/import/crate.scm
@@ -1,6 +1,6 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2016 David Craven <david@craven.ch>
-;;; Copyright © 2019, 2020 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2019, 2020 Martin Becze <mjbecze@riseup.net>
;;; Copyright © 2021 Nicolas Goaziou <mail@nicolasgoaziou.fr>
;;;
@@ -79,7 +79,10 @@
(number crate-version-number "num") ;string
(download-path crate-version-download-path "dl_path") ;string
(readme-path crate-version-readme-path "readme_path") ;string
- (license crate-version-license "license") ;string
+ (license crate-version-license "license" ;string | #f
+ (match-lambda
+ ('null #f)
+ ((? string? str) str)))
(links crate-version-links)) ;alist
;; Crate dependency. Each dependency (each edge in the graph) is annotated as
@@ -198,6 +201,7 @@ and LICENSE."
(description ,(beautify-description description))
(license ,(match license
(() #f)
+ (#f #f)
((license) license)
(_ `(list ,@license)))))))
(close-port port)
diff --git a/guix/import/egg.scm b/guix/import/egg.scm
index 75b7659944..0b88020554 100644
--- a/guix/import/egg.scm
+++ b/guix/import/egg.scm
@@ -52,10 +52,10 @@
;;;
;;; The following happens under the hood:
;;;
-;;; * <git://code.call-cc.org/eggs-5-latest> is a Git repository that contains
-;;; the latest version of all CHICKEN eggs. We look clone this repository
-;;; and retrieve the latest version number, and the PACKAGE.egg file, which
-;;; contains a list of lists containing metadata about the egg.
+;;; * <git://code.call-cc.org/eggs-5-all> is a Git repository that contains
+;;; all versions of all CHICKEN eggs. We look clone this repository and, by
+;;; default, retrieve the latest version number, and the PACKAGE.egg file,
+;;; which contains a list of lists containing metadata about the egg.
;;;
;;; * All the eggs are stored as tarballs at
;;; <https://code.call-cc.org/egg-tarballs/5>, so we grab the tarball for
@@ -97,7 +97,7 @@ NAME."
(define (eggs-repository)
"Update or fetch the latest version of the eggs repository and return the path
to the repository."
- (let* ((url "git://code.call-cc.org/eggs-5-latest")
+ (let* ((url "git://code.call-cc.org/eggs-5-all")
(directory commit _ (update-cached-checkout url)))
directory))
@@ -113,12 +113,13 @@ to the repository."
(last directory)
#f)))
-(define* (egg-metadata name #:optional file)
- "Return the package metadata file for the egg NAME, or if FILE is specified,
-return the package metadata in FILE."
+(define* (egg-metadata name #:key (version #f) (file #f))
+ "Return the package metadata file for the egg NAME at version VERSION, or if
+FILE is specified, return the package metadata in FILE."
(call-with-input-file (or file
(string-append (egg-directory name) "/"
- (find-latest-version name)
+ (or version
+ (find-latest-version name))
"/" name ".egg"))
read))
@@ -174,10 +175,11 @@ return the package metadata in FILE."
;;; Egg importer.
;;;
-(define* (egg->guix-package name #:key (file #f) (source #f))
- "Import a CHICKEN egg called NAME from either the given .egg FILE, or from
-the latest NAME metadata downloaded from the official repository if FILE is #f.
-Return a <package> record or #f on failure.
+(define* (egg->guix-package name version #:key (file #f) (source #f))
+ "Import a CHICKEN egg called NAME from either the given .egg FILE, or from the
+latest NAME metadata downloaded from the official repository if FILE is #f.
+Return a <package> record or #f on failure. If VERSION is specified, import
+the particular version from the egg repository.
SOURCE is a ``file-like'' object containing the source code corresponding to
the egg. If SOURCE is not specified, the latest tarball for egg NAME will be
@@ -187,8 +189,8 @@ Specifying the SOURCE argument is mainly useful for developing a CHICKEN egg
locally. Note that if FILE and SOURCE are specified, recursive import will
not work."
(define egg-content (if file
- (egg-metadata name file)
- (egg-metadata name)))
+ (egg-metadata name #:file file)
+ (egg-metadata name #:version version)))
(if (not egg-content)
(values #f '()) ; egg doesn't exist
(let* ((version* (or (assoc-ref egg-content 'version)
@@ -324,10 +326,11 @@ not work."
(define egg->guix-package/m ;memoized variant
(memoize egg->guix-package))
-(define (egg-recursive-import package-name)
+(define* (egg-recursive-import package-name #:optional version)
(recursive-import package-name
+ #:version version
#:repo->guix-package (lambda* (name #:key version repo)
- (egg->guix-package/m name))
+ (egg->guix-package/m name version))
#:guix-name egg-name->guix-name))
diff --git a/guix/import/elpa.scm b/guix/import/elpa.scm
index 05b4a45f2f..edabb88b7a 100644
--- a/guix/import/elpa.scm
+++ b/guix/import/elpa.scm
@@ -46,6 +46,7 @@
#:use-module (guix packages)
#:use-module ((guix utils) #:select (call-with-temporary-output-file))
#:export (elpa->guix-package
+ guix-package->elpa-name
%elpa-updater
elpa-recursive-import))
@@ -337,9 +338,10 @@ the package named PACKAGE-NAME."
type '<elpa-package>'."
(define melpa-recipe
- (if (eq? repo 'melpa)
- (package-name->melpa-recipe (elpa-package-name pkg))
- #f))
+ ;; XXX: Call 'identity' to work around a Guile 3.0.[5-7] compiler bug:
+ ;; <https://bugs.gnu.org/48368>.
+ (and (eq? (identity repo) 'melpa)
+ (package-name->melpa-recipe (elpa-package-name pkg))))
(define name (elpa-package-name pkg))
@@ -387,7 +389,7 @@ type '<elpa-package>'."
'())
(home-page ,(elpa-package-home-page pkg))
(synopsis ,(elpa-package-synopsis pkg))
- (description ,(elpa-package-description pkg))
+ (description ,(beautify-description (elpa-package-description pkg)))
(license ,license))
dependencies-names))
@@ -411,13 +413,17 @@ type '<elpa-package>'."
;;; Updates.
;;;
+(define (guix-package->elpa-name package)
+ "Given a Guix package, PACKAGE, return the upstream name on ELPA."
+ (or (and=> (package-properties package)
+ (cut assq-ref <> 'upstream-name))
+ (if (string-prefix? "emacs-" (package-name package))
+ (string-drop (package-name package) 6)
+ (package-name package))))
+
(define (latest-release package)
"Return an <upstream-release> for the latest release of PACKAGE."
- (define name
- (if (string-prefix? "emacs-" (package-name package))
- (string-drop (package-name package) 6)
- (package-name package)))
-
+ (define name (guix-package->elpa-name package))
(define repo 'gnu)
(match (elpa-package-info name repo)
diff --git a/guix/import/git.scm b/guix/import/git.scm
new file mode 100644
index 0000000000..1eb219f3fe
--- /dev/null
+++ b/guix/import/git.scm
@@ -0,0 +1,225 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
+;;; Copyright © 2021 Sarah Morgensen <iskarian@mgsn.dev>
+;;;
+;;; 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 import git)
+ #:use-module (guix build utils)
+ #:use-module (guix diagnostics)
+ #:use-module (guix git)
+ #:use-module (guix git-download)
+ #:use-module (guix i18n)
+ #:use-module (guix packages)
+ #:use-module (guix upstream)
+ #:use-module (guix utils)
+ #:use-module (ice-9 format)
+ #:use-module (ice-9 match)
+ #:use-module (ice-9 rdelim)
+ #:use-module (ice-9 regex)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
+ #:use-module (srfi srfi-34)
+ #:use-module (srfi srfi-35)
+ #:export (%generic-git-updater
+
+ ;; For tests.
+ latest-git-tag-version))
+
+;;; Commentary:
+;;;
+;;; This module provides a generic package updater for packages hosted on Git
+;;; repositories.
+;;;
+;;; It tries to be smart about tag names, but if it is not automatically able
+;;; to parse the tag names correctly, users can set the `release-tag-prefix',
+;;; `release-tag-suffix' and `release-tag-version-delimiter' properties of the
+;;; package to make the updater parse the Git tag name correctly.
+;;;
+;;; Possible improvements:
+;;;
+;;; * More robust method for trying to guess the delimiter. Maybe look at the
+;;; previous version/tag combo to determine the delimiter.
+;;;
+;;; * Differentiate between "normal" versions, e.g., 1.2.3, and dates, e.g.,
+;;; 2021.12.31. Honor a `release-tag-date-scheme?' property?
+;;;
+;;; Code:
+
+;;; Errors & warnings
+
+(define-condition-type &git-no-valid-tags-error &error
+ git-no-valid-tags-error?)
+
+(define (git-no-valid-tags-error)
+ (raise (condition (&message (message "no valid tags found"))
+ (&git-no-valid-tags-error))))
+
+(define-condition-type &git-no-tags-error &error
+ git-no-tags-error?)
+
+(define (git-no-tags-error)
+ (raise (condition (&message (message "no tags were found"))
+ (&git-no-tags-error))))
+
+
+;;; Updater
+
+(define %pre-release-words
+ '("alpha" "beta" "rc" "dev" "test" "pre"))
+
+(define %pre-release-rx
+ (map (lambda (word)
+ (make-regexp (string-append ".+" word) regexp/icase))
+ %pre-release-words))
+
+(define* (version-mapping tags #:key prefix suffix delim pre-releases?)
+ "Given a list of Git TAGS, return an association list where the car is the
+version corresponding to the tag, and the cdr is the name of the tag."
+ (define (guess-delimiter)
+ (let ((total (length tags))
+ (dots (reduce + 0 (map (cut string-count <> #\.) tags)))
+ (dashes (reduce + 0 (map (cut string-count <> #\-) tags)))
+ (underscores (reduce + 0 (map (cut string-count <> #\_) tags))))
+ (cond
+ ((>= dots (* total 0.35)) ".")
+ ((>= dashes (* total 0.8)) "-")
+ ((>= underscores (* total 0.8)) "_")
+ (else ""))))
+
+ (define delim-rx (regexp-quote (or delim (guess-delimiter))))
+ (define suffix-rx (string-append (or suffix "") "$"))
+ (define prefix-rx (string-append "^" (or prefix "[^[:digit:]]*")))
+ (define pre-release-rx
+ (if pre-releases?
+ (string-append "(.*(" (string-join %pre-release-words "|") ").*)")
+ ""))
+
+ (define tag-rx
+ (string-append prefix-rx "([[:digit:]][^" delim-rx "[:punct:]]*"
+ "(" delim-rx "[^[:punct:]" delim-rx "]+)"
+ ;; If there are no delimiters, it could mean that the
+ ;; version just contains one number (e.g., "2"), thus, use
+ ;; "*" instead of "+" to match zero or more numbers.
+ (if (string=? delim-rx "") "*" "+") ")"
+ ;; We don't want the pre-release stuff (e.g., "-alpha") be
+ ;; part of the first group; otherwise, the "-" in "-alpha"
+ ;; might be interpreted as a delimiter, and thus replaced
+ ;; with "."
+ pre-release-rx suffix-rx))
+
+
+
+ (define (get-version tag)
+ (let ((tag-match (regexp-exec (make-regexp tag-rx) tag)))
+ (and=> (and tag-match
+ (regexp-substitute/global
+ #f delim-rx (match:substring tag-match 1)
+ ;; If there were no delimiters, don't insert ".".
+ 'pre (if (string=? delim-rx "") "" ".") 'post))
+ (lambda (version)
+ (if pre-releases?
+ (string-append version (match:substring tag-match 3))
+ version)))))
+
+ (define (entry<? a b)
+ (eq? (version-compare (car a) (car b)) '<))
+
+ (stable-sort (filter-map (lambda (tag)
+ (let ((version (get-version tag)))
+ (and version (cons version tag))))
+ tags)
+ entry<?))
+
+(define* (latest-tag url #:key prefix suffix delim pre-releases?)
+ "Return the latest version and corresponding tag available from the Git
+repository at URL."
+ (define (pre-release? tag)
+ (any (cut regexp-exec <> tag)
+ %pre-release-rx))
+
+ (let* ((tags (map (cut string-drop <> (string-length "refs/tags/"))
+ (remote-refs url #:tags? #t)))
+ (versions->tags
+ (version-mapping (if pre-releases?
+ tags
+ (filter (negate pre-release?) tags))
+ #:prefix prefix
+ #:suffix suffix
+ #:delim delim
+ #:pre-releases? pre-releases?)))
+ (cond
+ ((null? tags)
+ (git-no-tags-error))
+ ((null? versions->tags)
+ (git-no-valid-tags-error))
+ (else
+ (match (last versions->tags)
+ ((version . tag)
+ (values version tag)))))))
+
+(define (latest-git-tag-version package)
+ "Given a PACKAGE, return the latest version of it, or #f if the latest version
+could not be determined."
+ (guard (c ((or (git-no-tags-error? c) (git-no-valid-tags-error? c))
+ (warning (or (package-field-location package 'source)
+ (package-location package))
+ (G_ "~a for ~a~%")
+ (condition-message c)
+ (package-name package))
+ #f)
+ ((eq? (exception-kind c) 'git-error)
+ (warning (or (package-field-location package 'source)
+ (package-location package))
+ (G_ "failed to fetch Git repository for ~a~%")
+ (package-name package))
+ #f))
+ (let* ((source (package-source package))
+ (url (git-reference-url (origin-uri source)))
+ (property (cute assq-ref (package-properties package) <>)))
+ (latest-tag url
+ #:prefix (property 'release-tag-prefix)
+ #:suffix (property 'release-tag-suffix)
+ #:delim (property 'release-tag-version-delimiter)
+ #:pre-releases? (property 'accept-pre-releases?)))))
+
+(define (git-package? package)
+ "Return true if PACKAGE is hosted on a Git repository."
+ (match (package-source package)
+ ((? origin? origin)
+ (and (eq? (origin-method origin) git-fetch)
+ (git-reference? (origin-uri origin))))
+ (_ #f)))
+
+(define (latest-git-release package)
+ "Return an <upstream-source> for the latest release of PACKAGE."
+ (let* ((name (package-name package))
+ (old-version (package-version package))
+ (url (git-reference-url (origin-uri (package-source package))))
+ (new-version (latest-git-tag-version package)))
+
+ (and new-version
+ (upstream-source
+ (package name)
+ (version new-version)
+ (urls (list url))))))
+
+(define %generic-git-updater
+ (upstream-updater
+ (name 'generic-git)
+ (description "Updater for packages hosted on Git repositories")
+ (pred git-package?)
+ (latest latest-git-release)))
diff --git a/guix/import/gnu.scm b/guix/import/gnu.scm
index 51d5b77d34..2b9b71feb0 100644
--- a/guix/import/gnu.scm
+++ b/guix/import/gnu.scm
@@ -100,7 +100,8 @@ download policy (see 'download-tarball' for details.)"
(file-sha256 tarball))))))
(build-system gnu-build-system)
(synopsis ,(gnu-package-doc-summary package))
- (description ,(gnu-package-doc-description package))
+ (description ,(beautify-description
+ (gnu-package-doc-description package)))
(home-page ,(match (gnu-package-doc-urls package)
((head . tail) (qualified-url head))))
(license find-by-yourself!)))
diff --git a/guix/import/go.scm b/guix/import/go.scm
index c6ecdbaffd..26dbc34b63 100644
--- a/guix/import/go.scm
+++ b/guix/import/go.scm
@@ -474,13 +474,13 @@ Optionally include a VERSION string to append to the name."
because goproxy servers don't currently provide all the information needed to
build a package."
(define (go-import->module-meta content-text)
- (match (string-split content-text #\space)
+ (match (string-tokenize content-text char-set:graphic)
((root-path vcs repo-url)
(make-module-meta root-path (string->symbol vcs)
(strip-.git-suffix/maybe repo-url)))))
;; <meta name="go-import" content="import-prefix vcs repo-root">
(let* ((meta-data (http-fetch* (format #f "https://~a?go-get=1" module-path)))
- (select (sxpath `(// head (meta (@ (equal? (name "go-import"))))
+ (select (sxpath `(// (meta (@ (equal? (name "go-import"))))
// content))))
(match (select (html->sxml meta-data #:strict? #t))
(() #f) ;nothing selected
@@ -612,6 +612,8 @@ hint: use one of the following available versions ~a\n"
(dependencies (if pin-versions?
dependencies+versions
(map car dependencies+versions)))
+ (module-path-sans-suffix
+ (match:prefix (string-match "([\\./]v[0-9]+)?$" module-path)))
(guix-name (go-module->guix-package-name module-path))
(root-module-path (module-path->repository-root module-path))
;; The VCS type and URL are not included in goproxy information. For
@@ -619,7 +621,7 @@ hint: use one of the following available versions ~a\n"
(meta-data (fetch-module-meta-data root-module-path))
(vcs-type (module-meta-vcs meta-data))
(vcs-repo-url (module-meta-data-repo-url meta-data goproxy))
- (synopsis (go-package-synopsis root-module-path))
+ (synopsis (go-package-synopsis module-path))
(description (go-package-description module-path))
(licenses (go-package-licenses module-path)))
(values
@@ -630,7 +632,10 @@ hint: use one of the following available versions ~a\n"
,(vcs->origin vcs-type vcs-repo-url version*))
(build-system go-build-system)
(arguments
- '(#:import-path ,root-module-path))
+ '(#:import-path ,module-path
+ ,@(if (string=? module-path-sans-suffix root-module-path)
+ '()
+ `(#:unpack-path ,root-module-path))))
,@(maybe-propagated-inputs
(map (match-lambda
((name version)
diff --git a/guix/import/hackage.scm b/guix/import/hackage.scm
index 7c6d9d0a22..b94f4169d4 100644
--- a/guix/import/hackage.scm
+++ b/guix/import/hackage.scm
@@ -33,7 +33,7 @@
#:use-module ((guix utils) #:select (package-name->name+version
canonical-newline-port))
#:use-module (guix http-client)
- #:use-module ((guix import utils) #:select (factorize-uri recursive-import))
+ #:use-module (guix import utils)
#:use-module (guix import cabal)
#:use-module (guix store)
#:use-module (gcrypt hash)
@@ -41,6 +41,7 @@
#:use-module (guix memoization)
#:use-module (guix upstream)
#:use-module (guix packages)
+ #:autoload (guix build-system haskell) (hackage-uri)
#:use-module ((guix utils) #:select (call-with-temporary-output-file))
#:export (%hackage-url
hackage->guix-package
@@ -55,8 +56,8 @@
hackage-package?))
(define ghc-standard-libraries
- ;; List of libraries distributed with ghc (8.6.5).
- ;; Contents of ...-ghc-8.6.5/lib/ghc-8.6.5.
+ ;; List of libraries distributed with ghc (as of 8.10.7).
+ ;; Contents of …-ghc-8.10.7/lib/ghc-8.10.7
'("ghc"
"cabal" ;; in the output of `ghc-pkg list` Cabal is uppercased, but
;; hackage-name->package-name takes this into account.
@@ -68,6 +69,7 @@
"containers"
"deepseq"
"directory"
+ "exceptions"
"filepath"
"ghc"
"ghc-boot"
@@ -121,12 +123,12 @@ version is returned."
(string-append package-name-prefix (string-downcase name))))
(define guix-package->hackage-name
- (let ((uri-rx (make-regexp "https?://hackage.haskell.org/package/([^/]+)/.*"))
+ (let ((uri-rx (make-regexp "(https?://hackage.haskell.org|mirror://hackage)/package/([^/]+)/.*"))
(name-rx (make-regexp "(.*)-[0-9\\.]+")))
(lambda (package)
"Given a Guix package name, return the corresponding Hackage name."
(let* ((source-url (and=> (package-source package) origin-uri))
- (name (match:substring (regexp-exec uri-rx source-url) 1)))
+ (name (match:substring (regexp-exec uri-rx source-url) 2)))
(match (regexp-exec name-rx name)
(#f name)
(m (match:substring m 1)))))))
@@ -301,7 +303,7 @@ the hash of the Cabal file."
(version ,version)
(source (origin
(method url-fetch)
- (uri (string-append ,@(factorize-uri source-url version)))
+ (uri (hackage-uri ,name version))
(sha256
(base32
,(if tarball
@@ -313,7 +315,7 @@ the hash of the Cabal file."
,@(maybe-arguments)
(home-page ,(cabal-package-home-page cabal))
(synopsis ,(cabal-package-synopsis cabal))
- (description ,(cabal-package-description cabal))
+ (description ,(beautify-description (cabal-package-description cabal)))
(license ,(string->license (cabal-package-license cabal))))
(append hackage-dependencies hackage-native-dependencies))))
@@ -351,7 +353,7 @@ respectively."
#:guix-name hackage-name->package-name))
(define hackage-package?
- (let ((hackage-rx (make-regexp "https?://hackage.haskell.org")))
+ (let ((hackage-rx (make-regexp "(https?://hackage.haskell.org|mirror://hackage/)")))
(url-predicate (cut regexp-exec hackage-rx <>))))
(define (latest-release package)
@@ -365,7 +367,7 @@ respectively."
(hackage-cabal-url hackage-name))
#f)
((_ *** ("version" (version)))
- (let ((url (hackage-source-url hackage-name version)))
+ (let ((url (hackage-uri hackage-name version)))
(upstream-source
(package (package-name package))
(version version)
diff --git a/guix/import/minetest.scm b/guix/import/minetest.scm
index e1f8487b75..abddd885ee 100644
--- a/guix/import/minetest.scm
+++ b/guix/import/minetest.scm
@@ -25,6 +25,8 @@
#:use-module (srfi srfi-2)
#:use-module (srfi srfi-11)
#:use-module (srfi srfi-26)
+ #:use-module ((guix packages) #:prefix package:)
+ #:use-module (guix upstream)
#:use-module (guix utils)
#:use-module (guix ui)
#:use-module (guix i18n)
@@ -36,15 +38,19 @@
#:use-module (json)
#:use-module (guix base32)
#:use-module (guix git)
+ #:use-module ((guix git-download) #:prefix download:)
#:use-module (guix store)
#:export (%default-sort-key
%contentdb-api
json->package
contentdb-fetch
elaborate-contentdb-name
+ minetest-package?
+ latest-minetest-release
minetest->guix-package
minetest-recursive-import
- sort-packages))
+ sort-packages
+ %minetest-updater))
;; The ContentDB API is documented at
;; <https://content.minetest.net>.
@@ -203,7 +209,7 @@ raise an exception."
(match correctly-named
((one) (package-keys-full-name one))
((too . many)
- (warning (G_ "~a is ambigious, presuming ~a (other options include: ~a)~%")
+ (warning (G_ "~a is ambiguous, presuming ~a (other options include: ~a)~%")
name (package-keys-full-name too)
(map package-keys-full-name many))
(package-keys-full-name too))
@@ -256,7 +262,7 @@ and possibly some other packages as well, or #f on failure."
(order "desc"))
"Search ContentDB for Q (a string). Sort by SORT, in ascending order
if ORDER is \"asc\" or descending order if ORDER is \"desc\". TYPE must
-be \"mod\", \"game\" or \"txp\", restricting thes search results to
+be \"mod\", \"game\" or \"txp\", restricting the search results to
respectively mods, games and texture packs. Limit to at most LIMIT
results. The return value is a list of <package-keys> records."
;; XXX does Guile have something for constructing (and, when necessary,
@@ -316,7 +322,7 @@ MEDIA-LICENSE and LICENSE."
,@(maybe-propagated-inputs (map contentdb->package-name inputs))
(home-page ,home-page)
(synopsis ,(delete-cr synopsis))
- (description ,(delete-cr description))
+ (description ,(beautify-description (delete-cr description)))
(license ,(if (eq? media-license license)
license
`(list ,media-license ,license)))
@@ -337,6 +343,25 @@ official Minetest forum and the Git repository (if any)."
(and=> (package-forums package) topic->url-sexp)
(package-repository package)))
+(define (release-version release)
+ "Guess the version of RELEASE from the release title."
+ (define title (release-title release))
+ (if (string-prefix? "v" title)
+ ;; Remove "v" prefix from release titles like ‘v1.0.1’.
+ (substring title 1)
+ title))
+
+(define (version-style version)
+ "Determine the kind of version number VERSION is -- a date, or a conventional
+conventional version number."
+ (define dots? (->bool (string-index version #\.)))
+ (define hyphens? (->bool (string-index version #\-)))
+ (match (cons dots? hyphens?)
+ ((#true . #false) 'regular) ; something like "0.1"
+ ((#false . #false) 'regular) ; single component version number
+ ((#true . #true) 'regular) ; result of 'git-version'
+ ((#false . #true) 'date))) ; something like "2021-01-25"
+
;; If the default sort key is changed, make sure to modify 'show-help'
;; in (guix scripts import minetest) appropriately as well.
(define %default-sort-key "score")
@@ -371,7 +396,11 @@ official Minetest forum and the Git repository (if any)."
DEPENDENCIES as a list of AUTHOR/NAME strings."
(define dependency-list
(assoc-ref dependencies author/name))
- (filter-map
+ ;; A mod can have multiple dependencies implemented by the same mod,
+ ;; so remove duplicate mod names.
+ (define (filter-deduplicate-map f list)
+ (delete-duplicates (filter-map f list)))
+ (filter-deduplicate-map
(lambda (dependency)
(and (not (dependency-optional? dependency))
(not (builtin-mod? (dependency-name dependency)))
@@ -432,7 +461,7 @@ list of AUTHOR/NAME strings."
(define important-upstream-dependencies
(important-dependencies dependencies author/name #:sort sort))
(values (make-minetest-sexp author/name
- (release-title release) ; version
+ (release-version release)
(package-repository package)
(release-commit release)
important-upstream-dependencies
@@ -454,3 +483,37 @@ list of AUTHOR/NAME strings."
(recursive-import author/name
#:repo->guix-package minetest->guix-package*
#:guix-name contentdb->package-name))
+
+(define (minetest-package? pkg)
+ "Is PKG a Minetest mod on ContentDB?"
+ (and (string-prefix? "minetest-" (package:package-name pkg))
+ (assq-ref (package:package-properties pkg) 'upstream-name)))
+
+(define (latest-minetest-release pkg)
+ "Return an <upstream-source> for the latest release of the package PKG,
+or #false if the latest release couldn't be determined."
+ (define author/name
+ (assq-ref (package:package-properties pkg) 'upstream-name))
+ (define contentdb-package (contentdb-fetch author/name)) ; TODO warn if #f?
+ (define release (latest-release author/name))
+ (define source (package:package-source pkg))
+ (and contentdb-package release
+ (release-commit release) ; not always set
+ ;; Only continue if both the old and new version number are both
+ ;; dates or regular version numbers, as two different styles confuses
+ ;; the logic for determining which version is newer.
+ (eq? (version-style (release-version release))
+ (version-style (package:package-version pkg)))
+ (upstream-source
+ (package (package:package-name pkg))
+ (version (release-version release))
+ (urls (list (download:git-reference
+ (url (package-repository contentdb-package))
+ (commit (release-commit release))))))))
+
+(define %minetest-updater
+ (upstream-updater
+ (name 'minetest)
+ (description "Updater for Minetest packages on ContentDB")
+ (pred minetest-package?)
+ (latest latest-minetest-release)))
diff --git a/guix/import/opam.scm b/guix/import/opam.scm
index f8402ff5ba..a6f6fe8c9f 100644
--- a/guix/import/opam.scm
+++ b/guix/import/opam.scm
@@ -231,7 +231,8 @@ path to the repository."
(('list-pat . stuff) stuff)
(('string-pat stuff) stuff)
(('multiline-string stuff) stuff)
- (('dict records ...) records))
+ (('dict records ...) records)
+ (_ #f))
acc))))
#f file))
@@ -317,7 +318,7 @@ path to the repository."
(_ others)))
#f
(filter-map get-opam-repository repositories-specs))
- (leave (G_ "package '~a' not found~%") name)))
+ (warning (G_ "opam: package '~a' not found~%") name)))
(define* (opam->guix-package name #:key (repo 'opam) version)
"Import OPAM package NAME from REPOSITORY (a directory name) or, if
@@ -370,7 +371,8 @@ or #f on failure."
,(list 'quasiquote `((upstream-name . ,name))))))
(home-page ,(metadata-ref opam-content "homepage"))
(synopsis ,(metadata-ref opam-content "synopsis"))
- (description ,(metadata-ref opam-content "description"))
+ (description ,(beautify-description
+ (metadata-ref opam-content "description")))
(license ,(spdx-string->license
(metadata-ref opam-content "license"))))
(filter
diff --git a/guix/import/print.scm b/guix/import/print.scm
index c1739f35e3..66016145cb 100644
--- a/guix/import/print.scm
+++ b/guix/import/print.scm
@@ -26,6 +26,7 @@
#:use-module (guix build-system)
#:use-module (gnu packages)
#:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
#:use-module (guix import utils)
#:use-module (ice-9 control)
#:use-module (ice-9 match)
@@ -39,9 +40,6 @@
(_ #f))
inputs))
-;; FIXME: the quasiquoted arguments field may contain embedded package
-;; objects, e.g. in #:disallowed-references; they will just be printed with
-;; their usual #<package ...> representation, not as variable names.
(define (package->code package)
"Return an S-expression representing the source code that produces PACKAGE
when evaluated."
@@ -81,6 +79,11 @@ when evaluated."
(file-type (quote ,(search-path-specification-file-type spec)))
(file-pattern ,(search-path-specification-file-pattern spec))))
+ (define (factorized-uri-code uri version)
+ (match (factorize-uri uri version)
+ ((? string? uri) uri)
+ ((factorized ...) `(string-append ,@factorized))))
+
(define (source->code source version)
(let ((uri (origin-uri source))
(method (origin-method source))
@@ -98,9 +101,14 @@ when evaluated."
(guix hg-download)
(guix svn-download)))
(procedure-name method)))
- (uri (string-append ,@(match (factorize-uri uri version)
- ((? string? uri) (list uri))
- (factorized factorized))))
+ (uri ,(if version
+ (match uri
+ ((? string? uri)
+ (factorized-uri-code uri version))
+ ((lst ...)
+ `(list
+ ,@(map (cut factorized-uri-code <> version) uri))))
+ uri))
,(if (equal? (content-hash-algorithm hash) 'sha256)
`(sha256 (base32 ,(bytevector->nix-base32-string
(content-hash-value hash))))
@@ -110,36 +118,62 @@ when evaluated."
;; FIXME: in order to be able to throw away the directory prefix,
;; we just assume that the patch files can be found with
;; "search-patches".
- ,@(if (null? patches) '()
- `((patches (search-patches ,@(map basename patches))))))))
+ ,@(cond ((null? patches)
+ '())
+ ((every string? patches)
+ `((patches (search-patches ,@(map basename patches)))))
+ (else
+ `((patches (list ,@(map (match-lambda
+ ((? string? file)
+ `(search-patch ,file))
+ ((? origin? origin)
+ (source->code origin #f)))
+ patches)))))))))
+
+ (define (variable-reference module name)
+ ;; FIXME: using '@ certainly isn't pretty, but it avoids having to import
+ ;; the individual package modules.
+ (list '@ module name))
+
+ (define (object->code obj quoted?)
+ (match obj
+ ((? package? package)
+ (let* ((module (package-module-name package))
+ (name (variable-name package module)))
+ (if quoted?
+ (list 'unquote (variable-reference module name))
+ (variable-reference module name))))
+ ((? origin? origin)
+ (let ((code (source->code origin #f)))
+ (if quoted?
+ (list 'unquote code)
+ code)))
+ ((lst ...)
+ (let ((lst (map (cut object->code <> #t) lst)))
+ (if quoted?
+ lst
+ (list 'quasiquote lst))))
+ (obj
+ obj)))
(define (inputs->code inputs)
(if (redundant-input-labels? inputs)
`(list ,@(map (match-lambda ;no need for input labels ("new style")
((_ package)
- (let ((module (package-module-name package)))
- `(@ ,module ,(variable-name package module))))
+ (let* ((module (package-module-name package))
+ (name (variable-name package module)))
+ (variable-reference module name)))
((_ package output)
- (let ((module (package-module-name package)))
+ (let* ((module (package-module-name package))
+ (name (variable-name package module)))
(list 'quasiquote
(list
(list 'unquote
- `(@ ,module
- ,(variable-name package module)))
+ (variable-reference module name))
output)))))
inputs))
(list 'quasiquote ;preserve input labels (deprecated)
- (map (match-lambda
- ((label pkg . out)
- (let ((mod (package-module-name pkg)))
- (cons* label
- ;; FIXME: using '@ certainly isn't pretty, but it
- ;; avoids having to import the individual package
- ;; modules.
- (list 'unquote
- (list '@ mod (variable-name pkg mod)))
- out))))
- inputs))))
+ (object->code inputs #t))))
(let ((name (package-name package))
(version (package-version package))
@@ -175,7 +209,8 @@ when evaluated."
'-build-system)))
,@(match arguments
(() '())
- (args `((arguments ,(list 'quasiquote args)))))
+ (_ `((arguments
+ ,(list 'quasiquote (object->code arguments #t))))))
,@(match outputs
(("out") '())
(outs `((outputs (list ,@outs)))))
diff --git a/guix/import/pypi.scm b/guix/import/pypi.scm
index f3619dcd9e..b4284f5c33 100644
--- a/guix/import/pypi.scm
+++ b/guix/import/pypi.scm
@@ -9,6 +9,8 @@
;;; Copyright © 2020 Lars-Dominik Braun <ldb@leibniz-psychology.org>
;;; Copyright © 2020 Arun Isaac <arunisaac@systemreboot.net>
;;; Copyright © 2020 Martin Becze <mjbecze@riseup.net>
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
+;;; Copyright © 2021 Marius Bakke <marius@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -112,7 +114,7 @@
(url distribution-url) ;string
(digests distribution-digests) ;list of string pairs
(file-name distribution-file-name "filename") ;string
- (has-signature? distribution-has-signature? "hash_sig") ;Boolean
+ (has-signature? distribution-has-signature? "has_sig") ;Boolean
(package-type distribution-package-type "packagetype") ;"bdist_wheel" | ...
(python-version distribution-package-python-version
"python_version"))
@@ -127,27 +129,30 @@
missing-source-error?
(package missing-source-error-package))
-(define (latest-source-release pypi-package)
- "Return the latest source release for PYPI-PACKAGE."
- (let ((releases (assoc-ref (pypi-project-releases pypi-package)
- (project-info-version
- (pypi-project-info pypi-package)))))
+(define (latest-version project)
+ "Return the latest version of PROJECT, a <pypi-project> record."
+ (project-info-version (pypi-project-info project)))
+
+(define* (source-release pypi-package
+ #:optional (version (latest-version pypi-package)))
+ "Return the source release of VERSION for PYPI-PACKAGE, a <pypi-project>
+record, by default the latest version."
+ (let ((releases (or (assoc-ref (pypi-project-releases pypi-package) version)
+ '())))
(or (find (lambda (release)
(string=? "sdist" (distribution-package-type release)))
releases)
(raise (condition (&missing-source-error
(package pypi-package)))))))
-(define (latest-wheel-release pypi-package)
+(define* (wheel-release pypi-package
+ #:optional (version (latest-version pypi-package)))
"Return the url of the wheel for the latest release of pypi-package,
or #f if there isn't any."
- (let ((releases (assoc-ref (pypi-project-releases pypi-package)
- (project-info-version
- (pypi-project-info pypi-package)))))
- (or (find (lambda (release)
- (string=? "bdist_wheel" (distribution-package-type release)))
- releases)
- #f)))
+ (let ((releases (assoc-ref (pypi-project-releases pypi-package) version)))
+ (find (lambda (release)
+ (string=? "bdist_wheel" (distribution-package-type release)))
+ releases)))
(define (python->package-name name)
"Given the NAME of a package on PyPI, return a Guix-compliant name for the
@@ -163,12 +168,13 @@ package on PyPI."
(hyphen-package-name->name+version
(basename (file-sans-extension url))))
- (match (and=> (package-source package) origin-uri)
- ((? string? url)
- (url->pypi-name url))
- ((lst ...)
- (any url->pypi-name lst))
- (#f #f)))
+ (or (assoc-ref (package-properties package) 'upstream-name)
+ (match (and=> (package-source package) origin-uri)
+ ((? string? url)
+ (url->pypi-name url))
+ ((lst ...)
+ (any url->pypi-name lst))
+ (#f #f))))
(define (wheel-url->extracted-directory wheel-url)
(match (string-split (basename wheel-url) #\-)
@@ -416,6 +422,11 @@ return the unaltered list of upstream dependency names."
description license)
"Return the `package' s-expression for a python package with the given NAME,
VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE."
+ (define (maybe-upstream-name name)
+ (if (string-match ".*\\-[0-9]+" name)
+ `((properties ,`'(("upstream-name" . ,name))))
+ '()))
+
(call-with-temporary-output-file
(lambda (temp port)
(and (url-fetch source-url temp)
@@ -454,12 +465,13 @@ VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE."
(sha256
(base32
,(guix-hash-url temp)))))
+ ,@(maybe-upstream-name name)
(build-system python-build-system)
,@(maybe-inputs required-inputs 'propagated-inputs)
,@(maybe-inputs native-inputs 'native-inputs)
(home-page ,home-page)
(synopsis ,synopsis)
- (description ,description)
+ (description ,(beautify-description description))
(license ,(license->symbol license)))
upstream-dependencies))))))))
@@ -469,18 +481,17 @@ VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE."
"Fetch the metadata for PACKAGE-NAME from pypi.org, and return the
`package' s-expression corresponding to that package, or #f on failure."
(let* ((project (pypi-fetch package-name))
- (info (and project (pypi-project-info project))))
+ (info (and=> project pypi-project-info))
+ (version (or version (and=> project latest-version))))
(and project
(guard (c ((missing-source-error? c)
(let ((package (missing-source-error-package c)))
(leave (G_ "no source release for pypi package ~a ~a~%")
- (project-info-name info)
- (project-info-version info)))))
- (make-pypi-sexp (project-info-name info)
- (project-info-version info)
- (and=> (latest-source-release project)
+ (project-info-name info) version))))
+ (make-pypi-sexp (project-info-name info) version
+ (and=> (source-release project version)
distribution-url)
- (and=> (latest-wheel-release project)
+ (and=> (wheel-release project version)
distribution-url)
(project-info-home-page info)
(project-info-summary info)
@@ -488,8 +499,9 @@ VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE."
(string->license
(project-info-license info)))))))))
-(define (pypi-recursive-import package-name)
+(define* (pypi-recursive-import package-name #:optional version)
(recursive-import package-name
+ #:version version
#:repo->guix-package pypi->guix-package
#:guix-name python->package-name))
@@ -522,12 +534,19 @@ VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE."
(guard (c ((missing-source-error? c) #f))
(let* ((info (pypi-project-info pypi-package))
(version (project-info-version info))
- (url (distribution-url
- (latest-source-release pypi-package))))
+ (dist (source-release pypi-package))
+ (url (distribution-url dist)))
(upstream-source
+ (urls (list url))
+ (signature-urls
+ (if (distribution-has-signature? dist)
+ (list (string-append url ".asc"))
+ #f))
+ (input-changes
+ (changed-inputs package
+ (pypi->guix-package pypi-name)))
(package (package-name package))
- (version version)
- (urls (list url))))))))
+ (version version)))))))
(define %pypi-updater
(upstream-updater
diff --git a/guix/import/stackage.scm b/guix/import/stackage.scm
index bbd903a2cd..49be982a7f 100644
--- a/guix/import/stackage.scm
+++ b/guix/import/stackage.scm
@@ -2,6 +2,8 @@
;;; Copyright © 2017 Federico Beffa <beffa@fbengineering.ch>
;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2020 Martin Becze <mjbecze@riseup.net>
+;;; Copyright © 2021 Xinglu Chem <public@yoctocell.xyz>
+;;; Copyright © 2021 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -20,9 +22,8 @@
(define-module (guix import stackage)
#:use-module (ice-9 match)
- #:use-module (ice-9 regex)
+ #:use-module (json)
#:use-module (srfi srfi-1)
- #:use-module (srfi srfi-26)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
#:use-module (guix import json)
@@ -31,6 +32,8 @@
#:use-module (guix memoization)
#:use-module (guix packages)
#:use-module (guix upstream)
+ #:use-module (guix diagnostics)
+ #:use-module (guix i18n)
#:export (%stackage-url
stackage->guix-package
stackage-recursive-import
@@ -44,15 +47,31 @@
(define %stackage-url
(make-parameter "https://www.stackage.org"))
-;; Latest LTS version compatible with GHC 8.6.5.
-(define %default-lts-version "14.27")
-
-(define (lts-info-packages lts-info)
- "Returns 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))))))
+;; Latest LTS version compatible with current GHC.
+(define %default-lts-version "18.14")
+
+(define-json-mapping <stackage-lts> make-stackage-lts
+ stackage-lts?
+ json->stackage-lts
+ (snapshot stackage-lts-snapshot "snapshot" json->snapshot)
+ (packages stackage-lts-packages "packages"
+ (lambda (vector)
+ (map json->stackage-package (vector->list vector)))))
+
+(define-json-mapping <snapshot> make-snapshot
+ stackage-snapshot?
+ json->snapshot
+ (name snapshot-name)
+ (ghc-version snapshot-ghc-version)
+ (compiler snapshot-compiler))
+
+(define-json-mapping <stackage-package> make-stackage-package
+ stackage-package?
+ json->stackage-package
+ (origin stackage-package-origin)
+ (name stackage-package-name)
+ (version stackage-package-version)
+ (synopsis stackage-package-synopsis))
(define stackage-lts-info-fetch
;; "Retrieve the information about the LTS Stackage release VERSION."
@@ -62,21 +81,15 @@
"/lts-" (if (string-null? version)
%default-lts-version
version)))
- (lts-info (json-fetch url)))
- (if lts-info
- (reverse lts-info)
- (leave-with-message "LTS release version not found: ~a" version))))))
-
-(define (stackage-package-name pkg-info)
- (assoc-ref pkg-info "name"))
-
-(define (stackage-package-version pkg-info)
- (assoc-ref pkg-info "version"))
+ (lts-info (and=> (json-fetch url) json->stackage-lts)))
+ (or lts-info
+ (raise (formatted-message (G_ "LTS release version not found: ~a")
+ version)))))))
-(define (lts-package-version pkgs-info name)
- "Return the version of the package with upstream NAME included in PKGS-INFO."
+(define (lts-package-version packages name)
+ "Return the version of the package with upstream NAME included in PACKAGES."
(let ((pkg (find (lambda (pkg) (string=? (stackage-package-name pkg) name))
- (vector->list pkgs-info))))
+ packages)))
(stackage-package-version pkg)))
@@ -93,21 +106,22 @@
#:key
(include-test-dependencies? #t)
(lts-version %default-lts-version)
- (packages-info
- (lts-info-packages
+ (packages
+ (stackage-lts-packages
(stackage-lts-info-fetch lts-version))))
"Fetch Cabal file for PACKAGE-NAME from hackage.haskell.org. The retrieved
version corresponds to the version of PACKAGE-NAME specified in the LTS-VERSION
release at stackage.org. Return the `package' S-expression corresponding to
that package, or #f on failure. PACKAGES-INFO is the alist with the packages
included in the Stackage LTS release."
- (let* ((version (lts-package-version packages-info package-name))
+ (let* ((version (lts-package-version packages package-name))
(name-version (hackage-name-version package-name version)))
(if name-version
(hackage->guix-package name-version
#:include-test-dependencies?
include-test-dependencies?)
- (leave-with-message "~a: Stackage package not found" package-name))))))
+ (raise (formatted-message (G_ "~a: Stackage package not found")
+ package-name)))))))
(define (stackage-recursive-import package-name . args)
(recursive-import package-name
@@ -121,31 +135,46 @@ included in the Stackage LTS release."
;;;
(define latest-lts-release
- (let ((pkgs-info
- (mlambda () (lts-info-packages
- (stackage-lts-info-fetch %default-lts-version)))))
- (lambda* (package)
+ (let ((packages
+ (mlambda ()
+ (stackage-lts-packages
+ (stackage-lts-info-fetch %default-lts-version)))))
+ (lambda* (pkg)
"Return an <upstream-source> for the latest Stackage LTS release of
PACKAGE or #f if the package is not included in the Stackage LTS release."
- (let* ((hackage-name (guix-package->hackage-name package))
- (version (lts-package-version (pkgs-info) hackage-name))
+ (let* ((hackage-name (guix-package->hackage-name pkg))
+ (version (lts-package-version (packages) hackage-name))
(name-version (hackage-name-version hackage-name version)))
(match (and=> name-version hackage-fetch)
- (#f (format (current-error-port)
- "warning: failed to parse ~a~%"
- (hackage-cabal-url hackage-name))
- #f)
+ (#f
+ (warning (G_ "failed to parse ~a~%")
+ (hackage-cabal-url hackage-name))
+ #f)
(_ (let ((url (hackage-source-url hackage-name version)))
(upstream-source
- (package (package-name package))
+ (package (package-name pkg))
(version version)
- (urls (list url))))))))))
+ (urls (list url))
+ (input-changes
+ (changed-inputs
+ pkg
+ (stackage->guix-package hackage-name #:packages (packages))))))))))))
+
+(define (stackage-lts-package? package)
+ "Return whether PACKAGE is available on the default Stackage LTS release."
+ (and (hackage-package? package)
+ (let ((packages (stackage-lts-packages
+ (stackage-lts-info-fetch %default-lts-version)))
+ (hackage-name (guix-package->hackage-name package)))
+ (find (lambda (package)
+ (string=? (stackage-package-name package) hackage-name))
+ packages))))
(define %stackage-updater
(upstream-updater
(name 'stackage)
(description "Updater for Stackage LTS packages")
- (pred hackage-package?)
+ (pred stackage-lts-package?)
(latest latest-lts-release)))
;;; stackage.scm ends here
diff --git a/guix/import/texlive.scm b/guix/import/texlive.scm
index 18d8b95ee0..bdef9f58b0 100644
--- a/guix/import/texlive.scm
+++ b/guix/import/texlive.scm
@@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2017 Ricardo Wurmus <rekado@elephly.net>
+;;; Copyright © 2017, 2021 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;;
;;; This file is part of GNU Guix.
@@ -19,18 +19,16 @@
(define-module (guix import texlive)
#:use-module (ice-9 match)
- #:use-module (sxml simple)
- #:use-module (sxml xpath)
- #:use-module (srfi srfi-11)
+ #:use-module (ice-9 rdelim)
#:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-2)
+ #:use-module (srfi srfi-11)
#:use-module (srfi srfi-26)
- #:use-module (srfi srfi-34)
- #:use-module (web uri)
- #:use-module (guix diagnostics)
- #:use-module (guix i18n)
- #:use-module (guix http-client)
#:use-module (gcrypt hash)
+ #:use-module (guix derivations)
#:use-module (guix memoization)
+ #:use-module (guix monads)
+ #:use-module (guix gexp)
#:use-module (guix store)
#:use-module (guix base32)
#:use-module (guix serialization)
@@ -39,24 +37,15 @@
#:use-module (guix utils)
#:use-module (guix upstream)
#:use-module (guix packages)
- #:use-module (gnu packages)
#:use-module (guix build-system texlive)
#:export (texlive->guix-package
-
- fetch-sxml
- sxml->package))
+ texlive-recursive-import))
;;; Commentary:
;;;
-;;; Generate a package declaration template for the latest version of a
-;;; package on CTAN, using the XML output produced by the XML API to the CTAN
-;;; database at http://www.ctan.org/xml/1.2/
-;;;
-;;; Instead of taking the packages from CTAN, however, we fetch the sources
-;;; from the SVN repository of the Texlive project. We do this because CTAN
-;;; only keeps a single version of each package whereas we can access any
-;;; version via SVN. Unfortunately, this means that the importer is really
-;;; just a Texlive importer, not a generic CTAN importer.
+;;; Generate a package declaration template for corresponding package in the
+;;; Tex Live Package Database (tlpdb). We fetch all sources from different
+;;; locations in the SVN repository of the Texlive project.
;;;
;;; Code:
@@ -79,6 +68,8 @@
("bsd4" 'bsd-4)
("opl" 'opl1.0+)
("ofl" 'silofl1.1)
+
+ ("lpplgpl" `(list lppl gpl1+))
("lppl" 'lppl)
("lppl1" 'lppl1.0+) ; usually means "or later"
("lppl1.2" 'lppl1.2+) ; usually means "or later"
@@ -107,91 +98,170 @@
("cc-by-nc-nd-4" 'non-free)
((x) (string->license x))
((lst ...) `(list ,@(map string->license lst)))
- (_ #f)))
-
-(define (fetch-sxml name)
- "Return an sxml representation of the package information contained in the
-XML description of the CTAN package or #f in case of failure."
- ;; This API always returns the latest release of the module.
- (let ((url (string-append "http://www.ctan.org/xml/1.2/pkg/" name)))
- (guard (c ((http-get-error? c)
- (format (current-error-port)
- "error: failed to retrieve package information \
-from ~s: ~a (~s)~%"
- (uri->string (http-get-error-uri c))
- (http-get-error-code c)
- (http-get-error-reason c))
- #f))
- (xml->sxml (http-fetch url)
- #:trim-whitespace? #t))))
-
-(define (guix-name component name)
+ (x `(error unknown-license ,x))))
+
+(define (guix-name name)
"Return a Guix package name for a given Texlive package NAME."
- (string-append "texlive-" component "-"
+ (string-append "texlive-"
(string-map (match-lambda
(#\_ #\-)
(#\. #\-)
(chr (char-downcase chr)))
name)))
-(define* (sxml->package sxml #:optional (component "latex"))
- "Return the `package' s-expression for a Texlive package from the SXML
-expression describing it."
- (define (sxml-value path)
- (match ((sxpath path) sxml)
- (() #f)
- ((val) val)))
+(define (tlpdb-file)
+ (define texlive-bin
+ ;; Resolve this variable lazily so that (gnu packages ...) does not end up
+ ;; in the closure of this module.
+ (module-ref (resolve-interface '(gnu packages tex))
+ 'texlive-bin))
+
(with-store store
- (let* ((id (sxml-value '(entry @ id *text*)))
- (synopsis (sxml-value '(entry caption *text*)))
- (version (or (sxml-value '(entry version @ number *text*))
- (sxml-value '(entry version @ date *text*))))
- (license (match ((sxpath '(entry license @ type *text*)) sxml)
- ((license) (string->license license))
- ((lst ...) (map string->license lst))))
- (home-page (string-append "http://www.ctan.org/pkg/" id))
- (ref (texlive-ref component id))
- (checkout (download-svn-to-store store ref)))
- (unless checkout
- (warning (G_ "Could not determine source location. \
-Please manually specify the source field.~%")))
- `(package
- (name ,(guix-name component id))
- (version ,version)
- (source ,(if checkout
- `(origin
- (method svn-fetch)
- (uri (texlive-ref ,component ,id))
- (sha256
- (base32
- ,(bytevector->nix-base32-string
- (let-values (((port get-hash) (open-sha256-port)))
- (write-file checkout port)
- (force-output port)
- (get-hash))))))
- #f))
- (build-system texlive-build-system)
- (arguments ,`(,'quote (#:tex-directory ,(string-join (list component id) "/"))))
- (home-page ,home-page)
- (synopsis ,synopsis)
- (description ,(string-trim-both
- (string-join
- (map string-trim-both
- (string-split
- (beautify-description
- (sxml->string (or (sxml-value '(entry description))
- '())))
- #\newline)))))
- (license ,(match license
- ((lst ...) `(list ,@lst))
- (license license)))))))
+ (run-with-store store
+ (mlet* %store-monad
+ ((drv (lower-object texlive-bin))
+ (built (built-derivations (list drv))))
+ (match (derivation->output-paths drv)
+ (((names . items) ...)
+ (return (string-append (first items)
+ "/share/tlpkg/texlive.tlpdb"))))))))
+
+(define tlpdb
+ (memoize
+ (lambda ()
+ (let ((file (tlpdb-file))
+ (fields
+ '((name . string)
+ (shortdesc . string)
+ (longdesc . string)
+ (catalogue-license . string)
+ (catalogue-ctan . string)
+ (srcfiles . list)
+ (runfiles . list)
+ (docfiles . list)
+ (depend . simple-list)))
+ (record
+ (lambda* (key value alist #:optional (type 'string))
+ (let ((new
+ (or (and=> (assoc-ref alist key)
+ (lambda (existing)
+ (cond
+ ((eq? type 'string)
+ (string-append existing " " value))
+ ((or (eq? type 'list) (eq? type 'simple-list))
+ (cons value existing)))))
+ (cond
+ ((eq? type 'string)
+ value)
+ ((or (eq? type 'list) (eq? type 'simple-list))
+ (list value))))))
+ (acons key new (alist-delete key alist))))))
+ (call-with-input-file file
+ (lambda (port)
+ (let loop ((all (list))
+ (current (list))
+ (last-property #false))
+ (let ((line (read-line port)))
+ (cond
+ ((eof-object? line) all)
+
+ ;; End of record.
+ ((string-null? line)
+ (loop (cons (cons (assoc-ref current 'name) current)
+ all)
+ (list) #false))
+
+ ;; Continuation of a list
+ ((and (zero? (string-index line #\space)) last-property)
+ ;; Erase optional second part of list values like
+ ;; "details=Readme" for files
+ (let ((plain-value (first
+ (string-split
+ (string-trim-both line) #\space))))
+ (loop all (record last-property
+ plain-value
+ current
+ 'list)
+ last-property)))
+ (else
+ (or (and-let* ((space (string-index line #\space))
+ (key (string->symbol (string-take line space)))
+ (value (string-drop line (1+ space)))
+ (field-type (assoc-ref fields key)))
+ ;; Erase second part of list keys like "size=29"
+ (cond
+ ((eq? field-type 'list)
+ (loop all current key))
+ (else
+ (loop all (record key value current field-type) key))))
+ (loop all current #false))))))))))))
+
+(define (files->directories files)
+ (map (cut string-join <> "/" 'suffix)
+ (delete-duplicates (map (lambda (file)
+ (drop-right (string-split file #\/) 1))
+ files)
+ equal?)))
+
+(define (tlpdb->package name package-database)
+ (and-let* ((data (assoc-ref package-database name))
+ (dirs (files->directories
+ (map (lambda (dir)
+ (string-drop dir (string-length "texmf-dist/")))
+ (append (or (assoc-ref data 'docfiles) (list))
+ (or (assoc-ref data 'runfiles) (list))
+ (or (assoc-ref data 'srcfiles) (list))))))
+ (name (guix-name name))
+ (version (number->string %texlive-revision))
+ (ref (svn-multi-reference
+ (url (string-append "svn://www.tug.org/texlive/tags/"
+ %texlive-tag "/Master/texmf-dist"))
+ (locations dirs)
+ (revision %texlive-revision)))
+ (source (with-store store
+ (download-multi-svn-to-store
+ store ref (string-append name "-svn-multi-checkout")))))
+ (values
+ `(package
+ (inherit (simple-texlive-package
+ ,name
+ (list ,@dirs)
+ (base32
+ ,(bytevector->nix-base32-string
+ (let-values (((port get-hash) (open-sha256-port)))
+ (write-file source port)
+ (force-output port)
+ (get-hash))))
+ ,@(if (assoc-ref data 'srcfiles) '() '(#:trivial? #true))))
+ ,@(or (and=> (assoc-ref data 'depend)
+ (lambda (inputs)
+ `((propagated-inputs
+ ,(map (lambda (tex-name)
+ (let ((name (guix-name tex-name)))
+ (list name (list 'unquote (string->symbol name)))))
+ inputs)))))
+ '())
+ ,@(or (and=> (assoc-ref data 'catalogue-ctan)
+ (lambda (url)
+ `((home-page ,(string-append "https://ctan.org" url)))))
+ '((home-page "https://www.tug.org/texlive/")))
+ (synopsis ,(assoc-ref data 'shortdesc))
+ (description ,(beautify-description
+ (assoc-ref data 'longdesc)))
+ (license ,(string->license
+ (assoc-ref data 'catalogue-license))))
+ (or (assoc-ref data 'depend) (list)))))
(define texlive->guix-package
(memoize
- (lambda* (package-name #:optional (component "latex"))
- "Fetch the metadata for PACKAGE-NAME from REPO and return the `package'
+ (lambda* (name #:key repo version (package-database tlpdb))
+ "Find the metadata for NAME in the tlpdb and return the `package'
s-expression corresponding to that package, or #f on failure."
- (and=> (fetch-sxml package-name)
- (cut sxml->package <> component)))))
+ (tlpdb->package name (package-database)))))
+
+(define (texlive-recursive-import name)
+ (recursive-import name
+ #:repo->guix-package texlive->guix-package
+ #:guix-name guix-name))
-;;; ctan.scm ends here
+;;; texlive.scm ends here
diff --git a/guix/import/utils.scm b/guix/import/utils.scm
index a180742ca3..1c3cfa3e0b 100644
--- a/guix/import/utils.scm
+++ b/guix/import/utils.scm
@@ -9,6 +9,7 @@
;;; Copyright © 2020 Martin Becze <mjbecze@riseup.net>
;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;; Copyright © 2021 Sarah Morgensen <iskarian@mgsn.dev>
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -39,6 +40,7 @@
#:use-module (guix store)
#:use-module (guix download)
#:use-module (guix sets)
+ #:use-module (guix ui)
#:use-module (gnu packages)
#:use-module (ice-9 match)
#:use-module (ice-9 rdelim)
@@ -152,6 +154,7 @@ of the string VERSION is replaced by the symbol 'version."
("CC0-1.0" 'license:cc0)
("CC-BY-2.0" 'license:cc-by2.0)
("CC-BY-3.0" 'license:cc-by3.0)
+ ("CC-BY-4.0" 'license:cc-by4.0)
("CC-BY-SA-2.0" 'license:cc-by-sa2.0)
("CC-BY-SA-3.0" 'license:cc-by-sa3.0)
("CC-BY-SA-4.0" 'license:cc-by-sa4.0)
@@ -163,6 +166,7 @@ of the string VERSION is replaced by the symbol 'version."
("EPL-1.0" 'license:epl1.0)
("MIT" 'license:expat)
("FTL" 'license:freetype)
+ ("Freetype" 'license:freetype)
("GFDL-1.1" 'license:fdl1.1+)
("GFDL-1.2" 'license:fdl1.2+)
("GFDL-1.3" 'license:fdl1.3+)
@@ -179,6 +183,7 @@ of the string VERSION is replaced by the symbol 'version."
("GPL-3.0-only" 'license:gpl3)
("GPL-3.0+" 'license:gpl3+)
("GPL-3.0-or-later" 'license:gpl3+)
+ ("HPND" 'license:hpnd)
("ISC" 'license:isc)
("IJG" 'license:ijg)
("Imlib2" 'license:imlib2)
@@ -231,9 +236,10 @@ to in the (guix licenses) module, or #f if there is no such known license."
with dashes."
(string-join (string-split (string-downcase str) #\_) "-"))
-(define (beautify-description description)
- "Improve the package DESCRIPTION by turning a beginning sentence fragment
-into a proper sentence and by using two spaces between sentences."
+(define* (beautify-description description #:optional (length 80))
+ "Improve the package DESCRIPTION by turning a beginning sentence fragment into
+a proper sentence and by using two spaces between sentences, and wrap lines at
+LENGTH characters."
(let ((cleaned (cond
((string-prefix? "A " description)
(string-append "This package provides a"
@@ -248,8 +254,9 @@ into a proper sentence and by using two spaces between sentences."
(string-length "Functions"))))
(else description))))
;; Use double spacing between sentences
- (regexp-substitute/global #f "\\. \\b"
- cleaned 'pre ". " 'post)))
+ (fill-paragraph (regexp-substitute/global #f "\\. \\b"
+ cleaned 'pre ". " 'post)
+ length)))
(define* (package-names->package-inputs names #:optional (output #f))
"Given a list of PACKAGE-NAMES or (PACKAGE-NAME VERSION) pairs, and an
diff --git a/guix/inferior.scm b/guix/inferior.scm
index 81958baaa5..febac29766 100644
--- a/guix/inferior.scm
+++ b/guix/inferior.scm
@@ -785,6 +785,9 @@ determines whether CHANNELS are authenticated."
(define add-indirect-root*
(store-lift add-indirect-root))
+ (define add-temp-root*
+ (store-lift add-temp-root))
+
(mkdir-p cache-directory)
(maybe-remove-expired-cache-entries cache-directory
cache-entries
@@ -805,11 +808,15 @@ determines whether CHANNELS are authenticated."
;; what's going to be built.
(built-derivations (list profile))
- ;; Note: Caching is fine even when AUTHENTICATE? is false because
- ;; we always call 'latest-channel-instances?'.
- (symlink* (derivation->output-path profile) cached)
- (add-indirect-root* cached)
- (return cached))))))
+ ;; Cache if and only if AUTHENTICATE? is true.
+ (if authenticate?
+ (mbegin %store-monad
+ (symlink* (derivation->output-path profile) cached)
+ (add-indirect-root* cached)
+ (return cached))
+ (mbegin %store-monad
+ (add-temp-root* profile)
+ (return profile))))))))
(define* (inferior-for-channels channels
#:key
diff --git a/guix/licenses.scm b/guix/licenses.scm
index c071aae4a9..82ca44f42e 100644
--- a/guix/licenses.scm
+++ b/guix/licenses.scm
@@ -308,6 +308,8 @@ at URI, which may be a file:// URI pointing the package's tree."
"https://directory.fsf.org/wiki/License:EUPL-1.2"
"https://www.gnu.org/licenses/license-list#EUPL-1.2"))
+;; Some people call it the MIT license. For clarification see:
+;; https://www.gnu.org/licenses/license-list.html#Expat
(define expat
(license "Expat"
"http://directory.fsf.org/wiki/License:Expat"
diff --git a/guix/lint.scm b/guix/lint.scm
index 2a703f9b6d..74b9a304d9 100644
--- a/guix/lint.scm
+++ b/guix/lint.scm
@@ -322,6 +322,21 @@ markup is valid return a plain-text version of DESCRIPTION, otherwise #f."
(G_ "Texinfo markup in description is invalid")
#:field 'description))))
+ (define (check-description-typo description typo-corrections)
+ "Check that DESCRIPTION does not contain typo, with optional correction"
+ (append-map
+ (match-lambda
+ ((typo . correction)
+ (if (string-contains description typo)
+ (list
+ (make-warning package
+ (G_
+ (format #false
+ "description contains typo '~a'~@[, should be '~a'~]"
+ typo correction))))
+ '())))
+ typo-corrections))
+
(define (check-trademarks description)
"Check that DESCRIPTION does not contain '™' or '®' characters. See
http://www.gnu.org/prep/standards/html_node/Trademarks.html."
@@ -402,6 +417,10 @@ by two spaces; possible infraction~p at ~{~a~^, ~}")
(check-not-empty description)
(check-quotes description)
(check-trademarks description)
+ (check-description-typo description '(("This packages" . "This package")
+ ("This modules" . "This module")
+ ("allows to" . #f)
+ ("permits to" . #f)))
;; Use raw description for this because Texinfo rendering
;; automatically fixes end of sentence space.
(check-end-of-sentence-space description)
@@ -459,6 +478,7 @@ of a package, and INPUT-NAMES, a list of package specifications such as
"help2man"
"intltool"
"itstool"
+ "kdoctools"
"libtool"
"m4"
"qttools"
@@ -967,8 +987,12 @@ patch could not be found."
;; Check whether we're reaching tar's maximum file name length.
(let ((prefix (string-length (%distro-directory)))
- (margin (string-length "guix-2.0.0rc3-10000-1234567890/"))
- (max 99))
+ ;; Margin approximating the largest path that "make dist" might
+ ;; create, with a release candidate version, 123456 commits, and
+ ;; git commit hash abcde0.
+ (margin (string-length "guix-92.0.0rc3-123456-abcde0/"))
+ ;; Tested maximum patch file length for ustar format.
+ (max 151))
(filter-map (match-lambda
((? string? patch)
(if (> (+ margin (if (string-prefix? (%distro-directory)
@@ -978,7 +1002,7 @@ patch could not be found."
max)
(make-warning
package
- (G_ "~a: file name is too long")
+ (G_ "~a: file name is too long, which may break 'make dist'")
(list (basename patch))
#:field 'patch-file-names)
#f))
@@ -1585,7 +1609,7 @@ Heritage and missing from the Disarchive database")
(#f '())
(id
(list (make-warning package
- (G_ "
+ (G_ "\
Disarchive entry refers to non-existent SWH directory '~a'")
(list id)
#:field 'source)))))))
diff --git a/guix/narinfo.scm b/guix/narinfo.scm
index 72e0f75fda..4fc550aa6c 100644
--- a/guix/narinfo.scm
+++ b/guix/narinfo.scm
@@ -144,7 +144,9 @@ must contain the original contents of a narinfo file."
(map (lambda (url)
(or (string->uri url)
(string->uri
- (string-append cache-url "/" url))))
+ (if (string-suffix? "/" cache-url)
+ (string-append cache-url url)
+ (string-append cache-url "/" url)))))
urls)
compressions
(match file-sizes
diff --git a/guix/packages.scm b/guix/packages.scm
index 863c12d528..b00fa2f702 100644
--- a/guix/packages.scm
+++ b/guix/packages.scm
@@ -1,6 +1,6 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
-;;; Copyright © 2014, 2015, 2017, 2018 Mark H Weaver <mhw@netris.org>
+;;; Copyright © 2014, 2015, 2017, 2018, 2019 Mark H Weaver <mhw@netris.org>
;;; Copyright © 2015 Eric Bavier <bavier@member.fsf.org>
;;; Copyright © 2016 Alex Kost <alezost@gmail.com>
;;; Copyright © 2017, 2019, 2020 Efraim Flashner <efraim@flashner.co.il>
@@ -52,6 +52,7 @@
#:use-module (srfi srfi-35)
#:use-module (rnrs bytevectors)
#:use-module (web uri)
+ #:autoload (texinfo) (texi-fragment->stexi)
#:re-export (%current-system
%current-target-system
search-path-specification) ;for convenience
@@ -169,6 +170,7 @@
bag-transitive-host-inputs
bag-transitive-build-inputs
bag-transitive-target-inputs
+ package-development-inputs
package-closure
default-guile
@@ -363,6 +365,27 @@ name of its URI."
;; git, svn, cvs, etc. reference
#f))))
+;; Work around limitations in the 'snippet' mechanism. It is not possible for
+;; a 'snippet' to produce a tarball with a different base name than the
+;; original downloaded source. Moreover, cherry picking dozens of upsteam
+;; patches and applying them suddenly is often impractical; especially when a
+;; comprehensive code reformatting is done upstream. Mainly designed for
+;; Linux and IceCat packages.
+;; XXXX: do not make part of public API (export) such radical capability
+;; before a detailed review process.
+(define* (computed-origin-method gexp-promise hash-algo hash
+ #:optional (name "source")
+ #:key (system (%current-system))
+ (guile (default-guile)))
+ "Return a derivation that executes the G-expression that results
+from forcing GEXP-PROMISE."
+ (mlet %store-monad ((guile (package->derivation guile system)))
+ (gexp->derivation (or name "computed-origin")
+ (force gexp-promise)
+ #:graft? #f ;nothing to graft
+ #:system system
+ #:guile-for-build guile)))
+
(define %supported-systems
;; This is the list of system types that are supported. By default, we
@@ -444,6 +467,49 @@ lexical scope of its body."
(lambda (s) #,location)))
body ...))))))
+(define-syntax validate-texinfo
+ (let ((validate? (getenv "GUIX_UNINSTALLED")))
+ (define ensure-thread-safe-texinfo-parser!
+ ;; Work around <https://issues.guix.gnu.org/51264> for Guile <= 3.0.7.
+ (let ((patched? (or (> (string->number (major-version)) 3)
+ (> (string->number (minor-version)) 0)
+ (> (string->number (micro-version)) 7)))
+ (next-token-of/thread-safe
+ (lambda (pred port)
+ (let loop ((chars '()))
+ (match (read-char port)
+ ((? eof-object?)
+ (list->string (reverse! chars)))
+ (chr
+ (let ((chr* (pred chr)))
+ (if chr*
+ (loop (cons chr* chars))
+ (begin
+ (unread-char chr port)
+ (list->string (reverse! chars)))))))))))
+ (lambda ()
+ (unless patched?
+ (set! (@@ (texinfo) next-token-of) next-token-of/thread-safe)
+ (set! patched? #t)))))
+
+ (lambda (s)
+ "Raise a syntax error when passed a literal string that is not valid
+Texinfo. Otherwise, return the string."
+ (syntax-case s ()
+ ((_ str)
+ (string? (syntax->datum #'str))
+ (if validate?
+ (catch 'parser-error
+ (lambda ()
+ (ensure-thread-safe-texinfo-parser!)
+ (texi-fragment->stexi (syntax->datum #'str))
+ #'str)
+ (lambda _
+ (syntax-violation 'package "invalid Texinfo markup" #'str)))
+ #'str))
+ ((_ obj)
+ #'obj)))))
+
;; A package.
(define-record-type* <package>
package make-package
@@ -481,9 +547,11 @@ lexical scope of its body."
(replacement package-replacement ; package | #f
(default #f) (thunked) (innate))
- (synopsis package-synopsis) ; one-line description
- (description package-description) ; one or two paragraphs
- (license package-license)
+ (synopsis package-synopsis
+ (sanitize validate-texinfo)) ; one-line description
+ (description package-description
+ (sanitize validate-texinfo)) ; one or two paragraphs
+ (license package-license) ; (list of) <license>
(home-page package-home-page)
(supported-systems package-supported-systems ; list of strings
(default %supported-systems))
@@ -913,10 +981,10 @@ specifies modules in scope when evaluating SNIPPET."
((file-is-directory? #+source)
(copy-recursively directory #$output
#:log (%make-void-port "w")))
- ((not #+comp)
- (copy-file file #$output))
- (else
- (repack directory #$output)))))))
+ ((or #+comp (tarball? #+source))
+ (repack directory #$output))
+ (else ;single uncompressed file
+ (copy-file file #$output)))))))
(let ((name (if (or (checkout? original-file-name)
(not (compressor original-file-name)))
@@ -1015,13 +1083,6 @@ otherwise."
otherwise."
(lookup-input (package-direct-inputs package) name))
-(define (inputs-sans-labels inputs)
- "Return INPUTS stripped of any input labels."
- (map (match-lambda
- ((label obj) obj)
- ((label obj output) `(,obj ,output)))
- inputs))
-
(define (replace-input name replacement inputs)
"Replace input NAME by REPLACEMENT within INPUTS."
(map (lambda (input)
@@ -1056,7 +1117,10 @@ inputs of Coreutils and adds libcap:
(delete \"gmp\" \"acl\")
(append libcap))
-Other types of clauses include 'prepend' and 'replace'."
+Other types of clauses include 'prepend' and 'replace'.
+
+The first argument must be a labeled input list; the result is also a labeled
+input list."
;; Note: This macro hides the fact that INPUTS, as returned by
;; 'package-inputs' & co., is actually an alist with labels. Eventually,
;; it will operate on list of inputs without labels.
@@ -1067,10 +1131,10 @@ Other types of clauses include 'prepend' and 'replace'."
(modify-inputs (fold alist-delete inputs (list names ...))
clauses ...))
((_ inputs (prepend lst ...) clauses ...)
- (modify-inputs (append (list lst ...) (inputs-sans-labels inputs))
+ (modify-inputs (append (map add-input-label (list lst ...)) inputs)
clauses ...))
((_ inputs (append lst ...) clauses ...)
- (modify-inputs (append (inputs-sans-labels inputs) (list lst ...))
+ (modify-inputs (append inputs (map add-input-label (list lst ...)))
clauses ...))
((_ inputs (replace name replacement) clauses ...)
(modify-inputs (replace-input name replacement inputs)
@@ -1155,23 +1219,36 @@ in INPUTS and their transitive propagated inputs."
(define package-transitive-supported-systems
(let ()
- (define supported-systems
- (mlambda (package system)
- (parameterize ((%current-system system))
- (fold (lambda (input systems)
- (match input
- ((label (? package? package) . _)
- (lset-intersection string=? systems
- (supported-systems package system)))
- (_
- systems)))
- (package-supported-systems package)
- (bag-direct-inputs (package->bag package))))))
+ (define (supported-systems-procedure system)
+ (define supported-systems
+ (mlambdaq (package)
+ (parameterize ((%current-system system))
+ (fold (lambda (input systems)
+ (match input
+ ((label (? package? package) . _)
+ (lset-intersection string=? systems
+ (supported-systems package)))
+ (_
+ systems)))
+ (package-supported-systems package)
+ (bag-direct-inputs (package->bag package))))))
+
+ supported-systems)
+
+ (define procs
+ ;; Map system strings to one-argument procedures. This allows these
+ ;; procedures to have fast 'eq?' memoization on their argument.
+ (make-hash-table))
(lambda* (package #:optional (system (%current-system)))
"Return the intersection of the systems supported by PACKAGE and those
supported by its dependencies."
- (supported-systems package system))))
+ (match (hash-ref procs system)
+ (#f
+ (hash-set! procs system (supported-systems-procedure system))
+ (package-transitive-supported-systems package system))
+ (proc
+ (proc package))))))
(define* (supported-package? package #:optional (system (%current-system)))
"Return true if PACKAGE is supported on SYSTEM--i.e., if PACKAGE and all its
@@ -1208,6 +1285,15 @@ dependencies are known to build on SYSTEM."
(%current-system (bag-system bag)))
(transitive-inputs (bag-target-inputs bag))))
+(define* (package-development-inputs package
+ #:optional (system (%current-system))
+ #:key target)
+ "Return the list of inputs required by PACKAGE for development purposes on
+SYSTEM. When TARGET is true, return the inputs needed to cross-compile
+PACKAGE from SYSTEM to TRIPLET, where TRIPLET is a triplet such as
+\"aarch64-linux-gnu\"."
+ (bag-transitive-inputs (package->bag package system target)))
+
(define* (package-closure packages #:key (system (%current-system)))
"Return the closure of PACKAGES on SYSTEM--i.e., PACKAGES and the list of
packages they depend on, recursively."
@@ -1770,7 +1856,7 @@ This is an internal procedure."
(return drv))
(grafts
(mlet %store-monad ((guile (package->derivation
- (default-guile)
+ (guile-for-grafts)
system #:graft? #f)))
(graft-derivation* drv grafts
#:system system
@@ -1793,7 +1879,7 @@ system identifying string)."
(return drv))
(grafts
(mlet %store-monad ((guile (package->derivation
- (default-guile)
+ (guile-for-grafts)
system #:graft? #f)))
(graft-derivation* drv grafts
#:system system
diff --git a/guix/profiles.scm b/guix/profiles.scm
index 9494684228..1d354ecb78 100644
--- a/guix/profiles.scm
+++ b/guix/profiles.scm
@@ -7,7 +7,7 @@
;;; Copyright © 2016, 2018, 2019, 2021 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2016 Chris Marusich <cmmarusich@gmail.com>
;;; Copyright © 2017 Huang Ying <huang.ying.caritas@gmail.com>
-;;; Copyright © 2017 Maxim Cournoyer <maxim.cournoyer@gmail.com>
+;;; Copyright © 2017, 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;; Copyright © 2019 Kyle Meyer <kyle@kyleam.com>
;;; Copyright © 2019 Mathieu Othacehe <m.othacehe@gmail.com>
;;; Copyright © 2020 Danny Milosavljevic <dannym@scratchpost.org>
@@ -124,6 +124,7 @@
profile-manifest
package->manifest-entry
+ package->development-manifest
packages->manifest
ca-certificate-bundle
%default-profile-hooks
@@ -400,6 +401,24 @@ file name."
(properties properties))))
entry))
+(define* (package->development-manifest package
+ #:optional
+ (system (%current-system))
+ #:key target)
+ "Return a manifest for the \"development inputs\" of PACKAGE for SYSTEM,
+optionally when cross-compiling to TARGET. Development inputs include both
+explicit and implicit inputs of PACKAGE."
+ (manifest
+ (filter-map (match-lambda
+ ((label (? package? package))
+ (package->manifest-entry package))
+ ((label (? package? package) output)
+ (package->manifest-entry package output))
+ ;; TODO: Support <inferior-package>.
+ (_
+ #f))
+ (package-development-inputs package system #:target target))))
+
(define (packages->manifest packages)
"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
@@ -1161,6 +1180,52 @@ MANIFEST. Single-file bundles are required by programs such as Git and Lynx."
`((type . profile-hook)
(hook . emacs-subdirs))))
+(define (gdk-pixbuf-loaders-cache-file manifest)
+ "Return a derivation that produces a loaders cache file for every gdk-pixbuf
+loaders discovered in MANIFEST."
+ (define gdk-pixbuf ;lazy reference
+ (module-ref (resolve-interface '(gnu packages gtk)) 'gdk-pixbuf))
+
+ (mlet* %store-monad
+ ((gdk-pixbuf (manifest-lookup-package manifest "gdk-pixbuf"))
+ (librsvg (manifest-lookup-package manifest "librsvg"))
+ (gdk-pixbuf-bin -> (if (string? gdk-pixbuf)
+ (string-append gdk-pixbuf "/bin")
+ (file-append gdk-pixbuf "/bin"))))
+
+ (define build
+ (with-imported-modules (source-module-closure
+ '((guix build glib-or-gtk-build-system)))
+ #~(begin
+ (use-modules (guix build glib-or-gtk-build-system))
+ (setenv "PATH" (string-append #$gdk-pixbuf-bin ":" (getenv "PATH")))
+
+ (generate-gdk-pixbuf-loaders-cache
+ ;; XXX: MANIFEST-LOOKUP-PACKAGE transitively searches through
+ ;; every input referenced by the manifest, while MANIFEST-INPUTS
+ ;; only retrieves the immediate inputs as well as their
+ ;; propagated inputs; to avoid causing an empty output derivation
+ ;; we must ensure that the inputs contain at least one
+ ;; loaders.cache file. This is why we include gdk-pixbuf or
+ ;; librsvg when they are transitively found.
+ (list #$@(if gdk-pixbuf
+ (list gdk-pixbuf)
+ '())
+ #$@(if librsvg
+ (list librsvg)
+ '())
+ #$@(manifest-inputs manifest))
+ (list #$output)))))
+
+ (if gdk-pixbuf
+ (gexp->derivation "gdk-pixbuf-loaders-cache-file" build
+ #:local-build? #t
+ #:substitutable? #f
+ #:properties
+ '((type . profile-hook)
+ (hook . gdk-pixbuf-loaders-cache-file)))
+ (return #f))))
+
(define (glib-schemas manifest)
"Return a derivation that unions all schemas from manifest entries and
creates the Glib 'gschemas.compiled' file."
@@ -1663,15 +1728,124 @@ the entries in MANIFEST."
`((type . profile-hook)
(hook . manual-database))))
+(define (manual-database/optional manifest)
+ "Return a derivation to build the manual database of MANIFEST, but only if
+MANIFEST contains the \"man-db\" package. Otherwise, return #f."
+ ;; Building the man database (for "man -k") is expensive and rarely used.
+ ;; Build it only if the profile also contains "man-db".
+ (mlet %store-monad ((man-db (manifest-lookup-package manifest "man-db")))
+ (if man-db
+ (manual-database manifest)
+ (return #f))))
+
+(define (texlive-configuration manifest)
+ "Return a derivation that builds a TeXlive configuration for the entries in
+MANIFEST."
+ (define entry->texlive-input
+ (match-lambda
+ (($ <manifest-entry> name version output thing deps)
+ (if (string-prefix? "texlive-" name)
+ (cons (gexp-input thing output)
+ (append-map entry->texlive-input deps))
+ '()))))
+ (define texlive-bin
+ (module-ref (resolve-interface '(gnu packages tex)) 'texlive-bin))
+ (define coreutils
+ (module-ref (resolve-interface '(gnu packages base)) 'coreutils))
+ (define sed
+ (module-ref (resolve-interface '(gnu packages base)) 'sed))
+ (define updmap.cfg
+ (module-ref (resolve-interface '(gnu packages tex))
+ 'texlive-default-updmap.cfg))
+ (define build
+ (with-imported-modules '((guix build utils)
+ (guix build union))
+ #~(begin
+ (use-modules (guix build utils)
+ (guix build union)
+ (ice-9 popen))
+
+ ;; Build a modifiable union of all texlive inputs. We do this so
+ ;; that TeX live can resolve the parent and grandparent directories
+ ;; correctly. There might be a more elegant way to accomplish this.
+ (union-build #$output
+ '#$(append-map entry->texlive-input
+ (manifest-entries manifest))
+ #:create-all-directories? #t
+ #:log-port (%make-void-port "w"))
+ (let ((texmf.cnf (string-append
+ #$output
+ "/share/texmf-dist/web2c/texmf.cnf")))
+ (when (file-exists? texmf.cnf)
+ (substitute* texmf.cnf
+ (("^TEXMFROOT = .*")
+ (string-append "TEXMFROOT = " #$output "/share\n"))
+ (("^TEXMF = .*")
+ "TEXMF = $TEXMFROOT/share/texmf-dist\n"))
+
+ ;; XXX: This is annoying, but it's necessary because texlive-bin
+ ;; does not provide wrapped executables.
+ (setenv "PATH"
+ (string-append #$(file-append coreutils "/bin")
+ ":"
+ #$(file-append sed "/bin")))
+ (setenv "PERL5LIB" #$(file-append texlive-bin "/share/tlpkg"))
+ (setenv "TEXMF" (string-append #$output "/share/texmf-dist"))
+
+ ;; Remove invalid maps from config file.
+ (let* ((web2c (string-append #$output "/share/texmf-config/web2c/"))
+ (maproot (string-append #$output "/share/texmf-dist/fonts/map/"))
+ (updmap.cfg (string-append web2c "updmap.cfg")))
+ (mkdir-p web2c)
+
+ ;; Some profiles may already have this file, which prevents us
+ ;; from copying it. Since we need to generate it from scratch
+ ;; anyway, we delete it here.
+ (when (file-exists? updmap.cfg)
+ (delete-file updmap.cfg))
+ (copy-file #$updmap.cfg updmap.cfg)
+ (make-file-writable updmap.cfg)
+ (let* ((port (open-pipe* OPEN_WRITE
+ #$(file-append texlive-bin "/bin/updmap-sys")
+ "--syncwithtrees"
+ "--nohash"
+ "--force"
+ (string-append "--cnffile=" web2c "updmap.cfg"))))
+ (display "Y\n" port)
+ (when (not (zero? (status:exit-val (close-pipe port))))
+ (error "failed to filter updmap.cfg")))
+
+ ;; Generate font maps.
+ (invoke #$(file-append texlive-bin "/bin/updmap-sys")
+ (string-append "--cnffile=" web2c "updmap.cfg")
+ (string-append "--dvipdfmxoutputdir="
+ maproot "updmap/dvipdfmx/")
+ (string-append "--dvipsoutputdir="
+ maproot "updmap/dvips/")
+ (string-append "--pdftexoutputdir="
+ maproot "updmap/pdftex/")))))
+ #t)))
+
+ (mlet %store-monad ((texlive-base (manifest-lookup-package manifest "texlive-base")))
+ (if texlive-base
+ (gexp->derivation "texlive-configuration" build
+ #:substitutable? #f
+ #:local-build? #t
+ #:properties
+ `((type . profile-hook)
+ (hook . texlive-configuration)))
+ (return #f))))
+
(define %default-profile-hooks
;; This is the list of derivation-returning procedures that are called by
;; default when making a non-empty profile.
(list info-dir-file
- manual-database
+ manual-database/optional
fonts-dir-file
ghc-package-cache-file
ca-certificate-bundle
emacs-subdirs
+ gdk-pixbuf-loaders-cache-file
glib-schemas
gtk-icon-themes
gtk-im-modules
diff --git a/guix/progress.scm b/guix/progress.scm
index 0cbc804ec1..4f8e98edc0 100644
--- a/guix/progress.scm
+++ b/guix/progress.scm
@@ -270,19 +270,25 @@ ABBREVIATION used to shorten FILE for display."
tasks is performed. Write PREFIX at the beginning of the line."
(define done 0)
+ (define (draw-bar)
+ (let* ((ratio (* 100. (/ done total))))
+ (erase-current-line port)
+ (if (string-null? prefix)
+ (display (progress-bar ratio (current-terminal-columns)) port)
+ (let ((width (- (current-terminal-columns)
+ (string-length prefix) 3)))
+ (display prefix port)
+ (display " " port)
+ (display (progress-bar ratio width) port)))
+ (force-output port)))
+
+ (define draw-bar/rate-limited
+ (rate-limited draw-bar %progress-interval))
+
(define (report-progress)
(set! done (+ 1 done))
(unless (> done total)
- (let* ((ratio (* 100. (/ done total))))
- (erase-current-line port)
- (if (string-null? prefix)
- (display (progress-bar ratio (current-terminal-columns)) port)
- (let ((width (- (current-terminal-columns)
- (string-length prefix) 3)))
- (display prefix port)
- (display " " port)
- (display (progress-bar ratio width) port)))
- (force-output port))))
+ (draw-bar/rate-limited)))
(progress-reporter
(start (lambda ()
diff --git a/guix/scripts/challenge.scm b/guix/scripts/challenge.scm
index 69c2781abb..c29d5105ae 100644
--- a/guix/scripts/challenge.scm
+++ b/guix/scripts/challenge.scm
@@ -35,10 +35,10 @@
#:use-module (gcrypt hash)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
- #:use-module (srfi srfi-11)
#:use-module (srfi srfi-26)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-37)
+ #:use-module (srfi srfi-71)
#:use-module (ice-9 match)
#:use-module (ice-9 vlist)
#:use-module (ice-9 format)
@@ -196,65 +196,68 @@ taken since we do not import the archives."
(define (port-sha256* port size)
;; Like 'port-sha256', but limited to SIZE bytes.
- (let-values (((out get) (open-sha256-port)))
+ (let ((out get (open-sha256-port)))
(dump-port* port out size)
(close-port out)
(get)))
(define (archive-contents port)
- "Return a list representing the files contained in the nar read from PORT."
- (fold-archive (lambda (file type contents result)
- (match type
- ((or 'regular 'executable)
- (match contents
- ((port . size)
- (cons `(,file ,type ,(port-sha256* port size))
- result))))
- ('directory result)
- ('directory-complete result)
- ('symlink
- (cons `(,file ,type ,contents) result))))
- '()
- port
- ""))
+ "Return a list representing the files contained in the nar read from PORT.
+The list is sorted in canonical order--i.e., the order in which entries appear
+in the nar."
+ (reverse
+ (fold-archive (lambda (file type contents result)
+ (match type
+ ((or 'regular 'executable)
+ (match contents
+ ((port . size)
+ (cons `(,file ,type ,(port-sha256* port size))
+ result))))
+ ('directory result)
+ ('directory-complete result)
+ ('symlink
+ (cons `(,file ,type ,contents) result))))
+ '()
+ port
+ "")))
(define (store-item-contents item)
"Return a list of files and contents for ITEM in the same format as
'archive-contents'."
- (file-system-fold (const #t) ;enter?
- (lambda (file stat result) ;leaf
- (define short
- (string-drop file (string-length item)))
-
- (match (stat:type stat)
- ('regular
- (let ((size (stat:size stat))
- (type (if (zero? (logand (stat:mode stat)
- #o100))
- 'regular
- 'executable)))
- (cons `(,short ,type
- ,(call-with-input-file file
- (cut port-sha256* <> size)))
- result)))
- ('symlink
- (cons `(,short symlink ,(readlink file))
- result))))
- (lambda (directory stat result) result) ;down
- (lambda (directory stat result) result) ;up
- (lambda (file stat result) result) ;skip
- (lambda (file stat errno result) result) ;error
- '()
- item
- lstat))
+ (let loop ((file item))
+ (define stat
+ (lstat file))
+
+ (define short
+ (string-drop file (string-length item)))
+
+ (match (stat:type stat)
+ ('regular
+ (let ((size (stat:size stat))
+ (type (if (zero? (logand (stat:mode stat)
+ #o100))
+ 'regular
+ 'executable)))
+ `((,short ,type
+ ,(call-with-input-file file
+ (cut port-sha256* <> size))))))
+ ('symlink
+ `((,short symlink ,(readlink file))))
+ ('directory
+ (append-map (match-lambda
+ ((or "." "..")
+ '())
+ (entry
+ (loop (string-append file "/" entry))))
+ ;; Traverse entries in canonical order, the same as the
+ ;; order of entries in nars.
+ (scandir file (const #t) string<?))))))
(define (call-with-nar narinfo proc)
"Call PROC with an input port from which it can read the nar pointed to by
NARINFO."
- (let*-values (((uri compression size)
- (narinfo-best-uri narinfo))
- ((port actual-size)
- (http-fetch uri)))
+ (let* ((uri compression size (narinfo-best-uri narinfo))
+ (port actual-size (http-fetch uri)))
(define reporter
(progress-reporter/file (narinfo-path narinfo)
(and size
diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm
index 6958bd6238..510cee727f 100644
--- a/guix/scripts/environment.scm
+++ b/guix/scripts/environment.scm
@@ -34,23 +34,33 @@
#:use-module (guix scripts)
#:use-module (guix scripts build)
#:use-module (guix transformations)
- #:use-module (gnu build linux-container)
- #:use-module (gnu build accounts)
- #:use-module ((guix build syscalls) #:select (set-network-interface-up))
- #:use-module (gnu system linux-container)
+ #:autoload (ice-9 ftw) (scandir)
+ #:autoload (gnu build linux-container) (call-with-container %namespaces
+ user-namespace-supported?
+ unprivileged-user-namespace-supported?
+ setgroups-supported?)
+ #:autoload (gnu build accounts) (password-entry group-entry
+ password-entry-name password-entry-directory
+ write-passwd write-group)
+ #:autoload (guix build syscalls) (set-network-interface-up openpty login-tty)
#:use-module (gnu system file-systems)
- #:use-module (gnu packages)
- #:use-module (gnu packages bash)
- #:use-module ((gnu packages bootstrap)
- #:select (bootstrap-executable %bootstrap-guile))
+ #:autoload (gnu packages) (specification->package+output)
+ #:autoload (gnu packages bash) (bash)
+ #:autoload (gnu packages bootstrap) (bootstrap-executable %bootstrap-guile)
#:use-module (ice-9 match)
+ #:autoload (ice-9 rdelim) (read-line)
+ #:use-module (ice-9 vlist)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-11)
#:use-module (srfi srfi-26)
#:use-module (srfi srfi-37)
#:use-module (srfi srfi-98)
#:export (assert-container-features
- guix-environment))
+ guix-environment
+ guix-environment*
+ show-environment-options-help
+ (%options . %environment-options)
+ (%default-options . %environment-default-options)))
(define %default-shell
(or (getenv "SHELL") "/bin/sh"))
@@ -66,41 +76,18 @@ do not augment existing environment variables with additional search paths."
(newline)))
(profile-search-paths profile manifest)))
-(define (input->manifest-entry input)
- "Return a manifest entry for INPUT, or #f if INPUT does not correspond to a
-package."
- (match input
- ((_ (? package? package))
- (package->manifest-entry package))
- ((_ (? package? package) output)
- (package->manifest-entry package output))
- (_
- #f)))
-
-(define (package-environment-inputs package)
- "Return a list of manifest entries corresponding to the transitive input
-packages for PACKAGE."
- ;; Remove non-package inputs such as origin records.
- (filter-map input->manifest-entry
- (bag-transitive-inputs (package->bag package))))
-
-(define (show-help)
- (display (G_ "Usage: guix environment [OPTION]... PACKAGE... [-- COMMAND...]
-Build an environment that includes the dependencies of PACKAGE and execute
-COMMAND or an interactive shell in that environment.\n"))
+(define (show-environment-options-help)
+ "Print help about options shared between 'guix environment' and 'guix
+shell'."
(display (G_ "
-e, --expression=EXPR create environment for the package that EXPR
evaluates to"))
(display (G_ "
- -l, --load=FILE create environment for the package that the code within
- FILE evaluates to"))
- (display (G_ "
-m, --manifest=FILE create environment with the manifest from FILE"))
(display (G_ "
-p, --profile=PATH create environment from profile at PATH"))
(display (G_ "
- --ad-hoc include all specified packages in the environment instead
- of only their inputs"))
+ --check check if the shell clobbers environment variables"))
(display (G_ "
--pure unset existing environment variables"))
(display (G_ "
@@ -136,7 +123,24 @@ COMMAND or an interactive shell in that environment.\n"))
(display (G_ "
-v, --verbosity=LEVEL use the given verbosity LEVEL"))
(display (G_ "
- --bootstrap use bootstrap binaries to build the environment"))
+ --bootstrap use bootstrap binaries to build the environment")))
+
+(define (show-help)
+ (display (G_ "Usage: guix environment [OPTION]... PACKAGE... [-- COMMAND...]
+Build an environment that includes the dependencies of PACKAGE and execute
+COMMAND or an interactive shell in that environment.\n"))
+ (warning (G_ "This command is deprecated in favor of 'guix shell'.\n"))
+ (newline)
+
+ ;; These two options are left out in 'guix shell'.
+ (display (G_ "
+ -l, --load=FILE create environment for the package that the code within
+ FILE evaluates to"))
+ (display (G_ "
+ --ad-hoc include all specified packages in the environment instead
+ of only their inputs"))
+
+ (show-environment-options-help)
(newline)
(show-build-options-help)
(newline)
@@ -179,6 +183,9 @@ COMMAND or an interactive shell in that environment.\n"))
(option '(#\V "version") #f #f
(lambda args
(show-version-and-exit "guix environment")))
+ (option '("check") #f #f
+ (lambda (opt name arg result)
+ (alist-cons 'check? #t result)))
(option '("pure") #f #f
(lambda (opt name arg result)
(alist-cons 'pure #t result)))
@@ -297,11 +304,11 @@ for the corresponding packages."
((? package? package)
(if (eq? mode 'ad-hoc-package)
(list (package->manifest-entry* package))
- (package-environment-inputs package)))
+ (manifest-entries (package->development-manifest package))))
(((? package? package) (? string? output))
(if (eq? mode 'ad-hoc-package)
(list (package->manifest-entry* package output))
- (package-environment-inputs package)))
+ (manifest-entries (package->development-manifest package))))
((lst ...)
(append-map (cut packages->outputs <> mode) lst))))
@@ -313,8 +320,9 @@ for the corresponding packages."
(specification->package+output spec)))
(list (package->manifest-entry* package output))))
(('package 'package (? string? spec))
- (package-environment-inputs
- (transform (specification->package+output spec))))
+ (manifest-entries
+ (package->development-manifest
+ (transform (specification->package+output spec)))))
(('expression mode str)
;; Add all the outputs of the package STR evaluates to.
(packages->outputs (read/eval str) mode))
@@ -394,7 +402,193 @@ regexps in WHITE-LIST."
(match command
((program . args)
- (apply execlp program program args))))
+ (catch 'system-error
+ (lambda ()
+ (apply execlp program program args))
+ (lambda _
+ ;; Following established convention, exit with 127 upon ENOENT.
+ (primitive-_exit 127))))))
+
+(define (child-shell-environment shell profile manifest)
+ "Create a child process, load PROFILE and MANIFEST, and then run SHELL in
+interactive mode in it. Return a name/value vhash for all the variables shown
+by running 'set' in the shell."
+ (define-values (controller inferior)
+ (openpty))
+
+ (define script
+ ;; Script to obtain the list of environment variable values. On a POSIX
+ ;; shell we can rely on 'set', but on fish we have to use 'env' (fish's
+ ;; 'set' truncates values and prints them in a different format.)
+ "env || /usr/bin/env || set; echo GUIX-CHECK-DONE; read x; exit\n")
+
+ (define lines
+ (match (primitive-fork)
+ (0
+ (catch #t
+ (lambda ()
+ (load-profile profile manifest #:pure? #t)
+ (setenv "GUIX_ENVIRONMENT" profile)
+ (close-fdes controller)
+ (login-tty inferior)
+ (execl shell shell))
+ (lambda _
+ (primitive-exit 127))))
+ (pid
+ (close-fdes inferior)
+ (let* ((port (fdopen controller "r+l"))
+ (result (begin
+ (display script port)
+ (let loop ((lines '()))
+ (match (read-line port)
+ ((? eof-object?) (reverse lines))
+ ("GUIX-CHECK-DONE\r"
+ (display "done\n" port)
+ (reverse lines))
+ (line
+ ;; Drop the '\r' from LINE.
+ (loop (cons (string-drop-right line 1)
+ lines))))))))
+ (close-port port)
+ (waitpid pid)
+ result))))
+
+ (fold (lambda (line table)
+ ;; Note: 'set' in fish outputs "NAME VALUE" instead of "NAME=VALUE"
+ ;; but it also truncates values anyway, so don't try to support it.
+ (let ((index (string-index line #\=)))
+ (if index
+ (vhash-cons (string-take line index)
+ (string-drop line (+ 1 index))
+ table)
+ table)))
+ vlist-null
+ lines))
+
+(define* (validate-child-shell-environment profile manifest
+ #:optional (shell %default-shell))
+ "Run SHELL in interactive mode in an environment for PROFILE and MANIFEST
+and report clobbered environment variables."
+ (define warned? #f)
+ (define-syntax-rule (warn exp ...)
+ (begin
+ (set! warned? #t)
+ (warning exp ...)))
+
+ (info (G_ "checking the environment variables visible from shell '~a'...~%")
+ shell)
+ (let ((actual (child-shell-environment shell profile manifest)))
+ (when (vlist-null? actual)
+ (leave (G_ "failed to determine environment of shell '~a'~%")
+ shell))
+ (for-each (match-lambda
+ ((spec . expected)
+ (let ((name (search-path-specification-variable spec)))
+ (match (vhash-assoc name actual)
+ (#f
+ (warn (G_ "variable '~a' is missing from shell \
+environment~%")
+ name))
+ ((_ . actual)
+ (cond ((string=? expected actual)
+ #t)
+ ((string-prefix? expected actual)
+ (warn (G_ "variable '~a' has unexpected \
+suffix '~a'~%")
+ name
+ (string-drop actual
+ (string-length expected))))
+ (else
+ (warn (G_ "variable '~a' is clobbered: '~a'~%")
+ name actual))))))))
+ (profile-search-paths profile manifest))
+
+ ;; Special case.
+ (match (vhash-assoc "GUIX_ENVIRONMENT" actual)
+ (#f
+ (warn (G_ "'GUIX_ENVIRONMENT' is missing from the shell \
+environment~%")))
+ ((_ . value)
+ (unless (string=? value profile)
+ (warn (G_ "'GUIX_ENVIRONMENT' is set to '~a' instead of '~a'~%")
+ value profile))))
+
+ ;; Check the prompt unless we have more important warnings.
+ (unless warned?
+ (match (vhash-assoc "PS1" actual)
+ (#f #f)
+ ((_ . str)
+ (when (and (getenv "PS1") (string=? str (getenv "PS1")))
+ (warning (G_ "'PS1' is the same in sub-shell~%"))
+ (display-hint (G_ "Consider setting a different prompt for
+environment shells to make them distinguishable.
+
+If you are using Bash, you can do that by adding these lines to
+@file{~/.bashrc}:
+
+@example
+if [ -n \"$GUIX_ENVIRONMENT\" ]
+then
+ export PS1=\"\\u@@\\h \\w [env]\\$ \"
+fi
+@end example
+"))))))
+
+ (if warned?
+ (begin
+ (display-hint (G_ "One or more environment variables have a
+different value in the shell than the one we set. This means that you may
+find yourself running code in an environment different from the one you asked
+Guix to prepare.
+
+This usually indicates that your shell startup files are unexpectedly
+modifying those environment variables. For example, if you are using Bash,
+make sure that environment variables are set or modified in
+@file{~/.bash_profile} and @emph{not} in @file{~/.bashrc}. For more
+information on Bash startup files, run:
+
+@example
+info \"(bash) Bash Startup Files\"
+@end example
+
+Alternatively, you can avoid the problem by passing the @option{--container}
+or @option{-C} option. That will give you a fully isolated environment
+running in a \"container\", immune to the issue described above."))
+ (exit 1))
+ (info (G_ "All is good! The shell gets correct environment \
+variables.~%")))))
+
+(define (suggest-command-name profile command)
+ "COMMAND was not found in PROFILE so display a hint suggesting the closest
+command name."
+ (define not-dot?
+ (match-lambda
+ ((or "." "..") #f)
+ (_ #t)))
+
+ (match (scandir (string-append profile "/bin") not-dot?)
+ ((or #f ()) #f)
+ (available
+ (match command
+ ((executable _ ...)
+ ;; Look for a suggestion with a high threshold: a suggestion is
+ ;; usually better than no suggestion.
+ (let ((closest (string-closest executable available
+ #:threshold 12)))
+ (unless (or (not closest) (string=? closest executable))
+ (display-hint (format #f (G_ "Did you mean '~a'?~%")
+ closest)))))))))
+
+(define (validate-exit-status profile command status)
+ "When STATUS, an integer as returned by 'waitpid', is 127, raise a \"command
+not found\" error. Otherwise return STATUS."
+ ;; Most likely, exit value 127 means ENOENT.
+ (when (eqv? (status:exit-val status) 127)
+ (report-error (G_ "~a: command not found~%")
+ (first command))
+ (suggest-command-name profile command)
+ (exit 1))
+ status)
(define* (launch-environment/fork command profile manifest
#:key pure? (white-list '()))
@@ -407,7 +601,8 @@ regexps in WHITE-LIST."
#:pure? pure?
#:white-list white-list))
(pid (match (waitpid pid)
- ((_ . status) status)))))
+ ((_ . status)
+ (validate-exit-status profile command status))))))
(define* (launch-environment/container #:key command bash user user-mappings
profile manifest link-profile? network?
@@ -428,6 +623,9 @@ WHILE-LIST."
(and (file-exists? (file-system-mapping-source mapping))
(file-system-mapping->bind-mount mapping)))
+ (define (exit/status* status)
+ (exit/status (validate-exit-status profile command status)))
+
(mlet %store-monad ((reqs (inputs->requisites
(list (direct-store-path bash) profile))))
(return
@@ -484,7 +682,7 @@ WHILE-LIST."
'())
(map file-system-mapping->bind-mount
mappings))))
- (exit/status
+ (exit/status*
(call-with-container file-systems
(lambda ()
;; Setup global shell.
@@ -666,11 +864,15 @@ message if any test fails."
(define-command (guix-environment . args)
(category development)
- (synopsis "spawn one-off software environments")
+ (synopsis "spawn one-off software environments (deprecated)")
+
+ (guix-environment* (parse-args args)))
+(define (guix-environment* opts)
+ "Run the 'guix environment' command on OPTS, an alist resulting for
+command-line option processing with 'parse-command-line'."
(with-error-handling
- (let* ((opts (parse-args args))
- (pure? (assoc-ref opts 'pure))
+ (let* ((pure? (assoc-ref opts 'pure))
(container? (assoc-ref opts 'container?))
(link-prof? (assoc-ref opts 'link-profile?))
(network? (assoc-ref opts 'network?))
@@ -690,6 +892,26 @@ message if any test fails."
(mappings (pick-all opts 'file-system-mapping))
(white-list (pick-all opts 'inherit-regexp)))
+ (define store-needed?
+ ;; Whether connecting to the daemon is needed.
+ (or container? (not profile)))
+
+ (define-syntax-rule (with-store/maybe store exp ...)
+ ;; Evaluate EXP... with STORE bound to a connection, unless
+ ;; STORE-NEEDED? is false, in which case STORE is bound to #f.
+ (let ((proc (lambda (store) exp ...)))
+ (if store-needed?
+ (with-store s
+ (set-build-options-from-command-line s opts)
+ (with-build-handler (build-notifier #:use-substitutes?
+ (assoc-ref opts 'substitutes?)
+ #:verbosity
+ (assoc-ref opts 'verbosity)
+ #:dry-run?
+ (assoc-ref opts 'dry-run?))
+ (proc s)))
+ (proc #f))))
+
(when container? (assert-container-features))
(when (and (not container?) link-prof?)
@@ -700,85 +922,89 @@ message if any test fails."
(leave (G_ "--no-cwd cannot be used without --container~%")))
- (with-store store
- (with-build-handler (build-notifier #:use-substitutes?
- (assoc-ref opts 'substitutes?)
- #:verbosity
- (assoc-ref opts 'verbosity)
- #:dry-run?
- (assoc-ref opts 'dry-run?))
- (with-status-verbosity (assoc-ref opts 'verbosity)
- (define manifest-from-opts
- (options/resolve-packages store opts))
-
- (define manifest
- (if profile
- (profile-manifest profile)
- manifest-from-opts))
-
- (when (and profile
- (> (length (manifest-entries manifest-from-opts)) 0))
- (leave (G_ "'--profile' cannot be used with package options~%")))
-
- (when (null? (manifest-entries manifest))
- (warning (G_ "no packages specified; creating an empty environment~%")))
-
- (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
- (default-guile)))))
- (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 -> (if profile
+ (with-store/maybe store
+ (with-status-verbosity (assoc-ref opts 'verbosity)
+ (define manifest-from-opts
+ (options/resolve-packages store opts))
+
+ (define manifest
+ (if profile
+ (profile-manifest profile)
+ manifest-from-opts))
+
+ (when (and profile
+ (> (length (manifest-entries manifest-from-opts)) 0))
+ (leave (G_ "'--profile' cannot be used with package options~%")))
+
+ (when (null? (manifest-entries manifest))
+ (warning (G_ "no packages specified; creating an empty environment~%")))
+
+ ;; Use the bootstrap Guile when requested.
+ (parameterize ((%graft? (assoc-ref opts 'graft?))
+ (%guile-for-build
+ (and store-needed?
+ (package-derivation
+ store
+ (if bootstrap?
+ %bootstrap-guile
+ (default-guile))))))
+ (run-with-store store
+ ;; Containers need a Bourne shell at /bin/sh.
+ (mlet* %store-monad ((bash (environment-bash container?
+ bootstrap?
+ system))
+ (prof-drv (if profile
+ (return #f)
+ (manifest->derivation
+ manifest system bootstrap?)))
+ (profile -> (if profile
(readlink* 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
- (built-derivations (if (derivation? bash)
- (list prof-drv bash)
- (list prof-drv)))
- (mwhen gc-root
- (register-gc-root profile gc-root))
-
- (cond
- ((assoc-ref opts 'search-paths)
- (show-search-paths profile manifest #:pure? pure?)
- (return #t))
- (container?
- (let ((bash-binary
- (if bootstrap?
- (derivation->output-path 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
- #:white-list white-list
- #:link-profile? link-prof?
- #:network? network?
- #:map-cwd? (not no-cwd?))))
-
- (else
- (return
- (exit/status
- (launch-environment/fork command profile manifest
- #:white-list white-list
- #:pure? pure?)))))))))))))))
+ (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
+ (mwhen store-needed?
+ (built-derivations (append
+ (if prof-drv (list prof-drv) '())
+ (if (derivation? bash) (list bash) '()))))
+ (mwhen gc-root
+ (register-gc-root profile gc-root))
+
+ (mwhen (assoc-ref opts 'check?)
+ (return
+ (validate-child-shell-environment profile manifest)))
+
+ (cond
+ ((assoc-ref opts 'search-paths)
+ (show-search-paths profile manifest #:pure? pure?)
+ (return #t))
+ (container?
+ (let ((bash-binary
+ (if bootstrap?
+ (derivation->output-path 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
+ #:white-list white-list
+ #:link-profile? link-prof?
+ #:network? network?
+ #:map-cwd? (not no-cwd?))))
+
+ (else
+ (return
+ (exit/status
+ (launch-environment/fork command profile manifest
+ #:white-list white-list
+ #:pure? pure?))))))))))))))
+
+;;; Local Variables:
+;;; eval: (put 'with-store/maybe 'scheme-indent-function 1)
+;;; End:
diff --git a/guix/scripts/graph.scm b/guix/scripts/graph.scm
index 66de824ef4..8943e87099 100644
--- a/guix/scripts/graph.scm
+++ b/guix/scripts/graph.scm
@@ -500,6 +500,10 @@ package modules, while attempting to retain user package modules."
(lambda (opt name arg result)
(alist-cons 'backend (lookup-backend arg)
result)))
+ (option '(#\M "max-depth") #t #f
+ (lambda (opt name arg result)
+ (alist-cons 'max-depth (string->number* arg)
+ result)))
(option '("list-backends") #f #f
(lambda (opt name arg result)
(list-backends)
@@ -538,6 +542,8 @@ Emit a representation of the dependency graph of PACKAGE...\n"))
(display (G_ "
--list-types list the available graph types"))
(display (G_ "
+ -M, --max-depth=DEPTH limit to nodes within distance DEPTH"))
+ (display (G_ "
--path display the shortest path between the given nodes"))
(display (G_ "
-e, --expression=EXPR consider the package EXPR evaluates to"))
@@ -559,6 +565,7 @@ Emit a representation of the dependency graph of PACKAGE...\n"))
(define %default-options
`((node-type . ,%package-node-type)
(backend . ,%graphviz-backend)
+ (max-depth . +inf.0)
(system . ,(%current-system))))
@@ -582,6 +589,7 @@ Emit a representation of the dependency graph of PACKAGE...\n"))
(with-store store
(let* ((transform (options->transformation opts))
+ (max-depth (assoc-ref opts 'max-depth))
(items (filter-map (match-lambda
(('argument . (? store-path? item))
item)
@@ -613,7 +621,8 @@ nodes (given ~a)~%")
(export-graph (concatenate nodes)
(current-output-port)
#:node-type type
- #:backend backend)))
+ #:backend backend
+ #:max-depth max-depth)))
#:system (assq-ref opts 'system)))))
#t)
diff --git a/guix/scripts/hash.scm b/guix/scripts/hash.scm
index b8622373cc..d73e3d13dd 100644
--- a/guix/scripts/hash.scm
+++ b/guix/scripts/hash.scm
@@ -3,6 +3,7 @@
;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
;;; Copyright © 2016 Jan Nieuwenhuizen <janneke@gnu.org>
;;; Copyright © 2018 Tim Gesthuizen <tim.gesthuizen@yahoo.de>
+;;; Copyright © 2021 Simon Tournier <zimon.toutoune@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -34,17 +35,53 @@
#:use-module (srfi srfi-11)
#:use-module (srfi srfi-26)
#:use-module (srfi srfi-37)
+ #:autoload (disarchive git-hash) (git-hash-file git-hash-directory)
#:export (guix-hash))
;;;
+;;; Serializers
+;;;
+
+(define* (nar-hash file #:optional
+ (algorithm (assoc-ref %default-options 'hash-algorithm))
+ select?)
+ (let-values (((port get-hash)
+ (open-hash-port algorithm)))
+ (write-file file port #:select? select?)
+ (force-output port)
+ (get-hash)))
+
+(define* (default-hash file #:optional
+ (algorithm (assoc-ref %default-options 'hash-algorithm))
+ select?)
+ (match file
+ ("-" (port-hash algorithm (current-input-port)))
+ (_
+ (call-with-input-file file
+ (cute port-hash algorithm <>)))))
+
+(define* (git-hash file #:optional
+ (algorithm (assoc-ref %default-options 'hash-algorithm))
+ select?)
+ (define directory?
+ (case (stat:type (stat file))
+ ((directory) #t)
+ (else #f)))
+ (if directory?
+ (git-hash-directory file algorithm)
+ (git-hash-file file algorithm)))
+
+
+;;;
;;; Command-line options.
;;;
(define %default-options
;; Alist of default option values.
`((format . ,bytevector->nix-base32-string)
- (hash-algorithm . ,(hash-algorithm sha256))))
+ (hash-algorithm . ,(hash-algorithm sha256))
+ (serializer . ,default-hash)))
(define (show-help)
(display (G_ "Usage: guix hash [OPTION] FILE
@@ -60,7 +97,7 @@ 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_ "
- -r, --recursive compute the hash on FILE recursively"))
+ -S, --serializer=TYPE compute the hash on FILE according to TYPE serialization"))
(newline)
(display (G_ "
-h, --help display this help and exit"))
@@ -101,7 +138,26 @@ and 'base16' ('hex' and 'hexadecimal' can be used as well).\n"))
(alist-delete 'format result))))
(option '(#\r "recursive") #f #f
(lambda (opt name arg result)
- (alist-cons 'recursive? #t result)))
+ (warning (G_ "'--recursive' is deprecated, \
+use '--serializer' instead~%"))
+ (alist-cons 'serializer nar-hash
+ (alist-delete 'serializer result))))
+ (option '(#\S "serializer") #t #f
+ (lambda (opt name arg result)
+ (define serializer-proc
+ (match arg
+ ("none"
+ default-hash)
+ ("nar"
+ nar-hash)
+ ("git"
+ git-hash)
+ (x
+ (leave (G_ "unsupported serializer type: ~a~%")
+ arg))))
+
+ (alist-cons 'serializer serializer-proc
+ (alist-delete 'serializer result))))
(option '(#\h "help") #f #f
(lambda args
(show-help)
@@ -144,32 +200,29 @@ and 'base16' ('hex' and 'hexadecimal' can be used as well).\n"))
(fmt (assq-ref opts 'format))
(select? (if (assq-ref opts 'exclude-vcs?)
(negate vcs-file?)
- (const #t))))
+ (const #t)))
+ (algorithm (assoc-ref opts 'hash-algorithm))
+ (serializer (assoc-ref opts 'serializer)))
(define (file-hash file)
;; Compute the hash of FILE.
- ;; Catch and gracefully report possible '&nar-error' conditions.
- (with-error-handling
- (if (assoc-ref opts 'recursive?)
- (let-values (((port get-hash)
- (open-hash-port (assoc-ref opts 'hash-algorithm))))
- (write-file file port #:select? select?)
- (force-output port)
- (get-hash))
- (match file
- ("-" (port-hash (assoc-ref opts 'hash-algorithm)
- (current-input-port)))
- (_ (call-with-input-file file
- (cute port-hash (assoc-ref opts 'hash-algorithm)
- <>)))))))
+ ;; Catch and gracefully report possible error
+ (catch 'system-error
+ (lambda _
+ (with-error-handling
+ (serializer file algorithm select?)))
+ (lambda args
+ (leave (G_ "~a ~a~%")
+ file
+ (strerror (system-error-errno args))))))
+
+ (define (formatted-hash thing)
+ (fmt (file-hash thing)))
(match args
- ((file)
- (catch 'system-error
- (lambda ()
- (format #t "~a~%" (fmt (file-hash file))))
- (lambda args
- (leave (G_ "~a~%")
- (strerror (system-error-errno args))))))
- (x
- (leave (G_ "wrong number of arguments~%"))))))
+ (()
+ (leave (G_ "no arguments specified~%")))
+ (_
+ (for-each
+ (compose (cute format #t "~a~%" <>) formatted-hash)
+ args)))))
diff --git a/guix/scripts/home.scm b/guix/scripts/home.scm
new file mode 100644
index 0000000000..afc7d8b39c
--- /dev/null
+++ b/guix/scripts/home.scm
@@ -0,0 +1,538 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Andrew Tropin <andrew@trop.in>
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
+;;; Copyright © 2021 Pierre Langlois <pierre.langlois@gmx.com>
+;;; Copyright © 2021 Oleg Pykhalov <go.wigust@gmail.com>
+;;;
+;;; 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 scripts home)
+ #:use-module (gnu packages admin)
+ #:use-module ((gnu services) #:hide (delete))
+ #:use-module (gnu packages)
+ #:use-module (gnu home)
+ #:use-module (gnu home services)
+ #:use-module (guix channels)
+ #:use-module (guix derivations)
+ #:use-module (guix ui)
+ #:use-module (guix grafts)
+ #:use-module (guix packages)
+ #:use-module (guix profiles)
+ #:use-module (guix store)
+ #:use-module (guix utils)
+ #:use-module (guix scripts)
+ #:use-module (guix scripts package)
+ #:use-module (guix scripts build)
+ #:use-module (guix scripts system search)
+ #:autoload (guix scripts pull) (channel-commit-hyperlink)
+ #:use-module (guix scripts home import)
+ #:use-module ((guix status) #:select (with-status-verbosity))
+ #:use-module ((guix build utils) #:select (mkdir-p))
+ #:use-module (guix gexp)
+ #:use-module (guix monads)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
+ #:use-module (srfi srfi-35)
+ #:use-module (srfi srfi-37)
+ #:use-module (ice-9 match)
+ #:export (guix-home))
+
+
+;;;
+;;; Options.
+;;;
+
+(define %user-module
+ (make-user-module '((gnu home))))
+
+(define %guix-home
+ (string-append %profile-directory "/guix-home"))
+
+(define (show-help)
+ (display (G_ "Usage: guix home [OPTION ...] ACTION [ARG ...] [FILE]
+Build the home environment declared in FILE according to ACTION.
+Some ACTIONS support additional ARGS.\n"))
+ (newline)
+ (display (G_ "The valid values for ACTION are:\n"))
+ (newline)
+ (display (G_ "\
+ search search for existing service types\n"))
+ (display (G_ "\
+ reconfigure switch to a new home environment configuration\n"))
+ (display (G_ "\
+ roll-back switch to the previous home environment configuration\n"))
+ (display (G_ "\
+ describe describe the current home environment\n"))
+ (display (G_ "\
+ list-generations list the home environment generations\n"))
+ (display (G_ "\
+ switch-generation switch to an existing home environment configuration\n"))
+ (display (G_ "\
+ delete-generations delete old home environment generations\n"))
+ (display (G_ "\
+ build build the home environment without installing anything\n"))
+ (display (G_ "\
+ import generates a home environment definition from dotfiles\n"))
+
+ (show-build-options-help)
+ (display (G_ "
+ -e, --expression=EXPR consider the home-environment EXPR evaluates to
+ instead of reading FILE, when applicable"))
+ (display (G_ "
+ -v, --verbosity=LEVEL use the given verbosity LEVEL"))
+ (newline)
+ (display (G_ "
+ -h, --help display this help and exit"))
+ (display (G_ "
+ -V, --version display version information and exit"))
+ (newline)
+ (show-bug-report-information))
+
+(define (verbosity-level opts)
+ "Return the verbosity level based on OPTS, the alist of parsed options."
+ (or (assoc-ref opts 'verbosity)
+ (if (eq? (assoc-ref opts 'action) 'build)
+ 2 1)))
+
+(define %options
+ ;; Specification of the command-line options.
+ (cons* (option '(#\h "help") #f #f
+ (lambda args
+ (show-help)
+ (exit 0)))
+ (option '(#\n "dry-run") #f #f
+ (lambda (opt name arg result)
+ (alist-cons 'dry-run? #t result)))
+ (option '(#\V "version") #f #f
+ (lambda args
+ (show-version-and-exit "guix show")))
+ (option '(#\v "verbosity") #t #f
+ (lambda (opt name arg result)
+ (let ((level (string->number* arg)))
+ (alist-cons 'verbosity level
+ (alist-delete 'verbosity result)))))
+ (option '(#\e "expression") #t #f
+ (lambda (opt name arg result)
+ (alist-cons 'expression arg result)))
+ %standard-build-options))
+
+(define %default-options
+ `((build-mode . ,(build-mode normal))
+ (graft? . #t)
+ (substitutes? . #t)
+ (offload? . #t)
+ (print-build-trace? . #t)
+ (print-extended-build-trace? . #t)
+ (multiplexed-build-output? . #t)
+ (verbosity . 3)
+ (debug . 0)))
+
+
+;;;
+;;; Actions.
+;;;
+
+(define* (perform-action action he
+ #:key
+ dry-run?
+ derivations-only?
+ use-substitutes?)
+ "Perform ACTION for home environment. "
+
+ (define println
+ (cut format #t "~a~%" <>))
+
+ (mlet* %store-monad
+ ((he-drv (home-environment-derivation he))
+ (drvs (mapm/accumulate-builds lower-object (list he-drv)))
+ (% (if derivations-only?
+ (return
+ (for-each (compose println derivation-file-name) drvs))
+ (built-derivations drvs)))
+
+ (he-out-path -> (derivation->output-path he-drv)))
+ (if (or dry-run? derivations-only?)
+ (return #f)
+ (begin
+ (for-each (compose println derivation->output-path) drvs)
+
+ (case action
+ ((reconfigure)
+ (let* ((number (generation-number %guix-home))
+ (generation (generation-file-name
+ %guix-home (+ 1 number))))
+
+ (switch-symlinks generation he-out-path)
+ (switch-symlinks %guix-home generation)
+ (setenv "GUIX_NEW_HOME" he-out-path)
+ (primitive-load (string-append he-out-path "/activate"))
+ (setenv "GUIX_NEW_HOME" #f)
+ (return he-out-path)))
+ (else
+ (newline)
+ (return he-out-path)))))))
+
+(define (process-action action args opts)
+ "Process ACTION, a sub-command, with the arguments are listed in ARGS.
+ACTION must be one of the sub-commands that takes a home environment
+declaration as an argument (a file name.) OPTS is the raw alist of options
+resulting from command-line parsing."
+ (define (ensure-home-environment file-or-exp obj)
+ (ensure-profile-directory)
+ (unless (home-environment? obj)
+ (leave (G_ "'~a' does not return a home environment ~%")
+ file-or-exp))
+ obj)
+
+ (let* ((file (match args
+ (() #f)
+ ((x . _) x)))
+ (expr (assoc-ref opts 'expression))
+ (system (assoc-ref opts 'system))
+
+ (transform (lambda (obj)
+ (home-environment-with-provenance obj file)))
+
+ (home-environment
+ (transform
+ (ensure-home-environment
+ (or file expr)
+ (cond
+ ((and expr file)
+ (leave
+ (G_ "both file and expression cannot be specified~%")))
+ (expr
+ (read/eval expr))
+ (file
+ (load* file %user-module
+ #:on-error (assoc-ref opts 'on-error)))
+ (else
+ (leave (G_ "no configuration specified~%")))))))
+
+ (dry? (assoc-ref opts 'dry-run?)))
+
+ (with-store store
+ (set-build-options-from-command-line store opts)
+ (with-build-handler (build-notifier #:use-substitutes?
+ (assoc-ref opts 'substitutes?)
+ #:verbosity
+ (verbosity-level opts)
+ #:dry-run?
+ (assoc-ref opts 'dry-run?))
+
+ (run-with-store store
+ (mbegin %store-monad
+ (set-guile-for-build (default-guile))
+
+ (case action
+ (else
+ (perform-action action home-environment
+ #:dry-run? dry?
+ #:derivations-only? (assoc-ref opts 'derivations-only?)
+ #:use-substitutes? (assoc-ref opts 'substitutes?))
+ ))))))
+ (warn-about-disk-space)))
+
+
+(define (process-command command args opts)
+ "Process COMMAND, one of the 'guix home' sub-commands. ARGS is its
+argument list and OPTS is the option alist."
+ (define-syntax-rule (with-store* store exp ...)
+ (with-store store
+ (set-build-options-from-command-line store opts)
+ exp ...))
+ (case command
+ ;; The following commands do not need to use the store, and they do not need
+ ;; an home environment file.
+ ((search)
+ (apply search args))
+ ((import)
+ (let* ((profiles (delete-duplicates
+ (match (filter-map (match-lambda
+ (('profile . p) p)
+ (_ #f))
+ opts)
+ (() (list %current-profile))
+ (lst (reverse lst)))))
+ (manifest (concatenate-manifests
+ (map profile-manifest profiles)))
+ (destination (match args
+ ((destination) destination)
+ (_ (leave (G_ "wrong number of arguments~%"))))))
+ (unless (file-exists? destination)
+ (mkdir-p destination))
+ (call-with-output-file
+ (string-append destination "/home-configuration.scm")
+ (cut import-manifest manifest destination <>))
+ (info (G_ "'~a' populated with all the Home configuration files~%")
+ destination)
+ (display-hint (format #f (G_ "\
+Run @command{guix home reconfigure ~a/home-configuration.scm} to effectively
+deploy the home environment described by these files.\n")
+ destination))))
+ ((describe)
+ (match (generation-number %guix-home)
+ (0
+ (error (G_ "no home environment generation, nothing to describe~%")))
+ (generation
+ (display-home-environment-generation generation))))
+ ((list-generations)
+ (let ((pattern (match args
+ (() #f)
+ ((pattern) pattern)
+ (x (leave (G_ "wrong number of arguments~%"))))))
+ (list-generations pattern)))
+ ((switch-generation)
+ (let ((pattern (match args
+ ((pattern) pattern)
+ (x (leave (G_ "wrong number of arguments~%"))))))
+ (with-store* store
+ (switch-to-home-environment-generation store pattern))))
+ ((roll-back)
+ (let ((pattern (match args
+ (() "")
+ (x (leave (G_ "wrong number of arguments~%"))))))
+ (with-store* store
+ (roll-back-home-environment store))))
+ ((delete-generations)
+ (let ((pattern (match args
+ (() #f)
+ ((pattern) pattern)
+ (x (leave (G_ "wrong number of arguments~%"))))))
+ (with-store*
+ store
+ (delete-matching-generations store %guix-home pattern))))
+ (else (process-action command args opts))))
+
+(define-command (guix-home . args)
+ (synopsis "build and deploy home environments")
+
+ (define (parse-sub-command arg result)
+ ;; Parse sub-command ARG and augment RESULT accordingly.
+ (if (assoc-ref result 'action)
+ (alist-cons 'argument arg result)
+ (let ((action (string->symbol arg)))
+ (case action
+ ((build
+ reconfigure
+ extension-graph shepherd-graph
+ list-generations describe
+ delete-generations roll-back
+ switch-generation search
+ import)
+ (alist-cons 'action action result))
+ (else (leave (G_ "~a: unknown action~%") action))))))
+
+ (define (match-pair car)
+ ;; Return a procedure that matches a pair with CAR.
+ (match-lambda
+ ((head . tail)
+ (and (eq? car head) tail))
+ (_ #f)))
+
+ (define (option-arguments opts)
+ ;; Extract the plain arguments from OPTS.
+ (let* ((args (reverse (filter-map (match-pair 'argument) opts)))
+ (count (length args))
+ (action (assoc-ref opts 'action))
+ (expr (assoc-ref opts 'expression)))
+ (define (fail)
+ (leave (G_ "wrong number of arguments for action '~a'~%")
+ action))
+
+ (unless action
+ (format (current-error-port)
+ (G_ "guix home: missing command name~%"))
+ (format (current-error-port)
+ (G_ "Try 'guix home --help' for more information.~%"))
+ (exit 1))
+
+ (case action
+ ((build reconfigure)
+ (unless (or (= count 1)
+ (and expr (= count 0)))
+ (fail)))
+ ((init)
+ (unless (= count 2)
+ (fail))))
+ args))
+
+ (with-error-handling
+ (let* ((opts (parse-command-line args %options
+ (list %default-options)
+ #:argument-handler
+ parse-sub-command))
+ (args (option-arguments opts))
+ (command (assoc-ref opts 'action)))
+ (parameterize ((%graft? (assoc-ref opts 'graft?)))
+ (with-status-verbosity (verbosity-level opts)
+ (process-command command args opts))))))
+
+
+;;;
+;;; Searching.
+;;;
+
+(define service-type-name*
+ (compose symbol->string service-type-name))
+
+(define (service-type-description-string type)
+ "Return the rendered and localised description of TYPE, a service type."
+ (and=> (service-type-description type)
+ (compose texi->plain-text P_)))
+
+(define %service-type-metrics
+ ;; Metrics used to estimate the relevance of a search result.
+ `((,service-type-name* . 3)
+ (,service-type-description-string . 2)
+ (,(lambda (type)
+ (match (and=> (service-type-location type) location-file)
+ ((? string? file)
+ (basename file ".scm"))
+ (#f
+ "")))
+ . 1)))
+
+(define (find-service-types regexps)
+ "Return a list of service type/score pairs: service types whose name or
+description matches REGEXPS sorted by relevance, and their score."
+ (let ((matches (fold-home-service-types
+ (lambda (type result)
+ (match (relevance type regexps
+ %service-type-metrics)
+ ((? zero?)
+ result)
+ (score
+ (cons (cons type score) result))))
+ '())))
+ (sort matches
+ (lambda (m1 m2)
+ (match m1
+ ((type1 . score1)
+ (match m2
+ ((type2 . score2)
+ (if (= score1 score2)
+ (string>? (service-type-name* type1)
+ (service-type-name* type2))
+ (> score1 score2))))))))))
+
+(define (search . args)
+ (with-error-handling
+ (let* ((regexps (map (cut make-regexp* <> regexp/icase) args))
+ (matches (find-service-types regexps)))
+ (leave-on-EPIPE
+ (display-search-results matches (current-output-port)
+ #:print service-type->recutils
+ #:command "guix home search")))))
+
+
+;;;
+;;; Generations.
+;;;
+
+(define* (display-home-environment-generation
+ number
+ #:optional (profile %guix-home))
+ "Display a summary of home-environment generation NUMBER in a
+human-readable format."
+ (define (display-channel channel)
+ (format #t " ~a:~%" (channel-name channel))
+ (format #t (G_ " repository URL: ~a~%") (channel-url channel))
+ (when (channel-branch channel)
+ (format #t (G_ " branch: ~a~%") (channel-branch channel)))
+ (format #t (G_ " commit: ~a~%")
+ (if (supports-hyperlinks?)
+ (channel-commit-hyperlink channel)
+ (channel-commit channel))))
+
+ (unless (zero? number)
+ (let* ((generation (generation-file-name profile number)))
+ (define-values (channels config-file)
+ ;; The function will work for home environments too, we just
+ ;; need to keep provenance file.
+ (system-provenance generation))
+
+ (display-generation profile number)
+ (format #t (G_ " file name: ~a~%") generation)
+ (format #t (G_ " canonical file name: ~a~%") (readlink* generation))
+ ;; TRANSLATORS: Please preserve the two-space indentation.
+
+ (unless (null? channels)
+ ;; TRANSLATORS: Here "channel" is the same terminology as used in
+ ;; "guix describe" and "guix pull --channels".
+ (format #t (G_ " channels:~%"))
+ (for-each display-channel channels))
+ (when config-file
+ (format #t (G_ " configuration file: ~a~%")
+ (if (supports-hyperlinks?)
+ (file-hyperlink config-file)
+ config-file))))))
+
+(define* (list-generations pattern #:optional (profile %guix-home))
+ "Display in a human-readable format all the home environment
+generations matching PATTERN, a string. When PATTERN is #f, display
+all the home environment generations."
+ (cond ((not (file-exists? profile)) ; XXX: race condition
+ (raise (condition (&profile-not-found-error
+ (profile profile)))))
+ ((not pattern)
+ (for-each display-home-environment-generation (profile-generations profile)))
+ ((matching-generations pattern profile)
+ =>
+ (lambda (numbers)
+ (if (null-list? numbers)
+ (exit 1)
+ (leave-on-EPIPE
+ (for-each display-home-environment-generation numbers)))))))
+
+
+;;;
+;;; Switch generations.
+;;;
+
+;; TODO: Make it public in (guix scripts system)
+(define-syntax-rule (unless-file-not-found exp)
+ (catch 'system-error
+ (lambda ()
+ exp)
+ (lambda args
+ (if (= ENOENT (system-error-errno args))
+ #f
+ (apply throw args)))))
+
+(define (switch-to-home-environment-generation store spec)
+ "Switch the home-environment profile to the generation specified by
+SPEC. STORE is an open connection to the store."
+ (let* ((number (relative-generation-spec->number %guix-home spec))
+ (generation (generation-file-name %guix-home number))
+ (activate (string-append generation "/activate")))
+ (if number
+ (begin
+ (setenv "GUIX_NEW_HOME" (readlink generation))
+ (switch-to-generation* %guix-home number)
+ (unless-file-not-found (primitive-load activate))
+ (setenv "GUIX_NEW_HOME" #f))
+ (leave (G_ "cannot switch to home environment generation '~a'~%") spec))))
+
+
+;;;
+;;; Roll-back.
+;;;
+
+(define (roll-back-home-environment store)
+ "Roll back the home-environment profile to its previous generation.
+STORE is an open connection to the store."
+ (switch-to-home-environment-generation store "-1"))
diff --git a/guix/scripts/home/import.scm b/guix/scripts/home/import.scm
new file mode 100644
index 0000000000..fbf89069a7
--- /dev/null
+++ b/guix/scripts/home/import.scm
@@ -0,0 +1,186 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
+;;; Copyright © 2021 Andrew Tropin <andrew@trop.in>
+;;; Copyright © 2021 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 scripts home import)
+ #:use-module (guix profiles)
+ #:use-module (guix ui)
+ #:use-module (guix utils)
+ #:use-module (guix packages)
+ #:autoload (guix scripts package) (manifest-entry-version-prefix)
+ #:use-module (gnu packages)
+ #:use-module (ice-9 match)
+ #:use-module (ice-9 pretty-print)
+ #:use-module (ice-9 rdelim)
+ #:use-module (ice-9 regex)
+ #:use-module (ice-9 popen)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
+ #:export (import-manifest
+
+ ;; For tests.
+ manifest+configuration-files->code))
+
+;;; Commentary:
+;;;
+;;; This module provides utilities for generating home service
+;;; configurations from existing "dotfiles".
+;;;
+;;; Code:
+
+(define (basename+remove-dots file-name)
+ "Remove the dot from the dotfile FILE-NAME; replace the other dots in
+FILE-NAME with \"-\", and return the basename of it."
+ (string-map (match-lambda
+ (#\. #\-)
+ (c c))
+ (let ((base (basename file-name)))
+ (if (string-prefix? "." base)
+ (string-drop base 1)
+ base))))
+
+(define (generate-bash-configuration+modules destination-directory)
+ (define (destination-append path)
+ (string-append destination-directory "/" path))
+
+ (define (bash-alias->pair line)
+ (if (string-prefix? "alias" line)
+ (let ((matched (string-match "alias (.+)=\"?'?([^\"']+)\"?'?" line)))
+ `(,(match:substring matched 1) . ,(match:substring matched 2)))
+ '()))
+
+ (define (parse-aliases input)
+ (let loop ((line (read-line input))
+ (result '()))
+ (if (eof-object? line)
+ (reverse result)
+ (loop (read-line input)
+ (cons (bash-alias->pair line) result)))))
+
+ (let ((rc (destination-append ".bashrc"))
+ (profile (destination-append ".bash_profile"))
+ (logout (destination-append ".bash_logout")))
+ `((service home-bash-service-type
+ (home-bash-configuration
+ ,@(if (file-exists? rc)
+ `((aliases
+ ',(let* ((port (open-pipe* OPEN_READ "bash" "-i" "-c" "alias"))
+ (alist (parse-aliases port)))
+ (close-port port)
+ (filter (negate null?) alist))))
+ '())
+ ,@(if (file-exists? rc)
+ `((bashrc
+ (list (local-file ,rc
+ ,(basename+remove-dots rc)))))
+ '())
+ ,@(if (file-exists? profile)
+ `((bash-profile
+ (list (local-file ,profile
+ ,(basename+remove-dots profile)))))
+ '())
+ ,@(if (file-exists? logout)
+ `((bash-logout
+ (list (local-file ,logout
+ ,(basename+remove-dots logout)))))
+ '())))
+ (guix gexp)
+ (gnu home services shells))))
+
+(define %files+configurations-alist
+ `((".bashrc" . ,generate-bash-configuration+modules)
+ (".bash_profile" . ,generate-bash-configuration+modules)
+ (".bash_logout" . ,generate-bash-configuration+modules)))
+
+(define (configurations+modules configuration-directory)
+ "Return a list of procedures which when called, generate code for a home
+service declaration. Copy configuration files to CONFIGURATION-DIRECTORY; the
+generated service declarations will refer to those files that have been saved
+in CONFIGURATION-DIRECTORY."
+ (define configurations
+ (delete-duplicates
+ (filter-map (match-lambda
+ ((file . proc)
+ (let ((absolute-path (string-append (getenv "HOME")
+ "/" file)))
+ (and (file-exists? absolute-path)
+ (begin
+ (copy-file absolute-path
+ (string-append
+ configuration-directory "/" file))
+ proc)))))
+ %files+configurations-alist)
+ eq?))
+
+ (map (lambda (proc) (proc configuration-directory)) configurations))
+
+(define (manifest+configuration-files->code manifest
+ configuration-directory)
+ "Read MANIFEST and the user's configuration files listed in
+%FILES+CONFIGURATIONS-ALIST, and return a 'home-environment' sexp. Copy the
+user's files to CONFIGURATION-DIRECTORY; the generated sexp refers to them."
+ (match (manifest->code manifest
+ #:entry-package-version
+ manifest-entry-version-prefix)
+ (('begin ('use-modules profile-modules ...)
+ definitions ... ('packages->manifest packages))
+ (match (configurations+modules configuration-directory)
+ (((services . modules) ...)
+ `(begin
+ (use-modules (gnu home)
+ (gnu packages)
+ (gnu services)
+ ,@(delete-duplicates
+ (append profile-modules (concatenate modules))))
+
+ ,@definitions
+
+ (home-environment
+ (packages ,packages)
+ (services (list ,@services)))))))
+ (('begin ('specifications->manifest packages))
+ (match (configurations+modules configuration-directory)
+ (((services . modules) ...)
+ `(begin
+ (use-modules (gnu home)
+ (gnu packages)
+ (gnu services)
+ ,@(delete-duplicates (concatenate modules)))
+
+ (home-environment
+ (packages (map specification->package ,packages))
+ (services (list ,@services)))))))))
+
+(define* (import-manifest
+ manifest destination-directory
+ #:optional (port (current-output-port)))
+ "Write to PORT a <home-environment> corresponding to MANIFEST."
+ (match (manifest+configuration-files->code manifest
+ destination-directory)
+ (('begin exp ...)
+ (format port (G_ "\
+;; This \"home-environment\" file can be passed to 'guix home reconfigure'
+;; to reproduce the content of your profile. This is \"symbolic\": it only
+;; specifies package names. To reproduce the exact same profile, you also
+;; need to capture the channels being used, as returned by \"guix describe\".
+;; See the \"Replicating Guix\" section in the manual.\n"))
+ (for-each (lambda (exp)
+ (newline port)
+ (pretty-print exp port))
+ exp))))
diff --git a/guix/scripts/import/cran.scm b/guix/scripts/import/cran.scm
index 3e4b038cc4..2934d4300a 100644
--- a/guix/scripts/import/cran.scm
+++ b/guix/scripts/import/cran.scm
@@ -27,8 +27,8 @@
#:use-module (guix import utils)
#:use-module (guix scripts import)
#:use-module (srfi srfi-1)
- #:use-module (srfi srfi-11)
#:use-module (srfi srfi-37)
+ #:use-module (srfi srfi-71)
#:use-module (ice-9 match)
#:use-module (ice-9 format)
#:export (guix-import-cran))
@@ -98,21 +98,24 @@ Import and convert the CRAN package for PACKAGE-NAME.\n"))
(reverse opts))))
(parameterize ((%input-style (assoc-ref opts 'style)))
(match args
- ((package-name)
- (if (assoc-ref opts 'recursive)
- ;; Recursive import
- (with-error-handling
- (map package->definition
- (filter identity
- (cran-recursive-import package-name
- #:repo (or (assoc-ref opts 'repo) 'cran)))))
- ;; Single import
- (let ((sexp (cran->guix-package package-name
- #:repo (or (assoc-ref opts 'repo) 'cran))))
- (unless sexp
- (leave (G_ "failed to download description for package '~a'~%")
- package-name))
- sexp)))
+ ((spec)
+ (let ((name version (package-name->name+version spec)))
+ (if (assoc-ref opts 'recursive)
+ ;; Recursive import
+ (with-error-handling
+ (map package->definition
+ (filter identity
+ (cran-recursive-import name
+ #:version version
+ #:repo (or (assoc-ref opts 'repo) 'cran)))))
+ ;; Single import
+ (let ((sexp (cran->guix-package name
+ #:version version
+ #:repo (or (assoc-ref opts 'repo) 'cran))))
+ (unless sexp
+ (leave (G_ "failed to download description for package '~a'~%")
+ name))
+ sexp))))
(()
(leave (G_ "too few arguments~%")))
((many ...)
diff --git a/guix/scripts/import/egg.scm b/guix/scripts/import/egg.scm
index 829cdc2ca0..6a9657d12c 100644
--- a/guix/scripts/import/egg.scm
+++ b/guix/scripts/import/egg.scm
@@ -26,6 +26,7 @@
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-11)
#:use-module (srfi srfi-37)
+ #:use-module (srfi srfi-71)
#:use-module (ice-9 match)
#:use-module (ice-9 format)
#:export (guix-import-egg))
@@ -83,21 +84,24 @@ Import and convert the egg package for PACKAGE-NAME.\n"))
(_ #f))
(reverse opts))))
(match args
- ((package-name)
- (if (assoc-ref opts 'recursive)
- ;; Recursive import
- (map (match-lambda
- ((and ('package ('name name) . rest) pkg)
- `(define-public ,(string->symbol name)
- ,pkg))
- (_ #f))
- (egg-recursive-import package-name))
- ;; Single import
- (let ((sexp (egg->guix-package package-name)))
- (unless sexp
- (leave (G_ "failed to download meta-data for package '~a'~%")
- package-name))
- sexp)))
+ ((spec)
+ (let ((name version (package-name->name+version spec)))
+ (if (assoc-ref opts 'recursive)
+ ;; Recursive import
+ (map (match-lambda
+ ((and ('package ('name name) . rest) pkg)
+ `(define-public ,(string->symbol name)
+ ,pkg))
+ (_ #f))
+ (egg-recursive-import name version))
+ ;; Single import
+ (let ((sexp (egg->guix-package name version)))
+ (unless sexp
+ (leave (G_ "failed to download meta-data for package '~a'~%")
+ (if version
+ (string-append name "@" version)
+ name)))
+ sexp))))
(()
(leave (G_ "too few arguments~%")))
((many ...)
diff --git a/guix/scripts/import/pypi.scm b/guix/scripts/import/pypi.scm
index 9170a0b359..a52cd95c93 100644
--- a/guix/scripts/import/pypi.scm
+++ b/guix/scripts/import/pypi.scm
@@ -27,6 +27,7 @@
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-11)
#:use-module (srfi srfi-37)
+ #:use-module (srfi srfi-71)
#:use-module (ice-9 match)
#:use-module (ice-9 format)
#:export (guix-import-pypi))
@@ -83,21 +84,22 @@ Import and convert the PyPI package for PACKAGE-NAME.\n"))
(_ #f))
(reverse opts))))
(match args
- ((package-name)
- (if (assoc-ref opts 'recursive)
- ;; Recursive import
- (map (match-lambda
- ((and ('package ('name name) . rest) pkg)
- `(define-public ,(string->symbol name)
- ,pkg))
- (_ #f))
- (pypi-recursive-import package-name))
- ;; Single import
- (let ((sexp (pypi->guix-package package-name)))
- (unless sexp
- (leave (G_ "failed to download meta-data for package '~a'~%")
- package-name))
- sexp)))
+ ((spec)
+ (let ((name version (package-name->name+version spec)))
+ (if (assoc-ref opts 'recursive)
+ ;; Recursive import
+ (map (match-lambda
+ ((and ('package ('name name) . rest) pkg)
+ `(define-public ,(string->symbol name)
+ ,pkg))
+ (_ #f))
+ (pypi-recursive-import name version))
+ ;; Single import
+ (let ((sexp (pypi->guix-package name #:version version)))
+ (unless sexp
+ (leave (G_ "failed to download meta-data for package '~a'~%")
+ name))
+ sexp))))
(()
(leave (G_ "too few arguments~%")))
((many ...)
diff --git a/guix/scripts/import/texlive.scm b/guix/scripts/import/texlive.scm
index 6f0818e274..4aeaa79eef 100644
--- a/guix/scripts/import/texlive.scm
+++ b/guix/scripts/import/texlive.scm
@@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2017 Ricardo Wurmus <rekado@elephly.net>
+;;; Copyright © 2017, 2021 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2021 Sarah Morgensen <iskarian@mgsn.dev>
;;;
;;; This file is part of GNU Guix.
@@ -43,8 +43,6 @@
(display (G_ "Usage: guix import texlive PACKAGE-NAME
Import and convert the Texlive package for PACKAGE-NAME.\n"))
(display (G_ "
- -a, --archive=ARCHIVE specify the archive repository"))
- (display (G_ "
-h, --help display this help and exit"))
(display (G_ "
-V, --version display version information and exit"))
@@ -60,10 +58,6 @@ Import and convert the Texlive package for PACKAGE-NAME.\n"))
(option '(#\V "version") #f #f
(lambda args
(show-version-and-exit "guix import texlive")))
- (option '(#\a "archive") #t #f
- (lambda (opt name arg result)
- (alist-cons 'component arg
- (alist-delete 'component result))))
%standard-import-options))
@@ -84,13 +78,11 @@ Import and convert the Texlive package for PACKAGE-NAME.\n"))
(_ #f))
(reverse opts))))
(match args
- ((package-name)
- (let ((sexp (texlive->guix-package package-name
- (or (assoc-ref opts 'component)
- "latex"))))
+ ((name)
+ (let ((sexp (texlive->guix-package name)))
(unless sexp
(leave (G_ "failed to download description for package '~a'~%")
- package-name))
+ name))
sexp))
(()
(leave (G_ "too few arguments~%")))
diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm
index 835078cb97..9ddf458c13 100644
--- a/guix/scripts/offload.scm
+++ b/guix/scripts/offload.scm
@@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2014-2021 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2017 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2020 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;; Copyright © 2020 Julien Lepiller <julien@lepiller.eu>
@@ -20,21 +20,26 @@
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (guix scripts offload)
- #:use-module (ssh key)
- #:use-module (ssh auth)
- #:use-module (ssh session)
- #:use-module (ssh channel)
- #:use-module (ssh popen)
- #:use-module (ssh version)
+ #:autoload (ssh key) (private-key-from-file
+ public-key-from-file)
+ #:autoload (ssh auth) (userauth-public-key!)
+ #:autoload (ssh session) (make-session
+ connect! get-error
+ disconnect! session-set!)
+ #:autoload (ssh version) (zlib-support?)
#:use-module (guix config)
#:use-module (guix records)
- #:use-module (guix ssh)
+ #:autoload (guix ssh) (authenticate-server*
+ connect-to-remote-daemon
+ send-files retrieve-files retrieve-files*
+ remote-inferior report-guile-error)
#:use-module (guix store)
- #:use-module (guix inferior)
- #:use-module (guix derivations)
- #:use-module ((guix serialization)
- #:select (nar-error? nar-error-file))
- #:use-module (guix nar)
+ #:autoload (guix inferior) (inferior-eval close-inferior inferior?)
+ #:autoload (guix derivations) (read-derivation-from-file
+ derivation-file-name
+ build-derivations)
+ #:autoload (guix serialization) (nar-error? nar-error-file)
+ #:autoload (guix nar) (restore-file-set)
#:use-module ((guix utils) #:select (%current-system))
#:use-module ((guix build syscalls)
#:select (fcntl-flock set-thread-name))
@@ -47,12 +52,10 @@
#:use-module (srfi srfi-26)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
- #:use-module (ice-9 popen)
#:use-module (ice-9 rdelim)
#:use-module (ice-9 match)
#:use-module (ice-9 regex)
#:use-module (ice-9 format)
- #:use-module (ice-9 binary-ports)
#:export (build-machine
build-machine?
build-machine-name
@@ -560,6 +563,15 @@ expired."
If TIMEOUT is #f, simply evaluate EXP..."
(call-with-timeout timeout drv (lambda () exp ...)))
+(define (check-ssh-zlib-support)
+ "Warn once if libssh lacks zlib support."
+ ;; We rely on protocol-level compression from libssh to optimize large data
+ ;; transfers. Warn if it's missing.
+ (unless (zlib-support?)
+ (warning (G_ "Guile-SSH lacks zlib support"))
+ (warning (G_ "data transfers will *not* be compressed!")))
+ (set! check-ssh-zlib-support (const #t)))
+
(define* (process-request wants-local? system drv features
#:key
print-build-trace? (max-silent-time 3600)
@@ -584,7 +596,9 @@ If TIMEOUT is #f, simply evaluate EXP..."
(lambda ()
;; Offload DRV to MACHINE.
(display "# accept\n")
- (let ((inputs (string-tokenize (read-line)))
+ (check-ssh-zlib-support)
+ (let ((drv (read-derivation-from-file drv))
+ (inputs (string-tokenize (read-line)))
(outputs (string-tokenize (read-line))))
;; Even if BUILD-TIMEOUT is honored by MACHINE, there can
;; be issues with the connection or deadlocks that could
@@ -782,12 +796,6 @@ machine."
(and=> (passwd:dir (getpw (getuid)))
(cut setenv "HOME" <>))
- ;; We rely on protocol-level compression from libssh to optimize large data
- ;; transfers. Warn if it's missing.
- (unless (zlib-support?)
- (warning (G_ "Guile-SSH lacks zlib support"))
- (warning (G_ "data transfers will *not* be compressed!")))
-
(match args
((system max-silent-time print-build-trace? build-timeout)
(let ((max-silent-time (string->number max-silent-time))
@@ -803,8 +811,7 @@ machine."
(with-error-handling
(process-request (equal? (match:substring match 1) "1")
(match:substring match 2) ; system
- (read-derivation-from-file
- (match:substring match 3))
+ (match:substring match 3)
(string-tokenize
(match:substring match 4) not-coma)
#:print-build-trace? print-build-trace?
diff --git a/guix/scripts/package.scm b/guix/scripts/package.scm
index a34ecdcb54..4b9c5f210d 100644
--- a/guix/scripts/package.scm
+++ b/guix/scripts/package.scm
@@ -68,6 +68,7 @@
guix-package
search-path-environment-variables
+ manifest-entry-version-prefix
transaction-upgrade-entry ;mostly for testing
@@ -327,31 +328,35 @@ Alternately, see @command{guix package --search-paths -p ~s}.")
;;; Export a manifest.
;;;
+(define (manifest-entry-version-prefix entry)
+ "Search among all the versions of ENTRY's package that are available, and
+return the shortest unambiguous version prefix for this package. If only one
+version of ENTRY's package is available, return the empty string."
+ (let ((name (manifest-entry-name entry)))
+ (match (map package-version (find-packages-by-name name))
+ ((_)
+ ;; A single version of NAME is available, so do not specify the
+ ;; version number, even if the available version doesn't match ENTRY.
+ "")
+ (versions
+ ;; If ENTRY uses the latest version, don't specify any version.
+ ;; Otherwise return the shortest unique version prefix. Note that
+ ;; this is based on the currently available packages, which could
+ ;; differ from the packages available in the revision that was used
+ ;; to build MANIFEST.
+ (let ((current (manifest-entry-version entry)))
+ (if (every (cut version>? current <>)
+ (delete current versions))
+ ""
+ (version-unique-prefix (manifest-entry-version entry)
+ versions)))))))
+
(define* (export-manifest manifest
#:optional (port (current-output-port)))
"Write to PORT a manifest corresponding to MANIFEST."
- (define (version-spec entry)
- (let ((name (manifest-entry-name entry)))
- (match (map package-version (find-packages-by-name name))
- ((_)
- ;; A single version of NAME is available, so do not specify the
- ;; version number, even if the available version doesn't match ENTRY.
- "")
- (versions
- ;; If ENTRY uses the latest version, don't specify any version.
- ;; Otherwise return the shortest unique version prefix. Note that
- ;; this is based on the currently available packages, which could
- ;; differ from the packages available in the revision that was used
- ;; to build MANIFEST.
- (let ((current (manifest-entry-version entry)))
- (if (every (cut version>? current <>)
- (delete current versions))
- ""
- (version-unique-prefix (manifest-entry-version entry)
- versions)))))))
-
(match (manifest->code manifest
- #:entry-package-version version-spec)
+ #:entry-package-version
+ manifest-entry-version-prefix)
(('begin exp ...)
(format port (G_ "\
;; This \"manifest\" file can be passed to 'guix package -m' to reproduce
diff --git a/guix/scripts/publish.scm b/guix/scripts/publish.scm
index 25846b7dc2..6e2b4368da 100644
--- a/guix/scripts/publish.scm
+++ b/guix/scripts/publish.scm
@@ -25,6 +25,7 @@
#:use-module ((system repl server) #:prefix repl:)
#:use-module (ice-9 binary-ports)
#:use-module (ice-9 format)
+ #:use-module (ice-9 iconv)
#:use-module (ice-9 match)
#:use-module (ice-9 poll)
#:use-module (ice-9 regex)
@@ -400,15 +401,18 @@ appropriate duration. NAR-PATH specifies the prefix for nar URLs."
(let ((store-path (hash-part->path store hash)))
(if (string-null? store-path)
(not-found request #:phrase "" #:ttl negative-ttl)
- (values `((content-type . (application/x-nix-narinfo))
+ (values `((content-type . (application/x-nix-narinfo
+ (charset . "UTF-8")))
+ (x-nar-path . ,nar-path)
+ (x-narinfo-compressions . ,compressions)
,@(if ttl
`((cache-control (max-age . ,ttl)))
'()))
- (cut display
- (narinfo-string store store-path
- #:nar-path nar-path
- #:compressions compressions)
- <>)))))
+ ;; Do not call narinfo-string directly here as it is an
+ ;; expensive call that could potentially block the main
+ ;; thread. Instead, create the narinfo string in the
+ ;; http-write procedure.
+ store-path))))
(define* (nar-cache-file directory item
#:key (compression %no-compression))
@@ -663,19 +667,38 @@ requested using POOL."
(link narinfo other)))
others))))))
+(define (compression->sexp compression)
+ "Return the SEXP representation of COMPRESSION."
+ (match compression
+ (($ <compression> type level)
+ `(compression ,type ,level))))
+
+(define (sexp->compression sexp)
+ "Turn the given SEXP into a <compression> record and return it."
+ (match sexp
+ (('compression type level)
+ (compression type level))))
+
;; XXX: Declare the 'X-Nar-Compression' HTTP header, which is in fact for
;; internal consumption: it allows us to pass the compression info to
;; 'http-write', as part of the workaround to <http://bugs.gnu.org/21093>.
(declare-header! "X-Nar-Compression"
(lambda (str)
- (match (call-with-input-string str read)
- (('compression type level)
- (compression type level))))
+ (sexp->compression
+ (call-with-input-string str read)))
compression?
(lambda (compression port)
- (match compression
- (($ <compression> type level)
- (write `(compression ,type ,level) port)))))
+ (write (compression->sexp compression) port)))
+
+;; This header is used to pass the supported compressions to http-write in
+;; order to format on-the-fly narinfo responses.
+(declare-header! "X-Narinfo-Compressions"
+ (lambda (str)
+ (map sexp->compression
+ (call-with-input-string str read)))
+ (cut every compression? <>)
+ (lambda (compressions port)
+ (write (map compression->sexp compressions) port)))
(define* (render-nar store request store-item
#:key (compression %no-compression))
@@ -830,7 +853,8 @@ example: \"/foo/bar\" yields '(\"foo\" \"bar\")."
"Return RESPONSE's headers minus 'Content-Length' and our internal headers."
(fold alist-delete
(response-headers response)
- '(content-length x-raw-file x-nar-compression)))
+ '(content-length x-raw-file x-nar-compression
+ x-narinfo-compressions x-nar-path)))
(define (sans-content-length response)
"Return RESPONSE without its 'content-length' header."
@@ -964,6 +988,38 @@ blocking."
(unless keep-alive?
(close-port client)))
(values))))))
+ (('application/x-nix-narinfo . _)
+ (let ((compressions (assoc-ref (response-headers response)
+ 'x-narinfo-compressions))
+ (nar-path (assoc-ref (response-headers response)
+ 'x-nar-path)))
+ (if nar-path
+ (begin
+ (when (keep-alive? response)
+ (keep-alive client))
+ (call-with-new-thread
+ (lambda ()
+ (set-thread-name "publish narinfo")
+ (let* ((narinfo
+ (with-store store
+ (narinfo-string store (utf8->string body)
+ #:nar-path nar-path
+ #:compressions compressions)))
+ (narinfo-bv (string->bytevector narinfo "UTF-8"))
+ (narinfo-length
+ (bytevector-length narinfo-bv))
+ (response (write-response
+ (with-content-length response
+ narinfo-length)
+ client))
+ (output (response-port response)))
+ (configure-socket client)
+ (put-bytevector output narinfo-bv)
+ (force-output output)
+ (unless (keep-alive? response)
+ (close-port output))
+ (values)))))
+ (%http-write server client response body))))
(_
(match (assoc-ref (response-headers response) 'x-raw-file)
((? string? file)
diff --git a/guix/scripts/refresh.scm b/guix/scripts/refresh.scm
index fb6c52a567..8806f0f740 100644
--- a/guix/scripts/refresh.scm
+++ b/guix/scripts/refresh.scm
@@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2021 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
;;; Copyright © 2014 Eric Bavier <bavier@member.fsf.org>
;;; Copyright © 2015 Alex Kost <alezost@gmail.com>
@@ -329,23 +329,39 @@ warn about packages that have no matching updater."
(package-version package) version)
(for-each
(lambda (change)
- (format (current-error-port)
- (match (list (upstream-input-change-action change)
- (upstream-input-change-type change))
- (('add 'regular)
- (G_ "~a: consider adding this input: ~a~%"))
- (('add 'native)
- (G_ "~a: consider adding this native input: ~a~%"))
- (('add 'propagated)
- (G_ "~a: consider adding this propagated input: ~a~%"))
- (('remove 'regular)
- (G_ "~a: consider removing this input: ~a~%"))
- (('remove 'native)
- (G_ "~a: consider removing this native input: ~a~%"))
- (('remove 'propagated)
- (G_ "~a: consider removing this propagated input: ~a~%")))
- (package-name package)
- (upstream-input-change-name change)))
+ (define field
+ (match (upstream-input-change-type change)
+ ('native 'native-inputs)
+ ('propagated 'propagated-inputs)
+ (_ 'inputs)))
+
+ (define name
+ (package-name package))
+ (define loc
+ (package-field-location package field))
+ (define change-name
+ (upstream-input-change-name change))
+
+ (match (list (upstream-input-change-action change)
+ (upstream-input-change-type change))
+ (('add 'regular)
+ (info loc (G_ "~a: consider adding this input: ~a~%")
+ name change-name))
+ (('add 'native)
+ (info loc (G_ "~a: consider adding this native input: ~a~%")
+ name change-name))
+ (('add 'propagated)
+ (info loc (G_ "~a: consider adding this propagated input: ~a~%")
+ name change-name))
+ (('remove 'regular)
+ (info loc (G_ "~a: consider removing this input: ~a~%")
+ name change-name))
+ (('remove 'native)
+ (info loc (G_ "~a: consider removing this native input: ~a~%")
+ name change-name))
+ (('remove 'propagated)
+ (info loc (G_ "~a: consider removing this propagated input: ~a~%")
+ name change-name))))
(upstream-source-input-changes source))
(let ((hash (call-with-input-file tarball
port-sha256)))
diff --git a/guix/scripts/shell.scm b/guix/scripts/shell.scm
new file mode 100644
index 0000000000..546639818f
--- /dev/null
+++ b/guix/scripts/shell.scm
@@ -0,0 +1,399 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 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 scripts shell)
+ #:use-module (guix ui)
+ #:use-module ((guix diagnostics) #:select (location))
+ #:use-module (guix scripts environment)
+ #:autoload (guix scripts build) (show-build-options-help)
+ #:autoload (guix transformations) (show-transformation-options-help)
+ #:use-module (guix scripts)
+ #:use-module (guix packages)
+ #:use-module (guix profiles)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
+ #:use-module (srfi srfi-37)
+ #:use-module (srfi srfi-71)
+ #:use-module (ice-9 match)
+ #:autoload (ice-9 rdelim) (read-line)
+ #:autoload (guix base32) (bytevector->base32-string)
+ #:autoload (rnrs bytevectors) (string->utf8)
+ #:autoload (guix utils) (config-directory cache-directory)
+ #:autoload (guix describe) (current-channels)
+ #:autoload (guix channels) (channel-commit)
+ #:autoload (gcrypt hash) (sha256)
+ #:use-module ((guix build utils) #:select (mkdir-p))
+ #:use-module (guix cache)
+ #:use-module ((ice-9 ftw) #:select (scandir))
+ #:export (guix-shell))
+
+(define (show-help)
+ (display (G_ "Usage: guix shell [OPTION] PACKAGES... [-- COMMAND...]
+Build an environment that includes PACKAGES and execute COMMAND or an
+interactive shell in that environment.\n"))
+ (newline)
+
+ ;; These two options differ from 'guix environment'.
+ (display (G_ "
+ -D, --development include the development inputs of the next package"))
+ (display (G_ "
+ -f, --file=FILE add to the environment the package FILE evaluates to"))
+ (display (G_ "
+ -q inhibit loading of 'guix.scm' and 'manifest.scm'"))
+ (display (G_ "
+ --rebuild-cache rebuild cached environment, if any"))
+
+ (show-environment-options-help)
+ (newline)
+ (show-build-options-help)
+ (newline)
+ (show-transformation-options-help)
+ (newline)
+ (display (G_ "
+ -h, --help display this help and exit"))
+ (display (G_ "
+ -V, --version display version information and exit"))
+ (newline)
+ (show-bug-report-information))
+
+(define (tag-package-arg opts arg)
+ "Return a two-element list with the form (TAG ARG) that tags ARG with either
+'ad-hoc' in OPTS has the 'ad-hoc?' key set to #t, or 'inputs' otherwise."
+ (if (assoc-ref opts 'ad-hoc?)
+ `(ad-hoc-package ,arg)
+ `(package ,arg)))
+
+(define (ensure-ad-hoc alist)
+ (if (assq-ref alist 'ad-hoc?)
+ alist
+ `((ad-hoc? . #t) ,@alist)))
+
+(define (wrapped-option opt)
+ "Wrap OPT, a SRFI-37 option, such that its processor always adds the
+'ad-hoc?' flag to the resulting alist."
+ (option (option-names opt)
+ (option-required-arg? opt)
+ (option-optional-arg? opt)
+ (compose ensure-ad-hoc (option-processor opt))))
+
+(define %options
+ ;; Specification of the command-line options.
+ (let ((to-remove '("ad-hoc" "inherit" "load" "help" "version")))
+ (append
+ (list (option '(#\h "help") #f #f
+ (lambda args
+ (show-help)
+ (exit 0)))
+ (option '(#\V "version") #f #f
+ (lambda args
+ (show-version-and-exit "guix shell")))
+
+ (option '(#\D "development") #f #f
+ (lambda (opt name arg result)
+ ;; Temporarily remove the 'ad-hoc?' flag from result.
+ ;; The next option will put it back thanks to
+ ;; 'wrapped-option'.
+ (alist-delete 'ad-hoc? result)))
+
+ ;; For consistency with 'guix package', support '-f' rather than
+ ;; '-l' like 'guix environment' does.
+ (option '(#\f "file") #t #f
+ (lambda (opt name arg result)
+ (alist-cons 'load (tag-package-arg result arg)
+ (ensure-ad-hoc result))))
+ (option '(#\q) #f #f
+ (lambda (opt name arg result)
+ (alist-cons 'explicit-loading? #t result)))
+ (option '("rebuild-cache") #f #f
+ (lambda (opt name arg result)
+ (alist-cons 'rebuild-cache? #t result))))
+ (filter-map (lambda (opt)
+ (and (not (any (lambda (name)
+ (member name to-remove))
+ (option-names opt)))
+ (wrapped-option opt)))
+ %environment-options))))
+
+(define %default-options
+ `((ad-hoc? . #t) ;always true
+ ,@%environment-default-options))
+
+(define (parse-args args)
+ "Parse the list of command line arguments ARGS."
+ (define (handle-argument arg result)
+ (alist-cons 'package (tag-package-arg result arg)
+ (ensure-ad-hoc result)))
+
+ ;; The '--' token is used to separate the command to run from the rest of
+ ;; the operands.
+ (let ((args command (break (cut string=? "--" <>) args)))
+ (let ((opts (parse-command-line args %options (list %default-options)
+ #:argument-handler handle-argument)))
+ (options-with-caching
+ (auto-detect-manifest
+ (match command
+ (() opts)
+ (("--") opts)
+ (("--" command ...) (alist-cons 'exec command opts))))))))
+
+(define (find-file-in-parent-directories candidates)
+ "Find one of CANDIDATES in the current directory or one of its ancestors."
+ (define start (getcwd))
+ (define device (stat:dev (stat start)))
+
+ (let loop ((directory start))
+ (let ((stat (stat directory)))
+ (and (= (stat:uid stat) (getuid))
+ (= (stat:dev stat) device)
+ (or (any (lambda (candidate)
+ (let ((candidate (string-append directory "/" candidate)))
+ (and (file-exists? candidate) candidate)))
+ candidates)
+ (and (not (string=? directory "/"))
+ (loop (dirname directory)))))))) ;lexical ".." resolution
+
+(define (authorized-directory-file)
+ "Return the name of the file listing directories for which 'guix shell' may
+automatically load 'guix.scm' or 'manifest.scm' files."
+ (string-append (config-directory) "/shell-authorized-directories"))
+
+(define (authorized-shell-directory? directory)
+ "Return true if DIRECTORY is among the authorized directories for automatic
+loading. The list of authorized directories is read from
+'authorized-directory-file'; each line must be either: an absolute file name,
+a hash-prefixed comment, or a blank line."
+ (catch 'system-error
+ (lambda ()
+ (call-with-input-file (authorized-directory-file)
+ (lambda (port)
+ (let loop ()
+ (match (read-line port)
+ ((? eof-object?) #f)
+ ((= string-trim line)
+ (cond ((string-prefix? "#" line) ;comment
+ (loop))
+ ((string-prefix? "/" line) ;absolute file name
+ (or (string=? line directory)
+ (loop)))
+ ((string-null? (string-trim-right line)) ;blank line
+ (loop))
+ (else ;bogus line
+ (let ((loc (location (port-filename port)
+ (port-line port)
+ (port-column port))))
+ (warning loc (G_ "ignoring invalid file name: '~a'~%")
+ line))))))))))
+ (const #f)))
+
+(define (options-with-caching opts)
+ "If OPTS contains exactly one 'load' or one 'manifest' key, automatically
+add a 'profile' key (when a profile for that file is already in cache) or a
+'gc-root' key (to add the profile to cache)."
+ (define (single-file-for-caching opts)
+ (let loop ((opts opts)
+ (file #f))
+ (match opts
+ (() file)
+ ((('package . _) . _) #f)
+ ((('load . ('package candidate)) . rest)
+ (and (not file) (loop rest candidate)))
+ ((('manifest . candidate) . rest)
+ (and (not file) (loop rest candidate)))
+ ((('expression . _) . _) #f)
+ ((_ . rest) (loop rest file)))))
+
+ ;; Check whether there's a single 'load' or 'manifest' option. When that is
+ ;; the case, arrange to automatically cache the resulting profile.
+ (match (single-file-for-caching opts)
+ (#f opts)
+ (file
+ (let* ((root (profile-cached-gc-root file))
+ (stat (and root (false-if-exception (lstat root)))))
+ (if (and (not (assoc-ref opts 'rebuild-cache?))
+ stat
+ (<= (stat:mtime ((@ (guile) stat) file))
+ (stat:mtime stat)))
+ (let ((now (current-time)))
+ ;; Update the atime on ROOT to reflect usage.
+ (utime root
+ now (stat:mtime stat) 0 (stat:mtimensec stat)
+ AT_SYMLINK_NOFOLLOW)
+ (alist-cons 'profile root
+ (remove (match-lambda
+ (('load . _) #t)
+ (('manifest . _) #t)
+ (_ #f))
+ opts))) ;load right away
+ (if (and root (not (assq-ref opts 'gc-root)))
+ (begin
+ (if stat
+ (delete-file root)
+ (mkdir-p (dirname root)))
+ (alist-cons 'gc-root root opts))
+ opts))))))
+
+(define (auto-detect-manifest opts)
+ "If OPTS do not specify packages or a manifest, load a \"guix.scm\" or
+\"manifest.scm\" file from the current directory or one of its ancestors.
+Return the modified OPTS."
+ (define (options-contain-payload? opts)
+ (match opts
+ (() #f)
+ ((('package . _) . _) #t)
+ ((('load . _) . _) #t)
+ ((('manifest . _) . _) #t)
+ ((('expression . _) . _) #t)
+ ((_ . rest) (options-contain-payload? rest))))
+
+ (define interactive?
+ (not (assoc-ref opts 'exec)))
+
+ (define disallow-implicit-load?
+ (assoc-ref opts 'explicit-loading?))
+
+ (if (or (not interactive?)
+ disallow-implicit-load?
+ (options-contain-payload? opts))
+ opts
+ (match (find-file-in-parent-directories '("manifest.scm" "guix.scm"))
+ (#f
+ (warning (G_ "no packages specified; creating an empty environment~%"))
+ opts)
+ (file
+ (if (authorized-shell-directory? (dirname file))
+ (begin
+ (info (G_ "loading environment from '~a'...~%") file)
+ (match (basename file)
+ ("guix.scm" (alist-cons 'load `(package ,file) opts))
+ ("manifest.scm" (alist-cons 'manifest file opts))))
+ (begin
+ (report-error
+ (G_ "not loading '~a' because not authorized to do so~%")
+ file)
+ (display-hint (format #f (G_ "To allow automatic loading of
+@file{~a} when running @command{guix shell}, you must explicitly authorize its
+directory, like so:
+
+@example
+echo ~a >> ~a
+@end example\n")
+ file
+ (dirname file)
+ (authorized-directory-file)))
+ (exit 1)))))))
+
+
+;;;
+;;; Profile cache.
+;;;
+
+(define %profile-cache-directory
+ ;; Directory where profiles created by 'guix shell' alone (without extra
+ ;; options) are cached.
+ (make-parameter (string-append (cache-directory #:ensure? #f)
+ "/profiles")))
+
+(define (profile-cache-key file)
+ "Return the cache key for the profile corresponding to FILE, a 'guix.scm' or
+'manifest.scm' file, or #f if we lack channel information."
+ (match (current-channels)
+ (() #f)
+ (((= channel-commit commits) ...)
+ (let ((stat (stat file)))
+ (bytevector->base32-string
+ ;; Since FILE is not canonicalized, only include the device/inode
+ ;; numbers. XXX: In some rare cases involving Btrfs and NFS, this can
+ ;; be insufficient: <https://lwn.net/Articles/866582/>.
+ (sha256 (string->utf8
+ (string-append (string-join commits) ":"
+ (number->string (stat:dev stat)) ":"
+ (number->string (stat:ino stat))))))))))
+
+(define (profile-cached-gc-root file)
+ "Return the cached GC root for FILE, a 'guix.scm' or 'manifest.scm' file, or
+#f if we lack information to cache it."
+ (match (profile-cache-key file)
+ (#f #f)
+ (key (string-append (%profile-cache-directory) "/" key))))
+
+
+;;;
+;;; One-time hints.
+;;;
+
+(define (hint-directory)
+ "Return the directory name where previously given hints are recorded."
+ (string-append (cache-directory #:ensure? #f) "/hints"))
+
+(define (hint-file hint)
+ "Return the name of the file that marks HINT as already printed."
+ (string-append (hint-directory) "/" (symbol->string hint)))
+
+(define (record-hint hint)
+ "Mark HINT as already given."
+ (let ((file (hint-file hint)))
+ (mkdir-p (dirname file))
+ (close-fdes (open-fdes file (logior O_CREAT O_WRONLY)))))
+
+(define (hint-given? hint)
+ "Return true if HINT was already given."
+ (file-exists? (hint-file hint)))
+
+
+(define-command (guix-shell . args)
+ (category development)
+ (synopsis "spawn one-off software environments")
+
+ (define (cache-entries directory)
+ (filter-map (match-lambda
+ ((or "." "..") #f)
+ (file (string-append directory "/" file)))
+ (or (scandir directory) '())))
+
+ (define* (entry-expiration file)
+ ;; Return the time at which FILE, a cached profile, is considered expired.
+ (match (false-if-exception (lstat file))
+ (#f 0) ;FILE may have been deleted in the meantime
+ (st (+ (stat:atime st) (* 60 60 24 7)))))
+
+ (define opts
+ (parse-args args))
+
+ (define interactive?
+ (not (assoc-ref opts 'exec)))
+
+ (if (assoc-ref opts 'check?)
+ (record-hint 'shell-check)
+ (when (and interactive?
+ (not (hint-given? 'shell-check))
+ (not (assoc-ref opts 'container?))
+ (not (assoc-ref opts 'search-paths)))
+ (display-hint (G_ "Consider passing the @option{--check} option once
+to make sure your shell does not clobber environment variables."))) )
+
+ ;; Clean the cache in EXIT-HOOK so that (1) it happens after potential use
+ ;; of cached profiles, and (2) cleanup actually happens, even when
+ ;; 'guix-environment*' calls 'exit'.
+ (add-hook! exit-hook
+ (lambda _
+ (maybe-remove-expired-cache-entries
+ (%profile-cache-directory)
+ cache-entries
+ #:entry-expiration entry-expiration)))
+
+ (guix-environment* opts))
diff --git a/guix/scripts/style.scm b/guix/scripts/style.scm
index 3c100197a7..3b246e9c66 100644
--- a/guix/scripts/style.scm
+++ b/guix/scripts/style.scm
@@ -382,13 +382,33 @@ bailing out~%")
package)
str)))
+(define (edit-expression/dry-run properties rewrite-string)
+ "Like 'edit-expression' but display what would be edited without actually
+doing it."
+ (edit-expression properties
+ (lambda (str)
+ (unless (string=? (rewrite-string str) str)
+ (info (source-properties->location properties)
+ (G_ "would be edited~%")))
+ str)))
+
+(define (absolute-location loc)
+ "Replace the file name in LOC by an absolute location."
+ (location (if (string-prefix? "/" (location-file loc))
+ (location-file loc)
+ (search-path %load-path (location-file loc)))
+ (location-line loc)
+ (location-column loc)))
+
(define* (simplify-package-inputs package
- #:key (policy 'silent))
+ #:key (policy 'silent)
+ (edit-expression edit-expression))
"Edit the source code of PACKAGE to simplify its inputs field if needed.
POLICY is a symbol that defines whether to simplify inputs; it can one of
'silent (change only if the resulting derivation is the same), 'safe (change
only if semantics are known to be unaffected), and 'always (fearlessly
-simplify inputs!)."
+simplify inputs!). Call EDIT-EXPRESSION to actually edit the source of
+PACKAGE."
(for-each (lambda (field-name field)
(match (field package)
(()
@@ -401,7 +421,7 @@ simplify inputs!)."
#f)
(location
(edit-expression
- (location->source-properties location)
+ (location->source-properties (absolute-location location))
(lambda (str)
(define matches?
(match policy
@@ -419,7 +439,7 @@ simplify inputs!)."
label-matches?))
('always
;; Assume it's gonna be alright.
- (const #f))))
+ (const #t))))
(simplify-inputs location
(package-name package)
@@ -449,6 +469,9 @@ simplify inputs!)."
(member "load-path" (option-names option)))
%standard-build-options)
+ (option '(#\n "dry-run") #f #f
+ (lambda (opt name arg result)
+ (alist-cons 'dry-run? #t result)))
(option '(#\e "expression") #t #f
(lambda (opt name arg result)
(alist-cons 'expression arg result)))
@@ -473,6 +496,8 @@ simplify inputs!)."
(display (G_ "Usage: guix style [OPTION]... [PACKAGE]...
Update package definitions to the latest style.\n"))
(display (G_ "
+ -n, --dry-run display files that would be edited but do nothing"))
+ (display (G_ "
-L, --load-path=DIR prepend DIR to the package module search path"))
(display (G_ "
-e, --expression=EXPR consider the package EXPR evaluates to"))
@@ -514,14 +539,19 @@ Update package definitions to the latest style.\n"))
(read/eval str))
(_ #f))
opts))
+ (edit (if (assoc-ref opts 'dry-run?)
+ edit-expression/dry-run
+ edit-expression))
(policy (assoc-ref opts 'input-simplification-policy)))
- (for-each (lambda (package)
- (simplify-package-inputs package #:policy policy))
- ;; Sort package by source code location so that we start editing
- ;; files from the bottom and going upward. That way, the
- ;; 'location' field of <package> records is not invalidated as
- ;; we modify files.
- (sort (if (null? packages)
- (fold-packages cons '() #:select? (const #t))
- packages)
- (negate package-location<?)))))
+ (with-error-handling
+ (for-each (lambda (package)
+ (simplify-package-inputs package #:policy policy
+ #:edit-expression edit))
+ ;; Sort package by source code location so that we start editing
+ ;; files from the bottom and going upward. That way, the
+ ;; 'location' field of <package> records is not invalidated as
+ ;; we modify files.
+ (sort (if (null? packages)
+ (fold-packages cons '() #:select? (const #t))
+ packages)
+ (negate package-location<?))))))
diff --git a/guix/scripts/system.scm b/guix/scripts/system.scm
index 65eb98e4b2..1db788a534 100644
--- a/guix/scripts/system.scm
+++ b/guix/scripts/system.scm
@@ -64,6 +64,7 @@
(device-module-aliases matching-modules)
#:use-module (gnu system linux-initrd)
#:use-module (gnu image)
+ #:use-module (gnu platform)
#:use-module (gnu system)
#:use-module (gnu bootloader)
#:use-module (gnu system file-systems)
@@ -253,7 +254,7 @@ the ownership of '~a' may be incorrect!~%")
(install-bootloader local-eval bootloader bootcfg
#:target target)
(return
- (info (G_ "bootloader successfully installed on '~a'~%")
+ (info (G_ "bootloader successfully installed on~{ ~a~}~%")
(bootloader-configuration-targets bootloader))))))))
@@ -688,6 +689,7 @@ checking this by themselves in their 'check' procedure."
(define* (system-derivation-for-action image action
#:key
full-boot?
+ (graphic? #t)
container-shared-network?
mappings)
"Return as a monadic value the derivation for IMAGE according to ACTION."
@@ -705,6 +707,7 @@ checking this by themselves in their 'check' procedure."
((vm)
(system-qemu-image/shared-store-script os
#:full-boot? full-boot?
+ #:graphic? graphic?
#:disk-image-size
(if full-boot?
image-size
@@ -771,6 +774,7 @@ and TARGET arguments."
dry-run? derivations-only?
use-substitutes? target
full-boot?
+ (graphic? #t)
container-shared-network?
(mappings '())
(gc-root #f))
@@ -824,6 +828,7 @@ static checks."
(mlet* %store-monad
((sys (system-derivation-for-action image action
#:full-boot? full-boot?
+ #:graphic? graphic?
#:container-shared-network? container-shared-network?
#:mappings mappings))
@@ -1012,6 +1017,8 @@ Some ACTIONS support additional ARGS.\n"))
(display (G_ "
--full-boot for 'vm', make a full boot sequence"))
(display (G_ "
+ --no-graphic for 'vm', use the tty that we are started in for IO"))
+ (display (G_ "
--skip-checks skip file system and initrd module safety checks"))
(display (G_ "
--target=TRIPLET cross-build for TRIPLET--e.g., \"armel-linux-gnu\""))
@@ -1080,6 +1087,9 @@ Some ACTIONS support additional ARGS.\n"))
(option '("full-boot") #f #f
(lambda (opt name arg result)
(alist-cons 'full-boot? #t result)))
+ (option '("no-graphic") #f #f
+ (lambda (opt name arg result)
+ (alist-cons 'no-graphic? #t result)))
(option '("save-provenance") #f #f
(lambda (opt name arg result)
(alist-cons 'save-provenance? #t result)))
@@ -1212,13 +1222,11 @@ resulting from command-line parsing."
(base-image (if (operating-system? obj)
(os->image obj
#:type image-type)
- obj))
- (base-target (image-target base-image)))
+ obj)))
(image
(inherit (if label
(image-with-label base-image label)
base-image))
- (target (or base-target target))
(size image-size)
(volatile-root? volatile?))))
(os (image-operating-system image))
@@ -1267,6 +1275,7 @@ resulting from command-line parsing."
#:validate-reconfigure
(assoc-ref opts 'validate-reconfigure)
#:full-boot? (assoc-ref opts 'full-boot?)
+ #:graphic? (not (assoc-ref opts 'no-graphic?))
#:container-shared-network?
(assoc-ref opts 'container-shared-network?)
#:mappings (filter-map (match-lambda
diff --git a/guix/self.scm b/guix/self.scm
index 79d93357a2..bd9a71de45 100644
--- a/guix/self.scm
+++ b/guix/self.scm
@@ -316,81 +316,23 @@ the result to OUTPUT."
chr))
str))
- (define xref-regexp
- ;; Texinfo cross-reference regexp.
- (make-regexp "@(px|x)?ref\\{([^,}]+)"))
-
- (define (translate-cross-references texi translations)
- ;; Translate the cross-references that appear in TEXI, a Texinfo
- ;; file, using the msgid/msgstr pairs from TRANSLATIONS.
- (define content
- (call-with-input-file texi get-string-all))
-
- (define matches
- (list-matches xref-regexp content))
-
- (define translation-map
- (fold (match-lambda*
- (((msgid . str) result)
- (vhash-cons msgid str result)))
- vlist-null
- translations))
-
- (define translated
- ;; Iterate over MATCHES and replace cross-references with their
- ;; translation found in TRANSLATION-MAP. (We can't use
- ;; 'substitute*' because matches can span multiple lines.)
- (let loop ((matches matches)
- (offset 0)
- (result '()))
- (match matches
- (()
- (string-concatenate-reverse
- (cons (string-drop content offset) result)))
- ((head . tail)
- (let ((prefix (match:substring head 1))
- (ref (canonicalize-whitespace (match:substring head 2))))
- (define translated
- (string-append "@" (or prefix "")
- "ref{"
- (match (vhash-assoc ref translation-map)
- (#f ref)
- ((_ . str) str))))
-
- (loop tail
- (match:end head)
- (append (list translated
- (string-take
- (string-drop content offset)
- (- (match:start head) offset)))
- result)))))))
-
- (format (current-error-port)
- "translated ~a cross-references in '~a'~%"
- (length matches) texi)
- (call-with-output-file texi
- (lambda (port)
- (display translated port))))
-
(define* (translate-texi prefix po lang
#:key (extras '()))
"Translate the manual for one language LANG using the PO file.
PREFIX must be the prefix of the manual, 'guix' or 'guix-cookbook'. EXTRAS is
a list of extra files, such as '(\"contributing\")."
- (let ((translations (call-with-input-file po read-po-file)))
- (for-each (lambda (file)
- (translate-tmp-texi po (string-append file ".texi")
- (string-append file "." lang
- ".texi.tmp")))
- (cons prefix extras))
-
- (for-each (lambda (file)
- (let* ((texi (string-append file "." lang ".texi"))
- (tmp (string-append texi ".tmp")))
- (copy-file tmp texi)
- (translate-cross-references texi
- translations)))
- (cons prefix extras))))
+ (for-each (lambda (file)
+ (translate-tmp-texi po (string-append file ".texi")
+ (string-append file "." lang
+ ".texi.tmp")))
+ (cons prefix extras))
+
+ (for-each (lambda (file)
+ (let* ((texi (string-append file "." lang ".texi"))
+ (tmp (string-append texi ".tmp")))
+ (copy-file tmp texi)
+ (translate-cross-references texi po)))
+ (cons prefix extras)))
(define (available-translations directory domain)
;; Return the list of available translations under DIRECTORY for
@@ -958,13 +900,42 @@ itself."
#:guile-for-build
guile-for-build))
+ (define *home-modules*
+ (scheme-node "guix-home"
+ `((gnu home)
+ (gnu home services)
+ ,@(scheme-modules* source "gnu/home/services"))
+ (list *core-package-modules* *package-modules*
+ *extra-modules* *core-modules* *system-modules*)
+ #:extensions dependencies
+ #:guile-for-build guile-for-build))
+
+ (define *core-cli-modules*
+ ;; Core command-line interface modules that do not depend on (gnu system
+ ;; …) or (gnu home …), and not even on *PACKAGE-MODULES*.
+ (scheme-node "guix-cli-core"
+ (remove (match-lambda
+ (('guix 'scripts 'system . _) #t)
+ (('guix 'scripts 'environment) #t)
+ (('guix 'scripts 'container . _) #t)
+ (('guix 'scripts 'deploy) #t)
+ (('guix 'scripts 'home . _) #t)
+ (('guix 'scripts 'import . _) #t)
+ (('guix 'pack) #t)
+ (_ #f))
+ (scheme-modules* source "guix/scripts"))
+ (list *core-modules* *extra-modules*
+ *core-package-modules*)
+ #:extensions dependencies
+ #:guile-for-build guile-for-build))
+
(define *cli-modules*
(scheme-node "guix-cli"
(append (scheme-modules* source "/guix/scripts")
`((gnu ci)))
(list *core-modules* *extra-modules*
*core-package-modules* *package-modules*
- *system-modules*)
+ *core-cli-modules* *system-modules* *home-modules*)
#:extensions dependencies
#:guile-for-build guile-for-build))
@@ -1010,8 +981,10 @@ itself."
;; comes with *CORE-MODULES*.
(list *config*
*cli-modules*
+ *core-cli-modules*
*system-test-modules*
*system-modules*
+ *home-modules*
*package-modules*
*core-package-modules*
*extra-modules*
diff --git a/guix/status.scm b/guix/status.scm
index f351a56d92..eefe18365f 100644
--- a/guix/status.scm
+++ b/guix/status.scm
@@ -381,6 +381,8 @@ the current build phase."
(G_ "building CA certificate bundle..."))
('emacs-subdirs
(G_ "listing Emacs sub-directories..."))
+ ('gdk-pixbuf-loaders-cache-file
+ (G_ "generating GdkPixbuf loaders cache..."))
('glib-schemas
(G_ "generating GLib schema cache..."))
('gtk-icon-themes
diff --git a/guix/store.scm b/guix/store.scm
index 89a719bcfc..a93e9596d9 100644
--- a/guix/store.scm
+++ b/guix/store.scm
@@ -1349,11 +1349,18 @@ on the build output of a previous derivation."
(things unresolved-things)
(continuation unresolved-continuation))
-(define (build-accumulator continue store things mode)
- "This build handler accumulates THINGS and returns an <unresolved> object."
- (if (= mode (build-mode normal))
- (unresolved things continue)
- (continue #t)))
+(define (build-accumulator expected-store)
+ "Return a build handler that accumulates THINGS and returns an <unresolved>
+object, only for build requests on EXPECTED-STORE."
+ (lambda (continue store things mode)
+ ;; Note: Do not compare STORE and EXPECTED-STORE with 'eq?' because
+ ;; 'cache-object-mapping' and similar functional "setters" change the
+ ;; store's object identity.
+ (if (and (eq? (store-connection-socket store)
+ (store-connection-socket expected-store))
+ (= mode (build-mode normal)))
+ (unresolved things continue)
+ (continue #t))))
(define* (map/accumulate-builds store proc lst
#:key (cutoff 30))
@@ -1366,13 +1373,16 @@ CUTOFF is the threshold above which we stop accumulating unresolved nodes."
;; stumbling upon the same .drv build requests with many incoming edges.
;; See <https://bugs.gnu.org/49439>.
+ (define accumulator
+ (build-accumulator store))
+
(define-values (result rest)
(let loop ((lst lst)
(result '())
(unresolved 0))
(match lst
((head . tail)
- (match (with-build-handler build-accumulator
+ (match (with-build-handler accumulator
(proc head))
((? unresolved? obj)
(if (>= unresolved cutoff)
diff --git a/guix/store/deduplication.scm b/guix/store/deduplication.scm
index cd9660174c..370df4a74c 100644
--- a/guix/store/deduplication.scm
+++ b/guix/store/deduplication.scm
@@ -1,6 +1,6 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017 Caleb Ristvedt <caleb.ristvedt@cune.org>
-;;; Copyright © 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2018-2021 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -22,12 +22,13 @@
(define-module (guix store deduplication)
#:use-module (gcrypt hash)
- #:use-module (guix build utils)
+ #:use-module ((guix build utils) #:hide (dump-port))
#:use-module (guix build syscalls)
#:use-module (guix base32)
#:use-module (srfi srfi-11)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
+ #:use-module (rnrs bytevectors)
#:use-module (rnrs io ports)
#:use-module (ice-9 ftw)
#:use-module (ice-9 match)
@@ -37,6 +38,31 @@
dump-file/deduplicate
copy-file/deduplicate))
+;; TODO: Remove once 'dump-port' in (guix build utils) has an optional 'len'
+;; parameter.
+(define* (dump-port in out
+ #:optional len
+ #:key (buffer-size 16384))
+ "Read LEN bytes from IN (or as much as possible if LEN is #f) and write it
+to OUT, using chunks of BUFFER-SIZE bytes."
+ (define buffer
+ (make-bytevector buffer-size))
+
+ (let loop ((total 0)
+ (bytes (get-bytevector-n! in buffer 0
+ (if len
+ (min len buffer-size)
+ buffer-size))))
+ (or (eof-object? bytes)
+ (and len (= total len))
+ (let ((total (+ total bytes)))
+ (put-bytevector out buffer 0 bytes)
+ (loop total
+ (get-bytevector-n! in buffer 0
+ (if len
+ (min (- len total) buffer-size)
+ buffer-size)))))))
+
(define (nar-sha256 file)
"Gives the sha256 hash of a file and the size of the file in nar form."
(let-values (((port get-hash) (open-sha256-port)))
@@ -127,11 +153,27 @@ Note: TARGET, TO-REPLACE, and SWAP-DIRECTORY must be on the same file system."
(unless (= EMLINK (system-error-errno args))
(apply throw args)))))))
+(define %deduplication-minimum-size
+ ;; Size below which files are not deduplicated. This avoids adding too many
+ ;; entries to '.links', which would slow down 'removeUnusedLinks' while
+ ;; saving little space. Keep in sync with optimize-store.cc.
+ 8192)
+
(define* (deduplicate path hash #:key (store (%store-directory)))
"Check if a store item with sha256 hash HASH already exists. If so,
replace PATH with a hardlink to the already-existing one. If not, register
PATH so that future duplicates can hardlink to it. PATH is assumed to be
under STORE."
+ ;; Lightweight promises.
+ (define-syntax-rule (delay exp)
+ (let ((value #f))
+ (lambda ()
+ (unless value
+ (set! value exp))
+ value)))
+ (define-syntax-rule (force promise)
+ (promise))
+
(define links-directory
(string-append store "/.links"))
@@ -144,13 +186,18 @@ under STORE."
((file . properties)
(unless (member file '("." ".."))
(let* ((file (string-append path "/" file))
+ (st (delay (lstat file)))
(type (match (assoc-ref properties 'type)
((or 'unknown #f)
- (stat:type (lstat file)))
+ (stat:type (force st)))
(type type))))
- (loop file type
- (and (not (eq? 'directory type))
- (nar-sha256 file)))))))
+ (when (or (eq? 'directory type)
+ (and (eq? 'regular type)
+ (>= (stat:size (force st))
+ %deduplication-minimum-size)))
+ (loop file type
+ (and (not (eq? 'directory type))
+ (nar-sha256 file))))))))
(scandir* path))
(let ((link-file (string-append links-directory "/"
(bytevector->nix-base32-string hash))))
@@ -222,9 +269,9 @@ OUTPUT as it goes."
This procedure is suitable as a #:dump-file argument to 'restore-file'. When
used that way, it deduplicates files on the fly as they are restored, thereby
-removing the need to a deduplication pass that would re-read all the files
+removing the need for a deduplication pass that would re-read all the files
down the road."
- (define hash
+ (define (dump-and-compute-hash)
(call-with-output-file file
(lambda (output)
(let-values (((hash-port get-hash)
@@ -236,7 +283,11 @@ down the road."
(close-port hash-port)
(get-hash)))))
- (deduplicate file hash #:store store))
+ (if (>= size %deduplication-minimum-size)
+ (deduplicate file (dump-and-compute-hash) #:store store)
+ (call-with-output-file file
+ (lambda (output)
+ (dump-port input output size)))))
(define* (copy-file/deduplicate source target
#:key (store (%store-directory)))
diff --git a/guix/substitutes.scm b/guix/substitutes.scm
index a5c554acff..9014cf61ec 100644
--- a/guix/substitutes.scm
+++ b/guix/substitutes.scm
@@ -156,7 +156,11 @@ indicates that PATH is unavailable at CACHE-URL."
(define (narinfo-request cache-url path)
"Return an HTTP request for the narinfo of PATH at CACHE-URL."
- (let* ((base (string->uri cache-url))
+ ;; Ensure BASE has a trailing slash so that REF is correct regardless of
+ ;; whether the user-provided CACHE-URL has a trailing slash.
+ (let* ((base (string->uri (if (string-suffix? "/" cache-url)
+ cache-url
+ (string-append cache-url "/"))))
(ref (build-relative-ref
#:path (string-append (store-path-hash-part path) ".narinfo")))
(url (resolve-uri-reference ref base))
diff --git a/guix/svn-download.scm b/guix/svn-download.scm
index 28ad49977b..55ce0d7351 100644
--- a/guix/svn-download.scm
+++ b/guix/svn-download.scm
@@ -1,7 +1,7 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2014, 2015, 2016, 2019, 2021 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2014 Sree Harsha Totakura <sreeharsha@totakura.in>
-;;; Copyright © 2017, 2019 Ricardo Wurmus <rekado@elephly.net>
+;;; Copyright © 2017, 2019, 2021 Ricardo Wurmus <rekado@elephly.net>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -26,7 +26,9 @@
#:use-module (guix packages)
#:use-module (guix utils)
#:use-module ((guix build svn) #:prefix build:)
+ #:use-module ((guix build utils) #:select (mkdir-p))
#:use-module (ice-9 match)
+ #:use-module (srfi srfi-1)
#:export (svn-reference
svn-reference?
svn-reference-url
@@ -41,7 +43,8 @@
svn-multi-reference-revision
svn-multi-reference-locations
svn-multi-reference-recursive?
- svn-multi-fetch))
+ svn-multi-fetch
+ download-multi-svn-to-store))
;;; Commentary:
;;;
@@ -166,4 +169,28 @@ reports to LOG."
(add-to-store store name #t "sha256"
(string-append temp "/svn")))))))
+(define* (download-multi-svn-to-store store ref
+ #:optional (name (basename (svn-multi-reference-url ref)))
+ #:key (log (current-error-port)))
+ "Download from REF, a <svn-multi-reference> object to STORE. Write progress
+reports to LOG."
+ (call-with-temporary-directory
+ (lambda (temp)
+ (and (every (lambda (location)
+ (let ((dir (string-append temp "/" (dirname location))))
+ (mkdir-p dir))
+ (parameterize ((current-output-port log))
+ (build:svn-fetch (string-append (svn-multi-reference-url ref)
+ "/" location)
+ (svn-multi-reference-revision ref)
+ (if (string-suffix? "/" location)
+ (string-append temp "/" location)
+ (string-append temp "/" (dirname location)))
+ #:recursive?
+ (svn-multi-reference-recursive? ref)
+ #:user-name (svn-multi-reference-user-name ref)
+ #:password (svn-multi-reference-password ref))))
+ (svn-multi-reference-locations ref))
+ (add-to-store store name #t "sha256" temp)))))
+
;;; svn-download.scm ends here
diff --git a/guix/swh.scm b/guix/swh.scm
index a62567dd58..c7c1c873a2 100644
--- a/guix/swh.scm
+++ b/guix/swh.scm
@@ -2,6 +2,7 @@
;;; Copyright © 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2020 Jakub Kądziołka <kuba@kadziolka.net>
;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
+;;; Copyright © 2021 Simon Tournier <zimon.toutoune@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -136,6 +137,12 @@
;; Whether to verify the X.509 HTTPS certificate for %SWH-BASE-URL.
(make-parameter #t))
+;; Token from an account to the Software Heritage Authentication service
+;; <https://archive.softwareheritage.org/api/>
+(define %swh-token
+ (make-parameter (and=> (getenv "GUIX_SWH_TOKEN")
+ string->symbol)))
+
(define (swh-url path . rest)
;; URLs returned by the API may be relative or absolute. This has changed
;; without notice before. Handle both cases by detecting whether the path
@@ -246,6 +253,10 @@ FALSE-IF-404? is true, return #f upon 404 responses."
(and ((%allow-request?) url method)
(let*-values (((response port)
(method url #:streaming? #t
+ #:headers
+ (if (%swh-token)
+ `((authorization . (Bearer ,(%swh-token))))
+ '())
#:verify-certificate?
(%verify-swh-certificate?))))
;; See <https://archive.softwareheritage.org/api/#rate-limiting>.
@@ -645,20 +656,29 @@ delete it when leaving the dynamic extent of this call."
(lambda ()
(false-if-exception (delete-file-recursively tmp-dir))))))
-(define* (swh-download-directory id output
- #:key (log-port (current-error-port)))
- "Download from Software Heritage the directory with the given ID, and
-unpack it to OUTPUT. Return #t on success and #f on failure"
+(define* (swh-download-archive swhid output
+ #:key
+ (archive-type 'flat)
+ (log-port (current-error-port)))
+ "Download from Software Heritage the directory or revision with the given
+SWID, in the ARCHIVE-TYPE format (one of 'flat or 'git-bare), and unpack it to
+OUTPUT. Return #t on success and #f on failure."
(call-with-temporary-directory
(lambda (directory)
- (match (vault-fetch id 'directory #:log-port log-port)
+ (match (vault-fetch swhid
+ #:archive-type archive-type
+ #:log-port log-port)
(#f
(format log-port
- "SWH: directory ~a could not be fetched from the vault~%"
- id)
+ "SWH: object ~a could not be fetched from the vault~%"
+ swhid)
#f)
((? port? input)
- (let ((tar (open-pipe* OPEN_WRITE "tar" "-C" directory "-xzvf" "-")))
+ (let ((tar (open-pipe* OPEN_WRITE "tar" "-C" directory
+ (match archive-type
+ ('flat "-xzvf") ;gzipped
+ ('git-bare "-xvf")) ;uncompressed
+ "-")))
(dump-port input tar)
(close-port input)
(let ((status (close-pipe tar)))
@@ -672,6 +692,14 @@ unpack it to OUTPUT. Return #t on success and #f on failure"
#:log (%make-void-port "w"))
#t))))))))
+(define* (swh-download-directory id output
+ #:key (log-port (current-error-port)))
+ "Download from Software Heritage the directory with the given ID, and
+unpack it to OUTPUT. Return #t on success and #f on failure."
+ (swh-download-archive (string-append "swh:1:dir:" id) output
+ #:archive-type 'flat
+ #:log-port log-port))
+
(define (commit-id? reference)
"Return true if REFERENCE is likely a commit ID, false otherwise---e.g., if
it is a tag name. This is based on a simple heuristic so use with care!"
@@ -679,8 +707,11 @@ it is a tag name. This is based on a simple heuristic so use with care!"
(string-every char-set:hex-digit reference)))
(define* (swh-download url reference output
- #:key (log-port (current-error-port)))
- "Download from Software Heritage a checkout of the Git tag or commit
+ #:key
+ (archive-type 'flat)
+ (log-port (current-error-port)))
+ "Download from Software Heritage a checkout (if ARCHIVE-TYPE is 'flat) or a
+full Git repository (if ARCHIVE-TYPE is 'git-bare) of the Git tag or commit
REFERENCE originating from URL, and unpack it in OUTPUT. Return #t on success
and #f on failure.
@@ -694,8 +725,16 @@ wait until it becomes available, which could take several minutes."
(format log-port "SWH: found revision ~a with directory at '~a'~%"
(revision-id revision)
(swh-url (revision-directory-url revision)))
- (swh-download-directory (revision-directory revision) output
- #:log-port log-port))
+ (swh-download-archive (match archive-type
+ ('flat
+ (string-append
+ "swh:1:dir:" (revision-directory revision)))
+ ('git-bare
+ (string-append
+ "swh:1:rev:" (revision-id revision))))
+ output
+ #:archive-type archive-type
+ #:log-port log-port))
(#f
(format log-port
"SWH: revision ~s originating from ~a could not be found~%"
diff --git a/guix/tests.scm b/guix/tests.scm
index 063b20183d..4cd1ad6cf9 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, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2013-2021 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -189,18 +189,22 @@ too expensive to build entirely in the test store."
(loop (1+ i)))
bv))))
-(define (file=? a b)
- "Return true if files A and B have the same type and same content."
- (and (eq? (stat:type (lstat a)) (stat:type (lstat b)))
- (case (stat:type (lstat a))
- ((regular)
- (equal?
- (call-with-input-file a get-bytevector-all)
- (call-with-input-file b get-bytevector-all)))
- ((symlink)
- (string=? (readlink a) (readlink b)))
- (else
- (error "what?" (lstat a))))))
+(define* (file=? a b #:optional (stat lstat))
+ "Return true if files A and B have the same type and same content. Call
+STAT to obtain file metadata."
+ (let ((sta (stat a)) (stb (stat b)))
+ (and (eq? (stat:type sta) (stat:type stb))
+ (case (stat:type sta)
+ ((regular)
+ (or (and (= (stat:ino sta) (stat:ino stb))
+ (= (stat:dev sta) (stat:dev stb)))
+ (equal?
+ (call-with-input-file a get-bytevector-all)
+ (call-with-input-file b get-bytevector-all))))
+ ((symlink)
+ (string=? (readlink a) (readlink b)))
+ (else
+ (error "what?" (stat a)))))))
(define (canonical-file? file)
"Return #t if FILE is in the store, is read-only, and its mtime is 1."
diff --git a/guix/tests/git.scm b/guix/tests/git.scm
index b8e5f7e643..69960284d9 100644
--- a/guix/tests/git.scm
+++ b/guix/tests/git.scm
@@ -1,5 +1,6 @@
;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2019, 2020 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -53,6 +54,7 @@ Return DIRECTORY on success."
(with-environment-variables
`(("GIT_CONFIG_NOSYSTEM" "1")
("GIT_ATTR_NOSYSTEM" "1")
+ ("GIT_CONFIG_GLOBAL" ,(string-append home "/.gitconfig"))
("HOME" ,home))
(apply invoke (git-command) "-C" directory
command args)))))
@@ -88,6 +90,9 @@ Return DIRECTORY on success."
((('tag name) rest ...)
(git "tag" name)
(loop rest))
+ ((('tag name text) rest ...)
+ (git "tag" "-m" text name)
+ (loop rest))
((('branch name) rest ...)
(git "branch" name)
(loop rest))
diff --git a/guix/ui.scm b/guix/ui.scm
index 1428c254b3..bd999103ff 100644
--- a/guix/ui.scm
+++ b/guix/ui.scm
@@ -377,7 +377,8 @@ ARGS is the list of arguments received by the 'throw' handler."
(+ 2 (string-contains message ": ")))))
(format (current-error-port) (G_ "~amissing closing parenthesis~%")
location))
- (apply throw args)))
+ (report-error (G_ "read error while loading '~a': ~a~%")
+ file (apply format #f message args))))
(('syntax-error proc message properties form subform . rest)
(let ((loc (source-properties->location properties)))
(report-error loc (G_ "~s: ~a~%")
@@ -1431,10 +1432,22 @@ converted to a space; sequences of more than one line break are preserved."
(with-fluids ((%default-port-encoding "UTF-8"))
(stexi->plain-text (texi-fragment->stexi str))))
+(define (texi->plain-text* package str)
+ "Same as 'texi->plain-text', but gracefully handle Texinfo errors."
+ (catch 'parser-error
+ (lambda ()
+ (texi->plain-text str))
+ (lambda args
+ (warning (package-location package)
+ (G_ "~a: invalid Texinfo markup~%")
+ (package-full-name package))
+ str)))
+
(define (package-field-string package field-accessor)
"Return a plain-text representation of PACKAGE field."
(and=> (field-accessor package)
- (compose texi->plain-text P_)))
+ (lambda (str)
+ (texi->plain-text* package (P_ str)))))
(define (package-description-string package)
"Return a plain-text representation of PACKAGE description field."
@@ -1555,7 +1568,8 @@ HYPERLINKS? is true, emit hyperlink escape sequences when appropriate."
(parameterize ((%text-width width*))
;; Call 'texi->plain-text' on the concatenated string to account
;; for the width of "description:" in paragraph filling.
- (texi->plain-text
+ (texi->plain-text*
+ p
(string-append "description: "
(or (and=> (package-description p) P_)
""))))
@@ -2085,10 +2099,17 @@ contain a 'define-command' form."
(lambda (command)
(eq? category (command-category command))))
- (format #t (G_ "Usage: guix COMMAND ARGS...
-Run COMMAND with ARGS.\n"))
+ (display (G_ "Usage: guix OPTION | COMMAND ARGS...
+Run COMMAND with ARGS, if given.\n"))
+
+ (display (G_ "
+ -h, --help display this helpful text again and exit"))
+ (display (G_ "
+ -V, --version display version and copyright information and exit"))
+ (newline)
+
(newline)
- (format #t (G_ "COMMAND must be one of the sub-commands listed below:\n"))
+ (display (G_ "COMMAND must be one of the sub-commands listed below:\n"))
(let ((commands (commands))
(categories (module-ref (resolve-interface '(guix scripts))
diff --git a/guix/utils.scm b/guix/utils.scm
index 3f253d6cd8..9596ff8582 100644
--- a/guix/utils.scm
+++ b/guix/utils.scm
@@ -97,6 +97,7 @@
target-ppc32?
target-ppc64le?
target-powerpc?
+ target-riscv64?
target-64bit?
cc-for-target
cxx-for-target
@@ -704,6 +705,11 @@ architecture (x86_64)?"
(%current-system))))
(string-prefix? "powerpc" target))
+(define* (target-riscv64? #:optional (target (or (%current-target-system)
+ (%current-system))))
+ "Is the architecture of TARGET a 'riscv64' machine?"
+ (string-prefix? "riscv64" target))
+
(define* (target-64bit? #:optional (system (or (%current-target-system)
(%current-system))))
(any (cut string-prefix? <> system) '("x86_64" "aarch64" "mips64" "powerpc64")))