forked from firecow/gitlab-ci-local
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexecutor.ts
More file actions
127 lines (113 loc) · 5.84 KB
/
Copy pathexecutor.ts
File metadata and controls
127 lines (113 loc) · 5.84 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
import chalk from "chalk-template";
import {Job} from "./job.js";
import assert, {AssertionError} from "node:assert";
import {Argv} from "./argv.js";
import pMap from "p-map";
import {matrixSelectorMatches} from "./parallel.js";
export class Executor {
static async runLoop (argv: Argv, jobs: ReadonlyArray<Job>, stages: readonly string[], potentialStarters: Job[]) {
let startCandidates: Job[];
do {
startCandidates = Executor.getStartCandidates(jobs, stages, potentialStarters, argv.manual);
if (startCandidates.length > 0) {
const mapper = async (startCandidate: Job) => startCandidate.start();
await pMap(startCandidates, mapper, {concurrency: argv.concurrency ?? startCandidates.length});
}
} while (startCandidates.length > 0);
}
static getStartCandidates (jobs: ReadonlyArray<Job>, stages: readonly string[], potentialStarters: readonly Job[], manuals: string[]) {
const startCandidates = [];
for (const job of [...new Set<Job>(potentialStarters)]) {
if (job.started) continue;
const jobsToWaitFor = Executor.getPastToWaitFor(jobs, stages, job, manuals);
if (Executor.isNotFinished(jobsToWaitFor)) {
continue;
}
if (job.when === "on_success" && Executor.isPastFailed(jobsToWaitFor)) {
continue;
}
if (job.when === "manual" && Executor.isPastFailed(jobsToWaitFor)) {
continue;
}
if (job.when === "on_failure" && !Executor.isPastFailed(jobsToWaitFor)) {
continue;
}
startCandidates.push(job);
}
return startCandidates;
}
static isPastFailed (jobsToWaitFor: ReadonlyArray<Job>) {
const failJobs = jobsToWaitFor.filter(j => {
if (j.allowFailure) {
return false;
}
return (j.preScriptsExitCode ? j.preScriptsExitCode : 0) > 0;
});
return failJobs.length > 0;
}
static isNotFinished (jobsToWaitFor: ReadonlyArray<Job>) {
const notFinishedJobs = jobsToWaitFor.filter(j => !j.finished);
return notFinishedJobs.length > 0;
}
static getFailed (jobs: ReadonlyArray<Job>) {
return jobs.filter(j => j.finished && !j.allowFailure && (j.preScriptsExitCode ?? 0) > 0);
}
static getPastToWaitFor (jobs: ReadonlyArray<Job>, stages: readonly string[], job: Job, manuals: string[]) {
const jobsToWaitForSet = new Set<Job>();
let waitForLoopArray: Job[] = [job];
while (waitForLoopArray.length > 0) {
const loopJob = waitForLoopArray.pop();
assert(loopJob != null, "Job not found in getPastToWaitFor, should be impossible!");
if (loopJob.needs) {
const neededToWaitFor = this.getNeededToWaitFor(jobs, manuals, loopJob);
waitForLoopArray.push(...neededToWaitFor);
} else {
const previousToWaitFor = this.getPreviousToWaitFor(jobs, stages, loopJob);
waitForLoopArray = waitForLoopArray.concat(previousToWaitFor);
waitForLoopArray = waitForLoopArray.filter(j => j.when !== "never");
waitForLoopArray = waitForLoopArray.filter(j => j.when !== "manual" || manuals.includes(j.name));
}
waitForLoopArray.forEach(j => jobsToWaitForSet.add(j));
}
return [...jobsToWaitForSet];
}
static getNeededToWaitFor (jobs: ReadonlyArray<Job>, manuals: string[], job: Job) {
const toWaitFor = [];
assert(job.needs != null, chalk`${job.name}.needs cannot be null in getNeededToWaitFor`);
for (const need of job.needs) {
let baseJobs = jobs.filter(j => j.baseName === need.job);
if (need.parallel?.matrix && baseJobs.length > 0) {
if (baseJobs.every(j => j.matrixVariables == null)) {
throw new AssertionError({message: chalk`{blueBright ${job.name}} uses needs.parallel.matrix targeting {blueBright ${need.job}}, but {blueBright ${need.job}} has no parallel:matrix configuration`});
}
baseJobs = baseJobs.filter(j => matrixSelectorMatches(j.matrixVariables, need.parallel!.matrix));
if (baseJobs.length === 0 && !need.optional) {
throw new AssertionError({message: chalk`{blueBright ${job.name}} needs.parallel.matrix selector for {blueBright ${need.job}} matched zero permutations`});
}
}
for (const j of baseJobs) {
if (j.when === "never" && !need.optional) {
throw new AssertionError({message: chalk`{blueBright ${j.name}} is when:never, but its needed by {blueBright ${job.name}}`});
}
if (j.when === "never" && need.optional) {
continue;
}
if (j.when === "manual" && !manuals.includes(j.name)) {
throw new AssertionError({message: chalk`{blueBright ${j.name}} is when:manual, its needed by {blueBright ${job.name}}, and not specified in --manual`});
}
assert(job.name !== j.name, chalk`This GitLab CI configuration is invalid: The pipeline has circular dependencies: self-dependency: {blueBright ${need.job}}.`);
toWaitFor.push(j);
}
}
return toWaitFor;
}
static getPreviousToWaitFor (jobs: ReadonlyArray<Job>, stages: readonly string[], job: Job) {
const previousToWaitFor: Job[] = [];
const stageIndex = stages.indexOf(job.stage);
const pastStages = stages.slice(0, stageIndex);
pastStages.forEach((pastStage) => {
previousToWaitFor.push(...[...jobs.values()].filter(j => j.stage === pastStage));
});
return previousToWaitFor;
}
}