Skip to content

Migrating From Cake

Scott Singleton edited this page May 10, 2026 · 1 revision

Migrating from Cake

Cake (Cake.Tool / dotnet cake build.cake) and Tamp solve the same problem with different shapes. This is a concept-by-concept guide for moving an existing Cake build to Tamp.

What's similar

  • Tool wrapper philosophy. Cake's "addins" model — independently-versioned packages that wrap external tools — is the model Tamp adopted (with explicit version pinning per ADR 0002).
  • Target authoring. "Run X, then Y, then Z" via declarative dependencies. Cake's Task("Build").IsDependentOn("Clean") ↔ Tamp's Target Build => _ => _.DependsOn(nameof(Clean)).
  • Multiple targets per build. Both projects let you invoke specific targets and run the full pipeline by default.
  • Argument injection. Cake's Argument("configuration", "Release") ↔ Tamp's [Parameter] string Configuration = "Release".
  • Global tool installation flow. dotnet tool install -g Cake.Tool && dotnet cake build.cakedotnet tool install -g dotnet-tamp && dotnet tamp ci.

What's different

Script vs. compiled project

Cake builds are C# scripts (build.cake) that the Cake runner compiles and executes on each invocation. There's no separate project, no .csproj, no compile phase you control.

Tamp builds are regular .NET console projects. You have a build/Build.csproj, you reference Tamp.Core like any other dependency, you get full IDE support, refactoring tools work, and the build itself is just dotnet run --project build. The global tool exists for ergonomics but nothing depends on it.

This is the biggest change in feel. Pros and cons of the Tamp model:

  • ✅ Real IDE support — IntelliSense, F12 navigation, refactoring, debugging.
  • ✅ Standard NuGet dependency management — same Directory.Packages.props you already use elsewhere.
  • ✅ Type-safe everything; Cake's preprocessor directives (#addin, #tool, #load) become <PackageReference> and ordinary C# namespaces.
  • ❌ Not a single-file script anymore. build/ is a small project with one or two files.

Addin discovery

Cake: #addin nuget:?package=Cake.Docker at the top of the script.

Tamp: <PackageReference Include="Tamp.Docker.V27" /> in build/Build.csproj, then using Tamp.Docker.V27; in Build.cs.

Tool discovery

Cake: #tool nuget:?package=ReportGenerator at the top of the script.

Tamp: [NuGetPackage("ReportGenerator")] readonly Tool ReportGenerator; as a property on the build class. The first run installs into a Tamp-managed cache; subsequent runs reuse.

Target syntax

Cake's task definition shape (Task("Build").IsDependentOn(...).Does(() => {...})) becomes Tamp's Target Build => _ => _.DependsOn(nameof(Clean)).Executes(() => {...}).

// Cake (build.cake)
Task("Clean")
    .Does(() => CleanDirectory("./artifacts"));

Task("Build")
    .IsDependentOn("Clean")
    .Does(() => DotNetBuild("./MySln.sln"));

Task("Default")
    .IsDependentOn("Build");

RunTarget(Argument("target", "Default"));
// Tamp (Build.cs)
using Tamp;
using Tamp.NetCli.V10;

class Build : TampBuild
{
    public static int Main(string[] args) => Execute<Build>(args);

    AbsolutePath Artifacts => RootDirectory / "artifacts";

    Target Clean => _ => _.Executes(() => Artifacts.Delete());

    Target Compile => _ => _
        .DependsOn(nameof(Clean))
        .Executes(() => DotNet.Build(s => s.SetProject("./MySln.slnx")));

    Target Default => _ => _.DependsOn(nameof(Compile));
}

Wrapper API

Cake aliases like DotNetBuild("./MySln.sln") become Tamp's DotNet.Build(s => s.SetProject("./MySln.slnx")). Configuration is a fluent chain on a settings object rather than overloads of free functions.

// Cake
DotNetBuild("./MySln.sln", new DotNetBuildSettings
{
    Configuration = "Release",
    NoRestore = true,
});

// Tamp
DotNet.Build(s => s
    .SetProject("./MySln.slnx")
    .SetConfiguration(Configuration.Release)
    .SetNoRestore(true));

Environment + arguments

Cake reads Argument<T>(name, default) and EnvironmentVariable(name) imperatively in the script body. Tamp uses [Parameter] attributes on properties; resolution happens once before any target runs, with CLI > env > default precedence.

Cake Tamp
var cfg = Argument("configuration", "Debug"); [Parameter] string Configuration = "Debug";
EnvironmentVariable("CI") [Parameter(EnvironmentVariable = "CI")] bool IsCi;

Path handling

Cake has DirectoryPath and FilePath distinct types. Tamp has a single AbsolutePath covering both — same operations whether the path is a file or a directory.

// Cake
DirectoryPath src = "./src";
FilePath csproj = src.CombineWithFilePath("MyApp/MyApp.csproj");

// Tamp
AbsolutePath src = RootDirectory / "src";
AbsolutePath csproj = src / "MyApp" / "MyApp.csproj";

Failure handling

Cake: OnError is a per-task setter that runs cleanup on failure.

Tamp:

  • OnFailureOf(nameof(X)) — explicit catch handler that runs only when X failed
  • AssuredAfterFailure() — cleanup that always runs
  • FailureMode(FailureMode.Continue) — this target's failure shouldn't stop the build

The decision matrix is in Failure Handling.

Migration recipe

  1. Project setup. mkdir build && cd build && dotnet new console -n Build -o .. Add references to Tamp.Core + the modules your script used. Each Cake #addin becomes a <PackageReference>.
  2. Convert the script. Move build.cakeBuild.cs (or several files in build/). The class derives from TampBuild; Main calls Execute<Build>(args).
  3. Rewrite tasks. Each Task("X") becomes a Target X property. Each IsDependentOn becomes DependsOn(nameof(Y)).
  4. Wrappers. Replace each Cake alias with the corresponding Tamp wrapper call. Most names are similar; settings move from anonymous-object initializer to fluent chain.
  5. Arguments. Convert each Argument(...) to a [Parameter] property. Defaults move to the property's declared default.
  6. Cleanup paths. Cake's CleanDirectory becomes path.Delete() or path.GlobDirectories("**/bin", "**/obj").ForEach(d => d.Delete()).
  7. Tool resolution. #tool nuget:?package=Foo becomes [NuGetPackage("Foo")] readonly Tool Foo;.
  8. Test it locally. dotnet run --project build -- Default — should run the full pipeline.
  9. Install the global tool (optional). dotnet tool install -g dotnet-tamp if you want the same dotnet cake-shaped invocation, just dotnet tamp Default.

See also

Clone this wiki locally