← docs2 min read

10. Dropping into Lua

Most Cookfiles are pure surface syntax. Underneath, every Cookfile compiles to Lua, and you can reach for that API when the surface can't express what you…

manual

Most Cookfiles are pure surface syntax. Underneath, every Cookfile compiles to Lua, and you can reach for that API when the surface can’t express what you need. cook emit-lua prints exactly what a Cookfile lowers to.

Cook runs in two phases, and Lua appears in both:

  • Execute-phase Lua is a >{ ... } body on a cook/test/probe step (or a > step inside a chore). It runs when the unit runs, with input/output (or inputs/outputs) bound to the unit’s resolved I/O:

    recipe upper
    gather "src/*.txt"
    cook "build/$<in.stem>.txt" >{
    local text = fs.read(input)
    fs.write(output, text:upper())
    }
  • Register-phase Lua in a recipe body is a bare module call: cook.add_unit {...}, cook.step_group(...), or mymod.fn(...). For loops or locals, put the code in a top-level register block or a module function:

    register
    for _, src in ipairs(fs.glob("src/*.c")) do
    cook.add_unit({
    inputs = { src },
    output = "build/" .. path.stem(src) .. ".o",
    command = "gcc -c " .. src .. " -o build/" .. path.stem(src) .. ".o",
    })
    end

cook.add_unit is the lowest level: the surface cook step desugars to it. Its fields include inputs, output/outputs, command (or lua_code), discovered_inputs (for compiler .d depfiles, folded into the key), and the cache-trust fields probes, seal, sharing, and record. Fields are strictly typed: a wrong-typed field is a hard error, never coerced. Use cook.sh(cmd) when you need a command’s output now in your Lua (feature detection, a git tag); it runs immediately and returns stdout.

The >> register prefix and the @ interactive prefix that older docs mention were removed; register-phase Lua is a bare module call, and chore shell is interactive by default.