No description
  • Scheme 88.4%
  • NewLisp 11.1%
  • Dockerfile 0.5%
Find a file
Rolando Abarca 86cf723f87
All checks were successful
Release / build-macos-arm64 (push) Successful in 7s
Release / build-macos-x86 (push) Successful in 13s
Release / package-macos (push) Successful in 5s
Release / build-linux (push) Successful in 31s
Release / release (push) Successful in 11s
Report failed egg names on installation failure
When egg installation fails, include the list of failed eggs in the
error message rather than just noting that installation failed.
2026-04-28 20:16:29 -07:00
.forgejo/workflows Fix macOS runner labels to use single tags instead of arrays 2026-03-23 11:19:29 -07:00
tests Switch build specs to .lsp extension, add -p flag, deployed option, fix chicken.* classification 2026-02-24 21:57:18 -08:00
.gitignore Add auto-discovery, parallel compilation, example build spec, and static linkage 2026-02-24 16:00:16 -08:00
build.example.lsp Add static binary support and link-flags spec option 2026-02-27 17:56:28 -08:00
build.lsp Enable static build for kdp-build itself 2026-03-12 08:42:01 -07:00
discover.scm Fix parse failure on FFI files with #> blocks 2026-04-08 20:18:37 -07:00
Dockerfile Set default working directory to /build in Docker image 2026-03-18 14:07:02 -07:00
eggs.scm Report failed egg names on installation failure 2026-04-28 20:16:29 -07:00
kdp-build.egg Add parallel egg installation and Dockerfile 2026-03-17 20:21:34 -07:00
LICENSE first commit 2026-02-24 15:57:03 -08:00
main.scm Report failed egg names on installation failure 2026-04-28 20:16:29 -07:00
native.scm Make macOS deployment target flag conditional on platform 2026-03-17 16:08:52 -07:00
README.md Add CLI version command and bump to 0.4.12 2026-04-14 17:52:48 -07:00
scheme.scm Add static binary support and link-flags spec option 2026-02-27 17:56:28 -08:00
spec.scm Add extra-modules support for dynamically loaded modules 2026-04-04 10:41:23 -07:00
utils.scm first commit 2026-02-24 15:57:03 -08:00

kdp-build

A declarative build system for CHICKEN Scheme applications that mix Scheme modules with native C/C++/Objective-C code.

Why?

CHICKEN's chicken-install is designed for building and installing eggs — libraries and simple programs. It works well for that purpose, but complex applications often need more:

  • Native code with per-file control: Different source files compiled with different compilers (clang, clang++, c++) and different flags. Objective-C .mm files next to C++ .cpp files next to plain C, each with their own include paths and framework dependencies.
  • Static libraries from external sources: Clone a git repository, apply patches, compile dozens of C++ files into a .a archive — all before any Scheme code is touched.
  • Shell variable expansion in flags: $(pkg-config --cflags sdl2) evaluated at build time, per-module and per-object.
  • FFI modules with distinct link flags: Each FFI wrapper links against different native objects and system frameworks. One module needs -framework AVFoundation, another needs -framework MapKit, a third needs $(pkg-config --libs libavcodec).
  • Incremental builds: Only recompile what changed, based on file timestamps.
  • Static binaries: Compile all modules as units and link them into a single self-contained executable.
  • macOS application bundling: Deployment targets, framework linking, resource bundling into .app bundles.

The .egg format has (custom-build ...) for running arbitrary scripts, but that defeats the purpose of a declarative build. kdp-build keeps the build specification readable while handling the complexity.

Install

chicken-install

Or from a local checkout:

cd kdp-build
chicken-install

Usage

Create a build.lsp in your project root, then run:

kdp-build [command] [options]

Commands

Command Description
build Build all targets (default)
clean Remove build artifacts
rebuild Clean then build
install-deps Install missing egg dependencies in parallel
list-modules Print all module names
deps Print required eggs (direct)
deps-deep Print all eggs including transitive dependencies
version Show kdp-build version
help Show help

Options

Option Description
-p, --project NAME Use build.<NAME>.lsp as spec file (see Multiple projects)
-j, --jobs N Max parallel jobs for install-deps (default: 4)
--verbose, -v Show all executed commands
--release Use release flags (-O3 -d0)
--version, -V Show kdp-build version

