You don’t need Cake anymore; the way to build .NET projects going forward.

The perfect void

Early on in the .NET realm, developers got creative with their build processes. Some used MSBuild proj files (like me, eww), some used Ruby's Rake system, etc, etc. Feel free to share what you used, before Cake, in the comments. I'm super curious!

The .NET community hadn't landed on a "recommended" approach. Then, in 2014 (when the first NuGet package was published), Cake came on the scene and that changed. It filled a perfect void.

  • .NET developers didn't need to learn Make/Bash/PowerShell/etc, all they needed was C#!
  • Only one simple script is needed (aside from bootstrapping), executed with a simple command.
  • Cross platform.
  • Any .NET library could be referenced, bringing an immense amount of power to the build process.

Because .NET developers were starved for something that felt like home, they (rightfully, IMO) were willing to power through the rough edges the Cake brought to the table.

Cake became the "go to" for build processes. The community grew, plugins were built, and all was happy!

Here comes .NET Core

But as with all great tools, the technology around them catches up. Enter, .NET Core:

  • .NET Core is available on all platforms.
  • The .csproj format is now simplified, making executing simple a Program.cs file as simple as dotnet run.
  • Because it's .csproj, you can reference any .NET Standard library.

Many of the pain points that .NET developers experienced before Cake are now addressed directly in the toolchain, but there is one thing that Cake does well that cannot be replaced by .NET Core alone, targets.

Since .NET Core has been released, many projects have attempted to fill this new void.

  • Bullseye - A .NET library for describing and running targets and their dependencies. Used in conjunction with SimpleExec, it can be really powerful.
  • NUKE - Build Automation System for C#/.NET

Using Bullseye and SimpleExec, your Program.cs (similar to build.cake) would look as follows:

using static Bullseye.Targets;
using static SimpleExec.Command;

namespace Build
{
    static class Program
    {
        static void Main(string[] args)
        {
            Target("clean", () =>
            {
                Run("rm", "-r ./output");
            });
            
            Target("build", () =>
            {
                Run("dotnet", "build");
            });
            
            Target("publish", DependsOn("clean"), () =>
            {
                Run("dotnet", "publish -o ./output");
            });
            
            RunTargetsAndExit(args);
        }
    }
}

All the goodness of targets, but with a regular old .NET project supported by any IDE/debugger!

Now this is rather rudementary, but NUKE takes this multiple steps even further. It is pretty much in feature-parity with Cake in terms of the functionality it provides. I'll leave it to you to see for yourself, if you wish.

But who cares? Why not Cake?

Cake has it's problems that any task runner/lib could have, but there is one large class of issues that simply will not happen with the others (NUKE, Bullseye, etc) because of one thing, preprocessing.

  • The *.cake files aren't valid C# files. That means that cake.exe must interpret them, dynamically compile them, and then execute them. When you do this, you loose support for the standard toolchain (dotnet), and built in IDE support (with debugging!). Wouldn't you want to open your build projects in Visual Studio? Launch a debugger with F5?
  • In order to run cake.exe, you need to bootstrap your build, which can fail for various reasons.
  • The DSL for Cake also has it's own dependency resolver, which is also a common source of failure/fustrations.

Take a look at all the reported issues of Cake. While you peruse each issue, ask yourself, "is this issue a result of the preprocessor?"

These class of issues are entirely moot when you use something like NUKE since you are just dealing with a standard .NET Core console application, like any other. The preprocessor made sense back in the day when msbuild was a mess, and when developers just wanted a single file with a single command. But those days are over and .NET Core is here.

I believe the maintainers of Cake recognized this disadvantage when .NET Core initially came on the scene, and started the Frosting project. My memory says that this project had been de facto abandoned, however, it has had some recent commits. Either way, they aren't promoting it. Maybe someone can enlighten me here? Regardless, I wish it had picked up more stream than it did.

Another thing is that a common theme within the Cake ecosystem is to wrap every shelled command into a fluent/typed interface. But why? Consider the two following examples.

NpmInstall(settings => settings.AddPackage("gulp").InstallGlobally());
npm install gulp -g

Do you really need to make the npm command type-safe? Which one looks nicer? What happens if the wrappers don't wrap a certain flag? What if your version of npm doesn't match the supported version that the wrappers shell out to? Is it really so hard to just invoke npm directly? Take a look at the issues associated with this wrapper, and think to yourself, "how many of these issues are a result of my insisting on keeping things type safe?"

Counter arguements

But Cake has an extensive amount of modules/plugins!

Yes, it does. There is an extensive amount of projects integrated into Cake. However...

Do you need a plugin? Consider the npm plugin/wrapper mentioned above. You could do with out it.

Are there CLI alternatives for what you are doing? There likely is. Consider the Cake.Slack integration and it's CLI alternative, slack-cli. Or, consider using a pure .NET lib to integrate with the Slack API directly (see here). I mean, this is just a .NET project, reference your libs and get on with your business!

And here is a less productive counter arguement. Why does Cake.Slack even exist? Why can't the users of Cake just reference the C# API directly in a Cake target? What exactly does Cake.Slack provide as an integration? IMO, it's a useless merging of two unlrelated concepts. The Cake ecosystem is full of examples like this.

