what1fool.info

1 Guix from end user to contributor

1.1 introduction

1.1.1 motivation

I have written elsewhere on this site why I like Guix and why you might want to use it too. For purposes of this article I will assume that you want to use Guix to package some application that isn't yet available.

I currently use Gnu Guix as one of the package managers on my Debian system. Guix has relatively few packages compared to Apt or Nix – many applications that I like to use have not yet been ported.

1.1.2 why write another Guix tutorial?

Some good tutorials on this process already exist. Why am I writing this article?

  1. Address a narrowly defined audience: Lisp fans without much Guile experience

    I am writing this article with a user like myself in mind. That is, somebody who doesn't know much about Scheme or Guix but who is fairly comfortable with a different lisp like Common Lisp, Emacs Lisp, Clojure, etc. One consequence of this approach: the article will present how to do things with Emacs as a matter of course, without addressing other environments.

  2. Illustrate increasingly complex packages

    This article will illustrate progressively more complex packages: when this article is finished I hope for it to cover packages from trivial (hello) to complex (FreeCAD)

  3. Demonstrate fixing of broken packages

    I have installed a few (maybe 4 or 5) broken packages with Guix. By 'broken' I mean that the install completed but the application was not usable. This article will illustrate how to address that situation for some simple cases.

  4. (selfish) improve my understanding of Guix by trying to teach others

1.2 Guix, Geiser, and Emacs

you may not be surprised to hear that this subject quickly began to balloon in size. I broke it into a separate article.

1.3 simplest example: gnu hello

The first example package usually discussed in Guix tutorials is Gnu Hello. Gnu Hello sends the string "Hello World" to stdout and then exits. Gnu Hello is meant to illustrate correct practices for configuring and buidling a Gnu package, so the source repository contains more source code and scripts than you might expect given Hello's simple behavior.

1.3.1 the example "hello" package definition provided by Guix

here's the package definition for Gnu Hello:

(use-modules (guix)
             (guix build-system gnu)
             (guix licenses))

(package
  (name "hello")
  (version "2.10")
  (source (origin
            (method url-fetch)
            (uri (string-append "mirror://gnu/hello/hello-" version
                                ".tar.gz"))
            (sha256
             (base32
              "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i"))))
  (build-system gnu-build-system)
  (synopsis "Hello, GNU world: An example GNU package")
  (description "Guess what GNU Hello prints!")
  (home-page "http://www.gnu.org/software/hello/")
  (license gpl3+))

If you are generally familiar with software packaging systems then this probably looks like a familiar pattern represented in Scheme syntax:

  • name the package
  • assign the package a version
  • fetch the source tarball from some remote location
  • verify the tarball with a hash
  • invoke an appropriate build system (called "gnu-build-system" in this case)
  • add some metadata about the package, like description and license.

1.3.2 create a new package definition by trivially changing the original definition, then make Guix build the package

This code still packages Gnu Hello (i.e. the source code, build process, and binary artifact are all the same as the upstream Hello package) but the metadata has changed:

(use-modules (guix)
             (guix build-system gnu)
             (guix licenses))

(package
  (name "hello-with-spoiler")
  (version "2.10")
  (source (origin
            (method url-fetch)
            (uri (string-append "mirror://gnu/hello/hello-" version
                                ".tar.gz"))
            (sha256
             (base32
              "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i"))))
  (build-system gnu-build-system)
  (synopsis "Hello, GNU world: An example GNU package")
  (description "Guess what GNU Hello prints! Spoiler: It prints 'Hello, World!'")
  (home-page "http://www.gnu.org/software/hello/")
  (license gpl3+))

This shell command builds the package from the local definition:

guix build -f hello-with-spoiler.scm

This command places a directory in the guix store: on my system it looks like

/gnu/store/bhdh32p3q3h5r06pww6qw5621ji8y02h-hello-with-spoiler-0.0/

My impression, as a Guix beginner, is that this name is a 'pure' output of the package definition expression

