Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
jnm2 opened this issue Aug 30, 2017 · 11 comments
Closed

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

jnm2 opened this issue Aug 30, 2017 · 11 comments
Labels
Milestone

Comments

@jnm2
Copy link
Contributor

jnm2 commented Aug 30, 2017

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

@jnm2
Copy link
Contributor Author

jnm2 commented Nov 3, 2017

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.

@tannerwatson
Copy link

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.

image

@patriksvensson
Copy link
Member

This functionality is included in #2008 which hopefully will be available in 0.28.0.

@jnm2
Copy link
Contributor Author

jnm2 commented Apr 30, 2018

@patriksvensson That's great news! I'm not laying eyes on it in #2008. What will a user's code look like which looks ahead at the list of tasks that will execute?

@patriksvensson
Copy link
Member

patriksvensson commented May 23, 2018

@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; }
    }

@jnm2
Copy link
Contributor Author

jnm2 commented May 23, 2018

Thank you, this is great! I can hardly wait!

devlead added a commit to devlead/cake that referenced this issue May 28, 2018
* Releates to cake-build#2008, cake-build#1594 and cake-build#1772
* Changes Setup from ICakeContext to ISetupContext
* Adds target/initating task as TargetTask on ISetupContext
* Adds typed context WithCriteria CakeTaskBuilder extension methods
* Adds integration tests for ISetupContext/TData Setup and typed WithCriteria
devlead added a commit to devlead/cake that referenced this issue May 28, 2018
* Relates to cake-build#2008, cake-build#1594 and cake-build#1772
* Changes Setup from ICakeContext to ISetupContext
* Adds target/initating task as TargetTask on ISetupContext
* Adds typed context WithCriteria CakeTaskBuilder extension methods
* Adds integration tests for ISetupContext/TData Setup and typed WithCriteria
@mdesousa
Copy link

mdesousa commented Jun 2, 2018

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"))
    {
    ...
    }
}

@patriksvensson
Copy link
Member

@mdesousa Take a look at the new typed context data. This might be a good solution for your problem.

@mdesousa
Copy link

mdesousa commented Jun 2, 2018

Sounds good! Is there a link to documentation or api? I'm not familiar with it...

@mdesousa
Copy link

mdesousa commented Jun 2, 2018

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.

@lonix1
Copy link

lonix1 commented Aug 19, 2022

Sorry to resurrect an old thread, but does someone know how to do this for a "Frosting" project? i.e. to get access to ISetupContext.TasksToExecute.

It is exposed in FrostingTaskLifetime<T>.Setup but that is before each task. I can't find an equivalent for FrostingLifetime<T>.Setup which is before all tasks. Maybe I'm looking in the wrong places?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants