packager: TransformCache: extract choice of root path outside of class

Summary: This is a first step towards doing an experiment I was talking about, that is, using the local directory (the directory where the config file lives) instead of the tmp dir, that is fragile.

Reviewed By: davidaurelio

Differential Revision: D5112326

fbshipit-source-id: 819636209972115e41213867f3eb78fbdf6ed9df
This commit is contained in:
Jean Lauliac 2017-05-24 07:42:28 -07:00 committed by Facebook Github Bot
parent f46eaa30cf
commit 0b4e772e3b
4 changed files with 71 additions and 66 deletions

View File

@ -34,6 +34,7 @@ var defaults = require('../../../defaults');
var sizeOf = require('image-size'); var sizeOf = require('image-size');
var fs = require('fs'); var fs = require('fs');
const os = require('os'); const os = require('os');
const path = require('path');
const {any, objectContaining} = expect; const {any, objectContaining} = expect;
@ -85,6 +86,9 @@ describe('Bundler', function() {
beforeEach(function() { beforeEach(function() {
os.cpus.mockReturnValue({length: 1}); os.cpus.mockReturnValue({length: 1});
// local directory on purpose, because it should not actually write
// anything to the disk during a unit test!
os.tmpDir.mockReturnValue(path.join(__dirname));
getDependencies = jest.fn(); getDependencies = jest.fn();
getModuleSystemDependencies = jest.fn(); getModuleSystemDependencies = jest.fn();

View File

@ -15,6 +15,7 @@
const crypto = require('crypto'); const crypto = require('crypto');
const debugRead = require('debug')('RNP:TransformCache:Read'); const debugRead = require('debug')('RNP:TransformCache:Read');
const fs = require('fs'); const fs = require('fs');
const invariant = require('fbjs/lib/invariant');
const mkdirp = require('mkdirp'); const mkdirp = require('mkdirp');
const path = require('path'); const path = require('path');
const rimraf = require('rimraf'); const rimraf = require('rimraf');
@ -29,7 +30,6 @@ import type {LocalPath} from '../node-haste/lib/toLocalPath';
type CacheFilePaths = {transformedCode: string, metadata: string}; type CacheFilePaths = {transformedCode: string, metadata: string};
export type GetTransformCacheKey = (options: {}) => string; export type GetTransformCacheKey = (options: {}) => string;
const CACHE_NAME = 'react-native-packager-cache';
const CACHE_SUB_DIR = 'cache'; const CACHE_SUB_DIR = 'cache';
export type CachedResult = { export type CachedResult = {
@ -68,11 +68,25 @@ const CACHE_FILE_MAX_LAST_ACCESS_TIME = GARBAGE_COLLECTION_PERIOD * 4;
class TransformCache { class TransformCache {
_cacheWasReset: boolean; _cacheWasReset: boolean;
_dirPath: string;
_lastCollected: ?number; _lastCollected: ?number;
_rootPath: string;
constructor() { /**
* The root path is where the data will be stored. It shouldn't contain
* other files other than the cache's own files, so it should start empty
* when the packager is first run. When doing a cache reset, it may be
* completely deleted.
*/
constructor(rootPath: string) {
this._cacheWasReset = false; this._cacheWasReset = false;
invariant(
path.isAbsolute(rootPath),
'root path of the transform cache must be absolute',
);
require('debug')('RNP:TransformCache:Dir')(
`transform cache directory: ${rootPath}`,
);
this._rootPath = rootPath;
} }
/** /**
@ -90,16 +104,14 @@ class TransformCache {
* close to each others, one of the workers is going to loose its results no * close to each others, one of the workers is going to loose its results no
* matter what. * matter what.
*/ */
writeSync( writeSync(props: {
props: { filePath: string,
filePath: string, sourceCode: string,
sourceCode: string, getTransformCacheKey: GetTransformCacheKey,
getTransformCacheKey: GetTransformCacheKey, transformOptions: WorkerOptions,
transformOptions: WorkerOptions, transformOptionsKey: string,
transformOptionsKey: string, result: CachedResult,
result: CachedResult, }): void {
},
): void {
const cacheFilePath = this._getCacheFilePaths(props); const cacheFilePath = this._getCacheFilePaths(props);
mkdirp.sync(path.dirname(cacheFilePath.transformedCode)); mkdirp.sync(path.dirname(cacheFilePath.transformedCode));
const {result} = props; const {result} = props;
@ -202,7 +214,7 @@ class TransformCache {
} }
_resetCache(reporter: Reporter) { _resetCache(reporter: Reporter) {
rimraf.sync(this._getCacheDirPath()); rimraf.sync(this._rootPath);
reporter.update({type: 'transform_cache_reset'}); reporter.update({type: 'transform_cache_reset'});
this._cacheWasReset = true; this._cacheWasReset = true;
this._lastCollected = Date.now(); this._lastCollected = Date.now();
@ -220,7 +232,7 @@ class TransformCache {
terminal.log( terminal.log(
'Error: Cleaning up the cache folder failed. Continuing anyway.', 'Error: Cleaning up the cache folder failed. Continuing anyway.',
); );
terminal.log('The cache folder is: %s', this._getCacheDirPath()); terminal.log('The cache folder is: %s', this._rootPath);
} }
this._lastCollected = Date.now(); this._lastCollected = Date.now();
} }
@ -231,7 +243,7 @@ class TransformCache {
* first. * first.
*/ */
_collectCacheIfOldSync() { _collectCacheIfOldSync() {
const cacheDirPath = this._getCacheDirPath(); const cacheDirPath = this._rootPath;
mkdirp.sync(cacheDirPath); mkdirp.sync(cacheDirPath);
const cacheCollectionFilePath = path.join(cacheDirPath, 'last_collected'); const cacheCollectionFilePath = path.join(cacheDirPath, 'last_collected');
const lastCollected = Number.parseInt( const lastCollected = Number.parseInt(
@ -255,12 +267,10 @@ class TransformCache {
* account because it would generate lots of file during development. (The * account because it would generate lots of file during development. (The
* source hash is stored in the metadata instead). * source hash is stored in the metadata instead).
*/ */
_getCacheFilePaths( _getCacheFilePaths(props: {
props: { filePath: string,
filePath: string, transformOptionsKey: string,
transformOptionsKey: string, }): CacheFilePaths {
},
): CacheFilePaths {
const hasher = crypto const hasher = crypto
.createHash('sha1') .createHash('sha1')
.update(props.filePath) .update(props.filePath)
@ -268,38 +278,9 @@ class TransformCache {
const hash = hasher.digest('hex'); const hash = hasher.digest('hex');
const prefix = hash.substr(0, 2); const prefix = hash.substr(0, 2);
const fileName = `${hash.substr(2)}`; const fileName = `${hash.substr(2)}`;
const base = path.join( const base = path.join(this._rootPath, CACHE_SUB_DIR, prefix, fileName);
this._getCacheDirPath(),
CACHE_SUB_DIR,
prefix,
fileName,
);
return {transformedCode: base, metadata: base + '.meta'}; return {transformedCode: base, metadata: base + '.meta'};
} }
/**
* If packager is running for two different directories, we don't want the
* caches to conflict with each other. `__dirname` carries that because
* packager will be, for example, installed in a different `node_modules/`
* folder for different projects.
*/
_getCacheDirPath() {
if (this._dirPath != null) {
return this._dirPath;
}
const hash = crypto.createHash('sha1').update(__dirname);
if (process.getuid != null) {
hash.update(process.getuid().toString());
}
this._dirPath = path.join(
require('os').tmpdir(),
CACHE_NAME + '-' + hash.digest('hex'),
);
require('debug')('RNP:TransformCache:Dir')(
`transform cache directory: ${this._dirPath}`,
);
return this._dirPath;
}
} }
/** /**
@ -388,15 +369,13 @@ function tryParseJSON(str: string): any {
} }
} }
function hashSourceCode( function hashSourceCode(props: {
props: { filePath: string,
filePath: string, sourceCode: string,
sourceCode: string, getTransformCacheKey: GetTransformCacheKey,
getTransformCacheKey: GetTransformCacheKey, transformOptions: WorkerOptions,
transformOptions: WorkerOptions, transformOptionsKey: string,
transformOptionsKey: string, }): string {
},
): string {
return crypto return crypto
.createHash('sha1') .createHash('sha1')
.update(props.getTransformCacheKey(props.transformOptions)) .update(props.getTransformCacheKey(props.transformOptions))

View File

@ -55,7 +55,7 @@ describe('TransformCache', () => {
beforeEach(() => { beforeEach(() => {
jest.resetModules(); jest.resetModules();
mockFS.clear(); mockFS.clear();
transformCache = new (require('../TransformCache'))(); transformCache = new (require('../TransformCache'))('/cache');
}); });
it('is caching different files and options separately', () => { it('is caching different files and options separately', () => {

View File

@ -20,6 +20,7 @@ const fs = require('fs');
const invariant = require('fbjs/lib/invariant'); const invariant = require('fbjs/lib/invariant');
const isAbsolutePath = require('absolute-path'); const isAbsolutePath = require('absolute-path');
const jsonStableStringify = require('json-stable-stringify'); const jsonStableStringify = require('json-stable-stringify');
const path = require('path');
const {join: joinPath, relative: relativePath, extname} = require('path'); const {join: joinPath, relative: relativePath, extname} = require('path');
@ -85,8 +86,6 @@ export type ConstructorArgs = {
type DocBlock = {+[key: string]: string}; type DocBlock = {+[key: string]: string};
const TRANSFORM_CACHE = new TransformCache();
class Module { class Module {
localPath: LocalPath; localPath: LocalPath;
path: string; path: string;
@ -107,6 +106,8 @@ class Module {
_readResultsByOptionsKey: Map<string, CachedReadResult>; _readResultsByOptionsKey: Map<string, CachedReadResult>;
static _transformCache: TransformCache;
constructor({ constructor({
depGraphHelpers, depGraphHelpers,
localPath, localPath,
@ -333,7 +334,7 @@ class Module {
return; return;
} }
invariant(result != null, 'missing result'); invariant(result != null, 'missing result');
TRANSFORM_CACHE.writeSync({...cacheProps, result}); Module._getTransformCache().writeSync({...cacheProps, result});
callback(undefined, result); callback(undefined, result);
}); });
} }
@ -380,7 +381,7 @@ class Module {
transformOptions, transformOptions,
transformOptionsKey, transformOptionsKey,
); );
const cachedResult = TRANSFORM_CACHE.readSync(cacheProps); const cachedResult = Module._getTransformCache().readSync(cacheProps);
if (cachedResult.result == null) { if (cachedResult.result == null) {
return { return {
result: null, result: null,
@ -468,6 +469,27 @@ class Module {
isPolyfill() { isPolyfill() {
return false; return false;
} }
/**
* If packager is running for two different directories, we don't want the
* caches to conflict with each other. `__dirname` carries that because
* packager will be, for example, installed in a different `node_modules/`
* folder for different projects.
*/
static _getTransformCache(): TransformCache {
if (this._transformCache != null) {
return this._transformCache;
}
const hash = crypto.createHash('sha1').update(__dirname);
if (process.getuid != null) {
hash.update(process.getuid().toString());
}
const tmpDir = require('os').tmpdir();
const cacheName = 'react-native-packager-cache';
const rootPath = path.join(tmpDir, cacheName + '-' + hash.digest('hex'));
this._transformCache = new TransformCache(rootPath);
return this._transformCache;
}
} }
// use weak map to speed up hash creation of known objects // use weak map to speed up hash creation of known objects