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

Allow users to customize tasks provided from TaskProvider #58836

Closed
ejizba opened this issue Sep 17, 2018 · 27 comments
Closed

Allow users to customize tasks provided from TaskProvider #58836

ejizba opened this issue Sep 17, 2018 · 27 comments
Assignees
Labels
feature-request Request for new features or functionality tasks Task system issues
Milestone

Comments

@ejizba
Copy link
Member

ejizba commented Sep 17, 2018

I'm attempting to move over to TaskProvider for our extension rather than custom shell tasks. Per these docs, the TaskDefinition provided through TaskProvider is used "To uniquely identify a task in the system", but I don't just want to "identify" the task, I want to let users customize it as well.

For example, we want to provide a task that starts the Azure Functions host before a user debugs. The launch.json would look like this:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Attach to JavaScript Functions",
      "type": "node",
      "request": "attach",
      "port": 5858,
      "preLaunchTask": "func: host start"
    }
  ]
}

And I would provide the "func: host start" task like this:

export class FuncTaskProvider implements TaskProvider {
    public async provideTasks(token?: CancellationToken | undefined): Promise<Task[]> {
        const taskDefinition: TaskDefinition = { type: 'func', command: 'host start', port: 5858 };
        const task: Task = new Task(
            taskDefinition,
            'host start',
            'func',
            new ProcessExecution('func', ['host', 'start', '--language-worker', '--', `--inspect=${taskDefinition.port}`]),
            '$func-watch'
        );
        return [task];
    }
 
    public async resolveTask(task: Task, token?: CancellationToken | undefined): Promise<Task> {
    }
}

However, as soon as I try to change the port, I get an error like this:

Error: The func task detection didn't contribute a task for the following configuration:
{
    "type": "func",
    "command": "host start",
    "port": 5959,
    "problemMatcher": [
        "$func-watch"
    ]
}
The task will be ignored.

cc @dbaeumer continuing the conversation from here: #57707. You mentioned this issue #4758, but that seems focused on prompting users. We don't want to prompt users at all - we just want to give them the ability to customize if necessary.

@vscodebot vscodebot bot added the tasks Task system issues label Sep 17, 2018
@dbaeumer
Copy link
Member

This is currently not possible. The closest duplicate is #43782.

One option would be to put the port into the settings since a task can reference settings values. But then you need to remove the port from the TaskDefinition.

@Hezkore
Copy link

Hezkore commented Jun 7, 2019

@dbaeumer
I've spent like 4 days trying to figure out how to let the user customize the provided extension tasks.
But I kept getting the "The task will be ignored." message.
It sounds insane to me that this isn't implemented yet.

Is there any way to work around this?
Could the extension perhaps provide a normal shell task.json somehow?

@dbaeumer
Copy link
Member

The problem here is if the property you want to let the user configure is a defining property of the task (e.g. it goes into the tasks key). In this case the tasks are different and there is no way to match these tasks.

However since the issue got opened VS Code has introduced the concept of a CustomTask where the extension can control everything itself.

Just to clarify: the resolveTask hook (as explained earlier) is not to change properties of a task. It is imagined to be a performance optimization so that a task provider can provide task stubs (including all arguments that make the key) and add the rest later.

Can you provide an example of what exactly you want to achieve?

@Hezkore
Copy link

Hezkore commented Jun 11, 2019

Oh I haven't seen any CustomTask example so far.
But basically what I want to do is just let the user configure the build task a lot more.
Ideally I'd let them change everything in the build task really.
Like what architecture this is building for, output dir, if threads should be supported, etc.

@dbaeumer
Copy link
Member

Actually what you want is to have a separate Process or Shell task based on task's properties. One idea would be to support access task properties as variables as we allow settings and environemt. Something like task:port. @alexr00 what do you think about this?

@ejizba
Copy link
Member Author

ejizba commented Jun 12, 2019

Just to clarify: the resolveTask hook (as explained earlier) is not to change properties of a task.

