OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library analyzer_cli.src.build_mode; | 5 library analyzer_cli.src.build_mode; |
6 | 6 |
7 import 'dart:convert'; | |
8 import 'dart:core' hide Resource; | 7 import 'dart:core' hide Resource; |
9 import 'dart:io' as io; | 8 import 'dart:io' as io; |
10 | 9 |
| 10 import 'package:protobuf/protobuf.dart'; |
| 11 |
11 import 'package:analyzer/dart/ast/ast.dart' show CompilationUnit; | 12 import 'package:analyzer/dart/ast/ast.dart' show CompilationUnit; |
12 import 'package:analyzer/dart/element/element.dart'; | 13 import 'package:analyzer/dart/element/element.dart'; |
13 import 'package:analyzer/file_system/file_system.dart'; | 14 import 'package:analyzer/file_system/file_system.dart'; |
14 import 'package:analyzer/file_system/physical_file_system.dart'; | 15 import 'package:analyzer/file_system/physical_file_system.dart'; |
15 import 'package:analyzer/src/generated/engine.dart'; | 16 import 'package:analyzer/src/generated/engine.dart'; |
16 import 'package:analyzer/src/generated/error.dart'; | 17 import 'package:analyzer/src/generated/error.dart'; |
17 import 'package:analyzer/src/generated/java_io.dart'; | 18 import 'package:analyzer/src/generated/java_io.dart'; |
18 import 'package:analyzer/src/generated/sdk_io.dart'; | 19 import 'package:analyzer/src/generated/sdk_io.dart'; |
19 import 'package:analyzer/src/generated/source.dart'; | 20 import 'package:analyzer/src/generated/source.dart'; |
20 import 'package:analyzer/src/generated/source_io.dart'; | 21 import 'package:analyzer/src/generated/source_io.dart'; |
21 import 'package:analyzer/src/summary/format.dart'; | 22 import 'package:analyzer/src/summary/format.dart'; |
22 import 'package:analyzer/src/summary/idl.dart'; | 23 import 'package:analyzer/src/summary/idl.dart'; |
23 import 'package:analyzer/src/summary/package_bundle_reader.dart'; | 24 import 'package:analyzer/src/summary/package_bundle_reader.dart'; |
24 import 'package:analyzer/src/summary/prelink.dart'; | 25 import 'package:analyzer/src/summary/prelink.dart'; |
25 import 'package:analyzer/src/summary/summarize_ast.dart'; | 26 import 'package:analyzer/src/summary/summarize_ast.dart'; |
26 import 'package:analyzer/src/summary/summarize_elements.dart'; | 27 import 'package:analyzer/src/summary/summarize_elements.dart'; |
27 import 'package:analyzer/task/dart.dart'; | 28 import 'package:analyzer/task/dart.dart'; |
28 import 'package:analyzer_cli/src/analyzer_impl.dart'; | 29 import 'package:analyzer_cli/src/analyzer_impl.dart'; |
29 import 'package:analyzer_cli/src/driver.dart'; | 30 import 'package:analyzer_cli/src/driver.dart'; |
30 import 'package:analyzer_cli/src/error_formatter.dart'; | 31 import 'package:analyzer_cli/src/error_formatter.dart'; |
31 import 'package:analyzer_cli/src/options.dart'; | 32 import 'package:analyzer_cli/src/options.dart'; |
32 | 33 |
| 34 import 'message_grouper.dart'; |
| 35 import 'worker_protocol.pb.dart'; |
| 36 |
33 /** | 37 /** |
34 * Analyzer used when the "--build-mode" option is supplied. | 38 * Analyzer used when the "--build-mode" option is supplied. |
35 */ | 39 */ |
36 class BuildMode { | 40 class BuildMode { |
37 final CommandLineOptions options; | 41 final CommandLineOptions options; |
38 final AnalysisStats stats; | 42 final AnalysisStats stats; |
39 | 43 |
40 final ResourceProvider resourceProvider = PhysicalResourceProvider.INSTANCE; | 44 final ResourceProvider resourceProvider = PhysicalResourceProvider.INSTANCE; |
41 SummaryDataStore summaryDataStore; | 45 SummaryDataStore summaryDataStore; |
42 InternalAnalysisContext context; | 46 InternalAnalysisContext context; |
(...skipping 234 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
277 } | 281 } |
278 Uri uri = Uri.parse(sourceFile.substring(0, pipeIndex)); | 282 Uri uri = Uri.parse(sourceFile.substring(0, pipeIndex)); |
279 String path = sourceFile.substring(pipeIndex + 1); | 283 String path = sourceFile.substring(pipeIndex + 1); |
280 uriToFileMap[uri] = new JavaFile(path); | 284 uriToFileMap[uri] = new JavaFile(path); |
281 } | 285 } |
282 return uriToFileMap; | 286 return uriToFileMap; |
283 } | 287 } |
284 } | 288 } |
285 | 289 |
286 /** | 290 /** |
287 * Interface that every worker related data object has. | |
288 */ | |
289 abstract class WorkDataObject { | |
290 /** | |
291 * Translate the data in this class into a JSON map. | |
292 */ | |
293 Map<String, Object> toJson(); | |
294 } | |
295 | |
296 /** | |
297 * Connection between a worker and input / output. | 291 * Connection between a worker and input / output. |
298 */ | 292 */ |
299 abstract class WorkerConnection { | 293 abstract class WorkerConnection { |
300 /** | 294 /** |
301 * Read a new line. Block until a line is read. Return `null` if EOF. | 295 * Read a new [WorkRequest]. Returns [null] when there are no more requests. |
302 */ | 296 */ |
303 String readLineSync(); | 297 WorkRequest readRequest(); |
304 | 298 |
305 /** | 299 /** |
306 * Write the given [json] as a new line to the output. | 300 * Write the given [response] as bytes to the output. |
307 */ | 301 */ |
308 void writeJson(Map<String, Object> json); | 302 void writeResponse(WorkResponse response); |
309 } | 303 } |
310 | 304 |
311 /** | 305 /** |
312 * Persistent Bazel worker. | 306 * Persistent Bazel worker. |
313 */ | 307 */ |
314 class WorkerLoop { | 308 class WorkerLoop { |
315 static const int EXIT_CODE_OK = 0; | 309 static const int EXIT_CODE_OK = 0; |
316 static const int EXIT_CODE_ERROR = 15; | 310 static const int EXIT_CODE_ERROR = 15; |
317 | 311 |
318 final WorkerConnection connection; | 312 final WorkerConnection connection; |
319 | 313 |
320 final StringBuffer errorBuffer = new StringBuffer(); | 314 final StringBuffer errorBuffer = new StringBuffer(); |
321 final StringBuffer outBuffer = new StringBuffer(); | 315 final StringBuffer outBuffer = new StringBuffer(); |
322 | 316 |
323 WorkerLoop(this.connection); | 317 WorkerLoop(this.connection); |
324 | 318 |
325 factory WorkerLoop.std() { | 319 factory WorkerLoop.std({io.Stdin stdinStream, io.Stdout stdoutStream}) { |
326 WorkerConnection connection = new _StdWorkerConnection(); | 320 stdinStream ??= io.stdin; |
| 321 stdoutStream ??= io.stdout; |
| 322 WorkerConnection connection = |
| 323 new StdWorkerConnection(stdinStream, stdoutStream); |
327 return new WorkerLoop(connection); | 324 return new WorkerLoop(connection); |
328 } | 325 } |
329 | 326 |
330 /** | 327 /** |
331 * Performs analysis with given [options]. | 328 * Performs analysis with given [options]. |
332 */ | 329 */ |
333 void analyze(CommandLineOptions options) { | 330 void analyze(CommandLineOptions options) { |
334 new BuildMode(options, new AnalysisStats()).analyze(); | 331 new BuildMode(options, new AnalysisStats()).analyze(); |
335 } | 332 } |
336 | 333 |
337 /** | 334 /** |
338 * Perform a single loop step. Return `true` if should exit the loop. | 335 * Perform a single loop step. Return `true` if should exit the loop. |
339 */ | 336 */ |
340 bool performSingle() { | 337 bool performSingle() { |
341 try { | 338 try { |
342 WorkRequest request = _readRequest(); | 339 WorkRequest request = connection.readRequest(); |
343 if (request == null) { | 340 if (request == null) { |
344 return true; | 341 return true; |
345 } | 342 } |
346 // Prepare options. | 343 // Prepare options. |
347 CommandLineOptions options = | 344 CommandLineOptions options = |
348 CommandLineOptions.parse(request.arguments, (String msg) { | 345 CommandLineOptions.parse(request.arguments, (String msg) { |
349 throw new ArgumentError(msg); | 346 throw new ArgumentError(msg); |
350 }); | 347 }); |
351 // Analyze and respond. | 348 // Analyze and respond. |
352 analyze(options); | 349 analyze(options); |
353 String msg = _getErrorOutputBuffersText(); | 350 String msg = _getErrorOutputBuffersText(); |
354 _writeResponse(new WorkResponse(EXIT_CODE_OK, msg)); | 351 connection.writeResponse(new WorkResponse() |
| 352 ..exitCode = EXIT_CODE_OK |
| 353 ..output = msg); |
355 } catch (e, st) { | 354 } catch (e, st) { |
356 String msg = _getErrorOutputBuffersText(); | 355 String msg = _getErrorOutputBuffersText(); |
357 msg += '$e \n $st'; | 356 msg += '$e \n $st'; |
358 _writeResponse(new WorkResponse(EXIT_CODE_ERROR, msg)); | 357 connection.writeResponse(new WorkResponse() |
| 358 ..exitCode = EXIT_CODE_ERROR |
| 359 ..output = msg); |
359 } | 360 } |
360 return false; | 361 return false; |
361 } | 362 } |
362 | 363 |
363 /** | 364 /** |
364 * Run the worker loop. | 365 * Run the worker loop. |
365 */ | 366 */ |
366 void run() { | 367 void run() { |
367 errorSink = errorBuffer; | 368 errorSink = errorBuffer; |
368 outSink = outBuffer; | 369 outSink = outBuffer; |
(...skipping 13 matching lines...) Expand all Loading... |
382 String _getErrorOutputBuffersText() { | 383 String _getErrorOutputBuffersText() { |
383 String msg = ''; | 384 String msg = ''; |
384 if (errorBuffer.isNotEmpty) { | 385 if (errorBuffer.isNotEmpty) { |
385 msg += errorBuffer.toString() + '\n'; | 386 msg += errorBuffer.toString() + '\n'; |
386 } | 387 } |
387 if (outBuffer.isNotEmpty) { | 388 if (outBuffer.isNotEmpty) { |
388 msg += outBuffer.toString() + '\n'; | 389 msg += outBuffer.toString() + '\n'; |
389 } | 390 } |
390 return msg; | 391 return msg; |
391 } | 392 } |
392 | |
393 /** | |
394 * Read a new [WorkRequest]. Return `null` if EOF. | |
395 * Throw [ArgumentError] if cannot be parsed. | |
396 */ | |
397 WorkRequest _readRequest() { | |
398 String line = connection.readLineSync(); | |
399 if (line == null) { | |
400 return null; | |
401 } | |
402 Object json = JSON.decode(line); | |
403 if (json is Map) { | |
404 return new WorkRequest.fromJson(json); | |
405 } else { | |
406 throw new ArgumentError('The request line is not a JSON object: $line'); | |
407 } | |
408 } | |
409 | |
410 void _writeResponse(WorkResponse response) { | |
411 Map<String, Object> json = response.toJson(); | |
412 connection.writeJson(json); | |
413 } | |
414 } | |
415 | |
416 /** | |
417 * Input file. | |
418 */ | |
419 class WorkInput implements WorkDataObject { | |
420 final String path; | |
421 final List<int> digest; | |
422 | |
423 WorkInput(this.path, this.digest); | |
424 | |
425 factory WorkInput.fromJson(Map<String, Object> json) { | |
426 // Parse path. | |
427 Object path2 = json['path']; | |
428 if (path2 == null) { | |
429 throw new ArgumentError('The field "path" is missing.'); | |
430 } | |
431 if (path2 is! String) { | |
432 throw new ArgumentError('The field "path" must be a string.'); | |
433 } | |
434 // Parse digest. | |
435 List<int> digest = const <int>[]; | |
436 { | |
437 Object digestJson = json['digest']; | |
438 if (digestJson != null) { | |
439 if (digestJson is List && digestJson.every((e) => e is int)) { | |
440 digest = digestJson; | |
441 } else { | |
442 throw new ArgumentError( | |
443 'The field "digest" should be a list of int.'); | |
444 } | |
445 } | |
446 } | |
447 // OK | |
448 return new WorkInput(path2, digest); | |
449 } | |
450 | |
451 @override | |
452 Map<String, Object> toJson() { | |
453 Map<String, Object> json = <String, Object>{}; | |
454 if (path != null) { | |
455 json['path'] = path; | |
456 } | |
457 if (digest != null) { | |
458 json['digest'] = digest; | |
459 } | |
460 return json; | |
461 } | |
462 } | |
463 | |
464 /** | |
465 * Single work unit that Bazel sends to the worker. | |
466 */ | |
467 class WorkRequest implements WorkDataObject { | |
468 /** | |
469 * Command line arguments for this request. | |
470 */ | |
471 final List<String> arguments; | |
472 | |
473 /** | |
474 * Input files that the worker is allowed to read during execution of this | |
475 * request. | |
476 */ | |
477 final List<WorkInput> inputs; | |
478 | |
479 WorkRequest(this.arguments, this.inputs); | |
480 | |
481 factory WorkRequest.fromJson(Map<String, Object> json) { | |
482 // Parse arguments. | |
483 List<String> arguments = const <String>[]; | |
484 { | |
485 Object argumentsJson = json['arguments']; | |
486 if (argumentsJson != null) { | |
487 if (argumentsJson is List && argumentsJson.every((e) => e is String)) { | |
488 arguments = argumentsJson; | |
489 } else { | |
490 throw new ArgumentError( | |
491 'The field "arguments" should be a list of strings.'); | |
492 } | |
493 } | |
494 } | |
495 // Parse inputs. | |
496 List<WorkInput> inputs = const <WorkInput>[]; | |
497 { | |
498 Object inputsJson = json['inputs']; | |
499 if (inputsJson != null) { | |
500 if (inputsJson is List && | |
501 inputsJson.every((e) { | |
502 return e is Map && e.keys.every((key) => key is String); | |
503 })) { | |
504 inputs = inputsJson | |
505 .map((Map input) => new WorkInput.fromJson(input)) | |
506 .toList(); | |
507 } else { | |
508 throw new ArgumentError( | |
509 'The field "inputs" should be a list of objects.'); | |
510 } | |
511 } | |
512 } | |
513 // No inputs. | |
514 if (arguments.isEmpty && inputs.isEmpty) { | |
515 throw new ArgumentError('Both "arguments" and "inputs" cannot be empty.'); | |
516 } | |
517 // OK | |
518 return new WorkRequest(arguments, inputs); | |
519 } | |
520 | |
521 @override | |
522 Map<String, Object> toJson() { | |
523 Map<String, Object> json = <String, Object>{}; | |
524 if (arguments != null) { | |
525 json['arguments'] = arguments; | |
526 } | |
527 if (inputs != null) { | |
528 json['inputs'] = inputs.map((input) => input.toJson()).toList(); | |
529 } | |
530 return json; | |
531 } | |
532 } | |
533 | |
534 /** | |
535 * Result that the worker sends back to Bazel when it finished its work on a | |
536 * [WorkRequest] message. | |
537 */ | |
538 class WorkResponse implements WorkDataObject { | |
539 final int exitCode; | |
540 final String output; | |
541 | |
542 WorkResponse(this.exitCode, this.output); | |
543 | |
544 @override | |
545 Map<String, Object> toJson() { | |
546 Map<String, Object> json = <String, Object>{}; | |
547 if (exitCode != null) { | |
548 json['exit_code'] = exitCode; | |
549 } | |
550 if (output != null) { | |
551 json['output'] = output; | |
552 } | |
553 return json; | |
554 } | |
555 } | 393 } |
556 | 394 |
557 /** | 395 /** |
558 * Default implementation of [WorkerConnection] that works with stdio. | 396 * Default implementation of [WorkerConnection] that works with stdio. |
559 */ | 397 */ |
560 class _StdWorkerConnection implements WorkerConnection { | 398 class StdWorkerConnection implements WorkerConnection { |
| 399 final MessageGrouper _messageGrouper; |
| 400 final io.Stdout _stdoutStream; |
| 401 |
| 402 StdWorkerConnection(io.Stdin stdinStream, this._stdoutStream) |
| 403 : _messageGrouper = new MessageGrouper(stdinStream); |
| 404 |
561 @override | 405 @override |
562 String readLineSync() { | 406 WorkRequest readRequest() { |
563 return io.stdin.readLineSync(); | 407 var buffer = _messageGrouper.next; |
| 408 if (buffer == null) return null; |
| 409 |
| 410 return new WorkRequest.fromBuffer(buffer); |
564 } | 411 } |
565 | 412 |
566 @override | 413 @override |
567 void writeJson(Map<String, Object> json) { | 414 void writeResponse(WorkResponse response) { |
568 io.stdout.writeln(JSON.encode(json)); | 415 var responseBuffer = response.writeToBuffer(); |
| 416 |
| 417 var writer = new CodedBufferWriter(); |
| 418 writer.writeInt32NoTag(responseBuffer.length); |
| 419 writer.writeRawBytes(responseBuffer); |
| 420 |
| 421 _stdoutStream.add(writer.toBuffer()); |
569 } | 422 } |
570 } | 423 } |
OLD | NEW |