@@ -115,13 +115,156 @@ 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+
219+ result = append (result , newGroup )
220+ }
221+
222+ return result , ctx , nil
223+ }
224+
225+ // expandDependsOnSelectors processes depends_on to expand matrix selectors.
226+ func expandDependsOnSelectors (dependsOn any , ctx * matrixExpansionContext ) ([]string , error ) {
227+ selectors , err := parseMatrixDependsOn (dependsOn )
228+ if err != nil {
229+ return nil , err
230+ }
231+
232+ var result []string
233+ for _ , sel := range selectors {
234+ if sel .Matrix == nil {
235+ // Simple key reference - check if it's a matrix step
236+ if expanded , ok := ctx .expandedKeys [sel .Key ]; ok {
237+ // Matrix step: expand to all expanded keys
238+ result = append (result , expanded ... )
239+ } else {
240+ // Non-matrix step: use key as-is
241+ result = append (result , sel .Key )
242+ }
243+ } else {
244+ // Selector with matrix filter - expand to matching keys
245+ matches , err := expandMatrixSelector (sel , ctx .registry , ctx .expandedKeys )
246+ if err != nil {
247+ return nil , err
248+ }
249+ result = append (result , matches ... )
250+ }
251+ }
252+
253+ return result , nil
254+ }
255+
118256func (c * converter ) convertGroups (gs []* pipelineGroup , filter * stepFilter ) (
119257 []* bkPipelineGroup , error ,
120258) {
259+ expandedGroups , matrixCtx , err := expandMatrixInGroups (gs )
260+ if err != nil {
261+ return nil , fmt .Errorf ("expand matrix: %w" , err )
262+ }
263+
121264 set := newStepNodeSet ()
122265 var groupNodes []* stepNode
123266
124- for i , g := range gs {
267+ for i , g := range expandedGroups {
125268 groupNode := & stepNode {
126269 id : fmt .Sprintf ("g%d" , i ),
127270 key : g .Key ,
@@ -169,8 +312,20 @@ func (c *converter) convertGroups(gs []*pipelineGroup, filter *stepFilter) (
169312 for _ , step := range groupNode .subSteps {
170313 // Track step dependencies.
171314 if dependsOn , ok := step .src ["depends_on" ]; ok {
172- deps := toStringList (dependsOn )
173- for _ , dep := range deps {
315+ // Expand matrix selectors in depends_on
316+ expandedDeps , err := expandDependsOnSelectors (dependsOn , matrixCtx )
317+ if err != nil {
318+ return nil , fmt .Errorf ("expand depends_on for step %q: %w" , step .key , err )
319+ }
320+
321+ // Update the step source with expanded deps for Buildkite output
322+ if len (expandedDeps ) == 1 {
323+ step .src ["depends_on" ] = expandedDeps [0 ]
324+ } else {
325+ step .src ["depends_on" ] = expandedDeps
326+ }
327+
328+ for _ , dep := range expandedDeps {
174329 if depNode , ok := set .byKey (dep ); ok {
175330 set .addDep (step .id , depNode .id )
176331 }
0 commit comments