Skip to content

Composition

Stages and pipelines are F# values. A reusable block can return a plain stage or an InputSpec<StageContext> when it needs CLI inputs.

Define a block

open Partas.Build

let configuration =
    Input.option<string> "--configuration"
    |> Input.def "Release"

let compile project = input {
    let! config = configuration
    return stage $"compile {project}" {
        run (cmd $"dotnet build {project} -c {config}")
    }
}

Assemble a workflow

let projects = [ "src/Core/Core.fsproj"; "src/App/App.fsproj" ]

let build = pipeline "build" {
    workingDir __SOURCE_DIRECTORY__
    stage "restore" { run "dotnet restore" }
    for project in projects do
        compile project
}

let root = Command.root {
    command "build" { build }
}

build --help registers --configuration once, even though several stages read it. The same block can be reused by another command.

Nest stages

let libraries = stage "libraries" {
    timeout 300
    for project in projects do
        compile project
}

Place settings before loops. For individually yielded blocks, settings can appear before or after the block. Children inherit unset settings from the enclosing stage.

Wrap blocks that carry inputs

A wrapper returning a stage can accept an input-bearing stage:

let group name (block: InputSpec<StageContext>) = stage name {
    block
}

let grouped = group "core" (compile "src/Core/Core.fsproj")

The wrapper retains the inner input set. See the composition reference for wrappers accepting lists, adding their own inputs, and compiler limits around nested specs.

Yield lists

let blocks = [ for project in projects -> compile project ]

let listed = pipeline "compile" {
    [ yield! blocks ]
}

Use a list when F# rejects yield! mixed with custom operations. A for loop is usually the simpler form.

Composition across files

Put reusable definitions in a file that constructs commands without invoking or exiting:

// tools/wire.defs.fsx
module Wire

open Partas.Build

let generate = command "wire" {
    stage "generate" { echo "generating" }
}

Load it from your entry-point script and add the command to the root:

// build.fsx; the package references precede this #load
#load "tools/wire.defs.fsx"
open Partas.Build

let root = Command.root {
    addCommand Wire.generate
}

exit (Command.invoke (Args.script ()) root).ExitCode

Keep command names unique among siblings. An explicit module name avoids guessing the identifier F# derives from a filename. For a reusable root shared with a session, follow Hosting.

The composition reference includes larger examples and standalone script entry points.

Edit this page