The above “Hello World” example is as simple as it goes. Packages can be more complex than that and Guix can handle more advanced scenarios. Let’s look at another, more sophisticated package (slightly modified from the source):
(define-module (gnu packages version-control) #:use-module ((guix licenses) #:prefix license:) #:use-module (guix utils) #:use-module (guix packages) #:use-module (guix git-download) #:use-module (guix build-system cmake) #:use-module (gnu packages ssh) #:use-module (gnu packages web) #:use-module (gnu packages pkg-config) #:use-module (gnu packages python) #:use-module (gnu packages compression) #:use-module (gnu packages tls)) (define-public my-libgit2 (let ((commit "e98d0a37c93574d2c6107bf7f31140b548c6a7bf") (revision "1")) (package (name "my-libgit2") (version (git-version "0.26.6" revision commit)) (source (origin (method git-fetch) (uri (git-reference (url "https://github.com/libgit2/libgit2/") (commit commit))) (file-name (git-file-name name version)) (sha256 (base32 "17pjvprmdrx4h6bb1hhc98w9qi6ki7yl57f090n9kbhswxqfs7s3")) (patches (search-patches "libgit2-mtime-0.patch")) (modules '((guix build utils))) (snippet '(begin ;; Remove bundled software. (delete-file-recursively "deps") #true)))) (build-system cmake-build-system) (outputs '("out" "debug")) (arguments `(#:tests? #true ; Run the test suite (this is the default) #:configure-flags '("-DUSE_SHA1DC=ON") ; SHA-1 collision detection #:phases (modify-phases %standard-phases (add-after 'unpack 'fix-hardcoded-paths (lambda _ (substitute* "tests/repo/init.c" (("#!/bin/sh") (string-append "#!" (which "sh")))) (substitute* "tests/clar/fs.h" (("/bin/cp") (which "cp")) (("/bin/rm") (which "rm"))) #true)) ;; Run checks more verbosely. (replace 'check (lambda _ (invoke "./libgit2_clar" "-v" "-Q"))) (add-after 'unpack 'make-files-writable-for-tests (lambda _ (for-each make-file-writable (find-files "." ".*"))))))) (inputs `(("libssh2" ,libssh2) ("http-parser" ,http-parser) ("python" ,python-wrapper))) (native-inputs `(("pkg-config" ,pkg-config))) (propagated-inputs ;; These two libraries are in 'Requires.private' in libgit2.pc. `(("openssl" ,openssl) ("zlib" ,zlib))) (home-page "https://libgit2.github.com/") (synopsis "Library providing Git core methods") (description "Libgit2 is a portable, pure C implementation of the Git core methods provided as a re-entrant linkable library with a solid API, allowing you to write native speed custom Git applications in any language with bindings.") ;; GPLv2 with linking exception (license license:gpl2))))
(In those cases were you only want to tweak a few fields from a package definition, you should rely on inheritance instead of copy-pasting everything. See below.)
Let’s discuss those fields in depth.
git-fetch expects a
git-reference which takes
a Git repository and a commit. The commit can be any Git reference such as
tags, so if the
version is tagged, then it can be used directly. Sometimes
the tag is prefixed with a
v, in which case you’d use
To ensure that the source code from the Git repository is stored in a
directory with a descriptive name, we use
(file-name (git-file-name name
git-version procedure can be used to derive the
version when packaging programs for a specific commit, following the
Guix contributor guidelines (see Version Numbers in GNU Guix
How does one obtain the
sha256 hash that’s in there, you ask? By
guix hash on a checkout of the desired commit, along
git clone https://github.com/libgit2/libgit2/ cd libgit2 git checkout v0.26.6 guix hash -rx .
guix hash -rx computes a SHA256 hash over the whole directory,
excluding the .git sub-directory (see Invoking guix hash in GNU Guix Reference Manual).
In the future,
guix download will hopefully be able to do
these steps for you, just like it does for regular downloads.
Snippets are quoted (i.e. non-evaluated) Scheme code that are a means of patching the source. They are a Guix-y alternative to the traditional .patch files. Because of the quote, the code in only evaluated when passed to the Guix daemon for building. There can be as many snippets as needed.
Snippets might need additional Guile modules which can be imported from the
First, a syntactic comment: See the quasi-quote / comma syntax?
(native-inputs `(("pkg-config" ,pkg-config)))
is equivalent to
You’ll mostly see the former because it’s shorter.
There are 3 different input types. In short:
Required for building but not runtime – installing a package through a substitute won’t install these inputs.
Installed in the store but not in the profile, as well as being present at build time.
Installed in the store and in the profile, as well as being present at build time.
See Package Reference in GNU Guix Reference Manual for more details.
The distinction between the various inputs is important: if a dependency can be handled as an input instead of a propagated input, it should be done so, or else it “pollutes” the user profile for no good reason.
For instance, a user installing a graphical program that depends on a command line tool might only be interested in the graphical part, so there is no need to force the command line tool into the user profile. The dependency is a concern to the package, not to the user. Inputs make it possible to handle dependencies without bugging the user by adding undesired executable files (or libraries) to their profile.
Same goes for native-inputs: once the program is installed, build-time dependencies can be safely garbage-collected. It also matters when a substitute is available, in which case only the inputs and propagated inputs will be fetched: the native inputs are not required to install a package from a substitute.
Just like how a package can have multiple inputs, it can also produce multiple outputs.
Each output corresponds to a separate directory in the store.
The user can choose which output to install; this is useful to save space or to avoid polluting the user profile with unwanted executables or libraries.
Output separation is optional. When the
outputs field is left out, the
default and only output (the complete package) is referred to as
Typical separate output names include
It’s advised to separate outputs only when you’ve shown it’s worth it: if the
output size is significant (compare with
guix size) or in case the package is
arguments is a keyword-value list used to configure the build process.
The simplest argument
#:tests? can be used to disable the test suite when
building the package. This is mostly useful when the package does not feature
any test suite. It’s strongly recommended to keep the test suite on if there is
Another common argument is
:make-flags, which specifies a list of flags to
append when running make, as you would from the command line. For instance, the
$ make CC=gcc prefix=/gnu/store/...-<out>
This sets the C compiler to
gcc and the
prefix variable (the installation
directory in Make parlance) to
(assoc-ref %outputs "out"), which is a build-stage
global variable pointing to the destination directory in the store (something like
Similarly, it’s possible to set the configure flags:
%build-inputs variable is also generated in scope. It’s an association
table that maps the input names to their store directories.
phases keyword lists the sequential steps of the build system. Typically
check. To know
more about those phases, you need to work out the appropriate build system
definition in ‘$GUIX_CHECKOUT/guix/build/gnu-build-system.scm’:
(define %standard-phases ;; Standard build phases, as a list of symbol/procedure pairs. (let-syntax ((phases (syntax-rules () ((_ p ...) `((p . ,p) ...))))) (phases set-SOURCE-DATE-EPOCH set-paths install-locale unpack bootstrap patch-usr-bin-file patch-source-shebangs configure patch-generated-file-shebangs build check install patch-shebangs strip validate-runpath validate-documentation-location delete-info-dir-file patch-dot-desktop-files install-license-files reset-gzip-timestamps compress-documentation)))
Or from the REPL:
(add-to-load-path "/path/to/guix/checkout") ,use (guix build gnu-build-system) (map first %standard-phases) ⇒ (set-SOURCE-DATE-EPOCH set-paths install-locale unpack bootstrap patch-usr-bin-file patch-source-shebangs configure patch-generated-file-shebangs build check install patch-shebangs strip validate-runpath validate-documentation-location delete-info-dir-file patch-dot-desktop-files install-license-files reset-gzip-timestamps compress-documentation)
If you want to know more about what happens during those phases, consult the associated procedures.
For instance, as of this writing the definition of
unpack for the GNU build
(define* (unpack #:key source #:allow-other-keys) "Unpack SOURCE in the working directory, and change directory within the source. When SOURCE is a directory, copy it in a sub-directory of the current working directory." (if (file-is-directory? source) (begin (mkdir "source") (chdir "source") ;; Preserve timestamps (set to the Epoch) on the copied tree so that ;; things work deterministically. (copy-recursively source "." #:keep-mtime? #true)) (begin (if (string-suffix? ".zip" source) (invoke "unzip" source) (invoke "tar" "xvf" source)) (chdir (first-subdirectory ".")))) #true)
chdir call: it changes the working directory to where the source was
Thus every phase following the
unpack will use the source as a working
directory, which is why we can directly work on the source files.
That is to say, unless a later phase changes the working directory to something
We modify the list of
%standard-phases of the build system with the
modify-phases macro as per the list of specified modifications, which may have
the following forms:
(add-before phase new-phase procedure): Run procedure named new-phase before phase.
(add-after phase new-phase procedure): Same, but afterwards.
(replace phase procedure).
The procedure supports the keyword arguments
input (whether native, propagated or not) and output directory is referenced
by their name in those variables. Thus
(assoc-ref outputs "out") is the store
directory of the main output of the package. A phase procedure may look like
The procedure must return
#true on success. It’s brittle to rely on the return
value of the last expression used to tweak the phase because there is no
guarantee it would be a
#true. Hence the trailing
#true to ensure the right value
is returned on success.
The astute reader may have noticed the quasi-quote and comma syntax in the argument field. Indeed, the build code in the package declaration should not be evaluated on the client side, but only when passed to the Guix daemon. This mechanism of passing code around two running processes is called code staging.
phases, we often need to write code that mimics the
equivalent system invocations (
cp, etc.) commonly used during
regular “Unix-style” installations.
chmod are native to Guile.
See Guile reference manual for a complete list.
Guix provides additional helper functions which prove especially handy in the context of package management.
Some of those functions can be found in ‘$GUIX_CHECKOUT/guix/guix/build/utils.scm’. Most of them mirror the behaviour of the traditional Unix system commands:
Like the ‘which’ system command.
Akin to the ‘find’ system command.
Like ‘mkdir -p’, which creates all parents as needed.
Similar to ‘install’ when installing a file to a (possibly
non-existing) directory. Guile has
copy-file which works
Like ‘cp -r’.
Like ‘rm -rf’.
Run an executable. This should be used instead of
Run the body in a different working directory, then restore the previous working directory.
See Build Utilities in GNU Guix Reference Manual, for more information on these utilities.
The license in our last example needs a prefix: this is because of how the
license module was imported in the package, as
#:use-module ((guix licenses)
#:prefix license:). The Guile module import mechanism
(see Using Guile Modules in Guile reference manual)
gives the user full control over namespacing: this is needed to avoid
clashes between, say, the
‘zlib’ variable from ‘licenses.scm’ (a license value) and the ‘zlib’ variable
from ‘compression.scm’ (a package value).