OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
nweiz
2014/01/28 03:21:51
2014. Same for other new files
Bob Nystrom
2014/01/28 23:02:04
Done.
| |
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. | |
4 | |
5 library pub.barback.build_environment; | |
6 | |
7 import 'dart:async'; | |
8 | |
9 import 'package:barback/barback.dart'; | |
10 import 'package:path/path.dart' as path; | |
11 import 'package:stack_trace/stack_trace.dart'; | |
12 import 'package:watcher/watcher.dart'; | |
13 | |
14 import '../entrypoint.dart'; | |
15 import '../io.dart'; | |
16 import '../log.dart' as log; | |
17 import '../package.dart'; | |
18 import '../package_graph.dart'; | |
19 import '../utils.dart'; | |
20 import 'dart_forwarding_transformer.dart'; | |
21 import 'dart2js_transformer.dart'; | |
22 import 'load_all_transformers.dart'; | |
23 import 'pub_package_provider.dart'; | |
24 import 'server.dart'; | |
25 | |
26 /// The entire "visible" state of the assets of a package and all of its | |
27 /// dependencies, taking into account the user's configuration when running pub. | |
28 /// | |
29 /// Where [PackageGraph] just describes the entrypoint's dependencies as | |
30 /// specified by pubspecs, this includes "transient" information like the mode | |
31 /// that the user is running pub in, or which directories they want to build. | |
32 class BuildEnvironment { | |
nweiz
2014/01/28 03:21:51
This class confuses me, since the only public stuf
Bob Nystrom
2014/01/28 23:02:04
It's exposing more stuff now.
| |
33 /// Creates a new build environment for working with the assets used by | |
34 /// [entrypoint] and its dependencies. | |
35 /// | |
36 /// Spawns an HTTP server on [hostname] and [port]. Loads all used | |
37 /// transformers using [mode] (including dart2js if [useDart2JS] is true). | |
38 /// | |
39 /// Includes [buildDirectories] in the root package. | |
nweiz
2014/01/28 03:21:51
" in addition to `lib/` and `asset/`."
Bob Nystrom
2014/01/28 23:02:04
Done.
| |
40 /// | |
41 /// If [watcherType] is not [WatcherType.NONE], watches source assets for | |
42 /// modification. | |
43 /// | |
44 /// Returns a [Future] that completes to the environment once the inputs, | |
45 /// transformers, and server are loaded and ready. | |
46 static Future<BuildEnvironment> create(Entrypoint entrypoint, | |
47 String hostname, int port, BarbackMode mode, WatcherType watcherType, | |
48 Set<String> buildDirectories, | |
49 {bool useDart2JS: true}) { | |
50 return entrypoint.loadPackageGraph().then((graph) { | |
51 var dart2JSTransformer; | |
52 if (useDart2JS) { | |
53 dart2JSTransformer = new Dart2JSTransformer(graph, mode); | |
54 } | |
55 | |
56 var builtInTransformers; | |
57 if (useDart2JS) { | |
58 builtInTransformers = [ | |
59 dart2JSTransformer, | |
60 new DartForwardingTransformer(mode) | |
61 ]; | |
62 } | |
63 | |
64 // If the entrypoint package manually configures the dart2js transformer, | |
65 // remove it from the built-in transformer list. | |
66 // | |
67 // TODO(nweiz): if/when we support more built-in transformers, make this | |
68 // more general. | |
69 var containsDart2Js = graph.entrypoint.root.pubspec.transformers | |
70 .any((transformers) => transformers | |
71 .any((id) => id.package == '\$dart2js')); | |
72 | |
73 if (containsDart2Js) { | |
74 builtInTransformers = builtInTransformers.where( | |
75 (transformer) => transformer is! Dart2JSTransformer); | |
76 } | |
nweiz
2014/01/28 03:21:51
It might be cleaner if we do this above and just r
Bob Nystrom
2014/01/28 23:02:04
Done. This brought to my attention that we weren't
| |
77 | |
78 var barback = new Barback(new PubPackageProvider(graph)); | |
79 barback.log.listen(_log); | |
80 | |
81 return BarbackServer.bind(hostname, port, barback, | |
82 graph.entrypoint.root.name).then((server) { | |
83 | |
84 var environment = new BuildEnvironment._(graph, server, mode, | |
85 watcherType, buildDirectories, dart2JSTransformer); | |
86 | |
87 return environment._load(barback, builtInTransformers) | |
88 .then((server) => environment); | |
nweiz
2014/01/28 03:21:51
"server" => "_"
Bob Nystrom
2014/01/28 23:02:04
Done.
| |
89 }); | |
90 }); | |
91 } | |
92 | |
93 /// The server serving this environment's assets. | |
94 final BarbackServer server; | |
95 | |
96 /// The [Dart2JSTransformer], or `null` if it's not enabled. | |
97 final Dart2JSTransformer dart2JSTransformer; | |
98 | |
99 /// The underlying [PackageGraph] being built. | |
100 final PackageGraph _graph; | |
101 | |
102 /// The mode to run the transformers in. | |
103 final BarbackMode _mode; | |
104 | |
105 /// How source files should be watched. | |
106 final WatcherType _watcherType; | |
107 | |
108 /// The set of top-level directories in the entrypoint package that should be | |
109 /// built. | |
110 final Set<String> _buildDirectories; | |
111 | |
112 BuildEnvironment._(this._graph, this.server, this._mode, this._watcherType, | |
113 this._buildDirectories, this.dart2JSTransformer); | |
114 | |
115 /// Creates a [BarbackServer] for this environment. | |
116 /// | |
117 /// This transforms and serves all library and asset files in all packages in | |
118 /// the environment's package graph. It loads any transformer plugins defined | |
119 /// in packages in [graph] and re-runs them as necessary when any input files | |
120 /// change. | |
121 /// | |
122 /// Returns a [Future] that completes once all inputs and transformers are | |
123 /// loaded. | |
124 Future _load(Barback barback, Iterable<Transformer> transformers) { | |
125 return _provideSources(barback).then((_) { | |
126 var completer = new Completer(); | |
127 | |
128 // If any errors get emitted either by barback or by the server, | |
129 // including non-programmatic barback errors, they should take down the | |
130 // whole program. | |
131 var subscriptions = [ | |
132 server.barback.errors.listen((error) { | |
133 if (error is TransformerException) error = error.error; | |
134 if (!completer.isCompleted) { | |
135 completer.completeError(error, new Chain.current()); | |
136 } | |
137 }), | |
138 server.barback.results.listen((_) {}, onError: (error, stackTrace) { | |
139 if (completer.isCompleted) return; | |
140 completer.completeError(error, stackTrace); | |
141 }), | |
142 server.results.listen((_) {}, onError: (error, stackTrace) { | |
143 if (completer.isCompleted) return; | |
144 completer.completeError(error, stackTrace); | |
145 }) | |
146 ]; | |
147 | |
148 loadAllTransformers(server, _graph, _mode, transformers).then((_) { | |
149 if (!completer.isCompleted) completer.complete(server); | |
nweiz
2014/01/28 03:21:51
"server" is unused, so this should probably just b
Bob Nystrom
2014/01/28 23:02:04
Done.
| |
150 }).catchError((error, stackTrace) { | |
151 if (!completer.isCompleted) { | |
152 completer.completeError(error, stackTrace); | |
153 } | |
154 }); | |
155 | |
156 return completer.future.whenComplete(() { | |
157 for (var subscription in subscriptions) { | |
158 subscription.cancel(); | |
159 } | |
160 }); | |
161 }); | |
162 } | |
163 | |
164 /// Provides all of the source assets in the environment to barback. | |
165 /// | |
166 /// If [watcherType] is not [WatcherType.NONE], enables watching on them. | |
167 Future _provideSources(Barback barback) { | |
168 if (_watcherType != WatcherType.NONE) { | |
169 return _watchSources(barback); | |
170 } | |
171 | |
172 return syncFuture(() { | |
173 _loadSources(barback); | |
174 }); | |
175 } | |
176 | |
177 /// Provides all of the source assets in the environment to barback. | |
178 void _loadSources(Barback barback) { | |
179 for (var package in _graph.packages.values) { | |
180 barback.updateSources(_listAssets(_graph.entrypoint, package)); | |
181 } | |
182 } | |
183 | |
184 /// Adds all of the source assets in this environment to barback and then | |
185 /// watches the public directories for changes. | |
186 /// | |
187 /// Returns a Future that completes when the sources are loaded and the | |
188 /// watchers are active. | |
189 Future _watchSources(Barback barback) { | |
190 return Future.wait(_graph.packages.values.map((package) { | |
191 // If this package comes from a cached source, its contents won't change | |
192 // so we don't need to monitor it. `packageId` will be null for the | |
193 // application package, since that's not locked. | |
194 var packageId = _graph.lockFile.packages[package.name]; | |
195 if (packageId != null && | |
196 _graph.entrypoint.cache.sources[packageId.source].shouldCache) { | |
197 barback.updateSources(_listAssets(_graph.entrypoint, package)); | |
198 return new Future.value(); | |
199 } | |
200 | |
201 // Watch the visible package directories for changes. | |
202 return Future.wait(_getPublicDirectories(_graph.entrypoint, package) | |
203 .map((name) { | |
204 var subdirectory = path.join(package.dir, name); | |
205 if (!dirExists(subdirectory)) return new Future.value(); | |
206 | |
207 // TODO(nweiz): close these watchers when [barback] is closed. | |
208 var watcher = _watcherType.create(subdirectory); | |
209 watcher.events.listen((event) { | |
210 // Don't watch files symlinked into these directories. | |
211 // TODO(rnystrom): If pub gets rid of symlinks, remove this. | |
212 var parts = path.split(event.path); | |
213 if (parts.contains("packages") || parts.contains("assets")) return; | |
214 | |
215 // Skip ".js" files that were (most likely) compiled from nearby | |
216 // ".dart" files. These are created by the Editor's "Run as | |
217 // JavaScript" command and are written directly into the package's | |
218 // directory. When pub's dart2js transformer then tries to create the | |
219 // same file name, we get a build error. To avoid that, just don't | |
220 // consider that file to be a source. | |
221 // TODO(rnystrom): Remove this when the Editor no longer generates | |
222 // .js files. See #15859. | |
223 if (event.path.endsWith(".dart.js")) return; | |
224 | |
225 var id = new AssetId(package.name, | |
226 path.relative(event.path, from: package.dir)); | |
227 if (event.type == ChangeType.REMOVE) { | |
228 barback.removeSources([id]); | |
229 } else { | |
230 barback.updateSources([id]); | |
231 } | |
232 }); | |
233 return watcher.ready; | |
234 })).then((_) { | |
235 barback.updateSources(_listAssets(_graph.entrypoint, package)); | |
236 }); | |
237 })); | |
238 } | |
239 | |
240 /// Lists all of the visible files in [package]. | |
241 /// | |
242 /// This is the recursive contents of the "asset" and "lib" directories (if | |
243 /// present). If [package] is the entrypoint package, it also includes the | |
244 /// contents of "web". | |
245 List<AssetId> _listAssets(Entrypoint entrypoint, Package package) { | |
246 var files = <AssetId>[]; | |
247 | |
248 for (var dirPath in _getPublicDirectories(entrypoint, package)) { | |
249 var dir = path.join(package.dir, dirPath); | |
250 if (!dirExists(dir)) continue; | |
251 for (var entry in listDir(dir, recursive: true)) { | |
252 // Ignore "packages" symlinks if there. | |
253 if (path.split(entry).contains("packages")) continue; | |
254 | |
255 // Skip directories. | |
256 if (!fileExists(entry)) continue; | |
257 | |
258 // Skip ".js" files that were (most likely) compiled from nearby ".dart" | |
259 // files. These are created by the Editor's "Run as JavaScript" command | |
260 // and are written directly into the package's directory. When pub's | |
261 // dart2js transformer then tries to create the same file name, we get | |
262 // a build error. To avoid that, just don't consider that file to be a | |
263 // source. | |
264 // TODO(rnystrom): Remove this when the Editor no longer generates .js | |
265 // files. See #15859. | |
266 if (entry.endsWith(".dart.js")) continue; | |
267 | |
268 var id = new AssetId(package.name, | |
269 path.relative(entry, from: package.dir)); | |
270 files.add(id); | |
271 } | |
272 } | |
273 | |
274 return files; | |
275 } | |
276 | |
277 /// Gets the names of the top-level directories in [package] whose contents | |
278 /// should be provided as source assets. | |
279 Iterable<String> _getPublicDirectories(Entrypoint entrypoint, | |
280 Package package) { | |
281 var directories = ["asset", "lib"]; | |
282 | |
283 if (package.name == entrypoint.root.name) { | |
284 directories.addAll(_buildDirectories); | |
285 } | |
286 | |
287 return directories; | |
288 } | |
289 } | |
290 | |
291 /// Log [entry] using Pub's logging infrastructure. | |
292 /// | |
293 /// Since both [LogEntry] objects and the message itself often redundantly | |
294 /// show the same context like the file where an error occurred, this tries | |
295 /// to avoid showing redundant data in the entry. | |
296 void _log(LogEntry entry) { | |
297 messageMentions(String text) { | |
298 return entry.message.toLowerCase().contains(text.toLowerCase()); | |
299 } | |
300 | |
301 var prefixParts = []; | |
302 | |
303 // Show the level (unless the message mentions it). | |
304 if (!messageMentions(entry.level.name)) { | |
305 prefixParts.add("${entry.level} from"); | |
306 } | |
307 | |
308 // Show the transformer. | |
309 prefixParts.add(entry.transform.transformer); | |
310 | |
311 // Mention the primary input of the transform unless the message seems to. | |
312 if (!messageMentions(entry.transform.primaryId.path)) { | |
313 prefixParts.add("on ${entry.transform.primaryId}"); | |
314 } | |
315 | |
316 // If the relevant asset isn't the primary input, mention it unless the | |
317 // message already does. | |
318 if (entry.assetId != entry.transform.primaryId && | |
319 !messageMentions(entry.assetId.path)) { | |
320 prefixParts.add("with input ${entry.assetId}"); | |
321 } | |
322 | |
323 var prefix = "[${prefixParts.join(' ')}]:"; | |
324 var message = entry.message; | |
325 if (entry.span != null) { | |
326 message = entry.span.getLocationMessage(entry.message); | |
327 } | |
328 | |
329 switch (entry.level) { | |
330 case LogLevel.ERROR: | |
331 log.error("${log.red(prefix)}\n$message"); | |
332 break; | |
333 | |
334 case LogLevel.WARNING: | |
335 log.warning("${log.yellow(prefix)}\n$message"); | |
336 break; | |
337 | |
338 case LogLevel.INFO: | |
339 log.message("${log.cyan(prefix)}\n$message"); | |
340 break; | |
341 } | |
342 } | |
343 | |
344 /// An enum describing different modes of constructing a [DirectoryWatcher]. | |
345 abstract class WatcherType { | |
346 /// A watcher that automatically chooses its type based on the operating | |
347 /// system. | |
348 static const AUTO = const _AutoWatcherType(); | |
349 | |
350 /// A watcher that always polls the filesystem for changes. | |
351 static const POLLING = const _PollingWatcherType(); | |
352 | |
353 /// No directory watcher at all. | |
354 static const NONE = const _NoneWatcherType(); | |
355 | |
356 /// Creates a new DirectoryWatcher. | |
357 DirectoryWatcher create(String directory); | |
358 | |
359 String toString(); | |
360 } | |
361 | |
362 class _AutoWatcherType implements WatcherType { | |
363 const _AutoWatcherType(); | |
364 | |
365 DirectoryWatcher create(String directory) => | |
366 new DirectoryWatcher(directory); | |
367 | |
368 String toString() => "auto"; | |
369 } | |
370 | |
371 class _PollingWatcherType implements WatcherType { | |
372 const _PollingWatcherType(); | |
373 | |
374 DirectoryWatcher create(String directory) => | |
375 new PollingDirectoryWatcher(directory); | |
376 | |
377 String toString() => "polling"; | |
378 } | |
379 | |
380 class _NoneWatcherType implements WatcherType { | |
381 const _NoneWatcherType(); | |
382 | |
383 DirectoryWatcher create(String directory) => null; | |
384 | |
385 String toString() => "none"; | |
386 } | |
OLD | NEW |