Extending Guix
Guix is all about empowering people, so it should come as no surprise that one
can extend it with new guix
commands. You
can show the commands available in your current Guix through the help command:
$ guix help
If you are using a recent Guix, there are a number of extensions at your
disposal, they are available as individual packages, and as a meta-package
providing a collection of
extensions. Try
them out with:
$ guix shell guix guile guix-extension-collection
I'm adding the guix and guile packages to the shell because we want the
different Guile and Guix search paths to be adjusted in the shell. To know
more about why this is needed, read Search
Paths.
You will notice that the help menu has been extended with information about the available extensions:
$ guix help
...
extension commands
explore interactively explore a Guix System configuration
removals keep up to date with package removals
compose docker compose compatibility layer
toys Explore packages and services through REST API
xsearch search for packages using a fast Xapian cache
...
Since this blog post is called "Extending Guix", let's write an extension, shall we?
How does Guix locate extensions?
Guix locates extensions by looking up
GUIX_EXTENSIONS_PATH. Recently
Guix has introduced a new way of writing extensions. The old way would search
extensions under /path/to/guix/extensions and the extensions modules would be
named (guix extensions NAME). The new schema expects extensions to be under
/path/to/SCHEMA_VERSION; the name of the module will still be (guix extensions NAME).
The advantage of this new schema is the Guix can treat the extension module as a
standard Guile module, meaning that the runtime is able to find the compiled
.go file of the extension. The old schema was relaying on runtime evaluation;
the load machinery was not handling compiled files. This has a considerable
improvement on performance, so you are encouraged to update any old extension to
the new schema.
Writing an extension
Enough introductions, let's write a basic extension.
The first step is to create the project structure. Remember that the extension
machinery expects modules to be named (guix extensions NAME).
We start by creating, in our project root, the directories for the extension.
$ mkdir -p guix/extensions
Now, we create the file guix/extensions/hello.scm with the following contents:
(define-module (guix extensions hello))
A blank canvas...
We import (guix scripts) to get the define-command macro. We declare it and
export it so the extension machinery can find the command in the public
interface of the module.
(define-module (guix extensions hello)
#:use-module (guix scripts)
#:export (guix-hello))
(define-command (guix-hello . args)
(category extension)
(synopsis "say hello")
(display "hello, I'm a Guix extension!\n"))
With this, we already have a working Guix extension. We can run the extension like this from the root of the project.
$ GUIX_EXTENSIONS_PATH=$PWD guix hello
hello, I'm a Guix extension!
Since we would like our extension to be discoverable by users, we will add a
help message which will make the extension display in the guix help menu.
We will use (srfi srfi-37) to write the option parser, and (guix ui) for the
internationalized strings; you will see that I import these modules when I show
you the complete extension. Let's focus on the option definitions:
(define (show-help)
(display (G_ "Usage: guix hello\n"))
(display (G_ "Just say hello, it's not that complicated.\n"))
(display (G_ "
--help display this message"))
(newline))
(define %options
(list (option '(#\h "help") #f #f
(lambda args
(leave-on-EPIPE (show-help))
(exit 0)))))
Since we are only handling the arguments for showing the help message, we just need to call the parser at the start of the command:
(define-command (guix-hello . args)
(category extension)
(synopsis "say hello")
(parse-command-line args %options (list '()))
(display "hello, I'm a Guix extension!\n"))
Here you have the complete extension module:
(define-module (guix extensions hello)
#:use-module (guix scripts)
#:use-module (guix ui)
#:use-module (srfi srfi-37)
#:export (guix-hello))
(define (show-help)
(display (G_ "Usage: guix hello\n"))
(display (G_ "Just say hello, it's not that complicated.\n"))
(display (G_ "
--help display this message"))
(newline))
(define %options
(list (option '(#\h "help") #f #f
(lambda args
(leave-on-EPIPE (show-help))
(exit 0)))))
(define-command (guix-hello . args)
(category extension)
(synopsis "say hello")
(parse-command-line args %options (list '()))
(display "hello, I'm a Guix extension!\n"))
With that, our extension will be present when we ask Guix for help:
$ GUIX_EXTENSIONS_PATH=$PWD guix help
...
extension commands
hello say hello
It also takes a --help flag:
$ GUIX_EXTENSIONS_PATH=$PWD guix hello --help
Usage: guix hello
Just say hello, it's not that complicated.
--help display this message
Packaging a Guix extension
At the time of writing, we don't have a dedicated Guix
build-system
for extensions. Fortunately, the
guile-build-system
is close enough for this use case.
Let's make a package definition for our new hello extension that uses our local
sources. Create a guix.scm file at the root of the project with the following
contents:
(use-modules (gnu packages package-management)
(guix build-system guile)
(guix gexp)
(guix git-download)
(guix packages)
((guix licenses) #:prefix license:))
(define vcs-file?
;; Return true if the given file is under version control.
(or
(git-predicate
(dirname (canonicalize-path
(assq-ref (current-source-location) 'filename))))
(const #t)))
(define-public guix-hello
(package
(name "guix-hello")
(version "0.0.0-git")
(source (local-file (assume-valid-file-name ".")
"pin-checkout"
#:recursive? #t
#:select? vcs-file?))
(build-system guile-build-system)
(arguments
(list
#:scheme-file-regexp
#~(lambda (file stat)
(and ((file-name-predicate #$default-scheme-file-regexp)
file stat)
(not ((file-name-predicate "^(guix|channels|manifest)\\.scmquot;)
file stat))))
#:phases
#~(modify-phases %standard-phases
(add-after 'build 'move-to-extension-directory
(lambda _
(with-directory-excursion #$output
(mkdir-p "share/guix/extensions/1.5/guix/extensions")
(rename-file (string-append "share/guile/site/"
(target-guile-effective-version)
"/guix/extensions/hello.scm")
"share/guix/extensions/1.5/guix/extensions/hello.scm")))))))
(native-inputs (list guix))
(inputs (list (lookup-package-input guix "guile")))
(home-page "https://codeberg.org/guix-extensions")
(synopsis "Make Guix say hello")
(description
"This extension provides the @command{guix hello} command,
which makes Guix say hello.")
(license license:gpl3+)))
guix-hello
We can test this new extension like this:
$ guix shell -CW -f guix.scm -- guix hello
The flags passed to guix shell are the following:
-C(--container): To prevent your environment from interfering.-W(--nesting): To bring the current Guix you are using into thecontainer so it can load the extension.
Since this example is using the new extension scheme, the guix command you
are running must be at or newer than commit
de069958fc.
Closing words
I hope you find this small introduction to Guix extensions useful and that you start to write your own Guix extensions.
I would like to encourage everyone reading this to submit their extensions to the guix-extensions Codeberg organization. The idea is to make this organization a central hub for everyone to participate in the development of useful extensions for the community.
Unless otherwise stated, blog posts on this site are copyrighted by their respective authors and published under the terms of the CC-BY-SA 4.0 license and those of the GNU Free Documentation License (version 1.3 or later, with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts).