diff --git a/apps/rush-lib/src/api/BuildCacheConfiguration.ts b/apps/rush-lib/src/api/BuildCacheConfiguration.ts index cddf496f124..211b90ca864 100644 --- a/apps/rush-lib/src/api/BuildCacheConfiguration.ts +++ b/apps/rush-lib/src/api/BuildCacheConfiguration.ts @@ -4,7 +4,6 @@ import * as path from 'path'; import { JsonFile, JsonSchema, FileSystem } from '@rushstack/node-core-library'; -import { BuildCacheProviderBase } from '../logic/buildCache/BuildCacheProviderBase'; import { AzureEnvironmentNames, AzureStorageBuildCacheProvider @@ -12,13 +11,14 @@ import { import { RushConfiguration } from './RushConfiguration'; import { FileSystemBuildCacheProvider } from '../logic/buildCache/FileSystemBuildCacheProvider'; import { RushConstants } from '../logic/RushConstants'; +import { CloudBuildCacheProviderBase } from '../logic/buildCache/CloudBuildCacheProviderBase'; import { RushUserConfiguration } from './RushUserConfiguration'; /** * Describes the file structure for the "common/config/rush/build-cache.json" config file. */ interface IBuildCacheJson { - cacheProvider: 'azure-blob-storage' | 'filesystem'; + cacheProvider: 'azure-blob-storage' | 'local-only'; } interface IAzureBlobStorageBuildCacheJson extends IBuildCacheJson { @@ -54,10 +54,6 @@ interface IAzureStorageConfigurationJson { isCacheWriteAllowed?: boolean; } -interface IFileSystemBuildCacheJson extends IBuildCacheJson { - cacheProvider: 'filesystem'; -} - interface IBuildCacheConfigurationOptions { buildCacheJson: IBuildCacheJson; rushConfiguration: RushConfiguration; @@ -74,16 +70,19 @@ export class BuildCacheConfiguration { path.join(__dirname, '..', 'schemas', 'build-cache.schema.json') ); - public readonly cacheProvider: BuildCacheProviderBase; + public readonly localCacheProvider: FileSystemBuildCacheProvider; + public readonly cloudCacheProvider: CloudBuildCacheProviderBase | undefined; private constructor(options: IBuildCacheConfigurationOptions) { const { buildCacheJson, rushConfiguration, rushUserConfiguration } = options; + this.localCacheProvider = new FileSystemBuildCacheProvider({ + rushUserConfiguration, + rushConfiguration + }); + switch (buildCacheJson.cacheProvider) { - case 'filesystem': { - this.cacheProvider = new FileSystemBuildCacheProvider({ - rushConfiguration, - rushUserConfiguration - }); + case 'local-only': { + // Don't configure a cloud cache provider break; } @@ -91,7 +90,7 @@ export class BuildCacheConfiguration { const azureStorageBuildCacheJson: IAzureBlobStorageBuildCacheJson = buildCacheJson as IAzureBlobStorageBuildCacheJson; const azureStorageConfigurationJson: IAzureStorageConfigurationJson = azureStorageBuildCacheJson.azureBlobStorageConfiguration; - this.cacheProvider = new AzureStorageBuildCacheProvider({ + this.cloudCacheProvider = new AzureStorageBuildCacheProvider({ storageAccountName: azureStorageConfigurationJson.storageAccountName, storageContainerName: azureStorageConfigurationJson.storageContainerName, azureEnvironment: azureStorageConfigurationJson.azureEnvironment, diff --git a/apps/rush-lib/src/cli/actions/UpdateCloudCredentials.ts b/apps/rush-lib/src/cli/actions/UpdateCloudCredentials.ts index b76725c90cf..3885b698584 100644 --- a/apps/rush-lib/src/cli/actions/UpdateCloudCredentials.ts +++ b/apps/rush-lib/src/cli/actions/UpdateCloudCredentials.ts @@ -75,8 +75,12 @@ export class UpdateCloudCredentials extends BaseRushAction { `If the ${this._deleteFlag.longName} is provided, no other parameters may be provided.` ); throw new AlreadyReportedError(); + } else if (buildCacheConfiguration.cloudCacheProvider) { + await buildCacheConfiguration.cloudCacheProvider.deleteCachedCredentialsAsync(terminal); } else { - await buildCacheConfiguration.cacheProvider.deleteCachedCredentialsAsync(terminal); + terminal.writeLine( + 'A cloud build cache is not configured; there is nothing to delete.' + ); } } else if (this._interactiveModeFlag.value && this._credentialParameter.value !== undefined) { terminal.writeErrorLine( @@ -86,12 +90,21 @@ export class UpdateCloudCredentials extends BaseRushAction { ); throw new AlreadyReportedError(); } else if (this._interactiveModeFlag.value) { - await buildCacheConfiguration.cacheProvider.updateCachedCredentialInteractiveAsync(terminal); + if (buildCacheConfiguration.cloudCacheProvider) { + await buildCacheConfiguration.cloudCacheProvider.updateCachedCredentialInteractiveAsync(terminal); + } else { + terminal.writeLine('A cloud build cache is not configured. Credentials are not required.'); + } } else if (this._credentialParameter.value !== undefined) { - await buildCacheConfiguration.cacheProvider.updateCachedCredentialAsync( - terminal, - this._credentialParameter.value - ); + if (buildCacheConfiguration.cloudCacheProvider) { + await buildCacheConfiguration.cloudCacheProvider.updateCachedCredentialAsync( + terminal, + this._credentialParameter.value + ); + } else { + terminal.writeErrorLine('A cloud build cache is not configured. Credentials are not supported.'); + throw new AlreadyReportedError(); + } } else { terminal.writeErrorLine( `One of the ${this._interactiveModeFlag.longName} parameter, the ` + diff --git a/apps/rush-lib/src/logic/buildCache/AzureStorageBuildCacheProvider.ts b/apps/rush-lib/src/logic/buildCache/AzureStorageBuildCacheProvider.ts index 3b21afb9a37..8e845b436e4 100644 --- a/apps/rush-lib/src/logic/buildCache/AzureStorageBuildCacheProvider.ts +++ b/apps/rush-lib/src/logic/buildCache/AzureStorageBuildCacheProvider.ts @@ -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 { BuildCacheProviderBase, IBuildCacheProviderBaseOptions } from './BuildCacheProviderBase'; import { Terminal } from '@rushstack/node-core-library'; import { BlobClient, @@ -19,10 +18,11 @@ import { EnvironmentConfiguration, EnvironmentVariableNames } from '../../api/En import { CredentialCache, ICredentialCacheEntry } from '../CredentialCache'; import { RushConstants } from '../RushConstants'; import { Utilities } from '../../utilities/Utilities'; +import { CloudBuildCacheProviderBase } from './CloudBuildCacheProviderBase'; export type AzureEnvironmentNames = keyof typeof AzureAuthorityHosts; -export interface IAzureStorageBuildCacheProviderOptions extends IBuildCacheProviderBaseOptions { +export interface IAzureStorageBuildCacheProviderOptions { storageContainerName: string; storageAccountName: string; azureEnvironment?: AzureEnvironmentNames; @@ -32,23 +32,24 @@ export interface IAzureStorageBuildCacheProviderOptions extends IBuildCacheProvi const SAS_TTL_MILLISECONDS: number = 7 * 24 * 60 * 60 * 1000; // Seven days -export class AzureStorageBuildCacheProvider extends BuildCacheProviderBase { +export class AzureStorageBuildCacheProvider extends CloudBuildCacheProviderBase { private readonly _storageAccountName: string; private readonly _storageContainerName: string; private readonly _azureEnvironment: AzureEnvironmentNames; private readonly _blobPrefix: string | undefined; - private readonly _isCacheWriteAllowed: boolean; private __credentialCacheId: string | undefined; + public readonly isCacheWriteAllowed: boolean; + private _containerClient: ContainerClient | undefined; public constructor(options: IAzureStorageBuildCacheProviderOptions) { - super(options); + super(); this._storageAccountName = options.storageAccountName; this._storageContainerName = options.storageContainerName; this._azureEnvironment = options.azureEnvironment || 'AzurePublicCloud'; this._blobPrefix = options.blobPrefix; - this._isCacheWriteAllowed = options.isCacheWriteAllowed; + this.isCacheWriteAllowed = options.isCacheWriteAllowed; if (!(this._azureEnvironment in AzureAuthorityHosts)) { throw new Error( @@ -67,7 +68,7 @@ export class AzureStorageBuildCacheProvider extends BuildCacheProviderBase { this._storageContainerName ]; - if (this._isCacheWriteAllowed) { + if (this.isCacheWriteAllowed) { cacheIdParts.push('cacheWriteAllowed'); } @@ -104,6 +105,13 @@ export class AzureStorageBuildCacheProvider extends BuildCacheProviderBase { cacheId: string, entryStream: Buffer ): Promise { + if (!this.isCacheWriteAllowed) { + terminal.writeErrorLine( + 'Writing to Azure Blob Storage cache is not allowed in the current configuration.' + ); + return false; + } + const blobClient: BlobClient = await this._getBlobClientForCacheIdAsync(cacheId); const blockBlobClient: BlockBlobClient = blobClient.getBlockBlobClient(); try { @@ -186,9 +194,12 @@ export class AzureStorageBuildCacheProvider extends BuildCacheProviderBase { } let blobServiceClient: BlobServiceClient; - if (sasString || !this._isCacheWriteAllowed) { + if (sasString) { const connectionString: string = this._getConnectionString(sasString); blobServiceClient = BlobServiceClient.fromConnectionString(connectionString); + } else if (!this.isCacheWriteAllowed) { + // If cache write isn't allowed and we don't have a credential, assume the blob supports anonymous read + blobServiceClient = new BlobServiceClient(this._storageAccountUrl); } else { throw new Error( "An Azure Storage SAS credential hasn't been provided, or has expired. " + @@ -232,7 +243,7 @@ export class AzureStorageBuildCacheProvider extends BuildCacheProviderBase { const containerSasPermissions: ContainerSASPermissions = new ContainerSASPermissions(); containerSasPermissions.read = true; - containerSasPermissions.write = this._isCacheWriteAllowed; + containerSasPermissions.write = this.isCacheWriteAllowed; const queryParameters: SASQueryParameters = generateBlobSASQueryParameters( { diff --git a/apps/rush-lib/src/logic/buildCache/BuildCacheProviderBase.ts b/apps/rush-lib/src/logic/buildCache/BuildCacheProviderBase.ts deleted file mode 100644 index d6e25613dcf..00000000000 --- a/apps/rush-lib/src/logic/buildCache/BuildCacheProviderBase.ts +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import * as path from 'path'; -import { Path, Terminal } from '@rushstack/node-core-library'; - -import { IProjectBuildDeps } from '../taskRunner/ProjectBuilder'; -import { PackageChangeAnalyzer } from '../PackageChangeAnalyzer'; -import { ProjectBuildCache } from './ProjectBuildCache'; -import { RushProjectConfiguration } from '../../api/RushProjectConfiguration'; - -export interface IBuildCacheProviderBaseOptions {} - -export interface IGetProjectBuildCacheOptions { - projectConfiguration: RushProjectConfiguration; - command: string; - projectBuildDeps: IProjectBuildDeps | undefined; - packageChangeAnalyzer: PackageChangeAnalyzer; -} - -export abstract class BuildCacheProviderBase { - public constructor(options: IBuildCacheProviderBaseOptions) {} - - public tryGetProjectBuildCache( - terminal: Terminal, - options: IGetProjectBuildCacheOptions - ): ProjectBuildCache | undefined { - const { projectConfiguration, projectBuildDeps, command, packageChangeAnalyzer } = options; - if (!projectBuildDeps) { - return undefined; - } - - if (!this._validateProject(terminal, projectConfiguration, projectBuildDeps)) { - return undefined; - } - - return new ProjectBuildCache({ - projectConfiguration, - command, - buildCacheProvider: this, - packageChangeAnalyzer, - terminal - }); - } - - public abstract tryGetCacheEntryBufferByIdAsync( - terminal: Terminal, - cacheId: string - ): Promise; - public abstract trySetCacheEntryBufferAsync( - terminal: Terminal, - cacheId: string, - entryBuffer: Buffer - ): Promise; - public abstract updateCachedCredentialAsync(terminal: Terminal, credential: string): Promise; - public abstract updateCachedCredentialInteractiveAsync(terminal: Terminal): Promise; - public abstract deleteCachedCredentialsAsync(terminal: Terminal): Promise; - - private _validateProject( - terminal: Terminal, - projectConfiguration: RushProjectConfiguration, - projectState: IProjectBuildDeps - ): boolean { - const normalizedProjectRelativeFolder: string = Path.convertToSlashes( - projectConfiguration.project.projectRelativeFolder - ); - const outputFolders: string[] = []; - for (const outputFolderName of projectConfiguration.projectOutputFolderNames) { - outputFolders.push(`${path.posix.join(normalizedProjectRelativeFolder, outputFolderName)}/`); - } - - const inputOutputFiles: string[] = []; - for (const file of Object.keys(projectState.files)) { - for (const outputFolder of outputFolders) { - if (file.startsWith(outputFolder)) { - inputOutputFiles.push(file); - } - } - } - - if (inputOutputFiles.length > 0) { - terminal.writeWarningLine( - 'Unable to use build cache. The following files are used to calculate project state ' + - `and are considered project output: ${inputOutputFiles.join(', ')}` - ); - return false; - } else { - return true; - } - } -} diff --git a/apps/rush-lib/src/logic/buildCache/CloudBuildCacheProviderBase.ts b/apps/rush-lib/src/logic/buildCache/CloudBuildCacheProviderBase.ts new file mode 100644 index 00000000000..66033eab7bb --- /dev/null +++ b/apps/rush-lib/src/logic/buildCache/CloudBuildCacheProviderBase.ts @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { Terminal } from '@rushstack/node-core-library'; + +export abstract class CloudBuildCacheProviderBase { + public abstract readonly isCacheWriteAllowed: boolean; + + public abstract tryGetCacheEntryBufferByIdAsync( + terminal: Terminal, + cacheId: string + ): Promise; + public abstract trySetCacheEntryBufferAsync( + terminal: Terminal, + cacheId: string, + entryBuffer: Buffer + ): Promise; + public abstract updateCachedCredentialAsync(terminal: Terminal, credential: string): Promise; + public abstract updateCachedCredentialInteractiveAsync(terminal: Terminal): Promise; + public abstract deleteCachedCredentialsAsync(terminal: Terminal): Promise; +} diff --git a/apps/rush-lib/src/logic/buildCache/FileSystemBuildCacheProvider.ts b/apps/rush-lib/src/logic/buildCache/FileSystemBuildCacheProvider.ts index ec409a62f0b..47a820aee8a 100644 --- a/apps/rush-lib/src/logic/buildCache/FileSystemBuildCacheProvider.ts +++ b/apps/rush-lib/src/logic/buildCache/FileSystemBuildCacheProvider.ts @@ -2,33 +2,28 @@ // See LICENSE in the project root for license information. import * as path from 'path'; -import { AlreadyReportedError, FileSystem, Terminal } from '@rushstack/node-core-library'; +import { FileSystem } 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 { +export interface IFileSystemBuildCacheProviderOptions { rushConfiguration: RushConfiguration; rushUserConfiguration: RushUserConfiguration; } const BUILD_CACHE_FOLDER_NAME: string = 'build-cache'; -export class FileSystemBuildCacheProvider extends BuildCacheProviderBase { +export class FileSystemBuildCacheProvider { private readonly _cacheFolderPath: string; public constructor(options: IFileSystemBuildCacheProviderOptions) { - super(options); this._cacheFolderPath = options.rushUserConfiguration.buildCacheFolder || path.join(options.rushConfiguration.commonTempFolder, BUILD_CACHE_FOLDER_NAME); } - public async tryGetCacheEntryBufferByIdAsync( - terminal: Terminal, - cacheId: string - ): Promise { + public async tryGetCacheEntryBufferByIdAsync(cacheId: string): Promise { const cacheEntryFilePath: string = path.join(this._cacheFolderPath, cacheId); try { return await FileSystem.readFileToBufferAsync(cacheEntryFilePath); @@ -41,28 +36,9 @@ export class FileSystemBuildCacheProvider extends BuildCacheProviderBase { } } - public async trySetCacheEntryBufferAsync( - terminal: Terminal, - cacheId: string, - entryBuffer: Buffer - ): Promise { + public async trySetCacheEntryBufferAsync(cacheId: string, entryBuffer: Buffer): Promise { const cacheEntryFilePath: string = path.join(this._cacheFolderPath, cacheId); await FileSystem.writeFileAsync(cacheEntryFilePath, entryBuffer, { ensureFolderExists: true }); return true; } - - public async updateCachedCredentialAsync(terminal: Terminal, credential: string): Promise { - terminal.writeErrorLine('A filesystem build cache is configured. Credentials are not supported.'); - throw new AlreadyReportedError(); - } - - public async updateCachedCredentialInteractiveAsync(terminal: Terminal): Promise { - terminal.writeLine('A filesystem build cache is configured. Credentials are not required.'); - } - - public async deleteCachedCredentialsAsync(terminal: Terminal): Promise { - terminal.writeLine( - 'A filesystem build cache is configured. No credentials are stored and can be deleted.' - ); - } } diff --git a/apps/rush-lib/src/logic/buildCache/ProjectBuildCache.ts b/apps/rush-lib/src/logic/buildCache/ProjectBuildCache.ts index 59fc5b506fa..374873f0852 100644 --- a/apps/rush-lib/src/logic/buildCache/ProjectBuildCache.ts +++ b/apps/rush-lib/src/logic/buildCache/ProjectBuildCache.ts @@ -6,18 +6,22 @@ import * as path from 'path'; import type * as stream from 'stream'; import * as tar from 'tar'; import * as fs from 'fs'; -import { FileSystem, Terminal } from '@rushstack/node-core-library'; +import { FileSystem, Path, Terminal } from '@rushstack/node-core-library'; import { RushConfigurationProject } from '../../api/RushConfigurationProject'; import { PackageChangeAnalyzer } from '../PackageChangeAnalyzer'; -import { BuildCacheProviderBase } from './BuildCacheProviderBase'; import { RushProjectConfiguration } from '../../api/RushProjectConfiguration'; import { RushConstants } from '../RushConstants'; +import { BuildCacheConfiguration } from '../../api/BuildCacheConfiguration'; +import { CloudBuildCacheProviderBase } from './CloudBuildCacheProviderBase'; +import { FileSystemBuildCacheProvider } from './FileSystemBuildCacheProvider'; +import { IProjectBuildDeps } from '../taskRunner/ProjectBuilder'; -export interface IProjectBuildCacheOptions { +interface IProjectBuildCacheOptions { + buildCacheConfiguration: BuildCacheConfiguration; projectConfiguration: RushProjectConfiguration; command: string; - buildCacheProvider: BuildCacheProviderBase; + projectBuildDeps: IProjectBuildDeps | undefined; packageChangeAnalyzer: PackageChangeAnalyzer; terminal: Terminal; } @@ -25,7 +29,8 @@ export interface IProjectBuildCacheOptions { export class ProjectBuildCache { private readonly _project: RushConfigurationProject; private readonly _command: string; - private readonly _buildCacheProvider: BuildCacheProviderBase; + private readonly _localBuildCacheProvider: FileSystemBuildCacheProvider; + private readonly _cloudBuildCacheProvider: CloudBuildCacheProviderBase | undefined; private readonly _packageChangeAnalyzer: PackageChangeAnalyzer; private readonly _projectOutputFolderNames: string[]; private readonly _terminal: Terminal; @@ -52,9 +57,7 @@ export class ProjectBuildCache { return undefined; } else if (!this.__cacheId) { const projectStates: string[] = []; - const projectsThatHaveBeenProcessed: Set = new Set< - RushConfigurationProject - >(); + const projectsThatHaveBeenProcessed: Set = new Set(); let projectsToProcess: Set = new Set(); projectsToProcess.add(this._project); @@ -101,15 +104,62 @@ export class ProjectBuildCache { return this.__cacheId; } - public constructor(options: IProjectBuildCacheOptions) { + private constructor(options: IProjectBuildCacheOptions) { this._project = options.projectConfiguration.project; this._command = options.command; - this._buildCacheProvider = options.buildCacheProvider; + this._localBuildCacheProvider = options.buildCacheConfiguration.localCacheProvider; + this._cloudBuildCacheProvider = options.buildCacheConfiguration.cloudCacheProvider; this._packageChangeAnalyzer = options.packageChangeAnalyzer; this._projectOutputFolderNames = options.projectConfiguration.projectOutputFolderNames; this._terminal = options.terminal; } + public static tryGetProjectBuildCache(options: IProjectBuildCacheOptions): ProjectBuildCache | undefined { + const { terminal, projectConfiguration, projectBuildDeps } = options; + if (!projectBuildDeps) { + return undefined; + } + + if (!ProjectBuildCache._validateProject(terminal, projectConfiguration, projectBuildDeps)) { + return undefined; + } + + return new ProjectBuildCache(options); + } + + private static _validateProject( + terminal: Terminal, + projectConfiguration: RushProjectConfiguration, + projectState: IProjectBuildDeps + ): boolean { + const normalizedProjectRelativeFolder: string = Path.convertToSlashes( + projectConfiguration.project.projectRelativeFolder + ); + const outputFolders: string[] = []; + for (const outputFolderName of projectConfiguration.projectOutputFolderNames) { + outputFolders.push(`${path.posix.join(normalizedProjectRelativeFolder, outputFolderName)}/`); + } + + const inputOutputFiles: string[] = []; + for (const file of Object.keys(projectState.files)) { + for (const outputFolder of outputFolders) { + if (file.startsWith(outputFolder)) { + inputOutputFiles.push(file); + } + } + } + + if (inputOutputFiles.length > 0) { + terminal.writeWarningLine( + 'Unable to use build cache. The following files are used to calculate project state ' + + `and are considered project output: ${inputOutputFiles.join(', ')}` + ); + return false; + } else { + return true; + } + } + public async tryRestoreFromCacheAsync(): Promise { const cacheId: string | undefined = this._cacheId; if (!cacheId) { @@ -117,12 +167,32 @@ export class ProjectBuildCache { return false; } - const cacheEntryBuffer: + let cacheEntryBuffer: | Buffer - | undefined = await this._buildCacheProvider.tryGetCacheEntryBufferByIdAsync(this._terminal, cacheId); + | undefined = await this._localBuildCacheProvider.tryGetCacheEntryBufferByIdAsync(cacheId); + const foundInLocalCache: boolean = !!cacheEntryBuffer; + if (!foundInLocalCache && this._cloudBuildCacheProvider) { + this._terminal.writeVerboseLine( + 'This project was not found in the local build cache. Querying the cloud build cache.' + ); + + // No idea why ESLint is complaining about this: + // eslint-disable-next-line require-atomic-updates + cacheEntryBuffer = await this._cloudBuildCacheProvider.tryGetCacheEntryBufferByIdAsync( + this._terminal, + cacheId + ); + } + + let setLocalCacheEntryPromise: Promise | undefined; if (!cacheEntryBuffer) { this._terminal.writeVerboseLine('This project was not found in the build cache.'); return false; + } else if (!foundInLocalCache) { + setLocalCacheEntryPromise = this._localBuildCacheProvider.trySetCacheEntryBufferAsync( + cacheId, + cacheEntryBuffer + ); } this._terminal.writeLine('Build cache hit.'); @@ -138,7 +208,7 @@ export class ProjectBuildCache { ); const tarStream: stream.Writable = tar.extract({ cwd: projectFolderPath }); - const success: boolean = await new Promise( + const extractTarPromise: Promise = new Promise( (resolve: (result: boolean) => void, reject: (error: Error) => void) => { try { tarStream.on('error', (error: Error) => reject(error)); @@ -151,13 +221,31 @@ export class ProjectBuildCache { } ); - if (success) { + let restoreSuccess: boolean; + let updateLocalCacheSuccess: boolean; + if (setLocalCacheEntryPromise) { + [restoreSuccess, updateLocalCacheSuccess] = await Promise.all([ + extractTarPromise, + setLocalCacheEntryPromise + ]); + } else { + restoreSuccess = await extractTarPromise; + updateLocalCacheSuccess = true; + } + + if (restoreSuccess) { this._terminal.writeLine('Successfully restored build output from cache.'); } else { this._terminal.writeWarningLine('Unable to restore build output from cache.'); } - return success; + if (!updateLocalCacheSuccess) { + this._terminal.writeWarningLine( + 'An error occurred updating the local cache with the cloud cache data.' + ); + } + + return restoreSuccess; } public async trySetCacheEntryAsync(): Promise { @@ -209,16 +297,37 @@ export class ProjectBuildCache { return false; } - const success: boolean = await this._buildCacheProvider.trySetCacheEntryBufferAsync( - this._terminal, + const setLocalCacheEntryPromise: Promise = this._localBuildCacheProvider.trySetCacheEntryBufferAsync( cacheId, cacheEntryBuffer ); + const setCloudCacheEntryPromise: Promise | undefined = + this._cloudBuildCacheProvider?.isCacheWriteAllowed === true + ? this._cloudBuildCacheProvider.trySetCacheEntryBufferAsync(this._terminal, cacheId, cacheEntryBuffer) + : undefined; + + let updateLocalCacheSuccess: boolean; + let updateCloudCacheSuccess: boolean; + if (setCloudCacheEntryPromise) { + [updateCloudCacheSuccess, updateLocalCacheSuccess] = await Promise.all([ + setCloudCacheEntryPromise, + setLocalCacheEntryPromise + ]); + } else { + updateCloudCacheSuccess = true; + updateLocalCacheSuccess = await setLocalCacheEntryPromise; + } + + const success: boolean = updateCloudCacheSuccess && updateLocalCacheSuccess; if (success) { this._terminal.writeLine('Successfully set cache entry.'); + } else if (!updateLocalCacheSuccess && updateCloudCacheSuccess) { + this._terminal.writeWarningLine('Unable to set local cache entry.'); + } else if (updateLocalCacheSuccess && !updateCloudCacheSuccess) { + this._terminal.writeWarningLine('Unable to set cloud cache entry.'); } else { - this._terminal.writeWarningLine('Unable to set cache entry.'); + this._terminal.writeWarningLine('Unable to set both cloud and local cache entries.'); } return success; diff --git a/apps/rush-lib/src/logic/taskRunner/ProjectBuilder.ts b/apps/rush-lib/src/logic/taskRunner/ProjectBuilder.ts index 3868e9d5329..f48ae97cc4f 100644 --- a/apps/rush-lib/src/logic/taskRunner/ProjectBuilder.ts +++ b/apps/rush-lib/src/logic/taskRunner/ProjectBuilder.ts @@ -214,8 +214,10 @@ export class ProjectBuilder extends BaseBuilder { | RushProjectConfiguration | undefined = await RushProjectConfiguration.tryLoadForProjectAsync(this._rushProject, terminal); if (projectConfiguration) { - projectBuildCache = this._buildCacheConfiguration.cacheProvider.tryGetProjectBuildCache(terminal, { + projectBuildCache = ProjectBuildCache.tryGetProjectBuildCache({ projectConfiguration, + buildCacheConfiguration: this._buildCacheConfiguration, + terminal, command: this._commandToRun, projectBuildDeps: projectBuildDeps, packageChangeAnalyzer: this._packageChangeAnalyzer diff --git a/apps/rush-lib/src/schemas/build-cache.schema.json b/apps/rush-lib/src/schemas/build-cache.schema.json index bfd09e1a5d4..aa8c598fea0 100644 --- a/apps/rush-lib/src/schemas/build-cache.schema.json +++ b/apps/rush-lib/src/schemas/build-cache.schema.json @@ -15,7 +15,7 @@ "cacheProvider": { "type": "string", - "enum": ["filesystem", "azure-blob-storage"] + "enum": ["local-only", "azure-blob-storage"] } } }, @@ -26,7 +26,7 @@ "properties": { "cacheProvider": { "type": "string", - "enum": ["filesystem"] + "enum": ["local-only"] } } }, diff --git a/common/changes/@microsoft/rush/ianc-drop-filesystem-cache-provider_2020-12-22-05-58.json b/common/changes/@microsoft/rush/ianc-drop-filesystem-cache-provider_2020-12-22-05-58.json new file mode 100644 index 00000000000..8b978fe48f3 --- /dev/null +++ b/common/changes/@microsoft/rush/ianc-drop-filesystem-cache-provider_2020-12-22-05-58.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "", + "type": "none" + } + ], + "packageName": "@microsoft/rush", + "email": "iclanton@users.noreply.github.com" +} \ No newline at end of file