Getting started¶
Add the flake input¶
{
inputs.cl-nix-forge = {
url = "github:nerima-lisp/cl-nix-forge/v0.6.1";
inputs.nixpkgs.follows = "nixpkgs";
};
}
Pin a release tag. A bare github:nerima-lisp/cl-nix-forge follows this
repository's default branch, so a push upstream would change your build
without warning. inputs.nixpkgs.follows keeps one nixpkgs in the closure
rather than two.
Build one system¶
outputs = { self, nixpkgs, cl-nix-forge }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
cl = cl-nix-forge.lib.${system};
in
{
packages.${system}.default = cl.lispDerivation {
lispSystem = "my-app";
version = cl.fromAsdSystem ./my-app.asd;
src = ./.;
lispDependencies = [
(cl.fromNixpkgsLisp {
drv = pkgs.sbcl.pkgs.alexandria;
lispImplementation = "sbcl";
})
];
};
};
Three things are worth naming in that snippet.
version = cl.fromAsdSystem ./my-app.asd reads the :version your .asd
already declares, so a release edits one file. See
fromAsdSystem.
lispDependencies is a Lisp dependency list. It resolves into
CL_SOURCE_REGISTRY and never into buildInputs. buildInputs and
nativeBuildInputs keep their ordinary Nix meanings and are still available
to you.
fromNixpkgsLisp requires lispImplementation explicitly. The package it
wraps contains fasls belonging to exactly one implementation, and a consumer
built with another one has to fail during evaluation rather than ask ASDF to
rewrite files inside the immutable store. See
Dependencies.
Filter the source¶
src = ./. hashes everything in the directory, including the .fasl files a
local sbcl --script run-tests.lisp wrote there. Use
mkLispSource instead:
That is an allowlist: .asd and .lisp anywhere under root, and nothing
else. Other build inputs are opted in explicitly:
Add a check¶
The derivation can run its own asdf:test-system in the same sandboxed
build:
If the suite cannot be driven by an enclosing test-op, or needs a time
limit, use mkScriptCheck, which
defaults to the org-standard run-tests.lisp entry point:
checks.${system}.my-app = cl.mkScriptCheck {
drv = self.packages.${system}.default;
timeoutSeconds = 600;
};
To run the same suite under more than one implementation, use
mkCheckMatrix rather than a
meta.broken predicate list.
Deliver an executable¶
packages.${system}.my-cli = cl.mkExecutable {
args = {
pname = "my-cli";
lispSystem = "my-app";
version = cl.fromAsdSystem ./my-app.asd;
src = cl.mkLispSource { root = ./.; };
lispDependencies = [ ... ];
};
};
apps.${system}.my-cli = cl.mkApp { drv = self.packages.${system}.my-cli; };
What a binary is stays in the .asd: :build-operation, :entry-point
and :build-pathname have no Nix option here, because a system with two
places to declare its own entry point has two places to disagree. See
mkExecutable.
Add installSource = true; when the image loads a system at run time rather
than only running what was dumped into it. It installs the sources and their
dependency closure under the binary's own prefix, which is where an image
looking for share/common-lisp/source/ next to itself will find them; see
what $out contains.
The rest of a flake¶
Flake outputs and delivery covers mkTestApp
(nix run .#test), mkOverlay, mkDevShell, mkDocsSite and collect —
the outputs a downstream repository writes once and then stops thinking
about. examples/flake-outputs/ is a worked version of
exactly that surface.
If the repository is a nerima-lisp package, do not assemble those by hand:
mkPackageFlake generates all of them
— plus the formatter, the docs check and the test app — from the .asd and a
source root, and every page above then describes an argument rather than a
call you write.