with-imported-modules and friends

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?

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-path1 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:

1

with-imported-modules also sets %load-compiled-path, but in the interest of brevity, we skip that detail.