In this post I will walk you through the basics of using Cake, a cross-platform build automation system with a C# DSL.
We will create a couple .NET Core projects, then create a Cake file to build, test, and package the app for deployment using Octopus Deploy tools.
Create Project Directory Link to heading
Let’s start at the beginning and create a new home for our code. I’m going to put mine under c:\code
, but you do you.
> md c:\code\HaveYourCakeAndBuildItToo
Open VS Code Link to heading
Now navigate into that directory and open VS Code.
> cd c:\code\HaveYourCakeAndBuildItToo
> code .
Install the Cake Extension Link to heading
The Cake extension for VS Code is quite simply amazing. It might be the single best reason to use Cake. It makes the experience of writing your build automation script no different than writing any other application code. It provides a set of useful commands which makes the initial setup of Cake dead easy, including a way to enable Intellisense! It also contains a bunch of helpful code snippets, CodeLens-like buttons for running specific tasks from the editor, oh and *gasp* debugging support! Yeah, you can friggin’ debug your build scripts. I love that!
So without further ado, open the Extensions sidebar (Ctrl+Shift+X
) and search for “Cake”.
Setup Your Workspace Link to heading
To get Cake setup in your project, the extension provides a single command that does just about all you need.
Open the Command Palette (Ctrl+Shift+P
) and run the command Cake: Install to workspace
.
This will:
- Install the Cake binaries into the
./tools
folder - Create two bootstrappers in the root:
build.ps1
andbuild.sh
- Create your Cake build file
build.cake
The one thing that doesn’t appear to be included in this command is adding the Intellisense support. But that’s simple enough… Just open the Command Palette again and run Cake: Install Intellisense support
. Intellisense support is provided by the Cake.Bakery
NuGet package, which you will now find in the ./tools
folder.
Ignore the Tools Folder Link to heading
We don’t want to commit any of the binaries in the ./tools
folder to our code repository. However, the Cake bootstrapper will need the packages.config
in there. If you are using Git, you can add the following to a .gitignore
file to omit everything in the ./tools
folder except the packages.config
.
While we’re at it, we’ll also go ahead and ignore the ./build
folder which we’ll be creating later.
# Build Related
tools/**
!tools/packages.config
build/**
The Cake File Link to heading
The build.cake
file is where you will define your build steps. The Install to workspace
command created this file with a basic starting point including two helpful arguments, setup and teardown methods, and a default task. Let’s take a brief look at each.
Arguments Link to heading
Arguments are specified on the command line when invoking the build script. The template starts you off with the two most commonly used arguments:
- target - tells Cake which task to run
- configuration - passed to your build tasks to identify which build configuration to use, e.g. Debug or Release
Usage:
> .\build.ps1 -target Build -configuration Debug
Setup and Teardown Link to heading
The Setup and Teardown methods will run before and after all tasks, respectively. To be honest, I haven’t really found a use for these yet. Feel free to remove them for now.
Default Task Link to heading
The default task is just a starting point. You can rename this tasks to something else, give it some actual functionality, or just use it as a pointer to another task using .IsDependentOn("OtherTask")
, which we will see a bit later.
Creating Our Application Link to heading
Obviously, we are going to need some kind of application to build. So let’s spend a little bit of time setting up our solution structure.
First, let’s add a solution file at the root of our project. This will simplify our build step, as we can specify the .sln
file instead of listing each of the individual .csproj
files to build:
> dotnet new sln
Next we’ll add a src
folder to contain all of our projects and within it we’ll create a Web API project called HaveYourApi
. Finally, we’ll add the API project to our .sln
file:
> md .\src
> dotnet new webapi -n HaveYourApi -o .\src\HaveYourApi
> dotnet sln add .\src\HaveYourApi
Create the Build Task Link to heading
Now that we have a solution to build, let’s add our first Cake task, the Build step.
Open build.cake
and add the following code (I like to add my tasks just before the Default
task, but you can put it anywhere you like):
Task("Build")
.Does(() =>
{
DotNetCoreBuild("HaveYourCakeAndBuildItToo.sln", new DotNetCoreBuildSettings {
Configuration = configuration
});
});
Notice in Visual Studio Code the run task|debug task
links above each of our tasks. You can click these to quickly build or debug your build script starting in that task, without typing a thing on the command line. Pretty neat-o!
Go ahead and click run task
and watch the build output in the VS Code terminal.
Create a UnitTests Task Link to heading
A very common task to have in a build script is one which runs all of your unit tests after the Build step. So let’s do that!
First, we’ll create an xUnit project and then add it to the solution file:
> dotnet new xunit -n HaveYourApi.Test -o .\src\HaveYourApi.Test
> dotnet sln add .\src\HaveYourApi.Test
Following the convention of using a .Test
suffix for you test projects will make it easy to match all of your test projects using a pattern in your UnitTests
task.
Let’s create that UnitTests
task now. We will do a couple of new things here:
- First, we’ll use the
IsDependentOn("Build")
method to ensure Cake has run theBuild
task first - Then we’ll use
DoesForEach()
instead ofDoes()
, passing it the list of.csproj
files for all the*.Test
projects in the solution - Finally, since we’re already depending on the
Build
task, we can telldotnet
not to bother building again
Task("UnitTests")
.IsDependentOn("Build")
.DoesForEach(GetFiles("./src/**/*.Test.csproj"), (project) => {
DotNetCoreTest(project.FullPath, new DotNetCoreTestSettings {
Configuration = configuration,
NoBuild = true
});
});
Octopus Deploy Link to heading
I use Octopus Deploy at work. And Cake makes it really easy to automate all sorts of things with Octopus. Today we will keep it pretty simple and just create an Octopus-friendly NuGet package that we can use in an Octopus deployment.
In order to create the NuGet package for Octopus we need a tool called OctoPack
. But to use this, we need the Octo.exe
command line tool. Often people will download this binary and commit it right to their repository. But Cake provides us with a couple of ways to avoid doing that, and instead get the tool at runtime from NuGet.
Option 1: Using the Bootstrapper and packages.config Link to heading
This is the recommended way to install a tool. Simply add a reference to it in the ./tools/packages.config
.
For OctopusTools this might look like:
<package id="OctopusTools" version="4.41" />
When we run the bootstrapper (i.e. build.ps1
) it will install any tools it finds in the ./tools/packages.config
.
Option 2: Using the #tool
directive
Link to heading
Cake extends the C# language with a few custom pre-processor directives. In this case, we are interested in the #tool
directive which will download a tool from NuGet and install it in the .\tools
folder.
For OctopusTools, we’d need to add the following line to the top of our build.cake
file:
#tool nuget:?package=OctopusTools
The VS Code Cake extension makes this even easier as it provides a way of searching NuGet in the Command Pallette for the available tools on NuGet, and even shows the available versions.
Install the Tool Link to heading
Pick one of the above 2 options to install the OctopusTools
. I kind of prefer Option 2 because it makes it explicit in your build file what tools you are depending on. But again, you do you.
Create An OctoPack Task Link to heading
Now that we have the OctopusTools
, and hence Octo.exe
at our disposal, let’s create the necessary task to create the package.
OctoPack doesn’t support ASP.NET Core projects directly yet. But the workaround is simple enough. First you have to publish your MVC project using dotnet publish
and then you can run OctoPack on the published files.
We will publish the HaveYourApi
project into ./build/publish
and then pack those files as a NuGet package in the ./build/pack
folder.
Task("OctoPack")
.IsDependentOn("UnitTests")
.Does(() => {
DotNetCorePublish("./src/HaveYourApi/HaveYourApi.csproj", new DotNetCorePublishSettings {
Configuration = configuration,
NoBuild = true,
OutputDirectory = "./build/publish"
});
OctoPack("HaveYourApi", new OctoPackSettings {
BasePath = "./build/publish",
OutFolder = "./build/pack"
});
});
Conclusion Link to heading
That’s it. That’s the basics. There’s plenty more to do. Make sure you try the debugging feature. And of course, commit all this to your repository to make sure we didn’t miss anything in the ignore file.
After that, your next steps would be to publish your NuGet package somewhere Octopus can find it and configure your deployment. But that is well beyond the scope of this article.
I hope you found this helpful, and got at least a little bit excited about how easy it is to work with Cake in Visual Studio Code. If you’re coming from an existing tool like psake or FAKE, I think you’ll find a lot of compelling reasons to switch to Cake, like great Intellisense support, built-in snippets, and the awesome debugging capability.
Okay, that’s all for now. Go have some Cake!