Skip to content

Commit 5b00dd8

Browse files
feat(raycicmd): add matrix expansion and filtering support
Integrate matrix_expand into conversion steps. This allows pipeline steps to selectively depend on a subset of matrix-defined steps, reducing unnecessary steps when tag filtering can filter those out. - Introduced a new YAML configuration for matrix selection tests in Buildkite. - Implemented matrix expansion logic in the converter, allowing for dynamic step generation based on matrix configurations. Topic: matrix-converter Relative: matrix-expand Labels: draft Signed-off-by: andrew <andrew@anyscale.com>
1 parent 853ec65 commit 5b00dd8

File tree

3 files changed

+412
-5
lines changed

3 files changed

+412
-5
lines changed

.buildkite/matrix-test.rayci.yaml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
group: matrix selection test
2+
sort_key: "99"
3+
tags:
4+
- matrix-test
5+
steps:
6+
# Matrix step with 5 values - only "selected" should be run when filtered
7+
- label: "Matrix {{matrix.variant}}"
8+
key: matrix-step
9+
command: |
10+
echo "Running variant: {{matrix.variant}}"
11+
if [ "{{matrix.variant}}" != "selected" ]; then
12+
echo "ERROR: This variant should not have been spawned!"
13+
echo "Only 'selected' variant should run when matrix-test tag is used."
14+
exit 1
15+
fi
16+
echo "Correct variant spawned."
17+
depends_on: forge
18+
matrix:
19+
setup:
20+
variant:
21+
- "selected"
22+
- "skipped-1"
23+
- "skipped-2"
24+
- "skipped-3"
25+
- "skipped-4"
26+
27+
# This step depends on only the "selected" variant
28+
- label: "Depends on selected only"
29+
key: depends-on-selected
30+
command: echo "This step depends on only the selected variant"
31+
tags:
32+
- always
33+
depends_on:
34+
- key: matrix-step
35+
matrix:
36+
variant: "selected"
37+
- forge

raycicmd/converter.go

Lines changed: 160 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
118258
func (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

Comments
 (0)