* [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. @ 2023-03-21 20:57 ( via Guix-patches via 2023-04-12 15:29 ` Simon Tournier 2023-04-15 22:29 ` [bug#62356] [PAtCH " ( via Guix-patches via 0 siblings, 2 replies; 12+ messages in thread From: ( via Guix-patches via @ 2023-03-21 20:57 UTC (permalink / raw) To: 62356; +Cc: ( * website/posts/dissecting-guix-3-gexps.md: New blog post. --- Heya Guix, Here's the third post in the Dissecting Guix series; this one aims to demystify g-expressions ;) -- ( website/posts/dissecting-guix-3-gexps.md | 673 +++++++++++++++++++++++ 1 file changed, 673 insertions(+) create mode 100644 website/posts/dissecting-guix-3-gexps.md diff --git a/website/posts/dissecting-guix-3-gexps.md b/website/posts/dissecting-guix-3-gexps.md new file mode 100644 index 0000000..32f5d51 --- /dev/null +++ b/website/posts/dissecting-guix-3-gexps.md @@ -0,0 +1,673 @@ +title: Dissecting Guix, Part 3: G-Expressions +date: TBC +author: ( +tags: Dissecting Guix, Functional package management, Programming interfaces, Scheme API +--- +Welcome back to [Dissecting Guix](https://guix.gnu.org/en/blog/tags/dissecting-guix)! +Last time, we discussed [monads](https://guix.gnu.org/en/blog/2023/dissecting-guix-part-2-the-store-monad), +the functional programming idiom used by Guix to thread a store connection +through a series of store-related operations. + +Today, we'll be talking about a concept rather more specific to Guix: +_g-expressions_. Being an implementation of the Scheme language, Guile is built +around [_s-expressions_](https://en.wikipedia.org/wiki/S-expression), which can +represent, as the saying goes, _code as data_, thanks to the simple structure of +Scheme forms. + +As Guix's package recipes are written in Scheme, it naturally needs some way to +represent code that is to be run only when the package is built. Additionally, +there needs to be some way to reference dependencies and retrieve output paths; +otherwise, you wouldn't be able to, for instance, create a phase to install a +file in the output directory. + +So, how do we implement this "deferred" code? Well, initially Guix used plain +old s-expressions for this purpose. + +# Once Upon a Time + +Let's say we want to create a store item that's just a symlink to the +`bin/irssi` file of the `irssi` package. How would we do that with an +s-expression? Well, the s-expression itself, which we call the _builder_, is +fairly simple: + +```scheme +(define sexp-builder + `(let* ((out (assoc-ref %outputs "out")) + (irssi (assoc-ref %build-inputs "irssi")) + (bin/irssi (string-append irssi "/bin/irssi"))) + (symlink bin/irssi out))) +``` + +If you aren't familliar with the "quoting" syntax used to create s-expressions, +I strongly recommend that you read the excellent Scheme Primer; specifically, +section 7, [_Lists and +"cons"_](https://spritely.institute/static/papers/scheme-primer.html#scheme-lists-and-cons) +and section 11, [_On the extensibility of Scheme (and Lisps in +general)_](https://spritely.institute/static/papers/scheme-primer.html#scheme-extensibility) + +The `%outputs` and `%build-inputs` variables are bound within builder scripts to +_association lists_, which are lists of pairs that act like key/value stores, +for instance: + +```scheme +'(("foo" . "bar") + ("floob" . "blarb") + ("fvoolag" . "bvarlag")) +``` + +To retrieve values from association lists, which are often referred to as +_alists_, we use the `assoc-ref` procedure: + +```scheme +(assoc-ref '(("boing" . "bouncy") + ("floing" . "flouncy")) + "boing") +⇒ "bouncy" +``` + +`%outputs`, as the name might suggest, maps derivation output names to the paths +of their respective store items, the default output being `out`, and +`%build-inputs` maps inputs labels to their store items. + +The builder is the easy part; we now need to turn it into a derivation and tell +it what `"irssi"` actually refers to. For this, we use the +`build-expression->derivation` procedure from `(guix derivations)`: + +```scheme +(use-modules (guix derivations) + (guix packages) + (guix store) + (gnu packages guile) + (gnu packages irc)) + +(with-store store + (let ((guile-3.0-drv (package-derivation store guile-3.0)) + (irssi-drv (package-derivation store irssi))) + (build-expression->derivation store "irssi-symlink" sexp-builder + #:guile-for-build guile-3.0-drv + #:inputs `(("irssi" ,irssi-drv))))) +⇒ #<derivation /gnu/store/…-irssi-symlink.drv => /gnu/store/…-irssi-symlink …> +``` + +There are several things to note here: + +- The inputs _must_ all be derivations, so we need to first convert the packages + using `package-derivation`. +- We need to explicitly set `#:guile-for-build`; there's no default value. +- The `build-expression->derivation` and `package-derivation` procedures are + _not_ monadic, so we need to explicitly pass them the store connection. + +The shortcomings of using s-expressions in this way are numerous: we have to +convert everything to a derivation before using it, and _inputs are not an +inherent aspect of the builder_. G-expressions were designed to overcome these +issues. + +# Premortem Examination + +A gexp is fundumentally a record of type `<gexp>`, which is, naturally, defined +in `(guix gexp)`. The two most important fields of this record type, out of a +total of five, are `proc` and `references`; the former is a procedure that +returns the equivalent sexp, the latter a list containing everything from the +"outside world" that's used by the gexp. + +When we want to turn the gexp into something that we can actually run as code, +we combine these two fields by first building any gexp inputs that can become +derivations (leaving alone those that cannot, such as and then passing the built +`references` as the arguments of `proc`. + +Here's an example gexp that is essentially equivalent to our `sexp-builder`: + +```scheme +(use-modules (guix gexp)) + +(define gexp-builder + #~(symlink #$(file-append irssi "/bin/irssi") + #$output)) +``` + +`gexp-builder` is far more concise than `sexp-builder`; let's examine the syntax +and the `<gexp>` object we've created. To make a gexp, we use the `#~` syntax, +equivalent to the `gexp` macro, rather than the `quasiquote` backtick used to +create sexps. + +When we want to embed values from outside as references, we use `#$`, or +`ungexp`, which is, in appearance if not function, equivalent to `unquote` +(`,`). `ungexp` can accept any of four reference types: + +- Sexps (strings, lists, etc), which will be embedded literally. +- Other gexps, embedded literally. +- Expressions returning any sort of object that can be lowered into a + derivation, such as `<package>`, embedding that object's `out` store item; if + the expression is specifically a symbol bound to a buildable object, you can + optionally follow it with a colon and an alternative output name, so + `package:lib` is permitted, but `(get-package):lib` isn't. +- The symbol `output`, embedding an output path. Like symbols bound to + buildable objects, this can be followed by a colon and the output name that + should be used rather than the default `out`. + +All these reference types will be represented by `<gexp-input>` records in the +`references` field, except for the last kind, which will become `<gexp-output>` +records. To give an example of each type of reference (with the return value +output formatted for easier reading): + +```scheme +(use-modules (gnu packages glib)) + +#~(list #$"foobar" ;s-expression + #$#~(string-append "foo" "bar") ;g-expression + #$(file-append irssi "/bin/irssi") ;buildable object (expression) + #$glib:bin ;buildable object (symbol) + #$output:out) ;output +⇒ #<gexp (list #<gexp-input "foobar":out> + #<gexp-input #<gexp (string-append "foo" "bar") …>:out> + #<gexp-input #<file-append #<package irssi@1.4.3 …> "/bin/irssi">:out> + #<gexp-input #<package glib@2.70.2 …>:bin> + #<gexp-output out>) …> +``` + +Note the use of `file-append` in both the previous example and `gexp-builder`; +this procedure produces a `<file-append>` object that builds its first argument +and is embedded as the concatenation of the first argument's output path and the +second argument, which should be a string. For instance, +`(file-append irssi "/bin/irssi")` builds `irssi` and expands to +`/gnu/store/…-irssi/bin/irssi`, rather than the `/gnu/store/…-irssi` that the +package alone would be embedded as. + +So, now that we have a gexp, how do we turn it into a derivation? This process +is known as _lowering_; it entails the use of the aptly-named `lower-gexp` +monadic procedure to combine `proc` and `references` and produce a +`<lowered-gexp>` record, which acts as a sort of intermediate representation +between gexps and derivations. We can piece apart this lowered form to get a +sense of what the final derivation's builder script would look like: + +```scheme +(define lowered-gexp-builder + (with-store store + (run-with-store store + (lower-gexp gexp-builder)))) + +(lowered-gexp-sexp lowered-gexp-builder) +⇒ (symlink + "/gnu/store/…-irssi-1.4.3/bin/irssi" + ((@ (guile) getenv) "out")) +``` + +And there you have it: a s-expression compiled from a g-expression, ready to be +written into a builder script file in the store. So, how exactly do you turn +this into said derivation? + +Well, it turns out that there isn't an interface for turning lowered gexps into +derivations, only one for turning regular gexps into derivations that first uses +`lower-gexp`, then implements the aforementioned conversion internally, rather +than outsourcing it to some other procedure, so that's what we'll use. + +Unsurprisingly, that procedure is called `gexp->derivation`, and unlike its sexp +equivalent, it's monadic. (`build-expression->derivation` and other deprecated +procedures were in Guix since before the monads system existed.) + +```scheme +(with-store store + (run-with-store store + (gexp->derivation "irssi-symlink" gexp-builder))) +⇒ #<derivation /gnu/store/…-irssi-symlink.drv => /gnu/store/…-irssi-symlink …> +``` + +Finally, we have a gexp-based equivalent to the derivation we earlier created +with `build-expression->derivation`! Here's the code we used for the sexp +version in full: + +```scheme +(define sexp-builder + `(let* ((out (assoc-ref %outputs "out")) + (irssi (assoc-ref %build-inputs "irssi")) + (bin/irssi (string-append irssi "/bin/irssi"))) + (symlink bin/irssi out))) + +(with-store store + (let ((guile-3.0-drv (package-derivation store guile-3.0)) + (irssi-drv (package-derivation store irssi))) + (build-expression->derivation store "irssi-symlink" sexp-builder + #:guile-for-build guile-3.0-drv + #:inputs `(("irssi" ,irssi-drv))))) +``` + +And here's the gexp equivalent: + +```scheme +(define gexp-builder + #~(symlink #$(file-append irssi "/bin/irssi") + #$output)) + +(with-store store + (run-with-store store + (gexp->derivation "irssi-symlink" gexp-builder))) +``` + +That's a lot of complexity abstracted away! For more complex packages and +services, especially, gexps are a lifesaver; you can refer to the output paths +of inputs just as easily as you would a string constant. You do, however, have +to watch out for situations where `ungexp-native`, written as `#+`, would be +preferable over regular `ungexp`, and that's something we'll discuss later. + +A brief digression before we continue: if you'd like to look inside a `<gexp>` +record, but you'd rather not build anything, you can use the +`gexp->approximate-sexp` procedure, which replaces all references with dummy +values: + +```scheme +(gexp->approximate-sexp gexp-builder) +⇒ (symlink (*approximate*) (*approximate*)) +``` + +# The Lowerable-Object Hardware Shop + +We've seen two examples already of records we can turn into derivations, which +are generally referred to as _lowerable objects_ or _file-like objects_: + +- `<package>`, a Guix package. +- `<file-append>`, which wraps another lowerable object and appends a string to + the embedded output path when ungexped. + +There are many more available to us. Recall from the previous post, +[_The Store Monad_](https://guix.gnu.org/en/blog/2023/dissecting-guix-part-2-the-store-monad), +that Guix provides the two monadic procedures `text-file` and `interned-file`, +which can be used, respectively, to put arbitrary text or files from the +filesystem in the store, returning the path to the created item. + +This doesn't work so well with gexps, though; you'd have to wrap each ungexped +use of either of them with `(with-store store (run-with-store store …))`, which +would be quite tedious. Thankfully, `(guix gexp)` provides the `plain-file` and +`local-file` procedures, which return equivalent lowerable objects. This code +example builds a directory containing symlinks to files greeting the world: + +```scheme +(use-modules (guix monads) + (ice-9 ftw) + (ice-9 textual-ports)) + +(define (build-derivation monadic-drv) + (with-store store + (run-with-store store + (mlet* %store-monad ((drv monadic-drv)) + (mbegin %store-monad + ;; BUILT-DERIVATIONS is the monadic version of BUILD-DERIVATIONS. + (built-derivations (list drv)) + (return (derivation-output-path + (assoc-ref (derivation-outputs drv) "out")))))))) + +(define world-greeting-output + (build-derivation + (gexp->derivation "world-greeting" + #~(begin + (mkdir #$output) + (symlink #$(plain-file "hi-world" + "Hi, world!") + (string-append #$output "/hi")) + (symlink #$(plain-file "hello-world" + "Hello, world!") + (string-append #$output "/hello")) + (symlink #$(plain-file "greetings-world" + "Greetings, world!") + (string-append #$output "/greetings")))))) + +;; We turn the list into multiple values using (APPLY VALUES …). +(apply values + (map (lambda (file-path) + (let* ((path (string-append world-greeting-output "/" file-path)) + (contents (call-with-input-file path get-string-all))) + (list path contents))) + ;; SCANDIR from (ICE-9 FTW) returns the list of all files in a + ;; directory (including ``.'' and ``..'', so we remove them with the + ;; second argument, SELECT?, which specifies a predicate). + (scandir world-greeting-output + (lambda (path) + (not (or (string=? path ".") + (string=? path ".."))))))) +⇒ ("/gnu/store/…-world-greeting/greetings" "Greetings, world!") +⇒ ("/gnu/store/…-world-greeting/hello" "Hello, world!") +⇒ ("/gnu/store/…-world-greeting/hi" "Hi, world!") +``` + +Note that we define a procedure for building the output; we will need to build +more derivations in a very similar fashion later, so it helps to have this to +reuse instead of copying the code in `world-greeting-output`. + +There are many other useful lowerable objects available as part of the gexp +library. These include `computed-file`, which accepts a gexp that builds +the output file, `program-file`, which creates an executable Scheme script in +the store using a gexp, and `mixed-text-file`, which allows you to, well, mix +text and lowerable objects; it creates a file from the concatenation of a +sequence of strings and file-likes. The +[G-Expressions](https://guix.gnu.org/manual/en/html_node/G_002dExpressions.html) +manual page has more details. + +So, you may be wondering, at this point: there's so many lowerable objects +included with the gexps library, surely there must be a way to define more? +Naturally, there is; this is Scheme, after all! We simply need to acquaint +ourselves with the `define-gexp-compiler` macro. + +The most basic usage of `define-gexp-compiler` essentially creates a procedure +that takes as arguments a record to lower, the host system, and the target +system, and returns a derivation or store item as a monadic value in +`%store-monad`. + +Let's try implementing a lowerable object representing a file that greets the +world. First, we'll define the record type: + +```scheme +(use-modules (srfi srfi-9)) + +(define-record-type <greeting-file> + (greeting-file greeting) + greeting? + (greeting greeting-file-greeting)) +``` + +Now we use `define-gexp-compiler` like so; note how we can use `lower-object` +to compile down any sort of lowerable object into the equivalent store item or +derivation; essentially, `lower-object` is just the procedure for applying the +right gexp compiler to an object: + +```scheme +(use-modules (ice-9 i18n)) + +(define-gexp-compiler (greeting-file-compiler + (greeting-file <greeting-file>) + system target) + (lower-object + (let ((greeting (greeting-file-greeting greeting-file))) + (plain-file (string-append greeting "-greeting") + (string-append (string-locale-titlecase greeting) ", world!"))))) +``` + +Let's try it out now. Here's how we could rewrite our greetings directory +example from before using `<greeting-file>`: + +```scheme +(define world-greeting-2-output + (build-derivation + (gexp->derivation "world-greeting-2" + #~(begin + (mkdir #$output) + (symlink #$(greeting-file "hi") + (string-append #$output "/hi")) + (symlink #$(greeting-file "hello") + (string-append #$output "/hello")) + (symlink #$(greeting-file "greetings") + (string-append #$output "/greetings")))))) + +(apply values + (map (lambda (file-path) + (let* ((path (string-append world-greeting-2-output + "/" file-path)) + (contents (call-with-input-file path get-string-all))) + (list path contents))) + (scandir world-greeting-2-output + (lambda (path) + (not (or (string=? path ".") + (string=? path ".."))))))) +⇒ ("/gnu/store/…-world-greeting-2/greetings" "Greetings, world!") +⇒ ("/gnu/store/…-world-greeting-2/hello" "Hello, world!") +⇒ ("/gnu/store/…-world-greeting-2/hi" "Hi, world!") +``` + +Now, this is probably not worth a whole new gexp compiler. How about something +a bit more complex? Sharp-eyed readers who are trying all this in the REPL may +have noticed the following output when they used `define-gexp-compiler` +(formatted for ease of reading): + +```scheme +⇒ #<<gexp-compiler> + type: #<record-type <greeting-file>> + lower: #<procedure … (greeting-file system target)> + expand: #<procedure default-expander (thing obj output)>> +``` + +Now, the purpose of `type` and `lower` is self-explanatory, but what's this +`expand` procedure here? Well, if you recall `file-append`, you may realise +that the text produced by a gexp compiler for embedding into a gexp doesn't +necessarily have to be the exact output path of the produced derivation. + +There turns out to be another way to write a `define-gexp-compiler` form that +allows you to specify _both_ the lowering procedure, which produces the +derivation or store item, and the expanding procedure, which produces the text. + +Let's make another record; this one will let us build a store item containing a +`bin` directory with multiple scripts inside, and expand to the full path to +that script. + +```scheme +(define-record-type <script-directory> + (script-directory scripts) + script-directory? + (scripts script-directory-scripts)) +``` + +Here's how we define both a compiler and expander for our new record: + +```scheme +(define-gexp-compiler script-directory-compiler <script-directory> + compiler => (lambda (obj system target) + (gexp->derivation "script-directory" + #~(let ((bindir (string-append #$output "/bin"))) + (mkdir #$output) + (mkdir bindir) + (for-each + (lambda (pair) + (let* ((name (car pair)) + (path (cdr pair)) + (bin-path (string-append bindir "/" name))) + (symlink path bin-path))) + #$(script-directory-scripts obj))))) + expander => (lambda (obj drv output) + (string-append output "/bin/" (script-directory-name obj)))) +``` + +Let's try this out now: + +```scheme +(use-modules (gnu packages vim)) + +(define script-directory-output + (build-derivation + (lower-object + (script-directory + #~'(("irc" . #$(file-append irssi "/bin/irssi")) + ("editor" . #$(file-append neovim "/bin/nvim"))))))) + +(scandir (string-append script-directory-output "/bin")) +⇒ ("." ".." "editor" "irc") +``` + +Who knows why you'd want to do this, but it certainly works! We've looked at +why we need gexps, how they work, and how to extend them, and we've now only got +two more advanced features to cover: cross-build support, and modules. + +# Importing External Modules + +Let's try using one of the helpful procedures from the `(guix build utils)` +module in a gexp. + +```scheme +(define silly-directory-output + (build-derivation + (gexp->derivation "silly-directory" + #~(begin + (use-modules (guix build utils)) + (mkdir-p (string-append #$output "/what/a/silly/directory")))))) +``` + +Looks fine, right? We've even got a `use-modules` in th-- + +```Scheme +ERROR: + 1. &store-protocol-error: + message: "build of `/gnu/store/…-silly-directory.drv' failed" + status: 100 +``` + +OUTRAGEOUS. Fortunately, there's an explanation to be found in the Guix build +log directory, `/var/log/guix/drvs`; locate the file using the first two +characters of the store hash as the subdirectory, and the rest as the file name, +and remember to use `zcat` or `zless`, as the logs are gzipped: + +```scheme +Backtrace: + 9 (primitive-load "/gnu/store/…") +In ice-9/eval.scm: + 721:20 8 (primitive-eval (begin (use-modules (guix build #)) (?))) +In ice-9/psyntax.scm: + 1230:36 7 (expand-top-sequence ((begin (use-modules (guix ?)) #)) ?) + 1090:25 6 (parse _ (("placeholder" placeholder)) ((top) #(# # ?)) ?) + 1222:19 5 (parse _ (("placeholder" placeholder)) ((top) #(# # ?)) ?) + 259:10 4 (parse _ (("placeholder" placeholder)) (()) _ c&e (eval) ?) +In ice-9/boot-9.scm: + 3927:20 3 (process-use-modules _) + 222:17 2 (map1 (((guix build utils)))) + 3928:31 1 (_ ((guix build utils))) + 3329:6 0 (resolve-interface (guix build utils) #:select _ #:hide ?) + +ice-9/boot-9.scm:3329:6: In procedure resolve-interface: +no code for module (guix build utils) +``` + +It turns out `use-modules` can't actually find `(guix build utils)` at all. +There's no typo; it's just that to ensure the build is isolated, Guix builds +`module-import` and `module-importe-compiled` directories, and sets the +_Guile module path_ within the build environment to contain said directories, +along with those containing the Guile standard library modules. + +So, what to do? Turns out one of the fields in `<gexp>` is `modules`, which, +funnily enough, contains the names of the modules which will be used to build +the aforementioned directories. To add to this field, we use the +`with-imported-modules` macro. (`gexp->derivation` _does_ provide a `modules` +parameter, but `with-imported-modules` lets you add the required modules +directly to the gexp value, rather than later on.) + +```scheme +(define silly-directory-output + (build-derivation + (gexp->derivation "silly-directory" + (with-imported-modules '((guix build utils)) + #~(begin + (use-modules (guix build utils)) + (mkdir-p (string-append #$output "/what/a/silly/directory"))))))) + +silly-directory-output +⇒ "/gnu/store/…-silly-directory" +``` + +It works, yay. It's worth noting that while passing just the list of modules to +`with-imported-modules` works in this case, this is only because +`(guix build utils)` has no dependencies on other Guix modules. Were we to try +adding, say, `(guix build emacs-build-system)`, we'd need to use the +`source-module-closure` procedure to add its dependencies to the list: + +```scheme +(source-module-closure '((guix build emacs-build-system))) +⇒ ((guix build emacs-build-system) + (guix build gnu-build-system) + (guix build utils) + (guix build gremlin) + (guix elf) + (guix build emacs-utils)) +``` + +Here's another scenario: what if we want to use a module not from Guix or Guile +but a third-party library? In this example, we'll use [guile-json +](https://github.com/aconchillo/guile-json), a library for converting between +S-expressions and [JavaScript Object Notation](https://json.org). + +We can't just `with-imported-modules` its modules, since it's not part of Guix, +so `<gexp>` provides another field for this purpose: `extensions`. Each of +these extensions is a lowerable object that produces a Guile package directory; +so usually a package. Let's try it out. + +```scheme +(use-modules (gnu packages guile)) + +(define helpful-guide-output + (build-derivation + (gexp->derivation "json-file" + (with-extensions (list guile-json-4) + #~(begin + (use-modules (json)) + (mkdir #$output) + (call-with-output-file (string-append #$output "/helpful-guide.json") + (lambda (port) + (scm->json '((truth . "Guix is the best!") + (lies . "Guix isn't the best!")) + port)))))))) + +(call-with-input-file + (string-append helpful-guide-output "/helpful-guide.json") + get-string-all) +⇒ "{\"truth\":\"Guix is the best!\",\"lies\":\"Guix isn't the best!\"}" +``` + +Amen to that, `helpful-guide.json`. Before we continue on to cross-compilation, +there's one last feature of `with-imported-modules` you should note. We can +add modules to a gexp by name, but we can also create entirely new ones with +lowerable objects, like this pattern, which is used in several places in the +Guix source code: + +```scheme +(with-imported-modules `(((guix config) => ,(make-config.scm)) + …) + …) +``` + +In case you're wondering, `make-config.scm` is found in `(guix self)` and +returns a lowerable object that compiles to a version of the `(guix config)` +module, which contains constants usually substituted into the source code at +compile time. + +# Native Ungexp + +There is another piece of syntax we can use with gexps, and it's called +`ungexp-native`. This helps us distinguish between native inputs and regular +host-built inputs in cross-compilation situations. We'll cover +cross-compilation in detail at a later date, but the gist of it is that it +allows you to compile a derivation for one architecture X, the target, using a +machine of architecture Y, the host, and Guix has excellent support for it. + +If we cross-compile a gexp G that _non-natively_ ungexps L1, a lowerable object, +from architecture Y to architecture X, both G and L1 will be compiled for +architecture X. However, if G _natively_ ungexps L1, G will be compiled for X +and L1 for Y. + +Essentially, we use `ungexp-native` in situations where there would be no +difference between compiling on different architectures (for instance, if `L1` +were a `plain-file`), or where using L1 built for X would actually _break_ G +(for instance, if `L1` corresponds to a compiled executable that needs to be run +during the build; the executable would fail to run on Y if it was built for X.) + +The `ungexp-native` macro naturally has a corresponding reader syntax, `#+`, and +there's also `ungexp-native-splicing`, which is written as `#+@`. These two +pieces of syntax are used in the same way as their regular counterparts. + +# Conclusion + +Mastering gexps is essential to understanding Guix's inner workings, so the aim +of this blog post is to be as thorough as possible. However, if you still find +yourself with questions, please don't hesitate to stop by at the IRC channel +`#guix:libera.chat` and mailing list `help-guix@gnu.org`; we'll be glad to +assist you! + +#### About GNU Guix + +[GNU Guix](https://guix.gnu.org) is a transactional package manager and +an advanced distribution of the GNU system that [respects user +freedom](https://www.gnu.org/distros/free-system-distribution-guidelines.html). +Guix can be used on top of any system running the Hurd or the Linux +kernel, or it can be used as a standalone operating system distribution +for i686, x86_64, ARMv7, AArch64 and POWER9 machines. + +In addition to standard package management features, Guix supports +transactional upgrades and roll-backs, unprivileged package management, +per-user profiles, and garbage collection. When used as a standalone +GNU/Linux distribution, Guix offers a declarative, stateless approach to +operating system configuration management. Guix is highly customizable +and hackable through [Guile](https://www.gnu.org/software/guile) +programming interfaces and extensions to the +[Scheme](http://schemers.org) language. base-commit: a81137f5c5eedc0c327e345285d40e84c68ed10b -- 2.39.1 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. 2023-03-21 20:57 [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions ( via Guix-patches via @ 2023-04-12 15:29 ` Simon Tournier 2023-04-12 16:06 ` ( via Guix-patches via 2023-04-15 22:29 ` [bug#62356] [PAtCH " ( via Guix-patches via 1 sibling, 1 reply; 12+ messages in thread From: Simon Tournier @ 2023-04-12 15:29 UTC (permalink / raw) To: 62356; +Cc: ( Hi, Cool! Thanks, it’s very helpful. Minor comments. On mar., 21 mars 2023 at 20:57, "\( via Guix-patches" via <guix-patches@gnu.org> wrote: [...] > +The shortcomings of using s-expressions in this way are numerous: we have to > +convert everything to a derivation before using it, and _inputs are not an > +inherent aspect of the builder_. G-expressions were designed to overcome these > +issues. Here I would link to the paper introducing G-expressions, https://hal.inria.fr/hal-01580582v1 > +# Premortem Examination [...] > +Here's an example gexp that is essentially equivalent to our `sexp-builder`: > + > +```scheme > +(use-modules (guix gexp)) > + > +(define gexp-builder > + #~(symlink #$(file-append irssi "/bin/irssi") > + #$output)) > +``` > + > +`gexp-builder` is far more concise than `sexp-builder`; let's examine the syntax > +and the `<gexp>` object we've created. To make a gexp, we use the `#~` syntax, > +equivalent to the `gexp` macro, rather than the `quasiquote` backtick used to > +create sexps. > + > +When we want to embed values from outside as references, we use `#$`, or > +`ungexp`, which is, in appearance if not function, equivalent to `unquote` > +(`,`). `ungexp` can accept any of four reference types: Well, maybe it is a bit stretching and is probably not natural at all but I would try to introduce some unquote in sexp-builder. I think it would help to see the parallel between S-exp and G-exp; well how G-exp extend S-exp as you explained in the introduction. > > +- Sexps (strings, lists, etc), which will be embedded literally. From a stylistic point of view, I would write ’S-expressions’ in plain and not S-exps or sexps… > +- Other gexps, embedded literally. …Similarly for G-expression. Both over all the post. Except when it refers to code as ’gexp-builder’. > +That's a lot of complexity abstracted away! For more complex packages and > +services, especially, gexps are a lifesaver; you can refer to the output paths > +of inputs just as easily as you would a string constant. You do, however, have > +to watch out for situations where `ungexp-native`, written as `#+`, would be > +preferable over regular `ungexp`, and that's something we'll discuss later. Before the brief digression, I would do another. ;-) Mention ,build and ,lower from “guix repl”. > +A brief digression before we continue: if you'd like to look inside a `<gexp>` [...] > +# The Lowerable-Object Hardware Shop [...] > +There are many other useful lowerable objects available as part of the gexp > +library. These include `computed-file`, which accepts a gexp that builds > +the output file, `program-file`, which creates an executable Scheme script in > +the store using a gexp, and `mixed-text-file`, which allows you to, well, mix > +text and lowerable objects; it creates a file from the concatenation of a > +sequence of strings and file-likes. The > +[G-Expressions](https://guix.gnu.org/manual/en/html_node/G_002dExpressions.html) > +manual page has more details. Maybe, I would start another section here; or split with 2 subsections. > +So, you may be wondering, at this point: there's so many lowerable objects > +included with the gexps library, surely there must be a way to define more? > +Naturally, there is; this is Scheme, after all! We simply need to acquaint > +ourselves with the `define-gexp-compiler` macro. [...] > +Let's try this out now: > + > +```scheme > +(use-modules (gnu packages vim)) > + > +(define script-directory-output > + (build-derivation > + (lower-object > + (script-directory > + #~'(("irc" . #$(file-append irssi "/bin/irssi")) ---^ Hum, maybe #~' needs an explanation. Well, using G-expressions, I am missing why Schemers are complaining about Haskell syntax. ;-) > + ("editor" . #$(file-append neovim "/bin/nvim"))))))) > + > +(scandir (string-append script-directory-output "/bin")) > +⇒ ("." ".." "editor" "irc") > +``` > + > +Who knows why you'd want to do this, but it certainly works! We've looked at > +why we need gexps, how they work, and how to extend them, and we've now only got > +two more advanced features to cover: cross-build support, and modules. Here, I would link to another introduction of G-expression, https://archive.fosdem.org/2020/schedule/event/gexpressionsguile/ or maybe in the Conclusion section. > +# Importing External Modules [...] > +```scheme > +(define silly-directory-output Maybe instead of ’silly’, I would pick another name as ’simple’ or ’empty’ or ’trivial’ or ’not-serious’ or else. :-) And similarly for snippets from above. > +# Conclusion > + > +Mastering gexps is essential to understanding Guix's inner workings, so the aim > +of this blog post is to be as thorough as possible. However, if you still find > +yourself with questions, please don't hesitate to stop by at the IRC channel > +`#guix:libera.chat` and mailing list `help-guix@gnu.org`; we'll be glad to > +assist you! Maybe, you could link to Arun’s or Marius’s posts; for the ones I am aware of. :-) https://www.systemreboot.net/post/deploy-scripts-using-g-expressions https://gexp.no/blog/guix-drops-part-3-g-expressions.html Cheers, simon ^ permalink raw reply [flat|nested] 12+ messages in thread
* [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. 2023-04-12 15:29 ` Simon Tournier @ 2023-04-12 16:06 ` ( via Guix-patches via 0 siblings, 0 replies; 12+ messages in thread From: ( via Guix-patches via @ 2023-04-12 16:06 UTC (permalink / raw) To: Simon Tournier; +Cc: 62356 Hi, Thanks for the review! :D Simon Tournier <zimon.toutoune@gmail.com> writes: >> +The shortcomings of using s-expressions in this way are numerous: we have to >> +convert everything to a derivation before using it, and _inputs are not an >> +inherent aspect of the builder_. G-expressions were designed to overcome these >> +issues. > > Here I would link to the paper introducing G-expressions, > > https://hal.inria.fr/hal-01580582v1 Good idea. I'll do that :) > Well, maybe it is a bit stretching and is probably not natural at all > but I would try to introduce some unquote in sexp-builder. I think it > would help to see the parallel between S-exp and G-exp; well how G-exp > extend S-exp as you explained in the introduction. I'll try, but no guarantees I'll be able to make that make sense. > From a stylistic point of view, I would write ’S-expressions’ in plain > and not S-exps or sexps… > > …Similarly for G-expression. Both over all the post. Except when it > refers to code as ’gexp-builder’. Okay. > Before the brief digression, I would do another. ;-) Mention ,build and > ,lower from “guix repl”. ,LOWER is mentioned in part 1 <https://guix.gnu.org/en/blog/2023/dissecting-guix-part-1-derivations/>; I should have mentioned ,BUILD there too, but it's too late now, and I don't think such an explanation fits a post meant to explain how gexps work. > Hum, maybe #~' needs an explanation. Well, using G-expressions, I am > missing why Schemers are complaining about Haskell syntax. ;-) Heh :) (I think it's more to do with Haskell's complexity than ease of reading.) I'll try to add a short note there. > Here, I would link to another introduction of G-expression, > > https://archive.fosdem.org/2020/schedule/event/gexpressionsguile/ > > or maybe in the Conclusion section. Yeah, I'll put the other references in the Conclusion. > Maybe instead of ’silly’, I would pick another name as ’simple’ or > ’empty’ or ’trivial’ or ’not-serious’ or else. :-) > > And similarly for snippets from above. Okay. > Maybe, you could link to Arun’s or Marius’s posts; for the ones I am > aware of. :-) > > https://www.systemreboot.net/post/deploy-scripts-using-g-expressions > https://gexp.no/blog/guix-drops-part-3-g-expressions.html Yup, and the FOSDEM talk in the same place. ^ permalink raw reply [flat|nested] 12+ messages in thread
* [bug#62356] [PAtCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. 2023-03-21 20:57 [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions ( via Guix-patches via 2023-04-12 15:29 ` Simon Tournier @ 2023-04-15 22:29 ` ( via Guix-patches via 2023-04-18 19:55 ` [bug#62356] [PATCH " Ludovic Courtès 2023-04-19 13:03 ` [bug#62356] [PAtCH " Théo Maxime Tyburn 1 sibling, 2 replies; 12+ messages in thread From: ( via Guix-patches via @ 2023-04-15 22:29 UTC (permalink / raw) To: 62356; +Cc: (, Théo Maxime Tyburn, Simon Tournier * website/posts/dissecting-guix-3-gexps.md: New blog post. --- website/posts/dissecting-guix-3-gexps.md | 735 +++++++++++++++++++++++ 1 file changed, 735 insertions(+) create mode 100644 website/posts/dissecting-guix-3-gexps.md diff --git a/website/posts/dissecting-guix-3-gexps.md b/website/posts/dissecting-guix-3-gexps.md new file mode 100644 index 0000000..cd56243 --- /dev/null +++ b/website/posts/dissecting-guix-3-gexps.md @@ -0,0 +1,735 @@ +title: Dissecting Guix, Part 3: G-Expressions +date: TBC +author: ( +tags: Dissecting Guix, Functional package management, Programming interfaces, Scheme API +--- +Welcome back to [Dissecting Guix](https://guix.gnu.org/en/blog/tags/dissecting-guix)! +Last time, we discussed [monads](https://guix.gnu.org/en/blog/2023/dissecting-guix-part-2-the-store-monad), +the functional programming idiom used by Guix to thread a store connection +through a series of store-related operations. + +Today, we'll be talking about a concept rather more specific to Guix: +_g-expressions_. Being an implementation of the Scheme language, Guile is built +around [_s-expressions_](https://en.wikipedia.org/wiki/S-expression), which can +represent, as the saying goes, _code as data_, thanks to the simple structure of +Scheme forms. + +As Guix's package recipes are written in Scheme, it naturally needs some way to +represent code that is to be run only when the package is built. Additionally, +there needs to be some way to reference dependencies and retrieve output paths; +otherwise, you wouldn't be able to, for instance, create a phase to install a +file in the output directory. + +So, how do we implement this "deferred" code? Well, initially Guix used plain +old s-expressions for this purpose. + +# Once Upon a Time + +Let's say we want to create a store item that's just a symlink to the +`bin/irssi` file of the `irssi` package. How would we do that with an +s-expression? Well, the s-expression itself, which we call the _builder_, is +fairly simple: + +```scheme +(define sexp-builder + `(let* ((out (assoc-ref %outputs "out")) + (irssi (assoc-ref %build-inputs "irssi")) + (bin/irssi (string-append irssi "/bin/irssi"))) + (symlink bin/irssi out))) +``` + +If you aren't familliar with the "quoting" syntax used to create s-expressions, +I strongly recommend that you read the excellent Scheme Primer; specifically, +section 7, [_Lists and +"cons"_](https://spritely.institute/static/papers/scheme-primer.html#scheme-lists-and-cons) +and section 11, [_On the extensibility of Scheme (and Lisps in +general)_](https://spritely.institute/static/papers/scheme-primer.html#scheme-extensibility) + +The `%outputs` and `%build-inputs` variables are bound within builder scripts to +_association lists_, which are lists of pairs that act like key/value stores, +for instance: + +```scheme +'(("foo" . "bar") + ("floob" . "blarb") + ("fvoolag" . "bvarlag")) +``` + +To retrieve values from association lists, which are often referred to as +_alists_, we use the `assoc-ref` procedure: + +```scheme +(assoc-ref '(("boing" . "bouncy") + ("floing" . "flouncy")) + "boing") +⇒ "bouncy" +``` + +`%outputs`, as the name might suggest, maps derivation output names to the paths +of their respective store items, the default output being `out`, and +`%build-inputs` maps inputs labels to their store items. + +The builder is the easy part; we now need to turn it into a derivation and tell +it what `"irssi"` actually refers to. For this, we use the +`build-expression->derivation` procedure from `(guix derivations)`: + +```scheme +(use-modules (guix derivations) + (guix packages) + (guix store) + (gnu packages guile) + (gnu packages irc)) + +(with-store store + (let ((guile-3.0-drv (package-derivation store guile-3.0)) + (irssi-drv (package-derivation store irssi))) + (build-expression->derivation store "irssi-symlink" sexp-builder + #:guile-for-build guile-3.0-drv + #:inputs `(("irssi" ,irssi-drv))))) +⇒ #<derivation /gnu/store/…-irssi-symlink.drv => /gnu/store/…-irssi-symlink …> +``` + +There are several things to note here: + +- The inputs _must_ all be derivations, so we need to first convert the packages + using `package-derivation`. +- We need to explicitly set `#:guile-for-build`; there's no default value. +- The `build-expression->derivation` and `package-derivation` procedures are + _not_ monadic, so we need to explicitly pass them the store connection. + +The shortcomings of using s-expressions in this way are numerous: we have to +convert everything to a derivation before using it, and _inputs are not an +inherent aspect of the builder_. G-expressions were designed to overcome these +issues. + +# Premortem Examination + +A g-expression is fundamentally a record of type `<gexp>`, which is, naturally, +defined in `(guix gexp)`. The two most important fields of this record type, +out of a total of five, are `proc` and `references`; the former is a procedure +that returns the equivalent s-expression, the latter a list containing +everything from the "outside world" that's used by the g-expression. + +When we want to turn the g-expression into something that we can actually run as +code, we combine these two fields by first building any g-expression inputs that +can become derivations (leaving alone those that cannot), and then passing the +built `references` as the arguments of `proc`. + +Here's an example g-expression that is essentially equivalent to our +`sexp-builder`: + +```scheme +(use-modules (guix gexp)) + +(define gexp-builder + #~(symlink #$(file-append irssi "/bin/irssi") + #$output)) +``` + +`gexp-builder` is far more concise than `sexp-builder`; let's examine the syntax +and the `<gexp>` object we've created. To make a g-expression, we use the `#~` +syntax, equivalent to the `gexp` macro, rather than the `quasiquote` backtick +used to create s-expressions. + +When we want to embed values from outside as references, we use `#$`, or +`ungexp`, which is, in appearance if not function, equivalent to `unquote` +(`,`). `ungexp` can accept any of four reference types: + +- S-expressions (strings, lists, etc), which will be embedded literally. +- Other g-expressions, embedded literally. +- Expressions returning any sort of object that can be lowered into a + derivation, such as `<package>`, embedding that object's `out` store item; if + the expression is specifically a symbol bound to a buildable object, you can + optionally follow it with a colon and an alternative output name, so + `package:lib` is permitted, but `(get-package):lib` isn't. +- The symbol `output`, embedding an output path. Like symbols bound to + buildable objects, this can be followed by a colon and the output name that + should be used rather than the default `out`. + +All these reference types will be represented by `<gexp-input>` records in the +`references` field, except for the last kind, which will become `<gexp-output>` +records. To give an example of each type of reference (with the return value +output formatted for easier reading): + +```scheme +(use-modules (gnu packages glib)) + +#~(list #$"foobar" ;s-expression + #$#~(string-append "foo" "bar") ;g-expression + #$(file-append irssi "/bin/irssi") ;buildable object (expression) + #$glib:bin ;buildable object (symbol) + #$output:out) ;output +⇒ #<gexp (list #<gexp-input "foobar":out> + #<gexp-input #<gexp (string-append "foo" "bar") …>:out> + #<gexp-input #<file-append #<package irssi@1.4.3 …> "/bin/irssi">:out> + #<gexp-input #<package glib@2.70.2 …>:bin> + #<gexp-output out>) …> +``` + +Note the use of `file-append` in both the previous example and `gexp-builder`; +this procedure produces a `<file-append>` object that builds its first argument +and is embedded as the concatenation of the first argument's output path and the +second argument, which should be a string. For instance, +`(file-append irssi "/bin/irssi")` builds `irssi` and expands to +`/gnu/store/…-irssi/bin/irssi`, rather than the `/gnu/store/…-irssi` that the +package alone would be embedded as. + +So, now that we have a g-expression, how do we turn it into a derivation? This +process is known as _lowering_; it entails the use of the aptly-named +`lower-gexp` monadic procedure to combine `proc` and `references` and produce a +`<lowered-gexp>` record, which acts as a sort of intermediate representation +between g-expressions and derivations. We can piece apart this lowered form to +get a sense of what the final derivation's builder script would look like: + +```scheme +(define lowered-gexp-builder + (with-store store + (run-with-store store + (lower-gexp gexp-builder)))) + +(lowered-gexp-sexp lowered-gexp-builder) +⇒ (symlink + "/gnu/store/…-irssi-1.4.3/bin/irssi" + ((@ (guile) getenv) "out")) +``` + +And there you have it: a s-expression compiled from a g-expression, ready to be +written into a builder script file in the store. So, how exactly do you turn +this into said derivation? + +Well, it turns out that there isn't an interface for turning lowered +g-expressions into derivations, only one for turning regular g-expressions into +derivations that first uses `lower-gexp`, then implements the aforementioned +conversion internally, rather than outsourcing it to some other procedure, so +that's what we'll use. + +Unsurprisingly, that procedure is called `gexp->derivation`, and unlike its +s-expression equivalent, it's monadic. (`build-expression->derivation` and +other deprecated procedures were in Guix since before the monads system +existed.) + +```scheme +(with-store store + (run-with-store store + (gexp->derivation "irssi-symlink" gexp-builder))) +⇒ #<derivation /gnu/store/…-irssi-symlink.drv => /gnu/store/…-irssi-symlink …> +``` + +Finally, we have a g-expression-based equivalent to the derivation we earlier +created with `build-expression->derivation`! Here's the code we used for the +s-expression version in full: + +```scheme +(define sexp-builder + `(let* ((out (assoc-ref %outputs "out")) + (irssi (assoc-ref %build-inputs "irssi")) + (bin/irssi (string-append irssi "/bin/irssi"))) + (symlink bin/irssi out))) + +(with-store store + (let ((guile-3.0-drv (package-derivation store guile-3.0)) + (irssi-drv (package-derivation store irssi))) + (build-expression->derivation store "irssi-symlink" sexp-builder + #:guile-for-build guile-3.0-drv + #:inputs `(("irssi" ,irssi-drv))))) +``` + +And here's the g-expression equivalent: + +```scheme +(define gexp-builder + #~(symlink #$(file-append irssi "/bin/irssi") + #$output)) + +(with-store store + (run-with-store store + (gexp->derivation "irssi-symlink" gexp-builder))) +``` + +That's a lot of complexity abstracted away! For more complex packages and +services, especially, g-expressions are a lifesaver; you can refer to the output +paths of inputs just as easily as you would a string constant. You do, however, +have to watch out for situations where `ungexp-native`, written as `#+`, would +be preferable over regular `ungexp`, and that's something we'll discuss later. + +A brief digression before we continue: if you'd like to look inside a `<gexp>` +record, but you'd rather not build anything, you can use the +`gexp->approximate-sexp` procedure, which replaces all references with dummy +values: + +```scheme +(gexp->approximate-sexp gexp-builder) +⇒ (symlink (*approximate*) (*approximate*)) +``` + +# The Lowerable-Object Hardware Shop + +We've seen two examples already of records we can turn into derivations, which +are generally referred to as _lowerable objects_ or _file-like objects_: + +- `<package>`, a Guix package. +- `<file-append>`, which wraps another lowerable object and appends a string to + the embedded output path when `ungexp`ed. + +There are many more available to us. Recall from the previous post, +[_The Store Monad_](https://guix.gnu.org/en/blog/2023/dissecting-guix-part-2-the-store-monad), +that Guix provides the two monadic procedures `text-file` and `interned-file`, +which can be used, respectively, to put arbitrary text or files from the +filesystem in the store, returning the path to the created item. + +This doesn't work so well with g-expressions, though; you'd have to wrap each +`ungexp`ed use of either of them with +`(with-store store (run-with-store store …))`, which would be quite tedious. +Thankfully, `(guix gexp)` provides the `plain-file` and `local-file` procedures, +which return equivalent lowerable objects. This code example builds a directory +containing symlinks to files greeting the world: + +```scheme +(use-modules (guix monads) + (ice-9 ftw) + (ice-9 textual-ports)) + +(define (build-derivation monadic-drv) + (with-store store + (run-with-store store + (mlet* %store-monad ((drv monadic-drv)) + (mbegin %store-monad + ;; BUILT-DERIVATIONS is the monadic version of BUILD-DERIVATIONS. + (built-derivations (list drv)) + (return (derivation-output-path + (assoc-ref (derivation-outputs drv) "out")))))))) + +(define world-greeting-output + (build-derivation + (gexp->derivation "world-greeting" + #~(begin + (mkdir #$output) + (symlink #$(plain-file "hi-world" + "Hi, world!") + (string-append #$output "/hi")) + (symlink #$(plain-file "hello-world" + "Hello, world!") + (string-append #$output "/hello")) + (symlink #$(plain-file "greetings-world" + "Greetings, world!") + (string-append #$output "/greetings")))))) + +;; We turn the list into multiple values using (APPLY VALUES …). +(apply values + (map (lambda (file-path) + (let* ((path (string-append world-greeting-output "/" file-path)) + (contents (call-with-input-file path get-string-all))) + (list path contents))) + ;; SCANDIR from (ICE-9 FTW) returns the list of all files in a + ;; directory (including ``.'' and ``..'', so we remove them with the + ;; second argument, SELECT?, which specifies a predicate). + (scandir world-greeting-output + (lambda (path) + (not (or (string=? path ".") + (string=? path ".."))))))) +⇒ ("/gnu/store/…-world-greeting/greetings" "Greetings, world!") +⇒ ("/gnu/store/…-world-greeting/hello" "Hello, world!") +⇒ ("/gnu/store/…-world-greeting/hi" "Hi, world!") +``` + +Note that we define a procedure for building the output; we will need to build +more derivations in a very similar fashion later, so it helps to have this to +reuse instead of copying the code in `world-greeting-output`. + +There are many other useful lowerable objects available as part of the gexp +library. These include `computed-file`, which accepts a gexp that builds +the output file, `program-file`, which creates an executable Scheme script in +the store using a g-expression, and `mixed-text-file`, which allows you to, +well, mix text and lowerable objects; it creates a file from the concatenation +of a sequence of strings and file-likes. The +[G-Expressions](https://guix.gnu.org/manual/en/html_node/G_002dExpressions.html) +manual page has more details. + +So, you may be wondering, at this point: there's so many lowerable objects +included with the g-expression library, surely there must be a way to define +more? Naturally, there is; this is Scheme, after all! We simply need to +acquaint ourselves with the `define-gexp-compiler` macro. + +The most basic usage of `define-gexp-compiler` essentially creates a procedure +that takes as arguments a record to lower, the host system, and the target +system, and returns a derivation or store item as a monadic value in +`%store-monad`. + +Let's try implementing a lowerable object representing a file that greets the +world. First, we'll define the record type: + +```scheme +(use-modules (srfi srfi-9)) + +(define-record-type <greeting-file> + (greeting-file greeting) + greeting? + (greeting greeting-file-greeting)) +``` + +Now we use `define-gexp-compiler` like so; note how we can use `lower-object` +to compile down any sort of lowerable object into the equivalent store item or +derivation; essentially, `lower-object` is just the procedure for applying the +right gexp-compiler to an object: + +```scheme +(use-modules (ice-9 i18n)) + +(define-gexp-compiler (greeting-file-compiler + (greeting-file <greeting-file>) + system target) + (lower-object + (let ((greeting (greeting-file-greeting greeting-file))) + (plain-file (string-append greeting "-greeting") + (string-append (string-locale-titlecase greeting) ", world!"))))) +``` + +Let's try it out now. Here's how we could rewrite our greetings directory +example from before using `<greeting-file>`: + +```scheme +(define world-greeting-2-output + (build-derivation + (gexp->derivation "world-greeting-2" + #~(begin + (mkdir #$output) + (symlink #$(greeting-file "hi") + (string-append #$output "/hi")) + (symlink #$(greeting-file "hello") + (string-append #$output "/hello")) + (symlink #$(greeting-file "greetings") + (string-append #$output "/greetings")))))) + +(apply values + (map (lambda (file-path) + (let* ((path (string-append world-greeting-2-output + "/" file-path)) + (contents (call-with-input-file path get-string-all))) + (list path contents))) + (scandir world-greeting-2-output + (lambda (path) + (not (or (string=? path ".") + (string=? path ".."))))))) +⇒ ("/gnu/store/…-world-greeting-2/greetings" "Greetings, world!") +⇒ ("/gnu/store/…-world-greeting-2/hello" "Hello, world!") +⇒ ("/gnu/store/…-world-greeting-2/hi" "Hi, world!") +``` + +Now, this is probably not worth a whole new gexp-compiler. How about something +a bit more complex? Sharp-eyed readers who are trying all this in the REPL may +have noticed the following output when they used `define-gexp-compiler` +(formatted for ease of reading): + +```scheme +⇒ #<<gexp-compiler> + type: #<record-type <greeting-file>> + lower: #<procedure … (greeting-file system target)> + expand: #<procedure default-expander (thing obj output)>> +``` + +Now, the purpose of `type` and `lower` is self-explanatory, but what's this +`expand` procedure here? Well, if you recall `file-append`, you may realise +that the text produced by a gexp-compiler for embedding into a g-expression +doesn't necessarily have to be the exact output path of the produced derivation. + +There turns out to be another way to write a `define-gexp-compiler` form that +allows you to specify _both_ the lowering procedure, which produces the +derivation or store item, and the expanding procedure, which produces the text. + +Let's try making another new lowerable object; this one will let us build a +Guile package and expand to the path to its module directory. Here's our +record: + +```scheme +(define-record-type <module-directory> + (module-directory package) + module-directory? + (package module-directory-package)) +``` + +Here's how we define both a compiler and expander for our new record: + +```scheme +(use-modules (gnu packages guile) + (guix utils)) + +(define lookup-expander (@@ (guix gexp) lookup-expander)) + +(define-gexp-compiler module-directory-compiler <module-directory> + compiler => (lambda (obj system target) + (let ((package (module-directory-package obj))) + (lower-object package system #:target target))) + expander => (lambda (obj drv output) + (let* ((package (module-directory-package obj)) + (expander (or (lookup-expander package) + (lookup-expander drv))) + (out (expander package drv output)) + (guile (or (lookup-package-input package "guile") + guile-3.0)) + (version (version-major+minor + (package-version guile)))) + (string-append out "/share/guile/site/" version)))) +``` + +Let's try this out now: + +```scheme +(use-modules (gnu packages guile-xyz)) + +(define module-directory-output/guile-webutils + (build-derivation + (gexp->derivation "module-directory-output" + #~(symlink #$(module-directory guile-webutils) #$output)))) + +(readlink module-directory-output/guile-webutils) +⇒ "/gnu/store/…-guile-webutils-0.1-1.d309d65/share/guile/site/3.0" + +(scandir module-directory-output/guile-webutils) +⇒ ("." ".." "webutils") + +(define module-directory-output/guile2.2-webutils + (build-derivation + (gexp->derivation "module-directory-output" + #~(symlink #$(module-directory guile2.2-webutils) #$output)))) + +(readlink module-directory-output/guile2.2-webutils) +⇒ "/gnu/store/…-guile-webutils-0.1-1.d309d65/share/guile/site/2.2" + +(scandir module-directory-output/guile2.2-webutils) +⇒ ("." ".." "webutils") +``` + +Who knows why you'd want to do this, but it certainly works! We've looked at +why we need g-expressions, how they work, and how to extend them, and we've now +only got two more advanced features to cover: cross-build support, and modules. + +# Importing External Modules + +Let's try using one of the helpful procedures from the `(guix build utils)` +module in a g-expression. + +```scheme +(define simple-directory-output + (build-derivation + (gexp->derivation "simple-directory" + #~(begin + (use-modules (guix build utils)) + (mkdir-p (string-append #$output "/a/rather/simple/directory")))))) +``` + +Looks fine, right? We've even got a `use-modules` in th-- + +```Scheme +ERROR: + 1. &store-protocol-error: + message: "build of `/gnu/store/…-simple-directory.drv' failed" + status: 100 +``` + +OUTRAGEOUS. Fortunately, there's an explanation to be found in the Guix build +log directory, `/var/log/guix/drvs`; locate the file using the first two +characters of the store hash as the subdirectory, and the rest as the file name, +and remember to use `zcat` or `zless`, as the logs are gzipped: + +```scheme +Backtrace: + 9 (primitive-load "/gnu/store/…") +In ice-9/eval.scm: + 721:20 8 (primitive-eval (begin (use-modules (guix build #)) (?))) +In ice-9/psyntax.scm: + 1230:36 7 (expand-top-sequence ((begin (use-modules (guix ?)) #)) ?) + 1090:25 6 (parse _ (("placeholder" placeholder)) ((top) #(# # ?)) ?) + 1222:19 5 (parse _ (("placeholder" placeholder)) ((top) #(# # ?)) ?) + 259:10 4 (parse _ (("placeholder" placeholder)) (()) _ c&e (eval) ?) +In ice-9/boot-9.scm: + 3927:20 3 (process-use-modules _) + 222:17 2 (map1 (((guix build utils)))) + 3928:31 1 (_ ((guix build utils))) + 3329:6 0 (resolve-interface (guix build utils) #:select _ #:hide ?) + +ice-9/boot-9.scm:3329:6: In procedure resolve-interface: +no code for module (guix build utils) +``` + +It turns out `use-modules` can't actually find `(guix build utils)` at all. +There's no typo; it's just that to ensure the build is isolated, Guix builds +`module-import` and `module-importe-compiled` directories, and sets the +_Guile module path_ within the build environment to contain said directories, +along with those containing the Guile standard library modules. + +So, what to do? Turns out one of the fields in `<gexp>` is `modules`, which, +funnily enough, contains the names of the modules which will be used to build +the aforementioned directories. To add to this field, we use the +`with-imported-modules` macro. (`gexp->derivation` _does_ provide a `modules` +parameter, but `with-imported-modules` lets you add the required modules +directly to the g-expression value, rather than later on.) + +```scheme +(define simple-directory-output + (build-derivation + (gexp->derivation "simple-directory" + (with-imported-modules '((guix build utils)) + #~(begin + (use-modules (guix build utils)) + (mkdir-p (string-append #$output "/a/rather/simple/directory"))))))) + +simple-directory-output +⇒ "/gnu/store/…-simple-directory" +``` + +It works, yay. It's worth noting that while passing just the list of modules to +`with-imported-modules` works in this case, this is only because +`(guix build utils)` has no dependencies on other Guix modules. Were we to try +adding, say, `(guix build emacs-build-system)`, we'd need to use the +`source-module-closure` procedure to add its dependencies to the list: + +```scheme +(use-modules (guix modules)) + +(source-module-closure '((guix build emacs-build-system))) +⇒ ((guix build emacs-build-system) + (guix build gnu-build-system) + (guix build utils) + (guix build gremlin) + (guix elf) + (guix build emacs-utils)) +``` + +Here's another scenario: what if we want to use a module not from Guix or Guile +but a third-party library? In this example, we'll use [guile-json +](https://github.com/aconchillo/guile-json), a library for converting between +S-expressions and [JavaScript Object Notation](https://json.org). + +We can't just `with-imported-modules` its modules, since it's not part of Guix, +so `<gexp>` provides another field for this purpose: `extensions`. Each of +these extensions is a lowerable object that produces a Guile package directory; +so usually a package. Let's try it out using the `guile-json-4` package to +produce a JSON file from a Scheme value within a g-expression. + +```scheme +(define helpful-guide-output + (build-derivation + (gexp->derivation "json-file" + (with-extensions (list guile-json-4) + #~(begin + (use-modules (json)) + (mkdir #$output) + (call-with-output-file (string-append #$output "/helpful-guide.json") + (lambda (port) + (scm->json '((truth . "Guix is the best!") + (lies . "Guix isn't the best!")) + port)))))))) + +(call-with-input-file + (string-append helpful-guide-output "/helpful-guide.json") + get-string-all) +⇒ "{\"truth\":\"Guix is the best!\",\"lies\":\"Guix isn't the best!\"}" +``` + +Amen to that, `helpful-guide.json`. Before we continue on to cross-compilation, +there's one last feature of `with-imported-modules` you should note. We can +add modules to a g-expression by name, but we can also create entirely new ones +using lowerable objects, such as in this pattern, which is used in several +places in the Guix source code to make an appropriately-configured +`(guix config)` module available: + +```scheme +(with-imported-modules `(((guix config) => ,(make-config.scm)) + …) + …) +``` + +In case you're wondering, `make-config.scm` is found in `(guix self)` and +returns a lowerable object that compiles to a version of the `(guix config)` +module, which contains constants usually substituted into the source code at +compile time. + +# Native `ungexp` + +There is another piece of syntax we can use with g-expressions, and it's called +`ungexp-native`. This helps us distinguish between native inputs and regular +host-built inputs in cross-compilation situations. We'll cover +cross-compilation in detail at a later date, but the gist of it is that it +allows you to compile a derivation for one architecture X, the target, using a +machine of architecture Y, the host, and Guix has excellent support for it. + +If we cross-compile a g-expression G that _non-natively_ `ungexp`s L1, a +lowerable object, from architecture Y to architecture X, both G and L1 will be +compiled for architecture X. However, if G _natively_ `ungexp`s L1, G will be +compiled for X and L1 for Y. + +Essentially, we use `ungexp-native` in situations where there would be no +difference between compiling on different architectures (for instance, if `L1` +were a `plain-file`), or where using L1 built for X would actually _break_ G +(for instance, if `L1` corresponds to a compiled executable that needs to be run +during the build; the executable would fail to run on Y if it was built for X.) + +The `ungexp-native` macro naturally has a corresponding reader syntax, `#+`, and +there's also `ungexp-native-splicing`, which is written as `#+@`. These two +pieces of syntax are used in the same way as their regular counterparts. + +# Conclusion + +What have we learned in this post? To summarise: + ++ G-expressions are essentially abstractions on top of s-expressions used in + Guix to stage code, often for execution within a build environment or a + Shepherd service script. ++ Much like you can `unquote` external values within a `quasiquote` form, you + can `ungexp` external values to access them within a `gexp` form. The key + difference is that you may use not only s-expressions with `ungexp`, but other + g-expressions and lowerable objects too. ++ When a lowerable object is used with `ungexp`, the g-expression ultimately + receives the path to the object's store item (or whatever string the lowerable + object's expander produces), rather than the object itself. ++ A lowerable object is any record that has a "g-expression compiler" defined + for it using the `define-gexp-compiler` macro. G-expression compilers always + contain a `compiler` procedure, which converts an appropriate record into a + derivation, and sometimes an `expander` procedure, which produces the string + that is to be expanded to within g-expressions when the object is `ungexp`ed. ++ G-expressions record the list of modules available in their environment, which + you may expand using `with-imported-modules` to add Guix modules, and + `with-extensions` to add modules from third-party Guile packages. ++ `ungexp-native` may be used within g-expressions to compile lowerable objects + for the host rather than the target system in cross-compilation scenarios. + +Mastering g-expressions is essential to understanding Guix's inner workings, so +the aim of this blog post is to be as thorough as possible. However, if you +still find yourself with questions, please don't hesitate to stop by at the IRC +channel `#guix:libera.chat` and mailing list `help-guix@gnu.org`; we'll be glad +to assist you! + +Also note that due to the centrality of g-expressions to Guix, there exist a +plethora of alternative resources on this topic; here are some which you may +find useful: + ++ Arun Isaac's + [post](https://www.systemreboot.net/post/deploy-scripts-using-g-expressions) + on using g-expressions with `guix deploy`. ++ Marius Bakke's + ["Guix Drops" post](https://gexp.no/blog/guix-drops-part-3-g-expressions.html) + which explains g-expressions in a more "top-down" way. ++ This 2020 + [FOSDEM talk](https://archive.fosdem.org/2020/schedule/event/gexpressionsguile/) + by Christopher Marusich on the uses of g-expressions. ++ And, of course, the one and only original + [g-expression paper](https://hal.inria.fr/hal-01580582v1) by Ludovic Courtès, + the original author of Guix. + +#### About GNU Guix + +[GNU Guix](https://guix.gnu.org) is a transactional package manager and +an advanced distribution of the GNU system that [respects user +freedom](https://www.gnu.org/distros/free-system-distribution-guidelines.html). +Guix can be used on top of any system running the Hurd or the Linux +kernel, or it can be used as a standalone operating system distribution +for i686, x86_64, ARMv7, AArch64 and POWER9 machines. + +In addition to standard package management features, Guix supports +transactional upgrades and roll-backs, unprivileged package management, +per-user profiles, and garbage collection. When used as a standalone +GNU/Linux distribution, Guix offers a declarative, stateless approach to +operating system configuration management. Guix is highly customizable +and hackable through [Guile](https://www.gnu.org/software/guile) +programming interfaces and extensions to the +[Scheme](http://schemers.org) language. base-commit: 9d8f36a722d33f75e2e081a8d8f04cf20c4d3511 -- 2.39.2 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. 2023-04-15 22:29 ` [bug#62356] [PAtCH " ( via Guix-patches via @ 2023-04-18 19:55 ` Ludovic Courtès 2023-04-18 20:05 ` ( via Guix-patches via 2023-04-18 20:08 ` ( via Guix-patches via 2023-04-19 13:03 ` [bug#62356] [PAtCH " Théo Maxime Tyburn 1 sibling, 2 replies; 12+ messages in thread From: Ludovic Courtès @ 2023-04-18 19:55 UTC (permalink / raw) To: (; +Cc: Théo Maxime Tyburn, 62356, Simon Tournier Hello, "(" <paren@disroot.org> skribis: > * website/posts/dissecting-guix-3-gexps.md: New blog post. This looks perfect to me, great job! If there are no objections, I’ll push it tomorrow noon (GMT), which should be better timing than now. Thanks a lot, and thanks Simon for reviewing! Ludo’. ^ permalink raw reply [flat|nested] 12+ messages in thread
* [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. 2023-04-18 19:55 ` [bug#62356] [PATCH " Ludovic Courtès @ 2023-04-18 20:05 ` ( via Guix-patches via 2023-04-18 20:08 ` ( via Guix-patches via 1 sibling, 0 replies; 12+ messages in thread From: ( via Guix-patches via @ 2023-04-18 20:05 UTC (permalink / raw) To: Ludovic Courtès; +Cc: Théo Maxime Tyburn, 62356, Simon Tournier Ludovic Courtès <ludo@gnu.org> writes: > Hello, > > "(" <paren@disroot.org> skribis: > >> * website/posts/dissecting-guix-3-gexps.md: New blog post. > > This looks perfect to me, great job! Thank you very much :D It was a bit easier to write than the last one, thank goodness :P > If there are no objections, I’ll push it tomorrow noon (GMT), which > should be better timing than now. \o/ ^ permalink raw reply [flat|nested] 12+ messages in thread
* [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. 2023-04-18 19:55 ` [bug#62356] [PATCH " Ludovic Courtès 2023-04-18 20:05 ` ( via Guix-patches via @ 2023-04-18 20:08 ` ( via Guix-patches via 2023-04-19 8:16 ` Simon Tournier 2023-04-19 10:00 ` Ludovic Courtès 1 sibling, 2 replies; 12+ messages in thread From: ( via Guix-patches via @ 2023-04-18 20:08 UTC (permalink / raw) To: Ludovic Courtès; +Cc: Théo Maxime Tyburn, 62356, Simon Tournier Ludovic Courtès <ludo@gnu.org> writes: > This looks perfect to me, great job! > > If there are no objections, I’ll push it tomorrow noon (GMT), which > should be better timing than now. Just to be sure, you're talking about v2, right? Because this showed up in mu4e as a reply to v1 :) ^ permalink raw reply [flat|nested] 12+ messages in thread
* [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. 2023-04-18 20:08 ` ( via Guix-patches via @ 2023-04-19 8:16 ` Simon Tournier 2023-04-19 10:00 ` Ludovic Courtès 1 sibling, 0 replies; 12+ messages in thread From: Simon Tournier @ 2023-04-19 8:16 UTC (permalink / raw) To: (, Ludovic Courtès; +Cc: Théo Maxime Tyburn, 62356 Hi, On Tue, 18 Apr 2023 at 21:08, "\( via Guix-patches" via <guix-patches@gnu.org> wrote: > Just to be sure, you're talking about v2, right? Because this showed up > in mu4e as a reply to v1 :) Well, if I am not missing a detail, Ludo (id:87edohhsmw.fsf_-_@gnu.org) is replying to id:20230415222954.567-1-paren@disroot.org [bug#62356] [PAtCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. Sat, 15 Apr 2023 23:29:54 +0100 as shown by [1] or [2]. Maybe a bug of mue4? :-) However, using the mboxes from [3] (bug in Mumi?), note that: --8<---------------cut here---------------start------------->8--- $ for i in $(seq 0 6); do printf "$i "; cat 62356-$i.mbox | grep Message-I ;done 0 Message-Id: <20230321205749.4974-1-paren@disroot.org> 1 Message-ID: <87ile1glv6.fsf@gmail.com> 2 Message-ID: <87ile1glv6.fsf@gmail.com> 3 Message-ID: <87r0sp6ppe.fsf@disroot.org> 4 Message-ID: <87r0sp6ppe.fsf@disroot.org> 5 Message-Id: <20230415222954.567-1-paren@disroot.org> 6 Message-ID: <87edohhsmw.fsf_-_@gnu.org --8<---------------cut here---------------end--------------->8--- which is different from notmuch or emacs-debbugs (gnus-summary-show-raw-article) 20230321205749.4974-1-paren@disroot.org 87ile1glv6.fsf@gmail.com 87r0sp6ppe.fsf@disroot.org 20230415222954.567-1-paren@disroot.org 87edohhsmw.fsf_-_@gnu.org 877cu9ymwi.fsf@disroot.org 87354xymu3.fsf@disroot.org Last, using emacs-debbugs, the Ludo’s message contains: --8<---------------cut here---------------start------------->8--- In-Reply-To: <20230415222954.567-1-paren@disroot.org> (paren@disroot.org's message of "Sat, 15 Apr 2023 23:29:54 +0100") --8<---------------cut here---------------end--------------->8--- 1: https://yhetil.org/guix/20230415222954.567-1-paren@disroot.org/#r 2: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=62356;mbox=yes;msg=23 3: https://issues.guix.gnu.org/issue/62356 Cheers, simon ^ permalink raw reply [flat|nested] 12+ messages in thread
* [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. 2023-04-18 20:08 ` ( via Guix-patches via 2023-04-19 8:16 ` Simon Tournier @ 2023-04-19 10:00 ` Ludovic Courtès 2023-04-19 14:17 ` bug#62356: " Ludovic Courtès 1 sibling, 1 reply; 12+ messages in thread From: Ludovic Courtès @ 2023-04-19 10:00 UTC (permalink / raw) To: (; +Cc: Théo Maxime Tyburn, 62356, Simon Tournier "(" <paren@disroot.org> skribis: > Ludovic Courtès <ludo@gnu.org> writes: >> This looks perfect to me, great job! >> >> If there are no objections, I’ll push it tomorrow noon (GMT), which >> should be better timing than now. > > Just to be sure, you're talking about v2, right? Because this showed up > in mu4e as a reply to v1 :) Yes, I’m talking about v2. :-) ^ permalink raw reply [flat|nested] 12+ messages in thread
* bug#62356: [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. 2023-04-19 10:00 ` Ludovic Courtès @ 2023-04-19 14:17 ` Ludovic Courtès 2023-04-19 15:15 ` [bug#62356] " ( via Guix-patches via 0 siblings, 1 reply; 12+ messages in thread From: Ludovic Courtès @ 2023-04-19 14:17 UTC (permalink / raw) To: (; +Cc: Théo Maxime Tyburn, Simon Tournier, 62356-done Done! https://guix.gnu.org/en/blog/2023/dissecting-guix-part-3-g-expressions/ Thank you! Ludo’. ^ permalink raw reply [flat|nested] 12+ messages in thread
* [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. 2023-04-19 14:17 ` bug#62356: " Ludovic Courtès @ 2023-04-19 15:15 ` ( via Guix-patches via 0 siblings, 0 replies; 12+ messages in thread From: ( via Guix-patches via @ 2023-04-19 15:15 UTC (permalink / raw) To: Ludovic Courtès; +Cc: Théo Maxime Tyburn, Simon Tournier, 62356-done Ludovic Courtès <ludo@gnu.org> writes: > Done! > > https://guix.gnu.org/en/blog/2023/dissecting-guix-part-3-g-expressions/ > > Thank you! \o/ ^ permalink raw reply [flat|nested] 12+ messages in thread
* [bug#62356] [PAtCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. 2023-04-15 22:29 ` [bug#62356] [PAtCH " ( via Guix-patches via 2023-04-18 19:55 ` [bug#62356] [PATCH " Ludovic Courtès @ 2023-04-19 13:03 ` Théo Maxime Tyburn 1 sibling, 0 replies; 12+ messages in thread From: Théo Maxime Tyburn @ 2023-04-19 13:03 UTC (permalink / raw) To: (; +Cc: 62356, Simon Tournier I like that V2 ! The references to other tutorials are quite nice :) ^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2023-04-19 20:04 UTC | newest] Thread overview: 12+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2023-03-21 20:57 [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions ( via Guix-patches via 2023-04-12 15:29 ` Simon Tournier 2023-04-12 16:06 ` ( via Guix-patches via 2023-04-15 22:29 ` [bug#62356] [PAtCH " ( via Guix-patches via 2023-04-18 19:55 ` [bug#62356] [PATCH " Ludovic Courtès 2023-04-18 20:05 ` ( via Guix-patches via 2023-04-18 20:08 ` ( via Guix-patches via 2023-04-19 8:16 ` Simon Tournier 2023-04-19 10:00 ` Ludovic Courtès 2023-04-19 14:17 ` bug#62356: " Ludovic Courtès 2023-04-19 15:15 ` [bug#62356] " ( via Guix-patches via 2023-04-19 13:03 ` [bug#62356] [PAtCH " Théo Maxime Tyburn
Code repositories for project(s) associated with this public inbox https://git.savannah.gnu.org/cgit/guix.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).