I think my biggest confusion here is that I assumed TaskProvider.resolveTask was modeled after DebugConfigurationProvider.resolveDebugConfiguration, which says the exact opposite in the first line of the description:

Resolves a debug configuration by filling in missing values or by adding/changing/removing attributes.

Couldn't you do the same thing for tasks?

@dbaeumer
Copy link
Member

The resolveTask is actually modeled after the languages API like CompletionProvider#resolve.

However I see the confusion. @EricJizbaMSFT to make things consistent we should think about a resolveTaskConfigurationthen. But I am not sure if this is necessary if we can access task properties in shell and process configurations.

@alexr00
Copy link
Member

alexr00 commented Jun 13, 2019

I think some sort of additional task property could be useful, but I'm not sure where the user should set it. It isn't something you would want to commit in a repository, so I don't think it makes sense to have in tasks.json.

@ejizba
Copy link
Member Author

ejizba commented Jun 13, 2019

The resolveTask is actually modeled after the languages API like CompletionProvider#resolve.

Fair enough, but debugConfig and tasks feel much more related and so I think it's more important for those two to have consistency. In both cases, you're providing a json configuration to a file in the .vscode folder that is usually commited to your repo. Also debug config can often rely on a task.

to make things consistent we should think about a resolveTaskConfiguration

That still confuses me. After all, DebugConfigurationProvider.resolveDebugConfiguration already has a 1:1 mapping of the word DebugConfiguration just like TaskProvider.resolveTask has a 1:1 mapping of the word Task.

But I am not sure if this is necessary if we can access task properties in shell and process configurations.

I'm not entirely following this. Can you give a more detailed example?

It isn't something you would want to commit in a repository, so I don't think it makes sense to have in tasks.json.

As discussed in my original comment, we provide a task to run the Azure Functions host on your local machine for a functions project. We've had users report that they have multiple projects in one repo and they want to run both at the same time. They immediately run into problems because of conflicting ports. It would be great if they could customize one of their tasks to have a non-default port and I think it makes sense for them to commit that change.

@dbaeumer suggested putting the port in a settings file which is a decent workaround, but it still feels more natural if the user can customize a task right in their tasks.json.

@dbaeumer
Copy link
Member

The idea of resolveTask is to be called once to avoid that a task provider need to compute all tasks available. The use case is if a user selects a configured task when VS Code just got opened. Currently we ask the task provider to resolve all task and then find the one we want to execute. The resolveTask would avoid this and only resolve that single task once.

Regarding the property. What I imagine is for a task like this:

{
    "type": "func",
    "command": "host start",
    "port": 5959,
    "problemMatcher": [
        "$func-watch"
    ]
}

port would not go into the id.

The provided process would have a shell or process command with something like this in its command func --port:${task:port} which when executed fills in the port value from the tasks.json.

I do agree to @alexr00 comment that this might not be a good user story when this is checked into the repository.

@ejizba
Copy link
Member Author

ejizba commented Jun 14, 2019

Can you guys give example scenarios and reasoning why you wouldn't want to commit this kind of thing to a repository?

The only scenario I've brought up so far is running multiple function projects in the same repo and you have to specify different ports otherwise you can't run both at the same time. This seems like something you would definitely want check in to your repo so that everyone who clones it can run both without any extra configuration needed.

@Hezkore
Copy link

Hezkore commented Jun 14, 2019

Something like ${task:port}, ${task:sourceFile} or what have you, is exactly what I want.
I'd also really like it if we could, via a command or similar, change task properties as well.
Like we can with workspace properties.

@alexr00
Copy link
Member

alexr00 commented Jun 17, 2019

@EricJizbaMSFT my concern is that if the provided value is something that users want to change, then different contributors to a project might want to have different values. I do think that having these properties on tasks is a good idea and the best options I've heard for passing properties to provided tasks.

@DanTup
Copy link
Contributor

DanTup commented Sep 21, 2020

@dbaeumer

