| // Copyright (c) 2021, 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. | |
| /// Command line entry point for Dart Development Compiler (known as ddc, | |
| /// dartdevc, dev compiler), used to compile a collection of dart libraries into | |
| /// a single JS module. | |
| library; | |
| import 'dart:async'; | |
| import 'dart:convert'; | |
| import 'dart:developer' show Service, debugger; | |
| import 'dart:io'; | |
| import 'dart:isolate'; | |
| import 'package:bazel_worker/bazel_worker.dart'; | |
| import 'package:kernel/ast.dart' show clearDummyTreeNodesParentPointer; | |
| import 'package:shell_arg_splitter/shell_arg_splitter.dart'; | |
| import 'src/command/arguments.dart'; | |
| import 'src/command/command.dart'; | |
| import 'src/command/result.dart'; | |
| import 'src/kernel/expression_compiler_worker.dart'; | |
| /// The internal entry point for the Dart Dev Compiler. | |
| /// | |
| /// [sendPort] may be passed in when started in an isolate. If provided, it is | |
| /// used for bazel worker communication instead of stdin/stdout. | |
| Future internalMain(List<String> args, [SendPort? sendPort]) async { | |
| // Always returns a new modifiable list. | |
| var parsedArgs = ParsedArguments.from(args); | |
| if (parsedArgs.isWorker) { | |
| var workerConnection = sendPort == null | |
| ? StdAsyncWorkerConnection() | |
| : SendPortAsyncWorkerConnection(sendPort); | |
| await _CompilerWorker(parsedArgs, workerConnection).run(); | |
| } else if (parsedArgs.isBatch) { | |
| var batch = _BatchHelper(); | |
| await batch._runBatch(parsedArgs); | |
| } else if (parsedArgs.isExpressionCompiler) { | |
| await ExpressionCompilerWorker.createAndStart( | |
| parsedArgs.rest, | |
| sendPort: sendPort, | |
| ); | |
| } else { | |
| var result = await compile(parsedArgs); | |
| exitCode = result.exitCode; | |
| } | |
| } | |
| /// Runs the compiler worker loop. | |
| class _CompilerWorker extends AsyncWorkerLoop { | |
| /// The original args supplied to the executable. | |
| final ParsedArguments _startupArgs; | |
| _CompilerWorker(this._startupArgs, AsyncWorkerConnection workerConnection) | |
| : super(connection: workerConnection); | |
| /// Keeps track of our last compilation result so it can potentially be | |
| /// re-used in a worker. | |
| CompilerResult? lastResult; | |
| /// Performs each individual work request. | |
| @override | |
| Future<WorkResponse> performRequest(WorkRequest request) async { | |
| var args = _startupArgs.merge(request.arguments); | |
| var output = StringBuffer(); | |
| var context = args.reuseResult ? lastResult : null; | |
| /// Build a map of uris to digests. | |
| final inputDigests = <Uri, List<int>>{}; | |
| for (var input in request.inputs) { | |
| inputDigests[sourcePathToUri(input.path)] = input.digest; | |
| } | |
| lastResult = await runZoned( | |
| () => compile( | |
| args, | |
| compilerState: context?.kernelState, | |
| inputDigests: inputDigests, | |
| ), | |
| zoneSpecification: ZoneSpecification( | |
| print: (self, parent, zone, message) { | |
| output.writeln(message.toString()); | |
| }, | |
| ), | |
| ); | |
| return WorkResponse() | |
| ..exitCode = lastResult!.success ? 0 : 1 | |
| ..output = output.toString(); | |
| } | |
| } | |
| class _BatchHelper { | |
| Stopwatch watch = Stopwatch(); | |
| CompilerResult? result; | |
| int totalTests = 0; | |
| int failedTests = 0; | |
| /// One can go into "leak test mode" by doing `export DDC_LEAK_TEST="true"` | |
| /// on the terminal (I think `set DDC_LEAK_TEST="true"` on Windows). | |
| /// Then one could run test.py, say | |
| /// ``` | |
| /// python3 tools/test.py -t10000 -c ddc --nnbd weak -m release -r none \ | |
| /// --enable-asserts --no-use-sdk -j 1 co19/LanguageFeatures/ | |
| /// ``` | |
| /// and attach the leak tester via | |
| /// ``` | |
| /// out/ReleaseX64/dart \ | |
| /// pkg/front_end/test/vm_service_for_leak_detection.dart --dart-leak-test | |
| /// ``` | |
| final bool leakTesting = Platform.environment['DDC_LEAK_TEST'] == 'true'; | |
| /// Runs DDC in Kernel batch mode for test.dart. | |
| Future _runBatch(ParsedArguments batchArgs) async { | |
| _workaroundForLeakingBug(); | |
| if (leakTesting) { | |
| var services = await Service.controlWebServer( | |
| enable: true, | |
| silenceOutput: true, | |
| ); | |
| File.fromUri(Directory.systemTemp.uri.resolve('./dart_leak_test_uri')) | |
| .writeAsStringSync(services.serverUri!.toString()); | |
| } | |
| watch.start(); | |
| print('>>> BATCH START'); | |
| String? line; | |
| while ((line = stdin.readLineSync(encoding: utf8))?.isNotEmpty == true) { | |
| await _doIteration(batchArgs, line!); | |
| _iterationDone(); | |
| } | |
| var time = watch.elapsedMilliseconds; | |
| print('>>> BATCH END (${totalTests - failedTests})/$totalTests ${time}ms'); | |
| } | |
| Future<void> _doIteration(ParsedArguments batchArgs, String line) async { | |
| totalTests++; | |
| var args = batchArgs.merge(splitLine(line, windows: Platform.isWindows)); | |
| String outcome; | |
| try { | |
| result = await compile(args, compilerState: result?.kernelState); | |
| outcome = result!.success ? 'PASS' : (result!.crashed ? 'CRASH' : 'FAIL'); | |
| } catch (e, s) { | |
| // Clear the cache. It might have been left in a weird state. | |
| result = null; | |
| outcome = 'CRASH'; | |
| print('Unhandled exception:'); | |
| print(e); | |
| print(s); | |
| } | |
| stderr.writeln('>>> EOF STDERR'); | |
| print('>>> TEST $outcome ${watch.elapsedMilliseconds}ms'); | |
| } | |
| void _iterationDone() { | |
| if (leakTesting) { | |
| // Dummy tree nodes can (currently) leak though the parent pointer. | |
| // To avoid that (here) (for leak testing) we'll null them out. | |
| clearDummyTreeNodesParentPointer(); | |
| print('Will now wait'); | |
| debugger(); | |
| } | |
| } | |
| /// Workaround for https://github.com/dart-lang/sdk/issues/51317. | |
| void _workaroundForLeakingBug() { | |
| try { | |
| stdin.echoMode; | |
| } catch (e) { | |
| /**/ | |
| } | |
| try { | |
| stdout.writeln(); | |
| } catch (e) { | |
| /**/ | |
| } | |
| try { | |
| stderr.writeln(); | |
| } catch (e) { | |
| /**/ | |
| } | |
| } | |
| } |