@@ -54,7 +54,9 @@ export class PipelineGraph {
54
54
private readonly assetNodesByType = new Map < AssetType , AGraphNode > ( ) ;
55
55
private readonly synthNode ?: AGraphNode ;
56
56
private readonly selfMutateNode ?: AGraphNode ;
57
- private readonly stackOutputDependencies = new DependencyBuilders < StackDeployment , any > ( ) ;
57
+ private readonly stackOutputDependencies = new DependencyBuilders < StackDeployment > ( ) ;
58
+ /** Mapping steps to depbuilders, satisfied by the step itself */
59
+ private readonly nodeDependencies = new DependencyBuilders < Step > ( ) ;
58
60
private readonly publishTemplate : boolean ;
59
61
private readonly prepareStep : boolean ;
60
62
private readonly singlePublisher : boolean ;
@@ -102,16 +104,15 @@ export class PipelineGraph {
102
104
waves [ i ] . dependOn ( waves [ i - 1 ] ) ;
103
105
}
104
106
105
- // Add additional dependencies between steps that depend on stack outputs and the stacks
106
- // that produce them.
107
+ this . addMissingDependencyNodes ( ) ;
107
108
}
108
109
109
110
public isSynthNode ( node : AGraphNode ) {
110
111
return this . synthNode === node ;
111
112
}
112
113
113
114
private addBuildStep ( step : Step ) {
114
- return this . addAndRecurse ( step , this . topLevelGraph ( 'Build' ) ) ;
115
+ return this . addStepNode ( step , this . topLevelGraph ( 'Build' ) ) ;
115
116
}
116
117
117
118
private addWave ( wave : Wave ) : AGraph {
@@ -174,7 +175,7 @@ export class PipelineGraph {
174
175
175
176
const cloudAssembly = this . cloudAssemblyFileSet ;
176
177
177
- firstDeployNode . dependOn ( this . addAndRecurse ( cloudAssembly . producer , retGraph ) ) ;
178
+ firstDeployNode . dependOn ( this . addStepNode ( cloudAssembly . producer , retGraph ) ) ;
178
179
179
180
// add the template asset
180
181
if ( this . publishTemplate ) {
@@ -195,7 +196,7 @@ export class PipelineGraph {
195
196
196
197
// Add stack output synchronization point
197
198
if ( this . queries . stackOutputsReferenced ( stack ) . length > 0 ) {
198
- this . stackOutputDependencies . get ( stack ) . dependOn ( deployNode ) ;
199
+ this . stackOutputDependencies . for ( stack ) . dependOn ( deployNode ) ;
199
200
}
200
201
}
201
202
@@ -220,7 +221,7 @@ export class PipelineGraph {
220
221
221
222
private addChangeSetNode ( changeSet : Step [ ] , prepareNode : AGraphNode , deployNode : AGraphNode , graph : AGraph ) {
222
223
for ( const c of changeSet ) {
223
- const changeSetNode = this . addAndRecurse ( c , graph ) ;
224
+ const changeSetNode = this . addStepNode ( c , graph ) ;
224
225
changeSetNode ?. dependOn ( prepareNode ) ;
225
226
deployNode . dependOn ( changeSetNode ) ;
226
227
}
@@ -230,12 +231,12 @@ export class PipelineGraph {
230
231
const currentNodes = new GraphNodeCollection ( parent . nodes ) ;
231
232
const preNodes = new GraphNodeCollection ( new Array < AGraphNode > ( ) ) ;
232
233
for ( const p of pre ) {
233
- const preNode = this . addAndRecurse ( p , parent ) ;
234
+ const preNode = this . addStepNode ( p , parent ) ;
234
235
currentNodes . dependOn ( preNode ) ;
235
236
preNodes . nodes . push ( preNode ! ) ;
236
237
}
237
238
for ( const p of post ) {
238
- const postNode = this . addAndRecurse ( p , parent ) ;
239
+ const postNode = this . addStepNode ( p , parent ) ;
239
240
postNode ?. dependOn ( ...currentNodes . nodes ) ;
240
241
}
241
242
return preNodes ;
@@ -250,7 +251,12 @@ export class PipelineGraph {
250
251
return ret as AGraph ;
251
252
}
252
253
253
- private addAndRecurse ( step : Step , parent : AGraph ) {
254
+ /**
255
+ * Add a Node to a Graph for a given Step
256
+ *
257
+ * Adds all dependencies for that Node to the same Step as well.
258
+ */
259
+ private addStepNode ( step : Step , parent : AGraph ) {
254
260
if ( step === PipelineGraph . NO_STEP ) { return undefined ; }
255
261
256
262
const previous = this . added . get ( step ) ;
@@ -267,21 +273,56 @@ export class PipelineGraph {
267
273
parent . add ( node ) ;
268
274
this . added . set ( step , node ) ;
269
275
276
+ // This used to recurse -- that's not safe, because it might create nodes in the
277
+ // wrong graph (it would create a dependency node, that might need to be created in
278
+ // a different graph, in the current one). Instead, use DependencyBuilders.
270
279
for ( const dep of step . dependencies ) {
271
- const producerNode = this . addAndRecurse ( dep , parent ) ;
272
- node . dependOn ( producerNode ) ;
280
+ this . nodeDependencies . for ( dep ) . dependBy ( node ) ;
273
281
}
282
+ this . nodeDependencies . for ( step ) . dependOn ( node ) ;
274
283
275
284
// Add stack dependencies (by use of the dependency builder this also works
276
285
// if we encounter the Step before the Stack has been properly added yet)
277
286
for ( const output of step . consumedStackOutputs ) {
278
287
const stack = this . queries . producingStack ( output ) ;
279
- this . stackOutputDependencies . get ( stack ) . dependBy ( node ) ;
288
+ this . stackOutputDependencies . for ( stack ) . dependBy ( node ) ;
280
289
}
281
290
282
291
return node ;
283
292
}
284
293
294
+ /**
295
+ * Add dependencies that aren't in the pipeline yet
296
+ *
297
+ * Build steps reference as many sources (or other builds) as they want, which will be added
298
+ * automatically. Do that here. We couldn't do it earlier, because if there were dependencies
299
+ * between steps we didn't want to reparent those unnecessarily.
300
+ */
301
+ private addMissingDependencyNodes ( ) {
302
+ // May need to do this more than once to recursively add all missing producers
303
+ let attempts = 20 ;
304
+ while ( attempts -- > 0 ) {
305
+ const unsatisfied = this . nodeDependencies . unsatisfiedBuilders ( ) . filter ( ( [ s ] ) => s !== PipelineGraph . NO_STEP ) ;
306
+ if ( unsatisfied . length === 0 ) { return ; }
307
+
308
+ for ( const [ step , builder ] of unsatisfied ) {
309
+ // Add a new node for this step to the parent of the "leftmost" consumer.
310
+ const leftMostConsumer = new GraphNodeCollection ( builder . consumers ) . first ( ) ;
311
+ const parent = leftMostConsumer . parentGraph ;
312
+ if ( ! parent ) {
313
+ throw new Error ( `Consumer doesn't have a parent graph: ${ leftMostConsumer } ` ) ;
314
+ }
315
+ this . addStepNode ( step , parent ) ;
316
+ }
317
+ }
318
+
319
+ const unsatisfied = this . nodeDependencies . unsatisfiedBuilders ( ) ;
320
+ throw new Error ( [
321
+ 'Recursion depth too large while adding dependency nodes:' ,
322
+ unsatisfied . map ( ( [ step , builder ] ) => `${ builder . consumersAsString ( ) } awaiting ${ step } .` ) ,
323
+ ] . join ( ' ' ) ) ;
324
+ }
325
+
285
326
private publishAsset ( stackAsset : StackAsset ) : AGraphNode {
286
327
const assetsGraph = this . topLevelGraph ( 'Assets' ) ;
287
328
0 commit comments