// 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 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 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 = >{}; 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 _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) { /**/ } } }