@@ -115,13 +115,158 @@ func stepTags(step map[string]any) []string {
115115 return nil
116116}
117117
118+ // matrixExpansionContext holds state for matrix expansion.
119+ type matrixExpansionContext struct {
120+ // registry maps base step keys to their matrix configs
121+ registry map [string ]* matrixConfig
122+ // expandedKeys maps base step keys to their expanded keys
123+ expandedKeys map [string ][]string
124+ }
125+
126+ func newMatrixExpansionContext () * matrixExpansionContext {
127+ return & matrixExpansionContext {
128+ registry : make (map [string ]* matrixConfig ),
129+ expandedKeys : make (map [string ][]string ),
130+ }
131+ }
132+
133+ // expandMatrixInGroups preprocesses pipeline groups to expand matrix steps.
134+ // Returns the modified groups and a context for selector expansion.
135+ func expandMatrixInGroups (gs []* pipelineGroup ) ([]* pipelineGroup , * matrixExpansionContext , error ) {
136+ ctx := newMatrixExpansionContext ()
137+ var result []* pipelineGroup
138+
139+ for _ , g := range gs {
140+ newGroup := & pipelineGroup {
141+ filename : g .filename ,
142+ sortKey : g .sortKey ,
143+ Group : g .Group ,
144+ Key : g .Key ,
145+ Tags : g .Tags ,
146+ SortKey : g .SortKey ,
147+ DependsOn : g .DependsOn ,
148+ DefaultJobEnv : g .DefaultJobEnv ,
149+ }
150+
151+ for _ , step := range g .Steps {
152+ matrixDef , hasMatrix := step ["matrix" ]
153+ if ! hasMatrix {
154+ // No matrix, keep step as-is
155+ newGroup .Steps = append (newGroup .Steps , step )
156+ continue
157+ }
158+
159+ baseKey := stepKey (step )
160+ if baseKey == "" {
161+ // No key - pass through to Buildkite for native matrix handling
162+ newGroup .Steps = append (newGroup .Steps , step )
163+ continue
164+ }
165+
166+ // Parse matrix configuration
167+ cfg , err := parseMatrixConfig (matrixDef )
168+ if err != nil {
169+ return nil , nil , fmt .Errorf ("parse matrix in step %q: %w" , stepKey (step ), err )
170+ }
171+
172+ // Validate label has placeholder
173+ if label , ok := step ["label" ].(string ); ok {
174+ if ! hasMatrixPlaceholder (label ) {
175+ return nil , nil , fmt .Errorf ("matrix step %q: label must contain {{matrix...}} placeholder" , baseKey )
176+ }
177+ }
178+
179+ // Register for selector expansion
180+ ctx .registry [baseKey ] = cfg
181+
182+ // Expand matrix into individual steps
183+ instances := expandMatrix (cfg )
184+ if len (instances ) == 0 {
185+ return nil , nil , fmt .Errorf ("matrix step %q: no instances after expansion" , baseKey )
186+ }
187+
188+ var expandedKeysList []string
189+ for _ , inst := range instances {
190+ // Deep copy and substitute
191+ expandedStep := deepCopyStepMap (step )
192+ expandedStep = substituteMatrixValues (expandedStep , inst .Values ).(map [string ]any )
193+
194+ // Generate expanded key and update name/key fields
195+ expandedKey := generateMatrixInstanceKey (baseKey , cfg , inst .Values )
196+ if _ , hasName := expandedStep ["name" ]; hasName {
197+ expandedStep ["name" ] = expandedKey
198+ } else {
199+ expandedStep ["key" ] = expandedKey
200+ }
201+ delete (expandedStep , "matrix" )
202+
203+ // Add auto-generated tags (inherit original + add matrix tags)
204+ originalTags := stepTags (step )
205+ matrixTags := generateMatrixTags (inst .Values )
206+ allTags := append ([]string {}, originalTags ... )
207+ allTags = append (allTags , matrixTags ... )
208+ if len (allTags ) > 0 {
209+ expandedStep ["tags" ] = allTags
210+ }
211+
212+ expandedKeysList = append (expandedKeysList , expandedKey )
213+ newGroup .Steps = append (newGroup .Steps , expandedStep )
214+ }
215+
216+ ctx .expandedKeys [baseKey ] = expandedKeysList
217+
218+ // Create meta wait-step with original key
219+ metaStep := map [string ]any {
220+ "wait" : nil ,
221+ "key" : baseKey ,
222+ "depends_on" : expandedKeysList ,
223+ }
224+ newGroup .Steps = append (newGroup .Steps , metaStep )
225+ }
226+
227+ result = append (result , newGroup )
228+ }
229+
230+ return result , ctx , nil
231+ }
232+
233+ // expandDependsOnSelectors processes depends_on to expand matrix selectors.
234+ func expandDependsOnSelectors (dependsOn any , ctx * matrixExpansionContext ) ([]string , error ) {
235+ selectors , err := parseMatrixDependsOn (dependsOn )
236+ if err != nil {
237+ return nil , err
238+ }
239+
240+ var result []string
241+ for _ , sel := range selectors {
242+ if sel .Matrix == nil {
243+ // Simple key reference
244+ result = append (result , sel .Key )
245+ } else {
246+ // Selector with matrix filter - expand to matching keys
247+ matches , err := expandMatrixSelector (sel , ctx .registry , ctx .expandedKeys )
248+ if err != nil {
249+ return nil , err
250+ }
251+ result = append (result , matches ... )
252+ }
253+ }
254+
255+ return result , nil
256+ }
257+
118258func (c * converter ) convertGroups (gs []* pipelineGroup , filter * stepFilter ) (
119259 []* bkPipelineGroup , error ,
120260) {
261+ expandedGroups , matrixCtx , err := expandMatrixInGroups (gs )
262+ if err != nil {
263+ return nil , fmt .Errorf ("expand matrix: %w" , err )
264+ }
265+
121266 set := newStepNodeSet ()
122267 var groupNodes []* stepNode
123268
124- for i , g := range gs {
269+ for i , g := range expandedGroups {
125270 groupNode := & stepNode {
126271 id : fmt .Sprintf ("g%d" , i ),
127272 key : g .Key ,
@@ -169,8 +314,20 @@ func (c *converter) convertGroups(gs []*pipelineGroup, filter *stepFilter) (
169314 for _ , step := range groupNode .subSteps {
170315 // Track step dependencies.
171316 if dependsOn , ok := step .src ["depends_on" ]; ok {
172- deps := toStringList (dependsOn )
173- for _ , dep := range deps {
317+ // Expand matrix selectors in depends_on
318+ expandedDeps , err := expandDependsOnSelectors (dependsOn , matrixCtx )
319+ if err != nil {
320+ return nil , fmt .Errorf ("expand depends_on for step %q: %w" , step .key , err )
321+ }
322+
323+ // Update the step source with expanded deps for Buildkite output
324+ if len (expandedDeps ) == 1 {
325+ step .src ["depends_on" ] = expandedDeps [0 ]
326+ } else {
327+ step .src ["depends_on" ] = expandedDeps
328+ }
329+
330+ for _ , dep := range expandedDeps {
174331 if depNode , ok := set .byKey (dep ); ok {
175332 set .addDep (step .id , depNode .id )
176333 }
0 commit comments