build: add --projects a.k.a the turbo button

we can now filter build graph via --project flag to speed up build performance

usage:

gulp test.unit.js --project=angular2,angular2_material

Closes #5272
This commit is contained in:
Igor Minar 2015-11-13 01:58:17 -08:00
parent 2ecbd0eaaf
commit 1d9c44b34f
6 changed files with 192 additions and 100 deletions

View File

@ -46,10 +46,36 @@ var dartSdk = require('./tools/build/dart');
var browserProvidersConf = require('./browser-providers.conf.js');
var os = require('os');
require('./tools/check-environment')({
requiredNpmVersion: '>=2.14.7',
requiredNodeVersion: '>=4.2.1'
});
require('./tools/check-environment')(
{requiredNpmVersion: '>=2.14.7', requiredNodeVersion: '>=4.2.1'});
var cliArgs = minimist(process.argv.slice(2));
if (cliArgs.projects) {
// normalize for analytics
cliArgs.projects.split(',').sort().join(',');
}
// --projects=angular2,angular2_material => {angular2: true, angular2_material: true}
var allProjects =
'angular1_router,angular2,angular2_material,benchmarks,benchmarks_external,benchpress,playground';
var cliArgsProjects = (cliArgs.projects || allProjects)
.split(',')
.reduce((map, projectName) => {
map[projectName] = true;
return map;
}, {});
function printModulesWarning() {
if (!cliArgs.projects && !process.env.CI) {
// if users didn't specify projects to build, tell them why and how they should
console.warn(
"Pro Tip: Did you know that you can speed up your build by specifying project name(s)?");
console.warn(" It's like pressing the turbo button in the old days, but better!");
console.warn(" Examples: --project=angular2 or --project=angular2,angular2_material");
}
}
// Make it easy to quiet down portions of the build.
// --logs=all -> log everything (This is the default)
@ -162,7 +188,7 @@ gulp.task('build/tree.dart', ['build/clean.dart', 'build.tools'],
gulp.task('!build/tree.dart',
function() { return angularBuilder.rebuildDartTree(); });
function() { return angularBuilder.rebuildDartTree(cliArgsProjects); });
// ------------
@ -444,15 +470,19 @@ function getBrowsersFromCLI(provider) {
}
gulp.task('test.unit.js', ['build.js.dev'], function(done) {
printModulesWarning();
runSequence('!test.unit.js/karma-server', function() {
watch('modules/**', {ignoreInitial: true}, ['!broccoli.js.dev', '!test.unit.js/karma-run']);
});
});
gulp.task('watch.js.dev', ['build.js.dev'],
function(done) { watch('modules/**', ['!broccoli.js.dev']); });
gulp.task('watch.js.dev', ['build.js.dev'], function(done) {
printModulesWarning();
watch('modules/**', ['!broccoli.js.dev']);
});
gulp.task('test.unit.js.sauce', ['build.js.dev'], function(done) {
printModulesWarning();
var browserConf = getBrowsersFromCLI('SL');
if (browserConf.isProvider) {
launchKarmaWithExternalBrowsers(['dots'], browserConf.browsersToRun, done);
@ -462,6 +492,7 @@ gulp.task('test.unit.js.sauce', ['build.js.dev'], function(done) {
});
gulp.task('test.unit.js.browserstack', ['build.js.dev'], function(done) {
printModulesWarning();
var browserConf = getBrowsersFromCLI('BS');
if (browserConf.isProvider) {
launchKarmaWithExternalBrowsers(['dots'], browserConf.browsersToRun, done);
@ -535,6 +566,7 @@ gulp.task('!test.unit.router/karma-run', function(done) {
gulp.task('buildRouter.dev', function() { buildRouter(); });
gulp.task('test.unit.dart', function(done) {
printModulesWarning();
runSequence('build/tree.dart', 'build/pure-packages.dart', '!build/pubget.angular2.dart',
'!build/change_detect.dart', '!build/remove-pub-symlinks', 'build.dart.material.css',
'!test.unit.dart/karma-server', '!test.unit.dart/karma-run', function(error) {
@ -632,12 +664,9 @@ gulp.task('test.unit.cjs/ci', function(done) {
gulp.task('test.unit.cjs', ['build/clean.js', 'build.tools'], function(neverDone) {
printModulesWarning();
treatTestErrorsAsFatal = false;
var buildAndTest = ['!build.js.cjs', 'test.unit.cjs/ci'];
watch('modules/**', buildAndTest);
watch('modules/**', ['!build.js.cjs', 'test.unit.cjs/ci']);
});
// Use this target to continuously run dartvm unit-tests (such as transformer
@ -812,12 +841,11 @@ gulp.task('!build.tools', function() {
gulp.task('broccoli.js.dev', ['build.tools'],
function(done) { runSequence('!broccoli.js.dev', sequenceComplete(done)); });
gulp.task('!broccoli.js.dev', () => {
return angularBuilder.rebuildBrowserDevTree();
});
gulp.task('!broccoli.js.dev',
() => { return angularBuilder.rebuildBrowserDevTree(cliArgsProjects); });
gulp.task('!broccoli.js.prod',
function() { return angularBuilder.rebuildBrowserProdTree(); });
function() { return angularBuilder.rebuildBrowserProdTree(cliArgsProjects); });
gulp.task('build.js.dev', ['build/clean.js'], function(done) {
runSequence('broccoli.js.dev', 'build.css.material', sequenceComplete(done));
@ -840,7 +868,7 @@ var firstBuildJsCjs = true;
* private task
*/
gulp.task('!build.js.cjs', function() {
return angularBuilder.rebuildNodeTree()
return angularBuilder.rebuildNodeTree(cliArgsProjects)
.then(function() {
if (firstBuildJsCjs) {
firstBuildJsCjs = false;

View File

@ -8,8 +8,14 @@
/// <reference path="../typings/hammerjs/hammerjs.d.ts"/>
/// <reference path="../typings/jasmine/jasmine.d.ts"/>
/// <reference path="../typings/angular-protractor/angular-protractor.d.ts"/>
// TODO: ideally the node.d.ts reference should be scoped only for files that need and not to all
// the code including client code
/// <reference path="../typings/node/node.d.ts" />
declare var assert: any;
interface BrowserNodeGlobal {
Object: typeof Object;
Array: typeof Array;

View File

@ -7,6 +7,10 @@ var path = require('path');
var printSlowTrees = require('broccoli-slow-trees');
var Q = require('q');
type ProjectMap = {
[key: string]: boolean
};
/**
* BroccoliBuilder facade for all of our build pipelines.
*/
@ -21,26 +25,26 @@ export class AngularBuilder {
constructor(public options: AngularBuilderOptions) { this.outputPath = options.outputPath; }
public rebuildBrowserDevTree(): Promise<BuildResult> {
this.browserDevBuilder = this.browserDevBuilder || this.makeBrowserDevBuilder();
public rebuildBrowserDevTree(projects: ProjectMap): Promise<BuildResult> {
this.browserDevBuilder = this.browserDevBuilder || this.makeBrowserDevBuilder(projects);
return this.rebuild(this.browserDevBuilder, 'js.dev');
}
public rebuildBrowserProdTree(): Promise<BuildResult> {
this.browserProdBuilder = this.browserProdBuilder || this.makeBrowserProdBuilder();
public rebuildBrowserProdTree(projects: ProjectMap): Promise<BuildResult> {
this.browserProdBuilder = this.browserProdBuilder || this.makeBrowserProdBuilder(projects);
return this.rebuild(this.browserProdBuilder, 'js.prod');
}
public rebuildNodeTree(): Promise<BuildResult> {
this.nodeBuilder = this.nodeBuilder || this.makeNodeBuilder();
public rebuildNodeTree(projects: ProjectMap): Promise<BuildResult> {
this.nodeBuilder = this.nodeBuilder || this.makeNodeBuilder(projects);
return this.rebuild(this.nodeBuilder, 'js.cjs');
}
public rebuildDartTree(): Promise<BuildResult> {
this.dartBuilder = this.dartBuilder || this.makeDartBuilder();
public rebuildDartTree(projects: ProjectMap): Promise<BuildResult> {
this.dartBuilder = this.dartBuilder || this.makeDartBuilder(projects);
return this.rebuild(this.dartBuilder, 'dart');
}
@ -54,31 +58,32 @@ export class AngularBuilder {
}
private makeBrowserDevBuilder(): BroccoliBuilder {
let tree = makeBrowserTree({name: 'dev', typeAssertions: true},
private makeBrowserDevBuilder(projects: ProjectMap): BroccoliBuilder {
let tree = makeBrowserTree({name: 'dev', typeAssertions: true, projects: projects},
path.join(this.outputPath, 'js', 'dev'));
return new broccoli.Builder(tree);
}
private makeBrowserProdBuilder(): BroccoliBuilder {
let tree = makeBrowserTree({name: 'prod', typeAssertions: false},
private makeBrowserProdBuilder(projects: ProjectMap): BroccoliBuilder {
let tree = makeBrowserTree({name: 'prod', typeAssertions: false, projects: projects},
path.join(this.outputPath, 'js', 'prod'));
return new broccoli.Builder(tree);
}
private makeNodeBuilder(): BroccoliBuilder {
let tree = makeNodeTree(path.join(this.outputPath, 'js', 'cjs'));
private makeNodeBuilder(projects: ProjectMap): BroccoliBuilder {
let tree = makeNodeTree(projects, path.join(this.outputPath, 'js', 'cjs'));
return new broccoli.Builder(tree);
}
private makeDartBuilder(): BroccoliBuilder {
private makeDartBuilder(projects: ProjectMap): BroccoliBuilder {
let options = {
outputPath: path.join(this.outputPath, 'dart'),
dartSDK: this.options.dartSDK,
logs: this.options.logs
logs: this.options.logs,
projects: projects
};
let tree = makeDartTree(options);
return new broccoli.Builder(tree);

View File

@ -47,6 +47,12 @@ class DiffingTSCompiler implements DiffingBroccoliPlugin {
// in tsc 1.7.x this api was renamed to parseJsonConfigFileContent
// the conversion is a bit awkward, see https://github.com/Microsoft/TypeScript/issues/5276
this.tsOpts = ts.parseConfigFile({compilerOptions: options, files: []}, null, null).options;
// TODO: the above turns rootDir set to './' into an empty string - looks like a tsc bug
// check back when we upgrade to 1.7.x
if (this.tsOpts.rootDir === '') {
this.tsOpts.rootDir = './';
}
this.tsOpts.outDir = this.cachePath;
this.tsServiceHost = new CustomLanguageServiceHost(this.tsOpts, this.rootFilePaths,

View File

@ -70,26 +70,55 @@ const kServedPaths = [
module.exports = function makeBrowserTree(options, destinationPath) {
var modulesTree = new Funnel('modules', {
include: ['**/**'],
exclude: [
'**/*.cjs',
'benchmarks/e2e_test/**',
'angular1_router/**',
// Exclude ES6 polyfill typings when tsc target=ES6
'angular2/typings/es6-*/**',
],
destDir: '/'
});
var modules = options.projects;
if (modules.angular2) {
var angular2Tree = new Funnel('modules/angular2', {
include: ['**/**'],
exclude: [
// Exclude ES6 polyfill typings when tsc target=ES6
'typings/es6-*/**',
],
destDir: '/angular2/'
});
}
if (modules.angular2_material) {
var angular2MaterialTree =
new Funnel('modules/angular2_material',
{include: ['**/**'], exclude: ['e2e_test/**'], destDir: '/angular2_material/'});
}
if (modules.benchmarks) {
var benchmarksTree =
new Funnel('modules/benchmarks',
{include: ['**/**'], exclude: ['e2e_test/**'], destDir: '/benchmarks/'});
}
if (modules.benchmarks_external) {
var benchmarksExternalTree = new Funnel(
'modules/benchmarks_external',
{include: ['**/**'], exclude: ['e2e_test/**'], destDir: '/benchmarks_external/'});
}
if (modules.playground) {
var playgroundTree =
new Funnel('modules/playground',
{include: ['**/**'], exclude: ['e2e_test/**'], destDir: '/playground/'});
}
var modulesTree = mergeTrees(
[angular2Tree, angular2MaterialTree, benchmarksTree, benchmarksExternalTree, playgroundTree]);
var clientModules = new Funnel(
'node_modules', {include: ['@reactivex/**/**', 'parse5/**/**', 'css/**/**'], destDir: '/'});
var es5ModulesTree = new Funnel('modules', {
include: ['**/**'],
exclude: ['**/*.cjs', 'angular1_router/**', 'benchmarks/e2e_test/**'],
destDir: '/'
});
var es6PolyfillTypings =
new Funnel('modules', {include: ['angular2/typings/es6-*/**'], destDir: '/'});
var es5ModulesTree = mergeTrees([modulesTree, es6PolyfillTypings]);
es5ModulesTree = stew.debug(es5ModulesTree, {name: 'debug-es5'});
var scriptPathPatternReplacement = {
match: '@@PATH',
@ -119,7 +148,7 @@ module.exports = function makeBrowserTree(options, destinationPath) {
experimentalDecorators: true,
mapRoot: '', // force sourcemaps to use relative path
noEmitOnError: false,
rootDir: '.',
rootDir: './',
rootFilePaths: ['angular2/manual_typings/globals-es6.d.ts'],
sourceMap: true,
sourceRoot: '.',
@ -127,7 +156,7 @@ module.exports = function makeBrowserTree(options, destinationPath) {
});
// Use TypeScript to transpile the *.ts files to ES5
var typescriptOptions = {
var es5Tree = compileWithTypescript(es5ModulesTree, {
declaration: false,
emitDecoratorMetadata: true,
experimentalDecorators: true,
@ -135,13 +164,12 @@ module.exports = function makeBrowserTree(options, destinationPath) {
module: 'commonjs',
moduleResolution: 'classic',
noEmitOnError: true,
rootDir: '.',
rootDir: './',
rootFilePaths: ['angular2/manual_typings/globals.d.ts'],
sourceMap: true,
sourceRoot: '.',
target: 'es5'
};
var es5Tree = compileWithTypescript(es5ModulesTree, typescriptOptions);
});
var vendorScriptsTree = flatten(new Funnel('.', {
files: [
@ -173,61 +201,80 @@ module.exports = function makeBrowserTree(options, destinationPath) {
return funnels;
}
if (modules.angular2_material || modules.benchmarks || modules.benchmarks_external ||
modules.playground) {
var assetsTree = new Funnel(
modulesTree, {include: ['**/*'], exclude: ['**/*.{html,ts,dart}'], destDir: '/'});
}
var htmlTree = new Funnel(
modulesTree, {include: ['*/src/**/*.html', '**/playground/**/*.html'], destDir: '/'});
htmlTree = replace(htmlTree, {
files: ['playground*/**/*.html'],
patterns: [
{match: /\$SCRIPTS\$/, replacement: htmlReplace('SCRIPTS')},
scriptPathPatternReplacement,
scriptFilePatternReplacement
]
});
if (modules.benchmarks || modules.benchmarks_external || modules.playground) {
htmlTree = replace(htmlTree, {
files: ['playground*/**/*.html'],
patterns: [
{match: /\$SCRIPTS\$/, replacement: htmlReplace('SCRIPTS')},
scriptPathPatternReplacement,
scriptFilePatternReplacement
]
});
}
htmlTree = replace(htmlTree, {
files: ['benchmarks/**'],
patterns: [
{match: /\$SCRIPTS\$/, replacement: htmlReplace('SCRIPTS_benchmarks')},
scriptPathPatternReplacement,
scriptFilePatternReplacement
]
});
if (modules.benchmarks) {
htmlTree = replace(htmlTree, {
files: ['benchmarks/**'],
patterns: [
{match: /\$SCRIPTS\$/, replacement: htmlReplace('SCRIPTS_benchmarks')},
scriptPathPatternReplacement,
scriptFilePatternReplacement
]
});
}
htmlTree = replace(htmlTree, {
files: ['benchmarks_external/**'],
patterns: [
{match: /\$SCRIPTS\$/, replacement: htmlReplace('SCRIPTS_benchmarks_external')},
scriptPathPatternReplacement,
scriptFilePatternReplacement
]
});
if (modules.benchmarks_external) {
htmlTree = replace(htmlTree, {
files: ['benchmarks_external/**'],
patterns: [
{match: /\$SCRIPTS\$/, replacement: htmlReplace('SCRIPTS_benchmarks_external')},
scriptPathPatternReplacement,
scriptFilePatternReplacement
]
});
}
// We need to replace the regular angular bundle with the web-worker bundle
// for web-worker e2e tests.
htmlTree = replace(htmlTree, {
files: ['playground*/**/web_workers/**/*.html'],
patterns: [{match: "/bundle/angular2.dev.js", replacement: "/bundle/web_worker/ui.dev.js"}]
});
if (modules.playground) {
// We need to replace the regular angular bundle with the web-worker bundle
// for web-worker e2e tests.
htmlTree = replace(htmlTree, {
files: ['playground*/**/web_workers/**/*.html'],
patterns: [{match: "/bundle/angular2.dev.js", replacement: "/bundle/web_worker/ui.dev.js"}]
});
}
var assetsTree =
new Funnel(modulesTree, {include: ['**/*'], exclude: ['**/*.{html,ts,dart}'], destDir: '/'});
if (modules.benchmarks || modules.benchmarks_external) {
var scripts = mergeTrees(servingTrees);
}
var scripts = mergeTrees(servingTrees);
var polymerFiles = new Funnel('.', {
files: [
'bower_components/polymer/polymer.html',
'bower_components/polymer/polymer-micro.html',
'bower_components/polymer/polymer-mini.html',
'tools/build/snippets/url_params_to_form.js'
]
});
var polymer = stew.mv(flatten(polymerFiles), 'benchmarks_external/src/tree/polymer');
if (modules.benchmarks_external) {
var polymerFiles = new Funnel('.', {
files: [
'bower_components/polymer/polymer.html',
'bower_components/polymer/polymer-micro.html',
'bower_components/polymer/polymer-mini.html',
'tools/build/snippets/url_params_to_form.js'
]
});
var polymer = stew.mv(flatten(polymerFiles), 'benchmarks_external/src/tree/polymer');
var reactFiles = new Funnel('.', {files: ['node_modules/react/dist/react.min.js']});
var react = stew.mv(flatten(reactFiles), 'benchmarks_external/src/tree/react');
var reactFiles = new Funnel('.', {files: ['node_modules/react/dist/react.min.js']});
var react = stew.mv(flatten(reactFiles), 'benchmarks_external/src/tree/react');
}
htmlTree = mergeTrees([htmlTree, scripts, polymer, react]);
if (modules.benchmarks || modules.benchmarks_external || modules.playground) {
htmlTree = mergeTrees([htmlTree, scripts, polymer, react]);
}
es5Tree = mergeTrees([es5Tree, htmlTree, assetsTree, clientModules]);
es6Tree = mergeTrees([es6Tree, htmlTree, assetsTree, clientModules]);

View File

@ -12,7 +12,7 @@ var stew = require('broccoli-stew');
var projectRootDir = path.normalize(path.join(__dirname, '..', '..', '..', '..'));
module.exports = function makeNodeTree(destinationPath) {
module.exports = function makeNodeTree(projects, destinationPath) {
// list of npm packages that this build will create
var outputPackages = ['angular2', 'benchpress'];