The layers and pieces of Cake
When you don’t know the details behind a technology, it’s easy to dismiss it as magic, but if you dare to look behind the curtain — it might not be that complicated or foreign to what you already know.
In this blog post, I’ll go through some of the pieces that make up the open source build orchestration tool Cake.
I’ll show you how you can reuse pieces of Cake with different environments and even languages, I’ll go into detail on some parts and glance over others.
If there’s any part you would want me to go into more detail with please let me now! I will provide links to the examples and resources at the end of this blog post.
“It’s just C# and .Net”
This is a statement I have often made as Cake scripts provide a superset of C# — which means anything you can do in C# you can do Cake. The Cake DSL via its aliases merely provide a shortcut to make APIs and tools easier to consume, but you can always opt to like “regular” code reference assemblies, write methods and classes.
An example of this could be working with JSON, in your console or MVC app it’s likely that you would use JSON.Net to serialize/deserialize your JSON from/to .NET objects and doing the same with a Cake script doesn’t differ much from plain vanilla C#
Basically, only difference is the
#addinpreprocessor, which fetches and references an assembly from NuGet and the lack of need to wrap in the code in a namespace/class/method.
Being “just” C# and .NET truly means that you’ll always have the safety net of .NET, C# and the vast number of resources available for those pieces of technology.
The pieces of Cake
Cake is distributed in two flavors, one for the full classic .NET which also works with Mono and a version for .NET Core — the new cross platform framework/runtime from Microsoft.
The most common way to obtain the Cake tool is via the
Cake.CoreCLRNuGet packages (Cake is also available via Chocolatey, Homebrew, GitHub and official docker containers are on their way).
The NuGet packages contain all dependencies needed to execute under respective runtime, but the “Magic” is in three assemblies and a console application
Cake.exe / Cake.dll — “The Tool”
To name a few things it handles
- Compilation of scripts
- Argument parsing
- Console logging
- Composition of modules/assemblies
- Basically, it’s what glues everything together and provides the “Cake experience”
Cake.Core — “The heart”
Provides things like
- Core abstractions/interfaces/attributes used by Cake Tool/Addins/Modules
(this is the only assembly needed to reference when creating a Cake addin/module)
- DSL Parsing and transpiling/codegen to C# that Roslyn understands and can compile
- Base tool implementation (tool resolution, execution, settings etc.)
- Cake Task runtime (task runner/dependency graph/setup & teardown etc.)
Cake.Common — “The doer”
This assembly provides almost 400 convenient aliases/extension methods for things like
- Tools (MSBuild, NuGet, .NET Core CLI, code signing, etc.)
- APIs (compression, project file parsing, text templating, HTTP, etc.)
- Build system support (AppVeyor, VSTS, Jenkins, TeamCity, MyGet, etc.)
Cake.NuGet — “The Fetcher”
Cake module that enables fetching dependencies from NuGet for i.e. preprocessor directives like
Cake composition — “It’s just C# and .Net part deux”
Cake.NuGet are all available up on NuGet.org targeting both .NET Full/Desktop and .NET Core.
This means you can reference and leverage parts/most of the work and effort that’s been put into Cake with almost any .NET application, environment or platform — it’s just standard C# code and assemblies!
That said Cake relies heavily on dependency injection and has over time been refactored into custom modules — so how it all fits together can be a bit daunting for someone that’s new to the project. A rough basic example of doing this using an Inversion of Control container like AutoFac could look something like this:
As the observant might see from the comment there’s a few cases where the implementation currently resides in
Cake.exe/Cake.dll (this might be something we’ll look at refactoring in the future), the interfaces they implement exist in Core so you can implement and provide your own implementation or depending on what parts of Cake you reuse you might not need them (for unit testing we provide Cake.Testing which provides fake context and file system, environment abstractions for tool testing, etc. Unit testing Cake addins / modules might be a good topic for another blog post — please let me know if you reckon that’s the case).
Proof of concept custom script host
So, to do your own custom host for your own “build script” implementation, you currently need to implement a few interfaces:
IScriptHost , because these implementations as mentioned earlier currently resides in
Cake.exe/Cake.dll, but all other are available in
Cake.Core ready for reuse in any .NET project.
To demonstrate this, I’ve created the “Proof of concept, in no way official, don’t use in production, just to see how the sausage is made, etc.” assembly called
Cake.Bridge, compiled for both .NET and .NET Core, which means it could be used most places .NET is available today (binary is up on NuGet and source on GitHub, I’ll provide links to all resources at end of this post).
Cake.Bridge provides a static
CakeBridgeclass which provides easy access to working with the Cake task runner and the
ICakeContext (which is what all Cake aliases/methods extend), created with the goal to demonstrate an easy way to reuse Cake assemblies from any .NET language and not only with C# which is what Cake supports out of the box.
What you won’t get is anything the Cake DSL provides and no addin nor module support.
To illustrate this I’ve authored a few code snippets on how using Cake from a few different .NET languages using the
Cake.Bridgeassembly, disclaimer the code snippets are quick and rough proof of concepts to mostly prove it can be done and obviously more refinement needs to be done for them to be more idiomatically correct.
There often exist more native, prominent and widely used build systems already.
Even if perhaps mainly the sysadmin language of choice — PowerShell still is a .NET based language and its ability to utilize .NET makes it a very powerful scripting language as anything not provided native by the language often can be solved by reaching out to the framework it’s running on. It also means that you can use Cake from it:
You can’t talk about .NET languages without mentioning Visual Basic and now when it’s also joining the .NET Core party too it’s getting up to date with the times. And obviously you can Bake with Cake using some Basic (there’s no VB.NET interactive console that I know of so I created a simple VB.NET Core console app)
F# is a .NET language which makes it fully possible to use Cake with F#.
Full disclaimer though, as I’m not very proficient with the F# language and wanted something that somewhat looked like F# I called on the help and assistance of my friend Mårten Rånge to do a quick port from C# to F# and this was the result:
Cake / C#
For reference below is the script that was used for inspiration when porting to other languages/runtimes, there’s a little less bootstrapping involved but besides that they’re very similar:
Even though there might seem to be some Magic around Cake and it’s DSL it’s mostly just .NET and C#, having skills in those areas — are skills you can use with Cake scripts or when using Cake assemblies in some other way. If you have a different need from what the official supported way offers, you could still have a piece of the Cake.