Skip to content

Commit dc02c17

Browse files
authored
[heft] Fix portability of configHash (#4955)
Co-authored-by: David Michon <[email protected]>
1 parent ca6e96b commit dc02c17

File tree

6 files changed

+90
-28
lines changed

6 files changed

+90
-28
lines changed

apps/heft/src/operations/runners/TaskOperationRunner.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22
// See LICENSE in the project root for license information.
33

4+
import { createHash, type Hash } from 'node:crypto';
5+
46
import {
57
type IOperationRunner,
68
type IOperationRunnerContext,
@@ -10,7 +12,12 @@ import {
1012
import { AlreadyReportedError, InternalError } from '@rushstack/node-core-library';
1113

1214
import type { HeftTask } from '../../pluginFramework/HeftTask';
13-
import { copyFilesAsync, normalizeCopyOperation } from '../../plugins/CopyFilesPlugin';
15+
import {
16+
copyFilesAsync,
17+
type ICopyOperation,
18+
asAbsoluteCopyOperation,
19+
asRelativeCopyOperation
20+
} from '../../plugins/CopyFilesPlugin';
1421
import { deleteFilesAsync } from '../../plugins/DeleteFilesPlugin';
1522
import type {
1623
HeftTaskSession,
@@ -51,6 +58,7 @@ export class TaskOperationRunner implements IOperationRunner {
5158
private readonly _options: ITaskOperationRunnerOptions;
5259

5360
private _fileOperations: IHeftTaskFileOperations | undefined = undefined;
61+
private _copyConfigHash: string | undefined;
5462
private _watchFileSystemAdapter: WatchFileSystemAdapter | undefined = undefined;
5563

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

102-
// Do this here so that we only need to do it once for each run
103-
for (const copyOperation of fileOperations.copyOperations) {
104-
normalizeCopyOperation(rootFolderPath, copyOperation);
110+
let copyConfigHash: string | undefined;
111+
const { copyOperations } = fileOperations;
112+
if (copyOperations.size > 0) {
113+
// Do this here so that we only need to do it once for each Heft invocation
114+
const hasher: Hash | undefined = createHash('sha256');
115+
const absolutePathCopyOperations: Set<ICopyOperation> = new Set();
116+
for (const copyOperation of fileOperations.copyOperations) {
117+
// The paths in the `fileOperations` object may be either absolute or relative
118+
// For execution we need absolute paths.
119+
const absoluteOperation: ICopyOperation = asAbsoluteCopyOperation(rootFolderPath, copyOperation);
120+
absolutePathCopyOperations.add(absoluteOperation);
121+
122+
// For portability of the hash we need relative paths.
123+
const portableCopyOperation: ICopyOperation = asRelativeCopyOperation(
124+
rootFolderPath,
125+
absoluteOperation
126+
);
127+
hasher.update(JSON.stringify(portableCopyOperation));
128+
}
129+
fileOperations.copyOperations = absolutePathCopyOperations;
130+
copyConfigHash = hasher.digest('base64');
105131
}
106132

107133
this._fileOperations = fileOperations;
134+
this._copyConfigHash = copyConfigHash;
108135
}
109136

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

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

181209
await Promise.all([
182-
copyOperations.size > 0
210+
copyConfigHash
183211
? copyFilesAsync(
184212
copyOperations,
185213
logger.terminal,
186214
`${taskSession.tempFolderPath}/file-copy.json`,
215+
copyConfigHash,
187216
isWatchMode ? getWatchFileSystemAdapter() : undefined
188217
)
189218
: Promise.resolve(),

apps/heft/src/pluginFramework/IncrementalBuildInfo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export interface ISerializedIncrementalBuildInfo {
5555
/**
5656
* Converts an absolute path to a path relative to a base path.
5757
*/
58-
const makePathRelative: (absolutePath: string, basePath: string) => string =
58+
export const makePathRelative: (absolutePath: string, basePath: string) => string =
5959
process.platform === 'win32'
6060
? (absolutePath: string, basePath: string) => {
6161
// On Windows, need to normalize slashes

apps/heft/src/plugins/CopyFilesPlugin.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22
// See LICENSE in the project root for license information.
33

4-
import { createHash, type Hash } from 'crypto';
5-
import type * as fs from 'fs';
6-
import * as path from 'path';
4+
import { createHash } from 'node:crypto';
5+
import type * as fs from 'node:fs';
6+
import * as path from 'node:path';
77

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

1111
import { Constants } from '../utilities/Constants';
1212
import {
13-
normalizeFileSelectionSpecifier,
13+
asAbsoluteFileSelectionSpecifier,
1414
getFileSelectionSpecifierPathsAsync,
1515
type IFileSelectionSpecifier
1616
} from './FileGlobSpecifier';
@@ -20,6 +20,7 @@ import type { IHeftTaskSession, IHeftTaskFileOperations } from '../pluginFramewo
2020
import type { WatchFileSystemAdapter } from '../utilities/WatchFileSystemAdapter';
2121
import {
2222
type IIncrementalBuildInfo,
23+
makePathRelative,
2324
tryReadBuildInfoAsync,
2425
writeBuildInfoAsync
2526
} from '../pluginFramework/IncrementalBuildInfo';
@@ -79,25 +80,40 @@ interface ICopyDescriptor {
7980
hardlink: boolean;
8081
}
8182

82-
export function normalizeCopyOperation(rootFolderPath: string, copyOperation: ICopyOperation): void {
83-
normalizeFileSelectionSpecifier(rootFolderPath, copyOperation);
84-
copyOperation.destinationFolders = copyOperation.destinationFolders.map((x) =>
85-
path.resolve(rootFolderPath, x)
83+
export function asAbsoluteCopyOperation(
84+
rootFolderPath: string,
85+
copyOperation: ICopyOperation
86+
): ICopyOperation {
87+
const absoluteCopyOperation: ICopyOperation = asAbsoluteFileSelectionSpecifier(
88+
rootFolderPath,
89+
copyOperation
8690
);
91+
absoluteCopyOperation.destinationFolders = copyOperation.destinationFolders.map((folder) =>
92+
path.resolve(rootFolderPath, folder)
93+
);
94+
return absoluteCopyOperation;
95+
}
96+
97+
export function asRelativeCopyOperation(
98+
rootFolderPath: string,
99+
copyOperation: ICopyOperation
100+
): ICopyOperation {
101+
return {
102+
...copyOperation,
103+
destinationFolders: copyOperation.destinationFolders.map((folder) =>
104+
makePathRelative(folder, rootFolderPath)
105+
),
106+
sourcePath: copyOperation.sourcePath && makePathRelative(copyOperation.sourcePath, rootFolderPath)
107+
};
87108
}
88109

89110
export async function copyFilesAsync(
90111
copyOperations: Iterable<ICopyOperation>,
91112
terminal: ITerminal,
92113
buildInfoPath: string,
114+
configHash: string,
93115
watchFileSystemAdapter?: WatchFileSystemAdapter
94116
): Promise<void> {
95-
const hasher: Hash = createHash('sha256');
96-
for (const copyOperation of copyOperations) {
97-
hasher.update(JSON.stringify(copyOperation));
98-
}
99-
const configHash: string = hasher.digest('base64');
100-
101117
const copyDescriptorByDestination: Map<string, ICopyDescriptor> = await _getCopyDescriptorsAsync(
102118
copyOperations,
103119
watchFileSystemAdapter

apps/heft/src/plugins/DeleteFilesPlugin.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { ITerminal } from '@rushstack/terminal';
88
import { Constants } from '../utilities/Constants';
99
import {
1010
getFileSelectionSpecifierPathsAsync,
11-
normalizeFileSelectionSpecifier,
11+
asAbsoluteFileSelectionSpecifier,
1212
type IFileSelectionSpecifier
1313
} from './FileGlobSpecifier';
1414
import type { HeftConfiguration } from '../configuration/HeftConfiguration';
@@ -43,11 +43,14 @@ async function _getPathsToDeleteAsync(
4343
await Async.forEachAsync(
4444
deleteOperations,
4545
async (deleteOperation: IDeleteOperation) => {
46-
normalizeFileSelectionSpecifier(rootFolderPath, deleteOperation);
46+
const absoluteSpecifier: IDeleteOperation = asAbsoluteFileSelectionSpecifier(
47+
rootFolderPath,
48+
deleteOperation
49+
);
4750

4851
// Glob the files under the source path and add them to the set of files to delete
4952
const sourcePaths: Map<string, fs.Dirent> = await getFileSelectionSpecifierPathsAsync({
50-
fileGlobSpecifier: deleteOperation,
53+
fileGlobSpecifier: absoluteSpecifier,
5154
includeFolders: true
5255
});
5356
for (const [sourcePath, dirent] of sourcePaths) {

apps/heft/src/plugins/FileGlobSpecifier.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,17 @@ export async function getFileSelectionSpecifierPathsAsync(
178178
return results;
179179
}
180180

181-
export function normalizeFileSelectionSpecifier(
181+
export function asAbsoluteFileSelectionSpecifier<TSpecifier extends IFileSelectionSpecifier>(
182182
rootPath: string,
183-
fileGlobSpecifier: IFileSelectionSpecifier
184-
): void {
183+
fileGlobSpecifier: TSpecifier
184+
): TSpecifier {
185185
const { sourcePath } = fileGlobSpecifier;
186-
fileGlobSpecifier.sourcePath = sourcePath ? path.resolve(rootPath, sourcePath) : rootPath;
187-
fileGlobSpecifier.includeGlobs = getIncludedGlobPatterns(fileGlobSpecifier);
186+
return {
187+
...fileGlobSpecifier,
188+
sourcePath: sourcePath ? path.resolve(rootPath, sourcePath) : rootPath,
189+
includeGlobs: getIncludedGlobPatterns(fileGlobSpecifier),
190+
fileExtensions: undefined
191+
};
188192
}
189193

190194
function getIncludedGlobPatterns(fileGlobSpecifier: IFileSelectionSpecifier): string[] {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@rushstack/heft",
5+
"comment": "Ensure `configHash` for file copy incremental cache file is portable.",
6+
"type": "patch"
7+
}
8+
],
9+
"packageName": "@rushstack/heft"
10+
}

0 commit comments

Comments
 (0)