Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rush] Add support for specifying a system-wide build cache location. #2405

Merged
merged 4 commits into from
Dec 29, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
29 changes: 18 additions & 11 deletions apps/rush-lib/src/api/BuildCacheConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
} from '../logic/buildCache/AzureStorageBuildCacheProvider';
import { RushConfiguration } from './RushConfiguration';
import { FileSystemBuildCacheProvider } from '../logic/buildCache/FileSystemBuildCacheProvider';
import { RushGlobalFolder } from './RushGlobalFolder';
import { RushConstants } from '../logic/RushConstants';
import { RushUserConfiguration } from './RushUserConfiguration';

/**
* Describes the file structure for the "common/config/rush/build-cache.json" config file.
Expand Down Expand Up @@ -64,6 +64,12 @@ interface IFileSystemBuildCacheJson extends IBuildCacheJson {
cacheProvider: 'filesystem';
}

interface IBuildCacheConfigurationOptions {
buildCacheJson: IBuildCacheJson;
rushConfiguration: RushConfiguration;
rushUserConfiguration: RushUserConfiguration;
}

/**
* 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.
Expand All @@ -78,17 +84,15 @@ export class BuildCacheConfiguration {

public readonly cacheProvider: BuildCacheProviderBase;

private constructor(
buildCacheJson: IBuildCacheJson,
rushConfiguration: RushConfiguration,
rushGlobalFolder: RushGlobalFolder
) {
private constructor(options: IBuildCacheConfigurationOptions) {
const { buildCacheJson, rushConfiguration, rushUserConfiguration } = options;
this.projectOutputFolderNames = buildCacheJson.projectOutputFolderNames;

switch (buildCacheJson.cacheProvider) {
case 'filesystem': {
this.cacheProvider = new FileSystemBuildCacheProvider({
rushConfiguration
rushConfiguration,
rushUserConfiguration
});
break;
}
Expand All @@ -98,7 +102,6 @@ export class BuildCacheConfiguration {
const azureStorageConfigurationJson: IAzureStorageConfigurationJson =
azureStorageBuildCacheJson.azureBlobStorageConfiguration;
this.cacheProvider = new AzureStorageBuildCacheProvider({
rushGlobalFolder,
storageAccountName: azureStorageConfigurationJson.storageAccountName,
storageContainerName: azureStorageConfigurationJson.storageContainerName,
azureEnvironment: azureStorageConfigurationJson.azureEnvironment,
Expand All @@ -119,16 +122,20 @@ export class BuildCacheConfiguration {
* If the file has not been created yet, then undefined is returned.
*/
public static async loadFromDefaultPathAsync(
rushConfiguration: RushConfiguration,
rushGlobalFolder: RushGlobalFolder
rushConfiguration: RushConfiguration
): Promise<BuildCacheConfiguration | undefined> {
const jsonFilePath: string = BuildCacheConfiguration.getBuildCacheConfigFilePath(rushConfiguration);
if (FileSystem.exists(jsonFilePath)) {
const buildCacheJson: IBuildCacheJson = await JsonFile.loadAndValidateAsync(
jsonFilePath,
BuildCacheConfiguration._jsonSchema
);
return new BuildCacheConfiguration(buildCacheJson, rushConfiguration, rushGlobalFolder);
const rushUserConfiguration: RushUserConfiguration = await RushUserConfiguration.initializeAsync();
return new BuildCacheConfiguration({
buildCacheJson,
rushConfiguration,
rushUserConfiguration
});
} else {
return undefined;
}
Expand Down
2 changes: 1 addition & 1 deletion apps/rush-lib/src/api/RushGlobalFolder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class RushGlobalFolder {
if (rushGlobalFolderOverride !== undefined) {
this._rushGlobalFolder = rushGlobalFolderOverride;
} else {
this._rushGlobalFolder = path.join(Utilities.getHomeDirectory(), '.rush');
this._rushGlobalFolder = path.join(Utilities.getHomeFolder(), '.rush');
}

const normalizedNodeVersion: string = process.version.match(/^[a-z0-9\-\.]+$/i)
Expand Down
62 changes: 62 additions & 0 deletions apps/rush-lib/src/api/RushUserConfiguration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import { FileSystem, JsonFile, JsonSchema } from '@rushstack/node-core-library';
import * as path from 'path';

import { Utilities } from '../utilities/Utilities';
import { RushConstants } from '../logic/RushConstants';

interface IRushUserSettingsJson {
buildCacheFolder?: string;
}

/**
* Rush per-user configuration data.
*
* @beta
*/
export class RushUserConfiguration {
private static _schema: JsonSchema = JsonSchema.fromFile(
path.resolve(__dirname, '..', 'schemas', 'rush-user-settings.schema.json')
);

/**
* If provided, store build cache in the specified folder. Must be an absolute path.
*/
public readonly buildCacheFolder: string | undefined;

private constructor(rushUserConfigurationJson: IRushUserSettingsJson | undefined) {
this.buildCacheFolder = rushUserConfigurationJson?.buildCacheFolder;
if (this.buildCacheFolder && !path.isAbsolute(this.buildCacheFolder)) {
throw new Error('buildCacheFolder must be an absolute path');
}
}

public static async initializeAsync(): Promise<RushUserConfiguration> {
const rushUserFolderPath: string = RushUserConfiguration.getRushUserFolderPath();
const rushUserSettingsFilePath: string = path.join(rushUserFolderPath, 'settings.json');
let rushUserSettingsJson: IRushUserSettingsJson | undefined;
try {
rushUserSettingsJson = await JsonFile.loadAndValidateAsync(
rushUserSettingsFilePath,
RushUserConfiguration._schema
);
} catch (e) {
if (!FileSystem.isNotExistError(e)) {
throw e;
}
}

return new RushUserConfiguration(rushUserSettingsJson);
}

public static getRushUserFolderPath(): string {
const homeFolderPath: string = Utilities.getHomeFolder();
const rushUserSettingsFilePath: string = path.join(
homeFolderPath,
RushConstants.rushUserConfigurationFolderName
);
return rushUserSettingsFilePath;
}
}
5 changes: 1 addition & 4 deletions apps/rush-lib/src/cli/actions/UpdateCloudCredentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,7 @@ export class UpdateCloudCredentials extends BaseRushAction {

const buildCacheConfiguration:
| BuildCacheConfiguration
| undefined = await BuildCacheConfiguration.loadFromDefaultPathAsync(
this.rushConfiguration,
this.rushGlobalFolder
);
| undefined = await BuildCacheConfiguration.loadFromDefaultPathAsync(this.rushConfiguration);

if (!buildCacheConfiguration) {
const buildCacheConfigurationFilePath: string = BuildCacheConfiguration.getBuildCacheConfigFilePath(
Expand Down
5 changes: 1 addition & 4 deletions apps/rush-lib/src/cli/scriptActions/BulkScriptAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,7 @@ export class BulkScriptAction extends BaseScriptAction {

const buildCacheConfiguration:
| BuildCacheConfiguration
| undefined = await BuildCacheConfiguration.loadFromDefaultPathAsync(
this.rushConfiguration,
this.rushGlobalFolder
);
| undefined = await BuildCacheConfiguration.loadFromDefaultPathAsync(this.rushConfiguration);

const taskSelector: TaskSelector = new TaskSelector({
rushConfiguration: this.rushConfiguration,
Expand Down
9 changes: 4 additions & 5 deletions apps/rush-lib/src/logic/CredentialCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import * as path from 'path';
import { FileSystem, JsonFile, JsonSchema, LockFile } from '@rushstack/node-core-library';

import { RushGlobalFolder } from '../api/RushGlobalFolder';
import { IDisposable, Utilities } from '../utilities/Utilities';
import { RushUserConfiguration } from '../api/RushUserConfiguration';

const CACHE_FILENAME: string = 'credentials.json';
const LATEST_CREDENTIALS_JSON_VERSION: string = '0.1.0';
Expand All @@ -28,7 +28,6 @@ export interface ICredentialCacheEntry {
}

export interface ICredentialCacheOptions {
rushGlobalFolder: RushGlobalFolder;
supportEditing: boolean;
}

Expand Down Expand Up @@ -59,8 +58,8 @@ export class CredentialCache implements IDisposable {
}

public static async initializeAsync(options: ICredentialCacheOptions): Promise<CredentialCache> {
const rushGlobalFolderPath: string = options.rushGlobalFolder.path;
const cacheFilePath: string = path.join(rushGlobalFolderPath, CACHE_FILENAME);
const rushUserFolderPath: string = RushUserConfiguration.getRushUserFolderPath();
const cacheFilePath: string = path.join(rushUserFolderPath, CACHE_FILENAME);
const jsonSchema: JsonSchema = JsonSchema.fromFile(
path.resolve(__dirname, '..', 'schemas', 'credentials.schema.json')
);
Expand All @@ -76,7 +75,7 @@ export class CredentialCache implements IDisposable {

let lockfile: LockFile | undefined;
if (options.supportEditing) {
lockfile = await LockFile.acquire(rushGlobalFolderPath, `${CACHE_FILENAME}.lock`);
lockfile = await LockFile.acquire(rushUserFolderPath, `${CACHE_FILENAME}.lock`);
}

const credentialCache: CredentialCache = new CredentialCache(cacheFilePath, loadedJson, lockfile);
Expand Down
5 changes: 5 additions & 0 deletions apps/rush-lib/src/logic/RushConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,9 @@ export class RushConstants {
* crypto.createHash('sha1').update('a').update('bc').digest('hex') === crypto.createHash('sha1').update('ab').update('c').digest('hex')
*/
public static readonly hashDelimiter: string = '|';

/**
* The name of the per-user Rush configuration data folder.
*/
public static readonly rushUserConfigurationFolderName: string = '.rush-user';
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
import { AzureAuthorityHosts, DeviceCodeCredential, DeviceCodeInfo } from '@azure/identity';

import { EnvironmentConfiguration, EnvironmentVariableNames } from '../../api/EnvironmentConfiguration';
import { RushGlobalFolder } from '../../api/RushGlobalFolder';
import { CredentialCache, ICredentialCacheEntry } from '../CredentialCache';
import { URLSearchParams } from 'url';
import { RushConstants } from '../RushConstants';
Expand All @@ -31,7 +30,6 @@ export interface IAzureStorageBuildCacheProviderOptions extends IBuildCacheProvi
azureEnvironment?: AzureEnvironmentNames;
blobPrefix?: string;
isCacheWriteAllowed: boolean;
rushGlobalFolder: RushGlobalFolder;
}

const SAS_TTL_MILLISECONDS: number = 7 * 24 * 60 * 60 * 1000; // Seven days
Expand All @@ -42,7 +40,6 @@ export class AzureStorageBuildCacheProvider extends BuildCacheProviderBase {
private readonly _azureEnvironment: AzureEnvironmentNames;
private readonly _blobPrefix: string | undefined;
private readonly _isCacheWriteAllowed: boolean;
private readonly _rushGlobalFolder: RushGlobalFolder;
private __credentialCacheId: string | undefined;

private _containerClient: ContainerClient | undefined;
Expand All @@ -54,7 +51,6 @@ export class AzureStorageBuildCacheProvider extends BuildCacheProviderBase {
this._azureEnvironment = options.azureEnvironment || 'AzurePublicCloud';
this._blobPrefix = options.blobPrefix;
this._isCacheWriteAllowed = options.isCacheWriteAllowed;
this._rushGlobalFolder = options.rushGlobalFolder;

if (!(this._azureEnvironment in AzureAuthorityHosts)) {
throw new Error(
Expand Down Expand Up @@ -119,7 +115,6 @@ export class AzureStorageBuildCacheProvider extends BuildCacheProviderBase {
public async updateCachedCredentialAsync(terminal: Terminal, credential: string): Promise<void> {
await CredentialCache.usingAsync(
{
rushGlobalFolder: this._rushGlobalFolder,
supportEditing: true
},
async (credentialsCache: CredentialCache) => {
Expand All @@ -135,7 +130,6 @@ export class AzureStorageBuildCacheProvider extends BuildCacheProviderBase {

await CredentialCache.usingAsync(
{
rushGlobalFolder: this._rushGlobalFolder,
supportEditing: true
},
async (credentialsCache: CredentialCache) => {
Expand All @@ -148,7 +142,6 @@ export class AzureStorageBuildCacheProvider extends BuildCacheProviderBase {
public async deleteCachedCredentialsAsync(terminal: Terminal): Promise<void> {
await CredentialCache.usingAsync(
{
rushGlobalFolder: this._rushGlobalFolder,
supportEditing: true
},
async (credentialsCache: CredentialCache) => {
Expand All @@ -171,7 +164,6 @@ export class AzureStorageBuildCacheProvider extends BuildCacheProviderBase {
let cacheEntry: ICredentialCacheEntry | undefined;
await CredentialCache.usingAsync(
{
rushGlobalFolder: this._rushGlobalFolder,
supportEditing: false
},
(credentialsCache: CredentialCache) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import * as path from 'path';
import { AlreadyReportedError, FileSystem, Terminal } from '@rushstack/node-core-library';

import { RushConfiguration } from '../../api/RushConfiguration';
import { RushUserConfiguration } from '../../api/RushUserConfiguration';
import { BuildCacheProviderBase, IBuildCacheProviderBaseOptions } from './BuildCacheProviderBase';

export interface IFileSystemBuildCacheProviderOptions extends IBuildCacheProviderBaseOptions {
rushConfiguration: RushConfiguration;
rushUserConfiguration: RushUserConfiguration;
}

const BUILD_CACHE_FOLDER_NAME: string = 'build-cache';
Expand All @@ -18,7 +20,9 @@ export class FileSystemBuildCacheProvider extends BuildCacheProviderBase {

public constructor(options: IFileSystemBuildCacheProviderOptions) {
super(options);
this._cacheFolderPath = path.join(options.rushConfiguration.commonTempFolder, BUILD_CACHE_FOLDER_NAME);
this._cacheFolderPath =
options.rushUserConfiguration.buildCacheFolder ||
path.join(options.rushConfiguration.commonTempFolder, BUILD_CACHE_FOLDER_NAME);
}

public async tryGetCacheEntryBufferByIdAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ describe('AzureStorageBuildCacheProvider', () => {
storageAccountName: 'storage-account',
storageContainerName: 'container-name',
azureEnvironment: 'INCORRECT_AZURE_ENVIRONMENT' as AzureEnvironmentNames,
isCacheWriteAllowed: false,
rushGlobalFolder: undefined!
isCacheWriteAllowed: false
})
).toThrowErrorMatchingSnapshot();
});
Expand Down
19 changes: 19 additions & 0 deletions apps/rush-lib/src/schemas/rush-user-settings.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Rush per-user settings file",
"description": "For use with the Rush tool, this file stores user-specific settings options. See http://rushjs.io for details.",

"type": "object",
"properties": {
"$schema": {
"description": "Part of the JSON Schema standard, this optional keyword declares the URL of the schema that the file conforms to. Editors may download the schema and use it to perform syntax highlighting.",
"type": "string"
},

"buildCacheFolder": {
"type": "string",
"description": "If provided, store build cache in the specified folder. Must be an absolute path."
}
},
"additionalProperties": false
}
2 changes: 1 addition & 1 deletion apps/rush-lib/src/utilities/Utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export class Utilities {
* Get the user's home directory. On windows this looks something like "C:\users\username\" and on UNIX
* this looks something like "/home/username/"
*/
public static getHomeDirectory(): string {
public static getHomeFolder(): string {
const unresolvedUserFolder: string | undefined =
process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME'];
const dirError: string = "Unable to determine the current user's home directory";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "",
"type": "none"
}
],
"packageName": "@microsoft/rush",
"email": "[email protected]"
}