| Index: sdk/lib/_internal/pub/lib/src/command/build.dart
|
| diff --git a/sdk/lib/_internal/pub/lib/src/command/build.dart b/sdk/lib/_internal/pub/lib/src/command/build.dart
|
| index b13e4d8bdb103e845d4df4542bb50b833ee19473..b470f1bf976c0815e03880701f009d0448a170de 100644
|
| --- a/sdk/lib/_internal/pub/lib/src/command/build.dart
|
| +++ b/sdk/lib/_internal/pub/lib/src/command/build.dart
|
| @@ -9,8 +9,7 @@ import 'dart:async';
|
| import 'package:barback/barback.dart';
|
| import 'package:path/path.dart' as path;
|
|
|
| -import '../barback/dart2js_transformer.dart';
|
| -import '../barback/dart_forwarding_transformer.dart';
|
| +import '../barback/build_environment.dart';
|
| import '../barback.dart' as barback;
|
| import '../command.dart';
|
| import '../exit_codes.dart' as exit_codes;
|
| @@ -20,87 +19,71 @@ import '../utils.dart';
|
|
|
| final _arrow = getSpecial('\u2192', '=>');
|
|
|
| +/// The set of top level directories in the entrypoint package that can be
|
| +/// built.
|
| +final _allowedBuildDirectories = new Set<String>.from([
|
| + "benchmark", "bin", "example", "test", "web"
|
| +]);
|
| +
|
| /// Handles the `build` pub command.
|
| class BuildCommand extends PubCommand {
|
| String get description =>
|
| "Copy and compile all Dart entrypoints in the 'web' directory.";
|
| String get usage => "pub build [options]";
|
| List<String> get aliases => const ["deploy", "settle-up"];
|
| + bool get takesArguments => true;
|
|
|
| - // TODO(nweiz): make these configurable.
|
| - /// The path to the source directory of the application.
|
| - String get source => path.join(entrypoint.root.dir, 'web');
|
| -
|
| + // TODO(nweiz): make this configurable.
|
| /// The path to the application's build output directory.
|
| String get target => path.join(entrypoint.root.dir, 'build');
|
|
|
| /// The build mode.
|
| BarbackMode get mode => new BarbackMode(commandOptions['mode']);
|
|
|
| + /// The number of files that have been built and written to disc so far.
|
| + int builtFiles = 0;
|
| +
|
| + /// The names of the top-level build directories that will be built.
|
| + final buildDirectories = new Set<String>();
|
| +
|
| BuildCommand() {
|
| commandParser.addOption('mode', defaultsTo: BarbackMode.RELEASE.toString(),
|
| help: 'Mode to run transformers in.');
|
| +
|
| + commandParser.addFlag('all', help: "Build all buildable directories.",
|
| + defaultsTo: false, negatable: false);
|
| }
|
|
|
| Future onRun() {
|
| - if (!dirExists(source)) {
|
| - throw new ApplicationException('There is no "$source" directory.');
|
| - }
|
| + var exitCode = _parseBuildDirectories();
|
| + if (exitCode != exit_codes.SUCCESS) return flushThenExit(exitCode);
|
|
|
| cleanDir(target);
|
|
|
| - var dart2jsTransformer;
|
| - var builtFiles = 0;
|
| -
|
| - return entrypoint.loadPackageGraph().then((graph) {
|
| - dart2jsTransformer = new Dart2JSTransformer(graph, mode);
|
| - var builtInTransformers = [
|
| - dart2jsTransformer,
|
| - new DartForwardingTransformer(mode)
|
| - ];
|
| -
|
| - // Since this server will only be hit by the transformer loader and isn't
|
| - // user-facing, just use an IPv4 address to avoid a weird bug on the
|
| - // OS X buildbots.
|
| - // TODO(rnystrom): Allow specifying mode.
|
| - return barback.createServer("127.0.0.1", 0, graph, mode,
|
| - builtInTransformers: builtInTransformers,
|
| - watcher: barback.WatcherType.NONE);
|
| - }).then((server) {
|
| + // Since this server will only be hit by the transformer loader and isn't
|
| + // user-facing, just use an IPv4 address to avoid a weird bug on the
|
| + // OS X buildbots.
|
| + return BuildEnvironment.create(entrypoint, "127.0.0.1", 0, mode,
|
| + WatcherType.NONE, buildDirectories, useDart2JS: true)
|
| + .then((environment) {
|
| +
|
| // Show in-progress errors, but not results. Those get handled implicitly
|
| // by getAllAssets().
|
| - server.barback.errors.listen((error) {
|
| + environment.server.barback.errors.listen((error) {
|
| log.error(log.red("Build error:\n$error"));
|
| });
|
|
|
| return log.progress("Building ${entrypoint.root.name}",
|
| - () => server.barback.getAllAssets());
|
| - }).then((assets) {
|
| - return Future.wait(assets.map((asset) {
|
| - // In release mode, strip out .dart files since all relevant ones have
|
| - // been compiled to JavaScript already.
|
| - if (mode == BarbackMode.RELEASE && asset.id.extension == ".dart") {
|
| - return new Future.value();
|
| - }
|
| -
|
| - builtFiles++;
|
| -
|
| - // Figure out the output directory for the asset, which is the same
|
| - // as the path pub serve would use to serve it.
|
| - var relativeUrl = barback.idtoUrlPath(entrypoint.root.name, asset.id);
|
| -
|
| - // Remove the leading "/".
|
| - relativeUrl = relativeUrl.substring(1);
|
| -
|
| - var relativePath = path.fromUri(new Uri(path: relativeUrl));
|
| - var destPath = path.join(target, relativePath);
|
| -
|
| - ensureDir(path.dirname(destPath));
|
| - // TODO(rnystrom): Should we display this to the user?
|
| - return createFileFromStream(asset.read(), destPath);
|
| - })).then((_) {
|
| - builtFiles += _copyBrowserJsFiles(dart2jsTransformer.entrypoints);
|
| - log.message("Built $builtFiles ${pluralize('file', builtFiles)}!");
|
| + () => environment.server.barback.getAllAssets()).then((assets) {
|
| + // Find all of the JS entrypoints we built.
|
| + var dart2JSEntrypoints = assets
|
| + .where((asset) => asset.id.path.endsWith(".dart.js"))
|
| + .map((asset) => asset.id);
|
| +
|
| + return Future.wait(assets.map(_writeAsset)).then((_) {
|
| + builtFiles += _copyBrowserJsFiles(dart2JSEntrypoints);
|
| + log.message("Built $builtFiles ${pluralize('file', builtFiles)}!");
|
| + });
|
| });
|
| }).catchError((error) {
|
| // If [getAllAssets()] throws a BarbackException, the error has already
|
| @@ -112,6 +95,128 @@ class BuildCommand extends PubCommand {
|
| });
|
| }
|
|
|
| + /// Parses the command-line arguments to determine the set of top-level
|
| + /// directories to build.
|
| + ///
|
| + /// If there are no arguments to `pub build`, this will just be "web".
|
| + ///
|
| + /// If the `--all` flag is set, then it will be all buildable directories
|
| + /// that exist.
|
| + ///
|
| + /// Otherwise, all arguments should be the names of directories to include.
|
| + ///
|
| + /// Returns the exit code of an error, or zero if it parsed correctly.
|
| + int _parseBuildDirectories() {
|
| + if (commandOptions["all"]) {
|
| + if (commandOptions.rest.isNotEmpty) {
|
| + log.error(
|
| + 'Build directory names are not allowed if "--all" is passed.');
|
| + return exit_codes.USAGE;
|
| + }
|
| +
|
| + // Include every build directory that exists in the package.
|
| + var allowed = _allowedBuildDirectories.where(
|
| + (d) => dirExists(path.join(entrypoint.root.dir, d)));
|
| +
|
| + if (allowed.isEmpty) {
|
| + var buildDirs = toSentence(ordered(_allowedBuildDirectories.map(
|
| + (name) => '"$name"')));
|
| + log.error('There are no buildable directories.\n'
|
| + 'The supported directories are $buildDirs.');
|
| + return exit_codes.DATA;
|
| + }
|
| +
|
| + buildDirectories.addAll(allowed);
|
| + return exit_codes.SUCCESS;
|
| + }
|
| +
|
| + buildDirectories.addAll(commandOptions.rest);
|
| +
|
| + // If no directory were specified, default to "web".
|
| + if (buildDirectories.isEmpty) {
|
| + buildDirectories.add("web");
|
| + }
|
| +
|
| + // Make sure the arguments are known directories.
|
| + var disallowed = buildDirectories.where(
|
| + (dir) => !_allowedBuildDirectories.contains(dir));
|
| + if (disallowed.isNotEmpty) {
|
| + var dirs = pluralize("directory", disallowed.length,
|
| + plural: "directories");
|
| + var names = toSentence(ordered(disallowed).map((name) => '"$name"'));
|
| + var allowed = toSentence(ordered(_allowedBuildDirectories.map(
|
| + (name) => '"$name"')));
|
| + log.error('Unsupported build $dirs $names.\n'
|
| + 'The allowed directories are $allowed.');
|
| + return exit_codes.USAGE;
|
| + }
|
| +
|
| + // Make sure all of the build directories exist.
|
| + var missing = buildDirectories.where(
|
| + (dir) => !dirExists(path.join(entrypoint.root.dir, dir)));
|
| +
|
| + if (missing.length == 1) {
|
| + log.error('Directory "${missing.single}" does not exist.');
|
| + return exit_codes.DATA;
|
| + } else if (missing.isNotEmpty) {
|
| + var names = toSentence(ordered(missing).map((name) => '"$name"'));
|
| + log.error('Directories $names do not exist.');
|
| + return exit_codes.DATA;
|
| + }
|
| +
|
| + return exit_codes.SUCCESS;
|
| + }
|
| +
|
| + /// Writes [asset] to the appropriate build directory.
|
| + ///
|
| + /// If [asset] is in the special "assets" directory, writes it to every
|
| + /// build directory.
|
| + Future _writeAsset(Asset asset) {
|
| + // In release mode, strip out .dart files since all relevant ones have been
|
| + // compiled to JavaScript already.
|
| + if (mode == BarbackMode.RELEASE && asset.id.extension == ".dart") {
|
| + return new Future.value();
|
| + }
|
| +
|
| + // If the asset is from a package's "lib" directory, we make it available
|
| + // as an input for transformers, but don't want it in the final output.
|
| + // (Any Dart code in there should be imported and compiled to JS, anything
|
| + // else we want to omit.)
|
| + if (asset.id.path.startsWith("lib/")) {
|
| + return new Future.value();
|
| + }
|
| +
|
| + // Figure out the output directory for the asset, which is the same as the
|
| + // path pub serve would use to serve it.
|
| + var relativeUrl = barback.idtoUrlPath(entrypoint.root.name, asset.id,
|
| + useWebAsRoot: false);
|
| +
|
| + // Remove the leading "/".
|
| + relativeUrl = relativeUrl.substring(1);
|
| +
|
| + // If the asset is from the shared "assets" directory, copy it into all of
|
| + // the top-level build directories.
|
| + if (relativeUrl.startsWith("assets/")) {
|
| + builtFiles += buildDirectories.length;
|
| + return Future.wait(buildDirectories.map(
|
| + (buildDir) => _writeOutputFile(asset,
|
| + path.url.join(buildDir, relativeUrl))));
|
| + }
|
| +
|
| + builtFiles++;
|
| + return _writeOutputFile(asset, relativeUrl);
|
| + }
|
| +
|
| + /// Writes the contents of [asset] to [relativeUrl] within the build
|
| + /// directory.
|
| + Future _writeOutputFile(Asset asset, String relativeUrl) {
|
| + var relativePath = path.fromUri(new Uri(path: relativeUrl));
|
| + var destPath = path.join(target, relativePath);
|
| +
|
| + ensureDir(path.dirname(destPath));
|
| + return createFileFromStream(asset.read(), destPath);
|
| + }
|
| +
|
| /// If this package depends directly on the `browser` package, this ensures
|
| /// that the JavaScript bootstrap files are copied into `packages/browser/`
|
| /// directories next to each entrypoint in [entrypoints].
|
| @@ -127,7 +232,6 @@ class BuildCommand extends PubCommand {
|
| // Get all of the directories that contain Dart entrypoints.
|
| var entrypointDirs = entrypoints
|
| .map((id) => path.url.split(id.path))
|
| - .map((parts) => parts.skip(1)) // Remove "web/".
|
| .map((relative) => path.dirname(path.joinAll(relative)))
|
| .toSet();
|
|
|
| @@ -149,6 +253,10 @@ class BuildCommand extends PubCommand {
|
| var jsPath = path.join(
|
| target, directory, 'packages', 'browser', '$name.js');
|
| ensureDir(path.dirname(jsPath));
|
| +
|
| + // TODO(rnystrom): This won't work if we get rid of symlinks and the top
|
| + // level "packages" directory. Will need to copy from the browser
|
| + // directory.
|
| copyFile(path.join(entrypoint.packagesDir, 'browser', '$name.js'), jsPath);
|
| }
|
| }
|
|
|