.NET · C# and F# · built on argh

runna build.
runna test.

Your build targets are the cases of a C# union or an F# DU. Dependencies run first, each target takes its own typed flags, and XML docs become --help: a build script that is a real CLI, with the help of argh.

nuget ↗
nuget ↗
./build.cs release --graph
$ ./build.cs release --graph
 
release Verify gates, pack, generate notes, diff APIs
├─ requires
│  ├─ pristine-check
│  └─ test
└─ composes
   ├─ pkg generate
   ├─ pkg validate
   ├─ release-notes
   └─ api-changes
 
Runs: pristine-check → test → pkg generate → pkg validate → release-notes → api-changes
 
$ ./build.cs test --filter Unit
$


C# or F#.
Then run it.

The same three targets either way: test needs build, which needs clean. It requires .NET 11.

build.cs
#!/usr/bin/env -S dotnet --
#:package [email protected]
#:property GenerateDocumentationFile=true
#:property NoWarn=CS1591
using Nullean.Runna;
using static Nullean.Runna.Shell;

var app = new RunnaApp<Target>("build");

app.Bind(t => t.Value switch
{
    Clean => app.Target(() => Exec("dotnet clean")),
    Build => app.Target([new Clean()], () => Exec("dotnet build")),
    Test  => app.Target([new Build()], () => Exec("dotnet test")),
    _ => throw new InvalidOperationException($"unmapped target {t.Value}")
});

return await app.RunAsync(args);

/// <summary>Delete build output</summary>
record Clean;
/// <summary>Build the solution</summary>
record Build;
/// <summary>Build, then run the tests</summary>
record Test;

union Target(Clean, Build, Test);

A file-based app is enough: build.cs next to your solution, run with dotnet run build.cs or straight from the shell.

The package READMEs cover the full DSL: Runna for C# and Runna.Fs for F#.

runna build.
Ship it.

Typed targets, dependencies first, and help you never have to write.