Skip to content

How to write a Cake Build script for ASP.net core project

Ensuring your software product can be built and deployed with ease with a repeatable process is the consistent across a number of environment i.e. on a build server or on a developers laptop, is absolutely fundamental and critical success factor for your software development efforts.

Investing time and effort in this in the early days, pays enormous benefits in the long run. Having reaped the benefits in previous startups, while  also being able to experience the downsides of not doing so on other large enterprise projects.

In this article I will be discussing how to use Cake to develop your build scripts.

What is Cake ?

Cake (C# Make) is a cross platform build automation system with a C# DSL to do things like compiling code, copy files/folders, running unit tests, compress files and build NuGet packages.

CakeBuild.net

Why Cake ?

I have to be honest, my primary reason for making use of cake is that I absolutely hate using Powershell! Personally I do not find it at all intuitive, difficult to read and understand.

Over the years I have found that I am not alone and there are very few developers who actually like using Powershell.

One of the side effects of having a quasi-racist affliction to Powershell, is that unfortunately it is used in a number of Build Scripts and Build Automation routines. Therefore I have had to try and make sense of Powershell over the years

Using Cake developers no longer need to learn Powershell to write Build Automation scripts, we can now use C# to write Build Scripts!

  • Cake speaks C#
    If you are working with developers that use C#, Cake will be easier to learn. You just write C# and it uses Roslyn to run the script.
  • Developed in C# & Open Source
    You can view the source code on GitHub
  • Addins available
    A build language must be able to connect to many different types of system, it's important to have the ability to coordinate with different systems. Check out the Cake Addin Library

It is also possible to make use of Cake in a cross platform Continuous Integration/Deployment scenario. In this post I will detail the steps required to install and configure a Cake Build Server on Ubuntu 16.x.

Although Cake is admittedly easy to use, once you know how I did experience a bit of steep learning curve in getting started with it.  The project documentation is  pretty light, in this regard and for me at least there was a lot of feeling my round in the dark.  I thought I save others time but documenting as much as possible what I learned.

I thought the best way to do this would be to walk through the process of  creating a new project and then create a build process using cake. In this specific example I will be using ASP.net Core project in Visual Studio Code on ubuntu, however the process is fairly similar on any platform. I'll try explain differences as much as possible where I found them.

Advice

This post was orginally written in 2016 and there have been a number of improvements in not only the dotnet framework but also Cake. Many of the basics may still apply but it will also probably be worth checking out How to use Cake with Github Actions where I discuss a few more current features and capabilities

Install Mono

Although not necessay to build ASP.net core, cake does make use of the of Mono to run two executables i.e. Nuget.exe and Cake.exe.

sudo apt install mono-complete -y 

Start new project

We'll open Visual Studio Code in a new folder , in my instance I have called this cakedemo

Use the Cake Bootstrapper

The Build Process is as important as source control,  (you wouldn't start writing code without at least creating a git repository would you ? 🙂 )

Therefore we'll make a start creating our build scripts, despite having anything to build yet.  In VS Code open the command pallete  (shift + ctrl + p)  or View -> Command Pallette, and search for Cake.

Selecting the Cake Bootstrapper will then display another two options. A bootstrapper  for builds on Windows making use of PowerShell or build.ps1 ,   and one for Linux making use of BASH  or build.sh.

We may as well add both just to give us greater flexibility going forward.  We may have have some developers working on Windows machines and some on either Linux or Macs. We may also decide at a later stage we may want to change our Build Agent or build server platform.  Having both options available helps us make the switch easier.

After we finished adding both our Project Explorer view should now contain 2 files.

The two files we have just added, essentially are entry points into our Build Process.  If we open one of the files, in my case it will be the build.sh file,  ( yes I hate PowerShell that much I never want to read it!)  and quickly examine it.  I won't go into too much detail but will hopefully touch on the areas of most interest.  The file is also commented which does help to understand it a lot quicker.

Add gitignore file

Lets go ahead and add out .gitignore  file to our project and exclude the tools folder from being commited to our repository.  The Bootstrapper will ensure that the folder and tools are download on whichever environment it executes so there is no need to store them.

create a new file .gitignore

Create build.cake file

Another job the build.sh file does is call the build.cake file, which will build your project.  This is a file that we will develop ourselves using C# that will carry out all the steps we would like to execute during our build process.

Create a new file build.cake

Once the file has been created we can now start writing all the plumbing code we will need.  The code below is purely how I like to lay this out.  This is by no means a complete file, and I am not entirely sure if this is how more seasoned cake users would lay out the file, but it works for me now, and I am absolutely positive that I will change my mind in the future, but it's good enough to start with.

#tool nuget:?package=NUnit.ConsoleRunner&version=3.4.0
//////////////////////////////////////////////////////////////////////
// ARGUMENTS
//////////////////////////////////////////////////////////////////////
 
var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");
 
 
//////////////////////////////////////////////////////////////////////
///    Build Variables
/////////////////////////////////////////////////////////////////////
var binDir = "";       // Destination Binary File Directory name i.e. bin
var projJson = "";     // Path to the project.json
var projDir = "";      //  Project Directory
var solutionFile = ""; // Solution file if needed
var outputDir = Directory("") + Directory(configuration);  // The output directory the build artefacts saved too
 
var buildSettings = new DotNetCoreBuildSettings
     {
         Framework = "netcoreapp1.1",
         Configuration = "Release",
         OutputDirectory = outputDir
     };
//////////////////////////////////////////////////////////////////////
// TASKS
//////////////////////////////////////////////////////////////////////
 
Task("Clean")
    .Does(() =>
{
    if (DirectoryExists(outputDir))
        {
            DeleteDirectory(outputDir, recursive:true);
        }
});
 
Task("Restore")
    .Does(() => {
        DotNetCoreRestore(projDir);
    });
 
Task("Build")
    .IsDependentOn("Clean")
    .IsDependentOn("Restore")
    .Does(() => {
	
	if(IsRunningOnWindows())
    {
      // Use MSBuild
     // MSBuild(solutionFile , settings => settings.SetConfiguration(configuration));
	 
	   DotNetCoreBuild(projJson, buildSettings);
    }
    else
    {
      // Use XBuild
     DotNetCoreBuild(projJson, buildSettings);
     
    }
    });
 
Task("Package")
    .IsDependentOn("Build")
    .Does(() => {
        var packSettings = new DotNetCorePackSettings
        {
            OutputDirectory = outputDir,
            NoBuild = true
        };
 
         DotNetCorePack(projJson, settings);
 
    });
 
 
//////////////////////////////////////////////////////////////////////
// TASK TARGETS
//////////////////////////////////////////////////////////////////////
 
Task("Default")
    .IsDependentOn("Package");
 
//////////////////////////////////////////////////////////////////////
// EXECUTION
//////////////////////////////////////////////////////////////////////
 
RunTarget(target);

Reviewing the file you'll notice it is fairly straight forward to grok.  It's mostly easy to understand C#  mostly consisting cake based API calls

Task( "[Some Name you want to call the task]" )
    .Does(() => {
  // whatever action you want the task to do
});

If you have a task that depends on the outcome of another task, you simply use the fluent syntax to .IsDependentOn

Task("SomeTask")
    .IsDependentOn("AnotherTask")
    .Does(() => {
 
//Do Something
 });

Add our project

We have a basic build process, but we haven't got anything to build yet. So lets go ahead and create an empty project and build it.

First, lets create a Folder in root of project folder and call it src . Then open the integrated terminal window and change directory into the src folder.

I will generate a new Asp.net core Web API, using yo aspnet. In my case I called my project demoapi

Once the the application has been generated. We won't do the usual tasks of running dotnet restore or dotnet build because we are going to use our build.cake file to do that for us.

We now need to just need to add some detail to our build.cake, about the project we want to build. Essentially I am just going to add values to my variables.

var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");
var projDir = "./src/demoapi/";
var binDir = String.Concat(projDir,"bin" ) ;
var projJson = String.Concat(projDir , "project.json");
var solutionFile = "";
var outputDir = Directory(binDir) + Directory(configuration);

Once this complete, we can just change back to a project root folder where our build files are cd .. and then execute the build.sh

Provided we have provided all the correct information to the variables in our build file, we should see a successful build.

Conclusion

Now that we have a basic build working and a minimal structure of our build file in place, enabling us to extend our build file to include executing our unit tests and any other additional features we would like to implement.

We can now also commit our project to our version control and make use of the build file on out Continuous Integration server , like TeamCity, Travis , Jenkins etc. I will discuss how we can do this in a forthcoming blog posts.

Additional Resources & References

Gary Woodfine
Latest posts by Gary Woodfine (see all)