However since the issue got opened VS Code has introduced the concept of a CustomTask where the extension can control everything itself.

Where can I find into about CustomTask? I can't see anything in the API docs. I'm trying to allow overriding of some args in tasks.json:

"tasks": [
	{
		"type": "dart",
		"command": "dartdoc",
		"args": [
			"--favicon" // This is added by the user
		],
		"problemMatcher": [],
		"label": "dart: dartdoc"
	}
]

VS Code shows a cog next to my contributed tasks that copy them to tasks.json, which I assumed was for customisation - but if I do customise them, they fail to run. Is there another way to do this? What's the cog for on the contributed tasks if not to customise the task?

@alexr00
Copy link
Member

alexr00 commented Sep 21, 2020

Documentation of custom execution tasks: https://code.visualstudio.com/api/extension-guides/task-provider#customexecution and https://code.visualstudio.com/api/references/vscode-api#CustomExecution

The cog icon is for customizing the problem matcher, run options, presentation, and other properties that exist on all tasks. Some tasks types might be able to handle you changing their "definition" properties (i.e. properties that are specific to that task type, and not provided by VS Code for all tasks).

In your case the args property is a definition property of dart type tasks. The extension that provides dart tasks doesn't handle new arg.

@DanTup
Copy link
Contributor

DanTup commented Sep 21, 2020

Documentation of custom execution tasks

Ah, I see. I don't think that helps me here. I'm using ProcessExecution and having the extension handle the execution doesn't solve the problem.

In your case the args property is a definition property of dart type tasks. The extension that provides dart tasks doesn't handle new arg.

I'm the author of the Dart extension - I'm trying to understand how to make the extension support args being customised. I added args to the taskDefinition in package.json (without it, it won't show up in completion when editing tasks.json) but when I supply it, VS code prints the message in the top comment here (no match).

Thanks!

@alexr00
Copy link
Member

alexr00 commented Sep 22, 2020

As the extension author you can implement resolveTask to allow your extension to handle any tasks of your task type that appear in the user's tasks.json but aren't returned by provideTasks. I assume your provideTasks doesn't return a task with your specific args value, so the VS Code tasks system has no idea what to do with it. If you implement resolveTask, you can provide a Task for tasks.json tasks that your provideTasks doesn't return.

Looks like the documentation doesn't outline this well. I'll update it.

@DanTup
Copy link
Contributor

DanTup commented Sep 22, 2020

@alexr00 I do have a resolve method, and I'm attaching execution details in it (it's calling the same code I use to build the execution details for the contributed task), but I still see the error above:

Screenshot 2020-09-22 at 10 34 46

Screenshot 2020-09-22 at 10 34 51

Are there any samples that already support this that I could use as a reference?

@alexr00
Copy link
Member

alexr00 commented Sep 22, 2020

The built in NPM task provider does this:

public resolveTask(_task: Task): Task | undefined {
const npmTask = (<any>_task.definition).script;
if (npmTask) {
const kind: NpmTaskDefinition = (<any>_task.definition);
let packageJsonUri: Uri;
if (_task.scope === undefined || _task.scope === TaskScope.Global || _task.scope === TaskScope.Workspace) {
// scope is required to be a WorkspaceFolder for resolveTask
return undefined;
}
if (kind.path) {
packageJsonUri = _task.scope.uri.with({ path: _task.scope.uri.path + '/' + kind.path + 'package.json' });
} else {
packageJsonUri = _task.scope.uri.with({ path: _task.scope.uri.path + '/package.json' });
}
return createTask(kind, `${kind.script === INSTALL_SCRIPT ? '' : 'run '}${kind.script}`, _task.scope, packageJsonUri);
}
return undefined;
}

You do need to new vs.Task the task that you return from resolveTask (I will document this).

@DanTup
Copy link
Contributor

DanTup commented Sep 22, 2020

@alexr00

You do need to new vs.Task the task

aha, that was it - thanks!

@ejizba
Copy link
Member Author

ejizba commented Sep 22, 2020

