Category Archives: cake

Extending Cake – creating a Cake extension method

There may come a time when cake’s existing commands do not do everything you want, so being that the cake DSL is basically C# we can easily create our own code.

In this post I’m going to create a DLL which is pretty standard but we’ll make it so we can fit into the cake ecosystem by making it an alias, this will make it simple to use and also give us access to the ICakeContext for logging etc..

First let’s see how it’ll be used inside cake

#r "./tools/SvnVersioning.dll" 

Task("Version")
    .Description("Generates a version based upon current SVN checkin")
    .Does(() =>
{
    // svnPath can be null if svn.exe is accessible via path
    var svnPath = @"C:\svn\svn-win32-1.6.0\bin\";
    var current = MakeAbsolute(Directory("."));
    var version = GetCurrentVersion(svnPath, current.FullPath);
    Information("Version: {0}", version);
});

Now if we create a class library (named SvnVersioning as uses in the above script).

We need to add a reference to Cake.Core to allows us access to the ICakeContext and cake attributes. Here’s the code for the GetCurrentVersion method (again, as can be seen in use in the script above)

public static class SvnVersionAliases
{
   [CakeMethodAlias]
   public static string GetCurrentVersion(this ICakeContext context, string svnPath, string path)
   {
      var REVISION_HEADING = "Last Changed Rev:";
      var revision = "10101";
      try
      {
         context.Log.Information("Solution path {0}", path);
         context.Log.Information("Starting svn {0}", svnPath);

         var svn = "svn.exe";
         if (!String.IsNullOrEmpty(svnPath))
         {
            svn = svnPath + @"\" + svn;
         }

         var proc = new Process();
         var procInfo = new ProcessStartInfo(svn)
         {
            CreateNoWindow = true,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            Arguments = "info " + path
         };

         proc.StartInfo = procInfo;
         proc.Start();
         var output = proc.StandardOutput.ReadLine();

         while (!String.IsNullOrEmpty(output))
         {
            if (output.StartsWith(REVISION_HEADING))
            {
               revision = output.Replace(REVISION_HEADING, String.Empty).Trim();
            }
            output = proc.StandardOutput.ReadLine();
         }

         //revision is restricted to 65535 - and svn is already past this!
         //so mod by 10000!
         revision = (Convert.ToInt32(revision) % 10000).ToString();
         return revision;
      }
      catch (Exception ex)
      {
         context.Log.Information(ex.Message);
         return revision;
      }
   }
}

The CakeMethodAlias simply marks the code as a method alias, we can also add CakeAliasCategoryAttribute on method of class which is used for documentations of methods/properties. The CakeNamespaceImportAttribute is used to “hint” about additional namespaces that are required by the alias.

Getting started with Cake

Before I start getting into cake usage, I want to just list some problems I had during the Getting Started process. I’m not blaming cake for these, but just wanting to put these issues out there in case others hit the same.

  • It does state in the Getting Started page, “NOTE: If downloading the latest zip file, ensure that it is unblocked otherwise you will run into issues when attempting to execute Cake.” and I forgot this step initially. If you do not unblock before unzipping, you’ll end up with all sorts of Powershell security problems.
  • NuGet.exe needed to be located on your machine (not a big deal) and copied to the Tools folder – I couldn’t find this mentioned in the Getting Started, so not quite sure wther this was supposed to be in the example zip or downloaded manually or whatever
  • When I eventually got cake to run I kept hitting this error “Error: Failed to install tool ‘NUnit.ConsoleRunner’.”. The .cake script specifies that NUnit.ConsoleRunner version 3.4.0 is required, but this continually failed to work. I ran NuGet list NUnit.ConsoleRunner and found that the package didn’t exist. When I ran NuGet sources I noticed nuget api v2 was disabled, enabling this using NuGet.exe source Enable -Name “https://www.nuget.org/api/v2/” solved this. I’m not sure why cake was unable to use nuget.org (v3) which was enabled, but this solved the problem for me.

Make, Rake, Fake, Cake…

So first there was make, the good old (and complicated to understand) C/C++ build tool, then there were others ant/nant etc. Then along comes fake, the F# build tool and now cake the C# build tool. When I say F# or C#, I simply mean they use the syntax of F# or C# as opposed to nant’s (for example) XML, for creating the build etc. process.

The Basics

I’m going to solely be discussing running the cake executable against a .cake script here, not a PowerShell ps1 file hence you needn’t have to use PowerShell to try these commands out.

By default, if you run cake without supplying a specific .cake file (the extension can be anything you prefer if specifying the cake file name), cake will try to run build.cake.

A .cake file will have one or more Task’s (these are analogous to Target’s in nant and strangely cake has the command RunTarget to run the tasks). The task ofcourse can have some meaningful name, such as Build, Clean, Deploy (or whatever your preference). Let’s look at a simple task from Cake’s example

Task("Clean")
    .Does(() =>
{
    CleanDirectory(buildDir);
});

In the above we have a task named Clean and this runs the code CleanDirectory, which is a DSL method to, as I’m sure you guessed, clean the supplied folder. The buildDir is declared in the example cack file as

var buildDir = Directory("./src/Example/bin") + Directory(configuration);

We can also compose multiple tasks using the DSL’s IsDependentOn method, such as

Task("Restore-NuGet-Packages")
    .IsDependentOn("Clean")
    .Does(() =>
{
    NuGetRestore("./src/Example.sln");
});

So now, when the Restore-NuGet-Packages task is run cake will first run the Clean task.

You’ll need to specify some form of command line argument to allow you to run one of these tasks, i.e. to pass a string from the command line into your cake script. This is done by declaring a line like this

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

in this case we’ve declared a target command line switch with the default value of Default (which obviously you’ll need to create a matching task name for). To run this command line using cake, you use a single hyphen such as

.\tools\Cake\Cake.exe --verbosity=Verbose -target=Clean

Note: the Cake exe command line switch uses –, whereas a command line to be passed into the cake script, uses a single -.

I’m not going to cover the DSL here, for that, go check out the Cake Reference.