Skip to content

How to use Cake with Rider

When it comes writing build scripts for my applications continuous integration and deployments, I am a big fan of Cake  a free and open source cross-platform build automation system with a C# DSL for tasks such as compiling code, copying files and folders, running unit tests, compressing files and building NuGet packages. I also tend to make use of Cake with Github actions.

I have previously posted about how I use cake to automate my Continuous Integration Check-In Dance. Essentially creating a script to run my build and tests for me. I also try to take this further when possible to use cake to create the entire build pipeline for my applications.

Back in 2017, I posted about How to write a Cake Build script for ASP.net core project , which for the most part is still relevant, but there have been some great improvements that have really helped simply and improve the experience in working with cake. Especially, if like me you develop .net core applications on Linux and use Rider as your IDE of choice.

Installing Cake

In my opinion this is one of the best improvements made to Cake over the years, the fact that it is now available as dotnet tool. A .NET tool is a special NuGet package that contains a console application.

The .NET CLI uses manifest files to keep track of which tools are installed as local to a directory. When the manifest file is saved in the root directory of a source code repository, a contributor can clone the repository and invoke a single .NET CLI command that installs all of the tools listed in the manifest files.

Check out How to manage .NET tools to learn more about this feature of .net, which brings the .net framework inline with all the other common software development frameworks.

There are 3 ways of installing Cake:

  • As a global tool.The tool binaries are installed in a default directory that is added to the PATH environment variable. You can invoke the tool from any directory on the machine without specifying its location. One version of a tool is used for all directories on the machine.
  • As a custom global tool in a user specified location (also known as a tool-path tool).The tool binaries are installed in a location that you specify. You can invoke the tool from the installation directory or by providing the directory with the command name or by adding the directory to the PATH environment variable. One version of a tool is used for all directories on the machine.
  • As a local tool (applies to .NET Core SDK 3.0 and later).The tool binaries are installed in a default directory. You invoke the tool from the installation directory or any of its subdirectories. Different directories can use different versions of the same tool.The .NET CLI uses manifest files to keep track of which tools are installed as local to a directory. When the manifest file is saved in the root directory of a source code repository, a contributor can clone the repository and invoke a single .NET CLI command that installs all of the tools listed in the manifest files.

Cake is a CLI tool and therefore it is assumed for the purposes of this post is that you at least have some understanding, familiarity and are comfortable using the terminal. I am also primarily a Linux based developer, so the instructions here are mostly focused on using Ubuntu, but for the most part they should apply to most operatiing systems.

Install Cake as local tool in your project

To install Cake as a local tool in your project the first step will be to enable the tool manifest file which is basically a json file which stores the locations of tools in your project. To create a tool manifest simply execute the following in a terminal window

 dotnet new tool-manifest

Then we can install the Cake.Tool globally on our machine. We will install this globally for reason which will become apparent later in this article.

dotnet tool install Cake.Tool

Cake is now ready for us to use, within your project. If you take this approach you may want to exclude the tools folder in your .gitignore file because you don't really want to commit this your version control. How to create git ignore files

Install Cake as a global tool on your system

Installing Cake as a global tool on your system is my preferred option, primarily because I tend to use Cake as my defacto CI/CD tool on all my projects.

Using this option there is no need to install the tools manifest and you simply install it using the --global switch

dotnet tool install Cake.Tool --global

How to write simple Cake Script

In general most of projects follow a kind of defacto standard directory structure most Open Source Projects tend to follow.

 src
 tests
 docs
 README.md
 solution.sln

All my CSharp project files are usually stored in the src folder. Unit and Integration test projects are stored in the tests directory and the Solution file(s) are stored in the root directory. Taken the project structure in mind we will start creating a first simple Cake Build Script. We create a text file to the our root folder and name it build.cake.

We can do this simply using the terminal command in the root of our project directory touch build.cake

src
tests
docs
README.md
solution.sln
build.cake

In this example Cake script, we want to preform a few basic actions to Clean, Restore, Build and Test our application.

In order to start developing a Cake Script we have to get familiar with the concept of Cake Tasks, and each task can have one or more actions to be executed when the task is executed. Actions are defined using the Does and DoesForEach methods, both of these methods can be chained to define more than one action per task.

Lets start with a really simple example to get famliar with the syntax. As part of the build process we want to ensure that we clean all projects removing previous versions of build outputs etc.

