Common Lisp environment with Nix
It is very difficult to find a working example of common lisp environment with Nix. I hope this article helps folks to start off the common lisp project utilizing Nix.
Make the nix-shell environment
First, take a look at how to create shell environments with shell.nix.
A basic shell.nix
file for the lisp(sbcl) environment
Create a shell.nix
file in the fresh directory.
let
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-24.05";
pkgs = import nixpkgs { config = {}; overlays = []; };
sbcl' = pkgs.sbcl.withPackages ( ps: with ps; [ ]);
in
pkgs.mkShellNoCC {
packages = [ sbcl' ];
}
Enter the environment by running nix-shell
in this project directory.
nix-shell looks first for shell.nix
in the current directory.
nix-shell
Now you are able to invoke sbcl in the project directory. Since there is no dependency in the environment, you can do almost nothing in there for now. Dependencies will be added in the later section of this article.
Automatic activation of the shell enviroment
You hove to invoke nix-shell
everytime you change to this directory. This is
very cumbersome. By using direnv
, you can automate this.
Prepare .envrc
. with a following content.
use nix
Activate direnv by issuing following command.
direnv allow
Now, every time you change directory to the project directory, the shell environment becomes automatically available.
Packaging a minimal code
Now that you have the lisp development environment, let’s build a web server as an example.
Define cl-tutorial system and prepare a server source code
Make cl-tutorial.asd
as following. Notice that the first part of the file
name is cl-tutorial
, which is same as the package name called “cl-tutorial”,
and a file name extension must be .asd
, not .asdf
.
As an web server library, hunghentoot
is used.
(defsystem "cl-tutorial"
:version "0.0.1"
:author "antarcticafalls"
:license "MIT"
:description "a minimal web server as a tutorial"
:depends-on (:hunchentoot)
:components ((:module "./."
:components
((:file "main")))))
A fake server code. you are going to revise later as needed.
(in-package :cl-user)
(defpackage :cl-tutorial
(:use :cl :hunchentoot)
(:export
:main))
(in-package :cl-tutorial)
(defun main ()
(loop))
Load cl-tutorial
system
Invoke sbcl
in the environment and type following to load cl-tutorial
.
(load (sb-ext:posix-getenv "ASDF"))
(pushnew (truename "./.") asdf:*central-registry*)
(asdf:load-system "cl-tutorial")
Then you would see:
debugger invoked on a ASDF/FIND-COMPONENT:MISSING-DEPENDENCY in thread
#<THREAD tid=2056565 "main thread" RUNNING {10003F0143}>:
Component :HUNCHENTOOT not found, required by #<SYSTEM "cl-tutorial">
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [RETRY ] Retry loading HUNCHENTOOT.
1: Retry ASDF operation.
2: [CLEAR-CONFIGURATION-AND-RETRY] Retry ASDF operation after resetting the
configuration.
3: [ABORT ] Exit debugger, returning to top level.
(ASDF/FIND-COMPONENT:RESOLVE-DEPENDENCY-NAME #<ASDF/SYSTEM:SYSTEM "cl-tutorial"> :HUNCHENTOOT NIL)
error finding frame source: Bogus form-number: the source file has probably
changed too much to cope with.
source: NIL
0]
Load fails because you don’t have hunchgentoot
in the shell environment.
Add a hunchentoot
dependency in the shell environment
In order to make hunchentoot
available in the nix shell environment, you need
adjust shell.nix
.
let
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-24.05";
pkgs = import nixpkgs { config = {}; overlays = []; };
sbcl' = pkgs.sbcl.withPackages (ps: with ps; [ hunchentoot ]);
in
pkgs.mkShellNoCC {
packages = [ sbcl' ];
}
After reloading an environment by direnv reload
,
again you type following on sbcl repl.
(load (sb-ext:posix-getenv "ASDF"))
(pushnew (truename "./.") asdf:*central-registry*)
(asdf:load-system "cl-tutorial")
WARNING: System definition file #P"/nix/store/db65yvmfx3zscw71kfkqz2rnnmj5kngw-sbcl-hunchentoot-v1.3.0/hunchentoot.asd" contains definition for system "hunchentoot-test". Please only define "hunchentoot" and secondary systems with a name starting with "hunchentoot/" (e.g. "hunchentoot/test") in that file.
WARNING: System definition file #P"/nix/store/db65yvmfx3zscw71kfkqz2rnnmj5kngw-sbcl-hunchentoot-v1.3.0/hunchentoot.asd" contains definition for system "hunchentoot-dev". Please only define "hunchentoot" and secondary systems with a name starting with "hunchentoot/" (e.g. "hunchentoot/test") in that file.
; compiling file "/home/c/cl-tutorial/main.lisp" (written 16 NOV 2024 10:19:26 PM):
; wrote /home/c/.cache/common-lisp/sbcl-2.4.4-linux-arm64/home/c/cl-tutorial/main-tmpGHU3ALSV.fasl
; compilation finished in 0:00:00.011
T
This should bring you to the cl-tutorial
system. Try
(in-package :cl-tutorial)
Implement a server code
Instead of a fake code, implemented a very minimal server code into mail.lisp
(in-package :cl-user)
(defpackage :cl-tutorial
(:use :cl)
(:export
:main))
(in-package :cl-tutorial)
(hunchentoot:define-easy-handler (root-route :uri "/") (name)
(format nil "Hey~@[ ~A~]!" name))
(defvar *server* (make-instance 'hunchentoot:easy-acceptor :port 6789))
(defun main ()
(hunchentoot:start *server*)
(loop))
If you load cl-tutorial system, you should see a response from the server.
curl -v http://localhost:6789/
* Host localhost:6789 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:6789...
* connect to ::1 port 6789 from ::1 port 49520 failed: Connection refused
* Trying 127.0.0.1:6789...
* Connected to localhost (127.0.0.1) port 6789
> GET / HTTP/1.1
> Host: localhost:6789
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Content-Length: 4
< Date: Sun, 17 Nov 2024 11:47:47 GMT
< Server: Hunchentoot 1.3.0
< Content-Type: text/html; charset=utf-8
<
* Connection #0 to host localhost left intact
Hey!⏎
Appendix
The directory structure is following.
.
├── .envrc
├── cl-tutorial.asd
├── main.lisp
└── shell.nix