As the extension author you can implement resolveTask to allow your extension to handle any tasks of your task type that appear in the user's tasks.json but aren't returned by provideTasks.

@alexr00 I think this solves my original issue (although it has been a while so I don't 100% remember 😝). Unless you have some unfinished work you planned to do for this, I think it's good to close

@DanTup
Copy link
Contributor

DanTup commented Nov 10, 2020

@alexr00 has anything changed around this in the latest version? This doesn't seem to be working for me now. I've created a task.json like this:

{
	"version": "2.0.0",
	"tasks": [
		{
			"type": "dart",
			"command": "dartdoc",
			"args": [
				"foo"
			],
			"problemMatcher": [],
			"label": "dart: dartdoc foo"
		}
	]
}

There is no contributed task for this (there is one for dartdoc without any args, but not with foo). My resolveTask method always returns a new task:

public async resolveTask(task: DartTask, token?: vs.CancellationToken): Promise<vs.Task> {
	console.log(`Resolving task for ${task.definition.command} ${task.definition.args?.join(" ")}`);
	const scope: any = task.scope;
	const cwd = "uri" in scope ? fsPath((scope as vs.WorkspaceFolder).uri) : undefined;

	const newDefinition = { ...task.definition };
	const options = this.getOptions(newDefinition);

	// We *must* return a new Task here, otherwise the task cannot be customised
	// in task.json.
	// https://github.com/microsoft/vscode/issues/58836#issuecomment-696620105
	const newTask: DartTask = new vs.Task(
		newDefinition,
		task.scope || vs.TaskScope.Workspace,
		task.name,
		task.source,
		await this.createTaskExecution(this.sdks, newDefinition, cwd),
		undefined,
	);

	newTask.problemMatchers = (newTask.problemMatchers && newTask.problemMatchers.length ? newTask.problemMatchers : options?.problemMatchers) ?? [];
	newTask.group = task.group ?? options?.group;
	newTask.isBackground = task.isBackground || (options?.isBackground ?? false);

	return newTask;
}

However when I run the Run Task command and then select the Dart section, it immediately reports:

Error: The dart task detection didn't contribute a task for the following configuration:
{
    "type": "dart",
    "command": "dartdoc",
    "args": [
        "foo"
    ],
    "problemMatcher": [],
    "label": "dart: dartdoc foo"
}
The task will be ignored.

Is there anything else special about the return value from resolveTask that must match for this to work? (if this is unexpected and you want me to build an isolated repro, let me know!)

@DanTup
Copy link
Contributor

DanTup commented Nov 10, 2020

The only difference between the input task to resolveTask and the output task is the __id and _execution (which I believe is expected):

Screenshot 2020-11-10 at 10 00 57

@alexr00
Copy link
Member

alexr00 commented Nov 12, 2020

@DanTup I'm not able to repro the behavior you're seeing. I installed the Dart extension from Dart Code and copied your task. Any other steps to repro?

@DanTup
Copy link
Contributor

DanTup commented Nov 12, 2020

@alexr00 the release version doesn't have the latest code, so you'd be better cloning from https://github.com/Dart-Code/Dart-Code. You should only need to npm install to make it work - though I can try to make a small simpler repro if that'd be easier. Thanks!

@alexr00
Copy link
Member

alexr00 commented Nov 30, 2020

@DanTup any errors in the developer tools?

You must use the exact same object for the task definition, and there should be an error for that in the dev tools since it looks like you're creating a new definition.

@DanTup
Copy link
Contributor

DanTup commented Nov 30, 2020

@alexr00 there was an error in the console (and it pops up as a notification), though it seems incorrect/misleading:

Screenshot 2020-11-30 at 14 44 35

You were right though - it was because I was creating new objects for the definition. It may be useful to detect this and report a clearer error.

Thanks for the help!

@github-actions github-actions bot locked and limited conversation to collaborators Dec 4, 2020
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 tasks Task system issues
Projects
None yet
Development

No branches or pull requests

5 participants