Page:Ludovic Courtès - Functional Package Management with Guix.djvu/3

From Wikisource
Jump to navigation Jump to search
This page has been proofread, but needs to be validated.

with builder is substituted with that file name. This ${…} form on line 3 for string interpolation makes it easy to insert Nix-language values, and in particular computed file names, in the contents of build scripts. The Nix-based GNU/Linux distribution, NixOS, has most of its build scripts written in Bash, and makes heavy use of string interpolation on the Nix-language side.

All the files referenced by derivations live under /nix/store, called the store. In a multi-user setup, users have read-only access to the store, and all other accesses to the store are mediated by a daemon running as root. Operations such as importing files in the store, computing a derivation, building a derivation, or running the garbage collector are all implemented as remote procedure calls (RPCs) to the daemon. This guarantees that the store is kept in a consistent state—e.g., that referenced files and directories are not garbage-collected, and that the contents of files and directories are genuine build results of the inputs hashed in their name.

The implementation of the Nix language is an interpreter written in C++. In terms of performance, it does not compete with typical general-purpose language implementations; that is often not a problem given its specific use case, but sometimes requires rewriting functions, such as list-processing tools, as language primitives in C++. The language itself is not extensible: it has no macros, a fixed set of data types, and no foreign function interface.

2.3 From Nix to Guix

Our main contribution with GNU Guix is the use of Scheme for both the composition and description of build processes, and the implementation of build scripts. In other words, Guix builds up on the build and deployment primitives of Nix, but replaces the Nix language by Scheme with embedded domain-specific languages (EDSLs), and promotes Scheme as a replacement for Bash in build scripts. Guix is implemented using GNU Guile 2.0[n 1], a rich implementation of Scheme based on a compiler and bytecode interpreter that supports the R5RS and R6RS standards. It reuses the build primitives of Nix by making remote procedure calls (RPCs) to the Nix build daemon.

We claim that using an embedded DSL has numerous practical benefits over an independent DSL: tooling (use of Guile’s compiler, debugger, and REPL, Unicode support, etc.), libraries (SRFIs, internationalization support, etc.), and seamless integration in larger programs. To illustrate this last point, consider an application that traverses the list of available packages and processes it—for instance to filter packages whose name matches a pattern, or to render it as HTML. A Scheme program can readily and efficiently do it with Guix, where packages are first-class Scheme objects; conversely, writing such an implementation with an external DSL such as Nix requires either extending the language implementation with the necessary functionality, or interfacing with it via an external representation such as XML, which is often inefficient and lossy.

We show that use of Scheme in build scripts is natural, and can achieve conciseness comparable to that of shell scripts, but with improved expressivity and clearer semantics.

The next section describes the main programming interfaces of Guix, with a focus on its high-level package description language and "shell programming" substitutes provided to builder-side code.


3. BUILD EXPRESSIONS AND PACKAGE DESCRIPTIONS

Our goal when designing Guix was to provide interfaces ranging from Nix’s low-level primitives such as derivation to high-level package declarations. The declarative interface is a requirement to help grow and maintain a large software distribution. This section describes the three level of abstractions implemented in Guix, and illustrates how Scheme’s homoiconicity and extensibility were instrumental.

3.1 Low-Level Store Operations

As seen above, derivations are the central concept in Nix. A derivation bundles together a builder and its execution environment: command-line arguments, environment variable definitions, as well as a list of input derivations whose result should be accessible to the builder. Builders are typically executed in a chroot environment where only those inputs explicitly listed are visible. Guix transposes Nix’s derivation primitive literally to its Scheme interface.

(let* ((store (open-connection))
       (bash (add-to-store store "static-bash" 
                           #t "sha256" 
                           "./static-bash"))) 
  (derivation store "example-1.0"  
              "x86_64 -linux" 
              bash 
              ’("-c" "echo hello > $out") 
              ’() ’())) 

=> 
''"/nix/store/nsswy''…-example-1.0.drv"
#<derivation "example-1.0" …>

Figure 2: Using the derivation primitive in Scheme with Guix.

Figure 2 shows the example of Figure 1 rewritten to use Guix’s low-level Scheme API. Notice how the former makes explicit several operations not visible in the latter. First, line 1 establishes a connection to the build daemon; line 2 explicitly asks the daemon to “intern” file static-bash into the store; finally, the derivation call instructs the daemon to compute the given derivation. The two arguments on line 9 are a set of environment variable definitions to be set in the build environment (here, it’s just the empty list), and a set of inputs other derivations depended on, and whose result must be available to the build process. Two values are returned (line 11): the file name of the on-disk representation of the derivation, and its in-memory representation as a Scheme record.

The build actions represented by this derivation can then be performed by passing it to the build-derivations RPC.

  1. http://www.gnu.org/software/guile/