tachometer
Version:
Web benchmark runner
178 lines • 7.19 kB
JavaScript
/**
* @license
* Copyright (c) 2019 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt The complete set of authors may be found
* at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
* be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
* Google as part of the polymer project is also subject to an additional IP
* rights grant found at http://polymer.github.io/PATENTS.txt
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.prepareVersionDirectory = exports.hashStrings = exports.makeServerPlans = exports.parsePackageVersions = void 0;
const crypto = require("crypto");
const fsExtra = require("fs-extra");
const path = require("path");
const util_1 = require("./util");
/**
* Parse an array of strings of the form <package>@<version>.
*/
function parsePackageVersions(flags) {
const versions = [];
for (const flag of flags) {
const match = flag.match(/^(?:(.+)=)?(.+)@(.+)$/);
if (match === null) {
throw new Error(`Invalid package format ${flag}`);
}
const [, label, dep, version] = match;
versions.push({
label: label || `${dep}@${version}`,
dependencyOverrides: {
[dep]: version,
},
});
}
return versions;
}
exports.parsePackageVersions = parsePackageVersions;
async function makeServerPlans(benchmarkRoot, npmInstallRoot, specs) {
const keySpecs = new Map();
const keyDeps = new Map();
const defaultSpecs = [];
for (const spec of specs) {
if (spec.url.kind === 'remote') {
// No server needed for remote URLs.
continue;
}
if (spec.url.version === undefined) {
defaultSpecs.push(spec);
continue;
}
const diskPath = path.join(benchmarkRoot, spec.url.urlPath); // TODO
const kind = await util_1.fileKind(diskPath);
if (kind === undefined) {
throw new Error(`No such file or directory ${diskPath}`);
}
const originalPackageJsonPath = await findPackageJsonPath(kind === 'file' ? path.dirname(diskPath) : diskPath);
if (originalPackageJsonPath === undefined) {
throw new Error(`Could not find a package.json for ${diskPath}`);
}
const originalPackageJson = await fsExtra.readJson(originalPackageJsonPath);
// TODO Key should use the actual dependencies instead of the label.
const key = JSON.stringify([
path.dirname(originalPackageJsonPath),
spec.url.urlPath,
spec.url.version.label,
]);
let arr = keySpecs.get(key);
if (arr === undefined) {
arr = [];
keySpecs.set(key, arr);
}
arr.push(spec);
const newDeps = Object.assign(Object.assign({}, originalPackageJson.dependencies), spec.url.version.dependencyOverrides);
keyDeps.set(key, newDeps);
}
const plans = [];
if (defaultSpecs.length > 0) {
plans.push({
specs: defaultSpecs,
npmInstalls: [],
mountPoints: [
{
urlPath: `/`,
diskPath: benchmarkRoot,
},
],
});
}
for (const [key, specs] of keySpecs.entries()) {
const [packageDir, , label] = JSON.parse(key);
const dependencies = keyDeps.get(key);
if (dependencies === undefined) {
throw new Error(`Internal error: no deps for key ${key}`);
}
const installDir = path.join(npmInstallRoot, hashStrings(packageDir, label));
plans.push({
specs,
npmInstalls: [{
installDir,
packageJson: {
private: true,
dependencies,
}
}],
mountPoints: [
{
urlPath: path.posix.join('/', path.relative(benchmarkRoot, packageDir)
.replace(path.win32.sep, '/'), 'node_modules'),
diskPath: path.join(installDir, 'node_modules'),
},
{
urlPath: `/`,
diskPath: benchmarkRoot,
},
],
});
}
return plans;
}
exports.makeServerPlans = makeServerPlans;
async function findPackageJsonPath(startDir) {
let cur = path.resolve(startDir);
while (true) {
const possibleLocation = path.join(cur, 'package.json');
if (await fsExtra.pathExists(possibleLocation)) {
return possibleLocation;
}
const parentDir = path.resolve(cur, '..');
if (parentDir === cur) {
return undefined;
}
cur = parentDir;
}
}
function hashStrings(...strings) {
return crypto.createHash('sha256')
.update(JSON.stringify(strings))
.digest('hex');
}
exports.hashStrings = hashStrings;
const tachometerVersion = require(path.join(__dirname, '..', 'package.json')).version;
/**
* Write the given package.json to the given directory and run "npm install"
* in it. If the directory already exists and its package.json is identical,
* don't install, just log instead.
*/
async function prepareVersionDirectory({ installDir, packageJson }, forceCleanInstall) {
const serializedPackageJson = JSON.stringify(Object.assign({
// Include our version here so that we automatically re-install any
// existing package version install directories when tachometer updates.
__tachometer_version: tachometerVersion }, packageJson), null, 2);
const packageJsonPath = path.join(installDir, 'package.json');
if (await fsExtra.pathExists(installDir)) {
if (forceCleanInstall === false) {
const previousPackageJson = await fsExtra.readFile(packageJsonPath, 'utf8');
// Note we're comparing the serialized JSON. Node JSON serialization is
// deterministic where property order is based on property creation order.
// That's good enough for our purposes, since we know this exact code also
// wrote the previous version of this file.
if (previousPackageJson.trimRight() ===
serializedPackageJson.trimRight()) {
console.log(`\nRe-using NPM install dir because ` +
`its package.json did not change:\n ${installDir}\n`);
return;
}
console.log(`\nDeleting previous NPM install dir ` +
`because its package.json changed:\n ${installDir}`);
}
await fsExtra.emptyDir(installDir);
}
console.log(`\nRunning npm install:\n ${installDir}\n`);
await fsExtra.ensureDir(installDir);
await fsExtra.writeFile(packageJsonPath, serializedPackageJson);
await util_1.runNpm(['install'], { cwd: installDir });
}
exports.prepareVersionDirectory = prepareVersionDirectory;
//# sourceMappingURL=versions.js.map
;