Skip to content

Migrating From NUKE

BrewingCoder edited this page Jul 13, 2026 · 4 revisions

Migrating from NUKE

Tamp is deliberately NUKE-shaped at the surface — the target authoring DSL syntax is similar because the syntax is good. The architecture underneath is what was rebuilt. This guide is a concept-by-concept mapping for someone porting an existing NUKE build.

What's the same

  • Build class shape. Derive from a base class, declare Target-typed properties, lambda configurer with _ => _.X().Y().Z(). NUKE's class Build : NukeBuild → Tamp's class Build : TampBuild.
  • Target authoring. DependsOn, Before, After, Triggers, TriggeredBy, OnlyWhen (split here into OnlyWhenStatic / OnlyWhenDynamic in NUKE), Requires, Executes, Description. Same idioms.
  • Parameter injection. [Parameter] attribute with environment-variable + CLI override. Same precedence (CLI > env > default).
  • Built-in statics. RootDirectory, TemporaryDirectory, IsLocalBuild, IsServerBuild — same names.
  • AbsolutePath. First-class path type with / operator, glob, hash, copy/move, plus OS temp-path factories and a build-scoped Scratch(...) helper. Tamp's AbsolutePath is a near-port; see Paths & Filesystem for the full method reference.
  • Tool-wrapper shape. Tool.Build(s => s.SetX(...)) — typed configurer returning a plan. Tamp's wrappers follow the same pattern.

What's different on purpose

Naming convention

NUKE: one big Nuke.Common package with all the wrapper extensions inside, plus Nuke.Build, Nuke.GlobalTool, etc.

Tamp: each tool wrapper is an independently versioned package, with the wrapped-tool major in the name when the CLI surface breaks across majors:

NUKE Tamp
using Nuke.Common.Tools.DotNet; using Tamp.NetCli.V10; (or .V8, .V9)
using Nuke.Common.Tools.Docker; using Tamp.Docker.V27;

Recorded in ADR 0002. The motivation is the lifecycle problem that broke NUKE: every wrapper bottlenecked on one repo's release cadence. Tamp pushes wrappers out where each can move independently.

Failure handling — added

