#!/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. // // 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 path; // TODO(vsm): Remove this once we stop ignoring. It's off here, but // configurable for manual testing. const ignoreWhitelistedErrors = false; void main(List args) async { void printUsage() { print('Usage: ddb [options] \n'); print('Compiles with the dev_compiler and runs it on a ' 'JS platform.\n'); } // Parse flags. var parser = new ArgParser() ..addFlag('help', abbr: 'h', help: 'Display this message.') ..addFlag('kernel', abbr: 'k', help: 'Compile with the new kernel-based front end.') ..addMultiOption('summary', abbr: 's', help: 'summary file(s) of imported libraries, optionally\n' 'with module import path: -s path.sum=js/import/path') ..addFlag('debug', abbr: 'd', help: 'Use current source instead of built SDK.', defaultsTo: false) ..addOption('runtime', abbr: 'r', help: 'Platform to run on (node|d8|chrome). Default is node.', allowed: ['node', 'd8', 'chrome'], defaultsTo: 'node') ..addOption('port', abbr: 'p', help: 'Run with the corresponding chrome/V8 debugging port open.', defaultsTo: '9222') ..addMultiOption('enable-experiment', help: 'Run with specified experiments enabled.') ..addOption('binary', abbr: 'b', help: 'Runtime binary path.'); var options = parser.parse(args); if (options['help']) { printUsage(); print('Available options:'); print(parser.usage); exit(0); } if (options.rest.length != 1) { print('Dart script file required.\n'); printUsage(); exit(1); } var entry = options.rest.first; var libRoot = path.dirname(entry); var basename = path.basenameWithoutExtension(entry); var debug = options['debug'] as bool; var kernel = options['kernel'] 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); // 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 = path.dirname(path.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(); var ddcPath = path.dirname(path.dirname(toolPath)); var dartCheckoutPath = path.dirname(path.dirname(ddcPath)); ProcessResult runDdc(String command, List args) { if (debug) { // Use unbuilt script. This only works from a source checkout. args.insertAll(0, ['--enable-asserts', path.join(ddcPath, 'bin', '${command}.dart')]); command = dartBinary; } else { // Use built snapshot. command = path.join(dartSdk, 'bin', command); } return Process.runSync(command, args); } /// Writes stdout and stderr from [result] to this process. /// /// Will exit with the exit code from [result] when it's not zero. void echoResult(ProcessResult result) async { stdout.write(result.stdout); await stdout.flush(); stderr.write(result.stderr); await stderr.flush(); if (result.exitCode != 0) exit(result.exitCode); } String mod; bool chrome = false; bool node = false; bool d8 = false; switch (options['runtime']) { case 'node': node = true; mod = 'common'; break; case 'd8': d8 = true; mod = 'es6'; break; case 'chrome': chrome = true; mod = 'amd'; break; } String sdkJsPath; String requirePath; String ddcSdk; if (debug) { var sdkRoot = path.dirname(path.dirname(ddcPath)); var buildDir = path.join(sdkRoot, Platform.isMacOS ? 'xcodebuild' : 'out'); sdkJsPath = path.join(buildDir, 'ReleaseX64', 'gen', 'utils', 'dartdevc', kernel ? 'kernel' : 'js', mod); requirePath = path.join(sdkRoot, 'third_party', 'requirejs'); ddcSdk = path.join(buildDir, 'ReleaseX64', 'gen', 'utils', 'dartdevc', kernel ? path.join('kernel', 'ddc_sdk.dill') : 'ddc_sdk.sum'); } else { var suffix = kernel ? path.join('kernel', mod) : mod; sdkJsPath = path.join(dartSdk, 'lib', 'dev_compiler', suffix); requirePath = sdkJsPath; ddcSdk = path.join( dartSdk, 'lib', '_internal', kernel ? 'ddc_sdk.dill' : 'ddc_sdk.sum'); } ProcessResult result; var ddcArgs = [ if (kernel) '--kernel', '--modules=$mod', '--dart-sdk-summary=$ddcSdk', // TODO(nshahan) Cleanup when we settle on using or removing library-root. if (!kernel) '--library-root=$libRoot', for (var summary in summaries) '--summary=$summary', for (var experiment in experiments) '--enable-experiment=$experiment', '-o', '$libRoot/$basename.js', entry ]; result = runDdc('dartdevc', ddcArgs); await echoResult(result); 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 = """ """; var htmlFile = '$libRoot/$basename.html'; new File(htmlFile).writeAsStringSync(html); var tmp = path.join(Directory.systemTemp.path, 'ddc'); result = Process.runSync(chromeBinary, [ '--auto-open-devtools-for-tabs', '--allow-file-access-from-files', '--remote-debugging-port=$port', '--user-data-dir=$tmp', htmlFile ]); } 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\"); let main = require(\"./$basename\").$basename.main; sdk.dart.ignoreWhitelistedErrors($ignoreWhitelistedErrors); try { sdk._isolate_helper.startRootIsolate(main, []); } catch(e) { if (!source_maps) { console.log('For Dart source maps: npm install source-map-support'); } console.error(e); process.exit(1); } '''; var nodeFile = '$libRoot/$basename.run.js'; new File(nodeFile).writeAsStringSync(runjs); var nodeBinary = binary ?? 'node'; result = Process.runSync( nodeBinary, ['--inspect=localhost:$port', nodeFile], environment: {'NODE_PATH': nodePath}); } else if (d8) { // Fix SDK import. `d8` doesn't let us set paths, so we need a full path // to the SDK. var jsFile = File('$libRoot/$basename.js'); var jsContents = jsFile.readAsStringSync(); jsContents = jsContents.replaceFirst( "from 'dart_sdk.js'", "from '$sdkJsPath/dart_sdk.js'"); jsFile.writeAsStringSync(jsContents); var runjs = ''' import { dart, _isolate_helper } from '$sdkJsPath/dart_sdk.js'; import { $basename } from '$basename.js'; let main = $basename.main; dart.ignoreWhitelistedErrors($ignoreWhitelistedErrors); try { _isolate_helper.startRootIsolate(() => {}, []); main(); } catch(e) { console.error(e); } '''; var d8File = path.join(libRoot, '$basename.d8.js'); new File(d8File).writeAsStringSync(runjs); var d8Binary = binary ?? path.join(dartCheckoutPath, _d8executable); result = Process.runSync(d8Binary, ['--module', d8File]); } await echoResult(result); } String get _d8executable { if (Platform.isWindows) { return path.join('third_party', 'd8', 'windows', 'd8.exe'); } else if (Platform.isLinux) { return path.join('third_party', 'd8', 'linux', 'd8'); } else if (Platform.isMacOS) { return path.join('third_party', 'd8', 'macos', 'd8'); } throw new UnsupportedError('Unsupported platform.'); }