GitHub

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:

ripgrep.rhai
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 { () }
}