Cake #load url:

One of the most underrated features in Cake is quite possibly the ability to chain multiple scripts together by using the #load preprocessor directive. I find this feature really helpful in setting up some commonly used Tasks, functions. I also make heavy use of #load ../somefile.cake to centralize and keep consistent my #addin and #tool declarations and the versions in use for each of those.

You can see we use a lot of Cake addins in our scripts, and in order to keep our CI builds sane, we want to make sure we're pinning very specific versions of each. Without this common file, it's pretty easy for versions of things to get mismatched between cake files, and for things to generally get out of hand quickly, and for CI builds to suddenly stop working when a new version of something is released.

Loading from a URL

Being able to #load arbitrary cake files is great, but as the number of repositories we use cake in grows, the need for a more centralized approach between repositories is becoming increasingly apparent.

One approach is to create a nuget package with the common cake script in it and load the nuget package (and the cake script). This is definitely fine, but it feels a little bit less agile than just fetching a file from an arbitrary url.

Instead, what I really felt I wanted was the ability to do this:

#load url:http://some.com/file.cake

BYO Cake Modules

It turns out, that Cake is architected really really well, and it's actually pretty trivial to make a custom Cake Module to introduce new functionality such as this.

In this specific case, there's an interface ILoadDirectiveProvider that I could implement in my own module. It has the following methods to implement:

bool CanLoad(IScriptAnalyzerContext context, LoadReference reference)

void Load(IScriptAnalyzerContext context, LoadReference reference)

The interface is pretty self explanatory. I just needed to check each LoadReference passed into CanLoad to decide if it was a url that could be loaded or not (this is why you'll see the url: scheme required - it makes it super easy to determine the intention of the #load directive and whether or not it is a url to be loaded).

My Load implementation ultimately just downloads the URL and saves it to the Urls path (which is ./tools/Urls/ by default), and finally tells the script analyzer context to Analyze the downloaded file.