Skip to content

Commit f5dcab2

Browse files
test
Signed-off-by: Roman Nikitenko <rnikiten@redhat.com>
1 parent 5e6582d commit f5dcab2

1 file changed

Lines changed: 174 additions & 7 deletions

File tree

code/extensions/che-commands/src/taskProvider.ts

Lines changed: 174 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ interface DevfileTaskDefinition extends vscode.TaskDefinition {
1717
command: string;
1818
workdir?: string;
1919
component?: string;
20+
commandId?: string;
2021
}
2122

2223
export class DevfileTaskProvider implements vscode.TaskProvider {
24+
private execTaskById = new Map<string, vscode.Task>();
25+
private compositeConfigById = new Map<string, { name: string; commandIds: string[]; parallel: boolean }>();
2326

2427
constructor(private channel: vscode.OutputChannel, private cheAPI: any, private terminalExtAPI: any) {
2528
}
@@ -35,15 +38,42 @@ export class DevfileTaskProvider implements vscode.TaskProvider {
3538
private async computeTasks(): Promise<vscode.Task[]> {
3639
const devfileCommands = await this.fetchDevfileCommands();
3740

38-
const cheTasks: vscode.Task[] = devfileCommands!
39-
.filter(command => command.exec?.commandLine)
41+
const localCommands = devfileCommands!
4042
.filter(command => {
4143
const importedByAttribute = (command.attributes as any)?.['controller.devfile.io/imported-by'];
4244
return !command.attributes || importedByAttribute === undefined || importedByAttribute === 'parent';
4345
})
44-
.filter(command => !/^init-ssh-agent-command-\d+$/.test(command.id))
45-
.map(command => this.createCheTask(command.exec?.label || command.id, command.exec?.commandLine!, command.exec?.workingDir || '${PROJECT_SOURCE}', command.exec?.component!, command.exec?.env));
46-
return cheTasks;
46+
.filter(command => !/^init-ssh-agent-command-\d+$/.test(command.id));
47+
48+
this.execTaskById.clear();
49+
const execTasks: vscode.Task[] = localCommands
50+
.filter(command => command.exec?.commandLine)
51+
.map(command => {
52+
const task = this.createCheTask(
53+
command.exec?.label || command.id,
54+
command.exec?.commandLine!,
55+
command.exec?.workingDir || '${PROJECT_SOURCE}',
56+
command.exec?.component!,
57+
command.exec?.env,
58+
command.id
59+
);
60+
this.execTaskById.set(command.id, task);
61+
return task;
62+
});
63+
64+
this.compositeConfigById.clear();
65+
const compositeTasks: vscode.Task[] = localCommands
66+
.filter(command => (command as any).composite?.commands?.length)
67+
.map(command => {
68+
const composite = (command as any).composite;
69+
const name = composite?.label || command.id;
70+
const commandIds = composite?.commands || [];
71+
const parallel = Boolean(composite?.parallel);
72+
this.compositeConfigById.set(command.id, { name, commandIds, parallel });
73+
return this.createCompositeTask(name, commandIds, parallel, command.id);
74+
});
75+
76+
return [...execTasks, ...compositeTasks];
4777
}
4878

4979
private async fetchDevfileCommands(): Promise<V1alpha2DevWorkspaceSpecTemplateCommands[]> {
@@ -56,7 +86,14 @@ export class DevfileTaskProvider implements vscode.TaskProvider {
5686
return [];
5787
}
5888

59-
private createCheTask(name: string, command: string, workdir: string, component: string, env?: Array<V1alpha2DevWorkspaceSpecTemplateCommandsItemsExecEnv>): vscode.Task {
89+
private createCheTask(
90+
name: string,
91+
command: string,
92+
workdir: string,
93+
component: string,
94+
env?: Array<V1alpha2DevWorkspaceSpecTemplateCommandsItemsExecEnv>,
95+
commandId?: string
96+
): vscode.Task {
6097
function expandEnvVariables(line: string): string {
6198
const regex = /\${[a-zA-Z_][a-zA-Z0-9_]*}/g;
6299
const envArray = line.match(regex);
@@ -75,7 +112,8 @@ export class DevfileTaskProvider implements vscode.TaskProvider {
75112
type: 'devfile',
76113
command,
77114
workdir,
78-
component
115+
component,
116+
commandId
79117
};
80118

81119
const execution = new vscode.CustomExecution(async (): Promise<vscode.Pseudoterminal> => {
@@ -92,4 +130,133 @@ export class DevfileTaskProvider implements vscode.TaskProvider {
92130
const task = new vscode.Task(kind, vscode.TaskScope.Workspace, name, 'devfile', execution, []);
93131
return task;
94132
}
133+
134+
private createCompositeTask(name: string, _commandIds: string[], _parallel: boolean, commandId: string): vscode.Task {
135+
const kind: DevfileTaskDefinition = {
136+
type: 'devfile',
137+
command: `composite:${commandId}`,
138+
commandId
139+
};
140+
141+
const execution = this.createCompositeExecution(commandId);
142+
const task = new vscode.Task(kind, vscode.TaskScope.Workspace, name, 'devfile', execution, []);
143+
task.presentationOptions = {
144+
reveal: vscode.TaskRevealKind.Never,
145+
panel: vscode.TaskPanelKind.Shared,
146+
focus: false,
147+
echo: false,
148+
showReuseMessage: false,
149+
close: true
150+
};
151+
return task;
152+
}
153+
154+
private createCompositeExecution(commandId: string): vscode.CustomExecution {
155+
const writeEmitter = new vscode.EventEmitter<string>();
156+
const closeEmitter = new vscode.EventEmitter<number | void>();
157+
const activeExecutions: vscode.TaskExecution[] = [];
158+
159+
return new vscode.CustomExecution(async (): Promise<vscode.Pseudoterminal> => {
160+
const pty: vscode.Pseudoterminal = {
161+
onDidWrite: writeEmitter.event,
162+
onDidClose: closeEmitter.event,
163+
open: async () => {
164+
let exitCode = 0;
165+
try {
166+
const result = await this.runCompositeById(commandId, activeExecutions);
167+
if (result.failed) {
168+
exitCode = 1;
169+
}
170+
} catch (error) {
171+
exitCode = 1;
172+
const message = `Composite task failed: ${String(error)}`;
173+
writeEmitter.fire(`${message}\r\n`);
174+
this.channel.appendLine(message);
175+
} finally {
176+
closeEmitter.fire(exitCode);
177+
}
178+
},
179+
close: () => {
180+
for (const execution of activeExecutions) {
181+
execution.terminate();
182+
}
183+
}
184+
};
185+
return pty;
186+
});
187+
}
188+
189+
private async runCompositeById(commandId: string, activeExecutions: vscode.TaskExecution[]): Promise<{ failed: boolean }> {
190+
const config = this.compositeConfigById.get(commandId);
191+
if (!config) {
192+
this.channel.appendLine(`Composite task not found: ${commandId}`);
193+
return { failed: true };
194+
}
195+
return this.runCompositeCommands(config.commandIds, config.parallel, activeExecutions, []);
196+
}
197+
198+
private async runCompositeCommands(
199+
commandIds: string[],
200+
parallel: boolean,
201+
activeExecutions: vscode.TaskExecution[],
202+
stack: string[]
203+
): Promise<{ failed: boolean }> {
204+
if (parallel) {
205+
const results = await Promise.all(commandIds.map(id => this.runCommandById(id, activeExecutions, stack)));
206+
return { failed: results.some(result => result.failed) };
207+
}
208+
209+
let failed = false;
210+
for (const id of commandIds) {
211+
const result = await this.runCommandById(id, activeExecutions, stack);
212+
if (result.failed) {
213+
failed = true;
214+
}
215+
}
216+
return { failed };
217+
}
218+
219+
private async runCommandById(
220+
commandId: string,
221+
activeExecutions: vscode.TaskExecution[],
222+
stack: string[]
223+
): Promise<{ failed: boolean }> {
224+
if (stack.includes(commandId)) {
225+
this.channel.appendLine(`Composite cycle detected: ${[...stack, commandId].join(' -> ')}`);
226+
return { failed: true };
227+
}
228+
const execTask = this.execTaskById.get(commandId);
229+
if (execTask) {
230+
const execution = await vscode.tasks.executeTask(execTask);
231+
activeExecutions.push(execution);
232+
const failed = await this.waitForTaskEnd(execution);
233+
return { failed };
234+
}
235+
236+
const compositeConfig = this.compositeConfigById.get(commandId);
237+
if (compositeConfig) {
238+
return this.runCompositeCommands(compositeConfig.commandIds, compositeConfig.parallel, activeExecutions, [...stack, commandId]);
239+
}
240+
241+
this.channel.appendLine(`Composite dependency not found: ${commandId}`);
242+
return { failed: true };
243+
}
244+
245+
private waitForTaskEnd(execution: vscode.TaskExecution): Promise<boolean> {
246+
return new Promise(resolve => {
247+
let exitCode: number | undefined;
248+
const processDisposable = vscode.tasks.onDidEndTaskProcess(event => {
249+
if (event.execution === execution) {
250+
exitCode = event.exitCode;
251+
}
252+
});
253+
const disposable = vscode.tasks.onDidEndTask(event => {
254+
if (event.execution === execution) {
255+
processDisposable.dispose();
256+
disposable.dispose();
257+
resolve(exitCode !== undefined && exitCode !== 0);
258+
}
259+
});
260+
});
261+
}
95262
}

0 commit comments

Comments
 (0)