Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common/reviews/api/rush-lib.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,7 @@ export interface IOperationSettings {
ignoreChangedProjectsOnlyFlag?: boolean;
operationName: string;
outputFolderNames?: string[];
parameterNamesToIgnore?: string[];
sharding?: IRushPhaseSharding;
weight?: number;
}
Expand Down
8 changes: 8 additions & 0 deletions libraries/rush-lib/src/api/RushProjectConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ export interface IOperationSettings {
* If true, this operation will never be skipped by the `--changed-projects-only` flag.
*/
ignoreChangedProjectsOnlyFlag?: boolean;

/**
* 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.
*/
parameterNamesToIgnore?: string[];
}

interface IOldRushProjectJson {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface IIPCOperationRunnerOptions {
commandForHash: string;
persist: boolean;
requestRun: OperationRequestRunCallback;
ignoredParameterNames: ReadonlyArray<string>;
}

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

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

this._persist = options.persist;
this._requestRun = options.requestRun;
this._ignoredParameterNames = options.ignoredParameterNames;
}

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

// Log any ignored parameters in verbose mode
if (this._ignoredParameterNames.length > 0) {
terminal.writeVerboseLine(`Ignored parameters: ${this._ignoredParameterNames.join(', ')}`);
}

const { rushConfiguration, projectFolder } = this._rushProject;

