This is the third post in a series showcasing advanced features of Guix. Previous posts can be found below:
If you've used Guix a while you may have heard of G-expressions. You may have even used them. But what are they? The inetd system test says it best:
Like an S-expression but with a G.
Even as a seasoned Guix developer, when G-expressions were introduced it took me a long time before having that "gee, I'm starting to understand how this all works" moment. Becoming proficient at it took even longer.
"Give me a break, I've only been working here five years!"
The academically inclined may want to check out this paper for an in-depth introduction. I'll try to break the concepts down a little for laypeople or learning challenged (like myself).
(Be sure to also check out the Guix manual)
I think of G-expressions as a scripting language with the usual niceties of Guix: reproducible and cached computations. It's an extension of the Scheme language that brings the power of all 20k+ packages in Guix with it.
Here is an example from my system configuration:
(use-modules (guix gexp)
(gnu artwork)
(gnu packages inkscape))
(define background
(computed-file
"guix-background.png"
#~(begin
(let ((inkscape #+(file-append inkscape "/bin/inkscape"))
(backgrounds #$(file-append %artwork-repository "/backgrounds")))
(system* inkscape (string-append "--export-filename=" #$output)
(string-append backgrounds "/guix-silver-checkered-16-9.svg")
"-w" "2560" "-h" "1440")))))
This computes a nice PNG file from the raw SVG logo found in the guix artwork repository.
This computed PNG is then used in my window manager configuration:
(define %sway-config
(mixed-text-file
"sway-config"
[...]
### Output configuration
#
# Wallpaper
output * bg " background " fill
mixed-text-file
is an N-argument procedure that creates a text file from the strings and variables
passed as arguments. A more gexp-y way to declare the configuration would be to use
computed-file
like so:
(define %sway-config
(computed-file
"sway-config"
#~(call-with-output-file #$output
[...]
# Wallpaper
output * bg #$background fill
In both cases the result is the same: the background we computed above is inserted into the configuration file with the full /gnu/store/... file name.
If the SVG file (or Inkscape) is updated, the background will be computed anew, fully transparent to the configuration. Otherwise the cached result is used.
This makes for a very powerful scripting language where all the programs you need are "already installed", and each computation is cached. If any of the inputs (e.g. packages) to the script change, the cache is automatically invalidated.
The gexp machinery works "out of the box" in the context of guix build
,
inside manifests, etc. It took me a while to figure out how to access this
in general-purpose scripts. Here is a toy example that calculates the
factorial of the given argument and saves the result in the store:
#!/usr/bin/env -S guile --no-auto-compile
;; -*- mode: Scheme;-*-
!#
(use-modules (guix gexp)
(guix derivations)
(guix store))
(define (calculate-factorial)
(let ((argument (cadr (command-line))))
(gexp->derivation (string-append "factorial-of-" argument)
#~(begin
(use-modules (ice-9 format))
(define (factorial n)
(if (zero? n)
1
(* n (factorial (- n 1)))))
(let ((result (factorial (string->number #$argument))))
(call-with-output-file #$output
(lambda (port)
(format port "~d~%" result))))))))
(let* ((store (open-connection))
(drv (run-with-store store (calculate-factorial)))
(output (derivation->output-path drv)))
(build-derivations store (list drv))
(format #t "~a~%" output))
It can be run with guile toy.scm
or made executable and run directly:
$ ./toy.scm 42
/gnu/store/brmr07dx7mhrm9f0wfic2rvx22dgyrzz-factorial-of-42
$ cat $(!!)
1405006117752879898543142606244511569936384000000000
Calculating the factorial of 25000 took 4 seconds on my machine. Running the same command again hit the cache and took 0.7 seconds.