Skip to content

Reference | Rules

olegz edited this page Mar 16, 2026 · 7 revisions

Defining rules

Rules are added to a build script using the rules directive inside xakeScript. The "main" target is the default entry point.

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

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

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

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

Rule types

File rules — ..>

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

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

Phony rules — =>

Creates a rule that does not produce a file. Use for actions like cleaning.

"clean" => rm {dir "out"}
"greet" => recipe { do! trace Message "Hello!" }

Command builder — command

A convenient way to define phony rules. Commands always re-run by default.

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

Target builder — target

A convenient way to define file rules with the recipe builder syntax:

target "out/version.txt" {
    let! version = vars.Version
    do! writeText version
}

Targets builder — targets

Defines 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" {}
}

Multi-file rule — *..>

Operator form of multi-target rules:

["out/app.exe"; "out/app.xml"] *..> recipe {
    let! [exeFile; xmlFile] = getTargetFiles()
    do! sh "dotnet publish" {
        args ["-o"; "out"; "/p:DocumentationFile=" + xmlFile.FullName]
    }
}

Dependency rules — <== and <<<

<== demands targets in parallel:

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

<<< demands targets sequentially, one after another:

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

Conditional file rule — ..?>

Creates a rule where the pattern is a predicate function:

(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.

Adding rules

Rules can be added in several ways:

do xakeScript {
    // Multiple rules at once
    rules [
        "main" <== ["build"]
        "clean" => rm {dir "out"}
    ]

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

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

Clone this wiki locally