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 output. |
| 307 */ |
| 308 void writeJson(Map<String, Object> json); |
| 309 } |
| 310 |
| 311 /** |
| 312 * Persistent Bazel worker. |
| 313 */ |
| 314 class WorkerLoop { |
| 315 static const int EXIT_CODE_OK = 0; |
| 316 static const int EXIT_CODE_ERROR = 15; |
| 317 |
| 318 final WorkerConnection connection; |
| 319 |
| 320 final StringBuffer errorBuffer = new StringBuffer(); |
| 321 final StringBuffer outBuffer = new StringBuffer(); |
| 322 |
| 323 WorkerLoop(this.connection); |
| 324 |
| 325 factory WorkerLoop.std() { |
| 326 WorkerConnection connection = new _StdWorkerConnection(); |
| 327 return new WorkerLoop(connection); |
| 328 } |
| 329 |
| 330 /** |
| 331 * Performs analysis with given [options]. |
| 332 */ |
| 333 void analyze(CommandLineOptions options) { |
| 334 new BuildMode(options, new AnalysisStats()).analyze(); |
| 335 } |
| 336 |
| 337 /** |
| 338 * Perform a single loop step. Return `true` if should exit the loop. |
| 339 */ |
| 340 bool performSingle() { |
| 341 try { |
| 342 WorkRequest request = _readRequest(); |
| 343 if (request == null) { |
| 344 return true; |
| 345 } |
| 346 // Prepare options. |
| 347 CommandLineOptions options = |
| 348 CommandLineOptions.parse(request.arguments, (String msg) { |
| 349 throw new ArgumentError(msg); |
| 350 }); |
| 351 // Analyze and respond. |
| 352 analyze(options); |
| 353 String msg = _getErrorOutputBuffersText(); |
| 354 _writeResponse(new WorkResponse(EXIT_CODE_OK, msg)); |
| 355 } catch (e, st) { |
| 356 String msg = _getErrorOutputBuffersText(); |
| 357 msg += '$e \n $st'; |
| 358 _writeResponse(new WorkResponse(EXIT_CODE_ERROR, msg)); |
| 359 } |
| 360 return false; |
| 361 } |
| 362 |
| 363 /** |
| 364 * Run the worker loop. |
| 365 */ |
| 366 void run() { |
| 367 errorSink = errorBuffer; |
| 368 outSink = outBuffer; |
| 369 exitHandler = (int exitCode) { |
| 370 return throw new StateError('Exit called: $exitCode'); |
| 371 }; |
| 372 while (true) { |
| 373 errorBuffer.clear(); |
| 374 outBuffer.clear(); |
| 375 bool shouldExit = performSingle(); |
| 376 if (shouldExit) { |
| 377 break; |
| 378 } |
| 379 } |
| 380 } |
| 381 |
| 382 String _getErrorOutputBuffersText() { |
| 383 String msg = ''; |
| 384 if (errorBuffer.isNotEmpty) { |
| 385 msg += errorBuffer.toString() + '\n'; |
| 386 } |
| 387 if (outBuffer.isNotEmpty) { |
| 388 msg += outBuffer.toString() + '\n'; |
| 389 } |
| 390 return msg; |
| 391 } |
| 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 } |
| 556 |
| 557 /** |
| 558 * Default implementation of [WorkerConnection] that works with stdio. |
| 559 */ |
| 560 class _StdWorkerConnection implements WorkerConnection { |
| 561 @override |
| 562 String readLineSync() { |
| 563 return io.stdin.readLineSync(); |
| 564 } |
| 565 |
| 566 @override |
| 567 void writeJson(Map<String, Object> json) { |
| 568 io.stdout.writeln(JSON.encode(json)); |
| 569 } |
| 570 } |
OLD | NEW |