What I mean here is that the directory name emitted by Guix in this example:

  • depends only on the package definition – it is not influenced by any global state, nonlocal variables, i/o, etc. (for purposes of this discussion let's say that the name of the directory is just a value, and that the creation of a directory with this name on the filesystem is conceptually separate)
  • will always be the same given the same package definition

Consequently, if the name is seen in some remote location a Guix installation can download the remote directory with some level of assurance (TODO: make this more specific) that the directory's contents are just what is specified by the package definition. This allows the user to download prebuilt binaries using Guix rather than building everything locally.

1.4 a trivial original package: howdly doodly worldarino

1.4.1 create a new package definition, howdly, that builds with gnu-build-system and gets source from a tarball over http

(use-modules (guix)
                   (guix build-system gnu)
                   (guix licenses))

(package
 (name "howdly")
 (version "0.1")
 (source (origin
          (method url-fetch)
          (uri (string-append "http://what1fool.info/howdly-" version ".tar.gz"))
          (sha256
           (base32
            ;; "this-will-not-work" ( see section 1.4.2.1 )
            "0np2dd7dcnfd7vhbb1a7nskb5n6793qgli7043ykg4dcwvx4fq4p"))))
 (build-system gnu-build-system)
 (arguments `(#:tests? #f)) ;; ref https://guix.gnu.org/blog/2018/a-packaging-tutorial-for-guix/
 (synopsis "an example package implementing trivial alterations to Gnu Hello")
 (description "print a classic greeting in the style of fictional character Ned Flanders")
 (home-page "http://what1fool.info")
 (license gpl3+))
  1. how to find the cryptographically significant string that is the third member of "origin"

    In general a Guix package definition requires a special string that is not meaningful to a human reading the string:

    > The sha256 checksum of the requested file. This is essential to ensure the source is not corrupted. Note that Guix works with base32 strings, hence the call to the base32 function.

    By reviewing the mailing list archives I learned that people often use the command

    $ guix hash
    

    To calculate the needed strings. The user can obtain the hash string by invoking the command with a local tarball as the sole argument:

    $ guix hash ~/howdly-0.1.tar.gz
    

    TODO Manually getting this string and putting it in the package definition did result in a working package definition, but "use this shell command and paste the output into your definition" isn't a great explanation. IMO a good explanation will point to relevent Guile documentation/source and Guix source code.

  2. disabling the test suite

    since this package is only an example, the form "(arguments `(#:tests? #f))" is added to disable the test suite.

    Without this form "guix build -f howdly.scm" will fail. This doesn't mean that "make" failed to produce a binary that works as intended. But by design Guix defaults consider any build with failing tests to be a failed build.

    <aside> This case illustratees the flexibility that Guix offers to end users – it's possible for a system to have multiple versions of howdly installed under Guix, by end users, some with failing tests allowed and some without, without the need to use any virtualization or container features of the underlying OS.

    For some users this compares favorably with incumbent package managers like Apt, which afaik need additional tools like the alternatives system to achieve a configuration like the one described above. </aside>

1.4.2 using howdly

$ guix build -f /tmp/howdly.scm
/gnu/store/1g32fcdkb33rfsanq10sy2lary4pj17s-howdly-0.1/bin/hello 
Howdly Doodly, worldarino!

1.5 IN_PROGRESS packaging and submitting an application without complex build requirements: Suckless Quark

1.5.1 why package Quark?

> quark is an extremely small and simple HTTP GET/HEAD-only web server for static content.

> The goal of this project is to do one thing and do it well, namely serving static web directories and doing that right. Most other solutions either are too complex (CGI support, dependencies on external libraries, …) or lack features you expect (TLS, virtual hosts, partial content, not modified since, …). quark tries to find a midway and just restrict itself to being static while still offering functions you only find in more bloated solutions and being as secure as possible (chroot, privilege dropping, strict parsers, no malloc at runtime, pledge, …).

Whatever your feelings about mainstream/featureful/bloated web servers, it seems safe to say that Quark is designed to do something useful in a simple fashion. So the effort of packaging the application for Guix is worth it, at least for me.

Additionally, Quark uses a minimal build system – no cmake or autotools, just a hand-written Makefile.

1.5.2 preliminaries: finding release data or verifying absence of release data

The suckless downloads page does not seem to provide any releases in archive form.

Additionally there are no tags defined by the quark repository

cd /tmp
git clone https://git.suckless.org/quark
cd /tmp/quark
PAGER=cat git tag

The final command will not return any non-whitespace string, meaning that there are no tags defined.

1.5.3 Guix conventions for packaging a program that does not provide releases

There are already suckless packages in the upstream Guix repository that do not rely on published releases, such as lchat or libutf

Here's the section of suckless.scm that handles libutf: (as of commit e8fc56a3d)

(define-public libutf
  (let ((revision "1")
        (commit "ff4c60635e1f455b0a0b4200f8183fbd5a88225b"))
    (package
      (name "libutf")
      (version (string-append "0.0.0-" revision "." (string-take commit 7)))
      (source
       (origin
         (method git-fetch)
         (uri (git-reference
               (url "https://github.com/cls/libutf")
               (commit commit)))
         (file-name (string-append name "-" version "-checkout"))
         (sha256
          (base32
           "1ih5vjavilzggyr1j1z6w1z12c2fs5fg77cfnv7ami5ivsy3kg3d"))))
      (build-system gnu-build-system)
      (arguments
       `(#:tests? #f ; No tests
                  #:make-flags (list "CC=gcc"
                                     (string-append "PREFIX=" %output))
                  #:phases
                  (modify-phases %standard-phases
                    (delete 'configure)))) ; No configure script
      (inputs
       `(("gawk" ,gawk)))
      (home-page "https://github.com/cls/libutf")
      (synopsis "Plan 9 compatible UTF-8 library")
      (description
       "This is a C89 UTF-8 library, with an API compatible with that of
Plan 9's libutf, but with a number of improvements:

@itemize
@item Support for runes beyond the Basic Multilingual Plane.
@item utflen and utfnlen cannot overflow on 32- or 64-bit machines.
@item chartorune treats all invalid codepoints as though Runeerror.
@item fullrune, utfecpy, and utfnlen do not overestimate the length
of malformed runes.
@item An extra function, charntorune(p,s,n), equivalent to
fullrune(s,n) ? chartorune(p,s): 0.
@item Runeerror may be set to an alternative replacement value, such
as -1, to be used instead of U+FFFD.
@end itemize\n")
      (license license:expat))))

1.5.4 writing the package definition

Here's the quark package definition:

(use-modules (guix)
             (guix build-system gnu)
             (guix licenses)
             (guix git-download))

(package
      (name "quark")
      (version (string-append "0.0.0-" "1" "." (string-take "32223c96bdee8f94980d3a1877a643a4d59f897f" 7)))
      (source
       (origin
         (method git-fetch)
         (uri (git-reference
               (url "https://git.suckless.org/quark")
               (commit "32223c96bdee8f94980d3a1877a643a4d59f897f")))
         (file-name (string-append name "-" version "-checkout"))
         (sha256
          (base32 "1sxh50shn1g6smr63n0impdpk6bfjj0ill805ng318i6ipz7nvb2"))))

      (build-system gnu-build-system) ;; TODO: explain why we use this
                                      ;; build system rather than
                                      ;; something else -- you can
                                      ;; imagine an interpretation
                                      ;; that considers the use of
                                      ;; this name to be misleading.
      (arguments
       `(#:tests? #f ; No tests
                  #:make-flags (list "CC=gcc"
                                     (string-append "PREFIX=" %output))
                  #:phases
                  (modify-phases %standard-phases
                    (delete 'configure)))) ; No configure script

      (home-page "https://tools.suckless.org/quark/")
      (synopsis "minimal HTTP server")
      (description
       "quark is an extremely small and simple HTTP GET/HEAD-only web server for static content.")
      (license isc))

This is mostly copypasta from the libutf package definition.

Most values like the commit hash, git url, etc can be plugged in without using guix tools. One exception is the origin hash string.

<this did not work>

guix hash --recursive $QUARK_PARENT/quark/

</this did not work>

The procedure above failed to create the correct string. Guix printed a notice about the string that it expected, and when I pasted in that string the package built as I expected. TODO explain what happened here.

1.5.5 testing the package definition

(note that I haven't figured out how to juggle the processes created by this shell code such that I can run the code in org-babel)

guix build -f ./quark.scm
[[ -d /tmp/trivial-website ]] || mkdir  /tmp/trivial-website && echo "not creating dir that already exists"
echo "<html><head><title>trivial website</title></head><body>this is a trivial website for testing quark http server</body></html>" > /tmp/trivial-website/index.html
# sudo to allow quark to use chroot
sudo /gnu/store/qsaazn8iy1bwrr09w3jamchmvz9icd10-quark-0.0.0-1.32223c9/bin/quark -h localhost -p 8080 -d /tmp/trivial-website/ &
curl http://localhost:8080/index.html -o /tmp/downloaded-index.html 
sudo pkill quark
cat /tmp/downloaded-index.html
<html><head><title>trivial website</title></head><body>this is a trivial website for testing quark http server</body></html>

1.5.6 IN_PROGRESS Submitting the Quark package definition to GNU

Guix developers and users should have a copy of the Guix source code. In my case the local repo has a remote pointing to a mirror hosted at Microsoft Github

  1. IN_PROGRESS `package` vs `define-public`

    Where can we see the source code for these two functions?

1.6 TODO racktris: a simple video game which expects dependencies to be installed with language-specific package manager `raco`

as of <2019-08-03 Sat> this section is somewhat different from the preceding sections, in that I can't really introduce the section by describing what I am trying to do in a relatively exact way.

Author: Coffee Crayon

Created: 2019-08-13 Tue 10:40

Validate