Skip to content

Commit

Permalink
Normalise path separators in cache key
Browse files Browse the repository at this point in the history
Summary:
Changelog:
* **[Experimental]**: `metro-file-map`: Normalize root-relative paths for cross-platform cache compatibility.

Reviewed By: GijsWeterings

Differential Revision: D46829313

fbshipit-source-id: 31f3cbbff66e0bd400b0d94432dccaa441aedf2e
  • Loading branch information
motiz88 authored and facebook-github-bot committed Jun 22, 2023
1 parent 8ee654f commit d282a08
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {AbortController} from 'node-abort-controller';
const path = require('path');

jest.mock('fb-watchman', () => {
const normalizePathSep = require('../../lib/normalizePathSep').default;
const normalizePathSeparatorsToSystem =
require('../../lib/normalizePathSeparatorsToSystem').default;
const Client = jest.fn();
const endedClients = new WeakSet();
Client.prototype.command = jest.fn(function (args, callback) {
Expand All @@ -23,7 +24,9 @@ jest.mock('fb-watchman', () => {
callback(new Error('Client has ended'));
return;
}
const path = args[1] ? normalizePathSep(args[1]) : undefined;
const path = args[1]
? normalizePathSeparatorsToSystem(args[1])
: undefined;
const response = mockResponse[args[0]][path];
callback(null, response.next ? response.next().value : response);
});
Expand Down
7 changes: 4 additions & 3 deletions packages/metro-file-map/src/crawlers/watchman/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import type {WatchmanQueryResponse, WatchmanWatchResponse} from 'fb-watchman';

import H from '../../constants';
import * as fastPath from '../../lib/fast_path';
import normalizePathSep from '../../lib/normalizePathSep';
import normalizePathSeparatorsToSystem from '../../lib/normalizePathSeparatorsToSystem';
import {planQuery} from './planQuery';
import invariant from 'invariant';
import * as path from 'path';
Expand Down Expand Up @@ -291,7 +291,7 @@ module.exports = async function watchmanCrawl({
perfLogger?.point('watchmanCrawl/processResults_start');

for (const [watchRoot, response] of results) {
const fsRoot = normalizePathSep(watchRoot);
const fsRoot = normalizePathSeparatorsToSystem(watchRoot);
const relativeFsRoot = fastPath.relative(rootDir, fsRoot);
newClocks.set(
relativeFsRoot,
Expand All @@ -302,7 +302,8 @@ module.exports = async function watchmanCrawl({
);

for (const fileData of response.files) {
const filePath = fsRoot + path.sep + normalizePathSep(fileData.name);
const filePath =
fsRoot + path.sep + normalizePathSeparatorsToSystem(fileData.name);
const relativeFilePath = fastPath.relative(rootDir, filePath);
const existingFileData = previousState.files.get(relativeFilePath);

Expand Down
9 changes: 6 additions & 3 deletions packages/metro-file-map/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import checkWatchmanCapabilities from './lib/checkWatchmanCapabilities';
import deepCloneRawModuleMap from './lib/deepCloneRawModuleMap';
import * as fastPath from './lib/fast_path';
import getPlatformExtension from './lib/getPlatformExtension';
import normalizePathSep from './lib/normalizePathSep';
import normalizePathSeparatorsToSystem from './lib/normalizePathSeparatorsToSystem';
import TreeFS from './lib/TreeFS';
import HasteModuleMap from './ModuleMap';
import {Watcher} from './Watcher';
Expand Down Expand Up @@ -866,7 +866,7 @@ export default class HasteMap extends EventEmitter {
if (this._options.mocksPattern) {
const absoluteFilePath = path.join(
this._options.rootDir,
normalizePathSep(relativeFilePath),
normalizePathSeparatorsToSystem(relativeFilePath),
);
if (
this._options.mocksPattern &&
Expand Down Expand Up @@ -951,7 +951,10 @@ export default class HasteMap extends EventEmitter {
return;
}

const absoluteFilePath = path.join(root, normalizePathSep(filePath));
const absoluteFilePath = path.join(
root,
normalizePathSeparatorsToSystem(filePath),
);

// Ignore files (including symlinks) whose path matches ignorePattern
// (we don't ignore node_modules in watch mode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,24 @@

'use strict';

describe('normalizePathSep', () => {
describe('normalizePathSeparatorsToSystem', () => {
it('does nothing on posix', () => {
jest.resetModules();
jest.mock('path', () => jest.requireActual('path').posix);
const normalizePathSep = require('../normalizePathSep').default;
expect(normalizePathSep('foo/bar/baz.js')).toEqual('foo/bar/baz.js');
const normalizePathSeparatorsToSystem =
require('../normalizePathSeparatorsToSystem').default;
expect(normalizePathSeparatorsToSystem('foo/bar/baz.js')).toEqual(
'foo/bar/baz.js',
);
});

it('replace slashes on windows', () => {
jest.resetModules();
jest.mock('path', () => jest.requireActual('path').win32);
const normalizePathSep = require('../normalizePathSep').default;
expect(normalizePathSep('foo/bar/baz.js')).toEqual('foo\\bar\\baz.js');
const normalizePathSeparatorsToSystem =
require('../normalizePathSeparatorsToSystem').default;
expect(normalizePathSeparatorsToSystem('foo/bar/baz.js')).toEqual(
'foo\\bar\\baz.js',
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

import type {BuildParameters} from '../../flow-types';

import typeof PathModule from 'path';

import rootRelativeCacheKeys from '../rootRelativeCacheKeys';

const buildParameters: BuildParameters = {
Expand Down Expand Up @@ -124,3 +126,31 @@ it('returns a distinct cache key for any change', () => {
seen.set(configHash, i);
}
});

describe('cross-platform cache keys', () => {
afterEach(() => {
jest.unmock('path');
});

it('returns the same cache key for Windows and POSIX path parameters', () => {
let mockPathModule;
jest.mock('path', () => mockPathModule);

jest.resetModules();
mockPathModule = jest.requireActual<PathModule>('path').posix;
const configHashPosix = require('../rootRelativeCacheKeys').default({
...buildParameters,
rootDir: '/root',
roots: ['/root/a', '/b/c'],
}).relativeConfigHash;

jest.resetModules();
mockPathModule = jest.requireActual<PathModule>('path').win32;
const configHashWin32 = require('../rootRelativeCacheKeys').default({
...buildParameters,
rootDir: 'c:\\root',
roots: ['c:\\root\\a', 'c:\\b\\c'],
}).relativeConfigHash;
expect(configHashWin32).toEqual(configHashPosix);
});
});
21 changes: 21 additions & 0 deletions packages/metro-file-map/src/lib/normalizePathSeparatorsToPosix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict
*/

import * as path from 'path';

let normalizePathSeparatorsToPosix: (string: string) => string;
if (path.sep === '/') {
normalizePathSeparatorsToPosix = (filePath: string): string => filePath;
} else {
normalizePathSeparatorsToPosix = (filePath: string): string =>
filePath.replace(/\\/g, '/');
}

export default normalizePathSeparatorsToPosix;
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@

import * as path from 'path';

let normalizePathSep: (string: string) => string;
let normalizePathSeparatorsToSystem: (string: string) => string;
if (path.sep === '/') {
normalizePathSep = (filePath: string): string => filePath;
normalizePathSeparatorsToSystem = (filePath: string): string => filePath;
} else {
normalizePathSep = (filePath: string): string =>
normalizePathSeparatorsToSystem = (filePath: string): string =>
filePath.replace(/\//g, path.sep);
}

export default normalizePathSep;
export default normalizePathSeparatorsToSystem;
7 changes: 5 additions & 2 deletions packages/metro-file-map/src/lib/rootRelativeCacheKeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import type {BuildParameters} from '../flow-types';

import * as fastPath from './fast_path';
import normalizePathSeparatorsToPosix from './normalizePathSeparatorsToPosix';
import {createHash} from 'crypto';

function moduleCacheKey(modulePath: ?string) {
Expand All @@ -37,15 +38,17 @@ export default function rootRelativeCacheKeys(
relativeConfigHash: string,
} {
const {rootDir, ...otherParameters} = buildParameters;
const rootDirHash = createHash('md5').update(rootDir).digest('hex');
const rootDirHash = createHash('md5')
.update(normalizePathSeparatorsToPosix(rootDir))
.digest('hex');

const cacheComponents = Object.keys(otherParameters)
.sort()
.map(key => {
switch (key) {
case 'roots':
return buildParameters[key].map(root =>
fastPath.relative(rootDir, root),
normalizePathSeparatorsToPosix(fastPath.relative(rootDir, root)),
);
case 'cacheBreaker':
case 'extensions':
Expand Down

0 comments on commit d282a08

Please sign in to comment.