-
Notifications
You must be signed in to change notification settings - Fork 0
Build Script Authoring
A Tamp build is a regular .NET console project. There's no DSL, no manifest format, no tamp.json — just C# with a fluent target API. This page is the reference for that API.
using Tamp;
using Tamp.NetCli.V10;
class Build : TampBuild
{
public static int Main(string[] args) => Execute<Build>(args);
[Parameter] Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
[Solution] readonly Solution Solution = null!;
AbsolutePath Artifacts => RootDirectory / "artifacts";
Target Clean => _ => _
.Executes(() => CleanArtifacts()); // 1.1.0+ helper: bin/obj/Artifacts wipe with self-evict guard
Target Restore => _ => _
.Internal() // 1.1.0+: hidden from --list / `tamp` no-args
.Executes(() => DotNet.Restore(s => s.SetProject(Solution.Path)));
Target Compile => _ => _
.DependsOn(nameof(Restore))
.Executes(() => DotNet.Build(s => s.SetConfiguration(Configuration)));
Target Pack => _ => _
.DependsOn(nameof(Compile))
.Executes(() => DotNet.Pack(s => s.SetOutput(Artifacts))); // top-level by default — no marker needed
Target Default => _ => _ // 1.1.0+: explicit default target marker
.Default()
.DependsOn(nameof(Pack));
}Five rules:
- The class derives from
TampBuild. -
MaincallsExecute<T>(args)— the framework's reflective entry. - Targets are
Target-typed properties whose value is a_ => _.X().Y().Z()lambda. -
[Parameter]properties get auto-bound from CLI args, environment variables, or their declared default. Member-name → flag-name mapping isPascalCase→kebab-case(e.g.Configuration→--configuration,MaxParallelism→--max-parallelism). The CLI parser is case-sensitive — only the kebab-case form binds (--Registrywon't matchRegistry). Override via[Parameter(Name = "...")]. Full rules: Parameter & Secret Injection — name → flag → env mapping. - Computed paths and helpers are normal C# members.
[Parameter]works on both properties and fields (includingreadonlyfields — the canonical Tamp build script idiom is[Parameter] readonly string Configuration;).
That's the whole programming model. Everything below is the surface you can configure on each target.
Every target you declare is top-level by default — visible in --list, runnable by name, surfaced in IDE runner menus. Mark internal helpers (Restore, intermediate fan-outs, glue targets) with .Internal(). The old .TopLevel() marker is obsolete; it's a no-op kept for source compatibility and removed in 2.0.
| Decorator | Effect |
|---|---|
| (none) | Default — target is top-level, surfaces in --list. |
.Internal() |
Hide from default --list; still invokable by name and via --list --all. |
.Default() |
Mark as the build's default target — runs when tamp is invoked with no args. Exactly one allowed per build class (otherwise the executor falls back to a target literally named Default, then Ci). |
The bin/obj/Artifacts wipe used to be five lines of GlobDirectories boilerplate, and the build script's own output directory had to be excluded by hand or the running process self-evicted. CleanArtifacts() ships that pattern with the self-deletion guard built in — pass it an AbsolutePath to scope to a project, or call it with no args to clean every solution project.
Target Clean => _ => _.Executes(() => CleanArtifacts()); // every solution project + Artifacts
Target Clean => _ => _.Executes(() => CleanArtifacts(RootDirectory / "src" / "MyLib")); // single projectEvery wrapper verb exposes both a fluent configurer and an object-init overload. Both produce identical CommandPlans — pick the one you prefer.
// fluent — chains read top-to-bottom, easy to grep for "SetX"
DotNet.Build(s => s
.SetProject(Solution.Path)
.SetConfiguration(Configuration)
.SetNoRestore(true));
// object-init — flatter, integrates with target-typed `new()` in C# 9+
DotNet.Build(new()
{
Project = Solution.Path,
Configuration = Configuration,
NoRestore = true,
});Fluent stays canonical in docs and the tamp init template; object-init is available across every first-party wrapper and any sub-facade. To scaffold a new build with the object-init shape, pass the flag at init time:
dotnet tamp init --settings-style=initFluent remains the default — pass --settings-style=fluent explicitly or omit the flag for the canonical shape.
Each method returns ITargetDefinition for chaining.
| Method | Purpose |
|---|---|
Phase(Phase) |
Group target by lifecycle: Restore / Build / Test / Pack / Publish / Deploy / Custom. |
Description(string) |
Shown in --list output. |
Tag(params string[]) |
Free-form labels for grouping in summaries. |
Internal() |
Hide from default --list and the runner's IDE menus. Target stays invokable by name and via --list --all. Replaces .TopLevel() — every target is top-level by default in 1.1.0+. |
Default() |
Mark as the build's default target (runs when invoked with no args). Mutually exclusive with Internal(). |
TopLevel() |
Obsolete in 1.1.0+, removed in 2.0. No-op kept for source compatibility — top-level is the default. |
| Method | Semantics |
|---|---|
DependsOn(...names) |
Hard prerequisite: pulls the named targets into the plan and orders before this one. |
After(...names) |
Order-only: if both targets happen to be in the plan, the named ones come first. Does not pull them in. |
Before(...names) |
Mirror of After. |
Triggers(...names) |
Outgoing fan-out: if this target runs, also pull in the named targets. |
TriggeredBy(...names) |
Incoming fan-out. Equivalent to declaring Triggers(this) on each named target. |
nameof(OtherTarget) is the recommended idiom for referring to other targets — type-safe and refactor-safe.
Cycles across DependsOn ∪ After ∪ Before ∪ TriggeredBy are detected at construction time and fail-fast with a path trace. OnFailureOf is intentionally excluded from cycle detection — it's a runtime conditional, not a planning edge.
| Method | Behaviour on false |
|---|---|
OnlyWhen(Func<bool>) |
Skip silently. The [CallerArgumentExpression] capture surfaces the predicate text in dry-run output and skip messages. |
Requires(Func<bool>) |
Hard fail: aborts the build with the predicate text in the failure message. |
Use OnlyWhen for "this target is irrelevant in this context" (e.g., Push only on main); use Requires for "this target can't run without X" (e.g., Deploy requires credentials set).
| Method | Purpose |
|---|---|
FailureMode(Mode) |
Default Fatal (abort build). Continue (this target's failure shouldn't stop the build). Retry (retry per Backoff). |
Retry(count, Backoff, ...exitCodes) |
Retry policy. Backoff factories: Backoff.Linear, Backoff.Exponential, Backoff.Constant, Backoff.Custom. |
OnFailureOf(...names) |
Catch handler. This target runs only when one of the named targets fails. Distinct from FailureMode.Continue and from AssuredAfterFailure. See Failure Handling for the full decision matrix. |
AssuredAfterFailure() |
Cleanup pattern. This target runs whether the build succeeded or failed, as long as it's in the plan. Failing assured cleanups don't reverse the original failure. |
| Method | Effect |
|---|---|
RequiresNetwork() |
Fails fast if offline mode is set. |
RequiresDocker() |
Fails fast if Docker daemon is unreachable. |
RequiresAdmin() |
Fails fast if not elevated. |
RequiresTool(name, minVersion?) |
Fails fast if the tool isn't on PATH (or below the minimum version). |
Note: Capability preflight is recorded on the spec but the v0 executor doesn't yet enforce it. Expected to land in v0.x.
| Method | Purpose |
|---|---|
Consumes(Resource, ConsumeMode) |
Declarative resource usage. ConsumeMode.Shared (parallel-safe) or ConsumeMode.Exclusive (serializes). |
Built-in resource kinds: Resource.BuildCache.Dotnet, Resource.BuildCache.Yarn, Resource.BuildCache.Nuget, Resource.Filesystem(path), Resource.Network.Internet, Resource.Network.Registry(host), Resource.Process.Docker. Modules can extend.
Note: Resource scheduling is recorded but not yet honoured by the v0 sequential executor. The declarative surface is in place for the eventual scheduler.
| Method | Purpose |
|---|---|
Timeout(TimeSpan) |
Hard wall-clock kill at expiry. |
ExpectedDuration(TimeSpan) |
Soft hint; powers "this is taking longer than usual" telemetry. |
MemoryBudget(int megabytes) |
Expected peak RSS. |
MemoryHardLimit(int megabytes) |
Optional ceiling enforced via cgroup if available. |
MaxParallelism(int) |
Copies of this target in one build invocation. |
MaxHostParallelism(int) |
Copies across the whole host. |
| Method | Purpose |
|---|---|
Idempotent() |
Same inputs → same result; safe to skip if cached. |
InputHash(Func<string>) |
Hash function over inputs (file globs, env vars, parameters). |
Produces(globPattern) |
Declarative outputs. |
RunMode(RunMode) |
Always (default), WhenInputsChanged, or Manual. |
Three overloads of Executes:
.Executes(() => { /* arbitrary code */ })
.Executes(() => DotNet.Build(...)) // single CommandPlan
.Executes(() => new[] { DotNet.Restore(), DotNet.Build(...) }) // sequence of plansMultiple Executes calls accumulate; they run in declaration order.
The executor resolves the default target in this order: explicit .Default() decorator → property literally named Default → property literally named Ci. A common shape:
Target Ci => _ => _.DependsOn(nameof(Pack)); // top-level by default
Target Default => _ => _.Default().DependsOn(nameof(Compile)); // local-dev shortcut, explicit markerNaming caveat:
Ciis a property name on user build classes. Tamp's staticTampBuild.CiHost(the typed CI vendor adapter) deliberately uses a different name to avoid collision.
Patterns that come up often enough to memorize but aren't load-bearing framework API — write inline, don't reach for a helper.
[GitRepository] readonly GitRepository Git = null!;
// + a Tool/Parameter source of GitVersion's SemVer per your project
string ImageTag => $"{GitVersion.SemVer}-{Git.Commit[..7]}";
Target DockerBuildBackend => _ => _
.Executes(() => Docker.Build(s => s
.SetContext(RootDirectory)
.AddTag($"holdfast-backend:{ImageTag}")));One line of string interpolation. Don't reach for a TampBuild.ImageTag(...) helper — different projects want different shapes (v{semver}+{sha}, {branch}-{sha}-{date}, etc.) and the inline form is already self-documenting. (HoldFast asked for a blessed helper during the 1.2.0 trial; we kept it as a doc pattern instead — recorded in this section for the record.)
Target Ci => _ => _
.Default()
.DependsOn(Test, Publish, FrontendBuild, DockerBuildBackend);The varargs DependsOn(Target t1, Target t2, params Target[] more) overload (1.3.0+) takes bare identifiers. Names are resolved by the framework via the same property reflection that registers the targets.
For 1 dep: the single-arg DependsOn(Restore) shape (CallerArgumentExpression captures the name). Both forms compose with chained .DependsOn(X).DependsOn(Y) if you prefer that style. All three produce identical specs.
using Tamp.Http;
Target SmokeQa => _ => _
.DependsOn(DeployQa)
.Executes(async () =>
await HttpProbe.WaitForHealthy(
url: "https://qa.example.com/health/live",
timeout: TimeSpan.FromMinutes(2)));HttpProbe.WaitForHealthy (in Tamp.Http 0.1.1+) polls a URL on a 2-second cadence until IsSuccessStatusCode or your custom predicate returns true. Treats connection errors and per-request timeouts as transient. Throws TimeoutException on budget exhaustion with the last observed status, attempt count, and last transport error in the message.
Override defaults via optional params: interval, headers (for auth'd health endpoints), isHealthy (async predicate for body-content checks like rejecting 200 OK with "status":"degraded"), HttpClient (for self-signed certs or shared client reuse), CancellationToken. See the Tamp.Http README for the parameterized example.
tamp Test Pack # both run; deduped union of dependency closuresTargets share dependencies — e.g., if both Test and Pack depend on Compile, Compile runs once.
tamp Pack --dry-run # print every CommandPlan that would run
tamp Pack --plan # render the target dependency graph
tamp --list # list top-level targets (or all if none marked)
tamp --list-tree # list targets with their dependencies
tamp --list --all # include internal targets tootamp Pack --skip StampVersion # skip a single dep; dependents still run
tamp Pack --skip StampVersion --skip Lint # repeat for multiple skips
tamp Pack --skip-deps # run only Pack's Executes; skip all upstreamSkipped targets land in the build summary as Skipped with the reason skipped by --skip or skipped by --skip-deps — visible distinct from OnlyWhen-driven skips. Their Executes block is a no-op; downstream targets that DependsOn them still run normally (treat the skip as "already satisfied").
Use cases:
-
Debug loops —
tamp Pack --skip-depsafter a successful upstream run to re-test just the pack step. -
Bypass a broken upstream tool — e.g.,
--skip StampVersionwhencargo-editisn't installed yet on a fresh machine. -
Selective replay — combine
--skipwithdotnet tamp Cito skip the long-running step you've already validated.
Typo protection: --skip XYZ where XYZ isn't a known target name errors with exit code 2 rather than silently no-op'ing.
| Flag | Purpose |
|---|---|
--dry-run |
Print plans, execute nothing. |
--plan |
Render target DAG, exit. |
--list |
List top-level targets. |
--list-tree |
List + show dependencies. |
--all |
Used with --list* — include internal targets. |
--skip <target> |
Treat the named target as already-satisfied. Repeatable. Dependents still run. (1.9.0+) |
--skip-deps |
Treat every non-root target as skipped. Run only the explicitly-named target's Executes. (1.9.0+) |
--format=<text|json> |
Output format for --list / --list-tree. JSON emits the full target + parameter catalog. (1.9.0+) |
--reporter=<text|json> |
Build event reporter. json emits NDJSON (one event per line) to stdout, suppresses banner + ==> decorations. For IDE-extension consumption. (1.9.0+) |
--verbosity <q|m|n|v|d> |
quiet / minimal / normal / verbose / diagnostic. |
--quiet |
Shortcut for --verbosity quiet. |
--verbose |
Shortcut for --verbosity verbose. |
--diagnostic |
Shortcut for --verbosity diagnostic. |
Anything else in --name value form is treated as a [Parameter] binding.
- Parameter & Secret Injection
- Failure Handling
- Module Catalog
-
Tooling Primitives —
AbsolutePath,RootDirectory,Verify
Start here
Modules
- Module Catalog (canonical list)
- .NET toolchain
- Containers
- JS toolchain
- Supply-chain security
Analyzers
Tooling
Execution
CI integration
Migration
Reference
Project