The  Dotnet Clean command cleans the output of the previous build. It's implemented as an MSBuild target, so the project is evaluated when the command is run. Only the outputs created during the build are cleaned. Both intermediate (obj) and final output (bin) folders are cleaned.

Cake exposes this Command as method name DotNetClean. We can define a Cake Task as follows

Task("Clean")
 .Does(() => {
   DotNetClean("./");
});

We've created our first cake task, however before we will be able to execute this task, we need to define a target that is used by cake to Run scripts.

First we'll amend our script to add the target. Which is essentially a variable we'll use and simply set it up as an Argument and define a value of Clean to it, which is the name of the Task we defined.

var target = Argument("target", "Clean");
Task("Clean")
 .Does(() => {
   DotNetClean("./");
});

We now need to instruct Cake that when it encounters this file that it should run the defined target. We use the RunTarget . The Running Targets on the Cake Documentation explains this a little more.

Essentially, what we do below is run out Target which is configured to run our clean task

var target = Argument("target", "Clean");

Task("Clean")
    .Does(() => {
    DotNetClean("./HelloCake.sln");
});

RunTarget (target);

We can execute this task in our terminal window using the command dotnet cake

This works great. However, typically what we'll most likely to do is want to chain a multiple tasks together to execute a complete build script. Lets expand out our Cake Script to include an additional Build Task that will essentially iterate through all the projects in our src folder and Build them using the Release configuration

The First step will be to define another variable which we will configure as an argument of configuration with the Release value.

var target = Argument("target", "Clean");
var configuration = Argument("configuration", "Release");

Task("Clean")
    .Does(() => {
    DotNetClean("./HelloCake.sln");
});

RunTarget (target);

We can then define a new Task we'll name Build and make it depend on the successful completion of the Clean task before it attempts to execute. It is also in this task where we'll make use of our new configuration variable.

We will also need to define some build settings which we need to pass to all our projects which contains the Release configuration information. We'll make use of additional DotNet.... variables that Cake exposes to us to enable this. We will iterate through all folders in src file and execute all .csproj files we find.

We'll make use of standard dotnet method to get these files var projects = GetFiles("./src/*/.csproj");

var target = Argument("target", "Clean");
var configuration = Argument("configuration", "Release");

Task("Clean")
    .Does(() => {
    DotNetClean("./HelloCake.sln");
});

Task("Build")
    .IsDependentOn("Clean")
    .Does(() => {
     var buildSettings = new DotNetBuildSettings {
                        Configuration = configuration,
                       };
     var projects = GetFiles("./src/**/*.csproj");
     foreach(var project in projects )
     {
         Information($"Building {project.ToString()}");
         DotNetBuild(project.ToString(),buildSettings);
     }
});

RunTarget (target);

Before we can actually successfully run these new tasks there is an additional configuration we need to do. The first is we want to create a Default Task, that actually chains all our task together and secondly we want to configure our target variable to contain the name of our Default task

var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");

Task("Clean")
    .Does(() => {
    DotNetClean("./HelloCake.sln");
});

Task("Build")
    .IsDependentOn("Clean")
    .Does(() => {
     var buildSettings = new DotNetBuildSettings {
                        Configuration = configuration,
                       };
     var projects = GetFiles("./src/*.csproj");
     foreach(var project in projects )
     {
         Information($"Building {project.ToString()}");
         DotNetBuild(project.ToString(),buildSettings);
     }
});

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

RunTarget(target);

We will once again use dotnet cake to execute the command.

More Complex Script

Using the same process above we can add additional tasks as we require. In the code below we have added additional Restore task and a task to run out Unit tests.

var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");

//////////////////////////////////////////////////////////////////////
// TASKS
//////////////////////////////////////////////////////////////////////

Task("Clean")
    .Does(() => {
    DotNetClean("./");
});

Task("Restore")
    .Description("Restoring the solution dependencies")
    .Does(() => {
           var projects = GetFiles("./**/*.csproj");

              foreach(var project in projects )
              {
                  Information($"Building { project.ToString()}");
                  DotNetRestore(project.ToString());
              }

});

