Skip to content

Commit 7ef45c6

Browse files
authored
[rush] Add a RUSH_BUILD_CACHE_OVERRIDE_JSON env var to override the build cache configuration. (#5154)
* Refactor BuildCacheConfiguration. * Include a RUSH_BUILD_CACHE_OVERRIDE_JSON env var. * fixup! Include a RUSH_BUILD_CACHE_OVERRIDE_JSON env var. * fixup! Include a RUSH_BUILD_CACHE_OVERRIDE_JSON env var. * Add support for RUSH_BUILD_CACHE_OVERRIDE_JSON_FILE_PATH environment variable * fixup! Add support for RUSH_BUILD_CACHE_OVERRIDE_JSON_FILE_PATH environment variable
1 parent c4dc64a commit 7ef45c6

4 files changed

Lines changed: 148 additions & 27 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@microsoft/rush",
5+
"comment": "Add support for `RUSH_BUILD_CACHE_OVERRIDE_JSON` environment variable that takes a JSON string with the same format as the `common/config/build-cache.json` file and a `RUSH_BUILD_CACHE_OVERRIDE_JSON_FILE_PATH` environment variable that takes a file path that can be used to override the build cache configuration that is normally provided by that file.",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@microsoft/rush"
10+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ export class EnvironmentConfiguration {
237237
static get allowWarningsInSuccessfulBuild(): boolean;
238238
static get buildCacheCredential(): string | undefined;
239239
static get buildCacheEnabled(): boolean | undefined;
240+
static get buildCacheOverrideJson(): string | undefined;
241+
static get buildCacheOverrideJsonFilePath(): string | undefined;
240242
static get buildCacheWriteAllowed(): boolean | undefined;
241243
static get cobuildContextId(): string | undefined;
242244
static get cobuildLeafProjectLogOnlyAllowed(): boolean | undefined;
@@ -274,6 +276,8 @@ export const EnvironmentVariableNames: {
274276
readonly RUSH_BUILD_CACHE_CREDENTIAL: "RUSH_BUILD_CACHE_CREDENTIAL";
275277
readonly RUSH_BUILD_CACHE_ENABLED: "RUSH_BUILD_CACHE_ENABLED";
276278
readonly RUSH_BUILD_CACHE_WRITE_ALLOWED: "RUSH_BUILD_CACHE_WRITE_ALLOWED";
279+
readonly RUSH_BUILD_CACHE_OVERRIDE_JSON: "RUSH_BUILD_CACHE_OVERRIDE_JSON";
280+
readonly RUSH_BUILD_CACHE_OVERRIDE_JSON_FILE_PATH: "RUSH_BUILD_CACHE_OVERRIDE_JSON_FILE_PATH";
277281
readonly RUSH_COBUILD_CONTEXT_ID: "RUSH_COBUILD_CONTEXT_ID";
278282
readonly RUSH_COBUILD_RUNNER_ID: "RUSH_COBUILD_RUNNER_ID";
279283
readonly RUSH_COBUILD_LEAF_PROJECT_LOG_ONLY_ALLOWED: "RUSH_COBUILD_LEAF_PROJECT_LOG_ONLY_ALLOWED";

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

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// See LICENSE in the project root for license information.
33

44
import { createHash } from 'node:crypto';
5-
import * as path from 'path';
65

76
import {
87
JsonFile,
@@ -18,7 +17,7 @@ import { FileSystemBuildCacheProvider } from '../logic/buildCache/FileSystemBuil
1817
import { RushConstants } from '../logic/RushConstants';
1918
import type { ICloudBuildCacheProvider } from '../logic/buildCache/ICloudBuildCacheProvider';
2019
import { RushUserConfiguration } from './RushUserConfiguration';
21-
import { EnvironmentConfiguration } from './EnvironmentConfiguration';
20+
import { EnvironmentConfiguration, EnvironmentVariableNames } from './EnvironmentConfiguration';
2221
import {
2322
CacheEntryId,
2423
type IGenerateCacheEntryIdOptions,
@@ -83,14 +82,14 @@ interface IBuildCacheConfigurationOptions {
8382
cloudCacheProvider: ICloudBuildCacheProvider | undefined;
8483
}
8584

85+
const BUILD_CACHE_JSON_SCHEMA: JsonSchema = JsonSchema.fromLoadedObject(schemaJson);
86+
8687
/**
8788
* Use this class to load and save the "common/config/rush/build-cache.json" config file.
8889
* This file provides configuration options for cached project build output.
8990
* @beta
9091
*/
9192
export class BuildCacheConfiguration {
92-
private static _jsonSchema: JsonSchema = JsonSchema.fromLoadedObject(schemaJson);
93-
9493
/**
9594
* Indicates whether the build cache feature is enabled.
9695
* Typically it is enabled in the build-cache.json config file.
@@ -146,11 +145,12 @@ export class BuildCacheConfiguration {
146145
rushConfiguration: RushConfiguration,
147146
rushSession: RushSession
148147
): Promise<BuildCacheConfiguration | undefined> {
149-
const jsonFilePath: string = BuildCacheConfiguration.getBuildCacheConfigFilePath(rushConfiguration);
150-
if (!FileSystem.exists(jsonFilePath)) {
151-
return undefined;
152-
}
153-
return await BuildCacheConfiguration._loadAsync(jsonFilePath, terminal, rushConfiguration, rushSession);
148+
const { buildCacheConfiguration } = await BuildCacheConfiguration._tryLoadInternalAsync(
149+
terminal,
150+
rushConfiguration,
151+
rushSession
152+
);
153+
return buildCacheConfiguration;
154154
}
155155

156156
/**
@@ -162,51 +162,85 @@ export class BuildCacheConfiguration {
162162
rushConfiguration: RushConfiguration,
163163
rushSession: RushSession
164164
): Promise<BuildCacheConfiguration> {
165-
const jsonFilePath: string = BuildCacheConfiguration.getBuildCacheConfigFilePath(rushConfiguration);
166-
if (!FileSystem.exists(jsonFilePath)) {
165+
const { buildCacheConfiguration, jsonFilePath } = await BuildCacheConfiguration._tryLoadInternalAsync(
166+
terminal,
167+
rushConfiguration,
168+
rushSession
169+
);
170+
171+
if (!buildCacheConfiguration) {
167172
terminal.writeErrorLine(
168173
`The build cache feature is not enabled. This config file is missing:\n` + jsonFilePath
169174
);
170175
terminal.writeLine(`\nThe Rush website documentation has instructions for enabling the build cache.`);
171176
throw new AlreadyReportedError();
172177
}
173178

174-
const buildCacheConfiguration: BuildCacheConfiguration = await BuildCacheConfiguration._loadAsync(
175-
jsonFilePath,
176-
terminal,
177-
rushConfiguration,
178-
rushSession
179-
);
180-
181179
if (!buildCacheConfiguration.buildCacheEnabled) {
182180
terminal.writeErrorLine(
183181
`The build cache feature is not enabled. You can enable it by editing this config file:\n` +
184182
jsonFilePath
185183
);
186184
throw new AlreadyReportedError();
187185
}
186+
188187
return buildCacheConfiguration;
189188
}
190189

191190
/**
192191
* Gets the absolute path to the build-cache.json file in the specified rush workspace.
193192
*/
194193
public static getBuildCacheConfigFilePath(rushConfiguration: RushConfiguration): string {
195-
return path.resolve(rushConfiguration.commonRushConfigFolder, RushConstants.buildCacheFilename);
194+
return `${rushConfiguration.commonRushConfigFolder}/${RushConstants.buildCacheFilename}`;
195+
}
196+
197+
private static async _tryLoadInternalAsync(
198+
terminal: ITerminal,
199+
rushConfiguration: RushConfiguration,
200+
rushSession: RushSession
201+
): Promise<{ buildCacheConfiguration: BuildCacheConfiguration | undefined; jsonFilePath: string }> {
202+
const jsonFilePath: string = BuildCacheConfiguration.getBuildCacheConfigFilePath(rushConfiguration);
203+
const buildCacheConfiguration: BuildCacheConfiguration | undefined =
204+
await BuildCacheConfiguration._tryLoadAsync(jsonFilePath, terminal, rushConfiguration, rushSession);
205+
return { buildCacheConfiguration, jsonFilePath };
196206
}
197207

198-
private static async _loadAsync(
208+
private static async _tryLoadAsync(
199209
jsonFilePath: string,
200210
terminal: ITerminal,
201211
rushConfiguration: RushConfiguration,
202212
rushSession: RushSession
203-
): Promise<BuildCacheConfiguration> {
204-
const buildCacheJson: IBuildCacheJson = await JsonFile.loadAndValidateAsync(
205-
jsonFilePath,
206-
BuildCacheConfiguration._jsonSchema
207-
);
208-
const rushUserConfiguration: RushUserConfiguration = await RushUserConfiguration.initializeAsync();
213+
): Promise<BuildCacheConfiguration | undefined> {
214+
let buildCacheJson: IBuildCacheJson;
215+
const buildCacheOverrideJson: string | undefined = EnvironmentConfiguration.buildCacheOverrideJson;
216+
if (buildCacheOverrideJson) {
217+
buildCacheJson = JsonFile.parseString(buildCacheOverrideJson);
218+
BUILD_CACHE_JSON_SCHEMA.validateObject(
219+
buildCacheJson,
220+
`${EnvironmentVariableNames.RUSH_BUILD_CACHE_OVERRIDE_JSON} environment variable`
221+
);
222+
} else {
223+
const buildCacheOverrideJsonFilePath: string | undefined =
224+
EnvironmentConfiguration.buildCacheOverrideJsonFilePath;
225+
if (buildCacheOverrideJsonFilePath) {
226+
buildCacheJson = await JsonFile.loadAndValidateAsync(
227+
buildCacheOverrideJsonFilePath,
228+
BUILD_CACHE_JSON_SCHEMA
229+
);
230+
} else {
231+
try {
232+
buildCacheJson = await JsonFile.loadAndValidateAsync(jsonFilePath, BUILD_CACHE_JSON_SCHEMA);
233+
} catch (e) {
234+
if (!FileSystem.isNotExistError(e)) {
235+
throw e;
236+
} else {
237+
return undefined;
238+
}
239+
}
240+
}
241+
}
209242

243+
const rushUserConfiguration: RushUserConfiguration = await RushUserConfiguration.initializeAsync();
210244
let innerGetCacheEntryId: GetCacheEntryIdFunction;
211245
try {
212246
innerGetCacheEntryId = CacheEntryId.parsePattern(buildCacheJson.cacheEntryNamePattern);
@@ -218,7 +252,9 @@ export class BuildCacheConfiguration {
218252
}
219253

220254
const { cacheHashSalt = '', cacheProvider } = buildCacheJson;
221-
const salt: string = `${RushConstants.buildCacheVersion}${cacheHashSalt ? `${RushConstants.hashDelimiter}${cacheHashSalt}` : ''}`;
255+
const salt: string = `${RushConstants.buildCacheVersion}${
256+
cacheHashSalt ? `${RushConstants.hashDelimiter}${cacheHashSalt}` : ''
257+
}`;
222258
// Extend the cache entry id with to salt the hash
223259
// This facilitates forcing cache invalidation either when the build cache version changes (new version of Rush)
224260
// or when the user-side salt changes (need to purge bad cache entries, plugins including additional files)

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,34 @@ export const EnvironmentVariableNames = {
144144
*/
145145
RUSH_BUILD_CACHE_WRITE_ALLOWED: 'RUSH_BUILD_CACHE_WRITE_ALLOWED',
146146

147+
/**
148+
* Set this environment variable to a JSON string to override the build cache configuration that normally lives
149+
* at `common/config/rush/build-cache.json`.
150+
*
151+
* This is useful for testing purposes, or for OSS repos that are have a local-only cache, but can have
152+
* a different cache configuration in CI/CD pipelines.
153+
*
154+
* @remarks
155+
* This is similar to {@link EnvironmentVariableNames.RUSH_BUILD_CACHE_OVERRIDE_JSON_FILE_PATH}, but it allows you to specify
156+
* a JSON string instead of a file path. The two environment variables are mutually exclusive, meaning you can
157+
* only use one of them at a time.
158+
*/
159+
RUSH_BUILD_CACHE_OVERRIDE_JSON: 'RUSH_BUILD_CACHE_OVERRIDE_JSON',
160+
161+
/**
162+
* Set this environment variable to the path to a `build-cache.json` file to override the build cache configuration
163+
* that normally lives at `common/config/rush/build-cache.json`.
164+
*
165+
* This is useful for testing purposes, or for OSS repos that are have a local-only cache, but can have
166+
* a different cache configuration in CI/CD pipelines.
167+
*
168+
* @remarks
169+
* This is similar to {@link EnvironmentVariableNames.RUSH_BUILD_CACHE_OVERRIDE_JSON}, but it allows you to specify
170+
* a file path instead of a JSON string. The two environment variables are mutually exclusive, meaning you can
171+
* only use one of them at a time.
172+
*/
173+
RUSH_BUILD_CACHE_OVERRIDE_JSON_FILE_PATH: 'RUSH_BUILD_CACHE_OVERRIDE_JSON_FILE_PATH',
174+
147175
/**
148176
* Setting this environment variable opts into running with cobuilds. The context id should be the same across
149177
* multiple VMs, but changed when it is a new round of cobuilds.
@@ -250,6 +278,10 @@ export class EnvironmentConfiguration {
250278

251279
private static _buildCacheWriteAllowed: boolean | undefined;
252280

281+
private static _buildCacheOverrideJson: string | undefined;
282+
283+
private static _buildCacheOverrideJsonFilePath: string | undefined;
284+
253285
private static _cobuildContextId: string | undefined;
254286

255287
private static _cobuildRunnerId: string | undefined;
@@ -360,6 +392,24 @@ export class EnvironmentConfiguration {
360392
return EnvironmentConfiguration._buildCacheWriteAllowed;
361393
}
362394

395+
/**
396+
* If set, overrides the build cache configuration that normally lives at `common/config/rush/build-cache.json`.
397+
* See {@link EnvironmentVariableNames.RUSH_BUILD_CACHE_OVERRIDE_JSON}
398+
*/
399+
public static get buildCacheOverrideJson(): string | undefined {
400+
EnvironmentConfiguration._ensureValidated();
401+
return EnvironmentConfiguration._buildCacheOverrideJson;
402+
}
403+
404+
/**
405+
* If set, overrides the build cache configuration that normally lives at `common/config/rush/build-cache.json`.
406+
* See {@link EnvironmentVariableNames.RUSH_BUILD_CACHE_OVERRIDE_JSON_FILE_PATH}
407+
*/
408+
public static get buildCacheOverrideJsonFilePath(): string | undefined {
409+
EnvironmentConfiguration._ensureValidated();
410+
return EnvironmentConfiguration._buildCacheOverrideJsonFilePath;
411+
}
412+
363413
/**
364414
* Provides a determined cobuild context id if configured
365415
* See {@link EnvironmentVariableNames.RUSH_COBUILD_CONTEXT_ID}
@@ -517,6 +567,16 @@ export class EnvironmentConfiguration {
517567
break;
518568
}
519569

570+
case EnvironmentVariableNames.RUSH_BUILD_CACHE_OVERRIDE_JSON: {
571+
EnvironmentConfiguration._buildCacheOverrideJson = value;
572+
break;
573+
}
574+
575+
case EnvironmentVariableNames.RUSH_BUILD_CACHE_OVERRIDE_JSON_FILE_PATH: {
576+
EnvironmentConfiguration._buildCacheOverrideJsonFilePath = value;
577+
break;
578+
}
579+
520580
case EnvironmentVariableNames.RUSH_COBUILD_CONTEXT_ID: {
521581
EnvironmentConfiguration._cobuildContextId = value;
522582
break;
@@ -578,6 +638,17 @@ export class EnvironmentConfiguration {
578638
);
579639
}
580640

641+
if (
642+
EnvironmentConfiguration._buildCacheOverrideJsonFilePath &&
643+
EnvironmentConfiguration._buildCacheOverrideJson
644+
) {
645+
throw new Error(
646+
`Environment variable ${EnvironmentVariableNames.RUSH_BUILD_CACHE_OVERRIDE_JSON_FILE_PATH} and ` +
647+
`${EnvironmentVariableNames.RUSH_BUILD_CACHE_OVERRIDE_JSON} are mutually exclusive. ` +
648+
`Only one may be specified.`
649+
);
650+
}
651+
581652
// See doc comment for EnvironmentConfiguration._getRushGlobalFolderOverride().
582653
EnvironmentConfiguration._rushGlobalFolderOverride =
583654
EnvironmentConfiguration._getRushGlobalFolderOverride(process.env);

0 commit comments

Comments
 (0)