From debbugs-submit-bounces@debbugs.gnu.org Tue Mar 21 16:58:15 2023 Received: (at submit) by debbugs.gnu.org; 21 Mar 2023 20:58:15 +0000 Received: from localhost ([127.0.0.1]:60945 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pej3d-00025v-CW for submit@debbugs.gnu.org; Tue, 21 Mar 2023 16:58:15 -0400 Received: from lists.gnu.org ([209.51.188.17]:40190) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pej3a-00025h-LA for submit@debbugs.gnu.org; Tue, 21 Mar 2023 16:58:12 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pej3W-0003Tt-AX for guix-patches@gnu.org; Tue, 21 Mar 2023 16:58:06 -0400 Received: from knopi.disroot.org ([178.21.23.139]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pej3S-0003xn-4s for guix-patches@gnu.org; Tue, 21 Mar 2023 16:58:06 -0400 Received: from localhost (localhost [127.0.0.1]) by disroot.org (Postfix) with ESMTP id A3EC34449A; Tue, 21 Mar 2023 21:57:57 +0100 (CET) X-Virus-Scanned: SPAM Filter at disroot.org Received: from knopi.disroot.org ([127.0.0.1]) by localhost (disroot.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id SEjwCLDnXdUe; Tue, 21 Mar 2023 21:57:54 +0100 (CET) From: "(" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=disroot.org; s=mail; t=1679432274; bh=M6B4xqzv1U/Stc18snRaRbZ+2r8zNRV1j1ryBpM/H+M=; h=From:To:Cc:Subject:Date; b=krcH7vEPWn05C486SmbwjLxhkbW/R5uFu20qkUU9grAy+w9RXUTELdf07Vf94kQhe BSdHvjbP83XsHbldmKXrpz1ZLDtms2QqsW5fKVO+yWbswXqTnZkAnBwQFX7dJ19FFJ tayVhyxrUqeDk1mwiHoU8+XbOByGTCLciyWC101UP1WF8MnNYweDfYPXjywoSLu8BZ OtndfODfkPRcRdgUYN2p/Mw83GNgH6AmbQSTklM+ovKagpZRbpjDzZow1ujhd3XdMf DBCFiFGfmPpNZc9haMTh4uN4LxaWSYN1k3sjqRN002E5GgRDH5plY4XrNfy0ZXPnms 8LjtIFeLhQfrA== To: guix-patches@gnu.org Subject: [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. Date: Tue, 21 Mar 2023 20:57:49 +0000 Message-Id: <20230321205749.4974-1-paren@disroot.org> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Received-SPF: pass client-ip=178.21.23.139; envelope-from=paren@disroot.org; helo=knopi.disroot.org X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Spam-Score: -1.4 (-) X-Debbugs-Envelope-To: submit Cc: "\(" X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -2.4 (--) * 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))))) +⇒ # /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 ``, 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 `` 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 ``, 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 `` records in the +`references` field, except for the last kind, which will become `` +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 +⇒ # + #:out> + # "/bin/irssi">:out> + #:bin> + #) …> +``` + +Note the use of `file-append` in both the previous example and `gexp-builder`; +this procedure produces a `` 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 +`` 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))) +⇒ # /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 `` +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_: + +- ``, a Guix package. +- ``, 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) + 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 ) + 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 ``: + +```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 +⇒ #< + type: #> + lower: # + expand: #> +``` + +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 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 + 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 `` 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 `` 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 From debbugs-submit-bounces@debbugs.gnu.org Wed Apr 12 11:39:45 2023 Received: (at submit) by debbugs.gnu.org; 12 Apr 2023 15:39:45 +0000 Received: from localhost ([127.0.0.1]:40932 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pmcZU-0005EQ-Sa for submit@debbugs.gnu.org; Wed, 12 Apr 2023 11:39:45 -0400 Received: from lists.gnu.org ([209.51.188.17]:38004) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pmcZS-0005ED-KH for submit@debbugs.gnu.org; Wed, 12 Apr 2023 11:39:43 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pmcZS-0001Rr-CL for guix-patches@gnu.org; Wed, 12 Apr 2023 11:39:42 -0400 Received: from mail-wm1-x32d.google.com ([2a00:1450:4864:20::32d]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1pmcZP-0001a0-IZ for guix-patches@gnu.org; Wed, 12 Apr 2023 11:39:42 -0400 Received: by mail-wm1-x32d.google.com with SMTP id 5b1f17b1804b1-3f080fc6994so4062295e9.1 for ; Wed, 12 Apr 2023 08:39:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1681313978; h=content-transfer-encoding:mime-version:message-id:date:references :in-reply-to:subject:cc:to:from:from:to:cc:subject:date:message-id :reply-to; bh=LYRP2WA5w5Ny/z4nwtBAVTBQuJ0wTvbFkC4uIKdxZO4=; b=SZzCUoKFaUyGm/aRwbPt7MvbSDpWO/cCZ47Ouvt3cn8qshwZpTTR/FtPbQ6u6eAy1e P26oHNgMN1j7GFmLKRplD4acM/6wp4nJzDGC9DWy+HwdfTfPeDeFWNuqZQjEL9ukAdks 8sixzKtvyouvOXi/HwReXOFU7WXR/KLYzNl1Rhekei+6Zygy8hIzggCvJ+hj1Dgcd+M/ xwzQ+kEZSygOa7RqJydxoLJA+zFD03l6YJZ82IFSQgEwfb33MWhn+8/RRStqEVfJO9+l 7v0dvyPRIJg6Az7sWtsobuc6asVh+nntHrOy+csBVrBdTtZXskXR/HmH1uuoWoJNPgdW tvDg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; t=1681313978; h=content-transfer-encoding:mime-version:message-id:date:references :in-reply-to:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=LYRP2WA5w5Ny/z4nwtBAVTBQuJ0wTvbFkC4uIKdxZO4=; b=R+jQZyYWOEW2H5MQP2hoO5Yu8Y/HCcxhZwlY8HeO/COtH7O2AnuiKRXBv7qSjNhmjL 9YVjaoi6XEQPmeq6Kkw/wv4cG3Uv8t8Yx9Ep12JKKK+fqJNoaWrQ8bp2lLCC/s7wjqWh 2xGTy0QOSgGJ/KP4ZojBau2/RETl251D/27gSaLz/BNFXUL+Gm/MZ/i780pkjKqDFTKA TXCsGIP2fpEyjVJ6TNHBX3ASzvf+Un6qfNG3WjLjpkjt741sfQnHYDJZdyLNWQoKa17v uzPag6s1cuXpYgQr5eCx2/10PhsZrTgZJscBx+/8E/GbzLunBXdIGQt9VLVWUVCOnx03 ADtA== X-Gm-Message-State: AAQBX9c6tGlBQCBcMskk3RNLmTZqm3slFfFvp2mo2WD6YykivfFAFD9o gkY1WPzBn5TxK99+XJhe8qY= X-Google-Smtp-Source: AKy350akyAI1pwhWIVhGTbNJMyjcisouH7NfNdQtXsCOvNDurAtG0z1+d6DYc51rwYzCckxDuLJIkw== X-Received: by 2002:a05:600c:c8f:b0:3f0:9deb:c60a with SMTP id fj15-20020a05600c0c8f00b003f09debc60amr2145845wmb.4.1681313977755; Wed, 12 Apr 2023 08:39:37 -0700 (PDT) Received: from pfiuh07 ([193.48.40.241]) by smtp.gmail.com with ESMTPSA id i6-20020a05600c290600b003e91b9a92c9sm2717034wmd.24.2023.04.12.08.39.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 12 Apr 2023 08:39:37 -0700 (PDT) From: Simon Tournier To: "( via Guix-patches via" , 62356@debbugs.gnu.org Subject: Re: [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. In-Reply-To: <20230321205749.4974-1-paren@disroot.org> References: <20230321205749.4974-1-paren@disroot.org> Date: Wed, 12 Apr 2023 17:29:17 +0200 Message-ID: <87ile1glv6.fsf@gmail.com> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Received-SPF: pass client-ip=2a00:1450:4864:20::32d; envelope-from=zimon.toutoune@gmail.com; helo=mail-wm1-x32d.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Spam-Score: -1.3 (-) X-Debbugs-Envelope-To: submit Cc: "\(" X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -2.3 (--) Hi, Cool! Thanks, it=E2=80=99s very helpful. Minor comments. On mar., 21 mars 2023 at 20:57, "\( via Guix-patches" via wrote: [...] > +The shortcomings of using s-expressions in this way are numerous: we hav= e to > +convert everything to a derivation before using it, and _inputs are not = an > +inherent aspect of the builder_. G-expressions were designed to overcom= e 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-build= er`: > + > +```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 th= e syntax > +and the `` object we've created. To make a gexp, we use the `#~` = syntax, > +equivalent to the `gexp` macro, rather than the `quasiquote` backtick us= ed 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 `unquot= e` > +(`,`). `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. >=20 > +- Sexps (strings, lists, etc), which will be embedded literally. >From a stylistic point of view, I would write =E2=80=99S-expressions=E2=80= =99 in plain and not S-exps or sexps=E2=80=A6 > +- Other gexps, embedded literally. =E2=80=A6Similarly for G-expression. Both over all the post. Except when = it refers to code as =E2=80=99gexp-builder=E2=80=99. > +That's a lot of complexity abstracted away! For more complex packages a= nd > +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, howeve= r, have > +to watch out for situations where `ungexp-native`, written as `#+`, woul= d be > +preferable over regular `ungexp`, and that's something we'll discuss lat= er. Before the brief digression, I would do another. ;-) Mention ,build and ,lower from =E2=80=9Cguix repl=E2=80=9D. > +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 g= exp > +library. These include `computed-file`, which accepts a gexp that builds > +the output file, `program-file`, which creates an executable Scheme scri= pt 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_002dExpressio= ns.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 objec= ts > +included with the gexps library, surely there must be a way to define mo= re? > +Naturally, there is; this is Scheme, after all! We simply need to acqua= int > +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")) > +=E2=87=92 ("." ".." "editor" "irc") > +``` > + > +Who knows why you'd want to do this, but it certainly works! We've look= ed 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 =E2=80=99silly=E2=80=99, I would pick another name as =E2= =80=99simple=E2=80=99 or =E2=80=99empty=E2=80=99 or =E2=80=99trivial=E2=80=99 or =E2=80=99not-seriou= s=E2=80=99 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 sti= ll find > +yourself with questions, please don't hesitate to stop by at the IRC cha= nnel > +`#guix:libera.chat` and mailing list `help-guix@gnu.org`; we'll be glad = to > +assist you! Maybe, you could link to Arun=E2=80=99s or Marius=E2=80=99s 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 From debbugs-submit-bounces@debbugs.gnu.org Wed Apr 12 12:16:42 2023 Received: (at submit) by debbugs.gnu.org; 12 Apr 2023 16:16:42 +0000 Received: from localhost ([127.0.0.1]:41546 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pmd9G-0000c7-BH for submit@debbugs.gnu.org; Wed, 12 Apr 2023 12:16:42 -0400 Received: from lists.gnu.org ([209.51.188.17]:46338) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pmd9E-0000bx-Jo for submit@debbugs.gnu.org; Wed, 12 Apr 2023 12:16:41 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pmd9C-0002GT-8E for guix-patches@gnu.org; Wed, 12 Apr 2023 12:16:39 -0400 Received: from knopi.disroot.org ([178.21.23.139]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pmd99-0002fH-Vl for guix-patches@gnu.org; Wed, 12 Apr 2023 12:16:37 -0400 Received: from localhost (localhost [127.0.0.1]) by disroot.org (Postfix) with ESMTP id A0AAB401C8; Wed, 12 Apr 2023 18:16:32 +0200 (CEST) X-Virus-Scanned: SPAM Filter at disroot.org Received: from knopi.disroot.org ([127.0.0.1]) by localhost (disroot.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id MIFliabkmf6P; Wed, 12 Apr 2023 18:16:31 +0200 (CEST) References: <20230321205749.4974-1-paren@disroot.org> <87ile1glv6.fsf@gmail.com> DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=disroot.org; s=mail; t=1681316190; bh=Pi+jdIMxuGWYSaYLYxLhtCPivqo7BFW9TmLWiZmQ+Ow=; h=References:From:To:Cc:Subject:Date:In-reply-to; b=l2SHOpJnyh2ah4O7dc4aMkO4i8EwLxw0PUGYT9B+SlsC3qoXBYY3EOeVRcKgUIRc0 19g5yjeTmv+Iiyg8I+txExQEilL/pIwxujxuFKqqTDGuaUAZ2Kqf8QlZ7zmZWJHt4+ fCiqeClIemn7lJl7rNe9ovaiLnovUscj9Vw50d3wc55tiZP3Xjs3SqT7y64nhZdne4 Zxo7WMSZQTaQhJ32RvnGtE6UElcr2gyQhpuyYDzBqPlEsyMOvN51nZP+wtb3RvVxf9 NOF7NXeYASsEeKQirSLR8/FxsvXwCWOlsBEVua2j0p+6bJLEGSIZJRvwhKsxzWTtil FNM+vtmQSzVVQ== From: "(" To: Simon Tournier Subject: Re: [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. Date: Wed, 12 Apr 2023 17:06:18 +0100 In-reply-to: <87ile1glv6.fsf@gmail.com> Message-ID: <87r0sp6ppe.fsf@disroot.org> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Received-SPF: pass client-ip=178.21.23.139; envelope-from=paren@disroot.org; helo=knopi.disroot.org X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Spam-Score: -1.4 (-) X-Debbugs-Envelope-To: submit Cc: 62356@debbugs.gnu.org, "\( via Guix-patches via" X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -2.4 (--) Hi, Thanks for the review! :D Simon Tournier writes: >> +The shortcomings of using s-expressions in this way are numerous: we ha= ve to >> +convert everything to a derivation before using it, and _inputs are not= an >> +inherent aspect of the builder_. G-expressions were designed to overco= me 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 =E2=80=99S-expressions=E2= =80=99 in plain > and not S-exps or sexps=E2=80=A6 > > =E2=80=A6Similarly for G-expression. Both over all the post. Except whe= n it > refers to code as =E2=80=99gexp-builder=E2=80=99. Okay. > Before the brief digression, I would do another. ;-) Mention ,build and > ,lower from =E2=80=9Cguix repl=E2=80=9D. ,LOWER is mentioned in part 1 ; 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 =E2=80=99silly=E2=80=99, I would pick another name as = =E2=80=99simple=E2=80=99 or > =E2=80=99empty=E2=80=99 or =E2=80=99trivial=E2=80=99 or =E2=80=99not-seri= ous=E2=80=99 or else. :-) > > And similarly for snippets from above. Okay. > Maybe, you could link to Arun=E2=80=99s or Marius=E2=80=99s posts; for th= e 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. From debbugs-submit-bounces@debbugs.gnu.org Sat Apr 15 18:30:11 2023 Received: (at 62356) by debbugs.gnu.org; 15 Apr 2023 22:30:11 +0000 Received: from localhost ([127.0.0.1]:50264 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pnoPJ-0007sg-89 for submit@debbugs.gnu.org; Sat, 15 Apr 2023 18:30:11 -0400 Received: from knopi.disroot.org ([178.21.23.139]:37738) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pnoPF-0007sO-25 for 62356@debbugs.gnu.org; Sat, 15 Apr 2023 18:30:07 -0400 Received: from localhost (localhost [127.0.0.1]) by disroot.org (Postfix) with ESMTP id B6C6240289; Sun, 16 Apr 2023 00:30:03 +0200 (CEST) X-Virus-Scanned: SPAM Filter at disroot.org Received: from knopi.disroot.org ([127.0.0.1]) by localhost (disroot.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id n2FEgVuWtGSo; Sun, 16 Apr 2023 00:30:00 +0200 (CEST) From: "(" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=disroot.org; s=mail; t=1681597800; bh=/3lI2URPkwMyegmAM2GqzkZo0Blx45YvQEZ07QGQ0hw=; h=From:To:Cc:Subject:Date; b=QbGWztsiu3ByTJNxE9j83ldB2GuaNQbEzX5euRy6UisekpEKcLMiTDuWvcS0Mo0xi KsDadxUr0AIbmD6icA5O0qLtcITIsoRj8PTAhfV8SL+NCQLI+J4Kq0+V9zKeVGnVdJ ICLH0oh2anwkGYSLwDaue+VZXunr9NnMGidY6yKZFMELotVPifHzXSljkBj5Cv0idK yEaDi8RBdKcUktiJFJ4MpO9rnGij11suzSoyzBY9KzYSFdaF4vC//zYJ6xE9t1/yVG Gk5VSNSyszMnqdBBLYk/fg1NHXtFqSLwEQuNtP42NllVuzZVtPtmqkbnREHlhzisaY C0mSjKwedvp4w== To: 62356@debbugs.gnu.org Subject: [PAtCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. Date: Sat, 15 Apr 2023 23:29:54 +0100 Message-Id: <20230415222954.567-1-paren@disroot.org> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 62356 Cc: "\(" , =?UTF-8?q?Th=C3=A9o=20Maxime=20Tyburn?= , Simon Tournier X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) * 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))))) +⇒ # /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 ``, 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 `` 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 ``, 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 `` records in the +`references` field, except for the last kind, which will become `` +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 +⇒ # + #:out> + # "/bin/irssi">:out> + #:bin> + #) …> +``` + +Note the use of `file-append` in both the previous example and `gexp-builder`; +this procedure produces a `` 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 +`` 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))) +⇒ # /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 `` +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_: + +- ``, a Guix package. +- ``, 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) + 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 ) + 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 ``: + +```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 +⇒ #< + type: #> + lower: # + expand: #> +``` + +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 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 + 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 `` 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 `` 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 From debbugs-submit-bounces@debbugs.gnu.org Tue Apr 18 15:56:06 2023 Received: (at 62356) by debbugs.gnu.org; 18 Apr 2023 19:56:06 +0000 Received: from localhost ([127.0.0.1]:60426 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1porQr-0005h1-N5 for submit@debbugs.gnu.org; Tue, 18 Apr 2023 15:56:05 -0400 Received: from eggs.gnu.org ([209.51.188.92]:56766) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1porQm-0005gU-JH for 62356@debbugs.gnu.org; Tue, 18 Apr 2023 15:56:04 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1porQg-0001Bo-IU; Tue, 18 Apr 2023 15:55:54 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=MIME-Version:In-Reply-To:Date:References:Subject:To: From; bh=YQkR7VkJOh2NbS4uI024z+XXOGdpw0gwr2N+zZzBXWI=; b=aKcUkzgpOM13nYZGaX+D o8VdhML3rXe8r/okDwiwQMuQU28A6erm57InM8TlWkvms5/cHnugGl9040OVw40pDG7xdxwzjYDHD xeB5O78GT3vg7hecFMh4ZeiXaTayRsU7uxfDhee5ic+XjGEIvOkwACh9tDrv/S9YmJTOLZYNuBBsX VAffxcn0/53Npv1xf17w0mQ4O8suJHnDeRpgtp8G/dP4xv3aJw8sS6TqjCFkQLzBhzqtQxSVo7RFD D+wHuh3yXsdhT+0VaavRGBe/TOodtWTCXT99pynwZQSnAddkFoa1MqNj+JAhPAF+3oImUFeR5eaYS i0GBaMRqWztwWA==; Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (helo=ribbon) by fencepost.gnu.org with esmtpsa (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1porQg-0002qI-5w; Tue, 18 Apr 2023 15:55:54 -0400 From: =?utf-8?Q?Ludovic_Court=C3=A8s?= To: "(" Subject: Re: bug#62356: [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. References: <20230321205749.4974-1-paren@disroot.org> <20230415222954.567-1-paren@disroot.org> Date: Tue, 18 Apr 2023 21:55:51 +0200 In-Reply-To: <20230415222954.567-1-paren@disroot.org> (paren@disroot.org's message of "Sat, 15 Apr 2023 23:29:54 +0100") Message-ID: <87edohhsmw.fsf_-_@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.2 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 62356 Cc: =?utf-8?Q?Th=C3=A9o?= Maxime Tyburn , 62356@debbugs.gnu.org, Simon Tournier X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) Hello, "(" skribis: > * website/posts/dissecting-guix-3-gexps.md: New blog post. This looks perfect to me, great job! If there are no objections, I=E2=80=99ll push it tomorrow noon (GMT), which should be better timing than now. Thanks a lot, and thanks Simon for reviewing! Ludo=E2=80=99. From debbugs-submit-bounces@debbugs.gnu.org Tue Apr 18 16:07:56 2023 Received: (at 62356) by debbugs.gnu.org; 18 Apr 2023 20:07:56 +0000 Received: from localhost ([127.0.0.1]:60453 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1porcH-00062X-R6 for submit@debbugs.gnu.org; Tue, 18 Apr 2023 16:07:56 -0400 Received: from knopi.disroot.org ([178.21.23.139]:60692) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1porcC-00062H-ML for 62356@debbugs.gnu.org; Tue, 18 Apr 2023 16:07:52 -0400 Received: from localhost (localhost [127.0.0.1]) by disroot.org (Postfix) with ESMTP id 5CBFF40607; Tue, 18 Apr 2023 22:07:47 +0200 (CEST) X-Virus-Scanned: SPAM Filter at disroot.org Received: from knopi.disroot.org ([127.0.0.1]) by localhost (disroot.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id Y-hIY9hb9Tk0; Tue, 18 Apr 2023 22:07:46 +0200 (CEST) References: <20230321205749.4974-1-paren@disroot.org> <20230415222954.567-1-paren@disroot.org> <87edohhsmw.fsf_-_@gnu.org> DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=disroot.org; s=mail; t=1681848466; bh=++i/9fLc8gw0pxdi1U8qtrn5t/3Lhv1iRUjXe8d81qM=; h=References:From:To:Cc:Subject:Date:In-reply-to; b=A8cpUaAm/2RZ6YwnQk4IJdAsQ6EKvmiqRBV+yTxhm6uU9FG3TsLwPU87rU3gI8LFA FAg9f0jptl/56J7k60o0ZZxmz2yjXz52vhHpcpOcTkBNh1Gk29JVEczn6YGsxshw5e NLYcjAcKfVBtIQjuakHMHyFRC1/me/NW1YapuzUk4PyxkgTfuBe0XXExMbTkiHBsos ptAqzDo4Nhcz5fTQvtuieuVmFsPU3aqLnCmIBWRq2QEP37VfGw5+dsOLgXibrQYk/+ ATB00okNBORxP3z5gxLEdNWvMDZpW3i5hoTp+iiPr8Jyp12n4gWIv+NRKiRzX06jIT kh9IcbhNZ6syw== From: "(" To: Ludovic =?utf-8?Q?Court=C3=A8s?= Subject: Re: bug#62356: [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. Date: Tue, 18 Apr 2023 21:05:18 +0100 In-reply-to: <87edohhsmw.fsf_-_@gnu.org> Message-ID: <877cu9ymwi.fsf@disroot.org> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 62356 Cc: =?utf-8?Q?Th=C3=A9o?= Maxime Tyburn , 62356@debbugs.gnu.org, Simon Tournier X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) Ludovic Court=C3=A8s writes: > Hello, > > "(" 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=E2=80=99ll push it tomorrow noon (GMT), whi= ch > should be better timing than now. \o/ From debbugs-submit-bounces@debbugs.gnu.org Tue Apr 18 16:09:13 2023 Received: (at 62356) by debbugs.gnu.org; 18 Apr 2023 20:09:13 +0000 Received: from localhost ([127.0.0.1]:60459 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pordZ-00065m-HC for submit@debbugs.gnu.org; Tue, 18 Apr 2023 16:09:13 -0400 Received: from knopi.disroot.org ([178.21.23.139]:54522) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pordX-00065d-Es for 62356@debbugs.gnu.org; Tue, 18 Apr 2023 16:09:12 -0400 Received: from localhost (localhost [127.0.0.1]) by disroot.org (Postfix) with ESMTP id DA284403FC; Tue, 18 Apr 2023 22:09:10 +0200 (CEST) X-Virus-Scanned: SPAM Filter at disroot.org Received: from knopi.disroot.org ([127.0.0.1]) by localhost (disroot.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id bZzdWGEXopX9; Tue, 18 Apr 2023 22:09:09 +0200 (CEST) References: <20230321205749.4974-1-paren@disroot.org> <20230415222954.567-1-paren@disroot.org> <87edohhsmw.fsf_-_@gnu.org> DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=disroot.org; s=mail; t=1681848549; bh=7EBx2lSJQmwj90wNg5VuCjPVFloQ17R4fO/vuyzJ3Zw=; h=References:From:To:Cc:Subject:Date:In-reply-to; b=LBzZ//HwmiebS7cKLyFWUjOzg4jGmD9FZq7yiM2uMDPZaF5qsU9CTjcx4jaKsTNtD cVwHIc0OZ44BKKPycTL0k44JciVerMIVgleoRQjgMZw/rPAaikFpS9DVpOYSCFaJdo hYQAdRpp/3gEppwK/0nSt0G03fS8nfyNlciYQxO3VoVc04G8GeBUEGfSgFYs747At4 OqVfem9H7pYoVV490YRct7DHmEbUS4XeYh/L11hWGNkOyiY6MOCH5FCaqIgi8IEj31 8s21CwM+qvuV3Db8G8xEg9O1Y+nvOR4eDCcWIx/Dr7MlMAIe8axvbF0CYloCvWcPZc LH8NOo1fb+gDg== From: "(" To: Ludovic =?utf-8?Q?Court=C3=A8s?= Subject: Re: bug#62356: [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. Date: Tue, 18 Apr 2023 21:08:28 +0100 In-reply-to: <87edohhsmw.fsf_-_@gnu.org> Message-ID: <87354xymu3.fsf@disroot.org> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 62356 Cc: =?utf-8?Q?Th=C3=A9o?= Maxime Tyburn , 62356@debbugs.gnu.org, Simon Tournier X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) Ludovic Court=C3=A8s writes: > This looks perfect to me, great job! > > If there are no objections, I=E2=80=99ll push it tomorrow noon (GMT), whi= ch > 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 :) From debbugs-submit-bounces@debbugs.gnu.org Wed Apr 19 04:55:20 2023 Received: (at 62356) by debbugs.gnu.org; 19 Apr 2023 08:55:20 +0000 Received: from localhost ([127.0.0.1]:32769 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pp3ax-0005M4-Ol for submit@debbugs.gnu.org; Wed, 19 Apr 2023 04:55:20 -0400 Received: from mail-wr1-f48.google.com ([209.85.221.48]:41424) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pp3ar-0005K4-H6 for 62356@debbugs.gnu.org; Wed, 19 Apr 2023 04:55:17 -0400 Received: by mail-wr1-f48.google.com with SMTP id ffacd0b85a97d-2f94fe993edso438537f8f.1 for <62356@debbugs.gnu.org>; Wed, 19 Apr 2023 01:55:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1681894506; x=1684486506; h=content-transfer-encoding:mime-version:message-id:date:references :in-reply-to:subject:cc:to:from:from:to:cc:subject:date:message-id :reply-to; bh=9qAl/69SmZQ03h8ppVHZ+axmxeYanuyi9PmWVtJHLbk=; b=QUktgt8Qfs22B9YkCJm0DuRvdeAHAWG/epi30eIqNUhSvPtGVDix2rMae/jide3xzX DUrZXXSYCH35YntDzR5Vf5zPzXuHxAbHNm+9TKH+DAmvIgaOxln3t72WFfJIgyd/1esP DKMa2JbY+4aOl6vf4B/76PvYHQwi8LJ9bW4lLV3TjvxzMXNcd5Uqb8LJJYD7Zfp/UBoX X2YOKOMPHjj8jwCy4MsQgafOPYTRD9XK5AyzLLI65p4zWUAtG0NW0N8N2KGDPxgAjaxa d+atngxGsNzYJWbVoy2Re3ZITNNmZeruUkWwLwN2N7pIlHvuvMNECjEJuFN/y36Up+1R LJNQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681894506; x=1684486506; h=content-transfer-encoding:mime-version:message-id:date:references :in-reply-to:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=9qAl/69SmZQ03h8ppVHZ+axmxeYanuyi9PmWVtJHLbk=; b=lsx51yNXVoEymL+7cXd+KFBGfFaJ+tE7/amMAKGnWkzWtIrhc2IBERF8halOG1Pq2y qwL/I6i+GASOWkCWWm8A5H7vYRedKhSQ5H7tida0SrsUXn7EtFcmUlneFNtPg2px8zYh Uu+X9NjAM21MX3j3DvDFwkCew73MaXzxWl14C2Ab/asJPmz0Tg+iqHDKtwSU+Ir4v5LB 60XlaSHQuTUUX4rasnMwoSzlXUkML49x3ABYaHPc9uZUPBwtRn/bNBPrhE2uzBhQUuTK 3X1eXIcT87uGZGmm0cXxWfx8BUUh+zz6DwQoH0fGP8deN7dFW+doM8NBSBUOuGaqIKiC YNxw== X-Gm-Message-State: AAQBX9eMTcO6uqzpLosMzWaE9MNQN/xfSTmc9CTduzkR1rDIdOpc9w6v Bf+L4HhS3YIday0Bhq1YLObWIqTMhX8= X-Google-Smtp-Source: AKy350acGdQEuIehZ2V5afswE0COYa4wEXy4qmDPCnsONY/EkVY0uysGhY9HzTIXSmcrc42tW1FKIw== X-Received: by 2002:a5d:548e:0:b0:2c7:1c72:699f with SMTP id h14-20020a5d548e000000b002c71c72699fmr10888035wrv.4.1681894506398; Wed, 19 Apr 2023 01:55:06 -0700 (PDT) Received: from lili ([2a01:e0a:59b:9120:65d2:2476:f637:db1e]) by smtp.gmail.com with ESMTPSA id z4-20020adfe544000000b002f6dafef040sm13514331wrm.12.2023.04.19.01.55.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 19 Apr 2023 01:55:05 -0700 (PDT) From: Simon Tournier To: "(" , Ludovic =?utf-8?Q?Court=C3=A8s?= Subject: Re: [bug#62356] [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. In-Reply-To: <87354xymu3.fsf@disroot.org> References: <20230321205749.4974-1-paren@disroot.org> <20230415222954.567-1-paren@disroot.org> <87edohhsmw.fsf_-_@gnu.org> <87354xymu3.fsf@disroot.org> Date: Wed, 19 Apr 2023 10:16:34 +0200 Message-ID: <86sfcwjnh9.fsf@gmail.com> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 62356 Cc: =?utf-8?Q?Th=C3=A9o?= Maxime Tyburn , 62356@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) Hi, On Tue, 18 Apr 2023 at 21:08, "\( via Guix-patches" via 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, P= art 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=E2=80=99s 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=3D62356;mbox=3Dyes;msg=3D23 3: https://issues.guix.gnu.org/issue/62356 Cheers, simon From debbugs-submit-bounces@debbugs.gnu.org Wed Apr 19 06:01:17 2023 Received: (at 62356) by debbugs.gnu.org; 19 Apr 2023 10:01:17 +0000 Received: from localhost ([127.0.0.1]:32809 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pp4cn-0007BS-1Z for submit@debbugs.gnu.org; Wed, 19 Apr 2023 06:01:17 -0400 Received: from eggs.gnu.org ([209.51.188.92]:47544) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pp4ci-0007BB-Fz for 62356@debbugs.gnu.org; Wed, 19 Apr 2023 06:01:15 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pp4ca-00059F-QS; Wed, 19 Apr 2023 06:01:06 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=MIME-Version:In-Reply-To:Date:References:Subject:To: From; bh=kDvqoH+NXrbxk+Y6b8xC/d1V0Xydsd5aeaDeTfiyrLo=; b=ZAh+3ZVvfs7qY2DPHco/ r0mc7aN7cq+8RenVWELfZ1rSmGGKHYJHfRrjZSPsxzJ8J/sw5UWXN7XSBk7IsmxRE+xtKVDGeGImH 8dwlpiLIO0q5zk/nMonFtQZlKlGEXJEXY+4WwJKSrXlhRIZI8eHM8OnIXO5gh2wA8lxa6AOy9b6O8 +D3yX4aNiM5becVRQFqYx8U1uxaI9OwHWhckur5YEozGsDgHlSjYbqcnoGy3oiOTgE+X8RY7mdgBD c6aguXb4eHJW0WJr+IjbV3RCwLMiu6Xmvvl+LcdH2S/mnqTvIatiVpp59qQi21ILyJ6W/I5mF158a gb16OwwoEe44iA==; Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (helo=ribbon) by fencepost.gnu.org with esmtpsa (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pp4cA-0006Mq-FT; Wed, 19 Apr 2023 06:00:53 -0400 From: =?utf-8?Q?Ludovic_Court=C3=A8s?= To: "(" Subject: Re: bug#62356: [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. References: <20230321205749.4974-1-paren@disroot.org> <20230415222954.567-1-paren@disroot.org> <87edohhsmw.fsf_-_@gnu.org> <87354xymu3.fsf@disroot.org> Date: Wed, 19 Apr 2023 12:00:34 +0200 In-Reply-To: <87354xymu3.fsf@disroot.org> (paren@disroot.org's message of "Tue, 18 Apr 2023 21:08:28 +0100") Message-ID: <87pm80gpj1.fsf_-_@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.2 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 62356 Cc: =?utf-8?Q?Th=C3=A9o?= Maxime Tyburn , 62356@debbugs.gnu.org, Simon Tournier X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) "(" skribis: > Ludovic Court=C3=A8s writes: >> This looks perfect to me, great job! >> >> If there are no objections, I=E2=80=99ll push it tomorrow noon (GMT), wh= ich >> 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=E2=80=99m talking about v2. :-) From debbugs-submit-bounces@debbugs.gnu.org Wed Apr 19 10:18:13 2023 Received: (at 62356-done) by debbugs.gnu.org; 19 Apr 2023 14:18:13 +0000 Received: from localhost ([127.0.0.1]:35501 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pp8dR-0005hM-Db for submit@debbugs.gnu.org; Wed, 19 Apr 2023 10:18:13 -0400 Received: from eggs.gnu.org ([209.51.188.92]:58604) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pp8dN-0005h4-1E for 62356-done@debbugs.gnu.org; Wed, 19 Apr 2023 10:18:12 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pp8dH-00007y-3b; Wed, 19 Apr 2023 10:18:03 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=MIME-Version:In-Reply-To:Date:References:Subject:To: From; bh=3LOK/hpF38QOfCpKL4LbkUcaGhlyugQ89WSAFI9zZUE=; b=ZGRPoZs1R4GA9auAi6CB F3ZpEbanD1n52r2V9YDYSC/n6QMdRPNPDEfI89RdAPZ3VQuhiaz1gbv5PdCDpA8ogSUui3sr4jNsF 67KrzmwqagmoUHg5mivNBeCy/m2zRhPppzWWSLq1v5Bf34raXgvUYUHVVYrbaRsTX7fJgVFRy7Lx8 G8Qj2cB9avmzWk9SVmk8xpxH9EayREwWxtTJXSL3YbQ4tW19OSEYgH+0atA7sbjupTreR7Y7pfPxf 8nMXrvGR8VhczgkiI0OGPE0sRc+wqC3yAwU0V09ArhG1GAOQLnOfsxi7bfZvaltJfwb0KhG6lD1hc wrTOf6PZWjw4CA==; Received: from 91-160-117-201.subs.proxad.net ([91.160.117.201] helo=ribbon) by fencepost.gnu.org with esmtpsa (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pp8dG-0005v7-7J; Wed, 19 Apr 2023 10:18:02 -0400 From: =?utf-8?Q?Ludovic_Court=C3=A8s?= To: "(" Subject: Re: bug#62356: [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. References: <20230321205749.4974-1-paren@disroot.org> <20230415222954.567-1-paren@disroot.org> <87edohhsmw.fsf_-_@gnu.org> <87354xymu3.fsf@disroot.org> <87pm80gpj1.fsf_-_@gnu.org> Date: Wed, 19 Apr 2023 16:17:59 +0200 In-Reply-To: <87pm80gpj1.fsf_-_@gnu.org> ("Ludovic =?utf-8?Q?Court=C3=A8s?= =?utf-8?Q?=22's?= message of "Wed, 19 Apr 2023 12:00:34 +0200") Message-ID: <87fs8wgdm0.fsf_-_@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.2 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 62356-done Cc: =?utf-8?Q?Th=C3=A9o?= Maxime Tyburn , Simon Tournier , 62356-done@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) Done! https://guix.gnu.org/en/blog/2023/dissecting-guix-part-3-g-expressions/ Thank you! Ludo=E2=80=99. From debbugs-submit-bounces@debbugs.gnu.org Wed Apr 19 11:16:17 2023 Received: (at 62356-done) by debbugs.gnu.org; 19 Apr 2023 15:16:17 +0000 Received: from localhost ([127.0.0.1]:35558 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pp9Xc-0007L9-VS for submit@debbugs.gnu.org; Wed, 19 Apr 2023 11:16:17 -0400 Received: from knopi.disroot.org ([178.21.23.139]:45654) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pp9XY-0007Kv-QT for 62356-done@debbugs.gnu.org; Wed, 19 Apr 2023 11:16:16 -0400 Received: from localhost (localhost [127.0.0.1]) by disroot.org (Postfix) with ESMTP id 3AD8D4062C; Wed, 19 Apr 2023 17:16:12 +0200 (CEST) X-Virus-Scanned: SPAM Filter at disroot.org Received: from knopi.disroot.org ([127.0.0.1]) by localhost (disroot.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id ur79K4u3ygwo; Wed, 19 Apr 2023 17:16:10 +0200 (CEST) References: <20230321205749.4974-1-paren@disroot.org> <20230415222954.567-1-paren@disroot.org> <87edohhsmw.fsf_-_@gnu.org> <87354xymu3.fsf@disroot.org> <87pm80gpj1.fsf_-_@gnu.org> <87fs8wgdm0.fsf_-_@gnu.org> DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=disroot.org; s=mail; t=1681917370; bh=9p6ggANQCkfUVRrzq0sDZ1H5Hwx4QD1yEKzg1UyhoD4=; h=References:From:To:Cc:Subject:Date:In-reply-to; b=ecrmjOb5iskLLtgAdLuciz7a/mtlAMHEMtz4XHORYvzUWziL8yj+fkUuKAsc7j8Y1 jUO0l3eW5NNSh/V1hOqsr3858aCjiAt2C5l1M05CNWVqlHUbOHIbdL0XfDuE+epIB8 p26XrETfuSX5TejltgYE9jra32HJ9Pm7dh6qCZAYjAiZR157zAXmHFOGVUMI50EOab Y53OxiwTVeW207zyZiLGulQ1uk7S9HHVhoI6ubueilHVrExRc6kGymhknkXo17FV35 sC+oaXu8S8DQvuB2DKqhy+iMrZ8BDUE30eWgF84WEf26xWwqe1VZkWTKueGMHyXmvn q+Fvz3GKBp3OQ== From: "(" To: Ludovic =?utf-8?Q?Court=C3=A8s?= Subject: Re: bug#62356: [PATCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. Date: Wed, 19 Apr 2023 16:15:57 +0100 In-reply-to: <87fs8wgdm0.fsf_-_@gnu.org> Message-ID: <871qkfzyva.fsf@disroot.org> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 62356-done Cc: =?utf-8?Q?Th=C3=A9o?= Maxime Tyburn , Simon Tournier , 62356-done@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) Ludovic Court=C3=A8s writes: > Done! > > https://guix.gnu.org/en/blog/2023/dissecting-guix-part-3-g-expressions/ > > Thank you! \o/ From debbugs-submit-bounces@debbugs.gnu.org Wed Apr 19 15:58:52 2023 Received: (at 62356) by debbugs.gnu.org; 19 Apr 2023 19:58:52 +0000 Received: from localhost ([127.0.0.1]:35923 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1ppDx5-000769-Rd for submit@debbugs.gnu.org; Wed, 19 Apr 2023 15:58:52 -0400 Received: from mail-ej1-f41.google.com ([209.85.218.41]:33413) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pp7Tj-0006dL-OO for 62356@debbugs.gnu.org; Wed, 19 Apr 2023 09:04:08 -0400 Received: by mail-ej1-f41.google.com with SMTP id kt6so43290646ejb.0 for <62356@debbugs.gnu.org>; Wed, 19 Apr 2023 06:04:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1681909442; x=1684501442; h=mime-version:message-id:in-reply-to:date:subject:cc:to:from :user-agent:references:from:to:cc:subject:date:message-id:reply-to; bh=cTHbIVpUtbnpPtT7noOzwUKm11W6MRjp+G4a+2TY1zY=; b=bqr28UoYOx/ibugi//W2M49PJuF0F67tlKmgyfjhg7YADRy6fc/TIL1ac+OHJzAH9X xS4p/82dqJhkkpgXh9PTO53WjFtReoKuBmLNZ9sH4uLt3LFTmkejBguIbGRac9c3SDdu e0PcFD4fMfM02vgOGYHH8yWMYBl/ie+fv8p/WU5zWVkthS7JjX2Y+awaU/iIoxPxZi0z YRwE37IsxNxWELCCOrktWMzGzvJLFY76BaE5P5FXN+RP2q/tAZI4rPHOtcM7OPwwJK6t FKm9VI4fmT8+38gCKXpgqJs/q2kAb8sEddhkqhVf7udSYBpqzk61WUzcmckDqrRZvUIF /qpQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681909442; x=1684501442; h=mime-version:message-id:in-reply-to:date:subject:cc:to:from :user-agent:references:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=cTHbIVpUtbnpPtT7noOzwUKm11W6MRjp+G4a+2TY1zY=; b=A2J4N9ETETVAviVkL1tdCP2PY5W7VVSdPAMla0GxQSVwu/x8Gx9sU4/ohY68Xdm+RN FbmE1NZykPxsAwmaq+hti1HOk+7Y1IY+Y8b2q7BiiCON+iGnOxrh/6xVHuevLXmSfU+p +N+EvG761LWAwiIKkJgC318+B0+By8PZRJPJI62BeOWUaouCoT94Tt0IBpcXDN+cIg8t /CuOrnirjoi5PrVZs6kYbVyixVa7zYHCZJOUh63R9WNFcM2dJ1XLg55DzyPwB4fKRm1q wVUiZ0FbdwhKq2uaOgoayt+B4Cci+4nKsUkjagvOs/28/1hnRGIRswVZknXySU/8l8LR U4tQ== X-Gm-Message-State: AAQBX9feEScB1zYsonQFKw/3DeaCDQmbT56PS0IxfrKGH3cY2WbdMdaE iIQUngdfvK2f0jgtNQEJuU8= X-Google-Smtp-Source: AKy350bAaznd6PkjLU1/1u9x0EsMW0PI41xeTTHjETjLYFxYiRbjAia2zDuL7eFMRwJyH0QKrn45ig== X-Received: by 2002:a17:906:a194:b0:94e:75f8:668 with SMTP id s20-20020a170906a19400b0094e75f80668mr14220490ejy.56.1681909441710; Wed, 19 Apr 2023 06:04:01 -0700 (PDT) Received: from gazelle.gmail.com (dhcp-15-219.math.tu-berlin.de. [130.149.15.219]) by smtp.gmail.com with ESMTPSA id rx22-20020a1709068e1600b0094f968ecc97sm3676926ejc.13.2023.04.19.06.04.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 19 Apr 2023 06:04:01 -0700 (PDT) References: <20230415222954.567-1-paren@disroot.org> User-agent: mu4e 1.10.2; emacs 28.2 From: =?utf-8?Q?Th=C3=A9o?= Maxime Tyburn To: "(" Subject: Re: [PAtCH guix-artwork] website: posts: Add Dissecting Guix, Part 3: G-Expressions. Date: Wed, 19 Apr 2023 15:03:11 +0200 In-reply-to: <20230415222954.567-1-paren@disroot.org> Message-ID: <87ttxcgh1c.fsf@gmail.com> MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 62356 X-Mailman-Approved-At: Wed, 19 Apr 2023 15:58:49 -0400 Cc: 62356@debbugs.gnu.org, Simon Tournier X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) I like that V2 ! The references to other tutorials are quite nice :) From unknown Mon Aug 18 09:00:52 2025 Received: (at fakecontrol) by fakecontrolmessage; To: internal_control@debbugs.gnu.org From: Debbugs Internal Request Subject: Internal Control Message-Id: bug archived. Date: Thu, 18 May 2023 11:24:08 +0000 User-Agent: Fakemail v42.6.9 # This is a fake control message. # # The action: # bug archived. thanks # This fakemail brought to you by your local debbugs # administrator