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..22562473d38804b0136364c79617a8efd2a4848e 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,289 @@ 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 output. |
+ */ |
+ void writeJson(Map<String, Object> json); |
+} |
+ |
+/** |
+ * Persistent Bazel worker. |
+ */ |
+class WorkerLoop { |
+ static const int EXIT_CODE_OK = 0; |
+ static const int EXIT_CODE_ERROR = 15; |
+ |
+ 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(EXIT_CODE_OK, msg)); |
+ } catch (e, st) { |
+ String msg = _getErrorOutputBuffersText(); |
+ msg += '$e \n $st'; |
+ _writeResponse(new WorkResponse(EXIT_CODE_ERROR, msg)); |
+ } |
+ 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() { |
+ Map<String, Object> json = <String, Object>{}; |
+ if (path != null) { |
+ json['path'] = path; |
+ } |
+ if (digest != null) { |
+ json['digest'] = digest; |
+ } |
+ return json; |
+ } |
+} |
+ |
+/** |
+ * 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() { |
+ Map<String, Object> json = <String, Object>{}; |
+ if (arguments != null) { |
+ json['arguments'] = arguments; |
+ } |
+ if (inputs != null) { |
+ json['inputs'] = inputs.map((input) => input.toJson()).toList(); |
+ } |
+ return json; |
+ } |
+} |
+ |
+/** |
+ * 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() { |
+ Map<String, Object> json = <String, Object>{}; |
+ if (exitCode != null) { |
+ json['exit_code'] = exitCode; |
+ } |
+ if (output != null) { |
+ json['output'] = output; |
+ } |
+ return json; |
+ } |
+} |
+ |
+/** |
+ * 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)); |
+ } |
+} |