OLD | NEW |
---|---|
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library pub.command.build; | 5 library pub.command.build; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 | 8 |
9 import 'package:barback/barback.dart'; | 9 import 'package:barback/barback.dart'; |
10 import 'package:path/path.dart' as path; | 10 import 'package:path/path.dart' as path; |
11 | 11 |
12 import '../barback/dart2js_transformer.dart'; | 12 import '../barback/build_environment.dart'; |
13 import '../barback/dart_forwarding_transformer.dart'; | |
14 import '../barback.dart' as barback; | 13 import '../barback.dart' as barback; |
15 import '../command.dart'; | 14 import '../command.dart'; |
16 import '../exit_codes.dart' as exit_codes; | 15 import '../exit_codes.dart' as exit_codes; |
17 import '../io.dart'; | 16 import '../io.dart'; |
18 import '../log.dart' as log; | 17 import '../log.dart' as log; |
19 import '../utils.dart'; | 18 import '../utils.dart'; |
20 | 19 |
21 final _arrow = getSpecial('\u2192', '=>'); | 20 final _arrow = getSpecial('\u2192', '=>'); |
22 | 21 |
22 /// The set of top level directories in the entrypoint package that can be | |
23 /// built. | |
24 final _allowedBuildDirectories = [ | |
25 "benchmark", "bin", "example", "test", "web" | |
26 ].toSet(); | |
nweiz
2014/01/28 03:21:51
Style nit: I slightly prefer [new Set.from] becaus
Bob Nystrom
2014/01/28 23:02:04
Done.
| |
27 | |
23 /// Handles the `build` pub command. | 28 /// Handles the `build` pub command. |
24 class BuildCommand extends PubCommand { | 29 class BuildCommand extends PubCommand { |
25 String get description => | 30 String get description => |
26 "Copy and compile all Dart entrypoints in the 'web' directory."; | 31 "Copy and compile all Dart entrypoints in the 'web' directory."; |
27 String get usage => "pub build [options]"; | 32 String get usage => "pub build [options]"; |
28 List<String> get aliases => const ["deploy", "settle-up"]; | 33 List<String> get aliases => const ["deploy", "settle-up"]; |
34 bool get takesArguments => true; | |
29 | 35 |
30 // TODO(nweiz): make these configurable. | 36 // TODO(nweiz): make this configurable. |
31 /// The path to the source directory of the application. | |
32 String get source => path.join(entrypoint.root.dir, 'web'); | |
33 | |
34 /// The path to the application's build output directory. | 37 /// The path to the application's build output directory. |
35 String get target => path.join(entrypoint.root.dir, 'build'); | 38 String get target => path.join(entrypoint.root.dir, 'build'); |
36 | 39 |
37 /// The build mode. | 40 /// The build mode. |
38 BarbackMode get mode => new BarbackMode(commandOptions['mode']); | 41 BarbackMode get mode => new BarbackMode(commandOptions['mode']); |
39 | 42 |
43 /// The number of files that have been built and written to disc so far. | |
44 int builtFiles = 0; | |
nweiz
2014/01/28 03:21:51
It's weird that this is public but [_buildDirector
Bob Nystrom
2014/01/28 23:02:04
Made _buildDirectories public. In the command clas
| |
45 | |
46 /// The names of the top-level build directories that will be built. | |
47 final _buildDirectories = new Set<String>(); | |
48 | |
40 BuildCommand() { | 49 BuildCommand() { |
41 commandParser.addOption('mode', defaultsTo: BarbackMode.RELEASE.toString(), | 50 commandParser.addOption('mode', defaultsTo: BarbackMode.RELEASE.toString(), |
42 help: 'Mode to run transformers in.'); | 51 help: 'Mode to run transformers in.'); |
43 } | 52 } |
44 | 53 |
45 Future onRun() { | 54 Future onRun() { |
46 if (!dirExists(source)) { | 55 _parseBuildDirectories(); |
47 throw new ApplicationException('There is no "$source" directory.'); | |
48 } | |
49 | |
50 cleanDir(target); | 56 cleanDir(target); |
51 | 57 |
52 var dart2jsTransformer; | 58 // Since this server will only be hit by the transformer loader and isn't |
53 var builtFiles = 0; | 59 // user-facing, just use an IPv4 address to avoid a weird bug on the |
60 // OS X buildbots. | |
61 return BuildEnvironment.create(entrypoint, "127.0.0.1", 0, mode, | |
62 WatcherType.NONE, _buildDirectories, useDart2JS: true) | |
63 .then((environment) { | |
54 | 64 |
55 return entrypoint.loadPackageGraph().then((graph) { | |
56 dart2jsTransformer = new Dart2JSTransformer(graph, mode); | |
57 var builtInTransformers = [ | |
58 dart2jsTransformer, | |
59 new DartForwardingTransformer(mode) | |
60 ]; | |
61 | |
62 // Since this server will only be hit by the transformer loader and isn't | |
63 // user-facing, just use an IPv4 address to avoid a weird bug on the | |
64 // OS X buildbots. | |
65 // TODO(rnystrom): Allow specifying mode. | |
66 return barback.createServer("127.0.0.1", 0, graph, mode, | |
67 builtInTransformers: builtInTransformers, | |
68 watcher: barback.WatcherType.NONE); | |
69 }).then((server) { | |
70 // Show in-progress errors, but not results. Those get handled implicitly | 65 // Show in-progress errors, but not results. Those get handled implicitly |
71 // by getAllAssets(). | 66 // by getAllAssets(). |
72 server.barback.errors.listen((error) { | 67 environment.server.barback.errors.listen((error) { |
73 log.error(log.red("Build error:\n$error")); | 68 log.error(log.red("Build error:\n$error")); |
74 }); | 69 }); |
75 | 70 |
76 return log.progress("Building ${entrypoint.root.name}", | 71 return log.progress("Building ${entrypoint.root.name}", |
77 () => server.barback.getAllAssets()); | 72 () => environment.server.barback.getAllAssets()).then((assets) { |
78 }).then((assets) { | 73 return Future.wait(assets.map(_writeAsset)).then((_) { |
79 return Future.wait(assets.map((asset) { | 74 builtFiles += _copyBrowserJsFiles( |
80 // In release mode, strip out .dart files since all relevant ones have | 75 environment.dart2JSTransformer.entrypoints); |
nweiz
2014/01/28 03:21:51
Accessing dart2JSTransformer here is gross. It als
Bob Nystrom
2014/01/28 23:02:04
Done.
| |
81 // been compiled to JavaScript already. | 76 log.message("Built $builtFiles ${pluralize('file', builtFiles)}!"); |
82 if (mode == BarbackMode.RELEASE && asset.id.extension == ".dart") { | 77 }); |
83 return new Future.value(); | |
84 } | |
85 | |
86 builtFiles++; | |
87 | |
88 // Figure out the output directory for the asset, which is the same | |
89 // as the path pub serve would use to serve it. | |
90 var relativeUrl = barback.idtoUrlPath(entrypoint.root.name, asset.id); | |
91 | |
92 // Remove the leading "/". | |
93 relativeUrl = relativeUrl.substring(1); | |
94 | |
95 var relativePath = path.fromUri(new Uri(path: relativeUrl)); | |
96 var destPath = path.join(target, relativePath); | |
97 | |
98 ensureDir(path.dirname(destPath)); | |
99 // TODO(rnystrom): Should we display this to the user? | |
100 return createFileFromStream(asset.read(), destPath); | |
101 })).then((_) { | |
102 builtFiles += _copyBrowserJsFiles(dart2jsTransformer.entrypoints); | |
103 log.message("Built $builtFiles ${pluralize('file', builtFiles)}!"); | |
104 }); | 78 }); |
105 }).catchError((error) { | 79 }).catchError((error) { |
106 // If [getAllAssets()] throws a BarbackException, the error has already | 80 // If [getAllAssets()] throws a BarbackException, the error has already |
107 // been reported. | 81 // been reported. |
108 if (error is! BarbackException) throw error; | 82 if (error is! BarbackException) throw error; |
109 | 83 |
110 log.error(log.red("Build failed.")); | 84 log.error(log.red("Build failed.")); |
111 return flushThenExit(exit_codes.DATA); | 85 return flushThenExit(exit_codes.DATA); |
112 }); | 86 }); |
113 } | 87 } |
114 | 88 |
89 /// Parses the command-line arguments to determine the set of top-level | |
90 /// directories to build. | |
91 /// | |
92 /// If there are no arguments to `pub build`, this will just be "web". | |
93 /// | |
94 /// If the argument is `*`, then it will be all buildable directories that | |
95 /// exist. | |
nweiz
2014/01/28 03:21:51
Passing * on the command line is very confusing. I
Bob Nystrom
2014/01/28 23:02:04
Done. --all.
| |
96 /// | |
97 /// Otherwise, all arguments should be the names of directories to include. | |
98 /// | |
99 /// Throws if any of the specified directories cannot be found. | |
100 void _parseBuildDirectories() { | |
101 if (commandOptions.rest.length == 1 && commandOptions.rest.single == "*") { | |
102 // Include every build directory that exists in the package. | |
103 var allowed = _allowedBuildDirectories.where( | |
104 (d) => dirExists(path.join(entrypoint.root.dir, d))); | |
105 | |
106 if (allowed.isEmpty) { | |
107 throw new ApplicationException("There are no buildable directories."); | |
nweiz
2014/01/28 03:21:51
List which directories are considered buildable in
Bob Nystrom
2014/01/28 23:02:04
Done.
| |
108 } | |
109 | |
110 _buildDirectories.addAll(allowed); | |
111 return; | |
112 } | |
113 | |
114 _buildDirectories.addAll(commandOptions.rest); | |
115 | |
116 // If no directory was specified, default to "web". | |
117 if (commandOptions.rest.isEmpty) { | |
118 _buildDirectories.add("web"); | |
119 } | |
120 | |
121 // Make sure the arguments are known directories. | |
122 var disallowed = commandOptions.rest.where( | |
123 (dir) => !_allowedBuildDirectories.contains(dir)); | |
124 if (disallowed.isNotEmpty) { | |
125 var dirs = pluralize("directory", disallowed.length, | |
126 plural: "directories"); | |
127 var names = toSentence(ordered(disallowed).map((name) => '"$name"')); | |
128 var allowed = toSentence(ordered(_allowedBuildDirectories.map( | |
129 (name) => '"$name"'))); | |
130 throw new ApplicationException( | |
131 'Unsupported build $dirs $names.\n' | |
132 'The allowed directories are: $allowed.'); | |
nweiz
2014/01/28 03:21:51
Nit: since [allowed] is sentenceized, I think this
Bob Nystrom
2014/01/28 23:02:04
Done.
| |
133 } | |
134 | |
135 // Make sure all of the build directories exist. | |
136 var missing = _buildDirectories.where( | |
137 (dir) => !dirExists(path.join(entrypoint.root.dir, dir))); | |
138 | |
139 if (missing.isNotEmpty) { | |
140 var dirs = pluralize("directory", missing.length, plural: "directories"); | |
141 var names = toSentence(ordered(missing).map((name) => '"$name"')); | |
142 throw new ApplicationException('Could not find $dirs $names.'); | |
nweiz
2014/01/28 03:21:51
I feel like "Could not find" is more ambiguous tha
Bob Nystrom
2014/01/28 23:02:04
Done.
| |
143 } | |
144 } | |
145 | |
146 /// Writes [asset] to the appropriate build directory. | |
147 /// | |
148 /// If [asset] is in the special "assets" directory, writes it to every | |
149 /// build directory. | |
150 Future _writeAsset(Asset asset) { | |
151 // In release mode, strip out .dart files since all relevant ones have been | |
152 // compiled to JavaScript already. | |
153 if (mode == BarbackMode.RELEASE && asset.id.extension == ".dart") { | |
154 return new Future.value(); | |
155 } | |
156 | |
157 // If the asset is from a package's "lib" directory, we make it available | |
158 // as an input for transformers, but don't want it in the final output. | |
159 // (Any Dart code in there should be imported and compiled to JS, anything | |
160 // else we want to omit.) | |
161 if (asset.id.path.startsWith("lib/")) { | |
162 return new Future.value(); | |
163 } | |
164 | |
165 // Figure out the output directory for the asset, which is the same as the | |
166 // path pub serve would use to serve it. | |
167 var relativeUrl = barback.idtoUrlPath(entrypoint.root.name, asset.id, | |
168 useWebAsRoot: false); | |
169 | |
170 // Remove the leading "/". | |
171 relativeUrl = relativeUrl.substring(1); | |
172 | |
173 // If the asset is from a special "shared" directory package, copy it into | |
nweiz
2014/01/28 03:21:51
"'shared' directory package" -> "'assets/' directo
Bob Nystrom
2014/01/28 23:02:04
Done.
| |
174 // all of the top-level build directories. | |
175 if (relativeUrl.startsWith("assets/")) { | |
176 builtFiles += _buildDirectories.length; | |
177 return Future.wait(_buildDirectories.map( | |
178 (buildDir) => _writeOutputFile(asset, | |
179 path.url.join(buildDir, relativeUrl)))); | |
180 } | |
181 | |
182 builtFiles++; | |
183 return _writeOutputFile(asset, relativeUrl); | |
184 } | |
185 | |
186 /// Writes the contents of [asset] to [relativeUrl] within the build | |
187 /// directory. | |
188 Future _writeOutputFile(Asset asset, String relativeUrl) { | |
189 var relativePath = path.fromUri(new Uri(path: relativeUrl)); | |
190 var destPath = path.join(target, relativePath); | |
191 | |
192 ensureDir(path.dirname(destPath)); | |
193 return createFileFromStream(asset.read(), destPath); | |
194 } | |
195 | |
115 /// If this package depends directly on the `browser` package, this ensures | 196 /// If this package depends directly on the `browser` package, this ensures |
116 /// that the JavaScript bootstrap files are copied into `packages/browser/` | 197 /// that the JavaScript bootstrap files are copied into `packages/browser/` |
117 /// directories next to each entrypoint in [entrypoints]. | 198 /// directories next to each entrypoint in [entrypoints]. |
118 /// | 199 /// |
119 /// Returns the number of files it copied. | 200 /// Returns the number of files it copied. |
120 int _copyBrowserJsFiles(Iterable<AssetId> entrypoints) { | 201 int _copyBrowserJsFiles(Iterable<AssetId> entrypoints) { |
121 // Must depend on the browser package. | 202 // Must depend on the browser package. |
122 if (!entrypoint.root.dependencies.any( | 203 if (!entrypoint.root.dependencies.any( |
123 (dep) => dep.name == 'browser' && dep.source == 'hosted')) { | 204 (dep) => dep.name == 'browser' && dep.source == 'hosted')) { |
124 return 0; | 205 return 0; |
125 } | 206 } |
126 | 207 |
127 // Get all of the directories that contain Dart entrypoints. | 208 // Get all of the directories that contain Dart entrypoints. |
128 var entrypointDirs = entrypoints | 209 var entrypointDirs = entrypoints |
129 .map((id) => path.url.split(id.path)) | 210 .map((id) => path.url.split(id.path)) |
130 .map((parts) => parts.skip(1)) // Remove "web/". | |
131 .map((relative) => path.dirname(path.joinAll(relative))) | 211 .map((relative) => path.dirname(path.joinAll(relative))) |
132 .toSet(); | 212 .toSet(); |
133 | 213 |
134 for (var dir in entrypointDirs) { | 214 for (var dir in entrypointDirs) { |
135 // TODO(nweiz): we should put browser JS files next to any HTML file | 215 // TODO(nweiz): we should put browser JS files next to any HTML file |
136 // rather than any entrypoint. An HTML file could import an entrypoint | 216 // rather than any entrypoint. An HTML file could import an entrypoint |
137 // that's not adjacent. | 217 // that's not adjacent. |
138 _addBrowserJs(dir, "dart"); | 218 _addBrowserJs(dir, "dart"); |
139 _addBrowserJs(dir, "interop"); | 219 _addBrowserJs(dir, "interop"); |
140 } | 220 } |
141 | 221 |
142 return entrypointDirs.length * 2; | 222 return entrypointDirs.length * 2; |
143 } | 223 } |
144 | 224 |
145 // TODO(nweiz): do something more principled when issue 6101 is fixed. | 225 // TODO(nweiz): do something more principled when issue 6101 is fixed. |
146 /// Ensures that the [name].js file is copied into [directory] in [target], | 226 /// Ensures that the [name].js file is copied into [directory] in [target], |
147 /// under `packages/browser/`. | 227 /// under `packages/browser/`. |
148 void _addBrowserJs(String directory, String name) { | 228 void _addBrowserJs(String directory, String name) { |
149 var jsPath = path.join( | 229 var jsPath = path.join( |
150 target, directory, 'packages', 'browser', '$name.js'); | 230 target, directory, 'packages', 'browser', '$name.js'); |
151 ensureDir(path.dirname(jsPath)); | 231 ensureDir(path.dirname(jsPath)); |
232 | |
233 // TODO(rnystrom): This won't work if we get rid of symlinks and the top | |
234 // level "packages" directory. Will need to copy from the browser | |
235 // directory. | |
152 copyFile(path.join(entrypoint.packagesDir, 'browser', '$name.js'), jsPath); | 236 copyFile(path.join(entrypoint.packagesDir, 'browser', '$name.js'), jsPath); |
153 } | 237 } |
154 } | 238 } |
OLD | NEW |