Nicolas Martyanoff — Brain dump About

ASDF in Common Lisp scripts

The usual way to develop programs in Common Lisp is to use SLIME in Emacs, which starts an implementation and provides a REPL. When a program needs to be running in production, one can either execute it from source or compile it to an executable core, for example with sb-ext:save-lisp-and-die in SBCL.

While executable cores works well for conventional applications, they are less suitable for small scripts which should be easy to run without having to build anything.

Writing a basic script with SBCL is easy:

#!/bin/sh
#|
exec sbcl --script "$0" "$@"
|#

(format t "Hello world!~%")

Since UNIX shebangs cannot be used to run commands with more than one argument, it is impossible to call SBCL directly (it requires the --script argument, and #!/usr/bin/env sbcl --script contains two arguments). However it is possible to start as a simple shell script and just execute SBCL with the right arguments. And since we can include any shell commands, it is possible to support multiple Common Lisp implementations depending on the environment.

This method works. But if your script has any dependency, configuring ASDF can be tricky. ASDF can pick up system directory paths from multiple places, and you do not want your program to depend on your development environment. If you run your script in a CI environment or a production system, you will not have access to your ASDF configuration and your systems.

Fortunately, ASDF makes it possible to manually configure the source registry at runtime using asdf:initialize-source-registry, giving you total control on the places which will be used to find systems.

For example, if your Common Lisp systems happen to be stored in a systems directory at the same level as your script, you can use the :here directive:

#!/bin/sh
#|
exec sbcl --script "$0" "$@"
|#

(require 'asdf)

(asdf:initialize-source-registry
 `(:source-registry :ignore-inherited-configuration
                    (:tree (:here "systems"))))

And if you store all your systems in a Git repository, you can use submodules to include a systems directory in every project, making it simple to manage the systems you need and their version. Additionally, anyone with an implementation installed, SBCL in this example, can now execute these scripts without having to install or configure anything. This is quite useful when you work with people who do not know Common Lisp.

Of course, you can use the same method when building executables: just create a script whose only job is to setup ASDF, load your program, and dump an executable core. This way, you can make sure you control exactly which set of systems is used. And it can easily be run in a CI environment.

Share the word!

Liked my article? Follow me on Twitter or on Mastodon to see what I'm up to.