Target Definitions
Inside a Build
class, you can define your build steps as Target
properties. The implementation for a build step is provided as a lambda function through the Executes
method:
- Regular Targets
- Async Targets
class Build : NukeBuild
{
public static int Main() => Execute<Build>();
Target MyTarget => _ => _
.Executes(() =>
{
Console.WriteLine("Hello!");
});
}
class Build : NukeBuild
{
public static int Main() => Execute<Build>();
Target MyTarget => _ => _
.Executes(async () =>
{
await Console.Out.WriteLineAsync("Hello!");
});
}
Async targets are just a convenience feature that allows you using async APIs in a straightforward way. Behind the scenes, they are still run synchronously.
Dependencies​
Specifying dependencies is essential to let targets run in a meaningful and predictable order. There are 3 different types of dependencies, each of them can be defined from both directions.
- Execution Dependencies
- Ordering Dependencies
- Trigger Dependencies
Define that target A
must run before target B
unless A
is skipped:
class Build : NukeBuild
{
Target A => _ => _
.DependentFor(B) // Choose this...
.Executes(() => { });
Target B => _ => _
.DependsOn(A) // ...or this!
.Executes(() => { });
}
Define that target A
runs before target B
if both are scheduled:
class Build : NukeBuild
{
Target A => _ => _
.Before(B) // Choose this...
.Executes(() => { });
Target B => _ => _
.After(A) // ...or this!
.Executes(() => { });
}
Define that target A
invokes target B
once it completes:
class Build : NukeBuild
{
Target A => _ => _
.Triggers(B) // Choose this...
.Executes(() => { });
Target B => _ => _
.TriggeredBy(A) // ...or this!
.Executes(() => { });
}
When choosing a direction, you should ask yourself which target should know about the existence of the other. For instance, should a Release
target trigger a Tweet
target? Or should a Tweet
target be triggered by a Release
target?
Dependencies between targets are ONLY defined between the individual targets and NOT through their positions in a dependency call. The following examples illustrate the difference between the partial and total order of targets:
- Partial Order
- Total Order
The execution is nondeterministic between A->B->C
and B->A->C
. This isn't necessarily problematic, but something to be aware of. In particular, it allows different targets to run in parallel (currently only in compatible CI/CD environments).
class Build : NukeBuild
{
Target A => _ => _
.Executes(() => { });
Target B => _ => _
.Executes(() => { });
Target C => _ => _
.DependsOn(A, B)
.Executes(() => { });
}
The execution is always deterministic with A->B->C
.
class Build : NukeBuild
{
Target A => _ => _
.Executes(() => { });
Target B => _ => _
.DependsOn(A)
.Executes(() => { });
Target C => _ => _
.DependsOn(B)
.Executes(() => { });
}
Conditional Execution​
Apart from skipping targets manually, you can also programmatically decide whether a target should be skipped. Depending on the use-case, you can choose between dynamic and static conditions.
- Dynamic Conditions
- Static Conditions
Define a condition that is checked right before target B
executes:
class Build : NukeBuild
{
readonly List<string> Data = new();
Target A => _ => _
.Executes(() => { /* Populate Data */ });
Target B => _ => _
.DependsOn(A)
.OnlyWhenDynamic(() => Data.Any())
.Executes(() => { });
}
Define a condition that is checked before target A
and B
execute:
class Build : NukeBuild
{
Target A => _ => _
.Executes(() => { });
Target B => _ => _
.OnlyWhenStatic(() => IsLocalBuild)
// By default, dependencies are skipped
.WhenSkipped(DependencyBehavior.Execute)
.DependsOn(A)
.Executes(() => { });
}
When a condition is not met, the skip reason is created from the boolean expression. For more complex conditions, you can extract the logic into a separate method or property to make the message more readable.
Requirements​
You can define target requirements that are checked right at the beginning of the build execution before any other targets are executed:
class Build : NukeBuild
{
Target A => _ => _
.Requires(() => IsServerBuild)
.Executes(() => { });
}
Target requirements are an important aspect to achieve a fail-fast behavior. Preceding targets won't waste any execution time only to discover that a condition that was known right from the beginning was not met.
When a requirement is not met, the exception message is created from the boolean expression. For more complex requirements, you can extract the logic into a separate method or property to make the message more readable.
Failure Handling​
Not every failing target should completely stop the build. Targets that are not essential can allow to continue the execution for other targets are important to run even if another target has failed. For these use-cases, you can configure the failure handling.
- Proceeded Execution
- Assured Execution
Define that execution continues after target A
throws:
class Build : NukeBuild
{
Target A => _ => _
.ProceedAfterFailure()
.Executes(() =>
{
Assert.Fail("error");
});
Target B => _ => _
.DependsOn(A)
.Executes(() => { });
}
Define that target B
executes even if another target fails:
class Build : NukeBuild
{
Target A => _ => _
.Executes(() =>
{
Assert.Fail("error");
});
Target B => _ => _
.AssuredAfterFailure()
.DependsOn(A)
.Executes(() => { });
}
Unlisting Targets​
It is good practice to follow the single-responsibility principle when implementing targets. However, you may not want to expose every target through the build help text. For cases like this, you can un-list a target:
class Build : NukeBuild
{
Target A => _ => _
.Unlisted()
.Executes(() => { });
}