Next: , Previous: , Up: Packaging Tutorial   [Contents][Index]


2.1.3 Extended example

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.

2.1.3.1 git-fetch method

Unlike 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.

2.1.3.2 Snippets

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.

2.1.3.3 Inputs

First, a syntactic comment: See the quasi-quote / comma syntax?

    (native-inputs
     `(("pkg-config" ,pkg-config)))

is equivalent to

    (native-inputs
     (list (list "pkg-config" pkg-config)))

You’ll mostly see the former because it’s shorter.

There are 3 different input types. In short:

native-inputs

Required for building but not runtime – installing a package through a substitute won’t install these inputs.

inputs

Installed in the store but not in the profile, as well as being present at build time.

propagated-inputs

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.

2.1.3.4 Outputs

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.

2.1.3.5 Build system arguments

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:

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.

2.1.3.6 Code staging

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.

2.1.3.7 Utility functions

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.

2.1.3.8 Module prefix

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: , Previous: , Up: Packaging Tutorial   [Contents][Index]