Partas.Build
0.2.2
See the version list below for details.
dotnet add package Partas.Build --version 0.2.2
NuGet\Install-Package Partas.Build -Version 0.2.2
<PackageReference Include="Partas.Build" Version="0.2.2" />
<PackageVersion Include="Partas.Build" Version="0.2.2" />
<PackageReference Include="Partas.Build" />
paket add Partas.Build --version 0.2.2
#r "nuget: Partas.Build, 0.2.2"
#:package Partas.Build@0.2.2
#addin nuget:?package=Partas.Build&version=0.2.2
#tool nuget:?package=Partas.Build&version=0.2.2
Partas.Build
The entire
FSharp.SystemCommandLinewas essentially just copy pasted directly into this repo. All credit to the original author. Much of the pipeline implementation is also copied fromFun.Build. All credit to the original author.
Motivation
I hate CICD/CLI plumbing.
At the same time, it saves me from headache when I return to projects later.
System.CommandLine is great, comes with lots of batteries, and there exists
a great enough wrapper for it with FSharp.SystemCommandLine.
Fun.Build looks good for a github yaml type vibe of making workflows. But a majority
of it is hampered by the outdated command line parsing, and lack of typing.
So I combined Fun.Build with the strong typing of FSharp.SystemCommandLine builders,
and dog fed it to this repos own CI/CD plumbing.
So this begs the question: do I get friends now?
No. This still sounds useless
Rude.
Well, we've made everything compositional with a minimal contract at layers to bind command line arguments to their real values.
// ==== CLI OPTIONS/ARGS
// We don't need CEs EVERYWHERE.
// This is beautiful and readable.
let quick =
Input.option<bool> "--quick"
|> Input.alias "-q"
|> Input.desc "Skips installations, linting, and other checks"
let config =
Input.option<string> "--configuration"
|> Input.alias "-c"
|> Input.desc "Build/pack configuration"
|> Input.def "Release"
|> Input.arity Arity.ExactlyOne
|> Input.helpName "Debug|Release"
|> Input.acceptOnlyFromAmong [ "Debug"; "Release" ]
let clean (skip: bool) = stage "clean" {
when' (not skip)
run (fun _ ->
!! "**/**/bin" // Fake.IO.Globbing.Operators
++ "temp"
-- "bin"
|> Shell.cleanDirs
)
}
let restore (skip: bool) = stage "restore" {
when' (not skip)
run "dotnet tool restore --verbosity q"
run (cmd $"dotnet restore {Solutions.Main}")
}
let build (config: string) (skip: bool) =
stage "build" {
when' (not skip)
run (cmd $"dotnet build {Projects.FsProj.Solution} -c {config}")
}
module Commands =
let build = command "build" {
description "Builds the solution"
pipeline "build" {
workingDir root
// The binding of inputs are collected and
// deduped by the CEs, so you never have to
// directly add them to the command (but you still can)
let options = input {
let! skip = quick
and! config = config
return {| skip = skip; config = config |}
}
restore options.skip
clean options.skip
build options.config options.skip
}
}
This makes it easier to declare what options an operation depends on immediately when writing it though. So the alternative would be this:
let build = inputs {
let! config = config
and! quick = skip
return stage "build" {
when' (not skip)
run (cmd $"dotnet build {Projects.FsProj.Solution} -c {config}")
}
}
module Commands =
let build = command "build" {
description "Builds the solution"
pipeline "build" {
workingDir root
restore
clean
build
}
}
nice
Friends now?
Documentation
See the documentation generated by fsdocs in lock-step with the source.
Development
Build CLI
Every repository task runs through the Build project rather than a script, so
the tasks are typed, debuggable, and discoverable:
dotnet run --project Build.fsproj -- --help
| Command | What it does |
|---|---|
build |
Restores and builds the solution |
test |
Builds and runs the Expecto suite |
publish |
Packs and pushes to NuGet (--nuget-key; falls back to the local feed) |
bump |
Rewrites <Version> in a project file (-p <project>) |
docs |
Builds the fsdocs site (--watch to serve it) |
Flags belong to the commands whose stages read them: --quick skips restores
and the clean, --skip-tests skips the suite, --configuration picks the
configuration. None of them is registered by hand — see Adding a step.
Versioning
Versions live in the project files, not in a notes file or on the command line:
dotnet run --project Build.fsproj -- bump -p Partas.Build # patch, the default
dotnet run --project Build.fsproj -- bump minor -p Partas.Build Partas.ExternalAnnotations
dotnet run --project Build.fsproj -- bump rc -p Partas.Build # 0.2.0 -> 0.2.1-rc.1
dotnet run --project Build.fsproj -- bump 2.0.0-nightly.7 -p Partas.Build
Each packable project carries a <Version> and an <AssemblyVersion>, and a
bump rewrites both — the second as <major>.0.0.0, so it only moves when the
major does. That is deliberate: an assembly's version is its identity to
everything already compiled against it, and moving it on a patch bump breaks
anything not rebuilt in the same pass.
pack passes no version property, so CI publishes what the project file says.
bump is skipped when --ci is set — which it is by default under GitHub
Actions — so a version is bumped locally and committed, never invented on a
runner. Add a project to Spec.Options.Project.versioned to make it a bump
target and have it packed.
Layout
Build.fsproj the build CLI
Build/
TargetOperators.fs list-taking FAKE target operators
Spec.fs typed repository paths and the CLI options
Program.fs the stages and the commands
src/Partas.Build/ the library
tests/ the Expecto suites
Adding a project
Spec.fs addresses the repository through EasyBuild.FileSystemProvider, so
paths are checked when the build project compiles. After adding a project,
register it in Spec.fs:
module Projects =
module Directory =
type Solution = Root.src.``Partas.Build``
type NewThing = Root.src.``Partas.NewThing``
A typo, or a project renamed without updating the build, then fails at compile time rather than halfway through a release.
Adding a step
A step is a stage of a pipeline. A stage that needs a flag binds it in an
inputs { } block, which is also what makes the flag appear in --help:
let myStep = inputs {
let! quick = Options.quick
return stage "my step" {
when' (not quick)
run (cmd $"dotnet ... {Projects.FsProj.Solution}")
}
}
Add it to any command's pipeline { }. The condition stays in the stage, so the
command carries no flags of its own, and adding the stage to a second command
registers --quick there too.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- FSharp.Control.AsyncSeq (>= 4.15.0)
- FSharp.Core (>= 10.1.400)
- FsToolkit.ErrorHandling (>= 5.2.0)
- Spectre.Console (>= 0.57.2)
- System.CommandLine (>= 2.0.11)
-
net10.0
- FSharp.Control.AsyncSeq (>= 4.15.0)
- FSharp.Core (>= 10.1.400)
- FsToolkit.ErrorHandling (>= 5.2.0)
- Spectre.Console (>= 0.57.2)
- System.CommandLine (>= 2.0.11)
-
net8.0
- FSharp.Control.AsyncSeq (>= 4.15.0)
- FSharp.Core (>= 10.1.400)
- FsToolkit.ErrorHandling (>= 5.2.0)
- Spectre.Console (>= 0.57.2)
- System.CommandLine (>= 2.0.11)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Partas.Build:
| Package | Downloads |
|---|---|
|
Partas.Build.ExternalAnnotations
Package Description |
|
|
Partas.Build.Baked
Prebaked options, arguments, and composed stages for Partas.Build |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.6.5 | 47 | 9/21/2026 |
| 0.6.4 | 41 | 9/21/2026 |
| 0.6.3 | 47 | 9/19/2026 |
| 0.6.2 | 56 | 9/19/2026 |
| 0.6.1 | 47 | 9/19/2026 |
| 0.6.1-alpha.1 | 43 | 9/18/2026 |
| 0.5.0 | 74 | 9/16/2026 |
| 0.4.0-alpha.3 | 128 | 9/5/2026 |
| 0.4.0-alpha.2 | 47 | 9/4/2026 |
| 0.4.0-alpha.1 | 52 | 9/3/2026 |
| 0.3.0 | 147 | 8/29/2026 |
| 0.2.3 | 99 | 8/27/2026 |
| 0.2.2 | 88 | 8/27/2026 |
| 0.2.1 | 90 | 8/26/2026 |
| 0.2.0 | 98 | 8/25/2026 |
| 0.1.4 | 164 | 8/25/2026 |
| 0.1.3 | 104 | 8/23/2026 |
| 0.1.2 | 96 | 8/23/2026 |
| 0.1.1 | 97 | 8/22/2026 |
| 0.1.0 | 96 | 8/22/2026 |