Task("Build")
    .IsDependentOn("Clean")
    .Does(() => {
     var buildSettings = new DotNetBuildSettings {
                        Configuration = configuration,
                       };
     var projects = GetFiles("./src/*.csproj");
     foreach(var project in projects )
     {
         Information($"Building {project.ToString()}");
         DotNetBuild(project.ToString(),buildSettings);
     }
});


Task("Test")
    .IsDependentOn("Build")
    .Does(() => {

       var testSettings = new DotNetTestSettings  {
                                  Configuration = configuration,
                                  NoBuild = true,
                              };
     var projects = GetFiles("./tests/*.csproj");
     foreach(var project in projects )
     {
       Information($"Running Tests : { project.ToString()}");
       DotNetTest(project.ToString(), testSettings );
     }


});


//////////////////////////////////////////////////////////////////////
// EXECUTION
//////////////////////////////////////////////////////////////////////

Task("Default")
       .IsDependentOn("Clean")
       .IsDependentOn("Restore")
       .IsDependentOn("Build")
       .IsDependentOn("Test");

RunTarget(target);

Once we have created our file we can now run the build anytime we like from the terminal just by using the command that is available to us from the Cli tool we installed previously

dotnet cake

This will execute the build process, where we will clean the solution, restore the dependency build our projects and run the tests. We will also have the results printed out in the terminal window.

I find this really handy and having a repeatable and reusable build script really helps to ensure we have a consistent and reliable quality check process.

We have found that having Build Scripts available to every developer, in the core language and the same language as the project itself provides such a huge improvement in productivity because our developers never have to context shift, or even flick between different IDE's or other tools because everything they need is all in one place.

We have been able to reduce bugs and build failures because the build automation scripts are in the exact same language (C#) and location (source control) as the production system code, regular developers felt more comfortable jumping in and contributing, fixing, and extending as necessary.

We also found that by placing all build automation logic in scripts that live under the same source control as production code we leverage the tight coupling instead of fighting it.  Build automation logic will flow smoothly through branches and merges, and no one ever needs to remember to update build definition logic, across different branches etc. because all build definitions are identical.

The other key advantage we've discovered making use make-like scripts and eliminating Continuous Integration Logic is that is completely improve the developer experience . When things go wrong on a CI server with custom logic you can't set breakpoints, environmental differences are inaccessible, logging options are limited, and you frequently have to wait very long times to see the results of any changes (i.e. the build manager inner loop, to coin another phrase).

The Cake for Rider Plugin

In Januray 2021, the team behind Cake have launched an official Cake for Rider plugin which in my opinion will take Cake to the next level on developer experience. The plugin at this stage of it's evolution is still the bare bones basic, but it is still stable and ready for daily use.

After you have installed the plugin in Rider, you can quickly and easily configure it in your project, to provide facility to run your build script right in your IDE.

To do this in Rider simply go to Edit your Build Configurations.

Then you can select Cake from the configuration choices after you have clicked the + button

The you simply configure the which Cake script you want to run and provide any additional arguments etc. and Provide a name for your option

This will now enable you to run the Cake Script using the play button

Top Tip

If you have issues running the script and receive error messages. Just ensure you have installed the Cake.Tool globally
dotnet tool install Cake.Tool --global

Using Cake Tasks Window

Once you have installed the Cake for Rider plugin you may well notice an additional tab will appear on the right hand side tools options of your IDE

If you open this view you will be presented with a list of your individual Cake tasks in your script and you now have the ability to run each of these tasks individually as well, which is really handy during development if you want to run your tests on their own, or just want to kick a quick build or whatever.

Information

Please do bear in mind that at the time of writing the Cake for rider plugin was still only in 0.2.0 release, so it was still early days and there were still some glitches that need to be ironed out.

However after raising a Github Issue, the team behind the plugin were really quick to jump in and help to get it sorted.

Instead of replicating the details on this post I suggest you take peak at the issue as we did try and document the issue and fix as best we could.

Conclusion

Cake plays big role in my own CI/CD pipeline and it is also my favourite approach to writing Build Scripts, I have found that the Cake For Rider plugin really helps in not only streamlining the process but also really improves the over all User experience of Cake. I really look forward to the additional enhancements the team behind Cake For Rider implement over time.

Download it today and have a play, I'm sure you'll enjoy it is as much as I do. You may also want learn how to use Cake with Github Actions and How to create docker containers with cake

Gary Woodfine
Latest posts by Gary Woodfine (see all)