Cake.Sdk supports multiple entry points using methods prefixed with Main_*. These methods are automatically discovered and executed before the top-level main code, allowing you to organize tasks across multiple files and create more modular build scripts.
Overview
Multiple entry points provide a way to define tasks and setup logic in separate methods that are automatically discovered and executed. This is particularly useful for:
- Organizing tasks by feature or module
- Creating conditional task registration
- Building dynamic task structures based on configuration
- Splitting large build scripts into manageable pieces
Basic Usage
Define entry points by creating methods prefixed with Main_ in a partial class Program:
#:sdk Cake.Sdk
var target = Argument("target", "Default");
Task("Default")
.Does(() =>
{
Information("Hello from top-level main!");
});
RunTarget(target);
public static partial class Program
{
private static void Main_One()
{
Task(nameof(Main_One))
.IsDependeeOf("Clean")
.Does(() => Information("Hello from Main_One"));
}
private static void Main_Two()
{
Task(nameof(Main_Two))
.IsDependeeOf("Clean")
.Does(() => Information("Hello from Main_Two"));
}
}
Naming Convention
- Methods must be prefixed with
Main_to be automatically discovered - The method name after
Main_can be any valid C# identifier - Methods must be
staticand can beprivateorpublic - Methods must be part of a
partial class Program
Execution Order
Main_* methods are executed in the following order:
- All
Main_*methods are discovered and executed first - Top-level main code (outside of methods) is executed after all
Main_*methods RunTargetis called at the end
This ensures that tasks defined in Main_* methods are available when the top-level code runs.
Examples
Basic Multiple Entry Points
Here's a simple example with two entry points:
#:sdk Cake.Sdk
var target = Argument("target", "Default");
Task("Clean")
.Does(() => Information("Cleaning..."));
Task("Build")
.IsDependentOn("Clean")
.Does(() => Information("Building..."));
RunTarget(target);
public static partial class Program
{
private static void Main_PreClean()
{
Task("PreClean")
.IsDependeeOf("Clean")
.Does(() => Information("Preparing for clean operation"));
}
private static void Main_PreBuild()
{
Task("PreBuild")
.IsDependeeOf("Build")
.Does(() => Information("Preparing for build operation"));
}
}
Entry Points with Criteria
You can use criteria to conditionally register tasks:
#:sdk Cake.Sdk
var target = Argument("target", "Default");
var rebuild = HasArgument("rebuild");
var buildData = new BuildData(rebuild);
Task("Clean")
.Does(() => Information("Cleaning..."));
RunTarget(target);
public record BuildData(bool Rebuild);
public static partial class Program
{
static void Main_PreClean()
{
Task("PreClean")
.IsDependeeOf("Clean")
.WithCriteria<BuildData>(c => c.Rebuild, nameof(BuildData.Rebuild))
.Does(() => Information("Preparing for clean operation"));
}
}
Entry Points with Task Dependencies
Entry points can define tasks with dependencies:
#:sdk Cake.Sdk
var target = Argument("target", "Default");
Task("Build")
.IsDependentOn("Validate")
.Does(() => Information("Building..."));
RunTarget(target);
public static partial class Program
{
private static void Main_Setup()
{
Task("Setup")
.Does(() =>
{
Information("Setting up build environment...");
});
}
private static void Main_Validate()
{
Task("Validate")
.IsDependentOn("Setup")
.Does(() =>
{
Information("Validating configuration...");
});
}
}
Use Cases
Organizing Tasks by Feature
Split your build script into logical modules:
public static partial class Program
{
private static void Main_Testing()
{
Task("UnitTest")
.Does(() => Information("Running unit tests..."));
Task("IntegrationTest")
.IsDependentOn("UnitTest")
.Does(() => Information("Running integration tests..."));
}
private static void Main_Packaging()
{
Task("Pack")
.Does(() => Information("Packing artifacts..."));
Task("Publish")
.IsDependentOn("Pack")
.Does(() => Information("Publishing artifacts..."));
}
}
Conditional Task Registration
Register tasks based on configuration or arguments:
public static partial class Program
{
private static void Main_ConditionalTasks()
{
if (HasArgument("include-tests"))
{
Task("RunTests")
.IsDependeeOf("Build")
.Does(() => Information("Running tests..."));
}
}
}
Dynamic Task Creation
Create tasks dynamically based on configuration:
public static partial class Program
{
private static void Main_DynamicTasks()
{
var environments = new[] { "Dev", "Staging", "Production" };
foreach (var env in environments)
{
Task($"Deploy-{env}")
.Does(() => Information($"Deploying to {env}..."));
}
}
}
Multi-file Structure
When using a multi-file structure, you can organize Main_* methods across multiple files:
Main file (build.cs)
#:sdk Cake.Sdk
#:property IncludeAdditionalFiles=build/**/*.cs
var target = Argument("target", "Default");
Task("Default")
.Does(() => Information("Hello from main file!"));
RunTarget(target);
Task definitions file (build/Task.cs)
public static partial class Program
{
static void Main_PreClean()
{
Task("PreClean")
.IsDependeeOf("Clean")
.WithCriteria<BuildData>(c => c.Rebuild, nameof(BuildData.Rebuild))
.Does(() => Information("Preparing for clean operation"));
}
}
Additional task file (build/Testing.cs)
public static partial class Program
{
private static void Main_Testing()
{
Task("UnitTest")
.Does(() => Information("Running unit tests..."));
Task("IntegrationTest")
.IsDependentOn("UnitTest")
.Does(() => Information("Running integration tests..."));
}
}
All Main_* methods across all included files will be automatically discovered and executed before the top-level main code.
