forked from microsoft/rushstack
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathBuildCacheConfiguration.ts
More file actions
255 lines (232 loc) · 9.07 KB
/
BuildCacheConfiguration.ts
File metadata and controls
255 lines (232 loc) · 9.07 KB
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { createHash } from 'node:crypto';
import * as path from 'path';
import {
JsonFile,
JsonSchema,
FileSystem,
type JsonObject,
AlreadyReportedError
} from '@rushstack/node-core-library';
import type { ITerminal } from '@rushstack/terminal';
import type { RushConfiguration } from './RushConfiguration';
import { FileSystemBuildCacheProvider } from '../logic/buildCache/FileSystemBuildCacheProvider';
import { RushConstants } from '../logic/RushConstants';
import type { ICloudBuildCacheProvider } from '../logic/buildCache/ICloudBuildCacheProvider';
import { RushUserConfiguration } from './RushUserConfiguration';
import { EnvironmentConfiguration } from './EnvironmentConfiguration';
import {
CacheEntryId,
type IGenerateCacheEntryIdOptions,
type GetCacheEntryIdFunction
} from '../logic/buildCache/CacheEntryId';
import type { CloudBuildCacheProviderFactory, RushSession } from '../pluginFramework/RushSession';
import schemaJson from '../schemas/build-cache.schema.json';
/**
* Describes the file structure for the "common/config/rush/build-cache.json" config file.
*/
export interface IBaseBuildCacheJson {
buildCacheEnabled: boolean;
cacheProvider: string;
/**
* Used to specify the cache entry ID format. If this property is set, it must
* contain a `[hash]` token. It may also contain one of the following tokens:
* - `[projectName]`
* - `[projectName:normalize]`
* - `[phaseName]`
* - `[phaseName:normalize]`
* - `[phaseName:trimPrefix]`
* - `[os]`
* - `[arch]`
* @privateRemarks
* NOTE: If you update this comment, make sure to update build-cache.json in the "rush init" template.
* The token parser is in CacheEntryId.ts
*/
cacheEntryNamePattern?: string;
/**
* An optional salt to inject during calculation of the cache key. This can be used to invalidate the cache for all projects when the salt changes.
*/
cacheHashSalt?: string;
}
/**
* @public
*/
export interface ILocalBuildCacheJson extends IBaseBuildCacheJson {
readonly cacheProvider: 'local-only';
}
/**
* @beta
*/
export interface ICloudBuildCacheJson extends IBaseBuildCacheJson {
readonly cacheProvider: string;
[otherConfigKey: string]: JsonObject;
}
/**
* @beta
*/
export type IBuildCacheJson = ICloudBuildCacheJson | ILocalBuildCacheJson;
interface IBuildCacheConfigurationOptions {
buildCacheJson: IBuildCacheJson;
getCacheEntryId: GetCacheEntryIdFunction;
rushConfiguration: RushConfiguration;
rushUserConfiguration: RushUserConfiguration;
rushSession: RushSession;
cloudCacheProvider: ICloudBuildCacheProvider | undefined;
}
/**
* Use this class to load and save the "common/config/rush/build-cache.json" config file.
* This file provides configuration options for cached project build output.
* @beta
*/
export class BuildCacheConfiguration {
private static _jsonSchema: JsonSchema = JsonSchema.fromLoadedObject(schemaJson);
/**
* Indicates whether the build cache feature is enabled.
* Typically it is enabled in the build-cache.json config file.
*/
public readonly buildCacheEnabled: boolean;
/**
* Indicates whether or not writing to the cache is enabled.
*/
public cacheWriteEnabled: boolean;
/**
* Method to calculate the cache entry id for a project, phase, and project state.
*/
public readonly getCacheEntryId: GetCacheEntryIdFunction;
/**
* The provider for interacting with the local build cache.
*/
public readonly localCacheProvider: FileSystemBuildCacheProvider;
/**
* The provider for interacting with the cloud build cache, if configured.
*/
public readonly cloudCacheProvider: ICloudBuildCacheProvider | undefined;
/**
* An optional salt to inject during calculation of the cache key. This can be used to invalidate the cache for all projects when the salt changes.
*/
public readonly cacheHashSalt: string | undefined;
private constructor({
getCacheEntryId,
buildCacheJson,
rushUserConfiguration,
rushConfiguration,
cloudCacheProvider
}: IBuildCacheConfigurationOptions) {
this.buildCacheEnabled = EnvironmentConfiguration.buildCacheEnabled ?? buildCacheJson.buildCacheEnabled;
this.cacheWriteEnabled =
!!this.buildCacheEnabled && EnvironmentConfiguration.buildCacheWriteAllowed !== false;
this.getCacheEntryId = getCacheEntryId;
this.localCacheProvider = new FileSystemBuildCacheProvider({
rushUserConfiguration: rushUserConfiguration,
rushConfiguration: rushConfiguration
});
this.cloudCacheProvider = cloudCacheProvider;
this.cacheHashSalt = buildCacheJson.cacheHashSalt;
}
/**
* Attempts to load the build-cache.json data from the standard file path `common/config/rush/build-cache.json`.
* If the file has not been created yet, then undefined is returned.
*/
public static async tryLoadAsync(
terminal: ITerminal,
rushConfiguration: RushConfiguration,
rushSession: RushSession
): Promise<BuildCacheConfiguration | undefined> {
const jsonFilePath: string = BuildCacheConfiguration.getBuildCacheConfigFilePath(rushConfiguration);
if (!FileSystem.exists(jsonFilePath)) {
return undefined;
}
return await BuildCacheConfiguration._loadAsync(jsonFilePath, terminal, rushConfiguration, rushSession);
}
/**
* Loads the build-cache.json data from the standard file path `common/config/rush/build-cache.json`.
* If the file has not been created yet, or if the feature is not enabled, then an error is reported.
*/
public static async loadAndRequireEnabledAsync(
terminal: ITerminal,
rushConfiguration: RushConfiguration,
rushSession: RushSession
): Promise<BuildCacheConfiguration> {
const jsonFilePath: string = BuildCacheConfiguration.getBuildCacheConfigFilePath(rushConfiguration);
if (!FileSystem.exists(jsonFilePath)) {
terminal.writeErrorLine(
`The build cache feature is not enabled. This config file is missing:\n` + jsonFilePath
);
terminal.writeLine(`\nThe Rush website documentation has instructions for enabling the build cache.`);
throw new AlreadyReportedError();
}
const buildCacheConfiguration: BuildCacheConfiguration = await BuildCacheConfiguration._loadAsync(
jsonFilePath,
terminal,
rushConfiguration,
rushSession
);
if (!buildCacheConfiguration.buildCacheEnabled) {
terminal.writeErrorLine(
`The build cache feature is not enabled. You can enable it by editing this config file:\n` +
jsonFilePath
);
throw new AlreadyReportedError();
}
return buildCacheConfiguration;
}
/**
* Gets the absolute path to the build-cache.json file in the specified rush workspace.
*/
public static getBuildCacheConfigFilePath(rushConfiguration: RushConfiguration): string {
return path.resolve(rushConfiguration.commonRushConfigFolder, RushConstants.buildCacheFilename);
}
private static async _loadAsync(
jsonFilePath: string,
terminal: ITerminal,
rushConfiguration: RushConfiguration,
rushSession: RushSession
): Promise<BuildCacheConfiguration> {
const buildCacheJson: IBuildCacheJson = await JsonFile.loadAndValidateAsync(
jsonFilePath,
BuildCacheConfiguration._jsonSchema
);
const rushUserConfiguration: RushUserConfiguration = await RushUserConfiguration.initializeAsync();
let innerGetCacheEntryId: GetCacheEntryIdFunction;
try {
innerGetCacheEntryId = CacheEntryId.parsePattern(buildCacheJson.cacheEntryNamePattern);
} catch (e) {
terminal.writeErrorLine(
`Error parsing cache entry name pattern "${buildCacheJson.cacheEntryNamePattern}": ${e}`
);
throw new AlreadyReportedError();
}
const { cacheHashSalt = '', cacheProvider } = buildCacheJson;
const salt: string = `${RushConstants.buildCacheVersion}${cacheHashSalt ? `${RushConstants.hashDelimiter}${cacheHashSalt}` : ''}`;
const getCacheEntryId: GetCacheEntryIdFunction = (options: IGenerateCacheEntryIdOptions): string => {
const saltedHash: string = createHash('sha1')
.update(salt)
.update(options.projectStateHash)
.digest('hex');
return innerGetCacheEntryId({
phaseName: options.phaseName,
projectName: options.projectName,
projectStateHash: saltedHash
});
};
let cloudCacheProvider: ICloudBuildCacheProvider | undefined;
// Don't configure a cloud cache provider if local-only
if (cacheProvider !== 'local-only') {
const cloudCacheProviderFactory: CloudBuildCacheProviderFactory | undefined =
rushSession.getCloudBuildCacheProviderFactory(cacheProvider);
if (!cloudCacheProviderFactory) {
throw new Error(`Unexpected cache provider: ${cacheProvider}`);
}
cloudCacheProvider = await cloudCacheProviderFactory(buildCacheJson as ICloudBuildCacheJson);
}
return new BuildCacheConfiguration({
buildCacheJson,
getCacheEntryId,
rushConfiguration,
rushUserConfiguration,
rushSession,
cloudCacheProvider
});
}
}