Skip to content

Commit 2c3b5af

Browse files
Copilotdmichon-msft
andcommitted
Add parameter ignoring feature to rush-lib
Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com>
1 parent 7860b20 commit 2c3b5af

8 files changed

Lines changed: 141 additions & 18 deletions

File tree

common/reviews/api/rush-lib.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,7 @@ export interface IOperationSettings {
670670
ignoreChangedProjectsOnlyFlag?: boolean;
671671
operationName: string;
672672
outputFolderNames?: string[];
673+
parameterNamesToIgnore?: string[];
673674
sharding?: IRushPhaseSharding;
674675
weight?: number;
675676
}

libraries/rush-lib/src/api/RushProjectConfiguration.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,14 @@ export interface IOperationSettings {
146146
* If true, this operation will never be skipped by the `--changed-projects-only` flag.
147147
*/
148148
ignoreChangedProjectsOnlyFlag?: boolean;
149+
150+
/**
151+
* An optional list of custom command-line parameter names (their `parameterLongName` values from
152+
* command-line.json) that should be ignored when invoking the command for this operation.
153+
* This allows a project to opt out of parameters that don't affect its operation, preventing
154+
* unnecessary cache invalidation for this operation and its consumers.
155+
*/
156+
parameterNamesToIgnore?: string[];
149157
}
150158

