Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(613)

Side by Side Diff: sdk/lib/_internal/pub/lib/src/command/build.dart

Issue 141113011: Support directories other than "web" in pub build. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698