Chromium Code Reviews| 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'; | |
| 7 import 'dart:core' hide Resource; | 8 import 'dart:core' hide Resource; |
| 8 import 'dart:io' as io; | 9 import 'dart:io' as io; |
| 9 | 10 |
| 10 import 'package:analyzer/dart/ast/ast.dart' show CompilationUnit; | 11 import 'package:analyzer/dart/ast/ast.dart' show CompilationUnit; |
| 11 import 'package:analyzer/dart/element/element.dart'; | 12 import 'package:analyzer/dart/element/element.dart'; |
| 12 import 'package:analyzer/file_system/file_system.dart'; | 13 import 'package:analyzer/file_system/file_system.dart'; |
| 13 import 'package:analyzer/file_system/physical_file_system.dart'; | 14 import 'package:analyzer/file_system/physical_file_system.dart'; |
| 14 import 'package:analyzer/src/generated/engine.dart'; | 15 import 'package:analyzer/src/generated/engine.dart'; |
| 15 import 'package:analyzer/src/generated/error.dart'; | 16 import 'package:analyzer/src/generated/error.dart'; |
| 16 import 'package:analyzer/src/generated/java_io.dart'; | 17 import 'package:analyzer/src/generated/java_io.dart'; |
| (...skipping 257 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 274 'Illegal input file (must be "\$uri|\$path"): $sourceFile'); | 275 'Illegal input file (must be "\$uri|\$path"): $sourceFile'); |
| 275 return null; | 276 return null; |
| 276 } | 277 } |
| 277 Uri uri = Uri.parse(sourceFile.substring(0, pipeIndex)); | 278 Uri uri = Uri.parse(sourceFile.substring(0, pipeIndex)); |
| 278 String path = sourceFile.substring(pipeIndex + 1); | 279 String path = sourceFile.substring(pipeIndex + 1); |
| 279 uriToFileMap[uri] = new JavaFile(path); | 280 uriToFileMap[uri] = new JavaFile(path); |
| 280 } | 281 } |
| 281 return uriToFileMap; | 282 return uriToFileMap; |
| 282 } | 283 } |
| 283 } | 284 } |
| 285 | |
| 286 /** | |
| 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. | |
| 298 */ | |
| 299 abstract class WorkerConnection { | |
| 300 /** | |
| 301 * Read a new line. Block until a line is read. Return `null` if EOF. | |
| 302 */ | |
| 303 String readLineSync(); | |
| 304 | |
| 305 /** | |
| 306 * Write the given [json] as a new line to the stdout. | |
|
Paul Berry
2016/03/30 18:27:43
s/to the stdout/to the output/
(since this class
scheglov
2016/03/30 18:39:02
Done.
| |
| 307 */ | |
| 308 void writeJson(Map<String, Object> json); | |
| 309 } | |
| 310 | |
| 311 /** | |
| 312 * Persistent Bazel worker. | |
| 313 */ | |
| 314 class WorkerLoop { | |
| 315 final WorkerConnection connection; | |
| 316 | |
| 317 final StringBuffer errorBuffer = new StringBuffer(); | |
| 318 final StringBuffer outBuffer = new StringBuffer(); | |
| 319 | |
| 320 WorkerLoop(this.connection); | |
| 321 | |
| 322 factory WorkerLoop.std() { | |
| 323 WorkerConnection connection = new _StdWorkerConnection(); | |
| 324 return new WorkerLoop(connection); | |
| 325 } | |
| 326 | |
| 327 /** | |
| 328 * Performs analysis with given [options]. | |
| 329 */ | |
| 330 void analyze(CommandLineOptions options) { | |
| 331 new BuildMode(options, new AnalysisStats()).analyze(); | |
| 332 } | |
| 333 | |
| 334 /** | |
| 335 * Perform a single loop step. Return `true` if should exit the loop. | |
| 336 */ | |
| 337 bool performSingle() { | |
| 338 try { | |
| 339 WorkRequest request = _readRequest(); | |
| 340 if (request == null) { | |
| 341 return true; | |
| 342 } | |
| 343 // Prepare options. | |
| 344 CommandLineOptions options = | |
| 345 CommandLineOptions.parse(request.arguments, (String msg) { | |
| 346 throw new ArgumentError(msg); | |
| 347 }); | |
| 348 // Analyze and respond. | |
| 349 analyze(options); | |
| 350 String msg = _getErrorOutputBuffersText(); | |
| 351 _writeResponse(new WorkResponse(0, msg)); | |
| 352 } catch (e, st) { | |
| 353 String msg = _getErrorOutputBuffersText(); | |
| 354 msg += '$e \n $st'; | |
| 355 _writeResponse(new WorkResponse(15, msg)); | |
|
Paul Berry
2016/03/30 18:27:43
Can we make constants for these exit codes (0 abov
scheglov
2016/03/30 18:39:02
Done.
| |
| 356 } | |
| 357 return false; | |
| 358 } | |
| 359 | |
| 360 /** | |
| 361 * Run the worker loop. | |
| 362 */ | |
| 363 void run() { | |
| 364 errorSink = errorBuffer; | |
| 365 outSink = outBuffer; | |
| 366 exitHandler = (int exitCode) { | |
| 367 return throw new StateError('Exit called: $exitCode'); | |
| 368 }; | |
| 369 while (true) { | |
| 370 errorBuffer.clear(); | |
| 371 outBuffer.clear(); | |
| 372 bool shouldExit = performSingle(); | |
| 373 if (shouldExit) { | |
| 374 break; | |
| 375 } | |
| 376 } | |
| 377 } | |
| 378 | |
| 379 String _getErrorOutputBuffersText() { | |
| 380 String msg = ''; | |
| 381 if (errorBuffer.isNotEmpty) { | |
| 382 msg += errorBuffer.toString() + '\n'; | |
| 383 } | |
| 384 if (outBuffer.isNotEmpty) { | |
| 385 msg += outBuffer.toString() + '\n'; | |
| 386 } | |
| 387 return msg; | |
| 388 } | |
| 389 | |
| 390 /** | |
| 391 * Read a new [WorkRequest]. Return `null` if EOF. | |
| 392 * Throw [ArgumentError] if cannot be parsed. | |
| 393 */ | |
| 394 WorkRequest _readRequest() { | |
| 395 String line = connection.readLineSync(); | |
| 396 if (line == null) { | |
| 397 return null; | |
| 398 } | |
| 399 Object json = JSON.decode(line); | |
| 400 if (json is Map) { | |
| 401 return new WorkRequest.fromJson(json); | |
| 402 } else { | |
| 403 throw new ArgumentError('The request line is not a JSON object: $line'); | |
| 404 } | |
| 405 } | |
| 406 | |
| 407 void _writeResponse(WorkResponse response) { | |
| 408 Map<String, Object> json = response.toJson(); | |
| 409 connection.writeJson(json); | |
| 410 } | |
| 411 } | |
| 412 | |
| 413 /** | |
| 414 * Input file. | |
| 415 */ | |
| 416 class WorkInput implements WorkDataObject { | |
| 417 final String path; | |
| 418 final List<int> digest; | |
| 419 | |
| 420 WorkInput(this.path, this.digest); | |
| 421 | |
| 422 factory WorkInput.fromJson(Map<String, Object> json) { | |
| 423 // Parse path. | |
| 424 Object path2 = json['path']; | |
| 425 if (path2 == null) { | |
| 426 throw new ArgumentError('The field "path" is missing.'); | |
| 427 } | |
| 428 if (path2 is! String) { | |
| 429 throw new ArgumentError('The field "path" must be a string.'); | |
| 430 } | |
| 431 // Parse digest. | |
| 432 List<int> digest = const <int>[]; | |
| 433 { | |
| 434 Object digestJson = json['digest']; | |
| 435 if (digestJson != null) { | |
| 436 if (digestJson is List && digestJson.every((e) => e is int)) { | |
| 437 digest = digestJson; | |
| 438 } else { | |
| 439 throw new ArgumentError( | |
| 440 'The field "digest" should be a list of int.'); | |
| 441 } | |
| 442 } | |
| 443 } | |
| 444 // OK | |
| 445 return new WorkInput(path2, digest); | |
| 446 } | |
| 447 | |
| 448 @override | |
| 449 Map<String, Object> toJson() { | |
| 450 return <String, Object>{'path': path, 'digest': digest}; | |
|
Paul Berry
2016/03/30 18:27:43
If digest is null, I'd prefer to see it not includ
scheglov
2016/03/30 18:39:02
Done.
| |
| 451 } | |
| 452 } | |
| 453 | |
| 454 /** | |
| 455 * Single work unit that Bazel sends to the worker. | |
| 456 */ | |
| 457 class WorkRequest implements WorkDataObject { | |
| 458 /** | |
| 459 * Command line arguments for this request. | |
| 460 */ | |
| 461 final List<String> arguments; | |
| 462 | |
| 463 /** | |
| 464 * Input files that the worker is allowed to read during execution of this | |
| 465 * request. | |
| 466 */ | |
| 467 final List<WorkInput> inputs; | |
| 468 | |
| 469 WorkRequest(this.arguments, this.inputs); | |
| 470 | |
| 471 factory WorkRequest.fromJson(Map<String, Object> json) { | |
| 472 // Parse arguments. | |
| 473 List<String> arguments = const <String>[]; | |
| 474 { | |
| 475 Object argumentsJson = json['arguments']; | |
| 476 if (argumentsJson != null) { | |
| 477 if (argumentsJson is List && argumentsJson.every((e) => e is String)) { | |
| 478 arguments = argumentsJson; | |
| 479 } else { | |
| 480 throw new ArgumentError( | |
| 481 'The field "arguments" should be a list of strings.'); | |
| 482 } | |
| 483 } | |
| 484 } | |
| 485 // Parse inputs. | |
| 486 List<WorkInput> inputs = const <WorkInput>[]; | |
| 487 { | |
| 488 Object inputsJson = json['inputs']; | |
| 489 if (inputsJson != null) { | |
| 490 if (inputsJson is List && | |
| 491 inputsJson.every((e) { | |
| 492 return e is Map && e.keys.every((key) => key is String); | |
| 493 })) { | |
| 494 inputs = inputsJson | |
| 495 .map((Map input) => new WorkInput.fromJson(input)) | |
| 496 .toList(); | |
| 497 } else { | |
| 498 throw new ArgumentError( | |
| 499 'The field "inputs" should be a list of objects.'); | |
| 500 } | |
| 501 } | |
| 502 } | |
| 503 // No inputs. | |
| 504 if (arguments.isEmpty && inputs.isEmpty) { | |
| 505 throw new ArgumentError('Both "arguments" and "inputs" cannot be empty.'); | |
| 506 } | |
| 507 // OK | |
| 508 return new WorkRequest(arguments, inputs); | |
| 509 } | |
| 510 | |
| 511 @override | |
| 512 Map<String, Object> toJson() { | |
| 513 return <String, Object>{ | |
| 514 'arguments': arguments, | |
| 515 'inputs': inputs.map((input) => input.toJson()).toList() | |
| 516 }; | |
| 517 } | |
| 518 } | |
| 519 | |
| 520 /** | |
| 521 * Result that the worker sends back to Bazel when it finished its work on a | |
| 522 * [WorkRequest] message. | |
| 523 */ | |
| 524 class WorkResponse implements WorkDataObject { | |
| 525 final int exitCode; | |
| 526 final String output; | |
| 527 | |
| 528 WorkResponse(this.exitCode, this.output); | |
| 529 | |
| 530 @override | |
| 531 Map<String, Object> toJson() { | |
| 532 return <String, Object>{'exit_code': exitCode, 'output': output}; | |
| 533 } | |
| 534 } | |
| 535 | |
| 536 /** | |
| 537 * Default implementation of [WorkerConnection] that works with stdio. | |
| 538 */ | |
| 539 class _StdWorkerConnection implements WorkerConnection { | |
| 540 @override | |
| 541 String readLineSync() { | |
| 542 return io.stdin.readLineSync(); | |
| 543 } | |
| 544 | |
| 545 @override | |
| 546 void writeJson(Map<String, Object> json) { | |
| 547 io.stdout.writeln(JSON.encode(json)); | |
| 548 } | |
| 549 } | |
| OLD | NEW |