;;; GNU Guix --- Functional package management for GNU
;;;
;;; 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 .
;;; This manifest returns build artfacts for all supported systems. This can be
;;; controlled by SUPPORTED_SYSTEMS environment variable. For the list of
;;; artifacts produced, see artifacts-for-system and the `-for-system?`
;;; procedures. NOTE: the --system argument does not change the system for which
;;; the resulting package is built. They return different definitions of the
;;; images. To change the system, pass different SUPPORTED_SYSTEMS.
(use-modules (gnu compression)
(gnu image)
(gnu packages graphviz)
(gnu packages imagemagick)
(gnu packages package-management)
(gnu packages perl)
(gnu services)
(gnu system image)
(gnu system install)
(gnu system)
(guix build-system gnu)
(guix build-system trivial)
(guix channels)
(guix gexp)
(guix git)
(guix grafts)
(guix memoization)
(guix monads)
(guix packages)
(guix profiles)
(guix records)
(guix scripts pack)
(guix store)
(guix ui)
(guix utils)
(ice-9 format)
(ice-9 match)
(srfi srfi-9)
(srfi srfi-26)
(srfi srfi-35))
;; For easier testing, use (snapshot) guix package from (gnu packages
;; package-management). Otherwise, the package is updated to current commit and
;; might not be substitutable, leading to longer build times.
(define %use-snapshot-package?
(string=? (or (getenv "GUIX_USE_SNAPSHOT_PACKAGE") "no") "yes"))
(define (%guix-version)
;; NOTE: while package-version guix is not correct in general,
;; it is correct for the release itself. At that time, the
;; guix package is updated to vX.Y.Z and it's the version
;; we want to use.
(package-version guix))
(define (%vm-image-path)
(search-path %load-path "gnu/system/examples/vm-image.tmpl"))
(define (%vm-image-efi-path)
(search-path %load-path "gnu/system/examples/vm-image-efi.tmpl"))
;; monadic record and gexp-compiler
;; taken from Inria
;; https://gitlab.inria.fr/numpex-pc5/wp3/guix-images/-/blob/17bf4585abc2d637faa5d339436e778b7c9fb1ce/modules/guix-hpc/packs.scm
;; XXX: The hack below will hopefully become unnecessary once the
;; (guix scripts pack) interface switches to declarative style--i.e.,
;; file-like objects.
(define-record-type
(monadic->declarative mvalue)
monadic?
(mvalue monadic-value))
(define-gexp-compiler (monadic-compiler (monadic ) system target)
(monadic-value monadic))
;; The tarball should be the same for every system.
;; Still, we need to decide what system to build it
;; for, so use the one that CI has most resources for.
(define (source-tarball-for-system? system)
(member system
'("x86_64-linux")))
(define (iso-for-system? system)
(member system
'("x86_64-linux" "i686-linux" "aarch64-linux")))
(define (qcow2-for-system? system)
(member system
'("x86_64-linux" "aarch64-linux")))
(define* (qcow2-gpt-for-system? system)
(string=? system "aarch64-linux"))
(define (copy-/etc/config.scm config)
"Copy the configuration.scm of the operating system to /etc/config.scm, for
user's convenience. The file has to be writable, not a link to the store, so
etc-service-type can't be used here. CONFIG is a pair of strings, (FROM . TO).
The config will be copied from FROM to TO."
(match config
((from . to)
(with-imported-modules '((guix build utils))
#~(begin
(use-modules (guix build utils))
(when (not (file-exists? #$to))
(copy-file #$from #$to)
(make-file-writable #$to)))))
(_ (raise
(formatted-message-string
(G_ "unexpected config parameter, should be pair of strings: ~a"
config))))))
(define copy-/etc/config.scm-service-type
(service-type (name 'copy-/etc/config.scm)
(description
"Copy the system configuration file to /etc/config.scm.")
(extensions (list (service-extension activation-service-type
copy-/etc/config.scm)))
(default-value (cons "/run/current-system/configuration.scm"
"/etc/config.scm"))))
(define (operating-system-with-/etc/config.scm os)
"Copy the system configuration file to writable /etc/config.scm on first boot."
(operating-system
(inherit os)
(services (cons (service copy-/etc/config.scm-service-type)
(operating-system-user-services os)))))
(define (simple-provenance-entry config-file)
"Return system entries describing the operating system config, provided
through CONFIG-FILE."
(mbegin %store-monad
(return `(("configuration.scm"
,(local-file (assume-valid-file-name config-file)
"configuration.scm"))))))
;; This is mostly taken from provenance-service-type from (gnu services),
;; but it provides only configuration.scm, not channels.scm. This is
;; to get the same derivations for both Cuirass and local builds.
;; In the future, provenance-service-type could be adapted to support
;; this use case as well.
(define simple-provenance-service-type
(service-type (name 'provenance)
(extensions
(list (service-extension system-service-type
simple-provenance-entry)))
(default-value #f) ;the OS config file
(description
"Store configuration.scm of the system in the system
itself.")))
(define* (operating-system-with-simple-provenance
os
#:optional
(config-file
(operating-system-configuration-file
os)))
"Return a variant of OS that stores its CONFIG-FILE. This is similar to
`operating-system-with-provenance`, but it does copy only the
configuration.scm."
(operating-system
(inherit os)
(services (cons (service simple-provenance-service-type config-file)
(operating-system-user-services os)))))
(define (guix-package-commit guix)
;; Extract the commit of the GUIX package.
(match (package-source guix)
((? channel? source)
(channel-commit source))
(_
(apply (lambda* (#:key commit #:allow-other-keys) commit)
(package-arguments guix)))))
;; NOTE: Normally, we would use (current-guix), along with url
;; overridden to the upstream repository to not leak our local checkout.
;; But currently, the (current-guix) derivation has to be computed through
;; QEMU for systems other than your host system. This takes a lot of time,
;; it takes at least half an hour to get the derivations.
(define (guix-package/with-commit guix commit)
"Use the guix from (gnu packages package-management),
but override its commit to the specified version. Make sure
to also override the channel commit to have the correct
provenance."
(let ((scm-version (car (string-split (package-version guix) #\-))))
(package
(inherit guix)
(version (string-append scm-version "." (string-take commit 7)))
(source (git-checkout
(url (channel-url %default-guix-channel))
(commit commit)))
(arguments
(substitute-keyword-arguments (package-arguments guix)
((#:configure-flags flags '())
#~(cons*
(string-append "--with-channel-commit=" #$commit)
(filter (lambda (flag)
(not (string-prefix? "--with-channel-commit=" flag)))
#$flags))))))))
(define guix-for-images
(mlambda (system)
(cond
;; For testing purposes, use the guix package directly.
(%use-snapshot-package? guix)
;; Normally, update the guix package to current commit.
(else
(guix-package/with-commit guix (guix-package-commit (current-guix)))))))
(define %binary-tarball-compression "xz")
;; Like guix pack -C xz -s --localstatedir --profile-name=current-guix guix
(define* (binary-tarball-for-system system #:key (extra-packages '()))
(let* ((base-name (string-append "guix-binary-" (%guix-version) "." system))
(manifest (packages->manifest (cons* guix extra-packages)))
(profile (profile (content manifest)))
(inputs `(("profile" ,profile)))
(compression %binary-tarball-compression))
(manifest-entry
(name (string-append base-name ".tar." compression))
(version (%guix-version))
(item (monadic->declarative
(self-contained-tarball
base-name profile
#:profile-name "current-guix"
#:compressor (lookup-compressor compression)
#:localstatedir? #t))))))
;; Like guix system image -t iso9660 \
;; --label="GUIX_$${system}_$(VERSION)" gnu/system/install.scm
(define* (iso-for-system system)
(let* ((name (string-append
"guix-system-install-" (%guix-version) "." system ".iso"))
(base-os (make-installation-os
#:grub-displayed-version (%guix-version)
#:efi-only? (string=? system "aarch64-linux")))
(base-image (os->image base-os #:type iso-image-type))
(label (string-append "GUIX_" system "_"
(if (> (string-length (%guix-version)) 7)
(string-take (%guix-version) 7)
(%guix-version)))))
(manifest-entry
(name name)
(version (%guix-version))
(item (system-image
(image-with-label
(image
(inherit base-image)
(name (string->symbol name)))
label))))))
;; Like guix system image -t qcow2 gnu/system/examples/vm-image.tmpl
(define* (qcow2-for-system system)
(let* ((name (string-append
"guix-system-vm-image-" (%guix-version) "." system ".qcow2"))
(base-os-path
(if (qcow2-gpt-for-system? system)
(%vm-image-efi-path)
(%vm-image-path)))
(target-image-type
(if (qcow2-gpt-for-system? system)
qcow2-gpt-image-type
qcow2-image-type))
(base-os
(operating-system-with-/etc/config.scm
(operating-system-with-simple-provenance
(load base-os-path) base-os-path)))
(base-image (os->image base-os #:type target-image-type)))
(manifest-entry
(name name)
(version (%guix-version))
(item (system-image
(image
(inherit base-image)
(volatile-root? #f)
(size (* 20 (expt 1024 3))) ; 20 GiB to have spare space
(name (string->symbol name))))))))
(define* (guix-source-tarball)
(let ((guix (package
(inherit guix)
(native-inputs
(modify-inputs (package-native-inputs guix)
;; graphviz-minimal -> graphviz
(replace "graphviz" graphviz)
(append imagemagick)
(append perl))))))
(manifest-entry
(name (string-append "guix-" (%guix-version) ".tar.gz"))
(version (package-version guix))
(item (dist-package
guix
;; Guix is built from git source, not from tarball.
;; So it's fine to use its source directly.
(package-source guix))))))
(define* (manifest-entry-with-parameters system entry
#:key
(guix-for-images-proc guix-for-images))
(manifest-entry
(inherit entry)
(item
(with-parameters
((%current-system system)
(%current-target-system #f)
(current-guix-package (guix-for-images-proc system)))
(manifest-entry-item entry)))))
(define* (manifest-with-parameters system manifest
#:key
(guix-for-images-proc guix-for-images))
"Returns entries in the manifest accompanied with %current-system,
%current-target-sytem and current-guix-package parameters."
(make-manifest
(map (cut manifest-entry-with-parameters system <>
#:guix-for-images-proc guix-for-images-proc)
(manifest-entries manifest))))
(define (artifacts-for-system/nonparameterized system)
"Get all artifacts for given system. This will always include the
guix-binary tarball and optionally iso and/or qcow2 images."
(manifest
(append
(list
(binary-tarball-for-system system))
;; TODO: After source tarball generation is ready, uncomment.
;; (if (source-tarball-for-system? system)
;; (list (guix-source-tarball))
;; '())
(if (iso-for-system? system)
(list (iso-for-system system))
'())
(if (qcow2-for-system? system)
(list (qcow2-for-system system))
'()))))
(define* (artifacts-for-system system
#:key
(guix-for-images-proc guix-for-images))
"Collects all artifacts for a system. Gives them the proper %current-system
and %current-target-system parameters, so the --system passed on CLI is
irrelevant."
;; NOTE: parameterizing current system, because the tarball seems to somehow
;; depend on it early on. I haven't investigated it, but seems like a bug. Could
;; it be the gexp->derivation + monadic->declarative, not passing down the
;; system? Symptom: guix build --system=x86_64 -m artifacts-manifest.scm and
;; guix build --system=i686-linux -m artifacts-manifest.scm gives out different
;; results without the parameterization.
(parameterize
((%current-system system)
(%current-target-system #f)
(current-guix-package (guix-for-images-proc system)))
(manifest-with-parameters
system
(artifacts-for-system/nonparameterized system)
#:guix-for-images-proc guix-for-images-proc)))
(define (manifest->union manifest)
"Makes a union that will be a folder with all the entries symlinked. This
is different from a profile as it expects the entries are just simple files
and symlinks them by their manifest-entry-name."
(let ((entries (manifest-entries manifest)))
(computed-file
"artifacts-union"
(with-imported-modules '((guix build union)
(guix build utils))
#~(begin
(use-modules (guix build utils))
(mkdir-p #$output)
(for-each
(lambda* (entry)
(symlink (cdr entry)
(string-append #$output "/" (car entry))))
(list #$@(map (lambda (entry)
#~(cons
#$(manifest-entry-name entry)
#$(manifest-entry-item entry)))
entries))))))))
(define %supported-systems
(or (and
(getenv "SUPPORTED_SYSTEMS")
(string-split (getenv "SUPPORTED_SYSTEMS") #\ ))
'("x86_64-linux" "i686-linux"
"armhf-linux" "aarch64-linux"
"powerpc64le-linux" "riscv64-linux")))
(define supported-systems-union-manifest
(concatenate-manifests
(map artifacts-for-system
%supported-systems)))
(when %use-snapshot-package?
(warning (G_ "building images using the 'guix' package (snapshot)~%")))
(info (G_ "producing artifacts for the following systems: ~a~%")
%supported-systems)
supported-systems-union-manifest