Skip to content

Development

Everything below runs through the flake, so the SBCL version and the sibling package revisions are the same on a laptop and in CI.

Commands

nix develop          # SBCL with CL_SOURCE_REGISTRY already set
nix run .#test       # run the test suite
nix flake check      # tests + formatting + docs — the gate CI uses
nix fmt              # format Nix sources (treefmt/nixfmt)
nix build .#docs     # render this site to ./result
nix build .#coverage # SB-COVER HTML report to ./result/cover-index.html

nix build .#coverage is not part of nix flake check: sb-cover instrumentation forces a full recompile of cl-cc-binary and every dependency it shares a source registry with, which is too slow for the fast path every check run takes, and a coverage percentage is a number to read rather than a pass/fail gate. Run it on demand and open result/cover-index.html; the subheading for .../source/src/ in that report is this package's own code, the others are cl-log-kit/cl-weave/etc. picking up incidental coverage from whatever of their surface the test suite happens to exercise.

nix flake check is the only command whose result matters for a pull request. It runs three derivations in parallel:

Check What it does
checks.default sbcl --script run-tests.lisp — the cl-weave suite
checks.formatting fails if any tracked Nix file is unformatted
checks.docs mkdocs build --strict, so a broken link or a page missing from nav fails the build

New granularity belongs here as another checks.* attribute, not as another GitHub Actions job. nix flake check already schedules them in parallel with build caching; a second job would duplicate that and lose the cache sharing.

Declared systems

The flake declares x86_64-linux, and nothing else. That is the one platform actually verified: CI builds it, and nothing else is gated anywhere. aarch64-darwin was dropped on 2026-08-01 because its only verification was a local nix flake check a developer could forget to run. ci.yml does not pass --all-systems; with one declared system it would check nothing extra.

Every per-system output comes from that one list, devShells included, so nix develop and nix build do not work on macOS. Development happens on Linux.

Running the tests without Nix

export CL_SOURCE_REGISTRY="/path/to/cl-weave//:/path/to/cl-log-kit//:/path/to/cl-date-kit//:/path/to/cl-concurrent-kit//:/path/to/cl-host-kit//:/path/to/cl-process-kit//:/path/to/cl-boundary-kit//"
sbcl --script run-tests.lisp

The trailing // makes ASDF search recursively. run-tests.lisp adds its own directory to asdf:*central-registry*, so it does not matter what your working directory is.

Do not write sbcl --noinform --non-interactive --script run-tests.lisp. SBCL acts on --non-interactive before it reaches the script, so that command exits 0 without running anything — which is exactly how a broken build-mach-o sat on main under a green nix flake check.

Writing tests

Tests live in t/, named after the source file they cover: src/got-plt.lisp is tested by t/got-plt-test.lisp. When one source file has several distinct concerns, add the concern to the name — t/macho-build-assemble-entry-point-test.lisp and t/macho-build-assemble-logging-test.lisp both cover src/macho-build-assemble.lisp. Every file is listed in the cl-cc-binary/test system in cl-cc-binary.asd. A file that is not listed is not run.

The framework is cl-weave — nested describe, it, and expect. Do not introduce FiveAM, parachute, rove or prove.

(in-package :cl-cc-binary/test)

(describe "align-up"
  (it "rounds up to the next multiple"
    (expect (= (cl-cc/binary:align-up 4097 4096) 8192))))

Reach for internal symbols with the double-colon cl-cc/binary:: prefix when a test needs one; several existing files do, and that is preferred over widening the export list for the sake of a test.

Optimization declarations

PERFORMANCE_STANDARD.md fixes the declaration form as (optimize (speed 3) (safety 1)). (safety 0) is not permitted anywhere in src/: under it SBCL trusts type declarations without checking them, so a caller's mistake becomes a corrupt object file rather than a type-error at the call site.

declaim (optimize ...) is global to everything compiled after it in the same image, so it is either at the top of every file in the system or in none of them. This system uses none; scope an optimization to a single function with declare instead.

Releasing

Bump :version in cl-cc-binary.asd, then push the matching vX.Y.Z tag. release.yml refuses a tag that disagrees with the .asd version, runs nix flake check, and creates the GitHub release as an empty draft. Write the release notes into that draft and publish it:

gh release edit vX.Y.Z --notes-file release-notes.md --draft=false

The release description is the only canonical changelog in this org; there is no CHANGELOG.md in the tree.