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

This page has been proofread, but needs to be validated.
  1. run make install and patch shebangs in installed files.

Of course, that is all implemented in Scheme, via build-expression->derivation. Supporting code is available as a build-side module that gnu-build-system automatically adds as an input to its build scripts. The default build programs just call the procedure of that module that runs the above phases.

The (guix build gnu-build-system) module contains the implementation of the above phases; it is imported on the builder side. The phases are modeled as follows: each phase is a procedure accepting several keyword arguments, and ignoring any keyword arguments it does not recognize.[n 1] For instance the configure procedure is in charge of running the package’s ./configure script; that procedure honors the #:configure-flags keyword parameter seen on Figure 4. Similarly, the build, check, and install procedures run the make command, and all honor the #:make-flags keyword parameter.

All the procedures implementing the standard phases of the GNU build system are listed in the %standard-phases builder-side variable, in the form of a list of phase name-/procedure pairs. The entry point of the builder-side code of gnu-build-system is shown on Figure 7. It calls all the phase procedures in order, by default those listed in the %standard-phases association list, passing them all the arguments it got; its return value is true when every procedure’s return value is true.

(define howdy 
  (package (inherit hello) 
    (arguments 
      (#:phases 
        (alist-cons -after 
          configure change-hello 
          (lambda* (#:key system #:allow-other-keys) 
            (substitute* "src/hello.c" 
             (("Hello, world!") 
              (string-append "Howdy! Running on " 
                             system ".")))) 
          %standard-phases)))))
Figure 8
Package specification with custom build phases.

The arguments field, shown on Figure 4, allows users to pass keyword arguments to the builder-side code. In addition to the #:configure-flags argument shown on the figure, users may use the #:phases argument to specify a different set of phases. The value of the #:phases must be a list of phase name/procedure pairs, as discussed above. This allows users to arbitrarily extend or modify the behavior of the build system. Figure 8 shows a variant of the definition in Figure 4 that adds a custom build phase. The alist-cons-after procedure is used to add a pair with change-hello as its first item and the lambda* as its second item right after the pair in %standard-phases whose first item is configure; in other words, it reuses the standard build phases, but with an additional change-hello phase right after the configure phase. The whole alist-cons-after expression is evaluated on the builder side.

This approach was inspired by that of NixOS, which uses Bash for its build scripts. Even with "advanced" Bash features such as functions, arrays, and associative arrays, the phases mechanism in NixOS remains limited and fragile, often leading to string escaping issues and obscure error reports due to the use of eval. Again, using Scheme instead of Bash unsurprisingly allows for better code structuring, and improves flexibility.

Other build systems are provided. For instance, the standard build procedure for Perl packages is slightly different: mainly, the configuration phase consists in running perl Makefile. - PL, and test suites are run with make test instead of make check. To accommodate that, Guix provides perl-build-system. Its companion build-side module essentially calls out to that of gnu-build-system, only with appropriate configure and check phases. This mechanism is similarly used for other build systems such as CMake and Python’s build system.

 
(substitute* (find-files "gcc/config" 
                         "^gnu-user(64)?.h$") 
  (("#define LIB_SPEC (.*)$" _ suffix) 
   (string-append "#define LIB_SPEC "-L" libc 
                  "/lib " " suffix "")) 
  (("#define STARTFILE_SPEC.*$" line) 
   (string-append "#define STARTFILE_PREFIX_1 "" 
                  libc "/lib\" \n" line)))
Figure 9
The substitute* macro for sed-like substitutions.


Build programs often need to traverse file trees, modify files according to a given pattern, etc. One example is the "patch shebang" phase mentioned above: all the source files must be traversed, and those starting with #! are candidate to patching. This kind of task is usually associated with "shell programming"—as is the case with the build scripts found in NixOS, which are written in Bash, and resort to sed, find, etc. In Guix, a build-side Scheme module provides the necessary tools, built on top of Guile’s operating system interface. For instance, find-files returns a list of files whose names matches a given pattern; patch-shebang performs the #! adjustment described above; copy-recursively and delete-recursively are the equivalent, respectively, of the shell cp -r and rm -rf/ commands; etc.

An interesting example is the substitute* macro, which does sed-style substitution on files. Figure 9 illustrates its use to patch a series of files returned by find-files. There are two clauses, each with a pattern in the form of a POSIX regular expression; each clause’s body returns a string, which is the substitution for any matching line in the given files. In the first clause’s body, suffix is bound to the submatch corresponding to (.*) in the regexp; in the second clause, line is bound to the whole match for that regexp. This snippet is nearly as concise than equivalent shell code using

  1. Like many Scheme implementations, Guile supports named or keyword arguments as an extension to the R5 and R6RS. In addition, procedure definitions whose formal argument list contains the #: allow-other-keys keyword ignore any unrecognized keyword arguments that they are passed.