|
6 | 6 | library dartdoc.io_utils;
|
7 | 7 |
|
8 | 8 | import 'dart:async';
|
9 |
| -import 'dart:convert'; |
10 | 9 | import 'dart:io';
|
11 | 10 |
|
12 |
| -import 'package:dartdoc/src/tuple.dart'; |
13 | 11 | import 'package:path/path.dart' as pathLib;
|
14 | 12 |
|
15 | 13 | /// Return a resolved path including the home directory in place of tilde
|
@@ -82,7 +80,6 @@ final libraryNameRegexp = new RegExp('[.:]');
|
82 | 80 | final partOfRegexp = new RegExp('part of ');
|
83 | 81 | final newLinePartOfRegexp = new RegExp('\npart of ');
|
84 | 82 |
|
85 |
| -final RegExp quotables = new RegExp(r'[ "\r\n\$]'); |
86 | 83 |
|
87 | 84 | /// Best used with Future<void>.
|
88 | 85 | class MultiFutureTracker<T> {
|
@@ -117,204 +114,4 @@ class MultiFutureTracker<T> {
|
117 | 114 | Future<void> wait() async => await _waitUntil(0);
|
118 | 115 | }
|
119 | 116 |
|
120 |
| -/// Keeps track of coverage data automatically for any processes run by this |
121 |
| -/// [CoverageSubprocessLauncher]. Requires that these be dart processes. |
122 |
| -class CoverageSubprocessLauncher extends SubprocessLauncher { |
123 |
| - CoverageSubprocessLauncher(String context, [Map<String, String> environment]) |
124 |
| - : super(context, environment); |
125 | 117 |
|
126 |
| - static int nextObservatoryPort = 9292; |
127 |
| - |
128 |
| - /// Set this to true to enable coverage runs. |
129 |
| - static bool coverageEnabled = false; |
130 |
| - |
131 |
| - /// A list of all coverage results picked up by all launchers. |
132 |
| - static List<Tuple2<String, Future<Iterable<Map>>>> coverageResults = []; |
133 |
| - |
134 |
| - static Directory _tempDir; |
135 |
| - static Directory get tempDir => |
136 |
| - _tempDir ??= Directory.systemTemp.createTempSync('dartdoc_coverage_data'); |
137 |
| - |
138 |
| - int _observatoryPort; |
139 |
| - // TODO(jcollins-g): use ephemeral ports |
140 |
| - int get observatoryPort => _observatoryPort ??= nextObservatoryPort++; |
141 |
| - |
142 |
| - String _outCoverageFilename; |
143 |
| - String get outCoverageFilename => _outCoverageFilename ??= |
144 |
| - pathLib.join(tempDir.path, 'dart-cov-0-${observatoryPort}.json'); |
145 |
| - |
146 |
| - /// Call once all coverage runs have been generated by calling runStreamed |
147 |
| - /// on all [CoverageSubprocessLaunchers]. |
148 |
| - static Future<void> generateCoverageToFile(File outputFile) async { |
149 |
| - if (!coverageEnabled) return Future.value(null); |
150 |
| - var currentCoverageResults = coverageResults; |
151 |
| - coverageResults = []; |
152 |
| - var launcher = SubprocessLauncher('format_coverage'); |
153 |
| - |
154 |
| - /// Wait for all coverage runs to finish. |
155 |
| - await Future.wait(currentCoverageResults.map((t) => t.item2)); |
156 |
| - |
157 |
| - return launcher.runStreamed(Platform.executable, [ |
158 |
| - 'tool/format_coverage.dart', // TODO(jcollins-g): use pub after dart-lang/coverage#240 is landed |
159 |
| - '--lcov', |
160 |
| - '-v', |
161 |
| - '-b', '.', |
162 |
| - '--packages=.packages', |
163 |
| - '--sdk-root=${pathLib.canonicalize(pathLib.join(pathLib.dirname(Platform.executable), '..'))}', |
164 |
| - '--out=${pathLib.canonicalize(outputFile.path)}', |
165 |
| - '--report-on=bin,lib', |
166 |
| - '-i', tempDir.path, |
167 |
| - ]); |
168 |
| - } |
169 |
| - |
170 |
| - @override |
171 |
| - Future<Iterable<Map>> runStreamed(String executable, List<String> arguments, |
172 |
| - {String workingDirectory}) { |
173 |
| - assert(executable == Platform.executable, |
174 |
| - 'Must use dart executable for tracking coverage'); |
175 |
| - |
176 |
| - if (coverageEnabled) { |
177 |
| - arguments = [ |
178 |
| - '--enable-vm-service=${observatoryPort}', |
179 |
| - '--pause-isolates-on-exit' |
180 |
| - ]..addAll(arguments); |
181 |
| - } |
182 |
| - |
183 |
| - Future<Iterable<Map>> results = super |
184 |
| - .runStreamed(executable, arguments, workingDirectory: workingDirectory); |
185 |
| - |
186 |
| - if (coverageEnabled) { |
187 |
| - coverageResults.add(new Tuple2( |
188 |
| - outCoverageFilename, |
189 |
| - super.runStreamed('pub', [ |
190 |
| - 'run', |
191 |
| - 'coverage:collect_coverage', |
192 |
| - '--wait-paused', |
193 |
| - '--resume-isolates', |
194 |
| - '--port=${observatoryPort}', |
195 |
| - '--out=${outCoverageFilename}', |
196 |
| - ]))); |
197 |
| - } |
198 |
| - return results; |
199 |
| - } |
200 |
| -} |
201 |
| - |
202 |
| -class SubprocessLauncher { |
203 |
| - final String context; |
204 |
| - final Map<String, String> environment; |
205 |
| - |
206 |
| - String get prefix => context.isNotEmpty ? '$context: ' : ''; |
207 |
| - |
208 |
| - // from flutter:dev/tools/dartdoc.dart, modified |
209 |
| - static Future<void> _printStream(Stream<List<int>> stream, Stdout output, |
210 |
| - {String prefix: '', Iterable<String> Function(String line) filter}) { |
211 |
| - assert(prefix != null); |
212 |
| - if (filter == null) filter = (line) => [line]; |
213 |
| - return stream |
214 |
| - .transform(utf8.decoder) |
215 |
| - .transform(const LineSplitter()) |
216 |
| - .expand(filter) |
217 |
| - .listen((String line) { |
218 |
| - if (line != null) { |
219 |
| - output.write('$prefix$line'.trim()); |
220 |
| - output.write('\n'); |
221 |
| - } |
222 |
| - }).asFuture(); |
223 |
| - } |
224 |
| - |
225 |
| - SubprocessLauncher(this.context, [Map<String, String> environment]) |
226 |
| - : this.environment = environment ?? <String, String>{}; |
227 |
| - |
228 |
| - /// A wrapper around start/await process.exitCode that will display the |
229 |
| - /// output of the executable continuously and fail on non-zero exit codes. |
230 |
| - /// It will also parse any valid JSON objects (one per line) it encounters |
231 |
| - /// on stdout/stderr, and return them. Returns null if no JSON objects |
232 |
| - /// were encountered, or if DRY_RUN is set to 1 in the execution environment. |
233 |
| - /// |
234 |
| - /// Makes running programs in grinder similar to set -ex for bash, even on |
235 |
| - /// Windows (though some of the bashisms will no longer make sense). |
236 |
| - /// TODO(jcollins-g): move this to grinder? |
237 |
| - Future<Iterable<Map>> runStreamed(String executable, List<String> arguments, |
238 |
| - {String workingDirectory}) async { |
239 |
| - List<Map> jsonObjects; |
240 |
| - |
241 |
| - /// Allow us to pretend we didn't pass the JSON flag in to dartdoc by |
242 |
| - /// printing what dartdoc would have printed without it, yet storing |
243 |
| - /// json objects into [jsonObjects]. |
244 |
| - Iterable<String> jsonCallback(String line) { |
245 |
| - Map result; |
246 |
| - try { |
247 |
| - result = json.decoder.convert(line); |
248 |
| - } catch (FormatException) {} |
249 |
| - if (result != null) { |
250 |
| - if (jsonObjects == null) { |
251 |
| - jsonObjects = new List(); |
252 |
| - } |
253 |
| - jsonObjects.add(result); |
254 |
| - if (result.containsKey('message')) { |
255 |
| - line = result['message']; |
256 |
| - } else if (result.containsKey('data')) { |
257 |
| - line = result['data']['text']; |
258 |
| - } |
259 |
| - } |
260 |
| - return line.split('\n'); |
261 |
| - } |
262 |
| - |
263 |
| - stderr.write('$prefix+ '); |
264 |
| - if (workingDirectory != null) stderr.write('(cd "$workingDirectory" && '); |
265 |
| - if (environment != null) { |
266 |
| - stderr.write(environment.keys.map((String key) { |
267 |
| - if (environment[key].contains(quotables)) { |
268 |
| - return "$key='${environment[key]}'"; |
269 |
| - } else { |
270 |
| - return "$key=${environment[key]}"; |
271 |
| - } |
272 |
| - }).join(' ')); |
273 |
| - stderr.write(' '); |
274 |
| - } |
275 |
| - stderr.write('$executable'); |
276 |
| - if (arguments.isNotEmpty) { |
277 |
| - for (String arg in arguments) { |
278 |
| - if (arg.contains(quotables)) { |
279 |
| - stderr.write(" '$arg'"); |
280 |
| - } else { |
281 |
| - stderr.write(" $arg"); |
282 |
| - } |
283 |
| - } |
284 |
| - } |
285 |
| - if (workingDirectory != null) stderr.write(')'); |
286 |
| - stderr.write('\n'); |
287 |
| - |
288 |
| - if (Platform.environment.containsKey('DRY_RUN')) return null; |
289 |
| - |
290 |
| - String realExecutable = executable; |
291 |
| - final List<String> realArguments = []; |
292 |
| - if (Platform.isLinux) { |
293 |
| - // Use GNU coreutils to force line buffering. This makes sure that |
294 |
| - // subprocesses that die due to fatal signals do not chop off the |
295 |
| - // last few lines of their output. |
296 |
| - // |
297 |
| - // Dart does not actually do this (seems to flush manually) unless |
298 |
| - // the VM crashes. |
299 |
| - realExecutable = 'stdbuf'; |
300 |
| - realArguments.addAll(['-o', 'L', '-e', 'L']); |
301 |
| - realArguments.add(executable); |
302 |
| - } |
303 |
| - realArguments.addAll(arguments); |
304 |
| - |
305 |
| - Process process = await Process.start(realExecutable, realArguments, |
306 |
| - workingDirectory: workingDirectory, environment: environment); |
307 |
| - Future<void> stdoutFuture = _printStream(process.stdout, stdout, |
308 |
| - prefix: prefix, filter: jsonCallback); |
309 |
| - Future<void> stderrFuture = _printStream(process.stderr, stderr, |
310 |
| - prefix: prefix, filter: jsonCallback); |
311 |
| - await Future.wait([stderrFuture, stdoutFuture, process.exitCode]); |
312 |
| - |
313 |
| - int exitCode = await process.exitCode; |
314 |
| - if (exitCode != 0) { |
315 |
| - throw new ProcessException(executable, arguments, |
316 |
| - "SubprocessLauncher got non-zero exitCode: $exitCode", exitCode); |
317 |
| - } |
318 |
| - return jsonObjects; |
319 |
| - } |
320 |
| -} |
0 commit comments