wip
diff --git a/bin/dartdevc.dart b/bin/dartdevc.dart index 6be7a96..2246a9c 100755 --- a/bin/dartdevc.dart +++ b/bin/dartdevc.dart
@@ -40,39 +40,27 @@ import 'package:dev_compiler/src/compiler/command.dart'; main(List<String> args) async { + args = _preprocessArgs(args); + var runner = new CommandRunner('dartdevc', 'Dart Development Compiler'); runner.addCommand(new CompileCommand()); try { await runner.run(args); - } on UsageException catch (e) { - // Incorrect usage, input file not found, etc. - print(e); - exit(64); - } on CompileErrorException catch (e) { - // Code has error(s) and failed to compile. - print(e); - exit(1); - } catch (e, s) { - // Anything else is likely a compiler bug. - // - // --unsafe-force-compile is a bit of a grey area, but it's nice not to - // crash while compiling - // (of course, output code may crash, if it had errors). - // - print(""); - print("We're sorry, you've found a bug in our compiler."); - print("You can report this bug at:"); - print(" https://github.com/dart-lang/dev_compiler/issues"); - print(""); - print("Please include the information below in your report, along with"); - print("any other information that may help us track it down. Thanks!"); - print(""); - print(" dartdevc arguments: " + args.join(' ')); - print(" dart --version: ${Platform.version}"); - print(""); - print("```"); - print(e); - print(s); - print("```"); + } catch(e, s) { + exit(handleError(e, s, args)); + } +} + +/// If the final arg is `@file_path` then read in all the lines of that file +/// and add those as args. +/// +/// Bazel actions that support workers must provide all their per-WorkRequest +/// arguments in a file like this instead of as normal args. +List<String> _preprocessArgs(List<String> args) { + if (args.last.startsWith('@')) { + return new List.from(args) + ..addAll(new File(args.last.substring(1)).readAsLinesSync()); + } else { + return args; } }
diff --git a/lib/src/compiler/command.dart b/lib/src/compiler/command.dart index 2b0eca1..686dddf 100644 --- a/lib/src/compiler/command.dart +++ b/lib/src/compiler/command.dart
@@ -8,6 +8,7 @@ import 'package:analyzer/src/generated/source.dart' show Source; import 'package:analyzer/src/summary/package_bundle_reader.dart' show InSummarySource; +import 'package:bazel_worker/bazel_worker.dart'; import 'compiler.dart' show BuildUnit, CompilerOptions, JSModuleFile, ModuleCompiler; import '../analyzer/context.dart' show AnalyzerOptions; @@ -20,26 +21,40 @@ CompileCommand() { argParser.addOption('out', abbr: 'o', help: 'Output file (required)'); + argParser.addFlag('persistent_worker', + help: 'Run in a persistent Bazel worker (http://bazel.io/)\n', + defaultsTo: false, + hide: true); CompilerOptions.addArguments(argParser); AnalyzerOptions.addArguments(argParser); } @override void run() { - var compilerOptions = new CompilerOptions.fromArguments(argResults); - var compiler = - new ModuleCompiler(new AnalyzerOptions.fromArguments(argResults)); + var analyzerOptions = new AnalyzerOptions.fromArguments(argResults); + if (argResults['persistent_worker']) { + new _CompilerWorker(analyzerOptions, this).run(); + } else { + compile( + new ModuleCompiler(analyzerOptions), + new CompilerOptions.fromArguments(argResults), + argResults['out'], + argResults.rest); + } + } - var outPath = argResults['out']; + void compile(ModuleCompiler compiler, CompilerOptions compilerOptions, + String outPath, List<String> extraArgs, + {void forEachError(String error): print}) { if (outPath == null) { usageException('Please include the output file location. For example:\n' ' -o PATH/TO/OUTPUT_FILE.js'); } - var unit = new BuildUnit(path.basenameWithoutExtension(outPath), - argResults.rest, _moduleForLibrary); + var unit = new BuildUnit( + path.basenameWithoutExtension(outPath), extraArgs, _moduleForLibrary); JSModuleFile module = compiler.compile(unit, compilerOptions); - module.errors.forEach(print); + module.errors.forEach(forEachError); if (!module.isValid) throw new CompileErrorException(); @@ -68,7 +83,81 @@ } } +/// Handles [error] in a uniform fashion. Returns the proper exit code and calls +/// [messageHandler] with messages. +int handleError(dynamic error, dynamic stackTrace, List<String> args, + {void messageHandler(Object message): print}) { + if (error is UsageException) { + // Incorrect usage, input file not found, etc. + messageHandler(error); + return 64; + } else if (error is CompileErrorException) { + // Code has error(s) and failed to compile. + messageHandler(error); + return 1; + } else { + // Anything else is likely a compiler bug. + // + // --unsafe-force-compile is a bit of a grey area, but it's nice not to + // crash while compiling + // (of course, output code may crash, if it had errors). + // + messageHandler(""); + messageHandler("We're sorry, you've found a bug in our compiler."); + messageHandler("You can report this bug at:"); + messageHandler(" https://github.com/dart-lang/dev_compiler/issues"); + messageHandler(""); + messageHandler( + "Please include the information below in your report, along with"); + messageHandler( + "any other information that may help us track it down. Thanks!"); + messageHandler(""); + messageHandler(" dartdevc arguments: " + args.join(' ')); + messageHandler(" dart --version: ${Platform.version}"); + messageHandler(""); + messageHandler("```"); + messageHandler(error); + messageHandler(stackTrace); + messageHandler("```"); + return 1; + } +} + /// Thrown when the input source code has errors. class CompileErrorException implements Exception { toString() => '\nPlease fix all errors before compiling (warnings are okay).'; } + +/// Runs the compiler worker loop. +class _CompilerWorker extends SyncWorkerLoop { + final AnalyzerOptions analyzerOptions; + final CompileCommand compileCommand; + + _CompilerWorker(this.analyzerOptions, this.compileCommand) : super(); + + WorkResponse performRequest(WorkRequest request) { + var arguments = new List.from(request.arguments) + ..addAll(compileCommand.argResults.rest); + var argResults = compileCommand.argParser.parse(arguments); + + var output = new StringBuffer(); + try { + compileCommand.compile( + new ModuleCompiler(analyzerOptions), + new CompilerOptions.fromArguments(argResults), + argResults['out'], + argResults.rest, + forEachError: output.writeln); + return new WorkResponse() + ..exitCode = EXIT_CODE_OK + ..output = output.toString(); + } catch (e, s) { + var response = new WorkResponse(); + var output = new StringBuffer(); + response.exitCode = + handleError(e, s, request.arguments, messageHandler: output.writeln); + response.output = output.toString(); + return response; + } + } +}
diff --git a/pubspec.yaml b/pubspec.yaml index e9e9c1d..07fc732 100644 --- a/pubspec.yaml +++ b/pubspec.yaml
@@ -10,6 +10,7 @@ dependencies: analyzer: ^0.27.3 args: ^0.13.0 + bazel_worker: ^0.1.0 cli_util: ^0.0.1 func: ^0.1.0 html: ^0.12.0
diff --git a/test/all_tests.dart b/test/all_tests.dart index a74ff9d..71d48ac 100644 --- a/test/all_tests.dart +++ b/test/all_tests.dart
@@ -11,6 +11,7 @@ import 'closure/closure_type_test.dart' as closure_type_test; import 'codegen_test.dart' as codegen_test; import 'js/builder_test.dart' as builder_test; +import 'worker/worker_test.dart' as worker_test; void main() { group('codegen', () => codegen_test.main([])); @@ -19,4 +20,5 @@ closure_type_test.main(); }); group('js', builder_test.main); + group('worker', worker_test.main); }
diff --git a/test/command/hello.dart b/test/command/hello.dart deleted file mode 100644 index fc503dc..0000000 --- a/test/command/hello.dart +++ /dev/null
@@ -1,3 +0,0 @@ -// This is compiled by ./tool/command_test.sh -import 'hello_lib.dart'; -main() => print(greeting);
diff --git a/test/command/hello.js b/test/command/hello.js deleted file mode 100644 index f9d2dd4..0000000 --- a/test/command/hello.js +++ /dev/null
@@ -1,19 +0,0 @@ -dart_library.library('hello', null, /* Imports */[ - 'dart_sdk', - 'hello_lib' -], function(exports, dart_sdk, hello_lib) { - 'use strict'; - const core = dart_sdk.core; - const dart = dart_sdk.dart; - const dartx = dart_sdk.dartx; - const hello_lib$ = hello_lib.hello_lib; - const hello = Object.create(null); - hello.main = function() { - return core.print(hello_lib$.greeting); - }; - dart.fn(hello.main); - // Exports: - exports.hello = hello; -}); - -//# sourceMappingURL=hello.js.map
diff --git a/test/command/hello.js.map b/test/command/hello.js.map deleted file mode 100644 index 35a47b8..0000000 --- a/test/command/hello.js.map +++ /dev/null
@@ -1 +0,0 @@ -{"version":3,"sourceRoot":"","sources":["hello.dart"],"names":["print","greeting"],"mappings":";;;;;;;;;;AAEA,eAAI,WAAG;UAAG,AAAAA,WAAK,CAACC,mBAAQ,CAAC;GACzB,AAAA;AAAA","file":"hello.js"} \ No newline at end of file
diff --git a/test/command/hello.sum b/test/command/hello.sum deleted file mode 100644 index 13fa193..0000000 --- a/test/command/hello.sum +++ /dev/null Binary files differ
diff --git a/test/command/hello_lib.dart b/test/command/hello_lib.dart deleted file mode 100644 index 3251595..0000000 --- a/test/command/hello_lib.dart +++ /dev/null
@@ -1 +0,0 @@ -String greeting = 'hello!';
diff --git a/test/command/hello_lib.js b/test/command/hello_lib.js deleted file mode 100644 index 9bd68b7..0000000 --- a/test/command/hello_lib.js +++ /dev/null
@@ -1,14 +0,0 @@ -dart_library.library('hello_lib', null, /* Imports */[ - 'dart_sdk' -], function(exports, dart_sdk) { - 'use strict'; - const core = dart_sdk.core; - const dart = dart_sdk.dart; - const dartx = dart_sdk.dartx; - const hello_lib = Object.create(null); - hello_lib.greeting = 'hello!'; - // Exports: - exports.hello_lib = hello_lib; -}); - -//# sourceMappingURL=hello_lib.js.map
diff --git a/test/command/hello_lib.js.map b/test/command/hello_lib.js.map deleted file mode 100644 index 7d592bc..0000000 --- a/test/command/hello_lib.js.map +++ /dev/null
@@ -1 +0,0 @@ -{"version":3,"sourceRoot":"","sources":["hello_lib.dart"],"names":[],"mappings":";;;;;;;;AAAO,uBAAW,QAAQ;AAAA","file":"hello_lib.js"} \ No newline at end of file
diff --git a/test/command/hello_lib.sum b/test/command/hello_lib.sum deleted file mode 100644 index 2bfe850..0000000 --- a/test/command/hello_lib.sum +++ /dev/null Binary files differ
diff --git a/test/worker/worker_test.dart b/test/worker/worker_test.dart new file mode 100644 index 0000000..bb10445 --- /dev/null +++ b/test/worker/worker_test.dart
@@ -0,0 +1,150 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:bazel_worker/bazel_worker.dart'; +// TODO(jakemac): Remove once this is a part of the testing library. +import 'package:bazel_worker/src/async_message_grouper.dart'; +import 'package:bazel_worker/testing.dart'; +import 'package:test/test.dart'; + +main() { + group('Hello World', () { + final inputDartFile = new File('test/worker/hello_world.dart').absolute; + final outputJsFile = new File('test/worker/hello_world.js').absolute; + final executableArgs = [ + 'bin/dartdevc.dart', + 'compile', + ]; + final compilerArgs = [ + '--no-source-map', + '--no-summarize', + '-o', + outputJsFile.path, + inputDartFile.path, + ]; + + setUp(() { + inputDartFile.createSync(); + inputDartFile.writeAsStringSync('main() => print("hello world");'); + }); + + tearDown(() { + if (inputDartFile.existsSync()) inputDartFile.deleteSync(); + if (outputJsFile.existsSync()) outputJsFile.deleteSync(); + }); + + test('can compile in worker mode', () async { + var args = new List.from(executableArgs)..add('--persistent_worker'); + var process = await Process.start('dart', args); + var messageGrouper = new AsyncMessageGrouper(process.stdout); + + var request = new WorkRequest(); + request.arguments.addAll(compilerArgs); + process.stdin.add(protoToDelimitedBuffer(request)); + + var response = await _readResponse(messageGrouper); + expect(response.exitCode, EXIT_CODE_OK, reason: response.output); + expect(response.output, isEmpty); + + expect(outputJsFile.existsSync(), isTrue); + expect(outputJsFile.readAsStringSync(), contains('hello world')); + + /// Now update hello_world.dart and send another [WorkRequest]. + inputDartFile.writeAsStringSync('main() => print("goodbye world");'); + process.stdin.add(protoToDelimitedBuffer(request)); + + response = await _readResponse(messageGrouper); + expect(response.exitCode, EXIT_CODE_OK, reason: response.output); + expect(response.output, isEmpty); + expect(outputJsFile.readAsStringSync(), contains('goodbye world')); + + process.kill(); + + // TODO(jakemac): This shouldn't be necessary, but it is for the process + // to exit properly. + expect(await messageGrouper.next, isNull); + }); + + test('can compile in basic mode', () { + var args = new List.from(executableArgs)..addAll(compilerArgs); + var result = Process.runSync('dart', args); + + expect(result.exitCode, EXIT_CODE_OK); + expect(result.stdout, isEmpty); + expect(result.stderr, isEmpty); + expect(outputJsFile.existsSync(), isTrue); + }); + }); + + group('Hello World with Summaries', () { + final greetingDart = new File('test/worker/greeting.dart').absolute; + final helloDart = new File('test/worker/hello.dart').absolute; + + final greetingJS = new File('test/worker/greeting.js').absolute; + final greetingSummary = new File('test/worker/greeting.sum').absolute; + final helloJS = new File('test/worker/hello_world.js').absolute; + + setUp(() { + greetingDart.writeAsStringSync('String greeting = "hello";'); + helloDart.writeAsStringSync( + 'import "greeting.dart";' + 'main() => print(greeting);'); + }); + + tearDown(() { + if (greetingDart.existsSync()) greetingDart.deleteSync(); + if (helloDart.existsSync()) helloDart.deleteSync(); + if (greetingJS.existsSync()) greetingJS.deleteSync(); + if (greetingSummary.existsSync()) greetingSummary.deleteSync(); + if (helloJS.existsSync()) helloJS.deleteSync(); + }); + + test('can compile in basic mode', () { + var result = Process.runSync('dart', [ + 'bin/dartdevc.dart', + 'compile', + '--no-source-map', + '-o', + greetingJS.path, + greetingDart.path, + ]); + expect(result.exitCode, EXIT_CODE_OK); + expect(result.stdout, isEmpty); + expect(result.stderr, isEmpty); + expect(greetingJS.existsSync(), isTrue); + expect(greetingSummary.existsSync(), isTrue); + + result = Process.runSync('dart', [ + 'bin/dartdevc.dart', + 'compile', + '--no-source-map', + '--no-summarize', + '-s', + greetingSummary.path, + '-o', + helloJS.path, + helloDart.path, + ]); + expect(result.exitCode, EXIT_CODE_OK); + expect(result.stdout, isEmpty); + expect(result.stderr, isEmpty); + expect(helloJS.existsSync(), isTrue); + }); + }); +} + +Future<WorkResponse> _readResponse(MessageGrouper messageGrouper) async { + var buffer = await messageGrouper.next; + try { + return new WorkResponse.fromBuffer(buffer); + } catch (_) { + throw 'Failed to parse response: \n' + 'bytes: $buffer\n' + 'String: ${new String.fromCharCodes(buffer)}\n'; + } +}
diff --git a/tool/command_test.sh b/tool/command_test.sh deleted file mode 100755 index 6332cab..0000000 --- a/tool/command_test.sh +++ /dev/null
@@ -1,26 +0,0 @@ -#!/bin/bash - -set -e # bail on error - -# Some tests require being run from the package root -# switch to the root directory of dev_compiler -cd $( dirname "${BASH_SOURCE[0]}" )/.. - - -dartanalyzer --strong --build-mode \ - --build-summary-output=test/command/hello_lib.sum \ - --build-summary-only \ - "file:///Users/jmesserly/src/dev_compiler/test/command/hello_lib.dart|/Users/jmesserly/src/dev_compiler/test/command/hello_lib.dart" - -#dartanalyzer --strong --build-mode \ -# --build-summary-input=test/command/hello_lib.sum \ -# "file:///Users/jmesserly/src/dev_compiler/test/command/hello.dart|/Users/jmesserly/src/dev_compiler/test/command/hello.dart" - - - -#dart -c ./bin/dartdevc.dart compile \ -# -o test/command/hello_lib.js test/command/hello_lib.dart - -dart -c ./bin/dartdevc.dart compile \ - -o test/command/hello.js \ - -s test/command/hello_lib.sum test/command/hello.dart
diff --git a/tool/test.sh b/tool/test.sh index 514f6ba..be87aa7 100755 --- a/tool/test.sh +++ b/tool/test.sh
@@ -28,8 +28,6 @@ unset COVERALLS_TOKEN pub run test:test test/all_tests.dart || fail -./tool/command_test.sh || fail - { fc=`find test -name "*.dart" |\ xargs grep "/\*\S* should be \S*\*/" | wc -l`