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); |
} |
} |