- Scheme 88.4%
- NewLisp 11.1%
- Dockerfile 0.5%
|
All checks were successful
When egg installation fails, include the list of failed eggs in the error message rather than just noting that installation failed. |
||
|---|---|---|
| .forgejo/workflows | ||
| tests | ||
| .gitignore | ||
| build.example.lsp | ||
| build.lsp | ||
| discover.scm | ||
| Dockerfile | ||
| eggs.scm | ||
| kdp-build.egg | ||
| LICENSE | ||
| main.scm | ||
| native.scm | ||
| README.md | ||
| scheme.scm | ||
| spec.scm | ||
| utils.scm | ||
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.mmfiles next to C++.cppfiles 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
.aarchive — 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
.appbundles.
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
- Setup — Run setup commands for static libraries (e.g., git clone, patch)
- Static libraries — Compile C/C++ sources into
.aarchives - Native objects — Compile individual
.c,.cpp,.mmfiles - FFI modules — Compile Scheme FFI wrappers, linking against native objects
- Pure modules — Compile Scheme modules (in parallel by dependency level when auto-discovered, sequentially when explicitly listed)
- 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)
link-flags
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