Helpers Overview
Helper functions available in recipe scripts. All helpers are pure functions that take explicit inputs and return explicit outputs.
Introduction
Helper functions are organized by lifecycle phase. All functions are pure - they take explicit paths as input and return paths or results. This makes data flow visible and enables caching.
let ctx = #{
name: "foo",
version: "1.0",
source_dir: "",
};
fn acquire(ctx) {
let archive = download(
"https://example.com/foo-1.0.tar.gz",
join_path(BUILD_DIR, "foo.tar.gz")
);
extract(archive, BUILD_DIR);
ctx.source_dir = join_path(BUILD_DIR, "foo-1.0");
ctx
}
fn build(ctx) {
shell_in(ctx.source_dir, "./configure --prefix=" + PREFIX);
shell_in(ctx.source_dir, "make -j" + NPROC);
ctx
}
fn install(ctx) {
shell("install -Dm755 " + join_path(ctx.source_dir, "foo") + " " + join_path(PREFIX, "bin/foo"));
ctx
}Categories
- Acquire - Download, clone, and verify sources
- Build - Extract archives
- Install - Install files to PREFIX
- Filesystem - File and directory operations
- Environment - Environment variables
- Commands - Shell command execution
- HTTP - HTTP requests and GitHub API
- Paths - Path manipulation utilities
- LLM - AI-assisted version/URL extraction
Built-in Variables
These constants are available in all recipe scripts:
| Variable | Description | Example |
|---|---|---|
| PREFIX | Installation prefix (or $OUT if set) | /usr/local |
| BUILD_DIR | Temporary build directory | /tmp/recipe-build-xxx |
| ARCH | Target architecture | x86_64, aarch64 |
| NPROC | Number of CPU cores | 8 |
| RPM_PATH | RPM repository path (from env) | /var/cache/rpms |
Pure Function Design
All helpers take explicit inputs and return explicit outputs. For example, download(url, dest) returns the path where the file was saved, and extract(archive, dest) takes that path as input:
// Explicit data flow - you can see where files come from and go
let archive = download(url, join_path(BUILD_DIR, "foo.tar.gz"));
verify_sha256(archive, "abc123...");
extract(archive, BUILD_DIR); This pure function design enables the living ctx persistence model - since all data flow is explicit through ctx, the engine can safely persist state to disk after each phase. There's no hidden state that could become stale or inconsistent.
Complete Example
A full recipe demonstrating multiple helper categories:
let ctx = #{
name: "ripgrep",
version: "14.1.0",
repo: "BurntSushi/ripgrep",
description: "Fast line-oriented search tool",
extract_dir: "",
};
fn is_acquired(ctx) {
let dir = join_path(BUILD_DIR, "ripgrep-" + ctx.version + "-" + ARCH + "-unknown-linux-musl");
if is_file(join_path(dir, "rg")) {
ctx.extract_dir = dir;
return ctx;
}
throw "not acquired";
}
fn acquire(ctx) {
mkdir(BUILD_DIR);
let pattern = "ripgrep-*-" + ARCH + "-unknown-linux-musl.tar.gz";
let archive = github_download_release(ctx.repo, ctx.version, pattern);
extract(archive, BUILD_DIR);
ctx.extract_dir = join_path(BUILD_DIR, "ripgrep-" + ctx.version + "-" + ARCH + "-unknown-linux-musl");
ctx
}
fn is_installed(ctx) {
if is_file(join_path(PREFIX, "bin/rg")) {
return ctx;
}
throw "not installed";
}
fn install(ctx) {
let src = ctx.extract_dir;
shell("install -Dm755 " + join_path(src, "rg") + " " + join_path(PREFIX, "bin/rg"));
shell("install -Dm644 " + join_path(src, "doc/rg.1") + " " + join_path(PREFIX, "share/man/man1/rg.1"));
if dir_exists(join_path(src, "complete")) {
shell("install -Dm644 " + join_path(src, "complete/_rg") + " " + join_path(PREFIX, "share/zsh/site-functions/_rg"));
}
ctx
}
fn check_update() {
let latest = github_latest_release(ctx.repo);
if latest != ctx.version { latest } else { () }
}