I started to use Cake (www.cakebuild.net) a while ago and once I had gone through all the basis and got something working in my build.cake file, I wanted to extract those features to put them into external cake files to finally move them into a NuGet package. After many tries and errors, I finally figured it out and decided to share it on TheFutureOfCode.

Assumptions

1) The build.cake file

Let’s create a build.cake file that will make all the calls we need for our future NuGet package. This script allows you to restore, build and run tests. It also defines a way to automatically find your solution file if not provided on the command line when calling the bootstrapper (build.ps1 or build.sh):

#load nuget:?package=TheFutureOfCode.Build.Cake

var target = Argument<string>("Target", "Default");
var configuration = Argument("Configuration", "Release");
FilePath solutionFile = Argument<FilePath>("SolutionFile", null);

Task("Default").IsDependentOn("UnitTests");

Task("Initialize")
    .Does(() =>
{
    solutionFile = GetSolutionFile(Context, solutionFile);
});

Task("Restore")
    .IsDependentOn("Initialize")
    .Does(() => 
{ 
	NuGetRestore(Context, solutionFile); 
});

Task("Build")
	.IsDependentOn("Restore")
	.Does(() => 
{
	Build(Context, solutionFile, configuration, "Build");
});

Task("UnitTests")
	.IsDependentOn("Build")
	.WithCriteria(() => BuildSystem.IsLocalBuild)
	.Does(() =>
{
	RunUnitTestsUsingXUnit(
		Context,
		configuration);
});

RunTarget(target);

Some considerations must be observed in this build.cake file:

  1. Each task is calling functions that we will define later into the NuGet package called TheFutureOfCode.Build.Cake. This allows any Build Master to let developers work around the main part of the build process, without compromising their freedom.
  2. There is no version on the NuGet package that we load. It’s intentional if you end up with many build.cake files and you need to update all of them at once. You want them to use the latest version of the package.
  3. You need Cake version 0.24.0 and up to have the #load directive understand your NuGet call correctly.

2) The NuGet package

The NuGet package can consist of the number of files you need. In this example, I only use one, but in other projects I have been working on, I have many more files. To build your NuGet package, you need to follow the rules defined by NuGet:

  • Put your cake files into the tools directory.
  • Use a nuget spec to create your spec file and edit it.

My spec file looks like this:

<?xml version="1.0"?>
<package >
  <metadata>
    <id>TheFutureOfCode.Build.Cake</id>
    <version>1.0.0.0</version>
    <authors>The Future Of Code</authors>
    <owners>The Future Of Code</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Cake scripts used to build any product.</description>
    <releaseNotes>
      1.0.0.0: First version of package TheFutureOfCode.Build.Cake.
    </releaseNotes>
    <copyright>Copyright 2018</copyright>    
  </metadata>
</package>

I “hide” my functions into a file called Tasks.cake:

#tool nuget:?package=xunit.runner.console&version=2.3.1

public static FilePath GetSolutionFile(ICakeContext context, FilePath solutionFile)
{
	if (solutionFile == null)
	{
		// Look for local solution file.
		IEnumerable<FilePath> slnFiles = context.Globber.GetFiles("*.sln");
		if (slnFiles.Count() == 1)
		{
			context.Log.Information($"Solution that will be loaded: {slnFiles.First().ToString()}");
			return slnFiles.First();
		}
		else
		{
			if (slnFiles.Count() == 0)
			{
				throw new System.IO.FileNotFoundException($"No solution file found.");
			}
			else
			{
				foreach(FilePath sln in slnFiles)
				{
					context.Log.Information($"Solution found: {sln.ToString()}");
				}
				throw new System.IO.IOException($"More than one solution file were found. Please specify which one to use.");				
			}
		}		
	}
	context.Log.Information($"Solution that will be loaded: {solutionFile.ToString()}");
	return solutionFile;
}


public static void NuGetRestore(ICakeContext context, FilePath nugetFileToRestore)
{
	context.StartProcess(
		"nuget",
		 new ProcessSettings {
			Arguments = new ProcessArgumentBuilder()
				.Append("restore")
				.Append(nugetFileToRestore.ToString())
			});
}

public static void Build(ICakeContext context, FilePath fileToBuild, string configuration, string buildTarget)
{
	context.MSBuild(fileToBuild,
		settings => settings.SetConfiguration(configuration)
							.WithTarget(buildTarget)
	);
}

public static void RunUnitTestsUsingXUnit(ICakeContext context, string configuration)
{
		context.XUnit2($"**/bin/{configuration}/*Test*.dll");
}
Some considerations must be observed while looking at this Tasks.cake file:
  • To have each Cake API call working, you must call the desired function on the ICakeContext!
  • I have not used the NuGet API call from Cake, since I wanted to use the version 4 of NuGet which was not supported at the time of writing this article.

3) Create your package, publish it and build your project

The only things left is to pack your package and push it to a repository (here is my publish.sh that I use on my mac):

#!/usr/bin/env bash

nuget pack 
nuget push *.nupkg -source Local
rm -f *.nupkg

Then run the Cake bootstrapper (build.sh or build.ps1) in your project, and it should automatically download your newly created NuGet package. You don’t have to add anything to the tools/package.config file to specify the version of your NuGet package since the #load nuget: command will take care of getting the package correctly.

I wish you a very good cake!

References