Skip to content

Reference | Rules

olegz edited this page Mar 16, 2026 · 7 revisions

Defining rules

Rules map targets to recipes. Add them using the rules directive inside xakeScript. The "main" target is the default entry point when no targets are specified on the command line.

do xakeScript {
    rules [
        "main" <== ["build"; "test"]

        command "build" {
            do! sh "dotnet build src/core -c Release" {}
        }

        command "test" {
            do! sh "dotnet test src/tests" {}
        }

        target "out/version.txt" {
            let! ver = getVar "VERSION"
            do! writeText ver
        }
    ]
}

Rule types

command — phony commands

Creates a phony rule that always re-runs. AlwaysRerun is added automatically if the recipe does not already include it. Use for build, test, deploy, and clean actions.

command "build" {
    do! sh "dotnet build src/core" {}
}

command "clean" {
    do! rm {dir "out"}
}

target — file rules

Creates a rule that produces a single file. The recipe runs only when the file is missing or its dependencies have changed.

target "out/version.txt" {
    let! version = getVar "VERSION"
    do! writeText version
}

targets — multi-file rules

Creates a rule that produces multiple files from a single recipe. The recipe runs once regardless of how many of its outputs are demanded.

targets ["out/netstandard2.0/Xake.dll"; "out/netstandard2.0/Xake.xml"] {
    do! sh "dotnet build src/core -o out/netstandard2.0" {}
}

Operator shorthands

The builder syntax shown above has equivalent operator forms:

Builder Operator Rule type
command "name" { ... } PhonyRule + AlwaysRerun
target "pattern" { ... } "pattern" ..> recipe { ... } FileRule
targets ["a";"b"] { ... } ["a";"b"] *..> recipe { ... } MultiFileRule
"name" => recipe { ... } PhonyRule without AlwaysRerun

Note that => creates a plain phony rule without AlwaysRerun, unlike command which adds it automatically. Use => for one-off actions like "clean" => rm {dir "out"} where re-run tracking is not needed.

// operator examples
"out/app.dll" ..> recipe {
    do! sh "dotnet build -o out" {}
}

["out/app.exe"; "out/app.xml"] *..> recipe {
    do! sh "dotnet publish -o out" {}
}

"clean" => rm {dir "out"}

Dependency rules — <== and <<<

Both create phony rules with AlwaysRerun that demand a list of targets.

<== demands targets in parallel:

"main" <== ["build"; "test"]

<<< demands targets sequentially, one after another:

"deploy" <<< ["build"; "test"; "package"]

Conditional file rule — ..?>

Creates a file rule where the pattern is a predicate function instead of a glob:

(fun name -> name.EndsWith ".generated.cs") ..?> recipe {
    let! target = getTargetFullName()
    do! trace Info "Generating %s" target
}

File/rule name matching

Rules can use wildcard patterns in file names:

"*.dll" ..> recipe {
    let! target = getTargetFullName()
    do! trace Info "Building %s" target
}

Patterns can include path fragments: "bin/*/app.exe" matches both bin/release/app.exe and bin/debug/app.exe.

Named capture groups

Wildcards can be named using parentheses to extract matched parts:

"bin/(config:*)/app.exe" ..> recipe {
    let! config = getRuleMatch "config"
    // config = "release" for target "bin/release/app.exe"
    do! sh "dotnet publish" {
        args ["-c"; config; "-o"; "bin/" + config]
    }
}

A more complex example with multiple groups:

"(dir:*)/(file:*).(ext:c*)" ..> recipe {
    let! dir = getRuleMatch "dir"
    let! file = getRuleMatch "file"
    let! ext = getRuleMatch "ext"
    // for "src/hello.cs": dir="src", file="hello", ext="cs"
}

Groups can span multiple path elements: "(src:a/**)/bin/*.exe".

Note: ** in rule patterns does not match an empty directory segment, unlike filesets.

Teardown rules

The teardown custom operation specifies targets to run during shutdown. It accepts a list of target names that must be defined as regular rules in the same script.

Engine mode

In xakeEngine mode, teardown targets run sequentially during StopAsync after all in-flight tasks complete:

let engine = xakeEngine {
    rules [
        command "cleanup" {
            do! trace Message "Releasing resources..."
        }
    ]
    teardown ["cleanup"]
    start
}

// ... use engine ...
do! engine.StopAsync()   // runs "cleanup" after in-flight work finishes

Script mode

In xakeScript mode, the teardown target names are stored in ExecOptions.Teardown and passed through to EngineOptions. To run teardown targets in script mode, use xakeEngine with start/StopAsync, or demand them explicitly after your main build.

Adding rules — legacy syntax

The rule, rules with operators, and phony custom operations still work but the builder syntax inside rules [...] is preferred.

do xakeScript {
    // Single rule via custom operation
    rule ("build" => recipe { do! sh "dotnet build" {} })

    // Phony shorthand
    phony "hello" (recipe { do! trace Message "Hi!" })

    // Multiple rules with operators
    rules [
        "main" <== ["build"]
        "clean" => rm {dir "out"}
    ]
}

Clone this wiki locally