@@ -17,6 +17,7 @@ interface DevfileTaskDefinition extends vscode.TaskDefinition {
1717 command : string ;
1818 workdir ?: string ;
1919 component ?: string ;
20+ commandId ?: string ;
2021}
2122
2223export class DevfileTaskProvider implements vscode . TaskProvider {
@@ -35,15 +36,34 @@ export class DevfileTaskProvider implements vscode.TaskProvider {
3536 private async computeTasks ( ) : Promise < vscode . Task [ ] > {
3637 const devfileCommands = await this . fetchDevfileCommands ( ) ;
3738
38- const cheTasks : vscode . Task [ ] = devfileCommands !
39- . filter ( command => command . exec ?. commandLine )
39+ const localCommands = devfileCommands !
4040 . filter ( command => {
4141 const importedByAttribute = ( command . attributes as any ) ?. [ 'controller.devfile.io/imported-by' ] ;
4242 return ! command . attributes || importedByAttribute === undefined || importedByAttribute === 'parent' ;
4343 } )
44- . filter ( command => ! / ^ i n i t - s s h - a g e n t - c o m m a n d - \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 ;
44+ . filter ( command => ! / ^ i n i t - s s h - a g e n t - c o m m a n d - \d + $ / . test ( command . id ) ) ;
45+
46+ const execTasks : vscode . Task [ ] = localCommands
47+ . filter ( command => command . exec ?. commandLine )
48+ . map ( command => this . createCheTask (
49+ command . exec ?. label || command . id ,
50+ command . exec ?. commandLine ! ,
51+ command . exec ?. workingDir || '${PROJECT_SOURCE}' ,
52+ command . exec ?. component ! ,
53+ command . exec ?. env ,
54+ command . id
55+ ) ) ;
56+
57+ const compositeTasks : vscode . Task [ ] = localCommands
58+ . filter ( command => ( command as any ) . composite ?. commands ?. length )
59+ . map ( command => this . createCompositeTask (
60+ ( command as any ) . composite ?. label || command . id ,
61+ ( command as any ) . composite ?. commands || [ ] ,
62+ Boolean ( ( command as any ) . composite ?. parallel ) ,
63+ command . id
64+ ) ) ;
65+
66+ return [ ...execTasks , ...compositeTasks ] ;
4767 }
4868
4969 private async fetchDevfileCommands ( ) : Promise < V1alpha2DevWorkspaceSpecTemplateCommands [ ] > {
@@ -56,7 +76,14 @@ export class DevfileTaskProvider implements vscode.TaskProvider {
5676 return [ ] ;
5777 }
5878
59- private createCheTask ( name : string , command : string , workdir : string , component : string , env ?: Array < V1alpha2DevWorkspaceSpecTemplateCommandsItemsExecEnv > ) : vscode . Task {
79+ private createCheTask (
80+ name : string ,
81+ command : string ,
82+ workdir : string ,
83+ component : string ,
84+ env ?: Array < V1alpha2DevWorkspaceSpecTemplateCommandsItemsExecEnv > ,
85+ commandId ?: string
86+ ) : vscode . Task {
6087 function expandEnvVariables ( line : string ) : string {
6188 const regex = / \$ { [ a - z A - Z _ ] [ a - z A - Z 0 - 9 _ ] * } / g;
6289 const envArray = line . match ( regex ) ;
@@ -75,7 +102,8 @@ export class DevfileTaskProvider implements vscode.TaskProvider {
75102 type : 'devfile' ,
76103 command,
77104 workdir,
78- component
105+ component,
106+ commandId
79107 } ;
80108
81109 const execution = new vscode . CustomExecution ( async ( ) : Promise < vscode . Pseudoterminal > => {
@@ -92,4 +120,99 @@ export class DevfileTaskProvider implements vscode.TaskProvider {
92120 const task = new vscode . Task ( kind , vscode . TaskScope . Workspace , name , 'devfile' , execution , [ ] ) ;
93121 return task ;
94122 }
123+
124+ private createCompositeTask ( name : string , commandIds : string [ ] , parallel : boolean , commandId : string ) : vscode . Task {
125+ const kind : DevfileTaskDefinition = {
126+ type : 'devfile' ,
127+ command : `composite:${ commandId } ` ,
128+ commandId
129+ } ;
130+
131+ const writeEmitter = new vscode . EventEmitter < string > ( ) ;
132+ const closeEmitter = new vscode . EventEmitter < void > ( ) ;
133+ const activeExecutions : vscode . TaskExecution [ ] = [ ] ;
134+
135+ const execution = new vscode . CustomExecution ( async ( ) : Promise < vscode . Pseudoterminal > => {
136+ const pty : vscode . Pseudoterminal = {
137+ onDidWrite : writeEmitter . event ,
138+ onDidClose : closeEmitter . event ,
139+ open : async ( ) => {
140+ writeEmitter . fire ( `Running composite task: ${ name } \r\n` ) ;
141+ try {
142+ await this . runComposite ( commandIds , parallel , writeEmitter , activeExecutions ) ;
143+ writeEmitter . fire ( `Composite task finished: ${ name } \r\n` ) ;
144+ } catch ( error ) {
145+ writeEmitter . fire ( `Composite task failed: ${ name } \r\n` ) ;
146+ writeEmitter . fire ( `${ String ( error ) } \r\n` ) ;
147+ } finally {
148+ closeEmitter . fire ( ) ;
149+ }
150+ } ,
151+ close : ( ) => {
152+ for ( const execution of activeExecutions ) {
153+ execution . terminate ( ) ;
154+ }
155+ }
156+ } ;
157+ return pty ;
158+ } ) ;
159+
160+ return new vscode . Task ( kind , vscode . TaskScope . Workspace , name , 'devfile' , execution , [ ] ) ;
161+ }
162+
163+ private async runComposite (
164+ commandIds : string [ ] ,
165+ parallel : boolean ,
166+ writeEmitter : vscode . EventEmitter < string > ,
167+ activeExecutions : vscode . TaskExecution [ ]
168+ ) : Promise < void > {
169+ const tasks = await vscode . tasks . fetchTasks ( { type : 'devfile' } ) ;
170+ const taskMap = new Map < string , vscode . Task > ( ) ;
171+
172+ for ( const task of tasks ) {
173+ const def = task . definition as DevfileTaskDefinition ;
174+ if ( def ?. commandId ) {
175+ taskMap . set ( def . commandId , task ) ;
176+ }
177+ taskMap . set ( task . name , task ) ;
178+ }
179+
180+ const targetTasks = commandIds
181+ . map ( id => taskMap . get ( id ) )
182+ . filter ( ( task ) : task is vscode . Task => Boolean ( task ) ) ;
183+
184+ const missing = commandIds . filter ( id => ! taskMap . has ( id ) ) ;
185+ if ( missing . length ) {
186+ writeEmitter . fire ( `Missing tasks: ${ missing . join ( ', ' ) } \r\n` ) ;
187+ }
188+ if ( ! targetTasks . length ) {
189+ writeEmitter . fire ( 'No tasks to run.\r\n' ) ;
190+ return ;
191+ }
192+
193+ if ( parallel ) {
194+ const executions = await Promise . all ( targetTasks . map ( task => vscode . tasks . executeTask ( task ) ) ) ;
195+ activeExecutions . push ( ...executions ) ;
196+ await Promise . all ( executions . map ( execution => this . waitForTaskEnd ( execution ) ) ) ;
197+ return ;
198+ }
199+
200+ for ( const task of targetTasks ) {
201+ writeEmitter . fire ( `Starting ${ task . name } \r\n` ) ;
202+ const execution = await vscode . tasks . executeTask ( task ) ;
203+ activeExecutions . push ( execution ) ;
204+ await this . waitForTaskEnd ( execution ) ;
205+ }
206+ }
207+
208+ private waitForTaskEnd ( execution : vscode . TaskExecution ) : Promise < void > {
209+ return new Promise ( resolve => {
210+ const disposable = vscode . tasks . onDidEndTask ( event => {
211+ if ( event . execution === execution ) {
212+ disposable . dispose ( ) ;
213+ resolve ( ) ;
214+ }
215+ } ) ;
216+ } ) ;
217+ }
95218}
0 commit comments