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

TaskProvider: resolveTask() never gets called #33523

Closed
LaurentTreguier opened this issue Aug 30, 2017 · 29 comments
Closed

TaskProvider: resolveTask() never gets called #33523

LaurentTreguier opened this issue Aug 30, 2017 · 29 comments
Assignees
Labels
feature-request Request for new features or functionality on-testplan tasks Task system issues
Milestone

Comments

@LaurentTreguier
Copy link

  • VSCode Version: 1.15.1
  • OS Version: Fedora 26 (x86_64)

Steps to Reproduce:

  1. Create a new extension
  2. Register a taskProvider that provides incomplete tasks in provideTasks() and completes them in resolveTask
  3. Launch the extension and list tasks in the command palette

Here is a minimal example:

'use strict';

import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {
    let provider = vscode.workspace.registerTaskProvider('test', {
        provideTasks: () => [new vscode.Task({ type: 'test' }, 'test', 'source')],
        resolveTask: (task: vscode.Task) => {
            task.execution = new vscode.ShellExecution('echo Hello');
            return task;
        }
    });

    context.subscriptions.push(provider);
}

export function deactivate() {
}

Reproduces without extensions: Yes

As I understand, resolveTask() should be called to complete incomplete tasks just like with code lenses: provideCodeLenses() can return incomplete code lenses that will be later completed upon calling resolveCodeLens().
However, resolveTask() never seems to be called at any point, and if provideTasks() returns incomplete tasks, these tasks will simply not get listed when trying to list all available tasks.
Am I missing something on how resolveTask() is supposed to work, or is there a bug here ? I've tried looking at other task providers, but every single one of them only use provideTasks().

@vscodebot vscodebot bot added tasks Task system issues new release labels Aug 30, 2017
@dbaeumer dbaeumer added the *question Issue represents a question, should be posted to StackOverflow (VS Code) label Aug 31, 2017
@dbaeumer dbaeumer added this to the August 2017 milestone Aug 31, 2017
@dbaeumer
Copy link
Member

@LaurentTreguier the API is currently in for a feature we would like to provide in the future. The idea is not that an extension can provide an incomplete task and that the task can be resolved later. Could you provide an example where that would be valueable.

The idea of the feature is to resolve tasks configuration defined in tasks.json without calling provideTasks first.

@LaurentTreguier
Copy link
Author

So the feature is simply not yet implemented ? The fact that I can't get it to work makes more sense now 😅...
So if I understand correctly, in the future provideTasks() would get called when no tasks.json file is available, and resolveTask() when it is available so that the resolved task can make use of the information provided in tasks.json ?

@dbaeumer
Copy link
Member

dbaeumer commented Sep 1, 2017

Yes, that is the idea. The reason being is that fetching all tasks takes a while and if a task is customized in the tasks.json we could resolve it without fetching all tasks.

@dbaeumer dbaeumer modified the milestones: September 2017, On Deck Sep 22, 2017
@dbaeumer
Copy link
Member

I spent quite some time of making task multi folder aware. I will postpone that one for September.

@TheRealPiotrP
Copy link

@dbaeumer have you had time to return to this issue? It seems that this API will be VSCode's mechanism to enable users to provide customizations for detected tasks. I'm working on enabling the taskProvider API in the C# extension and it is important for us to enable user-driven customization [e.g. selecting a build configuration like debug|release]. It would be great to know when this is coming so we can better plan our next steps!

@dbaeumer
Copy link
Member

@TheRealPiotrP having resolveTask() being called is not a prerequisite to allow users to further customize contributed tasks. This is already working today. Calling resolveTask() is only necessary for future performance improvements where a user disabels task auto detection completely and still wants to customize a task for example contributable by C#.

Can you explain in an example what currently isn't working for me better understand what is missing for the C# team.

@TheRealPiotrP
Copy link

@dbaeumer thanks! I think I may have jumped to a solution instead of focusing on the problem I'm trying to solve, so let's do a slight reboot.