Multiple projects

You can keep multiple build specs in the same directory by using named spec files. Instead of the default build.lsp, create files named build.<NAME>.lsp and select them with the -p flag:

# Uses build.lsp (default)
kdp-build build

# Uses build.editor.lsp
kdp-build build -p editor

# Uses build.cli.lsp
kdp-build build -p cli

This is useful when a single source tree produces multiple binaries (e.g. a GUI app and a CLI tool) that share code but have different entry points, dependencies, or native objects.

Build Specification

A build.lsp file describes your project. Only app-entry is required — everything else is optional. If you omit modules and eggs, kdp-build will auto-discover them by scanning import statements in your source files, starting from app-entry and following transitive imports. Only modules reachable from the entry point are included, so multiple build targets can share a directory without interfering (see Multiple projects).

Minimal example

(build-spec
  (app-name "my-app")
  (app-entry "app.scm"))

Full example

(build-spec
  (app-name "my-app")
  (macos-deployment-target "13.0")
  (csc-flags "-d3 -O0 -c++")
  (static #t)
  (deployed #t)
  (link-flags "-lssl" "-lcrypto")

  (eggs srfi-1 srfi-18 sqlite3)

  (static-libs
    (mylib.a
      (sources "vendor/foo.cpp" "vendor/bar.cpp")
      (cxx-flags "-std=c++11 -Ivendor")
      (setup
        "test -d vendor || git clone https://example.com/vendor.git")))

  (native-objects
    (helper.o "helper.c" "clang" "")
    (bridge.o "bridge.mm" "clang++" "-std=c++11 -fobjc-arc"))

  (ffi-modules
    (my-ffi (helper.o bridge.o)
      ("-Ivendor")
      ("-framework Foundation")))

  (modules config utils main-window)

  ;; Modules not reachable from app-entry (e.g. dynamically loaded)
  (extra-modules sample.jobs)

  (app-entry "app.scm")

  (bundle-resources "assets/icon.png" "fonts/*.ttf"))

Build Order

  1. Setup — Run setup commands for static libraries (e.g., git clone, patch)
  2. Static libraries — Compile C/C++ sources into .a archives
  3. Native objects — Compile individual .c, .cpp, .mm files
  4. FFI modules — Compile Scheme FFI wrappers, linking against native objects
  5. Pure modules — Compile Scheme modules (in parallel by dependency level when auto-discovered, sequentially when explicitly listed)
  6. Application binary — Link everything into the final executable

Spec Reference

app-name

Output binary name. Defaults to "app".

(app-name "my-app")

app-entry

Scheme source file for the application entry point. This file should import all modules it needs. Defaults to "app.scm".

(app-entry "app.scm")

csc-flags

Base flags passed to csc for all Scheme compilations (modules and the final binary). Defaults to "-d3 -O0 -c++". When --release is passed at build time, these are overridden with "-O3 -d0 -c++".

(csc-flags "-d3 -O0 -c++ -C \"-Wno-return-type\"")

macos-deployment-target

Minimum macOS version. Passed as -mmacosx-version-min= to native compilers. Defaults to "13.0".

(macos-deployment-target "13.0")

static

When #t, build a static binary with all modules compiled as units and linked into a single self-contained executable. No .so files are produced. Defaults to #f.

(static #t)

In static mode:

  • Modules are compiled with -c -unit <name> instead of -s (shared).
  • The final binary is linked with -static -uses <name> for each module.
  • All native objects and link flags from FFI modules are collected and linked into the final binary.

deployed

When #t, build with -deployed -private-repository so the binary looks for extensions relative to itself. Required for macOS .app bundles. Defaults to #f.

(deployed #t)

Additional linker flags passed when building the final binary. Useful in static builds when eggs depend on system libraries that need explicit linking (e.g., the openssl egg needs -lssl -lcrypto). Shell expansions like $(pkg-config ...) are supported.

(link-flags "-lssl" "-lcrypto")

eggs

CHICKEN eggs your project depends on. Used by kdp-build deps, kdp-build deps-deep, and kdp-build install-deps.

install-deps resolves the full transitive closure of missing eggs, computes a dependency-ordered install plan, and installs them in parallel (bounded by -j). Both retrieval and installation are parallelized.

If omitted, kdp-build auto-discovers eggs by scanning import statements in reachable .scm files.

(eggs http-client intarweb uri-common openssl
      medea sqlite3 ssql base64
      srfi-1 srfi-13 srfi-18 srfi-69)

static-libs

C/C++ source files compiled into .a archives, then linked into FFI modules or the final binary. Each entry has:

Sub-field Required Description
name yes Archive name (e.g. mylib.a)
sources yes List of C/C++ source files to compile. Glob patterns are supported.
cxx-flags no Compiler flags for all sources in this library. Shell expansions work. Defaults to "".
setup no Shell commands run before compilation (e.g., git clone, patch). Each command is a separate string, run in order.
(static-libs
  (imgui.a
    (sources
      "imgui/imgui.cpp"
      "imgui/imgui_draw.cpp"
      "imgui/imgui_widgets.cpp")
    (cxx-flags "-std=c++11 -Iimgui $(pkg-config --cflags sdl2)")
    (setup
      "test -d imgui || git clone -b docking https://github.com/ocornut/imgui.git imgui")))

native-objects

Individual C/C++/Objective-C files compiled to .o, then linked into FFI modules. Each entry is a tuple:

(name.o "source-file" "compiler" "flags")
Position Required Description
1st yes Output object name (e.g. helper.o)
2nd yes Source file path
3rd yes Compiler command ("clang", "clang++", "c++", or any command)
4th no Compiler flags. Shell expansions like $(pkg-config ...) and $(PWD) work. Defaults to "".
(native-objects
  (helper.o "helper.c" "clang" "")
  (bridge.o "bridge.mm" "clang++" "-std=c++11 -fobjc-arc")
  (video.o "video.cpp" "c++" "-std=c++11 $(pkg-config --cflags libavcodec)"))

ffi-modules

Scheme modules that wrap C/C++ code via CHICKEN's FFI. Compiled as shared libraries (.so) with native objects linked in. In static mode, native objects are deferred to the final binary link step.

Each entry:

(name (objects...) (compile-flags...) (link-flags...))
Position Required Description
1st yes Module name (must match <name>.scm)
2nd yes List of native .o files or .a archives to link. Use () for none.
3rd yes Extra compile flags passed as -C to csc (include paths, etc.)
4th yes Extra link flags passed as -L to csc (libraries, frameworks)

FFI modules must not import other project modules — they should only depend on CHICKEN eggs and native code.

(ffi-modules
  ;; Pure FFI bindings (no native objects)
  (sdl ()
    ("-std=c++11" "-Iimgui" "$(pkg-config --cflags sdl2)")
    ("-framework OpenGL" "$(pkg-config --libs sdl2)"))

  ;; Linking a static library
  (imgui (imgui.a)
    ("-std=c++11" "-Iimgui")
    ("-framework OpenGL"))

  ;; Linking native objects
  (video-ffi (video.o overlay.o)
    ("-std=c++11" "$(pkg-config --cflags libavcodec)")
    ("$(pkg-config --libs libavcodec)" "-framework VideoToolbox")))

modules

Pure Scheme modules compiled in declared order. Each module can import any module listed above it, plus any FFI module.

If omitted, kdp-build auto-discovers modules by recursively scanning import statements starting from app-entry. Only modules transitively reachable from the entry point are included — other .scm files in the directory are ignored. Auto-discovered modules are compiled in parallel by dependency level (independent modules build simultaneously). FFI module files are automatically excluded from scanning and recognized as local imports.

(modules
  app-state
  config
  models
  migrations
  telemetry
  session
  settings)

extra-modules

Additional Scheme modules to compile alongside auto-discovered (or explicit) modules. Unlike modules, this does not disable auto-discovery — extra modules are appended after the discovered ones.

This is useful for modules that are dynamically loaded at runtime (e.g., via import in a plugin system) and therefore not reachable by tracing imports from app-entry.

Extra modules are compiled as a final parallel level, after all auto-discovered modules are built.

(extra-modules
  sample.jobs
  email.jobs
  billing.jobs)

bundle-resources

Files to include in the Resources directory of a macOS .app bundle. Glob patterns are supported.

(bundle-resources
  "assets/icon.icns"
  "fonts/*.ttf"
  "imgui.ini")

License

BSD-3-Clause — Rolando Abarca