From 3d946b1e9c7ef853c8bad06da5f221140250d648 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Sun, 3 Sep 2017 20:12:57 +0200 Subject: [PATCH] build: parallel secondary entry-points building All secondary entry-points of the CDK are no longer built sequentially. Secondary entry-points will be resolved in batches that can be compiled in parallel. For example: ``` [ [ 'coercion', 'platform', 'rxjs', 'keycodes', 'bidi', 'collections', 'portal' ], [ 'a11y', 'observers', 'scrolling', 'stepper', 'table' ], [ 'overlay' ] ``` This should improve the build time of the CDK package. --- tools/package-tools/build-package.ts | 34 +++++++++----- tools/package-tools/build-release.ts | 3 +- tools/package-tools/secondary-entry-points.ts | 46 +++++++++++-------- 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/tools/package-tools/build-package.ts b/tools/package-tools/build-package.ts index d90af0899736..9a908b287aca 100644 --- a/tools/package-tools/build-package.ts +++ b/tools/package-tools/build-package.ts @@ -1,9 +1,8 @@ import {join} from 'path'; -import {main as tsc} from '@angular/tsc-wrapped'; import {buildConfig} from './build-config'; import {getSecondaryEntryPointsForPackage} from './secondary-entry-points'; import {buildPrimaryEntryPointBundles, buildSecondaryEntryPointBundles} from './build-bundles'; - +import {main as ngc} from '@angular/tsc-wrapped'; const {packagesDir, outputDir} = buildConfig; @@ -31,15 +30,21 @@ export class BuildPackage { private tsconfigTests: string; /** Secondary entry points for the package. */ - get secondaryEntryPoints(): string[] { + get secondaryEntryPoints(): string[][] { if (!this._secondaryEntryPoints) { this._secondaryEntryPoints = getSecondaryEntryPointsForPackage(this); } return this._secondaryEntryPoints; } + private _secondaryEntryPoints: string[][]; - private _secondaryEntryPoints: string[]; + /** Flat list of secondary entry-points for the build package. */ + get flatSecondaryEntryPoints(): string[] { + return this.secondaryEntryPoints.reduce((entryPoints: string[], entryPointLevel: string[]) => { + return [...entryPoints, ...entryPointLevel]; + }, []); + } constructor(public packageName: string, public dependencies: BuildPackage[] = []) { this.packageRoot = join(packagesDir, packageName); @@ -55,9 +60,12 @@ export class BuildPackage { async compile() { await this._compileEntryPoint(buildTsconfigName); - // Walk through every secondary entry point and build the TypeScript sources sequentially. - for (const entryPoint of this.secondaryEntryPoints) { - await this._compileEntryPoint(buildTsconfigName, entryPoint); + // Secondary entry points are built in batches. Meaning that some packages will be built + // in parallel while other packages have to be built sequentially. + for (const entryPointsLevel of this.secondaryEntryPoints) { + await Promise.all(entryPointsLevel.map(entryPoint => { + return this._compileEntryPoint(buildTsconfigName, entryPoint); + })); } } @@ -70,17 +78,19 @@ export class BuildPackage { async createBundles() { await buildPrimaryEntryPointBundles(this.entryFilePath, this.packageName); - for (const entryPoint of this.secondaryEntryPoints) { - const entryPointEntryFilePath = join(this.packageOut, entryPoint, 'index.js'); - await buildSecondaryEntryPointBundles(entryPointEntryFilePath, this.packageName, entryPoint); + for (const entryPointLevel of this.secondaryEntryPoints) { + await Promise.all(entryPointLevel.map(entryPoint => { + const entryPointEntryPath = join(this.packageOut, entryPoint, 'index.js'); + return buildSecondaryEntryPointBundles(entryPointEntryPath, this.packageName, entryPoint); + })); } } /** Compiles the TypeScript sources of a primary or secondary entry point. */ - private async _compileEntryPoint(tsconfigName: string, secondaryEntryPoint?: string) { + private _compileEntryPoint(tsconfigName: string, secondaryEntryPoint?: string) { const entryPointPath = join(this.packageRoot, secondaryEntryPoint || ''); const entryPointTsconfigPath = join(entryPointPath, tsconfigName); - await tsc(entryPointTsconfigPath, {basePath: entryPointPath}); + return ngc(entryPointTsconfigPath, {basePath: entryPointPath}); } } diff --git a/tools/package-tools/build-release.ts b/tools/package-tools/build-release.ts index c9a3c9fdb521..4fdc4ac2f3e6 100644 --- a/tools/package-tools/build-release.ts +++ b/tools/package-tools/build-release.ts @@ -5,7 +5,6 @@ import {replaceVersionPlaceholders} from './version-placeholders'; import {inlinePackageMetadataFiles} from './metadata-inlining'; import {createTypingsReexportFile} from './typings-reexport'; import {createMetadataReexportFile} from './metadata-reexport'; -import {getSecondaryEntryPointsForPackage} from './secondary-entry-points'; import {createEntryPointPackageJson} from './entry-point-package-json'; import {buildConfig} from './build-config'; import {BuildPackage} from './build-package'; @@ -47,7 +46,7 @@ export function composeRelease(buildPackage: BuildPackage) { function createFilesForSecondaryEntryPoint(buildPackage: BuildPackage, releasePath: string) { const {packageName, packageOut} = buildPackage; - getSecondaryEntryPointsForPackage(buildPackage).forEach(entryPointName => { + buildPackage.flatSecondaryEntryPoints.forEach(entryPointName => { // Create a directory in the root of the package for this entry point that contains // * A package.json that lists the different bundle locations // * An index.d.ts file that re-exports the index.d.ts from the typings/ directory diff --git a/tools/package-tools/secondary-entry-points.ts b/tools/package-tools/secondary-entry-points.ts index d646ad9709b5..3c202f7ec96a 100644 --- a/tools/package-tools/secondary-entry-points.ts +++ b/tools/package-tools/secondary-entry-points.ts @@ -52,26 +52,36 @@ export function getSecondaryEntryPointsForPackage(pkg: BuildPackage) { .map(depName => nodeLookup.get(depName)!) || []; }); - // Concatenate the build order for each node into one global build order. - // Duplicates are automatically omitted by getBuildOrder. - return buildNodes.reduce((order: string[], node) => { - return [...order, ...getBuildOrder(node)]; - }, []); -} + const buildOrder: string[][] = []; -/** Gets the build order for a given node with DFS. */ -function getBuildOrder(node: BuildNode): string[] { - if (node.visited) { - return []; - } + const addNodeToLevel = (node: BuildNode, level: number): number => { + node.visited = true; + node.level = level; - let buildOrder: string[] = []; - for (const dep of node.deps) { - buildOrder = [...buildOrder, ...getBuildOrder(dep)]; - } + buildOrder[level] = [...buildOrder[level] || [], node.name]; + + return level; + }; + + const sortDependencies = (node: BuildNode): number => { + if (node.visited) { + return node.level || 0; + } else if (!node.deps.length) { + return addNodeToLevel(node, 0); + } - node.visited = true; - return [...buildOrder, node.name]; + const dependencyDepth = 1 + node.deps + .map(dep => sortDependencies(dep)) + .sort((a, b) => a - b).slice(-1)[0]; + + addNodeToLevel(node, dependencyDepth); + + return dependencyDepth; + }; + + buildNodes.forEach(node => sortDependencies(node)); + + return buildOrder; } /** Gets the names of all subdirectories for a given path. */ @@ -84,9 +94,9 @@ interface BuildNode { name: string; deps: BuildNode[]; visited?: boolean; + level?: number; } - /** Builds the command that will be executed to find all import statements for a package. */ function buildPackageImportStatementFindCommand(searchDirectory: string, packageName: string) { if (platform() === 'win32') {