mirror of https://github.com/status-im/metro.git
metro-bunder: caching: when using project dir, keep cache itself in temp
Summary: I wanted to settle #18056064 once and for all. This solution both uses (1) random generation of temp dir, and (2) minimal overhead in the repo. The reason random generation of temporary folder directory names should always be used is the same as why [`mktemp(3)` should never be used](http://man7.org/linux/man-pages/man3/mktemp.3.html#BUGS). The reason we don't want the cache to be fully stored locally (2) is because watchman would catch too many change events we don't care about, hitting performance. Additionally, (1) has the benefit that when one clones the repo from fresh, it'll also always start with a fresh cache. (1) uses a function equivalent to [`mkdtemp(3)`](http://man7.org/linux/man-pages/man3/mkdtemp.3.html), that we cannot use here because it's POSIX and not exposed by the Node.js API. Reviewed By: davidaurelio Differential Revision: D5199698 fbshipit-source-id: a660ebbc470e1fe90ed1ab9d0c9fda063b06f90c
This commit is contained in:
parent
4460ed5307
commit
d4bb0d3282
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
|
||||
class CannotCreateTempDirError extends Error {
|
||||
constructor() {
|
||||
super("couldn't create a temporary directory");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim a temporary directory that doesn't exist already and that cannot be
|
||||
* predicted, so that nobody can race us to it. This is similar to `mkdtemp(3)`.
|
||||
*/
|
||||
function create(pathPrefix: string): string {
|
||||
let resultPath;
|
||||
let i = 0;
|
||||
do {
|
||||
const rndBase64 = crypto.randomBytes(15).toString('base64');
|
||||
resultPath = pathPrefix + rndBase64.replace(/\//g, '-');
|
||||
if (++i === 10) {
|
||||
throw new CannotCreateTempDirError();
|
||||
}
|
||||
} while (!tryMkdirSync(resultPath));
|
||||
return resultPath;
|
||||
}
|
||||
|
||||
function tryMkdirSync(dirPath: string, mode?: number): boolean {
|
||||
try {
|
||||
fs.mkdirSync(dirPath, mode);
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error.code == 'EEXIST') {
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {CannotCreateTempDirError, create, tryMkdirSync};
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
const TempDirs = require('./TempDirs');
|
||||
|
||||
const crypto = require('crypto');
|
||||
const debugRead = require('debug')('RNP:TransformCache:Read');
|
||||
const fs = require('fs');
|
||||
|
@ -230,7 +232,8 @@ class FileBasedCache {
|
|||
}
|
||||
|
||||
_resetCache(reporter: Reporter) {
|
||||
rimraf.sync(this._rootPath);
|
||||
rimraf.sync(path.join(this._rootPath, 'last_collected'));
|
||||
rimraf.sync(path.join(this._rootPath, 'cache'));
|
||||
reporter.update({type: 'transform_cache_reset'});
|
||||
this._cacheWasReset = true;
|
||||
this._lastCollected = Date.now();
|
||||
|
@ -263,11 +266,10 @@ class FileBasedCache {
|
|||
* first.
|
||||
*/
|
||||
_collectCacheIfOldSync() {
|
||||
const cacheDirPath = this._rootPath;
|
||||
mkdirp.sync(cacheDirPath);
|
||||
const cacheCollectionFilePath = path.join(cacheDirPath, 'last_collected');
|
||||
const {_rootPath} = this;
|
||||
const cacheCollectionFilePath = path.join(_rootPath, 'last_collected');
|
||||
const lastCollected = Number.parseInt(
|
||||
tryReadFileSync(cacheCollectionFilePath),
|
||||
tryReadFileSync(cacheCollectionFilePath) || '',
|
||||
10,
|
||||
);
|
||||
if (
|
||||
|
@ -276,7 +278,7 @@ class FileBasedCache {
|
|||
) {
|
||||
return;
|
||||
}
|
||||
const effectiveCacheDirPath = path.join(cacheDirPath, CACHE_SUB_DIR);
|
||||
const effectiveCacheDirPath = path.join(_rootPath, CACHE_SUB_DIR);
|
||||
mkdirp.sync(effectiveCacheDirPath);
|
||||
collectCacheSync(effectiveCacheDirPath);
|
||||
fs.writeFileSync(cacheCollectionFilePath, Date.now().toString());
|
||||
|
@ -326,17 +328,6 @@ function collectCacheSync(dirPath: string) {
|
|||
}
|
||||
}
|
||||
|
||||
function tryReadFileSync(filePath: string): string {
|
||||
try {
|
||||
return fs.readFileSync(filePath, 'utf8');
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') {
|
||||
throw error;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function readMetadataFileSync(
|
||||
metadataFilePath: string,
|
||||
): ?{
|
||||
|
@ -446,12 +437,58 @@ function useTempDir(): TransformCache {
|
|||
const tmpDir = require('os').tmpdir();
|
||||
const cacheName = 'react-native-packager-cache';
|
||||
const rootPath = path.join(tmpDir, cacheName + '-' + hash.digest('hex'));
|
||||
mkdirp.sync(rootPath);
|
||||
return new FileBasedCache(rootPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep track of the path used for the cache in a local file so that a fresh
|
||||
* clone gets a fresh cache, and so that the temp dir used cannot be predicted.
|
||||
* Sometimes a different user build the same project, in which case we use a
|
||||
* different subfolder of the temp directory.
|
||||
*/
|
||||
function useProjectDir(projectPath: string): TransformCache {
|
||||
const metaDirPath = readMetaDirPath(projectPath);
|
||||
const uidStr = process.getuid != null ? process.getuid().toString() : 'g';
|
||||
const finalPath = path.join(metaDirPath, '_' + uidStr);
|
||||
TempDirs.tryMkdirSync(finalPath, 448 /* == 0700 */);
|
||||
return new FileBasedCache(finalPath);
|
||||
}
|
||||
|
||||
function readMetaDirPath(projectPath: string): string {
|
||||
invariant(path.isAbsolute(projectPath), 'project path must be absolute');
|
||||
return new FileBasedCache(path.join(projectPath, '.metro-bundler'));
|
||||
const metaFilePath = path.resolve(projectPath, '.metro-bundler');
|
||||
const metaFile = tryReadFileSync(metaFilePath);
|
||||
if (metaFile != null) {
|
||||
const metaDirPath = metaFile.split('\n')[0];
|
||||
if (metaDirPath != null) {
|
||||
return metaDirPath;
|
||||
}
|
||||
}
|
||||
const tmpDirPath = tmpdir();
|
||||
const metaDirPath = TempDirs.create(path.join(tmpDirPath, 'mb-'));
|
||||
fs.writeFileSync(metaFilePath, metaDirPath + '\n');
|
||||
return metaDirPath;
|
||||
}
|
||||
|
||||
function tmpdir(): string {
|
||||
const tmpDirPath = require('os').tmpdir();
|
||||
invariant(
|
||||
tmpDirPath != null && tmpDirPath.length > 0,
|
||||
'os.tmpdir() returned nothing, a valid temp dir is required',
|
||||
);
|
||||
return tmpDirPath;
|
||||
}
|
||||
|
||||
function tryReadFileSync(filePath): ?string {
|
||||
try {
|
||||
return fs.readFileSync(filePath, 'utf8');
|
||||
} catch (error) {
|
||||
if (error.code == 'ENOENT') {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {FileBasedCache, none, useTempDir, useProjectDir};
|
||||
|
|
Loading…
Reference in New Issue