/
compiler_pool.dart
155 lines (124 loc) · 5.28 KB
/
compiler_pool.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// 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';
import '../load_exception.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();
/// Creates a compiler pool that multiple instances of `dart2js` at once.
CompilerPool() : _pool = new Pool(Configuration.current.concurrency);
/// Compile the Dart code at [dartPath] 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 dartPath, 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('''
import "package:stream_channel/stream_channel.dart";
import "package:test/src/runner/plugin/remote_platform_helpers.dart";
import "package:test/src/runner/browser/post_message_channel.dart";
import "${p.toUri(p.absolute(dartPath))}" as test;
main(_) async {
var channel = serializeSuite(() => test.main, hidePrints: false);
postMessageChannel().pipe(channel);
}
''');
var dart2jsPath = _config.dart2jsPath;
if (Platform.isWindows) dart2jsPath += '.bat';
var args = [
"--checked",
wrapperPath,
"--out=$jsPath",
await PackageResolver.current.processArgument
]..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 new LoadException(dartPath, "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;
}));
});
}
}