Understanding the target dependency differences between Cake and NUKE
In the past couple of years I’ve worked quite a bit with Cake, but since about a year I started working with NUKE. Apart from the differences in syntax, most things are pretty similar in both systems. However, there was one thing which I didn’t realize and eventually cost me some time to understand why something wasn’t working as intended. Obviously I would have known this if I would have read the NUKE’s fundamentals documentation upfront thoroughly.
IsDependentOn() and DependsOn()
You might know that in Cake you can use the IsDependentOn()
method to define a dependency on another target in your target definition. In NUKE, you have a similar method, DependsOn()
, which also allows you to specify a dependency on another target. Here’s the catch: in NUKE the dependency is solely specified at individual targets. Let’s take a look at the following NUKE build class:
class Build : NukeBuild
{
public static int Main() => Execute<Build>(x => x.Compile);
Target Compile => _ => _
.Executes(() => { });
Target Deploy => _ => _
.DependsOn(Compile)
.Executes(() => { });
Target InstallService => _ => _
.DependsOn(Deploy)
.Executes(() => { });
Target StartService => _ => _
.Executes(() => { });
Target FullDeploy => _ => _
.DependsOn(InstallService, StartService);
}
When I ran the FullDeploy
target, I expected the execution order to be Compile
, Deploy
, InstallService
, StartService
. The output shows you this is not the case:
═══════════════════════════════════════
Target Status Duration
───────────────────────────────────────
StartService Executed < 1sec
Compile Executed < 1sec
Deploy Executed < 1sec
InstallService Executed < 1sec
───────────────────────────────────────
Total < 1sec
═══════════════════════════════════════
Since the dependencies are only specified at individual targets, this does not guarantee that InstallService
and its dependencies will run before StartService
. It specifies that FullDeploy
has a dependency on both InstallService
and StartService
, but it doesn’t mean that the given parameter sequence matches the execution order, including their dependencies.
Before() and After()
If you want dependencies to run in a certain order, you can use the Before()
and After()
methods on your targets. For instance, in the above example, we want the StartService
target to run after InstallService
when it runs in conjunction with other targets in the FullDeploy
target. This can be achieved in two ways:
Target InstallService => _ => _
.DependsOn(Deploy)
.Before(StartService)
.Executes(() => { });
or
Target StartService => _ => _
.After(InstallService)
.Executes(() => { });
The first example makes sure that when InstallService
runs together with StartService
, InstallService
executes before StartService
. The second example has the same result but defines it the other way around: when StartService
runs together with InstallService
, StartService
executes after InstallService
.
Summary
If you’re using DependsOn()
in NUKE and also have targets that depend on other targets, make sure to check if you need to specify in which order targets need to run by using Before()
or After()
. This subtle but important difference between Cake and NUKE can lead to unexpected behavior which can be hard to debug.