Recipes¶
A recipe is a named build script. PCL points at one with recipe = "std.NAME"; the realiser dispatches to the matching module under src/punix/realise/recipes_lib/std/.
module Curl {
pname = "curl"
version = "8.5.0"
recipe = "std.autotools"
recipeArgs = { configureFlags = ["--without-ssl"] }
source = { … }
}
Shipped recipes¶
| Recipe | What it does |
|---|---|
std.shell |
Run an arbitrary POSIX shell command with $OUTPUT set: the escape hatch |
std.autotools |
./configure --prefix=$OUTPUT && make && make install |
std.make |
make && make install (no autoconf) |
std.cmake |
cmake -B build && cmake --build build && cmake --install build |
std.meson |
meson setup && meson compile && meson install |
std.cargo |
cargo install / cargo build --release |
std.go |
go build + install |
std.npm |
npm install |
std.ruby_gem |
gem build && gem install |
std.python_wheel |
Build a wheel and install it (a single package) |
std.python_venv |
pip install through pip's resolver (multi-package) |
std.swift |
swift build -c release |
std.binary |
Unpack a pre-built archive into $OUTPUT (no build); source supplies the bytes |
std.copy_tree |
Copy files only |
Each recipe lives in src/punix/realise/recipes_lib/std/<name>.py, and the recipeArgs each one accepts are documented at the top of that file. That docstring is the normative list. Frequently useful ones: command, src_dir, configure_flags, make_flags, cmake_flags, manifest_path, install_command.
src_dir is the one to reach for when a build fails with "manifest path not found" or "go.mod not found": the tarball extracted into a subdirectory the class didn't auto-detect. Use src_dir = "." for a flat extract, or src_dir = "foo-1.2.3" for the usual <name>-<version>/ layout. Recipe code is content-addressed via the canonical derivation; edit a recipe's .py (or its sibling _postinstall.sh, _dep_env.py) ⇒ the hash changes ⇒ every package using that recipe gets a new store path. The conformance suite has a dedicated test for this against a recipe-hook edit.
The recipe field in the canonical derivation¶
recipe = "std.autotools" is not just a string lookup. The canonical derivation includes the recipe's identity:
"recipe": {
"name": "std.autotools",
"source_hash": "<sha256 of the recipe's .py + sibling files>"
}
That means:
- Two packages with
recipe = "std.autotools"share the recipe's source_hash ⇒ if the recipe is edited, both rebuild. - A user-defined recipe with the same name but different bytes (impossible today, but conceptually) would have a different source_hash ⇒ distinct paths.
This is the cache-staleness fix that v2 makes structural. Old: "did I make clean after editing _postinstall.sh?" New: edit the file ⇒ all dependents already have a different hash ⇒ they'll be rebuilt next punix build.
Recipe environment¶
Every recipe runs in a sandbox with:
$OUTPUT: the temp path that becomes the store path on success.$SOURCE: the source dir (postprepare_source).$BUILD: the build dir (under$OUTPUTfor most recipes).$NIX_BUILD_CORES: the recommendedmake -jvalue.$MAKEFLAGS:-j$NIX_BUILD_CORES.$PUNIX_ARCH: the target system (x86_64-linuxetc.).$FINAL_STORE_PATH: the predicted final path. Important: this is the path the artifact will live at after the build. Languages that bake the prefix in (Ruby's--prefix, ELF's RPATH) need this value at build time, before the artifact has moved.$PATH: each dep's<store>/binprepended (in dep order). Nothing else; no system PATH leakage.
Writing a recipe¶
A recipe is class StdRecipe(Recipe): (or a function; see existing recipes for the pattern). The run(req) method receives a RealiseRequest and writes to $OUTPUT:
class Shell(Recipe):
def run(self, req: RealiseRequest, sandbox: Sandbox) -> None:
command = req.recipe_args["command"]
script = f"#!/bin/sh\nset -eu\n{command}\n"
sandbox.run(script=script, …)
You can ship custom recipes in your repo; they participate in the canonical derivation like any other (the recipe's source-hash field makes them addressable). Stage 7+ corpus migration may upstream patterns into std.
Fixed-output recipes¶
A source = { type = "fixed_output", command = "…", hash = "…" } is special:
commandruns in the sandbox (with network permitted; fixed-output IS the network seam).- The resulting tree is hashed (
compute_tree_hash). - If the hash matches the declared one ⇒ success. If not ⇒
[E13].
This is how Punix model arbitrary fetches (git clones, custom protocols, etc.): the recipe says "this is what I'll produce", the realiser verifies. A [E13] conformance test pins this.
→ CLI: punix build for the build flow. → Reference: error codes for E10–E13.