I had a use case today that required the capability to determine in Setup whether a certain target would be executed.
This is less useful for a simple script where you can see everything right in front of you. For utility code as part of a larger script or for a module, this is crucial for the reasons listed below.
The journey ended with a need for proper access inside Setup to the list of tasks about to be run. Modules are no help because they share the same limitations and even run into a few more than the script access.
Currently there is no way for a script or module to know what the run target is without resorting to using reflection to mutate the readonly compiler-generated backing fields of the host context, swapping out the ICakeEngine instance with a delegating interceptor to capture the target parameter on the RunTarget method. A module's only alternative to reflection hacks is would be to replace the cake engine without knowing what the original implementation was (https://github.com/cake-build/cake/issues/1770) which results in compatibility issues with other modules.
It's _not_ acceptable to simply assume that the argument passed to RunTarget was precisely Argument("target", "Default"). People may have other defaults, conditionals, or odd use cases. We need to know, in the context of the run that Setup is called for, deterministically, what the target was.
CakeEngine uses the internal CakeGraphBuilder and CakeGraph to obtain a sequence of CakeTasks but never exposes them. The calculation is even at the perfect place to hand them to the Setup handler.
Scripts and modules are forced to use reflection and waste cycles on duplicating the exact calculation that CakeEngine already does.
With these things in place a module would be able to implement things such as a task.BeforeDependencies(() => ... handler, as @devlead suggested.
```c#
Setup(context =>
{
if (context.TasksToExecute.Any(task => task == allTestsTask))
{
// ...
}
if (context.TasksToExecute.Last().Name == "Foo")
{
// ...
}
});
## Implementation
The change to CakeEngine is so small it's almost begging to be done: https://github.com/cake-build/cake/commit/5c7da371611863f96909dd3ab7c14a5dc962566d#diff-288fcd3a724914ae341fe456d42ca525R132
In order to expose this via `Setup(context => context.TasksToExecute`, we'll need to make an `ISetupContext` to extend the `ICakeContext` which `Setup` currently exposes. This means we'll need to make a breaking change to `IExecutionStrategy`, just like you did with teardown in https://github.com/cake-build/cake/issues/1089:
```diff
-void PerformSetup(Action<ICakeContext> action, ICakeContext context);
+void PerformSetup(Action<ISetupContext> action, ISetupContext context);
It's better to do this sooner rather than later. It allows any extension in the future to be non-breaking.
Every change made brings setup into symmetry with teardown: https://github.com/cake-build/cake/commit/5c7da371611863f96909dd3ab7c14a5dc962566d
Finally, it also seems worthwhile to consider returning the CakeTasks rather than just the strings since CakeEngine is doing this anyway. It's more user-friendly; the alternative forces people to first look up the right task via Tasks.Single(task => task.Name.Equals(x, StringComparison.OrdinalIgnoreCase)).
This commit changes IReadOnlyList<string> to IReadOnlyList<CakeTask>: https://github.com/cake-build/cake/commit/5fd3cc6aa37a7af5d617b9ab262b73c46fdb02f6
I wanted this yet again yesterday:
var testBinDirPattern = $"src/**/*.Tests/bin/{configuration}";
Task("Clean")
.IsDependentOn("Restore")
.Does(() =>
{
if (context.TasksToExecute.Any(task => task.Name == "Test"))
{
// Needed in case target frameworks change and leave stale artifacts that MSBuild doesn't clean
CleanDirectories(testBinDirPattern);
}
MSBuild("src", settings => settings.SetConfiguration(configuration).WithTarget("Clean"));
});
Task("Build")
.IsDependentOn("Clean")
.Does(() =>
{
MSBuild("src", settings => settings.SetConfiguration(configuration).WithTarget("Build"));
});
Task("Test")
.IsDependentOn("Build")
.Does(() =>
{
NUnit3(testBinDirPattern + "/**/*.Tests.dll");
});
Task("Pack")
.IsDependentOn("Test")
.Does(() =>
{
MSBuild("src/ProjectToPack", settings => settings.SetConfiguration(configuration).WithTarget("Pack"));
});
Of course, I could just always do the extra cleaning in case tests are going to be run so this isn't the best example.
I just hit this use case today as well with the use of VSTS. Currently, using the VSTS Cake task a single Cake task is being reported. Ideally, I'd during Setup I'd like to tell VSTS the individual Cake tasks that will execute then report their progress using TFBuildCommands.

This functionality is included in #2008 which hopefully will be available in 0.28.0.
@patriksvensson That's great news! I'm not laying eyes on it in https://github.com/cake-build/cake/issues/2008. What will a user's code look like which looks ahead at the list of tasks that will execute?
@jnm2 The Setup-step now provides a custom context (ISetupContext) that has a TasksToExecute property.
public interface ISetupContext : ICakeContext
{
/// <summary>
/// Gets all registered tasks that are going to be executed.
/// </summary>
IReadOnlyCollection<ICakeTaskInfo> TasksToExecute { get; }
}
Thank you, this is great! I can hardly wait!
This is all very nice! I'm wondering about the best way to get access to the TasksToExecute from inside a task. Is this the simplest approach:
IReadOnlyCollection<ICakeTaskInfo> tasksToExecute;
Setup(context =>
{
tasksToExecute = context.TasksToExecute;
});
Task("X")
{
if (context.TasksToExecute.Any(task => task.Name == "Test"))
{
...
}
}
@mdesousa Take a look at the new typed context data. This might be a good solution for your problem.
Sounds good! Is there a link to documentation or api? I'm not familiar with it...
Ah, right there in the release notes for 0.28.0: https://cakebuild.net/blog/2018/05/cake-v0.28.0-released
Thanks, I'll take a look.
Most helpful comment
This functionality is included in #2008 which hopefully will be available in 0.28.0.