But my build system is really complex, Cake helps!

I've heard this before, but it simply doesn't make sense.

Have you ever been writing a C# project and thought to yourself "I wish I was using Cake right now, then I could do X". Probably not. But you've likely been in a build.cake and thought the opposite!

Take a look at FakeItEasy or Qml.Net for some more complicated scenarios.

It works great for me, I'm comfortable with it.

I'd argue that because of the preprocessor it burdens you with, unless it provides strategic value that you couldn't do with out, you shouldn't use it. Think about the others on your team that aren't comfortable with it. You'd be just as comfortable writing a regular C# project. Even more so, considering you'd have complete IDE and debugger support!

Conclusion

Most of the things that people attribute to Cake are things they do in Cake, and not because of Cake. When all the smoke and mirrors are gone, Cake is just a target runner with a preprocessor. The "plugins" and "features" most people refer to are mirrages.

What feature of Cake is an absolute must for you? I'd really like to hear some examples that don't have an equally viable approach using pure .NET libs or shelling out to a command. I'd also wager that most CLI/lib alternatives would be more supported than what you'd get with a Cake plugin/module.

I know, I know, "to each his own". You are right. I can't argue with that if that is where the arguement stops.

I'm also aware that many people have spent many hours on Cake. I'm not in any way disparaging them. I've had many nice things to say about it, even in this post. But all great tools get obsoleted with time. Don't take it personal.

What are your thoughts?


Comments

Are Bullseye and NUKE mutually exclusive? Would I use one or the other? Would I use both together? The article makes it sound like you would use all three (NUKE, Bulls, SimpleExec)

They are mutually exclusive. I'm sorry I wasn't more clear. I was trying to list multiple alternatives to Cake.


Option 1

NUKE:

  • Targets
  • Process running
  • ...and a lot more (than you'd probably need).

Option 2

Bullseye

  • Targets

SimpleExec

  • Process running

Bullseye and SimpleExec are a perfect fit to be used together.

Awesome, thanks for clarifying and replying so quickly.

I also want to plug this here https://github.com/cake-build/frosting by the cake-build team.

It works similar to NUKE but it's more coreesq in the way that it's an actual host.

Its V0.x.* so id regard it as experimental. Even though its been around for a good while now.

I've asked the devs of Cake what it's status of Frosting is. No word. It seems inactive to me.

Also, I mentioned this in my post.

I believe the maintainers of Cake recognized this disadvantage when .NET Core initially came on the scene, and started the Frosting project. My memory says that this project had been de facto abandoned, however, it has had some recent commits. Either way, they aren't promoting it. Maybe someone can enlighten me here? Regardless, I wish it had picked up more stream than it did.

I wish that too :( it was before its time.

Thanks for the great article. Totally agree.

smaudet commented Sep 10, 2019  (edited)

Seems like there are still commits as of last month...on that frosting project...

Cake's runtime is heavy and I don't like the fact that it's pseudo C#, no... but then again I really don't like to script in C#. Such a heavy language to script in at all.

I'd offer a hypothesis why they're not promoting Frosting - they are probably too busy supporting their mainline and don't have a clear migration strategy (yet) (it also looks like its a one man-op so that's why they're not busy figuring our their marketing and product strategies - nobody to market or product plan).

"You'd be just as comfortable writing a regular C# project. Even more so, considering you'd have complete IDE and debugger support!"

Ugh, no? I don't like re-implementing everything from scratch? Better msbuild support is always welcome, but if its too opaque (which it will probably become over time as the msbuild devs "abstract" functionality away in a misguided effort to make things "simpler") then its no good and something like Cake will be needed/reborn anyways.

Syntax completion and IDE support can be provided by proper language support for a language. Maybe Cake's DSL isn't necessary, I'd prefer to see e.g. a Python or JS based DSL for invoking C# targets. I don't really care what it is, I just don't care to re-implement the world every time.

Cake's runtime is heavy and I don't like the fact that it's pseudo C#, no... but then again I really don't like to script in C#. Such a heavy language to script in at all.

What other language would you like to use? Standard Makefile? bash?

I don't like re-implementing everything from scratch? Better msbuild support is always welcome, but if its too opaque (which it will probably become over time as the msbuild devs "abstract" functionality away in a misguided effort to make things "simpler") then its no good and something like Cake will be needed/reborn anyways.

The future is the dotnet cli. MSBuild will always be a pain. And if you need to use it, you still don't need Cake. That's like saying "I need to hang this picture frame, so I need a sledge hammer". NUKE has a good abstraction around MSBuild, and it doesn't require you to buy-in to the pseudo C# to use it.

I sense a recurring theme:

I just don't care to re-implement the world every time.

What exactly do you mean by this? What exactly would you need to re-implement that the .NET framework doesn't provide for you? You have System.Diagnostics.Process for shelling out CLI stuff, System.IO for directory cleaning and file modifications, etc.

Cake's runtime is heavy and I don't like the fact that it's pseudo C#, no... but then again I really don't like to script in C#. Such a heavy language to script in at all.

What is the meaning of a light language?

Join the discussion at GitHub