with-imported-modules and friends
Published by Arun Isaac on
In other languages: தமிழ்
Tags: guix, software, scheme, lisp
What is the ubiquitous with-imported-modules
in G-expressions? Why do you need it when you already have use-modules?
Here are a couple of simple Hello World G-expression program-file
scripts.
The first one has no with-imported-modules
. When we build it and run it in a clean environment, it errors out with no code for module (guix build utils)
.
;; foo.scm (use-modules ((gnu packages base) #:select (coreutils)) (guix gexp)) (define foo-gexp #~(begin (use-modules (guix build utils)) (invoke #$(file-append coreutils "/bin/echo") "foo"))) (program-file "foo" foo-gexp)
$ env -i $(guix build -f foo.scm) Backtrace: 9 (primitive-load "/gnu/store/1r5mxrxqw43y75njxg89ichill6?") In ice-9/eval.scm: 721:20 8 (primitive-eval (begin (use-modules (guix build #)) (?))) In ice-9/psyntax.scm: 1229:36 7 (expand-top-sequence (#<syntax:1r5mxrxqw43y75njxg89ic?>) ?) 1089:25 6 (parse _ (("placeholder" placeholder)) ((top) #(# # ?)) ?) 1221:19 5 (parse _ (("placeholder" placeholder)) ((top) #(# # ?)) ?) 259:10 4 (parse _ (("placeholder" placeholder)) (()) _ c&e (eval) ?) In ice-9/boot-9.scm: 3935:20 3 (process-use-modules _) 222:17 2 (map1 (((guix build utils)))) 3936:31 1 (_ ((guix build utils))) 3330:6 0 (resolve-interface (guix build utils) #:select _ #:hide ?) ice-9/boot-9.scm:3330:6: In procedure resolve-interface: no code for module (guix build utils)
The second one has with-imported-modules
and works fine.
;; foo.scm (use-modules ((gnu packages base) #:select (coreutils)) (guix gexp)) (define foo-gexp (with-imported-modules '((guix build utils)) #~(begin (use-modules (guix build utils)) (invoke #$(file-append coreutils "/bin/echo") "foo")))) (program-file "foo" foo-gexp)
$ env -i $(guix build -f foo.scm) foo
What's the deal? Why the difference? Shouldn't use-modules
have covered the necessary import? To find out, let us look at %load-path
in both cases. First, without with-imported-modules
.
;; foo.scm (use-modules (guix gexp)) (define foo-gexp #~(begin (for-each (lambda (path) (display path) (newline)) %load-path))) (program-file "foo" foo-gexp)
$ env -i $(guix build -f foo.scm) /gnu/store/9jcmmhrmp66wa3zl4rfi9fl72v4jhhxz-guile-3.0.9/share/guile/3.0 /gnu/store/9jcmmhrmp66wa3zl4rfi9fl72v4jhhxz-guile-3.0.9/share/guile/site/3.0 /gnu/store/9jcmmhrmp66wa3zl4rfi9fl72v4jhhxz-guile-3.0.9/share/guile/site /gnu/store/9jcmmhrmp66wa3zl4rfi9fl72v4jhhxz-guile-3.0.9/share/guile
And then, with with-imported-modules
.
;; foo.scm (use-modules (guix gexp)) (define foo-gexp (with-imported-modules '((guix build utils)) #~(begin (for-each (lambda (path) (display path) (newline)) %load-path)))) (program-file "foo" foo-gexp)
$ env -i $(guix build -f foo.scm) /gnu/store/jsxgc979x79h81kzqz9n6cpf5pk4z262-module-import /gnu/store/9jcmmhrmp66wa3zl4rfi9fl72v4jhhxz-guile-3.0.9/share/guile/3.0 /gnu/store/9jcmmhrmp66wa3zl4rfi9fl72v4jhhxz-guile-3.0.9/share/guile/site/3.0 /gnu/store/9jcmmhrmp66wa3zl4rfi9fl72v4jhhxz-guile-3.0.9/share/guile/site /gnu/store/9jcmmhrmp66wa3zl4rfi9fl72v4jhhxz-guile-3.0.9/share/guile
Notice the additional module-import
in the %load-path
from the second case. When we look into it, we see that it contains the (guix build utils)
module. So, with-imported-modules
builds a store item with the required modules, and sets %load-path
1 so that our program-file
script can find them!
$ tree /gnu/store/jsxgc979x79h81kzqz9n6cpf5pk4z262-module-import /gnu/store/jsxgc979x79h81kzqz9n6cpf5pk4z262-module-import └── guix └── build └── utils.scm 3 directories, 1 file
And, if we inspect the generated script, we indeed see the following expression setting %load-path
.
(set! %load-path (prepend (cons "/gnu/store/jsxgc979x79h81kzqz9n6cpf5pk4z262-module-import" (map (lambda (extension) (string-append extension "/share/guile/site/" (effective-version))) extensions)) %load-path))
source-module-closure
Now we lucked out with (guix build utils)
because it does not depend on any modules other than those shipped with guile itself. But, other modules, say (guix search-paths)
, may depend on more modules and those modules may themselves depend on even more modules. Now, we certainly don't want to manually list all these modules in with-imported-modules
. That's where source-module-closure
comes in—it finds all the modules for us, and saves the day! Here's a quick example.
(use-modules (guix modules) (guix gexp)) (define foo-gexp (with-imported-modules (source-module-closure '((guix search-paths))) #~(begin (use-modules (guix search-paths)) […]))) (program-file "foo" foo-gexp)
with-extensions
Now, all that works if we were importing Guix modules. But, what if we were importing modules from some other package? Guix wouldn't have the modules at hand. It may have to build or substitute the packages. And, it will have to create the necessary garbage collector references for those packages and all their dependencies. This is where with-extensions
comes in. Here is a quick example with guile-json. Try the same using with-imported-modules
and satisfy yourself that it does not work.
(use-modules ((gnu packages guile) #:select (guile-json-4)) (guix gexp)) (define foo-gexp (with-extensions (list guile-json-4) #~(begin (use-modules (json))))) (program-file "foo" foo-gexp)
That's it, hope that helped clear things up a bit!
Footnotes:
with-imported-modules
also sets %load-compiled-path
, but in the interest of brevity, we skip that detail.