Skip to content

Commit

Permalink
[heft] Fix portability of configHash (#4955)
Browse files Browse the repository at this point in the history
Co-authored-by: David Michon <dmichon-msft@users.noreply.github.com>
  • Loading branch information
dmichon-msft and dmichon-msft authored Oct 1, 2024
1 parent ca6e96b commit dc02c17
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 28 deletions.
39 changes: 34 additions & 5 deletions apps/heft/src/operations/runners/TaskOperationRunner.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import { createHash, type Hash } from 'node:crypto';

import {
type IOperationRunner,
type IOperationRunnerContext,
Expand All @@ -10,7 +12,12 @@ import {
import { AlreadyReportedError, InternalError } from '@rushstack/node-core-library';

import type { HeftTask } from '../../pluginFramework/HeftTask';
import { copyFilesAsync, normalizeCopyOperation } from '../../plugins/CopyFilesPlugin';
import {
copyFilesAsync,
type ICopyOperation,
asAbsoluteCopyOperation,
asRelativeCopyOperation
} from '../../plugins/CopyFilesPlugin';
import { deleteFilesAsync } from '../../plugins/DeleteFilesPlugin';
import type {
HeftTaskSession,
Expand Down Expand Up @@ -51,6 +58,7 @@ export class TaskOperationRunner implements IOperationRunner {
private readonly _options: ITaskOperationRunnerOptions;

private _fileOperations: IHeftTaskFileOperations | undefined = undefined;
private _copyConfigHash: string | undefined;
private _watchFileSystemAdapter: WatchFileSystemAdapter | undefined = undefined;

public readonly silent: boolean = false;
Expand Down Expand Up @@ -99,12 +107,31 @@ export class TaskOperationRunner implements IOperationRunner {
deleteOperations: new Set()
});

// Do this here so that we only need to do it once for each run
for (const copyOperation of fileOperations.copyOperations) {
normalizeCopyOperation(rootFolderPath, copyOperation);
let copyConfigHash: string | undefined;
const { copyOperations } = fileOperations;
if (copyOperations.size > 0) {
// Do this here so that we only need to do it once for each Heft invocation
const hasher: Hash | undefined = createHash('sha256');
const absolutePathCopyOperations: Set<ICopyOperation> = new Set();
for (const copyOperation of fileOperations.copyOperations) {
// The paths in the `fileOperations` object may be either absolute or relative
// For execution we need absolute paths.
const absoluteOperation: ICopyOperation = asAbsoluteCopyOperation(rootFolderPath, copyOperation);
absolutePathCopyOperations.add(absoluteOperation);

// For portability of the hash we need relative paths.
const portableCopyOperation: ICopyOperation = asRelativeCopyOperation(
rootFolderPath,
absoluteOperation
);
hasher.update(JSON.stringify(portableCopyOperation));
}
fileOperations.copyOperations = absolutePathCopyOperations;
copyConfigHash = hasher.digest('base64');
}

this._fileOperations = fileOperations;
this._copyConfigHash = copyConfigHash;
}

const shouldRunIncremental: boolean = isWatchMode && hooks.runIncremental.isUsed();
Expand Down Expand Up @@ -177,13 +204,15 @@ export class TaskOperationRunner implements IOperationRunner {

if (this._fileOperations) {
const { copyOperations, deleteOperations } = this._fileOperations;
const copyConfigHash: string | undefined = this._copyConfigHash;

await Promise.all([
copyOperations.size > 0
copyConfigHash
? copyFilesAsync(
copyOperations,
logger.terminal,
`${taskSession.tempFolderPath}/file-copy.json`,
copyConfigHash,
isWatchMode ? getWatchFileSystemAdapter() : undefined
)
: Promise.resolve(),
Expand Down
2 changes: 1 addition & 1 deletion apps/heft/src/pluginFramework/IncrementalBuildInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export interface ISerializedIncrementalBuildInfo {
/**
* Converts an absolute path to a path relative to a base path.
*/
const makePathRelative: (absolutePath: string, basePath: string) => string =
export const makePathRelative: (absolutePath: string, basePath: string) => string =
process.platform === 'win32'
? (absolutePath: string, basePath: string) => {
// On Windows, need to normalize slashes
Expand Down
44 changes: 30 additions & 14 deletions apps/heft/src/plugins/CopyFilesPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import { createHash, type Hash } from 'crypto';
import type * as fs from 'fs';
import * as path from 'path';
import { createHash } from 'node:crypto';
import type * as fs from 'node:fs';
import * as path from 'node:path';

import { AlreadyExistsBehavior, FileSystem, Async } from '@rushstack/node-core-library';
import type { ITerminal } from '@rushstack/terminal';

import { Constants } from '../utilities/Constants';
import {
normalizeFileSelectionSpecifier,
asAbsoluteFileSelectionSpecifier,
getFileSelectionSpecifierPathsAsync,
type IFileSelectionSpecifier
} from './FileGlobSpecifier';
Expand All @@ -20,6 +20,7 @@ import type { IHeftTaskSession, IHeftTaskFileOperations } from '../pluginFramewo
import type { WatchFileSystemAdapter } from '../utilities/WatchFileSystemAdapter';
import {
type IIncrementalBuildInfo,
makePathRelative,
tryReadBuildInfoAsync,
writeBuildInfoAsync
} from '../pluginFramework/IncrementalBuildInfo';
Expand Down Expand Up @@ -79,25 +80,40 @@ interface ICopyDescriptor {
hardlink: boolean;
}

export function normalizeCopyOperation(rootFolderPath: string, copyOperation: ICopyOperation): void {
normalizeFileSelectionSpecifier(rootFolderPath, copyOperation);
copyOperation.destinationFolders = copyOperation.destinationFolders.map((x) =>
path.resolve(rootFolderPath, x)
export function asAbsoluteCopyOperation(
rootFolderPath: string,
copyOperation: ICopyOperation
): ICopyOperation {
const absoluteCopyOperation: ICopyOperation = asAbsoluteFileSelectionSpecifier(
rootFolderPath,
copyOperation
);
absoluteCopyOperation.destinationFolders = copyOperation.destinationFolders.map((folder) =>
path.resolve(rootFolderPath, folder)
);
return absoluteCopyOperation;
}

export function asRelativeCopyOperation(
rootFolderPath: string,
copyOperation: ICopyOperation
): ICopyOperation {
return {
...copyOperation,
destinationFolders: copyOperation.destinationFolders.map((folder) =>
makePathRelative(folder, rootFolderPath)
),
sourcePath: copyOperation.sourcePath && makePathRelative(copyOperation.sourcePath, rootFolderPath)
};
}

export async function copyFilesAsync(
copyOperations: Iterable<ICopyOperation>,
terminal: ITerminal,
buildInfoPath: string,
configHash: string,
watchFileSystemAdapter?: WatchFileSystemAdapter
): Promise<void> {
const hasher: Hash = createHash('sha256');
for (const copyOperation of copyOperations) {
hasher.update(JSON.stringify(copyOperation));
}
const configHash: string = hasher.digest('base64');

const copyDescriptorByDestination: Map<string, ICopyDescriptor> = await _getCopyDescriptorsAsync(
copyOperations,
watchFileSystemAdapter
Expand Down
9 changes: 6 additions & 3 deletions apps/heft/src/plugins/DeleteFilesPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { ITerminal } from '@rushstack/terminal';
import { Constants } from '../utilities/Constants';
import {
getFileSelectionSpecifierPathsAsync,
normalizeFileSelectionSpecifier,
asAbsoluteFileSelectionSpecifier,
type IFileSelectionSpecifier
} from './FileGlobSpecifier';
import type { HeftConfiguration } from '../configuration/HeftConfiguration';
Expand Down Expand Up @@ -43,11 +43,14 @@ async function _getPathsToDeleteAsync(
await Async.forEachAsync(
deleteOperations,
async (deleteOperation: IDeleteOperation) => {
normalizeFileSelectionSpecifier(rootFolderPath, deleteOperation);
const absoluteSpecifier: IDeleteOperation = asAbsoluteFileSelectionSpecifier(
rootFolderPath,
deleteOperation
);

// Glob the files under the source path and add them to the set of files to delete
const sourcePaths: Map<string, fs.Dirent> = await getFileSelectionSpecifierPathsAsync({
fileGlobSpecifier: deleteOperation,
fileGlobSpecifier: absoluteSpecifier,
includeFolders: true
});
for (const [sourcePath, dirent] of sourcePaths) {
Expand Down
14 changes: 9 additions & 5 deletions apps/heft/src/plugins/FileGlobSpecifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,17 @@ export async function getFileSelectionSpecifierPathsAsync(
return results;
}

export function normalizeFileSelectionSpecifier(
export function asAbsoluteFileSelectionSpecifier<TSpecifier extends IFileSelectionSpecifier>(
rootPath: string,
fileGlobSpecifier: IFileSelectionSpecifier
): void {
fileGlobSpecifier: TSpecifier
): TSpecifier {
const { sourcePath } = fileGlobSpecifier;
fileGlobSpecifier.sourcePath = sourcePath ? path.resolve(rootPath, sourcePath) : rootPath;
fileGlobSpecifier.includeGlobs = getIncludedGlobPatterns(fileGlobSpecifier);
return {
...fileGlobSpecifier,
sourcePath: sourcePath ? path.resolve(rootPath, sourcePath) : rootPath,
includeGlobs: getIncludedGlobPatterns(fileGlobSpecifier),
fileExtensions: undefined
};
}

function getIncludedGlobPatterns(fileGlobSpecifier: IFileSelectionSpecifier): string[] {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/heft",
"comment": "Ensure `configHash` for file copy incremental cache file is portable.",
"type": "patch"
}
],
"packageName": "@rushstack/heft"
}

0 comments on commit dc02c17

Please sign in to comment.