build: compile entry-points in parallel (#7094)

This commit is contained in:
Jeremy Elbourn
2017-09-15 13:51:08 -07:00
committed by mmalerba
parent 5ec538eeeb
commit b296e2bd59
2 changed files with 72 additions and 19 deletions
+40 -13
View File
@@ -1,10 +1,11 @@
import {main as ngc} from '@angular/tsc-wrapped';
import {red} from 'chalk';
import {spawn} from 'child_process';
import {readFileSync, writeFileSync} from 'fs';
import {sync as glob} from 'glob';
import {join} from 'path';
import {PackageBundler} from './build-bundles';
import {buildConfig} from './build-config';
import {getSecondaryEntryPointsForPackage} from './secondary-entry-points';
import {PackageBundler} from './build-bundles';
const {packagesDir, outputDir} = buildConfig;
@@ -37,19 +38,23 @@ export class BuildPackage {
/** Path to the tsconfig file, which will be used to build the tests. */
private readonly tsconfigTests: string;
private _secondaryEntryPoints: string[];
private _secondaryEntryPointsByDepth: string[][];
/** Secondary entry-points partitioned by their build depth. */
private get secondaryEntryPointsByDepth(): string[][] {
this.cacheSecondaryEntryPoints();
return this._secondaryEntryPointsByDepth;
}
private readonly bundler = new PackageBundler(this);
/** Secondary entry points for the package. */
get secondaryEntryPoints(): string[] {
if (!this._secondaryEntryPoints) {
this._secondaryEntryPoints = getSecondaryEntryPointsForPackage(this);
}
this.cacheSecondaryEntryPoints();
return this._secondaryEntryPoints;
}
private _secondaryEntryPoints: string[];
constructor(public readonly name: string, public readonly dependencies: BuildPackage[] = []) {
this.sourceDir = join(packagesDir, name);
this.outputDir = join(outputDir, 'packages', name);
@@ -62,9 +67,13 @@ export class BuildPackage {
/** Compiles the package sources with all secondary entry points. */
async compile() {
// Walk through every secondary entry point and build the TypeScript sources sequentially.
for (const entryPoint of this.secondaryEntryPoints) {
await this._compileEntryPoint(buildTsconfigName, entryPoint);
// Compile all secondary entry-points with the same depth in parallel, and each separate depth
// group in sequence. This will look something like:
// Depth 0: coercion, platform, keycodes, bidi
// Depth 1: a11y, scrolling
// Depth 2: overlay
for (const entryPointGroup of this.secondaryEntryPointsByDepth) {
await Promise.all(entryPointGroup.map(p => this._compileEntryPoint(buildTsconfigName, p)));
}
// Compile the primary entry-point.
@@ -86,8 +95,17 @@ export class BuildPackage {
const entryPointPath = join(this.sourceDir, secondaryEntryPoint);
const entryPointTsconfigPath = join(entryPointPath, tsconfigName);
await ngc(entryPointTsconfigPath, {basePath: entryPointPath});
this.renamePrivateReExportsToBeUnique(secondaryEntryPoint);
return new Promise((resolve, reject) => {
const childProcess = spawn('./node_modules/.bin/ngc', ['-p', entryPointTsconfigPath]);
// Pipe stdout and stderr from the child process.
childProcess.stdout.on('data', (data: any) => console.log(`${data}`));
childProcess.stderr.on('data', (data: any) => console.error(red(`${data}`)));
childProcess.on('exit', (exitCode: number) => exitCode === 0 ? resolve() : reject());
})
.catch(() => console.error(red(`Failed to compile ${secondaryEntryPoint}`)))
.then(() => this.renamePrivateReExportsToBeUnique(secondaryEntryPoint));
}
/** Renames `ɵa`-style re-exports generated by Angular to be unique across compilation units. */
@@ -107,4 +125,13 @@ export class BuildPackage {
});
}
}
/** Stores the secondary entry-points for this package if they haven't been computed already. */
private cacheSecondaryEntryPoints() {
if (!this._secondaryEntryPoints) {
this._secondaryEntryPointsByDepth = getSecondaryEntryPointsForPackage(this);
this._secondaryEntryPoints =
this._secondaryEntryPointsByDepth.reduce((list, p) => list.concat(p), []);
}
}
}
+32 -6
View File
@@ -24,7 +24,7 @@ export function getSecondaryEntryPointsForPackage(pkg: BuildPackage) {
.filter(d => existsSync(join(packageDir, d, 'tsconfig-build.json')));
// Create nodes that comprise the build graph.
const buildNodes: BuildNode[] = entryPoints.map(p => ({name: p, deps: []}));
const buildNodes: BuildNode[] = entryPoints.map(p => ({name: p, deps: [], depth: 0}));
// Create a lookup for name -> build graph node.
const nodeLookup = buildNodes.reduce((lookup, node) => {
@@ -54,24 +54,27 @@ export function getSecondaryEntryPointsForPackage(pkg: BuildPackage) {
// Concatenate the build order for each node into one global build order.
// Duplicates are automatically omitted by getBuildOrder.
return buildNodes.reduce((order: string[], node) => {
const buildOrder = buildNodes.reduce((order: BuildNode[], node) => {
return [...order, ...getBuildOrder(node)];
}, []);
return partitionNodesByDepth(buildOrder).map(level => level.map(node => node.name));
}
/** Gets the build order for a given node with DFS. */
function getBuildOrder(node: BuildNode): string[] {
/** Gets the build order for node with DFS. As a side-effect, sets the depth on visited nodes. */
function getBuildOrder(node: BuildNode): BuildNode[] {
if (node.visited) {
return [];
}
let buildOrder: string[] = [];
let buildOrder: BuildNode[] = [];
for (const dep of node.deps) {
buildOrder = [...buildOrder, ...getBuildOrder(dep)];
node.depth = node.deps.reduce((maxDepth, d) => Math.max(d.depth + 1, maxDepth), -1);
}
node.visited = true;
return [...buildOrder, node.name];
return [...buildOrder, node];
}
/** Gets the names of all subdirectories for a given path. */
@@ -84,9 +87,32 @@ interface BuildNode {
name: string;
deps: BuildNode[];
visited?: boolean;
depth: number;
}
/**
* Partitions nodes into groups by depth. For example,
* [{name: a11y, depth: 1}, {name: bidi, depth: 0}, {name: platform, depth: 0}]
* =>
* [ [{name: bidi, depth: 0}, {name: platform, depth: 0}], [{name: a11y, depth: 1}] ]
*/
function partitionNodesByDepth(nodes: BuildNode[]): BuildNode[][] {
const result: BuildNode[][] = [[]];
let lastDepth = 0;
nodes.sort((a, b) => a.depth - b.depth).forEach(node => {
if (node.depth === lastDepth) {
result[result.length - 1].push(node);
} else {
result.push([node]);
lastDepth = node.depth;
}
});
return result;
}
/** Builds the command that will be executed to find all import statements for a package. */
function buildPackageImportStatementFindCommand(searchDirectory: string, packageName: string) {
if (platform() === 'win32') {