diff --git a/common/changes/@microsoft/rush/enelson-env-vars_2022-11-19-18-14.json b/common/changes/@microsoft/rush/enelson-env-vars_2022-11-19-18-14.json new file mode 100644 index 00000000000..0a005e98426 --- /dev/null +++ b/common/changes/@microsoft/rush/enelson-env-vars_2022-11-19-18-14.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Add a \"dependsOnEnvVars\" configuration option to operations in rush-project.json. The variables specified in this option are included in the cache key hash calculation.", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} diff --git a/libraries/rush-lib/src/api/RushProjectConfiguration.ts b/libraries/rush-lib/src/api/RushProjectConfiguration.ts index 5c5503de0c9..f6a04a37c56 100644 --- a/libraries/rush-lib/src/api/RushProjectConfiguration.ts +++ b/libraries/rush-lib/src/api/RushProjectConfiguration.ts @@ -65,6 +65,16 @@ export interface IOperationSettings { * the build scripts to be compatible with caching. */ disableBuildCacheForOperation?: boolean; + + /** + * An optional list of environment variables that can affect this operation. The values of + * these environment variables will become part of the hash when reading and writing the build cache. + * + * Note: generally speaking, all environment variables available to Rush are also available to any + * operations performed -- Rush assumes that environment variables do not affect build outputs unless + * you list them here. + */ + dependsOnEnvVars?: string[]; } interface IOldRushProjectJson { @@ -89,7 +99,7 @@ const RUSH_PROJECT_CONFIGURATION_FILE: ConfigurationFile = } else if (!parent) { return child; } else { - // Merge the outputFolderNames arrays + // Merge any properties that need to be merged const resultOperationSettingsByOperationName: Map = new Map(); for (const parentOperationSettings of parent) { resultOperationSettingsByOperationName.set( @@ -117,7 +127,7 @@ const RUSH_PROJECT_CONFIGURATION_FILE: ConfigurationFile = let mergedOperationSettings: IOperationSettings | undefined = resultOperationSettingsByOperationName.get(operationName); if (mergedOperationSettings) { - // The parent operation settings object already exists, so append to the outputFolderNames + // The parent operation settings object already exists const outputFolderNames: string[] | undefined = mergedOperationSettings.outputFolderNames && childOperationSettings.outputFolderNames ? [ @@ -126,10 +136,19 @@ const RUSH_PROJECT_CONFIGURATION_FILE: ConfigurationFile = ] : mergedOperationSettings.outputFolderNames || childOperationSettings.outputFolderNames; + const dependsOnEnvVars: string[] | undefined = + mergedOperationSettings.dependsOnEnvVars && childOperationSettings.dependsOnEnvVars + ? [ + ...mergedOperationSettings.dependsOnEnvVars, + ...childOperationSettings.dependsOnEnvVars + ] + : mergedOperationSettings.dependsOnEnvVars || childOperationSettings.dependsOnEnvVars; + mergedOperationSettings = { ...mergedOperationSettings, ...childOperationSettings, - outputFolderNames + ...(outputFolderNames ? { outputFolderNames } : {}), + ...(dependsOnEnvVars ? { dependsOnEnvVars } : {}) }; resultOperationSettingsByOperationName.set(operationName, mergedOperationSettings); } else { diff --git a/libraries/rush-lib/src/logic/buildCache/ProjectBuildCache.ts b/libraries/rush-lib/src/logic/buildCache/ProjectBuildCache.ts index 0c6baf791ba..3e4e1e2f855 100644 --- a/libraries/rush-lib/src/logic/buildCache/ProjectBuildCache.ts +++ b/libraries/rush-lib/src/logic/buildCache/ProjectBuildCache.ts @@ -20,6 +20,7 @@ export interface IProjectBuildCacheOptions { projectConfiguration: RushProjectConfiguration; projectOutputFolderNames: ReadonlyArray; additionalProjectOutputFilePaths?: ReadonlyArray; + additionalContext?: Record; command: string; trackedProjectFiles: string[] | undefined; projectChangeAnalyzer: ProjectChangeAnalyzer; @@ -447,6 +448,18 @@ export class ProjectBuildCache { hash.update(RushConstants.hashDelimiter); hash.update(options.command); hash.update(RushConstants.hashDelimiter); + if (options.additionalContext) { + for (const key of Object.keys(options.additionalContext).sort()) { + // Add additional context keys and values. + // + // This choice (to modiy the hash for every key regardless of whether a value is set) implies + // that just _adding_ an env var to the list of dependsOnEnvVars will modify its hash. This + // seems appropriate, because this behavior is consistent whether or not the env var happens + // to have a value. + hash.update(`${key}=${options.additionalContext[key]}`); + hash.update(RushConstants.hashDelimiter); + } + } for (const projectHash of sortedProjectStates) { hash.update(projectHash); hash.update(RushConstants.hashDelimiter); diff --git a/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts b/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts index 5f5b46a6464..147b5609d5d 100644 --- a/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts +++ b/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts @@ -444,10 +444,17 @@ export class ShellOperationRunner implements IOperationRunner { const additionalProjectOutputFilePaths: ReadonlyArray = [ OperationStateFile.getFilenameRelativeToProjectRoot(this._phase) ]; + const additionalContext: Record = {}; + if (operationSettings.dependsOnEnvVars) { + for (const varName of operationSettings.dependsOnEnvVars) { + additionalContext['$' + varName] = process.env[varName] || ''; + } + } this._projectBuildCache = await ProjectBuildCache.tryGetProjectBuildCache({ projectConfiguration, projectOutputFolderNames, additionalProjectOutputFilePaths, + additionalContext, buildCacheConfiguration: this._buildCacheConfiguration, terminal, command: this._commandToRun, diff --git a/libraries/rush-lib/src/schemas/rush-project.schema.json b/libraries/rush-lib/src/schemas/rush-project.schema.json index fb9fc596af4..6b163cfd04f 100644 --- a/libraries/rush-lib/src/schemas/rush-project.schema.json +++ b/libraries/rush-lib/src/schemas/rush-project.schema.json @@ -51,6 +51,15 @@ "uniqueItems": true }, + "dependsOnEnvVars": { + "type": "array", + "description": "Specify a list of environment variables that affect the output of this operation. If provided, the values of these variables will become part of the hash when reading and writing from cache.", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "disableBuildCacheForOperation": { "description": "Disable caching for this operation. The operation will never be restored from cache. This may be useful if this operation affects state outside of its folder.", "type": "boolean"