forked from firecow/gitlab-ci-local
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparallel.ts
More file actions
150 lines (132 loc) · 6.14 KB
/
Copy pathparallel.ts
File metadata and controls
150 lines (132 loc) · 6.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import chalk from "chalk-template";
import assert, {AssertionError} from "node:assert";
import deepExtend from "deep-extend";
import {Need} from "./job.js";
type MatrixSelector = NonNullable<Need["parallel"]>["matrix"];
type MatrixVariables = {[key: string]: string};
export function isPlainParallel (jobData: any) {
return Number.isInteger(jobData.parallel);
}
// Expand a parallel.matrix-shaped selector into concrete {key: value} bindings.
// Mirrors GitLab's expansion: scalars are wrapped to single-element arrays, and
// arrays produce a cartesian product across keys within an entry.
export function expandMatrixSelector (matrix: MatrixSelector): MatrixVariables[] {
const expanded: MatrixVariables[] = [];
for (const entry of matrix) {
let inner: MatrixVariables[] = [{}];
for (const [key, raw] of Object.entries(entry)) {
const values = Array.isArray(raw) ? raw : [raw];
const next: MatrixVariables[] = [];
for (const clone of inner) {
for (const v of values) {
next.push({...clone, [key]: String(v)});
}
}
inner = next;
}
expanded.push(...inner);
}
return expanded;
}
// Returns true if `producerVars` matches at least one expanded selector entry.
// Match semantics: every key in the selector entry must equal the producer's value;
// the producer may have extra keys not mentioned in the selector.
export function matrixSelectorMatches (producerVars: MatrixVariables | null, matrix: MatrixSelector): boolean {
if (!producerVars) return false;
return expandMatrixSelector(matrix).some(sel =>
Object.entries(sel).every(([k, v]) => String(producerVars[k]) === String(v)),
);
}
// Find the consumer's `need` (if any) that this `producer` job satisfies.
// Used by both the executor (waitFor) and the artifacts producer pipeline so
// the two paths apply the same matching rules.
export function findMatchingNeed (consumerNeeds: ReadonlyArray<Need> | null, producerName: string, producerBaseName: string): Need | undefined {
return consumerNeeds?.find(n => n.job === producerName || n.job === producerBaseName);
}
// `$[[ matrix.IDENTIFIER ]]` — see https://docs.gitlab.com/ci/yaml/matrix_expressions/.
// Identifier charset matches upstream gitlab-org/gitlab's MatrixInterpolator regex
// `[a-zA-Z0-9_-]+` (letters, digits, underscore, hyphen).
const MATRIX_EXPR_RE = /\$\[\[\s*matrix\.([a-zA-Z0-9_-]+)\s*\]\]/g;
const MATRIX_EXPR_TEST_RE = /\$\[\[\s*matrix\./;
function valueContainsMatrixExpr (v: unknown): boolean {
if (Array.isArray(v)) return v.some(valueContainsMatrixExpr);
if (typeof v !== "string") return false;
return MATRIX_EXPR_TEST_RE.test(v);
}
// Detect whether any need's parallel.matrix selector contains a `$[[ matrix.X ]]` expression.
// Used to surface a clear error if a non-parallel-matrix consumer references such expressions.
export function needsContainMatrixExpressions (needs: Need[]): boolean {
return needs.some(n =>
n.parallel?.matrix?.some(entry => Object.values(entry).some(valueContainsMatrixExpr)) ?? false,
);
}
function substituteMatrixExpr (value: unknown, consumerVars: MatrixVariables, jobName: string): unknown {
if (Array.isArray(value)) return value.map(v => substituteMatrixExpr(v, consumerVars, jobName));
if (typeof value !== "string") return value;
return value.replace(MATRIX_EXPR_RE, (_, key) => {
if (!(key in consumerVars)) {
// Wording mirrors upstream gitlab-org/gitlab's MatrixInterpolator error
// ("'X' does not exist in matrix configuration") so log greps line up.
throw new AssertionError({message: chalk`{blueBright ${jobName}}: '{yellow ${key}}' does not exist in matrix configuration`});
}
return consumerVars[key];
});
}
// Substitute `$[[ matrix.X ]]` references inside each need's parallel.matrix
// selector, using the consumer permutation's own matrix bindings. Returns a
// shallow-cloned needs array (only entries with parallel.matrix are rebuilt);
// non-substituted fields on each need are shared with the input by reference.
export function resolveNeedsMatrixExpressions (needs: Need[], consumerVars: MatrixVariables, jobName: string): Need[] {
return needs.map(n => {
if (!n?.parallel?.matrix) return n;
return {
...n,
parallel: {
...n.parallel,
matrix: n.parallel.matrix.map(entry =>
Object.fromEntries(Object.entries(entry).map(([k, v]) => [k, substituteMatrixExpr(v, consumerVars, jobName)])),
) as MatrixSelector,
},
};
});
}
export function matrixVariablesList (jobData: any, jobName: string): {[key: string]: string}[] | null[] {
if (isPlainParallel(jobData)) {
return new Array(jobData.parallel).fill(null);
}
if (jobData?.parallel?.matrix == null) {
return [null];
}
assert(Array.isArray(jobData.parallel.matrix), `${jobName} parallel.matrix is not an array`);
const matrixVariables: {[key: string]: string}[] = [];
// Expand string value to array of values
for (const m of jobData.parallel.matrix) {
for (const [key, value] of Object.entries(m)) {
m[key] = Array.isArray(value) ? value : [value];
}
}
// Generate variables in while loop by expanding the matrix
const deep = deepExtend({}, jobData);
for (const m of deep.parallel.matrix) {
let i = 0;
let inner = [];
while (Object.keys(m).length > 0 && i < 100) {
const keys = Object.keys(m);
const key = keys[0];
const values = m[key];
delete m[key];
const innerClone = inner.length > 0 ? [...inner] : [{}];
inner = [];
for (const clone of innerClone) {
for (const v of values) {
const matrixVariable: {[key: string]: string} = {...clone};
matrixVariable[key] = v;
inner.push(matrixVariable);
}
}
i++;
}
matrixVariables.push(...inner);
}
return matrixVariables;
}