summaryrefslogtreecommitdiff
path: root/guix/scripts/pack.scm
diff options
context:
space:
mode:
Diffstat (limited to 'guix/scripts/pack.scm')
-rw-r--r--guix/scripts/pack.scm556
1 files changed, 401 insertions, 155 deletions
diff --git a/guix/scripts/pack.scm b/guix/scripts/pack.scm
index f65642fb85..eb41eb5563 100644
--- a/guix/scripts/pack.scm
+++ b/guix/scripts/pack.scm
@@ -1,11 +1,11 @@
;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2015, 2017-2022 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2015, 2017-2023 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2017, 2018 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2018 Konrad Hinsen <konrad.hinsen@fastmail.net>
;;; Copyright © 2018 Chris Marusich <cmmarusich@gmail.com>
;;; Copyright © 2018 Efraim Flashner <efraim@flashner.co.il>
;;; Copyright © 2020 Tobias Geerinckx-Rice <me@tobias.gr>
-;;; Copyright © 2020, 2021, 2022 Maxim Cournoyer <maxim.cournoyer@gmail.com>
+;;; Copyright © 2020, 2021, 2022, 2023 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;; Copyright © 2020 Eric Bavier <bavier@posteo.net>
;;; Copyright © 2022 Alex Griffin <a@ajgrf.com>
;;;
@@ -67,6 +67,7 @@
self-contained-tarball
debian-archive
+ rpm-archive
docker-image
squashfs-image
@@ -194,104 +195,150 @@ target the profile's @file{bin/env} file:
(leave (G_ "~a: invalid symlink specification~%")
arg))))
-
-;;;
-;;; Tarball format.
-;;;
-(define* (self-contained-tarball/builder profile
- #:key (profile-name "guix-profile")
- (compressor (first %compressors))
- localstatedir?
- (symlinks '())
- (archiver tar)
- (extra-options '()))
- "Return the G-Expression of the builder used for self-contained-tarball."
+(define (set-utf8-locale profile)
+ "Configure the environment to use the \"en_US.utf8\" locale provided by the
+GLIBC-UT8-LOCALES package."
+ ;; Arrange to not depend on 'glibc-utf8-locales' when using '--bootstrap'.
+ (and (or (not (profile? profile))
+ (profile-locales? profile))
+ #~(begin
+ (setenv "GUIX_LOCPATH"
+ #+(file-append glibc-utf8-locales "/lib/locale"))
+ (setlocale LC_ALL "en_US.utf8"))))
+
+(define* (populate-profile-root profile
+ #:key (profile-name "guix-profile")
+ target
+ localstatedir?
+ deduplicate?
+ (symlinks '()))
+ "Populate the root profile directory with SYMLINKS and a Guix database, when
+LOCALSTATEDIR? is set. When DEDUPLICATE? is true, deduplicate the store
+items, which relies on hard links."
(define database
(and localstatedir?
(file-append (store-database (list profile))
"/db/db.sqlite")))
- (define set-utf8-locale
- ;; Arrange to not depend on 'glibc-utf8-locales' when using '--bootstrap'.
- (and (or (not (profile? profile))
- (profile-locales? profile))
- #~(begin
- (setenv "GUIX_LOCPATH"
- #+(file-append glibc-utf8-locales "/lib/locale"))
- (setlocale LC_ALL "en_US.utf8"))))
+ (define bootstrap?
+ ;; Whether a '--bootstrap' environment is needed, for testing purposes.
+ ;; XXX: Infer that from available info.
+ (and (not database) (not (profile-locales? profile))))
(define (import-module? module)
;; Since we don't use deduplication support in 'populate-store', don't
;; import (guix store deduplication) and its dependencies, which includes
- ;; Guile-Gcrypt. That way we can run tests with '--bootstrap'.
+ ;; Guile-Gcrypt, unless DEDUPLICATE? is #t. This makes it possible to run
+ ;; tests with '--bootstrap'.
(and (not-config? module)
- (not (equal? '(guix store deduplication) module))))
+ (or deduplicate? (not (equal? '(guix store deduplication) module)))))
- (with-imported-modules (source-module-closure
- `((guix build pack)
- (guix build store-copy)
- (guix build utils)
- (guix build union)
- (gnu build install))
- #:select? import-module?)
- #~(begin
- (use-modules (guix build pack)
- (guix build store-copy)
- (guix build utils)
- ((guix build union) #:select (relative-file-name))
- (gnu build install)
- (srfi srfi-1)
- (srfi srfi-26)
- (ice-9 match))
+ (computed-file "profile-directory"
+ (with-imported-modules (source-module-closure
+ `((guix build pack)
+ (guix build store-copy)
+ (guix build utils)
+ (guix build union)
+ (gnu build install))
+ #:select? import-module?)
+ #~(begin
+ (use-modules (guix build pack)
+ (guix build store-copy)
+ (guix build utils)
+ ((guix build union) #:select (relative-file-name))
+ (gnu build install)
+ (srfi srfi-1)
+ (srfi srfi-26)
+ (ice-9 match))
+
+ (define symlink->directives
+ ;; Return "populate directives" to make the given symlink and its
+ ;; parent directories.
+ (match-lambda
+ ((source '-> target)
+ (let ((target (string-append #$profile "/" target))
+ (parent (dirname source)))
+ ;; Never add a 'directory' directive for "/" so as to
+ ;; preserve its ownership when extracting the archive (see
+ ;; below), and also because this would lead to adding the
+ ;; same entries twice in the tarball.
+ `(,@(if (string=? parent "/")
+ '()
+ `((directory ,parent)))
+ ;; Use a relative file name for compatibility with
+ ;; relocatable packs.
+ (,source -> ,(relative-file-name parent target)))))))
+
+ (define directives
+ ;; Fully-qualified symlinks.
+ (append-map symlink->directives '#$symlinks))
- (define %root "root")
+ ;; Make sure non-ASCII file names are properly handled.
+ #+(set-utf8-locale profile)
- (define symlink->directives
- ;; Return "populate directives" to make the given symlink and its
- ;; parent directories.
- (match-lambda
- ((source '-> target)
- (let ((target (string-append #$profile "/" target))
- (parent (dirname source)))
- ;; Never add a 'directory' directive for "/" so as to
- ;; preserve its ownership when extracting the archive (see
- ;; below), and also because this would lead to adding the
- ;; same entries twice in the tarball.
- `(,@(if (string=? parent "/")
- '()
- `((directory ,parent)))
- ;; Use a relative file name for compatibility with
- ;; relocatable packs.
- (,source -> ,(relative-file-name parent target)))))))
+ ;; Note: there is not much to gain here with deduplication and there
+ ;; is the overhead of the '.links' directory, so turn it off by
+ ;; default. Furthermore GNU tar < 1.30 sometimes fails to extract
+ ;; tarballs with hard links:
+ ;; <http://lists.gnu.org/archive/html/bug-tar/2017-11/msg00009.html>.
+ (populate-store (list "profile") #$output
+ #:deduplicate? #$deduplicate?)
- (define directives
- ;; Fully-qualified symlinks.
- (append-map symlink->directives '#$symlinks))
+ (when #+localstatedir?
+ (install-database-and-gc-roots #$output #+database #$profile
+ #:profile-name #$profile-name))
+
+ ;; Create SYMLINKS.
+ (for-each (cut evaluate-populate-directive <> #$output)
+ directives)))
+ #:local-build? #f
+ #:guile (if bootstrap? %bootstrap-guile (default-guile))
+ #:options (list #:references-graphs `(("profile" ,profile))
+ #:target target)))
+
+
+;;;
+;;; Tarball format.
+;;;
+(define* (self-contained-tarball/builder profile
+ #:key (profile-name "guix-profile")
+ target
+ localstatedir?
+ deduplicate?
+ symlinks
+ compressor
+ archiver)
+ "Return a GEXP that can build a self-contained tarball."
+
+ (define root (populate-profile-root profile
+ #:profile-name profile-name
+ #:target target
+ #:localstatedir? localstatedir?
+ #:deduplicate? deduplicate?
+ #:symlinks symlinks))
+
+ (with-imported-modules (source-module-closure '((guix build pack)
+ (guix build utils)))
+ #~(begin
+ (use-modules (guix build pack)
+ (guix build utils))
;; Make sure non-ASCII file names are properly handled.
- #+set-utf8-locale
+ #+(set-utf8-locale profile)
(define tar #+(file-append archiver "/bin/tar"))
- ;; Note: there is not much to gain here with deduplication and there
- ;; is the overhead of the '.links' directory, so turn it off.
- ;; Furthermore GNU tar < 1.30 sometimes fails to extract tarballs
- ;; with hard links:
- ;; <http://lists.gnu.org/archive/html/bug-tar/2017-11/msg00009.html>.
- (populate-store (list "profile") %root #:deduplicate? #f)
+ (define %root (if #$localstatedir? "." #$root))
- (when #+localstatedir?
- (install-database-and-gc-roots %root #+database #$profile
- #:profile-name #$profile-name))
+ (when #$localstatedir?
+ ;; Fix the permission of the Guix database file, which was made
+ ;; read-only when copied to the store in populate-profile-root.
+ (copy-recursively #$root %root)
+ (chmod (string-append %root "/var/guix/db/db.sqlite") #o644))
- ;; Create SYMLINKS.
- (for-each (cut evaluate-populate-directive <> %root)
- directives)
-
- ;; Create the tarball.
(with-directory-excursion %root
;; GNU Tar recurses directories by default. Simply add the whole
- ;; current directory, which contains all the generated files so far.
+ ;; current directory, which contains all the files to be archived.
;; This avoids creating duplicate files in the archives that would
;; be stored as hard links by GNU Tar.
(apply invoke tar "-cvf" #$output "."
@@ -320,17 +367,16 @@ added to the pack."
(warning (G_ "entry point not supported in the '~a' format~%")
'tarball))
- (gexp->derivation
- (string-append name ".tar"
- (compressor-extension compressor))
- (self-contained-tarball/builder profile
- #:profile-name profile-name
- #:compressor compressor
- #:localstatedir? localstatedir?
- #:symlinks symlinks
- #:archiver archiver)
- #:target target
- #:references-graphs `(("profile" ,profile))))
+ (gexp->derivation (string-append name ".tar"
+ (compressor-extension compressor))
+ (self-contained-tarball/builder profile
+ #:profile-name profile-name
+ #:target target
+ #:localstatedir? localstatedir?
+ #:deduplicate? deduplicate?
+ #:symlinks symlinks
+ #:compressor compressor
+ #:archiver archiver)))
;;;
@@ -676,18 +722,19 @@ Valid compressors are: ~a~%") compressor-name %valid-compressors)))
'deb))
(define data-tarball
- (computed-file (string-append "data.tar"
- (compressor-extension compressor))
- (self-contained-tarball/builder
- profile
- #:profile-name profile-name
- #:compressor compressor
- #:localstatedir? localstatedir?
- #:symlinks symlinks
- #:archiver archiver)
- #:local-build? #f ;allow offloading
- #:options (list #:references-graphs `(("profile" ,profile))
- #:target target)))
+ (computed-file (string-append "data.tar" (compressor-extension
+ compressor))
+ (self-contained-tarball/builder profile
+ #:target target
+ #:profile-name profile-name
+ #:localstatedir? localstatedir?
+ #:deduplicate? deduplicate?
+ #:symlinks symlinks
+ #:compressor compressor
+ #:archiver archiver)
+ #:local-build? #f ;allow offloading
+ #:options (list #:references-graphs `(("profile" ,profile))
+ #:target target)))
(define build
(with-extensions (list guile-gcrypt)
@@ -702,6 +749,7 @@ Valid compressors are: ~a~%") compressor-name %valid-compressors)))
(guix build utils)
(guix profiles)
(ice-9 match)
+ (ice-9 optargs)
(srfi srfi-1))
(define machine-type
@@ -762,32 +810,23 @@ Valid compressors are: ~a~%") compressor-name %valid-compressors)))
(copy-file #+data-tarball data-tarball-file-name)
- (define (keyword-ref lst keyword)
- (match (memq keyword lst)
- ((_ value . _) value)
- (#f #f)))
-
;; Generate the control archive.
- (define control-file
- (keyword-ref '#$extra-options #:control-file))
-
- (define postinst-file
- (keyword-ref '#$extra-options #:postinst-file))
-
- (define triggers-file
- (keyword-ref '#$extra-options #:triggers-file))
+ (let-keywords '#$extra-options #f
+ ((control-file #f)
+ (postinst-file #f)
+ (triggers-file #f))
- (define control-tarball-file-name
- (string-append "control.tar"
- #$(compressor-extension compressor)))
+ (define control-tarball-file-name
+ (string-append "control.tar"
+ #$(compressor-extension compressor)))
- ;; Write the compressed control tarball. Only the control file is
- ;; mandatory (see: 'man deb' and 'man deb-control').
- (if control-file
- (copy-file control-file "control")
- (call-with-output-file "control"
- (lambda (port)
- (format port "\
+ ;; Write the compressed control tarball. Only the control file is
+ ;; mandatory (see: 'man deb' and 'man deb-control').
+ (if control-file
+ (copy-file control-file "control")
+ (call-with-output-file "control"
+ (lambda (port)
+ (format port "\
Package: ~a
Version: ~a
Description: Debian archive generated by GNU Guix.
@@ -797,36 +836,196 @@ Priority: optional
Section: misc
~%" package-name package-version architecture))))
- (when postinst-file
- (copy-file postinst-file "postinst")
- (chmod "postinst" #o755))
+ (when postinst-file
+ (copy-file postinst-file "postinst")
+ (chmod "postinst" #o755))
- (when triggers-file
- (copy-file triggers-file "triggers"))
+ (when triggers-file
+ (copy-file triggers-file "triggers"))
- (define tar (string-append #+archiver "/bin/tar"))
+ (define tar (string-append #+archiver "/bin/tar"))
- (apply invoke tar
- `(,@(tar-base-options
- #:tar tar
- #:compressor #+(and=> compressor compressor-command))
- "-cvf" ,control-tarball-file-name
- "control"
- ,@(if postinst-file '("postinst") '())
- ,@(if triggers-file '("triggers") '())))
+ (apply invoke tar
+ `(,@(tar-base-options
+ #:tar tar
+ #:compressor #+(and=> compressor compressor-command))
+ "-cvf" ,control-tarball-file-name
+ "control"
+ ,@(if postinst-file '("postinst") '())
+ ,@(if triggers-file '("triggers") '())))
- ;; Create the .deb archive using GNU ar.
- (invoke (string-append #+binutils "/bin/ar") "-rv" #$output
- "debian-binary"
- control-tarball-file-name data-tarball-file-name)))))
+ ;; Create the .deb archive using GNU ar.
+ (invoke (string-append #+binutils "/bin/ar") "-rv" #$output
+ "debian-binary"
+ control-tarball-file-name data-tarball-file-name))))))
- (gexp->derivation (string-append name ".deb")
- build
- #:target target
- #:references-graphs `(("profile" ,profile))))
+ (gexp->derivation (string-append name ".deb") build))
;;;
+;;; RPM archive format.
+;;;
+(define* (rpm-archive name profile
+ #:key target
+ (profile-name "guix-profile")
+ entry-point
+ (compressor (first %compressors))
+ deduplicate?
+ localstatedir?
+ (symlinks '())
+ archiver
+ (extra-options '()))
+ "Return a RPM archive (.rpm) containing a store initialized with the closure
+of PROFILE, a derivation. The archive contains /gnu/store. SYMLINKS must be
+a list of (SOURCE -> TARGET) tuples denoting symlinks to be added to the pack.
+ARCHIVER and ENTRY-POINT are not used. RELOCATABLE?, PREIN-FILE, POSTIN-FILE,
+PREUN-FILE and POSTUN-FILE can be provided via EXTRA-OPTIONS."
+ (when entry-point
+ (warning (G_ "entry point not supported in the '~a' format~%") 'rpm))
+
+ (define root (populate-profile-root profile
+ #:profile-name profile-name
+ #:target target
+ #:localstatedir? localstatedir?
+ #:deduplicate? deduplicate?
+ #:symlinks symlinks))
+
+ (define payload
+ (let* ((raw-cpio-file-name "payload.cpio")
+ (compressed-cpio-file-name (string-append raw-cpio-file-name
+ (compressor-extension
+ compressor))))
+ (computed-file compressed-cpio-file-name
+ (with-imported-modules (source-module-closure
+ '((guix build utils)
+ (guix cpio)
+ (guix rpm)))
+ #~(begin
+ (use-modules (guix build utils)
+ (guix cpio)
+ (guix rpm)
+ (srfi srfi-1))
+
+ ;; Make sure non-ASCII file names are properly handled.
+ #+(set-utf8-locale profile)
+
+ (define %root (if #$localstatedir? "." #$root))
+
+ (when #$localstatedir?
+ ;; Fix the permission of the Guix database file, which was made
+ ;; read-only when copied to the store in populate-profile-root.
+ (copy-recursively #$root %root)
+ (chmod (string-append %root "/var/guix/db/db.sqlite") #o644))
+
+ (call-with-output-file #$raw-cpio-file-name
+ (lambda (port)
+ (with-directory-excursion %root
+ ;; The first "." entry is discarded.
+ (write-cpio-archive
+ (remove fhs-directory?
+ (cdr (find-files "." #:directories? #t)))
+ port))))
+ (when #+(compressor-command compressor)
+ (apply invoke (append #+(compressor-command compressor)
+ (list #$raw-cpio-file-name))))
+ (copy-file #$compressed-cpio-file-name #$output)))
+ #:local-build? #f))) ;allow offloading
+
+ (define build
+ (with-extensions (list guile-gcrypt)
+ (with-imported-modules `(((guix config) => ,(make-config.scm))
+ ,@(source-module-closure
+ `((gcrypt hash)
+ (guix build utils)
+ (guix profiles)
+ (guix rpm))
+ #:select? not-config?))
+ #~(begin
+ (use-modules (gcrypt hash)
+ (guix build utils)
+ (guix profiles)
+ (guix rpm)
+ (ice-9 binary-ports)
+ (ice-9 match) ;for manifest->friendly-name
+ (ice-9 optargs)
+ (rnrs bytevectors)
+ (srfi srfi-1))
+
+ ;; Make sure non-ASCII file names are properly handled.
+ #+(set-utf8-locale profile)
+
+ (define machine-type
+ (and=> (or #$target %host-type)
+ (lambda (triplet)
+ (first (string-split triplet #\-)))))
+
+ #$(procedure-source manifest->friendly-name)
+
+ (define manifest (profile-manifest #$profile))
+
+ (define single-entry ;manifest entry
+ (match (manifest-entries manifest)
+ ((entry)
+ entry)
+ (_ #f)))
+
+ (define name
+ (or (and=> single-entry manifest-entry-name)
+ (manifest->friendly-name manifest)))
+
+ (define version
+ (or (and=> single-entry manifest-entry-version) "0.0.0"))
+
+ (define lead
+ (generate-lead (string-append name "-" version)
+ #:target (or #$target %host-type)))
+
+ (define payload-digest
+ (bytevector->hex-string (file-sha256 #$payload)))
+
+ (let-keywords '#$extra-options #f ((relocatable? #f)
+ (prein-file #f)
+ (postin-file #f)
+ (preun-file #f)
+ (postun-file #f))
+
+ (let ((header (generate-header name version
+ payload-digest
+ #$root
+ #$(compressor-name compressor)
+ #:target (or #$target %host-type)
+ #:relocatable? relocatable?
+ #:prein-file prein-file
+ #:postin-file postin-file
+ #:preun-file preun-file
+ #:postun-file postun-file)))
+
+ (define header-sha256
+ (bytevector->hex-string (sha256 (u8-list->bytevector header))))
+
+ (define payload-size (stat:size (stat #$payload)))
+
+ (define header+compressed-payload-size
+ (+ (length header) payload-size))
+
+ (define signature
+ (generate-signature header-sha256
+ header+compressed-payload-size))
+
+ ;; Serialize the archive components to a file.
+ (call-with-input-file #$payload
+ (lambda (in)
+ (call-with-output-file #$output
+ (lambda (out)
+ (put-bytevector out (assemble-rpm-metadata lead
+ signature
+ header))
+ (sendfile out in payload-size)))))))))))
+
+ (gexp->derivation (string-append name ".rpm") build))
+
+
+;;;
;;; Compiling C programs.
;;;
@@ -1158,7 +1357,8 @@ last resort for relocation."
`((tarball . ,self-contained-tarball)
(squashfs . ,squashfs-image)
(docker . ,docker-image)
- (deb . ,debian-archive)))
+ (deb . ,debian-archive)
+ (rpm . ,rpm-archive)))
(define (show-formats)
;; Print the supported pack formats.
@@ -1172,18 +1372,22 @@ last resort for relocation."
docker Tarball ready for 'docker load'"))
(display (G_ "
deb Debian archive installable via dpkg/apt"))
+ (display (G_ "
+ rpm RPM archive installable via rpm/yum"))
(newline))
+(define (required-option symbol)
+ "Return an SYMBOL option that requires a value."
+ (option (list (symbol->string symbol)) #t #f
+ (lambda (opt name arg result . rest)
+ (apply values
+ (alist-cons symbol arg result)
+ rest))))
+
(define %deb-format-options
- (let ((required-option (lambda (symbol)
- (option (list (symbol->string symbol)) #t #f
- (lambda (opt name arg result . rest)
- (apply values
- (alist-cons symbol arg result)
- rest))))))
- (list (required-option 'control-file)
- (required-option 'postinst-file)
- (required-option 'triggers-file))))
+ (list (required-option 'control-file)
+ (required-option 'postinst-file)
+ (required-option 'triggers-file)))
(define (show-deb-format-options)
(display (G_ "
@@ -1202,6 +1406,32 @@ last resort for relocation."
(newline)
(exit 0))
+(define %rpm-format-options
+ (list (required-option 'prein-file)
+ (required-option 'postin-file)
+ (required-option 'preun-file)
+ (required-option 'postun-file)))
+
+(define (show-rpm-format-options)
+ (display (G_ "
+ --help-rpm-format list options specific to the RPM format")))
+
+(define (show-rpm-format-options/detailed)
+ (display (G_ "
+ --prein-file=FILE
+ Embed the provided prein script"))
+ (display (G_ "
+ --postin-file=FILE
+ Embed the provided postin script"))
+ (display (G_ "
+ --preun-file=FILE
+ Embed the provided preun script"))
+ (display (G_ "
+ --postun-file=FILE
+ Embed the provided postun script"))
+ (newline)
+ (exit 0))
+
(define %options
;; Specifications of the command-line options.
(cons* (option '(#\h "help") #f #f
@@ -1278,7 +1508,12 @@ last resort for relocation."
(lambda args
(show-deb-format-options/detailed)))
+ (option '("help-rpm-format") #f #f
+ (lambda args
+ (show-rpm-format-options/detailed)))
+
(append %deb-format-options
+ %rpm-format-options
%transformation-options
%standard-build-options
%standard-cross-build-options
@@ -1296,6 +1531,7 @@ Create a bundle of PACKAGE.\n"))
(show-transformation-options-help)
(newline)
(show-deb-format-options)
+ (show-rpm-format-options)
(newline)
(display (G_ "
-f, --format=FORMAT build a pack in the given FORMAT"))
@@ -1454,6 +1690,16 @@ Create a bundle of PACKAGE.\n"))
(process-file-arg opts 'postinst-file)
#:triggers-file
(process-file-arg opts 'triggers-file)))
+ ('rpm
+ (list #:relocatable? relocatable?
+ #:prein-file
+ (process-file-arg opts 'prein-file)
+ #:postin-file
+ (process-file-arg opts 'postin-file)
+ #:preun-file
+ (process-file-arg opts 'preun-file)
+ #:postun-file
+ (process-file-arg opts 'postun-file)))
(_ '())))
(target (assoc-ref opts 'target))
(bootstrap? (assoc-ref opts 'bootstrap?))