Next: Other build systems, Previous: Setup, Up: Packaging Tutorial [Contents][Index]
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 compression) #:use-module (gnu packages pkg-config) #:use-module (gnu packages python) #:use-module (gnu packages ssh) #:use-module (gnu packages tls) #:use-module (gnu packages web)) (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))) ;; Remove bundled software. (snippet '(delete-file-recursively "deps")))) (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"))))) ;; Run checks more verbosely. (replace 'check (lambda* (#:key tests? #:allow-other-keys) (when tests? (invoke "./libgit2_clar" "-v" "-Q")))) (add-after 'unpack 'make-files-writable-for-tests (lambda _ (for-each make-file-writable (find-files "."))))))) (inputs (list libssh2 http-parser python-wrapper)) (native-inputs (list pkg-config)) (propagated-inputs ;; These two libraries are in 'Requires.private' in libgit2.pc. (list openssl 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
methodgit-fetch
methodUnlike the url-fetch
method, 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 (commit (string-append
"v" version))
.
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
version))
.
The 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
Reference Manual).
How does one obtain the sha256
hash that’s in there, you ask? By
invoking guix hash
on a checkout of the desired commit, along
these lines:
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
modules
field.
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.
Note: You may see here and there snippets where package inputs are written quite differently, like so:
;; The "old style" for inputs. (inputs `(("libssh2" ,libssh2) ("http-parser" ,http-parser) ("python" ,python-wrapper)))This is the “old style”, where each input in the list is explicitly given a label (a string). It is still supported but we recommend using the style above instead. See package Reference in GNU Guix Reference Manual, for more info.
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 "out"
.
Typical separate output names include debug
and doc
.
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
modular.
The 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
one.
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
following flags
#:make-flags (list (string-append "prefix=" (assoc-ref %outputs "out")) "CC=gcc")
translate into
$ 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
/gnu/store/...-my-libgit2-20180408).
Similarly, it’s possible to set the configure flags:
#:configure-flags '("-DUSE_SHA1DC=ON")
The %build-inputs
variable is also generated in scope. It’s an association
table that maps the input names to their store directories.
The phases
keyword lists the sequential steps of the build system. Typically
phases include unpack
, configure
, build
, install
and 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
system is:
(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)
Note the chdir
call: it changes the working directory to where the source was
unpacked.
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
else.
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)
.
(delete phase)
.
The procedure supports the keyword arguments inputs
and outputs
. Each
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
this:
(lambda* (#:key inputs outputs #:allow-other-keys)
(let ((bash-directory (assoc-ref inputs "bash"))
(output-directory (assoc-ref outputs "out"))
(doc-directory (assoc-ref outputs "doc")))
;; ...
#true))
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.
When customizing phases
, we often need to write code that mimics the
equivalent system invocations (make
, mkdir
, cp
, etc.) commonly used during
regular “Unix-style” installations.
Some like 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:
which
Like the ‘which’ system command.
find-files
Akin to the ‘find’ system command.
mkdir-p
Like ‘mkdir -p’, which creates all parents as needed.
install-file
Similar to ‘install’ when installing a file to a (possibly
non-existing) directory. Guile has copy-file
which works
like ‘cp’.
copy-recursively
Like ‘cp -r’.
delete-file-recursively
Like ‘rm -rf’.
invoke
Run an executable. This should be used instead of system*
.
with-directory-excursion
Run the body in a different working directory, then restore the previous working directory.
substitute*
A “sed
-like” function.
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).
Next: Other build systems, Previous: Setup, Up: Packaging Tutorial [Contents][Index]