Chromium Code Reviews| Index: pkg/analyzer_cli/lib/src/build_mode.dart |
| diff --git a/pkg/analyzer_cli/lib/src/build_mode.dart b/pkg/analyzer_cli/lib/src/build_mode.dart |
| index 800efff7a811d7e15678eecbcc0d3a178682847f..93526111d59d0319c1eefd516063bd0a1a242681 100644 |
| --- a/pkg/analyzer_cli/lib/src/build_mode.dart |
| +++ b/pkg/analyzer_cli/lib/src/build_mode.dart |
| @@ -4,6 +4,7 @@ |
| library analyzer_cli.src.build_mode; |
| +import 'dart:convert'; |
| import 'dart:core' hide Resource; |
| import 'dart:io' as io; |
| @@ -281,3 +282,268 @@ class BuildMode { |
| return uriToFileMap; |
| } |
| } |
| + |
| +/** |
| + * Interface that every worker related data object has. |
| + */ |
| +abstract class WorkDataObject { |
| + /** |
| + * Translate the data in this class into a JSON map. |
| + */ |
| + Map<String, Object> toJson(); |
| +} |
| + |
| +/** |
| + * Connection between a worker and input / output. |
| + */ |
| +abstract class WorkerConnection { |
| + /** |
| + * Read a new line. Block until a line is read. Return `null` if EOF. |
| + */ |
| + String readLineSync(); |
| + |
| + /** |
| + * 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.
|
| + */ |
| + void writeJson(Map<String, Object> json); |
| +} |
| + |
| +/** |
| + * Persistent Bazel worker. |
| + */ |
| +class WorkerLoop { |
| + final WorkerConnection connection; |
| + |
| + final StringBuffer errorBuffer = new StringBuffer(); |
| + final StringBuffer outBuffer = new StringBuffer(); |
| + |
| + WorkerLoop(this.connection); |
| + |
| + factory WorkerLoop.std() { |
| + WorkerConnection connection = new _StdWorkerConnection(); |
| + return new WorkerLoop(connection); |
| + } |
| + |
| + /** |
| + * Performs analysis with given [options]. |
| + */ |
| + void analyze(CommandLineOptions options) { |
| + new BuildMode(options, new AnalysisStats()).analyze(); |
| + } |
| + |
| + /** |
| + * Perform a single loop step. Return `true` if should exit the loop. |
| + */ |
| + bool performSingle() { |
| + try { |
| + WorkRequest request = _readRequest(); |
| + if (request == null) { |
| + return true; |
| + } |
| + // Prepare options. |
| + CommandLineOptions options = |
| + CommandLineOptions.parse(request.arguments, (String msg) { |
| + throw new ArgumentError(msg); |
| + }); |
| + // Analyze and respond. |
| + analyze(options); |
| + String msg = _getErrorOutputBuffersText(); |
| + _writeResponse(new WorkResponse(0, msg)); |
| + } catch (e, st) { |
| + String msg = _getErrorOutputBuffersText(); |
| + msg += '$e \n $st'; |
| + _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.
|
| + } |
| + return false; |
| + } |
| + |
| + /** |
| + * Run the worker loop. |
| + */ |
| + void run() { |
| + errorSink = errorBuffer; |
| + outSink = outBuffer; |
| + exitHandler = (int exitCode) { |
| + return throw new StateError('Exit called: $exitCode'); |
| + }; |
| + while (true) { |
| + errorBuffer.clear(); |
| + outBuffer.clear(); |
| + bool shouldExit = performSingle(); |
| + if (shouldExit) { |
| + break; |
| + } |
| + } |
| + } |
| + |
| + String _getErrorOutputBuffersText() { |
| + String msg = ''; |
| + if (errorBuffer.isNotEmpty) { |
| + msg += errorBuffer.toString() + '\n'; |
| + } |
| + if (outBuffer.isNotEmpty) { |
| + msg += outBuffer.toString() + '\n'; |
| + } |
| + return msg; |
| + } |
| + |
| + /** |
| + * Read a new [WorkRequest]. Return `null` if EOF. |
| + * Throw [ArgumentError] if cannot be parsed. |
| + */ |
| + WorkRequest _readRequest() { |
| + String line = connection.readLineSync(); |
| + if (line == null) { |
| + return null; |
| + } |
| + Object json = JSON.decode(line); |
| + if (json is Map) { |
| + return new WorkRequest.fromJson(json); |
| + } else { |
| + throw new ArgumentError('The request line is not a JSON object: $line'); |
| + } |
| + } |
| + |
| + void _writeResponse(WorkResponse response) { |
| + Map<String, Object> json = response.toJson(); |
| + connection.writeJson(json); |
| + } |
| +} |
| + |
| +/** |
| + * Input file. |
| + */ |
| +class WorkInput implements WorkDataObject { |
| + final String path; |
| + final List<int> digest; |
| + |
| + WorkInput(this.path, this.digest); |
| + |
| + factory WorkInput.fromJson(Map<String, Object> json) { |
| + // Parse path. |
| + Object path2 = json['path']; |
| + if (path2 == null) { |
| + throw new ArgumentError('The field "path" is missing.'); |
| + } |
| + if (path2 is! String) { |
| + throw new ArgumentError('The field "path" must be a string.'); |
| + } |
| + // Parse digest. |
| + List<int> digest = const <int>[]; |
| + { |
| + Object digestJson = json['digest']; |
| + if (digestJson != null) { |
| + if (digestJson is List && digestJson.every((e) => e is int)) { |
| + digest = digestJson; |
| + } else { |
| + throw new ArgumentError( |
| + 'The field "digest" should be a list of int.'); |
| + } |
| + } |
| + } |
| + // OK |
| + return new WorkInput(path2, digest); |
| + } |
| + |
| + @override |
| + Map<String, Object> toJson() { |
| + 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.
|
| + } |
| +} |
| + |
| +/** |
| + * Single work unit that Bazel sends to the worker. |
| + */ |
| +class WorkRequest implements WorkDataObject { |
| + /** |
| + * Command line arguments for this request. |
| + */ |
| + final List<String> arguments; |
| + |
| + /** |
| + * Input files that the worker is allowed to read during execution of this |
| + * request. |
| + */ |
| + final List<WorkInput> inputs; |
| + |
| + WorkRequest(this.arguments, this.inputs); |
| + |
| + factory WorkRequest.fromJson(Map<String, Object> json) { |
| + // Parse arguments. |
| + List<String> arguments = const <String>[]; |
| + { |
| + Object argumentsJson = json['arguments']; |
| + if (argumentsJson != null) { |
| + if (argumentsJson is List && argumentsJson.every((e) => e is String)) { |
| + arguments = argumentsJson; |
| + } else { |
| + throw new ArgumentError( |
| + 'The field "arguments" should be a list of strings.'); |
| + } |
| + } |
| + } |
| + // Parse inputs. |
| + List<WorkInput> inputs = const <WorkInput>[]; |
| + { |
| + Object inputsJson = json['inputs']; |
| + if (inputsJson != null) { |
| + if (inputsJson is List && |
| + inputsJson.every((e) { |
| + return e is Map && e.keys.every((key) => key is String); |
| + })) { |
| + inputs = inputsJson |
| + .map((Map input) => new WorkInput.fromJson(input)) |
| + .toList(); |
| + } else { |
| + throw new ArgumentError( |
| + 'The field "inputs" should be a list of objects.'); |
| + } |
| + } |
| + } |
| + // No inputs. |
| + if (arguments.isEmpty && inputs.isEmpty) { |
| + throw new ArgumentError('Both "arguments" and "inputs" cannot be empty.'); |
| + } |
| + // OK |
| + return new WorkRequest(arguments, inputs); |
| + } |
| + |
| + @override |
| + Map<String, Object> toJson() { |
| + return <String, Object>{ |
| + 'arguments': arguments, |
| + 'inputs': inputs.map((input) => input.toJson()).toList() |
| + }; |
| + } |
| +} |
| + |
| +/** |
| + * Result that the worker sends back to Bazel when it finished its work on a |
| + * [WorkRequest] message. |
| + */ |
| +class WorkResponse implements WorkDataObject { |
| + final int exitCode; |
| + final String output; |
| + |
| + WorkResponse(this.exitCode, this.output); |
| + |
| + @override |
| + Map<String, Object> toJson() { |
| + return <String, Object>{'exit_code': exitCode, 'output': output}; |
| + } |
| +} |
| + |
| +/** |
| + * Default implementation of [WorkerConnection] that works with stdio. |
| + */ |
| +class _StdWorkerConnection implements WorkerConnection { |
| + @override |
| + String readLineSync() { |
| + return io.stdin.readLineSync(); |
| + } |
| + |
| + @override |
| + void writeJson(Map<String, Object> json) { |
| + io.stdout.writeln(JSON.encode(json)); |
| + } |
| +} |