Skip to content

Provide access to the run target and ordered list of tasks #1772

@jnm2

Description

@jnm2

Motivation

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.

No access to the run target

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 (#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.

No access to the final resolved dependency path

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.

Future extensibility

With these things in place a module would be able to implement things such as a task.BeforeDependencies(() => ... handler, as @devlead suggested.

Usage

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: 5c7da37#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 #1089:

-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: 5c7da37

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>: 5fd3cc6

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions