diff --git a/lib/src/barback/asset_environment.dart b/lib/src/barback/asset_environment.dart index 935bb64117d11aa08eab2fc50e8ef5f0fe508224..6663f5b6bdf4a6751942945246b988c492a49de0 100644 --- a/lib/src/barback/asset_environment.dart +++ b/lib/src/barback/asset_environment.dart @@ -18,12 +18,7 @@ import '../package.dart'; import '../package_graph.dart'; import '../source/cached.dart'; import '../utils.dart'; -import 'dartdevc/dartdevc_bootstrap_transformer.dart'; -import 'dartdevc/dartdevc_module_transformer.dart'; -import 'dartdevc/dartdevc_resource_transformer.dart'; -import 'dartdevc/linked_summary_transformer.dart'; -import 'dartdevc/module_config_transformer.dart'; -import 'dartdevc/unlinked_summary_transformer.dart'; +import 'dartdevc/dartdevc_environment.dart'; import 'admin_server.dart'; import 'barback_server.dart'; import 'compiler.dart'; @@ -87,10 +82,16 @@ class AssetEnvironment { return log.progress("Loading asset environment", () async { var graph = _adjustPackageGraph(entrypoint.packageGraph, mode, packages); var barback = new Barback(new PubPackageProvider(graph, compiler)); + DartDevcEnvironment dartDevcEnvironment; + if (compiler == Compiler.dartDevc) { + dartDevcEnvironment = + new DartDevcEnvironment(barback, mode, environmentConstants, graph); + } barback.log.listen(_log); var environment = new AssetEnvironment._(graph, barback, mode, - watcherType, hostname, basePort, environmentConstants, compiler); + watcherType, hostname, basePort, environmentConstants, compiler, + dartDevcEnvironment: dartDevcEnvironment); await environment._load(entrypoints: entrypoints); return environment; @@ -126,6 +127,11 @@ class AssetEnvironment { /// The [Barback] instance used to process assets in this environment. final Barback barback; + /// Manages running the dartdevc compiler on top of [barback]. + /// + /// This is `null` unless `compiler == Compiler.dartDevc`. + final DartDevcEnvironment dartDevcEnvironment; + /// The root package being built. Package get rootPackage => graph.entrypoint.root; @@ -175,35 +181,18 @@ class AssetEnvironment { final Compiler compiler; AssetEnvironment._(this.graph, this.barback, this.mode, this._watcherType, - this._hostname, this._basePort, this.environmentConstants, this.compiler); + this._hostname, this._basePort, this.environmentConstants, this.compiler, + {this.dartDevcEnvironment}); /// Gets the built-in [Transformer]s or [AggregateTransformer]s that should be /// added to [package]. /// /// Returns `null` if there are none. - Iterable<Set> getBuiltInTransformers(Package package) { - var transformers = <List>[]; + Iterable<Set<Transformer>> getBuiltInTransformers(Package package) { + var transformers = <List<Transformer>>[]; var isRootPackage = package.name == rootPackage.name; switch (compiler) { - case Compiler.dartDevc: - var firstPhase = <dynamic>[new ModuleConfigTransformer()]; - var lastPhase = <dynamic>[ - new DartDevcModuleTransformer(mode, - environmentConstants: environmentConstants) - ]; - if (isRootPackage) { - firstPhase.add(new DartDevcResourceTransformer()); - lastPhase.add(new DartDevcBootstrapTransformer(mode)); - } - - transformers.addAll([ - firstPhase, - [new UnlinkedSummaryTransformer()], - [new LinkedSummaryTransformer()], - lastPhase, - ]); - break; case Compiler.dart2JS: // the dart2js transformer only runs on the root package. if (isRootPackage) { @@ -286,7 +275,8 @@ class AssetEnvironment { sourceDirectory.watchSubscription = await _provideDirectorySources(rootPackage, rootDirectory); - return await sourceDirectory.serve(); + return await sourceDirectory.serve( + dartDevcEnvironment: dartDevcEnvironment); } /// Binds a new port to serve assets from within the "bin" directory of @@ -465,6 +455,11 @@ class AssetEnvironment { assert(_modifiedSources != null); barback.updateSources(_modifiedSources); + if (dartDevcEnvironment != null) { + var modifiedPackages = new Set<String>() + ..addAll(_modifiedSources.map((id) => id.package)); + modifiedPackages.forEach(dartDevcEnvironment.invalidatePackage); + } _modifiedSources = null; } @@ -478,9 +473,6 @@ class AssetEnvironment { /// If [Compiler.dart2JS], then the [Dart2JSTransformer] is implicitly /// added to end of the root package's transformer phases. /// - /// if [Compiler.dartDevc], then the [DevCompilerTransformer] is - /// implicitly added to the end of all package's transformer phases. - /// /// If [entrypoints] is passed, only transformers necessary to run those /// entrypoints will be loaded. /// @@ -489,7 +481,8 @@ class AssetEnvironment { Future _load({Iterable<AssetId> entrypoints}) { return log.progress("Initializing barback", () async { // Bind a server that we can use to load the transformers. - var transformerServer = await BarbackServer.bind(this, _hostname, 0); + var transformerServer = await BarbackServer.bind(this, _hostname, 0, + dartDevcEnvironment: dartDevcEnvironment); var errorStream = barback.errors.map((error) { // Even most normally non-fatal barback errors should take down pub if @@ -578,6 +571,7 @@ class AssetEnvironment { var ids = _listDirectorySources(package, dir); if (_modifiedSources == null) { barback.updateSources(ids); + dartDevcEnvironment?.invalidatePackage(package.name); } else { _modifiedSources.addAll(ids); } @@ -588,6 +582,7 @@ class AssetEnvironment { var ids = _listDirectorySources(rootPackage, dir); if (_modifiedSources == null) { barback.removeSources(ids); + dartDevcEnvironment?.invalidatePackage(rootPackage.name); } else { _modifiedSources.removeAll(ids); } @@ -662,11 +657,13 @@ class AssetEnvironment { _modifiedSources.remove(id); } else { barback.removeSources([id]); + dartDevcEnvironment?.invalidatePackage(package.name); } } else if (_modifiedSources != null) { _modifiedSources.add(id); } else { barback.updateSources([id]); + dartDevcEnvironment?.invalidatePackage(package.name); } }); diff --git a/lib/src/barback/barback_server.dart b/lib/src/barback/barback_server.dart index 6e29bf0b65ed664a1b597560ba681523f1b0dd03..17e634ad0f30ba00d8c899aa7ae9559c93544c73 100644 --- a/lib/src/barback/barback_server.dart +++ b/lib/src/barback/barback_server.dart @@ -19,6 +19,8 @@ import '../utils.dart'; import 'base_server.dart'; import 'asset_environment.dart'; +import 'dartdevc/dartdevc_environment.dart'; + /// Callback for determining if an asset with [id] should be served or not. typedef bool AllowAsset(AssetId id); @@ -43,6 +45,11 @@ class BarbackServer extends BaseServer<BarbackServerResult> { /// If this is `null`, all assets may be served. AllowAsset allowAsset; + /// Manages running the dartdevc compiler on top of barback. + /// + /// This is `null` unless `environment.compiler == Compiler.dartDevc`. + final DartDevcEnvironment dartDevcEnvironment; + /// Creates a new server and binds it to [port] of [host]. /// /// This server serves assets from [barback], and uses [rootDirectory] @@ -52,7 +59,9 @@ class BarbackServer extends BaseServer<BarbackServerResult> { /// URLs). If [package] is omitted, it defaults to the entrypoint package. static Future<BarbackServer> bind( AssetEnvironment environment, String host, int port, - {String package, String rootDirectory}) { + {String package, + String rootDirectory, + DartDevcEnvironment dartDevcEnvironment}) { if (package == null) package = environment.rootPackage.name; return bindServer(host, port).then((server) { if (rootDirectory == null) { @@ -60,12 +69,14 @@ class BarbackServer extends BaseServer<BarbackServerResult> { } else { log.fine('Bound "$rootDirectory" to $host:$port.'); } - return new BarbackServer._(environment, server, package, rootDirectory); + return new BarbackServer._(environment, server, package, rootDirectory, + dartDevcEnvironment: dartDevcEnvironment); }); } BarbackServer._(AssetEnvironment environment, HttpServer server, this.package, - this.rootDirectory) + this.rootDirectory, + {this.dartDevcEnvironment}) : super(environment, server); /// Converts a [url] served by this server into an [AssetId] that can be @@ -115,51 +126,54 @@ class BarbackServer extends BaseServer<BarbackServerResult> { return environment.barback .getAssetById(id) - .then((result) { - return result; - }) .then((asset) => _serveAsset(request, asset)) .catchError((error, trace) { - if (error is! AssetNotFoundException) throw error; - return environment.barback - .getAssetById(id.addExtension("/index.html")) - .then((asset) { - if (request.url.path.isEmpty || request.url.path.endsWith('/')) { - return _serveAsset(request, asset); - } - - // We only want to serve index.html if the URL explicitly ends in a - // slash. For other URLs, we redirect to one with the slash added to - // implicitly support that too. This follows Apache's behavior. - logRequest(request, "302 Redirect to /${request.url}/"); - return new shelf.Response.found('/${request.url}/'); - }).catchError((newError, newTrace) { - // If we find neither the original file or the index, we should report - // the error about the original to the user. - throw newError is AssetNotFoundException ? error : newError; - }); - }) - .catchError((error, trace) { - if (error is! AssetNotFoundException) { - var chain = new Chain.forTrace(trace); - logRequest(request, "$error\n$chain"); - - addError(error, chain); - close(); - return new shelf.Response.internalServerError(); - } - - addResult(new BarbackServerResult._failure(request.url, id, error)); - return notFound(request, asset: id); - }) - .then((response) { - // Allow requests of any origin to access "pub serve". This is useful for - // running "pub serve" in parallel with another development server. Since - // "pub serve" is only used as a development server and doesn't require - // any sort of credentials anyway, this is secure. - return response - .change(headers: const {"Access-Control-Allow-Origin": "*"}); - }); + if (error is! AssetNotFoundException) throw error; + return environment.barback + .getAssetById(id.addExtension("/index.html")) + .then((asset) { + if (request.url.path.isEmpty || request.url.path.endsWith('/')) { + return _serveAsset(request, asset); + } + + // We only want to serve index.html if the URL explicitly ends in a + // slash. For other URLs, we redirect to one with the slash added to + // implicitly support that too. This follows Apache's behavior. + logRequest(request, "302 Redirect to /${request.url}/"); + return new shelf.Response.found('/${request.url}/'); + }).catchError((newError, newTrace) { + // If we find neither the original file or the index, we should report + // the error about the original to the user. + throw newError is AssetNotFoundException ? error : newError; + }); + }).catchError((error, trace) { + if (error is! AssetNotFoundException || dartDevcEnvironment == null) { + throw error; + } + return dartDevcEnvironment.getAssetById(id).then((asset) { + if (asset == null) throw new AssetNotFoundException(id); + return _serveAsset(request, asset); + }); + }).catchError((error, trace) { + if (error is! AssetNotFoundException) { + var chain = new Chain.forTrace(trace); + logRequest(request, "$error\n$chain"); + + addError(error, chain); + close(); + return new shelf.Response.internalServerError(); + } + + addResult(new BarbackServerResult._failure(request.url, id, error)); + return notFound(request, asset: id); + }).then((response) { + // Allow requests of any origin to access "pub serve". This is useful for + // running "pub serve" in parallel with another development server. Since + // "pub serve" is only used as a development server and doesn't require + // any sort of credentials anyway, this is secure. + return response + .change(headers: const {"Access-Control-Allow-Origin": "*"}); + }); } /// Returns the body of [asset] as a response to [request]. diff --git a/lib/src/barback/dartdevc/dartdevc.dart b/lib/src/barback/dartdevc/dartdevc.dart index a17853ae5b0ecb87629af941f3b6e15cb221ae7e..9b79cbf0a38fe7aac45e021b3a488e3f223104d6 100644 --- a/lib/src/barback/dartdevc/dartdevc.dart +++ b/lib/src/barback/dartdevc/dartdevc.dart @@ -5,18 +5,32 @@ import 'dart:async'; import 'dart:io'; +import 'package:analyzer/analyzer.dart'; import 'package:barback/barback.dart'; import 'package:bazel_worker/bazel_worker.dart'; import 'package:cli_util/cli_util.dart' as cli_util; import 'package:path/path.dart' as p; +import '../../dart.dart'; import '../../io.dart'; -import 'module.dart'; import 'module_reader.dart'; import 'scratch_space.dart'; import 'summaries.dart'; import 'workers.dart'; +/// Returns whether or not [dartId] is an app entrypoint (basically, whether or +/// not it has a `main` function). +Future<bool> isAppEntryPoint( + AssetId dartId, Future<Asset> getAsset(AssetId id)) async { + assert(dartId.extension == '.dart'); + var dartAsset = await getAsset(dartId); + // Skip reporting errors here, dartdevc will report them later with nicer + // formatting. + var parsed = parseCompilationUnit(await dartAsset.readAsString(), + suppressErrors: true); + return isEntrypoint(parsed); +} + /// Bootstraps the JS module for the entrypoint dart file [dartEntrypointId] /// with two additional JS files: /// @@ -34,7 +48,10 @@ import 'workers.dart'; /// Synchronously returns a `Map<AssetId, Future<Asset>>` so that you can know /// immediately what assets will be output. Map<AssetId, Future<Asset>> bootstrapDartDevcEntrypoint( - AssetId dartEntrypointId, BarbackMode mode, ModuleReader moduleReader) { + AssetId dartEntrypointId, + BarbackMode mode, + ModuleReader moduleReader, + Future<Asset> getAsset(AssetId id)) { var bootstrapId = dartEntrypointId.addExtension('.bootstrap.js'); var jsEntrypointId = dartEntrypointId.addExtension('.js'); var jsMapEntrypointId = jsEntrypointId.addExtension('.map'); @@ -63,15 +80,31 @@ Map<AssetId, Future<Asset>> bootstrapDartDevcEntrypoint( // TODO(jakemac53): Sane module name creation, this only works in the most // basic of cases. // - // See https://github.com/dart-lang/sdk/issues/27262 for the root issue which - // will allow us to not rely on the naming schemes that dartdevc uses + // See https://github.com/dart-lang/sdk/issues/27262 for the root issue + // which will allow us to not rely on the naming schemes that dartdevc uses // internally, but instead specify our own. var appModuleScope = p.url .split(p.url.withoutExtension( p.url.relative(dartEntrypointId.path, from: moduleDir))) .join("__") .replaceAll('.', '\$46'); + + // Modules not under a `packages` directory need custom module paths. + var customModulePaths = <String>[]; + var transitiveDeps = await moduleReader.readTransitiveDeps(module); + for (var dep in transitiveDeps) { + if (dep.dir != 'lib') { + customModulePaths.add('"${dep.dir}/${dep.name}": "${dep.name}"'); + } + } + var bootstrapContent = ''' +require.config({ + paths: { + ${customModulePaths.join(',\n ')} + } +}); + require(["$appModulePath", "dart_sdk"], function(app, dart_sdk) { dart_sdk._isolate_helper.startRootIsolate(() => {}, []); app.$appModuleScope.main(); @@ -112,20 +145,34 @@ document.head.appendChild(el); /// Synchronously returns a `Map<AssetId, Future<Asset>>` so that you can know /// immediately what assets will be output. Map<AssetId, Future<Asset>> createDartdevcModule( - Module module, + AssetId id, + ModuleReader moduleReader, ScratchSpace scratchSpace, - Set<AssetId> linkedSummaryIds, Map<String, String> environmentConstants, BarbackMode mode, logError(String message)) { + assert(id.extension == '.js'); var outputCompleters = <AssetId, Completer<Asset>>{ - module.id.jsId: new Completer(), + id: new Completer(), }; if (mode == BarbackMode.DEBUG) { - outputCompleters[module.id.jsSourceMapId] = new Completer(); + outputCompleters[id.addExtension('.map')] = new Completer(); } () async { + var module = await moduleReader.moduleFor(id); + if (module == null) { + logError('No module found for $id.'); + outputCompleters.values.forEach((c) => c.complete(null)); + return; + } + var transitiveModuleDeps = await moduleReader.readTransitiveDeps(module); + var linkedSummaryIds = + transitiveModuleDeps.map((depId) => depId.linkedSummaryId).toSet(); + var allAssetIds = new Set<AssetId>() + ..addAll(module.assetIds) + ..addAll(linkedSummaryIds); + await scratchSpace.ensureAssets(allAssetIds); var jsOutputFile = scratchSpace.fileFor(module.id.jsId); var sdk_summary = p.url.join(sdkDir.path, 'lib/_internal/ddc_sdk.sum'); var request = new WorkRequest(); @@ -158,8 +205,8 @@ Map<AssetId, Future<Asset>> createDartdevcModule( request.arguments.addAll(['-s', scratchSpace.fileFor(id).path]); } - // Add URL mappings for all the package: files to tell DartDevc where to find - // them. + // Add URL mappings for all the package: files to tell DartDevc where to + // find them. for (var id in module.assetIds) { var uri = canonicalUriFor(id); if (uri.startsWith('package:')) { @@ -167,8 +214,8 @@ Map<AssetId, Future<Asset>> createDartdevcModule( .add('--url-mapping=$uri,${scratchSpace.fileFor(id).path}'); } } - // And finally add all the urls to compile, using the package: path for files - // under lib and the full absolute path for other files. + // And finally add all the urls to compile, using the package: path for + // files under lib and the full absolute path for other files. request.arguments.addAll(module.assetIds.map((id) { var uri = canonicalUriFor(id); if (uri.startsWith('package:')) { @@ -183,12 +230,9 @@ Map<AssetId, Future<Asset>> createDartdevcModule( // status code if something failed. Today we just make sure there is an output // JS file to verify it was successful. if (response.exitCode != EXIT_CODE_OK || !jsOutputFile.existsSync()) { - var message = - 'Error compiling dartdevc module: ${module.id}.\n${response.output}'; - logError(message); - outputCompleters.values.forEach((completer) { - completer.completeError(message); - }); + logError('Error compiling dartdevc module: ${module.id}.\n' + '${response.output}'); + outputCompleters.values.forEach((c) => c.complete(null)); } else { outputCompleters[module.id.jsId].complete( new Asset.fromBytes(module.id.jsId, jsOutputFile.readAsBytesSync())); diff --git a/lib/src/barback/dartdevc/dartdevc_bootstrap_transformer.dart b/lib/src/barback/dartdevc/dartdevc_bootstrap_transformer.dart deleted file mode 100644 index 863531a839d87a6e0f1c97a197a8bdb9c5cf33b0..0000000000000000000000000000000000000000 --- a/lib/src/barback/dartdevc/dartdevc_bootstrap_transformer.dart +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:async'; - -import 'package:analyzer/analyzer.dart'; -import 'package:barback/barback.dart'; - -import '../../dart.dart'; -import '../../io.dart'; -import 'dartdevc.dart'; -import 'module_reader.dart'; - -class DartDevcBootstrapTransformer extends Transformer { - final BarbackMode mode; - - DartDevcBootstrapTransformer(this.mode); - - @override - bool isPrimary(AssetId id) { - // Only `.dart` files not under `lib` or `bin` are considered candidates for - // being dartdevc application entrypoints. - if (id.extension != '.dart') return false; - var dir = topLevelDir(id.path); - return dir != 'lib' && dir != 'bin'; - } - - @override - Future apply(Transform transform) async { - var parsed = - parseCompilationUnit(await transform.primaryInput.readAsString()); - if (!isEntrypoint(parsed)) return; - var outputs = await bootstrapDartDevcEntrypoint(transform.primaryInput.id, - mode, new ModuleReader(transform.readInputAsString)); - await Future.wait(outputs.values - .map((futureAsset) async => transform.addOutput(await futureAsset))); - } -} diff --git a/lib/src/barback/dartdevc/dartdevc_environment.dart b/lib/src/barback/dartdevc/dartdevc_environment.dart new file mode 100644 index 0000000000000000000000000000000000000000..382172807b40c16c63fc06a1b150bb78c7bedfa4 --- /dev/null +++ b/lib/src/barback/dartdevc/dartdevc_environment.dart @@ -0,0 +1,266 @@ +// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:barback/barback.dart'; +import 'package:cli_util/cli_util.dart' as cli_util; +import 'package:path/path.dart' as p; + +import '../../io.dart'; +import '../../log.dart' as log; +import '../../package_graph.dart'; +import 'dartdevc.dart'; +import 'module.dart'; +import 'module_computer.dart'; +import 'module_reader.dart'; +import 'scratch_space.dart'; +import 'summaries.dart'; + +/// Handles running dartdevc on top of a [Barback] instance. +/// +/// You must call [invalidatePackage] any time a package is updated, since +/// barback doesn't provide a mechanism to tell you which files have changed. +class DartDevcEnvironment { + final _AssetCache _assetCache; + final Barback _barback; + final Map<String, String> _environmentConstants; + final BarbackMode _mode; + ModuleReader _moduleReader; + final PackageGraph _packageGraph; + ScratchSpace _scratchSpace; + + DartDevcEnvironment( + this._barback, this._mode, this._environmentConstants, this._packageGraph) + : _assetCache = new _AssetCache(_packageGraph) { + _moduleReader = new ModuleReader(_readModule); + _scratchSpace = new ScratchSpace(_getAsset); + } + + /// Deletes the [_scratchSpace]. + Future cleanUp() => _scratchSpace.delete(); + + /// Builds all dartdevc files required for all app entrypoints in + /// [inputAssets]. + /// + /// Returns only the `.js` files which are required to load the apps. + Future<AssetSet> doFullBuild(AssetSet inputAssets, + {logError(String message)}) async { + var modulesToBuild = new Set<ModuleId>(); + var jsAssets = new AssetSet(); + for (var asset in inputAssets) { + if (asset.id.package != _packageGraph.entrypoint.root.name) continue; + if (asset.id.extension != '.dart') continue; + // We only care about real entrypoint modules, we collect those and all + // their transitive deps. + if (!await isAppEntryPoint(asset.id, _barback.getAssetById)) continue; + // Build the entrypoint js files, and collect the set of transitive + // modules that are required (will be built later). + var futureAssets = + _buildAsset(asset.id.addExtension('.js'), logError: logError); + var assets = await Future.wait(futureAssets.values); + jsAssets.addAll(assets.where((asset) => asset != null)); + var module = await _moduleReader.moduleFor(asset.id); + modulesToBuild.add(module.id); + modulesToBuild.addAll(await _moduleReader.readTransitiveDeps(module)); + } + + // Build all required modules for the apps that were discovered. + var allFutureAssets = <Future<Asset>>[]; + for (var module in modulesToBuild) { + allFutureAssets + .addAll(_buildAsset(module.jsId, logError: logError).values); + } + var assets = await Future.wait(allFutureAssets); + jsAssets.addAll(assets.where((asset) => asset != null)); + + return jsAssets; + } + + /// Attempt to get an [Asset] by [id], completes with an + /// [AssetNotFoundException] if the asset couldn't be built. + Future<Asset> getAssetById(AssetId id) async { + if (_assetCache[id] == null) { + if (_isEntrypointId(id)) { + var dartId = _entrypointDartId(id); + if (dartId != null && + await isAppEntryPoint(dartId, _barback.getAssetById)) { + _buildAsset(id); + } + } else { + _buildAsset(id); + } + } + return _assetCache[id]; + } + + /// Invalidates [package] and all packages that depend on [package]. + void invalidatePackage(String package) { + _assetCache.invalidatePackage(package); + _scratchSpace.deletePackageFiles(package, + isRootPackage: package == _packageGraph.entrypoint.root.name); + } + + /// Handles building all assets that we know how to build. + /// + /// Completes with an [AssetNotFoundException] if the asset couldn't be built. + Map<AssetId, Future<Asset>> _buildAsset(AssetId id, + {logError(String message)}) { + logError ??= log.error; + Map<AssetId, Future<Asset>> assets; + if (id.path.endsWith(unlinkedSummaryExtension)) { + assets = { + id: createUnlinkedSummary(id, _moduleReader, _scratchSpace, logError) + }; + } else if (id.path.endsWith(linkedSummaryExtension)) { + assets = { + id: createLinkedSummary(id, _moduleReader, _scratchSpace, logError) + }; + } else if (_isEntrypointId(id)) { + var dartId = _entrypointDartId(id); + if (dartId != null) { + assets = bootstrapDartDevcEntrypoint( + dartId, _mode, _moduleReader, _barback.getAssetById); + } + } else if (id.path.endsWith('require.js') || + id.path.endsWith('dart_sdk.js')) { + assets = {id: _buildJsResource(id)}; + } else if (id.path.endsWith('require.js.map') || + id.path.endsWith('dart_sdk.js.map')) { + assets = {id: new Future.error(new AssetNotFoundException(id))}; + } else if (id.path.endsWith('.js') || id.path.endsWith('.js.map')) { + var jsId = id.extension == '.map' ? id.changeExtension('') : id; + assets = createDartdevcModule(jsId, _moduleReader, _scratchSpace, + _environmentConstants, _mode, logError); + } else if (id.path.endsWith(moduleConfigName)) { + assets = {id: _buildModuleConfig(id)}; + } + assets ??= <AssetId, Future<Asset>>{}; + + for (var id in assets.keys) { + _assetCache[id] = assets[id]; + } + return assets; + } + + /// Builds a module config asset at [id]. + Future<Asset> _buildModuleConfig(AssetId id) async { + assert(id.path.endsWith(moduleConfigName)); + var moduleDir = topLevelDir(id.path); + var allAssets = await _barback.getAllAssets(); + var moduleAssets = allAssets.where((asset) => + asset.id.package == id.package && + asset.id.extension == '.dart' && + topLevelDir(asset.id.path) == moduleDir); + var moduleMode = + moduleDir == 'lib' ? ModuleMode.public : ModuleMode.private; + var modules = await computeModules(moduleMode, moduleAssets); + var encoded = JSON.encode(modules); + return new Asset.fromString(id, encoded); + } + + /// Builds the `dart_sdk.js` or `require.js` assets by copying them from the + /// SDK. + Future<Asset> _buildJsResource(AssetId id) async { + var sdk = cli_util.getSdkDir(); + + switch (p.url.basename(id.path)) { + case 'dart_sdk.js': + var sdkAmdJsPath = + p.url.join(sdk.path, 'lib/dev_compiler/amd/dart_sdk.js'); + return new Asset.fromPath(id, sdkAmdJsPath); + case 'require.js': + var requireJsPath = + p.url.join(sdk.path, 'lib/dev_compiler/amd/require.js'); + return new Asset.fromFile(id, new File(requireJsPath)); + default: + return null; + } + } + + /// Whether or not this looks like a request for an entrypoint or bootstrap + /// file. + bool _isEntrypointId(AssetId id) => + id.path.endsWith('.bootstrap.js') || + id.path.endsWith('.bootstrap.js.map') || + id.path.endsWith('.dart.js') || + id.path.endsWith('.dart.js.map'); + + /// Helper to read a module config file, used by [_moduleReader]. + /// + /// Skips barback and reads directly from [this] since we create all these + /// files. + Future<String> _readModule(AssetId moduleConfigId) async { + var asset = await getAssetById(moduleConfigId); + return asset.readAsString(); + } + + /// Gets an [Asset] by [id] asynchronously. + /// + /// All `.dart` files are read from [_barback], and all other files are read + /// from [this]. This is because the only files we care about from barback are + /// `.dart` files. + Future<Asset> _getAsset(AssetId id) async { + var asset = id.extension == '.dart' + ? await _barback.getAssetById(id) + : await getAssetById(id); + if (asset == null) throw new AssetNotFoundException(id); + return asset; + } +} + +/// Gives the dart entrypoint [AssetId] for a bootstrap js [id]. +AssetId _entrypointDartId(AssetId id) { + if (id.extension == '.map') id = id.changeExtension(''); + assert(id.path.endsWith('.bootstrap.js') || id.path.endsWith('.dart.js')); + // Skip entrypoints under lib. + if (topLevelDir(id.path) == 'lib') return null; + + // Remove the `.js` extension. + var dartId = id.changeExtension(''); + // Conditionally change the `.bootstrap` extension to a `.dart` extension. + if (dartId.extension == '.bootstrap') dartId.changeExtension('.dart'); + assert(dartId.extension == '.dart'); + return dartId; +} + +/// Manages a set of cached future [Asset]s. +class _AssetCache { + /// [Asset]s are indexed first by package and then path, this allows us to + /// invalidate whole packages efficiently. + final _assets = <String, Map<String, Future<Asset>>>{}; + + final PackageGraph _packageGraph; + + _AssetCache(this._packageGraph); + + Future<Asset> operator [](AssetId id) { + var packageCache = _assets[id.package]; + if (packageCache == null) return null; + return packageCache[id.path]; + } + + void operator []=(AssetId id, Future<Asset> asset) { + var packageCache = + _assets.putIfAbsent(id.package, () => <String, Future<Asset>>{}); + packageCache[id.path] = asset; + } + + /// Invalidates [package] and all packages that depend on [package]. + void invalidatePackage(String packageNameToInvalidate) { + _assets.remove(packageNameToInvalidate); + // Also invalidate any package with a transitive dep on the invalidated + // package. + var packageToInvalidate = _packageGraph.packages[packageNameToInvalidate]; + for (var packageName in _packageGraph.packages.keys) { + if (_packageGraph + .transitiveDependencies(packageName) + .contains(packageToInvalidate)) { + _assets.remove(packageName); + } + } + } +} diff --git a/lib/src/barback/dartdevc/dartdevc_module_transformer.dart b/lib/src/barback/dartdevc/dartdevc_module_transformer.dart deleted file mode 100644 index f1f34bb8a73f1ad1738f5c47162d376e9e53ba5b..0000000000000000000000000000000000000000 --- a/lib/src/barback/dartdevc/dartdevc_module_transformer.dart +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:async'; - -import 'package:barback/barback.dart'; - -import 'dartdevc.dart'; -import 'module.dart'; -import 'module_reader.dart'; -import 'scratch_space.dart'; - -/// Creates dartdevc modules given [moduleConfigName] files which describe a set -/// of [Module]s. -/// -/// Linked summaries should have already been created for all modules and will -/// be read in to create the modules. -class DartDevcModuleTransformer extends Transformer { - @override - String get allowedExtensions => moduleConfigName; - - final Map<String, String> environmentConstants; - final BarbackMode mode; - - DartDevcModuleTransformer(this.mode, {this.environmentConstants = const {}}); - - @override - Future apply(Transform transform) async { - ScratchSpace scratchSpace; - var reader = new ModuleReader(transform.readInputAsString); - var modules = await reader.readModules(transform.primaryInput.id); - try { - var allAssetIds = new Set<AssetId>(); - var summariesForModule = <ModuleId, Set<AssetId>>{}; - for (var module in modules) { - var transitiveModuleDeps = await reader.readTransitiveDeps(module); - var linkedSummaryIds = - transitiveModuleDeps.map((depId) => depId.linkedSummaryId).toSet(); - summariesForModule[module.id] = linkedSummaryIds; - allAssetIds..addAll(module.assetIds)..addAll(linkedSummaryIds); - } - // Create a single temp environment for all the modules in this package. - scratchSpace = - await ScratchSpace.create(allAssetIds, transform.readInput); - // We only log warnings in debug mode for ddc modules because they don't all - // need to compile successfully, only the ones imported by an entrypoint do. - var logError = mode == BarbackMode.DEBUG - ? transform.logger.warning - : transform.logger.error; - await Future.wait(modules.map((m) async { - var outputs = createDartdevcModule(m, scratchSpace, - summariesForModule[m.id], environmentConstants, mode, logError); - await Future.wait(outputs.values.map( - (futureAsset) async => transform.addOutput(await futureAsset))); - })); - } finally { - scratchSpace?.delete(); - } - } -} diff --git a/lib/src/barback/dartdevc/dartdevc_resource_transformer.dart b/lib/src/barback/dartdevc/dartdevc_resource_transformer.dart deleted file mode 100644 index d160243fde4ead56ba9f8a7a77191ee02567f49b..0000000000000000000000000000000000000000 --- a/lib/src/barback/dartdevc/dartdevc_resource_transformer.dart +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:async'; - -import 'package:analyzer/analyzer.dart'; -import 'package:barback/barback.dart'; -import 'package:path/path.dart' as p; - -import '../../dart.dart'; -import '../../io.dart'; -import 'dartdevc.dart'; - -/// Copies the `dart_sdk.js` and `require.js` AMD files from the SDK into each -/// entrypoint dir. -class DartDevcResourceTransformer extends AggregateTransformer - implements DeclaringAggregateTransformer { - /// Group files by output directory, skipping `lib` and `bin`. - @override - String classifyPrimary(AssetId id) { - if (p.extension(id.path) != '.dart') return null; - var dir = topLevelDir(id.path); - if (dir == 'lib' || dir == 'bin') return null; - return p.url.dirname(id.path); - } - - @override - Future apply(AggregateTransform transform) async { - // If there are no entrypoints then skip this folder. - var hasEntrypoint = false; - await for (var asset in transform.primaryInputs) { - if (isEntrypoint(parseCompilationUnit(await asset.readAsString(), - parseFunctionBodies: false))) { - hasEntrypoint = true; - break; - } - } - if (!hasEntrypoint) return; - - var outputs = copyDartDevcResources(transform.package, transform.key); - outputs.values.forEach(transform.addOutput); - } - - @override - Future declareOutputs(DeclaringAggregateTransform transform) async { - transform.declareOutput(new AssetId( - transform.package, p.url.join(transform.key, 'dart_sdk.js'))); - transform.declareOutput(new AssetId( - transform.package, p.url.join(transform.key, 'require.js'))); - } -} diff --git a/lib/src/barback/dartdevc/linked_summary_transformer.dart b/lib/src/barback/dartdevc/linked_summary_transformer.dart deleted file mode 100644 index 1eba96c89f7d3f2c847ef38337ce3533be72029b..0000000000000000000000000000000000000000 --- a/lib/src/barback/dartdevc/linked_summary_transformer.dart +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:async'; - -import 'package:barback/barback.dart'; - -import 'module.dart'; -import 'module_reader.dart'; -import 'scratch_space.dart'; -import 'summaries.dart'; - -/// Creates linked analyzer summaries given [moduleConfigName] files which -/// describe a set of [Module]s. -/// -/// Unlinked summaries should have already been created for all modules and will -/// be read in to create the linked summaries. -class LinkedSummaryTransformer extends Transformer { - @override - String get allowedExtensions => moduleConfigName; - - LinkedSummaryTransformer(); - - @override - Future apply(Transform transform) async { - var reader = new ModuleReader(transform.readInputAsString); - var configId = transform.primaryInput.id; - var modules = await reader.readModules(configId); - ScratchSpace scratchSpace; - try { - var allAssetIds = new Set<AssetId>(); - var summariesForModule = <ModuleId, Set<AssetId>>{}; - for (var module in modules) { - var transitiveModuleDeps = await reader.readTransitiveDeps(module); - var unlinkedSummaryIds = transitiveModuleDeps - .map((depId) => depId.unlinkedSummaryId) - .toSet(); - summariesForModule[module.id] = unlinkedSummaryIds; - allAssetIds..addAll(module.assetIds)..addAll(unlinkedSummaryIds); - } - // Create a single temp environment for all the modules in this package. - scratchSpace = - await ScratchSpace.create(allAssetIds, transform.readInput); - await Future.wait(modules.map((m) async { - var outputs = createLinkedSummaryForModule( - m, summariesForModule[m.id], scratchSpace, transform.logger.error); - - await Future.wait(outputs.values.map( - (futureAsset) async => transform.addOutput(await futureAsset))); - })); - } finally { - scratchSpace?.delete(); - } - } -} diff --git a/lib/src/barback/dartdevc/module_config_transformer.dart b/lib/src/barback/dartdevc/module_config_transformer.dart deleted file mode 100644 index b63cc95f2730966b57d310e01d8a794b178c349c..0000000000000000000000000000000000000000 --- a/lib/src/barback/dartdevc/module_config_transformer.dart +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:async'; -import 'dart:convert'; - -import 'package:barback/barback.dart'; -import 'package:path/path.dart' as p; - -import '../../io.dart'; -import 'module.dart'; -import 'module_computer.dart'; -import 'module_reader.dart'; - -/// Computes the ideal set of [Module]s for top level directories in a package, -/// and outputs a single `.moduleConfig` file in each one. -class ModuleConfigTransformer extends AggregateTransformer { - ModuleConfigTransformer(); - - @override - String classifyPrimary(AssetId id) { - if (p.extension(id.path) != '.dart') return null; - return topLevelDir(id.path); - } - - @override - Future apply(AggregateTransform transform) async { - var moduleMode = - transform.key == 'lib' ? ModuleMode.public : ModuleMode.private; - var allAssets = await transform.primaryInputs.toList(); - var modules = await computeModules(moduleMode, allAssets); - var encoded = JSON.encode(modules); - transform.addOutput(new Asset.fromString( - new AssetId( - transform.package, p.url.join(transform.key, moduleConfigName)), - encoded)); - } -} diff --git a/lib/src/barback/dartdevc/module_reader.dart b/lib/src/barback/dartdevc/module_reader.dart index 8e30ad55a630abf67d4cae8c96c2f456c7c59acb..a07f78458319edcf85aadd3403d421d24766f48f 100644 --- a/lib/src/barback/dartdevc/module_reader.dart +++ b/lib/src/barback/dartdevc/module_reader.dart @@ -9,6 +9,7 @@ import 'package:barback/barback.dart'; import 'package:path/path.dart' as p; import 'module.dart'; +import 'summaries.dart'; typedef FutureOr<String> ReadAsString(AssetId id); @@ -45,7 +46,29 @@ class ModuleReader { var moduleConfigId = new AssetId(id.package, p.join(parts.first, moduleConfigName)); await readModules(moduleConfigId); - return _modulesByAssetId[id]; + if (id.extension == '.dart') { + return _modulesByAssetId[id]; + } else { + var baseName = p.url.basename(id.path); + String moduleName; + if (baseName.endsWith('.js')) { + moduleName = p.withoutExtension(baseName); + } else if (baseName.endsWith('.js.map')) { + moduleName = baseName.substring(0, baseName.length - '.js.map'.length); + } else if (baseName.endsWith(unlinkedSummaryExtension)) { + moduleName = baseName.substring( + 0, baseName.length - unlinkedSummaryExtension.length); + } else if (baseName.endsWith(linkedSummaryExtension)) { + moduleName = baseName.substring( + 0, baseName.length - linkedSummaryExtension.length); + } + if (moduleName == null) { + throw new ArgumentError( + 'Can only get modules for `.js` or `.dart` files, but got `$id`.'); + } + var moduleId = new ModuleId(id.package, moduleName, parts.first); + return _modulesByModuleId[moduleId]; + } } /// Computes the transitive deps of [id] by reading all the modules for all diff --git a/lib/src/barback/dartdevc/scratch_space.dart b/lib/src/barback/dartdevc/scratch_space.dart index 2c03986603319c86a167a1aa9bd407304efc5131..446787e0f47d9d8313cf64b912ff3ce80ffaaa80 100644 --- a/lib/src/barback/dartdevc/scratch_space.dart +++ b/lib/src/barback/dartdevc/scratch_space.dart @@ -10,32 +10,70 @@ import 'package:path/path.dart' as p; import '../../io.dart'; +typedef Future<Asset> AssetReader(AssetId id); + /// An on-disk temporary environment for running executables that don't have /// a standard Dart library API. class ScratchSpace { final Directory tempDir; final Directory packagesDir; + final AssetReader getAsset; + + // Assets which have a file created but it is still being written to. + final _pendingWrites = <AssetId, Future>{}; - ScratchSpace._(Directory tempDir) + ScratchSpace._(Directory tempDir, this.getAsset) : packagesDir = new Directory(p.join(tempDir.path, 'packages')), this.tempDir = tempDir; - /// Creates a new [ScratchSpace] containing [assetIds]. + factory ScratchSpace(Future<Asset> getAsset(AssetId id)) { + var tempDir = new Directory(createSystemTempDir()); + return new ScratchSpace._(tempDir, getAsset); + } + + /// Copies [assetIds] to [tempDir] if they don't exist. /// /// Any [Asset] that is under a `lib` dir will be output under a `packages` /// directory corresponding to its package, and any other assets are output /// directly under the temp dir using their unmodified path. - static Future<ScratchSpace> create( - Iterable<AssetId> assetIds, Stream<List<int>> readAsset(AssetId)) async { - var tempDir = new Directory(createSystemTempDir()); + Future ensureAssets(Iterable<AssetId> assetIds) async { var futures = <Future>[]; for (var id in assetIds) { - var filePath = p.join(tempDir.path, _relativePathFor(id)); - ensureDir(p.dirname(filePath)); - futures.add(createFileFromStream(readAsset(id), filePath)); + var file = fileFor(id); + if (file.existsSync()) { + var pending = _pendingWrites[id]; + if (pending != null) futures.add(pending); + } else { + file.createSync(recursive: true); + var done = () async { + var asset = await getAsset(id); + await createFileFromStream(asset.read(), file.path); + _pendingWrites.remove(id); + }(); + _pendingWrites[id] = done; + futures.add(done); + } + } + return Future.wait(futures); + } + + /// Deletes all files for [package] from the temp dir (synchronously). + /// + /// This always deletes the [package] dir under [packagesDir]. + /// + /// If [isRootPackage] then this also deletes all top level entities under + /// [tempDir] other than the [packagesDir]. + void deletePackageFiles(String package, {bool isRootPackage}) { + isRootPackage ??= false; + var packageDir = new Directory(p.join(packagesDir.path, package)); + if (packageDir.existsSync()) packageDir.deleteSync(recursive: true); + if (isRootPackage) { + var entities = tempDir.listSync(recursive: false); + for (var entity in entities) { + if (entity.path == packagesDir.path) continue; + entity.deleteSync(recursive: true); + } } - await Future.wait(futures); - return new ScratchSpace._(tempDir); } /// Deletes the temp directory for this environment. diff --git a/lib/src/barback/dartdevc/summaries.dart b/lib/src/barback/dartdevc/summaries.dart index fc60170a2a84439294635b9dbca27abd6db58a77..b5d6e215c22e2715e0371d53716a8996674b2836 100644 --- a/lib/src/barback/dartdevc/summaries.dart +++ b/lib/src/barback/dartdevc/summaries.dart @@ -8,6 +8,7 @@ import 'package:barback/barback.dart'; import 'package:bazel_worker/bazel_worker.dart'; import 'module.dart'; +import 'module_reader.dart'; import 'scratch_space.dart'; import 'workers.dart'; @@ -22,104 +23,77 @@ final String unlinkedSummaryExtension = '.unlinked.sum'; /// /// Synchronously returns a `Map<AssetId, Future<Asset>>` so that you can know /// immediately what assets will be output. -Map<AssetId, Future<Asset>> createLinkedSummaryForModule( - Module module, - Set<AssetId> unlinkedSummaryIds, - ScratchSpace scratchSpace, - logError(String message)) { - var outputCompleters = <AssetId, Completer<Asset>>{ - module.id.linkedSummaryId: new Completer<Asset>(), - }; - - () async { - var summaryOutputFile = scratchSpace.fileFor(module.id.linkedSummaryId); - var request = new WorkRequest(); - // TODO(jakemac53): Diet parsing results in erroneous errors in later steps, - // but ideally we would do that (pass '--build-summary-only-diet'). - request.arguments.addAll([ - '--build-summary-only', - '--build-summary-output=${summaryOutputFile.path}', - '--strong', - ]); - // Add all the unlinked summaries as build summary inputs. - request.arguments.addAll(unlinkedSummaryIds.map((id) => - '--build-summary-unlinked-input=${scratchSpace.fileFor(id).path}')); - // Add all the files to include in the linked summary bundle. - request.arguments.addAll(module.assetIds.map((id) { - var uri = canonicalUriFor(id); - if (!uri.startsWith('package:')) { - uri = 'file://$uri'; - } - return '$uri|${scratchSpace.fileFor(id).path}'; - })); - var response = await analyzerDriver.doWork(request); - if (response.exitCode == EXIT_CODE_ERROR) { - var message = - 'Error creating linked summaries for module: ${module.id}.\n' - '${response.output}'; - logError(message); - outputCompleters.values.forEach((completer) { - completer.completeError(message); - }); - } else { - outputCompleters[module.id.linkedSummaryId].complete(new Asset.fromBytes( - module.id.linkedSummaryId, summaryOutputFile.readAsBytesSync())); - } - }(); - - var outputFutures = <AssetId, Future<Asset>>{}; - outputCompleters.forEach((k, v) => outputFutures[k] = v.future); - return outputFutures; +Future<Asset> createLinkedSummary(AssetId id, ModuleReader moduleReader, + ScratchSpace scratchSpace, logError(String message)) async { + assert(id.path.endsWith(linkedSummaryExtension)); + var module = await moduleReader.moduleFor(id); + var transitiveModuleDeps = await moduleReader.readTransitiveDeps(module); + var unlinkedSummaryIds = + transitiveModuleDeps.map((depId) => depId.unlinkedSummaryId).toSet(); + var allAssetIds = new Set<AssetId>() + ..addAll(module.assetIds) + ..addAll(unlinkedSummaryIds); + await scratchSpace.ensureAssets(allAssetIds); + var summaryOutputFile = scratchSpace.fileFor(module.id.linkedSummaryId); + var request = new WorkRequest(); + // TODO(jakemac53): Diet parsing results in erroneous errors in later steps, + // but ideally we would do that (pass '--build-summary-only-diet'). + request.arguments.addAll([ + '--build-summary-only', + '--build-summary-output=${summaryOutputFile.path}', + '--strong', + ]); + // Add all the unlinked summaries as build summary inputs. + request.arguments.addAll(unlinkedSummaryIds.map((id) => + '--build-summary-unlinked-input=${scratchSpace.fileFor(id).path}')); + // Add all the files to include in the linked summary bundle. + request.arguments.addAll(_analyzerSourceArgsForModule(module, scratchSpace)); + var response = await analyzerDriver.doWork(request); + if (response.exitCode == EXIT_CODE_ERROR) { + logError('Error creating linked summaries for module: ${module.id}.\n' + '${response.output}\n${request.arguments}'); + return null; + } + return new Asset.fromBytes( + module.id.linkedSummaryId, summaryOutputFile.readAsBytesSync()); } -/// Creates an unlinked summary for [module]. -/// -/// [scratchSpace] must have the Dart sources for this module available. -/// -/// Synchronously returns a `Map<AssetId, Future<Asset>>` so that you can know -/// immediately what assets will be output. -Map<AssetId, Future<Asset>> createUnlinkedSummaryForModule( - Module module, ScratchSpace scratchSpace, logError(String message)) { - var outputCompleters = <AssetId, Completer<Asset>>{ - module.id.unlinkedSummaryId: new Completer<Asset>(), - }; +/// Creates an unlinked summary at [id]. +Future<Asset> createUnlinkedSummary(AssetId id, ModuleReader moduleReader, + ScratchSpace scratchSpace, logError(String message)) async { + assert(id.path.endsWith(unlinkedSummaryExtension)); + var module = await moduleReader.moduleFor(id); + await scratchSpace.ensureAssets(module.assetIds); + var summaryOutputFile = scratchSpace.fileFor(module.id.unlinkedSummaryId); + var request = new WorkRequest(); + // TODO(jakemac53): Diet parsing results in erroneous errors later on today, + // but ideally we would do that (pass '--build-summary-only-diet'). + request.arguments.addAll([ + '--build-summary-only', + '--build-summary-only-unlinked', + '--build-summary-output=${summaryOutputFile.path}', + '--strong', + ]); + // Add all the files to include in the unlinked summary bundle. + request.arguments.addAll(_analyzerSourceArgsForModule(module, scratchSpace)); + var response = await analyzerDriver.doWork(request); + if (response.exitCode == EXIT_CODE_ERROR) { + logError('Error creating unlinked summaries for module: ${module.id}.\n' + '${response.output}'); + return null; + } + return new Asset.fromBytes( + module.id.unlinkedSummaryId, summaryOutputFile.readAsBytesSync()); +} - () async { - var summaryOutputFile = scratchSpace.fileFor(module.id.unlinkedSummaryId); - var request = new WorkRequest(); - // TODO(jakemac53): Diet parsing results in erroneous errors later on today, - // but ideally we would do that (pass '--build-summary-only-diet'). - request.arguments.addAll([ - '--build-summary-only', - '--build-summary-only-unlinked', - '--build-summary-output=${summaryOutputFile.path}', - '--strong', - ]); - // Add all the files to include in the unlinked summary bundle. - request.arguments.addAll(module.assetIds.map((id) { - var uri = canonicalUriFor(id); - if (!uri.startsWith('package:')) { - uri = 'file://$uri'; - } - return '$uri|${scratchSpace.fileFor(id).path}'; - })); - var response = await analyzerDriver.doWork(request); - if (response.exitCode == EXIT_CODE_ERROR) { - var message = - 'Error creating unlinked summaries for module: ${module.id}.\n' - '${response.output}'; - logError(message); - outputCompleters.values.forEach((completer) { - completer.completeError(message); - }); - } else { - outputCompleters[module.id.unlinkedSummaryId].complete( - new Asset.fromBytes(module.id.unlinkedSummaryId, - summaryOutputFile.readAsBytesSync())); +Iterable<String> _analyzerSourceArgsForModule( + Module module, ScratchSpace scratchSpace) { + return module.assetIds.map((id) { + var uri = canonicalUriFor(id); + var file = scratchSpace.fileFor(id); + if (!uri.startsWith('package:')) { + uri = file.uri.toString(); } - }(); - - var outputFutures = <AssetId, Future<Asset>>{}; - outputCompleters.forEach((k, v) => outputFutures[k] = v.future); - return outputFutures; + return '$uri|${file.path}'; + }); } diff --git a/lib/src/barback/dartdevc/unlinked_summary_transformer.dart b/lib/src/barback/dartdevc/unlinked_summary_transformer.dart deleted file mode 100644 index 8d1ad5458724f3a610204e0eafe16a194210e1e4..0000000000000000000000000000000000000000 --- a/lib/src/barback/dartdevc/unlinked_summary_transformer.dart +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:async'; - -import 'package:barback/barback.dart'; - -import 'module.dart'; -import 'module_reader.dart'; -import 'scratch_space.dart'; -import 'summaries.dart'; - -/// Creates unlinked analyzer summaries given [moduleConfigName] files which -/// describe a set of [Module]s. -class UnlinkedSummaryTransformer extends Transformer { - @override - String get allowedExtensions => moduleConfigName; - - UnlinkedSummaryTransformer(); - - @override - Future apply(Transform transform) async { - var reader = new ModuleReader(transform.readInputAsString); - var configId = transform.primaryInput.id; - var modules = await reader.readModules(configId); - ScratchSpace scratchSpace; - try { - var allAssetIds = modules.fold(new Set<AssetId>(), (allAssets, module) { - allAssets.addAll(module.assetIds); - return allAssets; - }); - // Create a single temp environment for all the modules in this package. - scratchSpace = - await ScratchSpace.create(allAssetIds, transform.readInput); - await Future.wait(modules.map((m) async { - var outputs = createUnlinkedSummaryForModule( - m, scratchSpace, transform.logger.error); - await Future.wait(outputs.values.map( - (futureAsset) async => transform.addOutput(await futureAsset))); - })); - } finally { - scratchSpace?.delete(); - } - } -} diff --git a/lib/src/barback/load_all_transformers.dart b/lib/src/barback/load_all_transformers.dart index d4cbda291eba8511afc2ad772f9171d1a133ffb4..897823621767b5a9d077056e5bfdf5cc0ef17606 100644 --- a/lib/src/barback/load_all_transformers.dart +++ b/lib/src/barback/load_all_transformers.dart @@ -94,6 +94,7 @@ Future loadAllTransformers( var phases = await loader.transformersForPhases(package.pubspec.transformers); environment.barback.updateTransformers(packageName, phases); + environment.dartDevcEnvironment?.invalidatePackage(packageName); })); } @@ -112,8 +113,10 @@ Future loadAllTransformers( // immediate emission. Issue 17305 means that the caller will be unable // to receive this result unless we delay the update to after this // function returns. - newFuture( - () => environment.barback.updateTransformers(package.name, phases)); + newFuture(() { + environment.barback.updateTransformers(package.name, phases); + environment.dartDevcEnvironment?.invalidatePackage(package.name); + }); })); } diff --git a/lib/src/barback/source_directory.dart b/lib/src/barback/source_directory.dart index 8d202d2cd0bd5dd2bccc90cbbe17c396c04f8f02..dc39723cce46e86b97e91fcbc80c923f707acf13 100644 --- a/lib/src/barback/source_directory.dart +++ b/lib/src/barback/source_directory.dart @@ -8,6 +8,7 @@ import 'package:watcher/watcher.dart'; import 'asset_environment.dart'; import 'barback_server.dart'; +import 'dartdevc/dartdevc_environment.dart'; /// A directory in the entrypoint package whose contents have been made /// available to barback and that are bound to a server. @@ -39,9 +40,10 @@ class SourceDirectory { SourceDirectory(this._environment, this.directory, this.hostname, this.port); /// Binds a server running on [hostname]:[port] to this directory. - Future<BarbackServer> serve() { + Future<BarbackServer> serve({DartDevcEnvironment dartDevcEnvironment}) { return BarbackServer - .bind(_environment, hostname, port, rootDirectory: directory) + .bind(_environment, hostname, port, + rootDirectory: directory, dartDevcEnvironment: dartDevcEnvironment) .then((server) { _serverCompleter.complete(server); return server; diff --git a/lib/src/command/build.dart b/lib/src/command/build.dart index 5805b152325ca06cba45f9150aab3d34c3001979..6e3e4a6c915c2818f7e47174a68886c1ad1a23cd 100644 --- a/lib/src/command/build.dart +++ b/lib/src/command/build.dart @@ -70,9 +70,9 @@ class BuildCommand extends BarbackCommand { environmentConstants: environmentConstants, compiler: compiler); var hasError = false; - // Show in-progress errors, but not results. Those get handled - // implicitly by getAllAssets(). - environment.barback.errors.listen((error) { + + // Unified error handler for barback and dartdevc. + logError(error) { log.error(log.red("Build error:\n$error")); hasError = true; @@ -81,7 +81,11 @@ class BuildCommand extends BarbackCommand { // more properties later. errorsJson.add({"error": error.toString()}); } - }); + } + + // Show in-progress errors, but not results. Those get handled + // implicitly by getAllAssets(). + environment.barback.errors.listen(logError); // If we're using JSON output, the regular server logging is disabled. // Instead, we collect it here to include in the final JSON result. @@ -102,16 +106,15 @@ class BuildCommand extends BarbackCommand { return environment.barback.getAllAssets(); }); - // Find all of the JS entrypoints we built. - var dart2JSEntrypoints = assets - .where((asset) => asset.id.path.endsWith(".dart.js")) - .map((asset) => asset.id); - - await Future.wait(assets.map(_writeAsset)); - await _copyBrowserJsFiles(dart2JSEntrypoints, assets); - - log.message('Built $builtFiles ${pluralize('file', builtFiles)} ' - 'to "$outputDirectory".'); + // Add all the js assets from dartdevc to the build, we don't need the + // rest once a build is complete. + if (environment.dartDevcEnvironment != null) { + await log.progress("Building dartdevc modules", () async { + assets.addAll(await environment.dartDevcEnvironment + .doFullBuild(assets, logError: logError)); + }); + await environment.dartDevcEnvironment.cleanUp(); + } if (hasError) { log.error(log.red("Build failed.")); @@ -119,6 +122,16 @@ class BuildCommand extends BarbackCommand { {"buildResult": "failure", "errors": errorsJson, "log": logJson}); return flushThenExit(exit_codes.DATA); } else { + // Find all of the JS entrypoints we built. + var dart2JSEntrypoints = assets + .where((asset) => asset.id.path.endsWith(".dart.js")) + .map((asset) => asset.id); + + await Future.wait(assets.map(_writeAsset)); + await _copyBrowserJsFiles(dart2JSEntrypoints, assets); + + log.message('Built $builtFiles ${pluralize('file', builtFiles)} ' + 'to "$outputDirectory".'); log.json.message({ "buildResult": "success", "outputDirectory": outputDirectory, diff --git a/lib/src/package_graph.dart b/lib/src/package_graph.dart index 12f30bceada66a1dd5912105e36e416b919765f6..570af5d7d38406b51cc7a0c7cbe50b6dbfb73911 100644 --- a/lib/src/package_graph.dart +++ b/lib/src/package_graph.dart @@ -159,7 +159,6 @@ class PackageGraph { /// Note that a static package isn't the same as an immutable package (see /// [isPackageMutable]). bool isPackageStatic(String package, Compiler compiler) { - if (compiler == Compiler.dartDevc) return false; var id = lockFile.packages[package]; if (id == null) return false; if (entrypoint.cache.source(id.source) is! CachedSource) return false; diff --git a/test/barback/dartdevc/dartdevc_resource_transformer_test.dart b/test/barback/dartdevc/dartdevc_resource_transformer_test.dart index fe5a17af5c2d7d204bd1cf7dda60268784a6b0a8..20d21bd7fe42a02298c5b08f46844595ea1022d1 100644 --- a/test/barback/dartdevc/dartdevc_resource_transformer_test.dart +++ b/test/barback/dartdevc/dartdevc_resource_transformer_test.dart @@ -7,7 +7,7 @@ import '../../test_pub.dart'; import '../../serve/utils.dart'; main() { - integration("dartdevc resources are copied next to entrypoints only", () { + integration("dartdevc resources are copied next to entrypoints", () { d.dir(appPath, [ d.appPubspec(), d.dir("lib", [ @@ -27,8 +27,6 @@ main() { requestShouldSucceed('require.js', null); requestShouldSucceed('subdir/dart_sdk.js', null); requestShouldSucceed('subdir/require.js', null); - requestShould404('packages/$appPath/dart_sdk.js'); - requestShould404('packages/$appPath/require.js'); endPubServe(); }); } diff --git a/test/barback/dartdevc/linked_summary_transformer_test.dart b/test/barback/dartdevc/linked_summary_transformer_test.dart index 000b023bcbe4e93d7d292380e7ad3b6d4d37a706..2f3a45208483366aa00ba678f32fe940bfa54a72 100644 --- a/test/barback/dartdevc/linked_summary_transformer_test.dart +++ b/test/barback/dartdevc/linked_summary_transformer_test.dart @@ -53,34 +53,34 @@ void main() {} pubServe(args: ['--compiler', 'dartdevc']); linkedSummaryRequestShouldSucceed('web__main$linkedSummaryExtension', [ - 'file://web/main.dart', - 'package:myapp/hello.dart', - 'package:foo/foo.dart' + endsWith('web/main.dart'), + equals('package:myapp/hello.dart'), + equals('package:foo/foo.dart') ], [ - 'file://web/main.dart' + endsWith('web/main.dart') ], [ endsWith('packages/myapp/lib__hello.unlinked.sum'), endsWith('packages/foo/lib__foo.unlinked.sum'), ]); linkedSummaryRequestShouldSucceed( 'packages/myapp/lib__hello$linkedSummaryExtension', [ - 'package:myapp/hello.dart', - 'package:foo/foo.dart' + equals('package:myapp/hello.dart'), + equals('package:foo/foo.dart') ], [ - 'package:myapp/hello.dart' + equals('package:myapp/hello.dart') ], [ endsWith('packages/foo/lib__foo.unlinked.sum'), ]); linkedSummaryRequestShouldSucceed( 'packages/foo/lib__foo$linkedSummaryExtension', - ['package:foo/foo.dart'], - ['package:foo/foo.dart']); + [equals('package:foo/foo.dart')], + [equals('package:foo/foo.dart')]); endPubServe(); }); } void linkedSummaryRequestShouldSucceed(String uri, - List<String> expectedLinkedUris, List<String> expectedUnlinkedUris, + List<Matcher> expectedLinkedUris, List<Matcher> expectedUnlinkedUris, [List<Matcher> expectedSummaryDeps = const []]) { scheduleRequest(uri).then((response) { expect(response.statusCode, 200); diff --git a/test/barback/dartdevc/scratch_space_test.dart b/test/barback/dartdevc/scratch_space_test.dart index cfa65528ce19a38802a22c79aede9759be5f1a48..f734ffabeff538fc1ce0f83c0ee3388a75120424 100644 --- a/test/barback/dartdevc/scratch_space_test.dart +++ b/test/barback/dartdevc/scratch_space_test.dart @@ -2,7 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'dart:async'; import 'dart:io'; import 'package:barback/barback.dart'; @@ -13,7 +12,8 @@ import 'package:pub/src/barback/dartdevc/scratch_space.dart'; import 'package:pub/src/io.dart'; void main() { - test('Can create and delete a scratch space', () async { + group('ScratchSpace', () { + ScratchSpace scratchSpace; Map<AssetId, List<int>> allAssets = [ 'dep|lib/dep.dart', 'myapp|lib/myapp.dart', @@ -23,35 +23,57 @@ void main() { return assets; }); - var scratchSpace = await ScratchSpace.create( - allAssets.keys, (id) => new Stream.fromIterable([allAssets[id]])); + setUp(() async { + scratchSpace = new ScratchSpace( + (id) async => new Asset.fromBytes(id, allAssets[id])); + await scratchSpace.ensureAssets(allAssets.keys); + }); + + tearDown(() async { + await scratchSpace.delete(); + for (var id in allAssets.keys) { + var file = scratchSpace.fileFor(id); + expect(file.existsSync(), isFalse); + } + expect(scratchSpace.tempDir.existsSync(), isFalse); + }); - expect(p.isWithin(Directory.systemTemp.path, scratchSpace.tempDir.path), - isTrue); + test('Can create and delete a scratch space', () async { + expect(p.isWithin(Directory.systemTemp.path, scratchSpace.tempDir.path), + isTrue); - for (var id in allAssets.keys) { - var file = scratchSpace.fileFor(id); - expect(file.existsSync(), isTrue); - expect(file.readAsStringSync(), equals('$id')); + for (var id in allAssets.keys) { + var file = scratchSpace.fileFor(id); + expect(file.existsSync(), isTrue); + expect(file.readAsStringSync(), equals('$id')); - var relativeFilePath = - p.relative(file.path, from: scratchSpace.tempDir.path); - if (topLevelDir(id.path) == 'lib') { - var packagesPath = - p.join('packages', id.package, p.relative(id.path, from: 'lib')); - expect(relativeFilePath, equals(packagesPath)); - } else { - expect(relativeFilePath, equals(id.path)); + var relativeFilePath = + p.relative(file.path, from: scratchSpace.tempDir.path); + if (topLevelDir(id.path) == 'lib') { + var packagesPath = + p.join('packages', id.package, p.relative(id.path, from: 'lib')); + expect(relativeFilePath, equals(packagesPath)); + } else { + expect(relativeFilePath, equals(id.path)); + } } - } + }); - await scratchSpace.delete(); + test('can delete an individual package from a scratch space', () async { + scratchSpace.deletePackageFiles('dep', isRootPackage: false); + var depId = new AssetId.parse('dep|lib/dep.dart'); + expect(scratchSpace.fileFor(depId).existsSync(), isFalse); + allAssets.keys.where((id) => id.package == 'myapp').forEach((id) { + expect(scratchSpace.fileFor(id).existsSync(), isTrue); + }); - for (var id in allAssets.keys) { - var file = scratchSpace.fileFor(id); - expect(file.existsSync(), isFalse); - } - expect(scratchSpace.tempDir.existsSync(), isFalse); + await scratchSpace.ensureAssets(allAssets.keys); + scratchSpace.deletePackageFiles('myapp', isRootPackage: true); + allAssets.keys.where((id) => id.package == 'myapp').forEach((id) { + expect(scratchSpace.fileFor(id).existsSync(), isFalse); + }); + expect(scratchSpace.fileFor(depId).existsSync(), isTrue); + }); }); test('canonicalUriFor', () { diff --git a/test/barback/dartdevc/unlinked_summary_transformer_test.dart b/test/barback/dartdevc/unlinked_summary_transformer_test.dart index a8259ede07c1137cff33a1e075b9da5eda6b0198..ecb9866af0c1b9a84fe689cc44959377ed3525ce 100644 --- a/test/barback/dartdevc/unlinked_summary_transformer_test.dart +++ b/test/barback/dartdevc/unlinked_summary_transformer_test.dart @@ -54,19 +54,19 @@ void main() {} pubServe(args: ['--compiler', 'dartdevc']); unlinkedSummaryRequestShouldSucceed( - 'web__main$unlinkedSummaryExtension', ['file://web/main.dart']); + 'web__main$unlinkedSummaryExtension', [endsWith('web/main.dart')]); unlinkedSummaryRequestShouldSucceed( 'packages/myapp/lib__hello$unlinkedSummaryExtension', - ['package:myapp/hello.dart']); + [equals('package:myapp/hello.dart')]); unlinkedSummaryRequestShouldSucceed( 'packages/foo/lib__foo$unlinkedSummaryExtension', - ['package:foo/foo.dart']); + [equals('package:foo/foo.dart')]); endPubServe(); }); } void unlinkedSummaryRequestShouldSucceed( - String uri, List<String> expectedUnlinkedUris) { + String uri, List<Matcher> expectedUnlinkedUris) { var expected = unorderedMatches(expectedUnlinkedUris); scheduleRequest(uri).then((response) { expect(response.statusCode, 200); diff --git a/test/compiler/ignores_non_entrypoint_dart_files_test.dart b/test/compiler/ignores_non_entrypoint_dart_files_test.dart index 3e678e43610c7b3e8690d05d85cd9cdb238798bb..555a338162fba78d278dae71faefd17d123b34fb 100644 --- a/test/compiler/ignores_non_entrypoint_dart_files_test.dart +++ b/test/compiler/ignores_non_entrypoint_dart_files_test.dart @@ -29,17 +29,8 @@ main() { args: ["build", "--compiler=${compiler.name}"], output: new RegExp(r'Built [\d]+ files? to "build".')); - var expectedWebDir; - switch (compiler) { - case Compiler.dart2JS: - expectedWebDir = d.nothing('web'); - break; - case Compiler.dartDevc: - expectedWebDir = d.dir('web', [d.file('.moduleConfig', '[]')]); - break; - } d.dir(appPath, [ - d.dir('build', [expectedWebDir]) + d.dir('build', [d.nothing('web')]) ]).validate(); });