Introducing Cake Bridge Dependency Injection

Utilize Cake abstractions and addins using Microsoft dependency injection

Published on Tuesday, 12 January 2021

A couple of years ago I blogged Dispelling the magic!, a post explaining the internals of the Cake build orchestration tool, with that post as a proof of concept I created Cake.Bridge assembly which provided an easy way from any .NET language get access to Cake abstractions and addins from a single instance static class.

The Need

Cake has some real nice testable abstractions around working with the filesystem, processes, tools, etc. and for a .NET project, we had just that need. But a static instance isn't very testable, and for most of our .NET projects (console apps, APIs, Azure Functions, WPF, etc.) we now use dependency injection using Microsoft.Extensions.DependencyInjection.

So with that in mind, I created an extension method to IServiceCollection that easily provided me a way to get a IFileSystem injected and utilizing the FakeFileSystem provided by Cake.Testing for testing. That grew into supporting most core Cake types including the more complex ICakeContext and IScriptHost.

Introducing Cake.Bridge.DependencyInjection

If we got a need, chances are someone else has it too, so I've open-sourced the code and made it available as a NuGet package. It's early bits tailored for a specific need, so you should expect some rough edges, but sharing is caring.

Usage

Obtain

The assembly is published at NuGet.org/packages/Cake.Bridge.DependencyInjection.

.NET CLI

dotnet add package Cake.Bridge.DependencyInjection

PackageReference

<PackageReference Include="Cake.Bridge.DependencyInjection" Version="0.1.0" />

Register

using Cake.Bridge.DependencyInjection;
...
serviceCollection
    .AddCakeCore();

Resolve

using Cake.Core;
...
var cakeContext = serviceProvider.GetRequiredService<ICakeContext>();

Use

Once registered you can now via dependency injection access the majority Cake.Core interfaces with ease, i.e:

  • ICakeContext - Gives access to Cake built-in and addin aliases, and most Cake abstractions.
  • IScriptHost - Gives access to script/task runner.
  • ICakeLog - Cake logging implementation.
  • IFileSystem - Cake file system abstraction.

Example ICakeContext

using Cake.Bridge.DependencyInjection;
using Cake.Core;
using Cake.Core.Diagnostics;
using Microsoft.Extensions.DependencyInjection;

var serviceCollection = new ServiceCollection()
    .AddCakeCore();

var serviceProvider = serviceCollection.BuildServiceProvider();

var cakeContext = serviceProvider.GetRequiredService<ICakeContext>();

cakeContext.Log.Write(
    Verbosity.Normal,
    LogLevel.Information,
    "Hello World!");

will output

Hello World!

Example IScriptHost and Cake.Common

Cake.Common contains Cake aliases normally ships together with the Cake script runner, when using Cake Bridge you'll need to add it to you project (same for any Cake addin).

.NET CLI

dotnet add package Cake.Common --version 1.0.0-rc0002

PackageReference

<PackageReference Include="Cake.Common" Version="1.0.0-rc0002" />

then add the appropriate using statements and you can achieve something very similar to a Cake "script"

using Cake.Bridge.DependencyInjection;
using Cake.Core;
using Cake.Common.Diagnostics;
using Cake.Core.Scripting;
using Microsoft.Extensions.DependencyInjection;

var serviceCollection = new ServiceCollection()
    .AddCakeCore();

var serviceProvider = serviceCollection.BuildServiceProvider();

var scriptHost = serviceProvider.GetRequiredService<IScriptHost>();

scriptHost.Task("Hello")
    .Does(ctx => ctx.Information("Hello"));

scriptHost.Task("World")
    .IsDependentOn("Hello")
    .Does(ctx => ctx.Information("World"));

await scriptHost.RunTargetAsync("World");

will output

========================================
Hello
========================================
Hello

========================================
World
========================================
World

Task                          Duration
--------------------------------------------------
Hello                         00:00:00.0226275
World                         00:00:00.0002682
--------------------------------------------------
Total:                        00:00:00.0228957

Complete example project

A full example console application using Spectre.Console demonstrating usage of both ICakeContext and IScriptHost can be found in project's GitHub repository at src/Cake.Bridge.DependencyInjection.Example.

Resources