|  | // Copyright (c) 2015, 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:async/async.dart'; | 
|  | import 'package:package_resolver/package_resolver.dart'; | 
|  | import 'package:path/path.dart' as p; | 
|  | import 'package:pool/pool.dart'; | 
|  |  | 
|  | import '../util/io.dart'; | 
|  | import 'configuration.dart'; | 
|  | import 'configuration/suite.dart'; | 
|  |  | 
|  | /// A regular expression matching the first status line printed by dart2js. | 
|  | final _dart2jsStatus = | 
|  | new RegExp(r"^Dart file \(.*\) compiled to JavaScript: .*\n?"); | 
|  |  | 
|  | /// A pool of `dart2js` instances. | 
|  | /// | 
|  | /// This limits the number of compiler instances running concurrently. | 
|  | class CompilerPool { | 
|  | /// The test runner configuration. | 
|  | final _config = Configuration.current; | 
|  |  | 
|  | /// The internal pool that controls the number of process running at once. | 
|  | final Pool _pool; | 
|  |  | 
|  | /// The currently-active dart2js processes. | 
|  | final _processes = new Set<Process>(); | 
|  |  | 
|  | /// Whether [close] has been called. | 
|  | bool get _closed => _closeMemo.hasRun; | 
|  |  | 
|  | /// The memoizer for running [close] exactly once. | 
|  | final _closeMemo = new AsyncMemoizer(); | 
|  |  | 
|  | /// Extra arguments to pass to dart2js. | 
|  | final List<String> _extraArgs; | 
|  |  | 
|  | /// Creates a compiler pool that multiple instances of `dart2js` at once. | 
|  | CompilerPool([Iterable<String> extraArgs]) | 
|  | : _pool = new Pool(Configuration.current.concurrency), | 
|  | _extraArgs = extraArgs?.toList() ?? const []; | 
|  |  | 
|  | /// Compiles [code] to [jsPath]. | 
|  | /// | 
|  | /// This wraps the Dart code in the standard browser-testing wrapper. | 
|  | /// | 
|  | /// The returned [Future] will complete once the `dart2js` process completes | 
|  | /// *and* all its output has been printed to the command line. | 
|  | Future compile(String code, String jsPath, SuiteConfiguration suiteConfig) { | 
|  | return _pool.withResource(() { | 
|  | if (_closed) return null; | 
|  |  | 
|  | return withTempDir((dir) async { | 
|  | var wrapperPath = p.join(dir, "runInBrowser.dart"); | 
|  | new File(wrapperPath).writeAsStringSync(code); | 
|  |  | 
|  | var dart2jsPath = _config.dart2jsPath; | 
|  | if (Platform.isWindows) dart2jsPath += '.bat'; | 
|  |  | 
|  | var args = [ | 
|  | "--checked", | 
|  | wrapperPath, | 
|  | "--out=$jsPath", | 
|  | await PackageResolver.current.processArgument | 
|  | ] | 
|  | ..addAll(_extraArgs) | 
|  | ..addAll(suiteConfig.dart2jsArgs); | 
|  |  | 
|  | if (_config.color) args.add("--enable-diagnostic-colors"); | 
|  |  | 
|  | var process = await Process.start(dart2jsPath, args); | 
|  | if (_closed) { | 
|  | process.kill(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | _processes.add(process); | 
|  |  | 
|  | /// Wait until the process is entirely done to print out any output. | 
|  | /// This can produce a little extra time for users to wait with no | 
|  | /// update, but it also avoids some really nasty-looking interleaved | 
|  | /// output. Write both stdout and stderr to the same buffer in case | 
|  | /// they're intended to be printed in order. | 
|  | var buffer = new StringBuffer(); | 
|  |  | 
|  | await Future.wait([ | 
|  | _printOutputStream(process.stdout, buffer), | 
|  | _printOutputStream(process.stderr, buffer), | 
|  | ]); | 
|  |  | 
|  | var exitCode = await process.exitCode; | 
|  | _processes.remove(process); | 
|  | if (_closed) return; | 
|  |  | 
|  | var output = buffer.toString().replaceFirst(_dart2jsStatus, ''); | 
|  | if (output.isNotEmpty) print(output); | 
|  |  | 
|  | if (exitCode != 0) throw "dart2js failed."; | 
|  |  | 
|  | _fixSourceMap(jsPath + '.map'); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | // TODO(nweiz): Remove this when sdk#17544 is fixed. | 
|  | /// Fix up the source map at [mapPath] so that it points to absolute file: | 
|  | /// URIs that are resolvable by the browser. | 
|  | void _fixSourceMap(String mapPath) { | 
|  | var map = JSON.decode(new File(mapPath).readAsStringSync()); | 
|  | var root = map['sourceRoot']; | 
|  |  | 
|  | map['sources'] = map['sources'].map((source) { | 
|  | var url = Uri.parse(root + source); | 
|  | if (url.scheme != '' && url.scheme != 'file') return source; | 
|  | if (url.path.endsWith("/runInBrowser.dart")) return ""; | 
|  | return p.toUri(mapPath).resolveUri(url).toString(); | 
|  | }).toList(); | 
|  |  | 
|  | new File(mapPath).writeAsStringSync(JSON.encode(map)); | 
|  | } | 
|  |  | 
|  | /// Sanitizes the bytes emitted by [stream], converts them to text, and writes | 
|  | /// them to [buffer]. | 
|  | Future _printOutputStream(Stream<List<int>> stream, StringBuffer buffer) { | 
|  | return sanitizeForWindows(stream) | 
|  | .listen((data) => buffer.write(UTF8.decode(data))) | 
|  | .asFuture(); | 
|  | } | 
|  |  | 
|  | /// Closes the compiler pool. | 
|  | /// | 
|  | /// This kills all currently-running compilers and ensures that no more will | 
|  | /// be started. It returns a [Future] that completes once all the compilers | 
|  | /// have been killed and all resources released. | 
|  | Future close() { | 
|  | return _closeMemo.runOnce(() async { | 
|  | await Future.wait(_processes.map((process) async { | 
|  | process.kill(); | 
|  | await process.exitCode; | 
|  | })); | 
|  | }); | 
|  | } | 
|  | } |