In the 0.1.0 tasks.json format, .NET tasks looked like this:

    "tasks": [
        {
            "taskName": "build",
            "command": "dotnet build",
            "type": "shell",
            "group": "build",
            "presentation": {
                "reveal": "silent"
            },
            "problemMatcher": "$msCompile"
        }

This is largely boilerplate, so I'm glad we're able to reduce it with the Tasks API. However, a nice feature of this format was that we could guide users to selectively modify the command property for things like build configurations. So we could ask them to change it to:

    "command":"dotnet build -c Release",

My expectation with task customization is that I should be able to expose a custom property on my task e.g. Configuration and allow the user to modify that property. However, I don't see the data flow by which this customization would make it back to our extension and allow us to augment the generated command. The only wire-up point that I've identified for Tasks is vscode.workspace.registerTaskProvider which only pulls for Tasks without providing any data. I also didn't see anything in the example that clearly points to interactions with tasks.json.

How should I be accessing info coming from tasks.json?

@dbaeumer
Copy link
Member

dbaeumer commented Nov 1, 2017

@TheRealPiotrP

The flow would be like this:

  • Task > Configure Task
  • User would select the dotnet build task
  • VS Code adds a stub for your task into the task.json. I don't know which format the TaskDefinition has for your task, so this might be not exactly what is inserted:
{
    "type": "dotnet",
    "....": "...."
}

The user can then edit that stub and add a command
"command": "dotnet build -c Release"

When executed the command property will overwrite the one provided by the task provider. All properties can be customized that way.

Alternatively you can even contribute a Release and a Debug task so that the user sees both in the dropdown list.

Since the Task list is now MRU having more tasks in there is not really hurting.

@TheRealPiotrP
Copy link

@dbaeumer what is the purpose of the additional properties we can expose in the task definition? Can I not read these back inside of our extension?

@dbaeumer
Copy link
Member

dbaeumer commented Nov 2, 2017

@TheRealPiotrP actually I don't understand your question. May be you can give me an example. Contributing tasks need a definition the package.json of how the format of the TaskDefinition looks like. There is an example here showing how that works: https://github.com/Microsoft/vscode-extension-samples/tree/master/task-provider-sample and the definition in the package.json is here: https://github.com/Microsoft/vscode-extension-samples/blob/master/task-provider-sample/package.json#L18

Do you have already code that contributes a dotnet task. If yes I will have a look to see what might be the problem.

@TheRealPiotrP
Copy link

TheRealPiotrP commented Nov 3, 2017

@dbaeumer sure.

Consider the example you linked:

"taskDefinitions": [
  {
    "type": "rake",
    "required": ["task"],
    "properties": {
      "task": {
      "type": "string",
      "description": "The Rake task to customize"
      },
      "file": {
      "type": "string",
      "description": "The Rake file that provides the task. Can be omitted."
      }
    }
  }
]

This defines a task with two custom properties:

  • task
  • file

And, upon running Task > Configure Task they are reflected in tasks.json similar to:

{
    "type": "Rake",
    "task": "doSomething",
    "file": "my/rake.file"
}

So now I have this task configuration in tasks.json. My question is what can I do with it? As an extension author, I don't see any path by which the Rake extension can be informed about these configurations in tasks.json. Is there such a data channel?

In other words, what happens if I modify the task in my tasks.json to be:

{
    "type": "Rake",
    "task": "doSomething",
    "file": "my/other/rake.file"
}

My expectation as a user is that the task will now act upon the new file path. However, as an extension author I don't see how I will find out that the user changed the value of the file property.

Given that you have gone to some length to enable these custom properties, I'm sure that they are valuable tools... I'm just don't know how to make use of them. When I looked at the sample, I assumed the intent was similar to this:

"taskDefinitions": [
  {
    "type": "dotnet",
    "required": ["task"],
    "properties": {
      "task": {
      "type": "string",
      "description": "The dotnet task to customize"
      },
      "projectFile": {
      "type": "string",
      "description": "The csproj file to provide to to the task."
      },
      "Configuration": {
      "type": "string",
      "description": "Debug | Release."
      }
    }
  }
]

and that Task -> Configure Task would produce:

{
    "type": "dotnet",
    "task": "build",
    "projectFile": "my/project.csproj",
    "configuration": "Debug"
}

Now I could instruct the user to replace Debug with Release and my task would somehow get access to the configuration change.

Based on this conversation I think my understanding is simply wrong... so I'd like to understand the real intent for these additional task properties.

@dbaeumer
Copy link
Member

dbaeumer commented Nov 3, 2017

@TheRealPiotrP The current model is a merge model base on the tasks provided by the task provider. So in the rake example the task engine would search for a task provided by the rake extension that has the properties

{
    "type": "Rake",
    "task": "doSomething",
    "file": "my/rake.file"
}

and take it as a base for the task to be executed. Any properties specified in the tasks.json for that file overwrite the properties provided by the task provider. So for example if the tasks.json contains

{
    "type": "Rake",
    "task": "doSomething",
    "file": "my/rake.file"
    "presentation": {
            "reveal": "never"
     }
}

It will overwrite the presentation.reveal setting with "never" which usually is "always".

So in you case the dotnet task provider needs to provide all customizable tasks.

The resolve hook is more an optimization that a task provider doesn't need to provide all tasks on request if a task configured in the tasks.json is executed. But if a user starts blank (which is the default) it is more convenient for the user if the task provider provides all possible tasks. Otherwise he would need to know how to start with a stub in the tasks.json file.

@LaurentTreguier
Copy link
Author

@dbaeumer but where is there any possibility of actually using the overridden task ? There is only one place in the code where a ShellExecution object can be provided to VSCode, and that's when creating the default tasks. After the tasks have been overridden in tasks.json, there is no way of providing a new ShellExecution object that will take the customizations into account...

@dbaeumer
Copy link
Member

dbaeumer commented Nov 3, 2017

The task engine does this. Tasks provided by a task provider are internally translated into the same object format structure the task from a tasks.json file are transformed to and then merged. So you can only override properties. If you for example want to provide a new command the user needs to provide the full command in the tasks.json file.

Usually users only want to override presentation properties or attach problem matchers. So this is an easy way to do so without letting the user type the task in the tasks.json file in the first place. I agree that for more advanced cases a resolve is desirable with is why we added it (but to calling it yet)

@TheRealPiotrP
Copy link

@dbaeumer thanks for the detailed explanation of where we are today!

It sounds like an API for enabling user-extension communication via Tasks.json is not immediately on the roadmap. However, according to the documentation it looks like I may be able to interrogate tasks.json a bit more directly. Unless you feel this is contrary to where VS Code is headed, I'm going to experiment with something like this:

  • I will have two categories of fields in my taskDefinition
    • Primary Key: fields which allow me to identify that a given tasks.json entry matches an extension-generated task. This will likely be the task verb, build for instance, and a relative path to the project/sln that will be built.
    • Configuration: fields which the user can use to override task behavior. In our case this will include the Build Configuration, such as Debug || Release. It will also include the Target Framework, such as netcoreapp2.0 vs. net46.
  • When discovering tasks, I will try to use the aforementioned configuration API to identify all of our task definitions in tasks.json and map them to generated tasks using the Primary Key fields. For any hits I will override our default configuration prior to generating the command line.

Why go to such trouble? We have a coupling between our launch configurations and our task definitions. Our prebuild task in launch.json will trigger a build, but that build's output varies depending on the aforementioned configuration properties. Today users are forced to modify the command in the task to change our configuration, then to modify the path in launch.json to match. This has led to many task.json/launch.json pairs which are both coupled and non-standard. The data in both of these artifacts evolves over time in response to the user's own decisions as well as in response to the evolution of the extension/runtime. It is very difficult for us to properly define and test the multitude of scenarios that emerge. This is further complicated when the hints we get regarding user intent are in the form of commands. To keep things in sync, our extension would need to become a very good command line parser which can map this string to our configuration prior to reasoning about them.

By moving our configuration into well-known fields we can take a step towards simplifying the problem, both for ourselves and for our users.

One additional task ahead of me is the rationalization of data stored in launch.json vs. tasks.json. I've not had much time to invest in launch.json, but it appears that the new configuration API is a map function that converts objects from the extension into a launch.json. This suggests the launch.json is intended to be a persistent artifact. Tasks.json, on the other hand, seems to be becoming more virtualized: users can still generate the file for customization, but will be able to happily run tasks without it. This suggests that I should actually be inspecting the launch.json file for configuration, and that I should perhaps even create a special build for launch task which will travel down that path.

I'm sharing a bunch of context here because I've had a hard time finding other folks' opinions on how best to use the new APIs and I hope this input may be useful both to the VS Code team and to other extension contributors!

@dbaeumer
Copy link
Member

dbaeumer commented Nov 6, 2017

@TheRealPiotrP having these additional fields in tasks.json makes perfect sense and we do the same for the TypeScript tasks. The TaskDefinitions have an option property to determine whether the build is a normal build or a watch build.

		{
			"type": "typescript",
			"tsconfig": "tsconfig.json",
			"option": "watch",
			"problemMatcher": [
				"$tsc-watch"
			]
		}

If you encounter problems with that approach please let me know.

@g-arjones
Copy link
Contributor

Is this already implemented? Thanks!

@dbaeumer
Copy link
Member

No work has started here yet.

@llgcode
Copy link

llgcode commented Jan 17, 2018

Interested in this feature too.
Our extension is not able to list all the possible task list (too huge), but it can list "boiler plate" task that the user can customize later for his purpose

@fcurts
Copy link

fcurts commented May 23, 2018

I spent a day trying to make sense of this API until I found this issue. Leaving aside hacks, I don't see how custom task properties are useful without resolveTask() being called.

A somewhat related issue I encountered is that a provided task is not shown in "Run Task..." if its scope is set. I have no idea why.

More extensive documentation for the task API in general and registerTaskProvider() and contributes.taskDefinitions in particular would be very helpful.

I wonder if integrations of external cli tools that don't require parsing a build script are better implemented as commands? Couldn't find any information on this either.

@dbaeumer
Copy link
Member

@fcurts agree with the documentation. I created microsoft/vscode-docs#1624 this week :-) and we do maintain an example here: https://github.com/Microsoft/vscode-extension-samples/tree/master/task-provider-sample

Regarding resolveTask: the idea here is that we can resolve a task added to the tasks.json without call the task provider before hand. The idea is not to call the method before a task is executed. We have a different issue for this.

A somewhat related issue I encountered is that a provided task is not shown in "Run Task..." if its scope is set. I have no idea why.

Can you provide more details for this.

@g-arjones
Copy link
Contributor

Probably this: #40515

@fcurts
Copy link

fcurts commented May 24, 2018

Yes, except that I see the same behavior (task not shown) for vscode.TaskScope.Workspace. Only way I've found for the task to be shown is to avoid setting the scope property and avoid using the constructor that sets it.

I'd be grateful if somebody could explain how to choose between implement sth. like "run javadoc tool" as task vs. command and what the purpose of task parameters is given that they can't be accessed during task execution (or perhaps I just need to avoid shell execution?).

@dbaeumer
Copy link
Member

@fcurts yes, you are affected by #40515. Tools in 99% shouldn't run in the global especially if they rely on files in the workspace. Reason is multi root workspaces. If you have a workspace with two root folders a task like run javadoc tool must express on which folder it would like to run since it affects variable resolving and the working directory of a task. If both folders are Java project you would basically provide two tasks one for folder One and one for folder Two.

A global task would for example be Install javadoc tooling but as said they are very rare this is why I didn't put higher priority onto #40515.

So you should actually use this constructor: https://github.com/Microsoft/vscode/blob/master/src/vs/vscode.d.ts#L4751 and pass the workspace folder the task should run on. You might want to look here for an example on how to do that: https://github.com/Microsoft/vscode/blob/master/extensions/gulp/src/main.ts#L143

@joelday
Copy link
Contributor

joelday commented Mar 19, 2019

@dbaeumer Would you take a PR for this? I believe this is the behavior. Let me know if I'm wrong.

In cases where provideTasks is currently called on a given provider:

  • If there is no matching task
    • create a new Task object with the definition
    • call resolveTask.
    • If resolveTask returns a Task
      • assume its execution has been configured and continue as if the Task came from provideTasks.
    • Or, if resolveTask returns undefined
      • continue with the current behavior.

What should happen if resolveTask throws?

jgranick added a commit to openfl/lime-vscode-extension that referenced this issue Mar 21, 2019
@dbaeumer
Copy link
Member

@joelday yes, that is the expected behavior. If resolveTask throws it should continue if as undefined got returned.

@joelday
Copy link
Contributor

joelday commented Mar 23, 2019

@dbaeumer @alexr00 Thanks! I'll have a first-pass PR later today most likely.

@alexr00
Copy link
Member

alexr00 commented Jul 3, 2019

FYI for anyone looking at this issue: resolveTask does not allow you to pass additional input to tasks. If you want that, checkout #58836.

@alexr00
Copy link
Member

alexr00 commented Jul 3, 2019

Thank you @joelday for implementing resolveTask! I have already updated the built-in gulp extension, but the other built-in task providers need some love now too.

If anyone is interested, there are other built-in extensions that should properly implement resolveTask now:
#76518
#76519
#76520
#76521

Finally, we should create a setting to only use tasks.json for performance: #76522

@alexr00 alexr00 closed this as completed Jul 3, 2019
@alexr00 alexr00 modified the milestones: On Deck, July 2019 Jul 3, 2019
@alexr00 alexr00 added feature-request Request for new features or functionality and removed *question Issue represents a question, should be posted to StackOverflow (VS Code) labels Jul 8, 2019
@alexr00 alexr00 mentioned this issue Jul 29, 2019
3 tasks
@vscodebot vscodebot bot locked and limited conversation to collaborators Aug 17, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
feature-request Request for new features or functionality on-testplan tasks Task system issues
Projects
None yet
Development

No branches or pull requests

8 participants