-
Notifications
You must be signed in to change notification settings - Fork 622
/
Copy pathPhasedOperationPlugin.ts
167 lines (142 loc) · 5.45 KB
/
PhasedOperationPlugin.ts
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import type { RushConfigurationProject } from '../../api/RushConfigurationProject';
import type { IPhase } from '../../api/CommandLineConfiguration';
import { Operation } from './Operation';
import type {
ICreateOperationsContext,
IPhasedCommandPlugin,
PhasedCommandHooks
} from '../../pluginFramework/PhasedCommandHooks';
import type { IOperationSettings } from '../../api/RushProjectConfiguration';
const PLUGIN_NAME: 'PhasedOperationPlugin' = 'PhasedOperationPlugin';
/**
* Core phased command plugin that provides the functionality for generating a base operation graph
* from the set of selected projects and phases.
*/
export class PhasedOperationPlugin implements IPhasedCommandPlugin {
public apply(hooks: PhasedCommandHooks): void {
hooks.createOperations.tap(PLUGIN_NAME, createOperations);
// Configure operations later.
hooks.createOperations.tap(
{
name: `${PLUGIN_NAME}.Configure`,
stage: 1000
},
configureOperations
);
}
}
function createOperations(
existingOperations: Set<Operation>,
context: ICreateOperationsContext
): Set<Operation> {
const { phaseSelection, projectSelection, projectConfigurations } = context;
const operations: Map<string, Operation> = new Map();
// Create tasks for selected phases and projects
// This also creates the minimal set of dependencies needed
for (const phase of phaseSelection) {
for (const project of projectSelection) {
getOrCreateOperation(phase, project);
}
}
return existingOperations;
// Binds phaseSelection, projectSelection, operations via closure
function getOrCreateOperation(phase: IPhase, project: RushConfigurationProject): Operation {
const key: string = getOperationKey(phase, project);
let operation: Operation | undefined = operations.get(key);
if (!operation) {
const {
dependencies: { self, upstream },
name,
logFilenameIdentifier
} = phase;
const operationSettings: IOperationSettings | undefined = projectConfigurations
.get(project)
?.operationSettingsByOperationName.get(name);
operation = new Operation({
project,
phase,
settings: operationSettings,
logFilenameIdentifier: logFilenameIdentifier
});
operations.set(key, operation);
existingOperations.add(operation);
for (const depPhase of self) {
operation.addDependency(getOrCreateOperation(depPhase, project));
}
if (upstream.size) {
const { dependencyProjects } = project;
if (dependencyProjects.size) {
for (const depPhase of upstream) {
for (const dependencyProject of dependencyProjects) {
operation.addDependency(getOrCreateOperation(depPhase, dependencyProject));
}
}
}
}
}
return operation;
}
}
function configureOperations(operations: Set<Operation>, context: ICreateOperationsContext): Set<Operation> {
const {
changedProjectsOnly,
projectsInUnknownState: changedProjects,
phaseOriginal,
phaseSelection,
projectSelection,
includePhaseDeps,
isInitial
} = context;
const basePhases: ReadonlySet<IPhase> = includePhaseDeps ? phaseOriginal : phaseSelection;
// Grab all operations that were explicitly requested.
const operationsWithWork: Set<Operation> = new Set();
for (const operation of operations) {
const { associatedPhase, associatedProject } = operation;
if (basePhases.has(associatedPhase) && changedProjects.has(associatedProject)) {
operationsWithWork.add(operation);
}
}
if (!isInitial && changedProjectsOnly) {
const potentiallyAffectedOperations: Set<Operation> = new Set(operationsWithWork);
for (const operation of potentiallyAffectedOperations) {
if (operation.settings?.ignoreChangedProjectsOnlyFlag) {
operationsWithWork.add(operation);
}
for (const consumer of operation.consumers) {
potentiallyAffectedOperations.add(consumer);
}
}
} else {
// Add all operations that are selected that depend on the explicitly requested operations.
// This will mostly be relevant during watch; in initial runs it should not add any new operations.
for (const operation of operationsWithWork) {
for (const consumer of operation.consumers) {
operationsWithWork.add(consumer);
}
}
}
if (includePhaseDeps) {
// Add all operations that are dependencies of the operations already scheduled.
for (const operation of operationsWithWork) {
for (const dependency of operation.dependencies) {
operationsWithWork.add(dependency);
}
}
}
for (const operation of operations) {
// Enable exactly the set of operations that are requested.
operation.enabled &&= operationsWithWork.has(operation);
if (!includePhaseDeps || !isInitial) {
const { associatedPhase, associatedProject } = operation;
// This filter makes the "unsafe" selections happen.
operation.enabled &&= phaseSelection.has(associatedPhase) && projectSelection.has(associatedProject);
}
}
return operations;
}
// Convert the [IPhase, RushConfigurationProject] into a value suitable for use as a Map key
function getOperationKey(phase: IPhase, project: RushConfigurationProject): string {
return `${project.packageName};${phase.name}`;
}