Skip to content

Core Concepts

Leonard Ramminger edited this page Aug 9, 2026 · 1 revision

Core Concepts

Beez is a build and task orchestrator. You describe what your project can do in a Lua file (build.lua), and Beez runs it, parallelizes where possible, and skips work via caching when nothing relevant has changed.

Three building blocks form almost every pipeline:

Building block Role Typical use
Step Smallest executable unit Concrete work: compile, test, lint
Task Short name for an action Shell command or chained step invocations
Workflow Sequence of phase/scope pairs Full pipelines: build, check, deploy

Step

A step is the atomic unit of work. Every step has:

  • a unique name
  • a phase and scope (you choose the names; see Phases and Scopes)
  • a run field: either a shell command (string) or a Lua function (callback)

A step can optionally define:

  • input, output, and mutate: glob patterns for files the step reads, creates, or changes (relevant for caching; see Caching when available)
  • description: short text for lists and logs
  • config: step-specific settings (or attached later via configure_step())
step({
    name = "compile",
    phase = "build",
    scope = "default",
    input = { "src/**/*.cpp" },
    output = { "build/app" },
    run = "cmake --build build",
})

Steps are not run on their own. Beez runs them when you:

  • run a workflow that includes their phase+scope pair
  • filter by phase on the CLI (-p)
  • run a single step by name (-s)
  • run a task that references the step

Task

A task is a named shortcut. You invoke it with beez <taskname>.

Simple shell task

task("hello", "echo hello > hello.out")

Runs one shell command, similar to a Makefile target but declared in Lua.

Task with multiple actions

task("check", {
    "echo step 1",
    { name = "compile" },
    { name = "test:unit" },
})

A task list can mix shell strings and step references ({ name = "..." }). Actions run one after another.

Tasks are useful for:

  • quick one-off commands (clean, deploy)
  • convenience aliases you call often
  • short chains without defining a workflow

Workflow

A workflow runs an ordered sequence of phase+scope pairs. For each pair, Beez runs all steps that belong to it.

workflow("build", {
    { phase = "generate", scope = "code" },
    { phase = "compile",  scope = "code" },
})

Each entry in the list is one workflow step. Beez runs workflow steps in order. Within a step, the matching steps follow the rules in Parallel Execution and Dependencies.

Parallel workflow steps

Several phase+scope pairs can run in parallel inside one workflow step:

workflow("ci", {
    { parallel = {
        { phase = "generate", scope = "docs" },
        { phase = "generate", scope = "code" },
    }},
    { phase = "compile", scope = "code" },
})

Here generate:docs and generate:code run at the same time; then compile:code runs.

Workflows are useful for:

  • recurring pipelines (local and CI)
  • clear entry points (beez build, beez ci)
  • separating "run everything" from individual tasks

How the pieces fit together

beez ci                    <- workflow invocation
  |
  +- generate:docs         <- phase+scope -> all matching steps
  +- generate:code         <- (parallel with the previous workflow step)
  \- compile:code
       +- compile           <- step (may run in parallel with other steps
       \- link              <-  in the same phase+scope if no order())

Tasks bypass workflow structure and run shell commands or named steps directly.

Steps do the actual work, with caching, parallelism, and artifact tracking.

Workflows order phase+scope blocks in the right sequence.

What you define yourself

Beez does not prescribe phase names, scope names, workflows, or step names. The examples above (build, compile, generate) are illustrations only.

You decide:

  • how fine-grained your steps are
  • how you name phases and scopes
  • which workflows and tasks exist

The mechanics (execution, parallelism, caching) stay the same regardless of naming.

Next steps

Clone this wiki locally