Chromium Code Reviews| 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 |