NUKE has AssuredAfterFailure (always run after the build) and ProceedAfterFailure (this target's failure shouldn't stop the build, equivalent to Tamp's FailureMode.Continue). Tamp adds OnFailureOf(name) — a true catch handler that runs only when the named target fails. NUKE has nothing like this; the closest in NUKE is wrapping the failing target in try/catch inside a single Executes block.

// Tamp:
Target Notify => _ => _
    .OnFailureOf(nameof(Deploy))
    .Executes(() => Slack.Post(...));

See Failure Handling for the full decision matrix.

Dependency edges

NUKE: DependsOn, DependentFor, Before, After, Triggers, TriggeredBy. Tamp has the same except DependentFor (the inverse direction of DependsOn) — you can express the same thing by adding DependsOn on the receiving end, and we found this clearer in practice.

Internal vs Unlisted

NUKE: Unlisted() hides a target from --list.

Tamp 1.1.0+: every target is top-level (listed) by default; mark internal helpers with .Internal(). Same shape as NUKE's Unlisted() — different name to avoid implying the target is unreachable. Hidden targets stay invokable by name and surface under --list --all. The pre-1.1.0 inverse TopLevel() marker is obsolete and a no-op; remove it during the upgrade.

Components / mixins

NUKE has Nuke.ComponentsIRestore, ICompile, ITest, IPack, IPublish, IHazSolution, IHazGitRepository, etc. — an interface-based mixin pattern for target reuse. Tamp does not ship this yet, and to be precise about what "yet" means: it is named as a candidate in ADR 0001 and listed on the v2 roadmap, but it is not designed, not built, and not scheduled. Staging it after the small core and module catalog stabilised was deliberate — pre-composed target bundles bake in opinions, and baking them in early would have been a guess.

If your NUKE build leaned heavily on components, you'll write those targets out by hand for now. Concretely, that means one Target property per verb (Restore / Compile / Test / Pack) wired with .DependsOn(...) — the shape in Build Script Authoring. For most builds this is 20–40 lines you write once; the pain scales with how many repos share the same target set.

This is the single biggest gap for a NUKE migration, and it's open for contribution. If you have a real NUKE-components-shaped build, you are better positioned to design this than the maintainers are — you know which mixins actually earn their keep. The path is an ADR: open a PR against docs/adr/ with Status: Proposed. Anyone may propose one (ADR 0009 §3.1), there's no CLA, and lazy consensus carries it after 7 days without objection.

CI configuration generation

NUKE generates CI YAML from build code ([GitHubActions(...)], [AzurePipelines(...)], etc.). Tamp does not ship this. Configuration is hand-written YAML for now. The thinking: most teams treat their CI YAML as the source of truth for when things run, and the build script as the source of truth for what runs. Tamp builds the latter and stays out of the former.

Schema-driven wrapper codegen

NUKE has Nuke.Tooling.Generator — JSON schemas describe each tool's CLI; codegen produces the wrapper classes. Tamp's wrappers are hand-authored today. Codegen is on the roadmap (deferred ADR 0013) and will eliminate the V8/V9/V10 duplication; for the moment it's small enough to maintain manually.

Logging

NUKE uses Serilog. Tamp ships a minimal in-house Logger with Trace/Debug/Info/Warn/Error levels, no external deps. Output flows through a RedactingTextWriter so registered secrets are scrubbed automatically. A future Tamp.Logging.Serilog package would adapt the calls into a Serilog pipeline for consumers who want it.

Concept mapping table

NUKE Tamp
class Build : NukeBuild class Build : TampBuild
[Parameter] string Configuration [Parameter] string Configuration
[Solution] readonly Solution Solution [Solution] readonly Solution Solution
[GitRepository] readonly GitRepository GitRepository [GitRepository] readonly GitRepository Git
[GitVersion] readonly GitVersion GitVersion (not yet — Tamp.GitVersion planned)
[NuGetPackage("foo")] readonly Tool Foo [NuGetPackage("foo")] readonly Tool Foo
Target Compile => _ => _.DependsOn(Restore).Executes(...) Target Compile => _ => _.DependsOn(nameof(Restore)).Executes(...)
OnlyWhenStatic / OnlyWhenDynamic OnlyWhen (single method; expression always captured)
Requires(() => Solution) Requires(() => Solution != null)
Triggers(...) / TriggeredBy(...) same
AssuredAfterFailure() same
ProceedAfterFailure() FailureMode(FailureMode.Continue)
(no equivalent) OnFailureOf(nameof(X)) — catch handler
Unlisted() Internal() (1.1.0+) — same shape, renamed to remove the "unreachable" implication
DotNetTasks.DotNetBuild(...) DotNet.Build(...) (different namespace per major: Tamp.NetCli.V10)
DockerTasks.DockerBuild(...) Docker.Build(...) (Tamp.Docker.V27)
RootDirectory / TemporaryDirectory same

Migration recipe

  1. Project setup. Create build/Build.csproj referencing Tamp.Core + the modules you need. Replace NUKE references.
  2. Class. Change : NukeBuild to : TampBuild.
  3. Imports. using Nuke.Common.Tools.DotNet;using Tamp.NetCli.V10; (pick the V matching your dotnet SDK).
  4. Targets. Convert wrapper calls — DotNetTasks.DotNetBuildDotNet.Build. Most flag setters keep identical names.
  5. Parameter expressions. Requires(() => Solution) becomes Requires(() => Solution != null).
  6. nameof for dep references. Tamp's DependsOn takes target names; NUKE accepts both delegates and names. Use nameof(OtherTarget).
  7. UnlistedInternal(). Targets are visible by default in Tamp 1.1.0+; mark helpers you don't want surfaced in --list with .Internal(). If you've migrated from a pre-1.1.0 Tamp build, strip the obsolete .TopLevel() calls — they're now no-ops.
  8. Components. If you used Nuke.Components interfaces, you'll inline those for now.
  9. CI YAML. If NUKE was generating your YAML, hand-write it. Tamp ships GitHub Actions / Azure DevOps / TeamCity vendor adapters but doesn't generate the workflow files themselves.

See also

Clone this wiki locally