← docs2 min read

4. Connecting recipes

When one recipe's command reads another recipe's artifact, say so in the…

manual

When one recipe’s command reads another recipe’s artifact, say so in the command:

recipe compile
gather "src/*.c"
cook "build/$<in.stem>.o" { gcc -c $<in> -o $<out> }
recipe link
cook "build/app" { gcc $<compile> -o $<out> }

$<compile> does three jobs in one stroke: it expands to compile’s outputs (space-joined), it records the dependency edge, and it folds those artifacts’ contents into link’s cache key. That last one is the important one. Edit a .c file and cook recompiles one object, then relinks; but if a rebuilt object comes out byte-identical, the change stops propagating and link stays cached. And because the read is declared, cook why can attribute a link miss to the exact artifact that changed.

The reference is also precise about waiting: only the step that references compile waits for it. Other steps in the recipe proceed.

The colon is for ordering, not reading. A header dependency (recipe a: b) is a fence: every unit of a waits for all of b, and nothing of b’s content enters a’s key. That’s the right tool exactly when nothing is read: grouping meta-targets, chores that want a build first, run-X-before-Y.

recipe all: compile link check
chore play: link
./build/app

If your command reads an artifact, use the reference. If you only need order, use the colon. Writing both for the same edge is redundant, and cook warns.

Parallel where it can be. Independent units run concurrently; the default parallelism is your CPU count, overridable with -j:

Terminal window
cook -j 1 link # serial
cook -j 8 link # cap at 8

Cycles are an error, caught during graph construction before any work runs.

The default recipe is build: bare cook runs it. If a recipe name collides with a subcommand (init, menu, list, modules, test, dag, logs, cache, serve, emit-lua, affected, why), the subcommand wins; prefix the recipe with + to force it: cook +test runs the recipe named test, cook test runs the test runner.

Namespacing across Cookfiles is done with import, not dotted names. A recipe declared in one Cookfile always has a single, undotted name; dots appear only at reference sites and route through an import alias:

import backend ./backend
import frontend ./frontend
recipe ship: backend.build frontend.build
cook "out/release.tar" { tar cf $<out> dist }

recipe backend.build at a declaration site is a parse error; put recipe build in backend/Cookfile and import it. (More on composition in Modules and composition.)