const { environment: initialEnvironment } = context;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import type { IPhase } from '../../api/CommandLineConfiguration';
import type {
ICreateOperationsContext,
IPhasedCommandPlugin,
Expand All @@ -14,7 +13,8 @@ import { OperationStatus } from './OperationStatus';
import {
PLUGIN_NAME as ShellOperationPluginName,
formatCommand,
getCustomParameterValuesByPhase,
getCustomParameterValuesForOperation,
type ICustomParameterValuesForOperation,
getDisplayName
} from './ShellOperationRunnerPlugin';

Expand Down Expand Up @@ -45,8 +45,8 @@ export class IPCOperationRunnerPlugin implements IPhasedCommandPlugin {

currentContext = context;

const getCustomParameterValuesForPhase: (phase: IPhase) => ReadonlyArray<string> =
getCustomParameterValuesByPhase();
const getCustomParameterValues: (operation: Operation) => ICustomParameterValuesForOperation =
getCustomParameterValuesForOperation();

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

const customParameterValues: ReadonlyArray<string> = getCustomParameterValuesForPhase(phase);
const { parameterValues: customParameterValues, ignoredParameterNames } =
getCustomParameterValues(operation);
const commandToRun: string = formatCommand(rawScript, customParameterValues);

const operationName: string = getDisplayName(phase, project);
Expand All @@ -86,6 +87,7 @@ export class IPCOperationRunnerPlugin implements IPhasedCommandPlugin {
commandToRun,
commandForHash,
persist: true,
ignoredParameterNames,
requestRun: (requestor: string, detail?: string) => {
const operationState: IOperationExecutionResult | undefined =
operationStatesByRunner.get(ipcOperationRunner);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ function spliceShards(existingOperations: Set<Operation>, context: ICreateOperat
displayName: collatorDisplayName,
rushConfiguration,
commandToRun,
customParameterValues: collatorParameters
customParameterValues: collatorParameters,
ignoredParameterNames: []
});

const shardOperationName: string = `${phase.name}:shard`;
Expand Down Expand Up @@ -207,7 +208,8 @@ function spliceShards(existingOperations: Set<Operation>, context: ICreateOperat
commandToRun: baseCommand,
customParameterValues: shardedParameters,
displayName: shardDisplayName,
rushConfiguration
rushConfiguration,
ignoredParameterNames: []
});

shardOperation.addDependency(preShardOperation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface IShellOperationRunnerOptions {
displayName: string;
commandToRun: string;
commandForHash: string;
ignoredParameterNames: ReadonlyArray<string>;
}

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

private readonly _rushProject: RushConfigurationProject;

private readonly _ignoredParameterNames: ReadonlyArray<string>;

public constructor(options: IShellOperationRunnerOptions) {
const { phase } = options;

Expand All @@ -53,6 +56,7 @@ export class ShellOperationRunner implements IOperationRunner {
this._rushProject = options.rushProject;
this.commandToRun = options.commandToRun;
this._commandForHash = options.commandForHash;
this._ignoredParameterNames = options.ignoredParameterNames;
}

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

// Log any ignored parameters in verbose mode
if (this._ignoredParameterNames.length > 0) {
terminal.writeVerboseLine(`Ignored parameters: ${this._ignoredParameterNames.join(', ')}`);
}

const { rushConfiguration, projectFolder } = this._rushProject;

const { environment: initialEnvironment } = context;
Expand Down
115 changes: 101 additions & 14 deletions libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ export class ShellOperationRunnerPlugin implements IPhasedCommandPlugin {
): Set<Operation> {
const { rushConfiguration, isInitial } = context;

const getCustomParameterValuesForPhase: (phase: IPhase) => ReadonlyArray<string> =
getCustomParameterValuesByPhase();
const getCustomParameterValues: (operation: Operation) => ICustomParameterValuesForOperation =
getCustomParameterValuesForOperation();

for (const operation of operations) {
const { associatedPhase: phase, associatedProject: project } = operation;

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

const displayName: string = getDisplayName(phase, project);
const { name: phaseName, shellCommand } = phase;
Expand All @@ -63,6 +65,7 @@ export class ShellOperationRunnerPlugin implements IPhasedCommandPlugin {
commandForHash,
commandToRun,
customParameterValues,
ignoredParameterNames,
rushConfiguration
});
}
Expand All @@ -82,8 +85,9 @@ export function initializeShellOperationRunner(options: {
commandToRun: string | undefined;
commandForHash?: string;
customParameterValues: ReadonlyArray<string>;
ignoredParameterNames: ReadonlyArray<string>;
}): IOperationRunner {
const { phase, project, commandToRun: rawCommandToRun, displayName } = options;
const { phase, project, commandToRun: rawCommandToRun, displayName, ignoredParameterNames } = options;

if (typeof rawCommandToRun !== 'string' && phase.missingScriptBehavior === 'error') {
throw new Error(
Expand All @@ -104,7 +108,8 @@ export function initializeShellOperationRunner(options: {
commandForHash,
displayName,
phase,
rushProject: project
rushProject: project,
ignoredParameterNames
});
} else {
// Empty build script indicates a no-op, so use a no-op runner
Expand All @@ -116,28 +121,110 @@ export function initializeShellOperationRunner(options: {
}
}

/**
* Result of filtering custom parameters for an operation
*/
export interface ICustomParameterValuesForOperation {
/**
* The serialized custom parameter values that should be included in the command
*/
parameterValues: ReadonlyArray<string>;
/**
* The names of parameters that were ignored for this operation
*/
ignoredParameterNames: ReadonlyArray<string>;
}

/**
* Helper function to collect all parameter arguments for a phase
*/
function collectPhaseParameterArguments(phase: IPhase): Set<string> {
const customParameterSet: Set<string> = new Set();
for (const tsCommandLineParameter of phase.associatedParameters) {
const tempArgs: string[] = [];
tsCommandLineParameter.appendToArgList(tempArgs);
for (const arg of tempArgs) {
customParameterSet.add(arg);
}
}
return customParameterSet;
}

/**
* Memoizer for custom parameter values by phase
* @returns A function that returns the custom parameter values for a given phase
*/
export function getCustomParameterValuesByPhase(): (phase: IPhase) => ReadonlyArray<string> {
const customParametersByPhase: Map<IPhase, string[]> = new Map();
const customParametersByPhase: Map<IPhase, Set<string>> = new Map();

function getCustomParameterValuesForPhase(phase: IPhase): ReadonlyArray<string> {
let customParameterValues: string[] | undefined = customParametersByPhase.get(phase);
if (!customParameterValues) {
customParameterValues = [];
for (const tsCommandLineParameter of phase.associatedParameters) {
tsCommandLineParameter.appendToArgList(customParameterValues);
let customParameterSet: Set<string> | undefined = customParametersByPhase.get(phase);
if (!customParameterSet) {
customParameterSet = collectPhaseParameterArguments(phase);
customParametersByPhase.set(phase, customParameterSet);
}

return Array.from(customParameterSet);
}

return getCustomParameterValuesForPhase;
}

/**
* Gets custom parameter values for an operation, filtering out any parameters that should be ignored
* based on the operation's settings.
* @returns A function that returns the filtered custom parameter values and ignored parameter names for a given operation
*/
export function getCustomParameterValuesForOperation(): (
operation: Operation
) => ICustomParameterValuesForOperation {
const customParametersByPhase: Map<IPhase, Set<string>> = new Map();

function getCustomParameterValuesForOp(operation: Operation): ICustomParameterValuesForOperation {
const { associatedPhase: phase, settings } = operation;

// Check if there are any parameters to ignore
const parameterNamesToIgnore: string[] | undefined = settings?.parameterNamesToIgnore;
if (!parameterNamesToIgnore || parameterNamesToIgnore.length === 0) {
// No filtering needed - use the cached parameter set for efficiency
let customParameterSet: Set<string> | undefined = customParametersByPhase.get(phase);
if (!customParameterSet) {
customParameterSet = collectPhaseParameterArguments(phase);
customParametersByPhase.set(phase, customParameterSet);
}

customParametersByPhase.set(phase, customParameterValues);
return {
parameterValues: Array.from(customParameterSet),
ignoredParameterNames: []
};
}

// Filtering is needed - we must iterate through parameter objects to check longName
// Note: We cannot use the cached parameter set here because we need access to
// the parameter objects to get their longName property for filtering
const ignoreSet: Set<string> = new Set(parameterNamesToIgnore);
const filteredParameterValues: string[] = [];
const ignoredParameterNames: string[] = [];

for (const tsCommandLineParameter of phase.associatedParameters) {
const parameterLongName: string = tsCommandLineParameter.longName;

if (ignoreSet.has(parameterLongName)) {
// This parameter should be ignored for this operation
ignoredParameterNames.push(parameterLongName);
} else {
// Include this parameter in the command
tsCommandLineParameter.appendToArgList(filteredParameterValues);
}
}

return customParameterValues;
return {
parameterValues: filteredParameterValues,
ignoredParameterNames
};
}

return getCustomParameterValuesForPhase;
return getCustomParameterValuesForOp;
}

export function formatCommand(rawCommand: string, customParameterValues: ReadonlyArray<string>): string {
Expand Down
Loading