Getting Started with DotNET Core Cake Runner
Create custom builds for your .NET applications using Cake: a free open-source task runner for cross-platform automation in C#. Learn how with MailSlurp!
Cake is a free open-source task runner for writing custom cross-platform builds stages for .NET applications.
MailSlurp uses Cake to build CSharp SDKs for its free email API. In this tutorial we will show you how to create Cake files and a dotnet tool manifest in order to create custom builds and tasks in your .NET project.
What is Cake?
Cake is the Make of C#. It's a cross-platform build automation system with a C# DSL that makes it easy to compile code, copy files, run tests, build packages, collect code coverage and more on Linux, Mac, and Windows - using the same tools for each.
Cake is not intended to replace dotnet test
commands or your CI pipelines in Circle or TeamCity. Cake is designed for developers so they can run common tasks on any machine within an organization and get the same results. If you have ever developed a NodeJS application it is a bit like package.json
scripts.
An example of a task runner in another environment, NodeJS:
{
"scripts": {
"test": "jest"
}
}
Why Cake? (Why not Make?)
Cake might seem unusual if you are used to Visual Studio or Resharper for .NET development but Cake adds the possibility of a central file for common tasks that can result in deterministic builds across different machine types. In plain english, Cake lets you write common tasks such as running tests, collecting code coverage, zipping files etc. in a automated way when you may have performed them manually using an IDE in the past. It also helps to add a common build system to cross-platform apps that may run on Windows, Linux and Mac.
Why not Make? Make is the original inspiration for Cake and is used extensively in the Linux world. Make however isn't bundled with Windows and uses a syntax that can be unfamiliar for many developers. Using Cake lets you write tasks in C# and include regular Nuget packages like you would in a DotNET project.
Writing a Cakefile
You can install Cake as a dotnet tool locally using a tool manifest. This means versions and dependencies for your tools are stored in code.
Create a tool manifest
First create a tool manifest in your DotNET project:
dotnet new tool-manifest
This will create a new .config
directory and inside that will be a dotnet-tools.json
file.
{
"version": 1,
"isRoot": true,
"tools": {
}
}
Add Cake to your manifest
Next we want to install Cake as a tool:
dotnet tool install Cake.Tool --version 1.1.0
For demonstration purposes let's also add dotnet-format
to let use format code.
dotnet tool install dotnet-format`
Restore dependencies
Now that we have added tools our .config/dotnet-tools.json
file should look like this:
{
"version": 1,
"isRoot": true,
"tools": {
"cake.tool": {
"version": "1.1.0",
"commands": [
"dotnet-cake"
]
},
"dotnet-format": {
"version": "5.1.225507",
"commands": [
"dotnet-format"
]
}
}
}
To install tools on a new machine run:
dotnet tool restore
This will read the tool manifest and install any missing tools on your machine. It is good practice in an organization to add these setup steps to a README.md
file like so:
# My Project
## Install
`dotnet tool restore`
Simple example
Create a new file called build.cake
in the root directory of your project. The main concepts in Cake are targets and tasks. One defined tasks in a build file and targets them in the command line. For instance here is a simple Cake file that builds a solution:
var target = Argument("target", "Report");
var configuration = Argument("configuration", "Release");
Task("Build")
.Does(() =>
{
DotNetCoreBuild("./MySolution.sln", new DotNetCoreBuildSettings
{
Configuration = configuration,
});
});
The build task can be invoked in a command line by running:
dotnet cake
Or you can specify the target explicitly using:
dotnet cake --target=Build
Creating custom Cake tasks
We can use Cake to turn tasks that might typically involve clicking through submenus in an IDE into jobs that can be run on any platform using a command line. This includes continous integration pipelines like Jenkins and Azure. For an example let's create a Cake file that can collect code coverage using coverlet and generate a report to display the percentage of lines covered by our tests in the terminal.
Install coverlet and report generator
In your test solution add the coverlet package - this will orchestrate tests to collect code coverage. Code coverage defines how many lines of code your tests test for. Code coverage is a useful way to measure how well your application is tested.
dotnet add package coverlet.collector
Add tasks to your Cake file
Now we can define a Cake file to run our build, tests, code coverage and output the results in the command line with one simple command:
dotnet cake
The Cake file looks like this (see each comment):
// add package directives
#tool nuget:?package=ReportGenerator&version=4.8.8
#addin nuget:?package=Cake.FileHelpers&version=4.0.1
#r "Spectre.Console"
// import spectre for colored console output
using Spectre.Console
// define a default target
var target = Argument("target", "Report");
var configuration = Argument("configuration", "Release");
// define a build task
Task("Build")
.Does(() =>
{
DotNetCoreBuild("./project.sln", new DotNetCoreBuildSettings
{
Configuration = configuration,
});
});
// define a test task that includes coverlet arguments
Task("Test")
.IsDependentOn("Build")
.Does(() =>
{
var testSettings = new DotNetCoreTestSettings
{
Configuration = configuration,
NoBuild = true,
ArgumentCustomization = args => args
.Append("--collect").AppendQuoted("XPlat Code Coverage")
.Append("--logger").Append("trx")
};
var files = GetFiles("./*.{sln,csproj}").Select(p => p.FullPath);
foreach(var file in files)
{
DotNetCoreTest(file, testSettings);
}
});
// generate a text summary report from the cobertura coverage collected during test
Task("Report")
.IsDependentOn("Test")
.Does(() =>
{
// generate coverage report
var reportSettings = new ReportGeneratorSettings
{
ArgumentCustomization = args => args.Append("-reportTypes:TextSummary")
};
var coverageDirectory = Directory("./reports");
var files = GetFiles("./**/TestResults/*/coverage.cobertura.xml");
ReportGenerator(files, coverageDirectory, reportSettings);
// print summaries to console
var summaries = GetFiles($"{coverageDirectory}/Summary.txt");
foreach(var file in summaries)
{
var summary = FileReadText(file);
AnsiConsole.Markup($"[teal]{summary}[/]");
}
});
// add a format task
Task("Format")
.Does(() =>
{
var projects = GetFiles("./*.csproj}").Select(p => p.FullPath);
foreach(var project in projects)
{
DotNetCoreTool($"dotnet-format {project}");
}
});
RunTarget(target);
Task output
Running the above Cake file will print code coverage to the terminal. This is great for cross platform tooling or for CI builds. The output looks like so:
2021-06-08T19:55:17: Report generation took 1.4 seconds
Summary
Generated on: 8/06/2021 - 7:55:17 p.m.
Parser: MultiReportParser (21x CoberturaParser)
Assemblies: 1
Classes: 174
Files: 160
Line coverage: 88.8%
Covered lines: 2064
Uncovered lines: 259
Coverable lines: 2323
Total lines: 5850
Extending Cake and next steps
Cake is a highly underated tool that lets DotNET teams create custom build tasks that run cross-platform and IDE-independent. There are many Cake extensions available for AWS, GitHub, NuGet, Azure and more. You can even create your own!
We use Cake at MailSlurp to build and deploy our Email APIs. You can use MailSlurp to unlimited free test email accounts. Send and receive emails from tests or application using intuitive CSharp SDKs. Check it out!