| #!/usr/bin/env dart |
| // Copyright (c) 2018, 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. |
| |
| // @dart = 2.9 |
| |
| // |
| // Compiles code with DDC and runs the resulting code with either node or |
| // chrome. |
| // |
| // The first script supplied should be the one with `main()`. |
| // |
| // Saves the output in the same directory as the sources for convenient |
| // inspection, modification or rerunning the code. |
| |
| import 'dart:io'; |
| |
| import 'package:args/args.dart' show ArgParser; |
| import 'package:path/path.dart' as p; |
| import 'package:dev_compiler/src/compiler/module_builder.dart' |
| as module_builder; |
| |
| enum NullSafety { strict, weak, disabled } |
| |
| void main(List<String> args) async { |
| void printUsage() { |
| print('Usage: ddb [options] <dart-script-file>\n'); |
| print('Compiles <dart-script-file> with the dev_compiler and runs it on a ' |
| 'JS platform.\n'); |
| } |
| |
| // Parse flags. |
| var parser = ArgParser(usageLineLength: 80) |
| ..addOption('binary', abbr: 'b', help: 'Runtime binary path.') |
| ..addOption('compile-vm-options', |
| help: 'DART_VM_OPTIONS for the compilation VM.') |
| ..addFlag('debug', |
| abbr: 'd', |
| help: 'Use current source instead of built SDK.', |
| defaultsTo: false) |
| ..addMultiOption('enable-experiment', |
| help: 'Run with specified experiments enabled.') |
| ..addFlag('help', abbr: 'h', help: 'Display this message.') |
| ..addOption('mode', |
| help: 'Option to (compile|run|all). Default is all (compile and run).', |
| allowed: ['compile', 'run', 'all'], |
| defaultsTo: 'all') |
| ..addFlag('sound-null-safety', |
| help: 'Compile for sound null safety at runtime. Passed through to the ' |
| 'DDC binary. Defaults to false.', |
| defaultsTo: false) |
| ..addFlag('emit-debug-symbols', |
| help: 'Pass through flag for DDC, emits debug symbols file along with ' |
| 'the compiled module.', |
| defaultsTo: false) |
| ..addFlag('null-assertions', |
| help: 'Run with assertions that values passed to non-nullable method ' |
| 'parameters are not null.', |
| defaultsTo: false) |
| ..addFlag('native-null-assertions', |
| help: 'Run with assertions on non-nullable values returned from native ' |
| 'APIs.', |
| defaultsTo: true, |
| negatable: true) |
| ..addFlag('weak-null-safety-errors', |
| help: 'Treat weak null safety warnings as errors.', defaultsTo: false) |
| ..addFlag('observe', |
| help: |
| 'Run the compiler in the Dart VM with --observe. Implies --debug.', |
| defaultsTo: false) |
| ..addOption('out', help: 'Output file.') |
| ..addOption('packages', help: 'Where to find a package spec file.') |
| ..addOption('port', |
| abbr: 'p', |
| help: 'Run with the corresponding chrome/V8 debugging port open.', |
| defaultsTo: '9222') |
| ..addOption('runtime', |
| abbr: 'r', |
| help: 'Platform to run on (node|d8|chrome). Default is node.', |
| allowed: ['node', 'd8', 'chrome'], |
| defaultsTo: 'node') |
| ..addFlag('summarize-text', |
| help: 'Emit API summary in a .js.txt file.', defaultsTo: false) |
| ..addMultiOption('summary', |
| abbr: 's', |
| help: 'summary file(s) of imported libraries, optionally with module ' |
| 'import path: -s path.sum=js/import/path') |
| ..addFlag('verbose', |
| abbr: 'v', |
| help: 'Echos the commands, arguments, and environment this script is ' |
| 'running.', |
| defaultsTo: false) |
| ..addOption('vm-service-port', |
| help: 'Specify the observatory port. Implied --observe.'); |
| |
| var options = parser.parse(args); |
| if (options['help'] as bool) { |
| printUsage(); |
| print('Available options:'); |
| print(parser.usage); |
| exit(0); |
| } |
| if (options.rest.length != 1) { |
| print('Dart script file required.\n'); |
| printUsage(); |
| exit(1); |
| } |
| var debug = options['debug'] as bool || |
| options['observe'] as bool || |
| options.wasParsed('vm-service-port'); |
| var summarizeText = options['summarize-text'] as bool; |
| var binary = options['binary'] as String; |
| var experiments = options['enable-experiment'] as List; |
| var summaries = options['summary'] as List; |
| var port = int.parse(options['port'] as String); |
| var mode = options['mode'] as String; |
| var compile = mode == 'compile' || mode == 'all'; |
| var run = mode == 'run' || mode == 'all'; |
| var verbose = options['verbose'] as bool; |
| var soundNullSafety = options['sound-null-safety'] as bool; |
| var emitDebugSymbols = options['emit-debug-symbols'] as bool; |
| var nonNullAsserts = options['null-assertions'] as bool; |
| var nativeNonNullAsserts = options['native-null-assertions'] as bool; |
| var weakNullSafetyErrors = options['weak-null-safety-errors'] as bool; |
| var entry = p.canonicalize(options.rest.first); |
| var out = (options['out'] as String) ?? p.setExtension(entry, '.js'); |
| var libRoot = p.dirname(entry); |
| var basename = p.basenameWithoutExtension(entry); |
| var libname = |
| module_builder.pathToJSIdentifier(p.relative(p.withoutExtension(entry))); |
| |
| // By default (no `-d`), we use the `dartdevc` binary on the user's path to |
| // compute the SDK we use for execution. I.e., we assume that `dart` is |
| // under `$DART_SDK/bin/dart` and use that to find `dartdevc` and related |
| // artifacts. In this mode, this script can run against any installed SDK. |
| // If you want to run against a freshly built SDK, that must be first on |
| // your path. |
| var dartBinary = Platform.resolvedExecutable; |
| var dartSdk = p.dirname(p.dirname(dartBinary)); |
| |
| // In debug mode (`-d`), we run from the `pkg/dev_compiler` sources. We |
| // determine the location via this actual script (i.e., `-d` assumes |
| // this script remains under to `tool` sub-directory). |
| var toolPath = |
| Platform.script.normalizePath().toFilePath(windows: Platform.isWindows); |
| var ddcPath = p.dirname(p.dirname(toolPath)); |
| var dartCheckoutPath = p.dirname(p.dirname(ddcPath)); |
| |
| /// Runs the [command] with [args] in [environment]. |
| /// |
| /// Will echo the commands to the console before running them when running in |
| /// `verbose` mode. |
| Future<Process> startProcess(String name, String command, List<String> args, |
| [Map<String, String> environment = const {}]) { |
| if (verbose) { |
| print('Running $name:\n$command ${args.join(' ')}\n'); |
| if (environment.isNotEmpty) { |
| var environmentVariables = |
| environment.entries.map((e) => '${e.key}: ${e.value}').join('\n'); |
| print('With Environment:\n$environmentVariables\n'); |
| } |
| } |
| return Process.start(command, args, |
| mode: ProcessStartMode.inheritStdio, environment: environment); |
| } |
| |
| Future<void> runDdc(String command, List<String> args) async { |
| if (debug) { |
| // Use unbuilt script. This only works from a source checkout. |
| var vmServicePort = options.wasParsed('vm-service-port') |
| ? '=${options['vm-service-port']}' |
| : ''; |
| var observe = |
| options.wasParsed('vm-service-port') || options['observe'] as bool; |
| args.insertAll(0, [ |
| if (observe) ...[ |
| '--enable-vm-service$vmServicePort', |
| '--pause-isolates-on-start', |
| ], |
| '--enable-asserts', |
| p.join(ddcPath, 'bin', '$command.dart') |
| ]); |
| command = dartBinary; |
| } else { |
| // Use built snapshot. |
| command = p.join(dartSdk, 'bin', command); |
| } |
| var process = await startProcess('DDC', command, args, <String, String>{ |
| if (options['compile-vm-options'] != null) |
| 'DART_VM_OPTIONS': options['compile-vm-options'] as String |
| }); |
| if (await process.exitCode != 0) exit(await process.exitCode); |
| } |
| |
| String mod; |
| bool chrome = false; |
| bool node = false; |
| bool d8 = false; |
| switch (options['runtime'] as String) { |
| case 'node': |
| node = true; |
| mod = 'common'; |
| break; |
| case 'd8': |
| d8 = true; |
| mod = 'legacy'; |
| break; |
| case 'chrome': |
| chrome = true; |
| mod = 'amd'; |
| break; |
| } |
| |
| var sdkRoot = p.dirname(p.dirname(ddcPath)); |
| var buildDir = |
| p.join(sdkRoot, Platform.isMacOS ? 'xcodebuild' : 'out', 'ReleaseX64'); |
| var requirePath = p.join(sdkRoot, 'third_party', 'requirejs'); |
| var sdkOutlineDill = p.join(buildDir, |
| soundNullSafety ? 'ddc_outline_sound.dill' : 'ddc_outline.dill'); |
| var suffix = soundNullSafety ? p.join('sound', mod) : p.join('kernel', mod); |
| var sdkJsPath = p.join(buildDir, 'gen', 'utils', 'dartdevc', suffix); |
| |
| // Print an initial empty line to separate the invocation from the output. |
| if (verbose) { |
| print(''); |
| } |
| |
| if (compile) { |
| var ddcArgs = [ |
| if (summarizeText) '--summarize-text', |
| '--modules=$mod', |
| '--dart-sdk-summary=$sdkOutlineDill', |
| for (var summary in summaries) '--summary=$summary', |
| for (var experiment in experiments) '--enable-experiment=$experiment', |
| if (soundNullSafety) '--sound-null-safety', |
| if (options['packages'] != null) '--packages=${options['packages']}', |
| if (emitDebugSymbols) '--emit-debug-symbols', |
| '-o', |
| out, |
| entry |
| ]; |
| await runDdc('dartdevc', ddcArgs); |
| } |
| |
| if (run) { |
| if (chrome) { |
| String chromeBinary; |
| if (binary != null) { |
| chromeBinary = binary; |
| } else if (Platform.isWindows) { |
| chromeBinary = |
| 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'; |
| } else if (Platform.isMacOS) { |
| chromeBinary = |
| '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'; |
| } else { |
| // Assume Linux |
| chromeBinary = 'google-chrome'; |
| } |
| |
| var html = ''' |
| <script src='$requirePath/require.js'></script> |
| <script> |
| require.config({ |
| paths: { |
| 'dart_sdk': '$sdkJsPath/dart_sdk', |
| }, |
| waitSeconds: 15 |
| }); |
| require(['dart_sdk', '$basename'], |
| function(sdk, app) { |
| 'use strict'; |
| |
| sdk.dart.weakNullSafetyWarnings( |
| !($weakNullSafetyErrors || $soundNullSafety)); |
| sdk.dart.weakNullSafetyErrors($weakNullSafetyErrors); |
| sdk.dart.nonNullAsserts($nonNullAsserts); |
| sdk.dart.nativeNonNullAsserts($nativeNonNullAsserts); |
| sdk._debugger.registerDevtoolsFormatter(); |
| app.$libname.main([]); |
| }); |
| </script> |
| '''; |
| var htmlFile = p.setExtension(out, '.html'); |
| File(htmlFile).writeAsStringSync(html); |
| var tmp = p.join(Directory.systemTemp.path, 'ddc'); |
| |
| var process = await startProcess('Chrome', chromeBinary, [ |
| '--auto-open-devtools-for-tabs', |
| '--allow-file-access-from-files', |
| '--remote-debugging-port=$port', |
| '--user-data-dir=$tmp', |
| htmlFile |
| ]); |
| if (await process.exitCode != 0) exit(await process.exitCode); |
| } else if (node) { |
| var nodePath = '$sdkJsPath:$libRoot'; |
| var runjs = ''' |
| let source_maps; |
| try { |
| source_maps = require('source-map-support'); |
| source_maps.install(); |
| } catch(e) { |
| } |
| let sdk = require(\"dart_sdk\"); |
| // Create a self reference for JS interop tests that set fields on self. |
| sdk.dart.global.self = sdk.dart.global; |
| let main = require(\"./$basename\").$libname.main; |
| try { |
| sdk.dart.weakNullSafetyWarnings( |
| !($weakNullSafetyErrors || $soundNullSafety)); |
| sdk.dart.weakNullSafetyErrors($weakNullSafetyErrors); |
| sdk.dart.nonNullAsserts($nonNullAsserts); |
| sdk.dart.nativeNonNullAsserts($nativeNonNullAsserts); |
| sdk._isolate_helper.startRootIsolate(main, []); |
| } catch(e) { |
| if (!source_maps) { |
| console.log('For Dart source maps: npm install source-map-support'); |
| } |
| sdk.core.print(sdk.dart.stackTrace(e)); |
| process.exit(1); |
| } |
| '''; |
| var nodeFile = p.setExtension(out, '.run.js'); |
| File(nodeFile).writeAsStringSync(runjs); |
| var nodeBinary = binary ?? 'node'; |
| var process = await startProcess('Node', nodeBinary, |
| ['--inspect=localhost:$port', nodeFile], {'NODE_PATH': nodePath}); |
| if (await process.exitCode != 0) exit(await process.exitCode); |
| } else if (d8) { |
| var runjs = ''' |
| load("$ddcPath/lib/js/legacy/dart_library.js"); |
| load("$sdkJsPath/dart_sdk.js"); |
| load("$out"); |
| |
| let sdk = dart_library.import('dart_sdk'); |
| // Create a self reference for JS interop tests that set fields on self. |
| sdk.dart.global.self = sdk.dart.global; |
| sdk.dart.weakNullSafetyWarnings( |
| !($weakNullSafetyErrors || $soundNullSafety)); |
| sdk.dart.weakNullSafetyErrors($weakNullSafetyErrors); |
| sdk.dart.nonNullAsserts($nonNullAsserts); |
| sdk.dart.nativeNonNullAsserts($nativeNonNullAsserts); |
| |
| // Invoke main through the d8 preamble to ensure the code is running |
| // within the fake event loop. |
| self.dartMainRunner(function () { |
| dart_library.start("$basename", "$libname"); |
| }); |
| '''; |
| var dart2jsD8Preamble = |
| '$sdkRoot/sdk/lib/_internal/js_runtime/lib/preambles/d8.js'; |
| var d8File = p.setExtension(out, '.d8.js'); |
| File(d8File).writeAsStringSync(runjs); |
| var d8Binary = binary ?? p.join(dartCheckoutPath, _d8executable); |
| var process = |
| await startProcess('D8', d8Binary, [dart2jsD8Preamble, d8File]); |
| if (await process.exitCode != 0) exit(await process.exitCode); |
| } |
| } |
| } |
| |
| String get _d8executable { |
| if (Platform.isWindows) { |
| return p.join('third_party', 'd8', 'windows', 'd8.exe'); |
| } else if (Platform.isLinux) { |
| return p.join('third_party', 'd8', 'linux', 'd8'); |
| } else if (Platform.isMacOS) { |
| return p.join('third_party', 'd8', 'macos', 'd8'); |
| } |
| throw UnsupportedError('Unsupported platform.'); |
| } |