151159
interface IOldRushProjectJson {

libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface IIPCOperationRunnerOptions {
3030
commandForHash: string;
3131
persist: boolean;
3232
requestRun: OperationRequestRunCallback;
33+
ignoredParameterNames: ReadonlyArray<string>;
3334
}
3435

3536
function isAfterExecuteEventMessage(message: unknown): message is IAfterExecuteEventMessage {
@@ -59,6 +60,7 @@ export class IPCOperationRunner implements IOperationRunner {
5960
private readonly _commandForHash: string;
6061
private readonly _persist: boolean;
6162
private readonly _requestRun: OperationRequestRunCallback;
63+
private readonly _ignoredParameterNames: ReadonlyArray<string>;
6264

6365
private _ipcProcess: ChildProcess | undefined;
6466
private _processReadyPromise: Promise<void> | undefined;
@@ -75,6 +77,7 @@ export class IPCOperationRunner implements IOperationRunner {
7577

7678
this._persist = options.persist;
7779
this._requestRun = options.requestRun;
80+
this._ignoredParameterNames = options.ignoredParameterNames;
7881
}
7982

8083
public async executeAsync(context: IOperationRunnerContext): Promise<OperationStatus> {
@@ -85,6 +88,11 @@ export class IPCOperationRunner implements IOperationRunner {
8588
// Run the operation
8689
terminal.writeLine('Invoking: ' + this._commandToRun);
8790

91+
// Log any ignored parameters in verbose mode
92+
if (this._ignoredParameterNames.length > 0) {
93+
terminal.writeVerboseLine(`Ignored parameters: ${this._ignoredParameterNames.join(', ')}`);
94+
}
95+
8896
const { rushConfiguration, projectFolder } = this._rushProject;
8997

9098
const { environment: initialEnvironment } = context;

libraries/rush-lib/src/logic/operations/IPCOperationRunnerPlugin.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22
// See LICENSE in the project root for license information.
33

4-
import type { IPhase } from '../../api/CommandLineConfiguration';
54
import type {
65
ICreateOperationsContext,
76
IPhasedCommandPlugin,
@@ -14,7 +13,8 @@ import { OperationStatus } from './OperationStatus';
1413
import {
1514
PLUGIN_NAME as ShellOperationPluginName,
1615
formatCommand,
17-
getCustomParameterValuesByPhase,
16+
getCustomParameterValuesForOperation,
17+
type ICustomParameterValuesForOperation,
1818
getDisplayName
1919
} from './ShellOperationRunnerPlugin';
2020

@@ -45,8 +45,8 @@ export class IPCOperationRunnerPlugin implements IPhasedCommandPlugin {
4545

4646
currentContext = context;
4747

48-
const getCustomParameterValuesForPhase: (phase: IPhase) => ReadonlyArray<string> =
49-
getCustomParameterValuesByPhase();
48+
const getCustomParameterValues: (operation: Operation) => ICustomParameterValuesForOperation =
49+
getCustomParameterValuesForOperation();
5050

5151
for (const operation of operations) {
5252
const { associatedPhase: phase, associatedProject: project, runner } = operation;
@@ -73,7 +73,8 @@ export class IPCOperationRunnerPlugin implements IPhasedCommandPlugin {
7373
// for this operation (or downstream operations) to be restored from the build cache.
7474
const commandForHash: string | undefined = phase.shellCommand ?? scripts?.[phaseName];
7575

76-
const customParameterValues: ReadonlyArray<string> = getCustomParameterValuesForPhase(phase);
76+
const { parameterValues: customParameterValues, ignoredParameterNames } =
77+
getCustomParameterValues(operation);
7778
const commandToRun: string = formatCommand(rawScript, customParameterValues);
7879

7980
const operationName: string = getDisplayName(phase, project);
@@ -86,6 +87,7 @@ export class IPCOperationRunnerPlugin implements IPhasedCommandPlugin {
8687
commandToRun,
8788
commandForHash,
8889
persist: true,
90+
ignoredParameterNames,
8991
requestRun: (requestor: string, detail?: string) => {
9092
const operationState: IOperationExecutionResult | undefined =
9193
operationStatesByRunner.get(ipcOperationRunner);

libraries/rush-lib/src/logic/operations/ShardedPhaseOperationPlugin.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ function spliceShards(existingOperations: Set<Operation>, context: ICreateOperat
137137
displayName: collatorDisplayName,
138138
rushConfiguration,
139139
commandToRun,
140-
customParameterValues: collatorParameters
140+
customParameterValues: collatorParameters,
141+
ignoredParameterNames: []
141142
});
142143

143144
const shardOperationName: string = `${phase.name}:shard`;
@@ -207,7 +208,8 @@ function spliceShards(existingOperations: Set<Operation>, context: ICreateOperat
207208
commandToRun: baseCommand,
208209
customParameterValues: shardedParameters,
209210
displayName: shardDisplayName,
210-
rushConfiguration
211+
rushConfiguration,
212+
ignoredParameterNames: []
211213
});
212214

213215
shardOperation.addDependency(preShardOperation);

libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface IShellOperationRunnerOptions {
2020
displayName: string;
2121
commandToRun: string;
2222
commandForHash: string;
23+
ignoredParameterNames: ReadonlyArray<string>;
2324
}
2425

2526
/**
@@ -44,6 +45,8 @@ export class ShellOperationRunner implements IOperationRunner {
4445

4546
private readonly _rushProject: RushConfigurationProject;
4647

48+
private readonly _ignoredParameterNames: ReadonlyArray<string>;
49+
4750
public constructor(options: IShellOperationRunnerOptions) {
4851
const { phase } = options;
4952

@@ -53,6 +56,7 @@ export class ShellOperationRunner implements IOperationRunner {
5356
this._rushProject = options.rushProject;
5457
this.commandToRun = options.commandToRun;
5558
this._commandForHash = options.commandForHash;
59+
this._ignoredParameterNames = options.ignoredParameterNames;
5660
}
5761

5862
public async executeAsync(context: IOperationRunnerContext): Promise<OperationStatus> {
@@ -75,6 +79,11 @@ export class ShellOperationRunner implements IOperationRunner {
7579
// Run the operation
7680
terminal.writeLine(`Invoking: ${this.commandToRun}`);
7781

82+
// Log any ignored parameters in verbose mode
83+
if (this._ignoredParameterNames.length > 0) {
84+
terminal.writeVerboseLine(`Ignored parameters: ${this._ignoredParameterNames.join(', ')}`);
85+
}
86+
7887
const { rushConfiguration, projectFolder } = this._rushProject;
7988

8089
const { environment: initialEnvironment } = context;

libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts

Lines changed: 96 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,17 @@ export class ShellOperationRunnerPlugin implements IPhasedCommandPlugin {
3131
): Set<Operation> {
3232
const { rushConfiguration, isInitial } = context;
3333

34-
const getCustomParameterValuesForPhase: (phase: IPhase) => ReadonlyArray<string> =
35-
getCustomParameterValuesByPhase();
34+
const getCustomParameterValues: (operation: Operation) => ICustomParameterValuesForOperation =
35+
getCustomParameterValuesForOperation();
36+
3637
for (const operation of operations) {
3738
const { associatedPhase: phase, associatedProject: project } = operation;
3839

3940
if (!operation.runner) {
4041
// This is a shell command. In the future, may consider having a property on the initial operation
4142
// to specify a runner type requested in rush-project.json
42-
const customParameterValues: ReadonlyArray<string> = getCustomParameterValuesForPhase(phase);
43+
const { parameterValues: customParameterValues, ignoredParameterNames } =
44+
getCustomParameterValues(operation);
4345

4446
const displayName: string = getDisplayName(phase, project);
4547
const { name: phaseName, shellCommand } = phase;
@@ -63,6 +65,7 @@ export class ShellOperationRunnerPlugin implements IPhasedCommandPlugin {
6365
commandForHash,
6466
commandToRun,
6567
customParameterValues,
68+
ignoredParameterNames,
6669
rushConfiguration
6770
});
6871
}
@@ -82,8 +85,9 @@ export function initializeShellOperationRunner(options: {
8285
commandToRun: string | undefined;
8386
commandForHash?: string;
8487
customParameterValues: ReadonlyArray<string>;
88+
ignoredParameterNames: ReadonlyArray<string>;
8589
}): IOperationRunner {
86-
const { phase, project, commandToRun: rawCommandToRun, displayName } = options;
90+
const { phase, project, commandToRun: rawCommandToRun, displayName, ignoredParameterNames } = options;
8791

8892
if (typeof rawCommandToRun !== 'string' && phase.missingScriptBehavior === 'error') {
8993
throw new Error(
@@ -104,7 +108,8 @@ export function initializeShellOperationRunner(options: {
104108
commandForHash,
105109
displayName,
106110
phase,
107-
rushProject: project
111+
rushProject: project,
112+
ignoredParameterNames
108113
});
109114
} else {
110115
// Empty build script indicates a no-op, so use a no-op runner
@@ -116,30 +121,110 @@ export function initializeShellOperationRunner(options: {
116121
}
117122
}
118123

124+
/**
125+
* Result of filtering custom parameters for an operation
126+
*/
127+
export interface ICustomParameterValuesForOperation {
128+
/**
129+
* The serialized custom parameter values that should be included in the command
130+
*/
131+
parameterValues: ReadonlyArray<string>;
132+
/**
133+
* The names of parameters that were ignored for this operation
134+
*/
135+
ignoredParameterNames: ReadonlyArray<string>;
136+
}
137+
119138
/**
120139
* Memoizer for custom parameter values by phase
121140
* @returns A function that returns the custom parameter values for a given phase
122141
*/
123142
export function getCustomParameterValuesByPhase(): (phase: IPhase) => ReadonlyArray<string> {
124-
const customParametersByPhase: Map<IPhase, string[]> = new Map();
143+
const customParametersByPhase: Map<IPhase, Set<string>> = new Map();
125144

126145
function getCustomParameterValuesForPhase(phase: IPhase): ReadonlyArray<string> {
127-
let customParameterValues: string[] | undefined = customParametersByPhase.get(phase);
128-
if (!customParameterValues) {
129-
customParameterValues = [];
146+
let customParameterSet: Set<string> | undefined = customParametersByPhase.get(phase);
147+
if (!customParameterSet) {
148+
const customParameterValues: string[] = [];
130149
for (const tsCommandLineParameter of phase.associatedParameters) {
131150
tsCommandLineParameter.appendToArgList(customParameterValues);
132151
}
133152

134-
customParametersByPhase.set(phase, customParameterValues);
153+
customParameterSet = new Set(customParameterValues);
154+
customParametersByPhase.set(phase, customParameterSet);
135155
}
136156

137-
return customParameterValues;
157+
return Array.from(customParameterSet);
138158
}
139159

140160
return getCustomParameterValuesForPhase;
141161
}
142162

163+
/**
164+
* Gets custom parameter values for an operation, filtering out any parameters that should be ignored
165+
* based on the operation's settings.
166+
* @returns A function that returns the filtered custom parameter values and ignored parameter names for a given operation
167+
*/
168+
export function getCustomParameterValuesForOperation(): (
169+
operation: Operation
170+
) => ICustomParameterValuesForOperation {
171+
const customParametersByPhase: Map<IPhase, Set<string>> = new Map();
172+
173+
function getCustomParameterValuesForOp(operation: Operation): ICustomParameterValuesForOperation {
174+
const { associatedPhase: phase, settings } = operation;
175+
176+
// Get or compute the set of all custom parameters for this phase
177+
let customParameterSet: Set<string> | undefined = customParametersByPhase.get(phase);
178+
if (!customParameterSet) {
179+
customParameterSet = new Set();
180+
for (const tsCommandLineParameter of phase.associatedParameters) {
181+
const tempArgs: string[] = [];
182+
tsCommandLineParameter.appendToArgList(tempArgs);
183+
for (const arg of tempArgs) {
184+
customParameterSet.add(arg);
185+
}
186+
}
187+
188+
customParametersByPhase.set(phase, customParameterSet);
189+
}
190+
191+
// If there are no parameters to ignore, return early with all parameters
192+
const parameterNamesToIgnore: string[] | undefined = settings?.parameterNamesToIgnore;
193+
if (!parameterNamesToIgnore || parameterNamesToIgnore.length === 0) {
194+
return {
195+
parameterValues: Array.from(customParameterSet),
196+
ignoredParameterNames: []
197+
};
198+
}
199+
200+
// Create a set of parameter long names to ignore for fast lookup
201+
const ignoreSet: Set<string> = new Set(parameterNamesToIgnore);
202+
203+
// Filter out ignored parameters and track which ones were ignored
204+
const filteredParameterValues: string[] = [];
205+
const ignoredParameterNames: string[] = [];
206+
207+
for (const tsCommandLineParameter of phase.associatedParameters) {
208+
const parameterLongName: string = tsCommandLineParameter.longName;
209+
210+
if (ignoreSet.has(parameterLongName)) {
211+
// This parameter should be ignored for this operation
212+
ignoredParameterNames.push(parameterLongName);
213+
} else {
214+
// Include this parameter in the command
215+
tsCommandLineParameter.appendToArgList(filteredParameterValues);
216+
}
217+
}
218+
219+
return {
220+
parameterValues: filteredParameterValues,
221+
ignoredParameterNames
222+
};
223+
}
224+
225+
return getCustomParameterValuesForOp;
226+
}
227+
143228
export function formatCommand(rawCommand: string, customParameterValues: ReadonlyArray<string>): string {
144229
if (!rawCommand) {
145230
return '';

libraries/rush-lib/src/schemas/rush-project.schema.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@
110110
"ignoreChangedProjectsOnlyFlag": {
111111
"type": "boolean",
112112
"description": "If true, this operation never be skipped by the `--changed-projects-only` flag. This is useful for projects that bundle code from other packages."
113+
},
114+
"parameterNamesToIgnore": {
115+
"type": "array",
116+
"description": "An optional list of custom command-line parameter names (their parameterLongName values from command-line.json) that should be ignored when invoking the command for this operation. This allows a project to opt out of parameters that don't affect its operation, preventing unnecessary cache invalidation for this operation and its consumers.",
117+
"items": {
118+
"type": "string"
119+
},
120+
"uniqueItems": true
113121
}
114122
}
115123
}

0 commit comments

Comments
 (0)