| // 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 'dart:isolate'; |
| |
| import 'package:async/async.dart'; |
| import 'package:path/path.dart' as p; |
| import 'package:pool/pool.dart'; |
| |
| import '../util/io.dart'; |
| import 'suite.dart'; |
| import 'configuration.dart'; |
| |
| /// A regular expression matching the first status line printed by dart2js. |
| final _dart2jsStatus = |
| 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 = <Process>{}; |
| |
| /// Whether [close] has been called. |
| bool get _closed => _closeMemo.hasRun; |
| |
| /// The memoizer for running [close] exactly once. |
| final _closeMemo = 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 = 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'); |
| File(wrapperPath).writeAsStringSync(code); |
| |
| var dart2jsPath = _config.dart2jsPath; |
| if (Platform.isWindows) dart2jsPath += '.bat'; |
| |
| var args = [ |
| '--enable-asserts', |
| wrapperPath, |
| '--out=$jsPath', |
| '--packages=${await Isolate.packageConfig}', |
| ..._extraArgs, |
| ...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 = StringBuffer(); |
| |
| await Future.wait([ |
| process.stdout.transform(utf8.decoder).forEach(buffer.write), |
| process.stderr.transform(utf8.decoder).forEach(buffer.write), |
| ]); |
| |
| 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 = jsonDecode(File(mapPath).readAsStringSync()); |
| var root = map['sourceRoot'] as String; |
| |
| 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(); |
| |
| File(mapPath).writeAsStringSync(jsonEncode(map)); |
| } |
| |
| /// 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; |
| })); |
| }); |
| } |
| } |