blob: 9b07711dd4dbd73d20860a0269de4038d5b42038 [file] [log] [blame]
#!/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 p;
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()
..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)
..addFlag('observe',
help:
'Run the compiler in the Dart VM with --observe. Implies --debug.',
defaultsTo: false)
..addOption('vm-service-port',
help: 'Specify the observatory port. Implied --observe.')
..addFlag('summarize-text',
help: 'Emit API summary in a .js.txt file.\n'
'Ignored when not passed with --kernel.',
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.')
..addOption('mode',
help: 'Option to (compile|run|all). Default is all (compile and run).',
allowed: ['compile', 'run', 'all'],
defaultsTo: 'all')
..addOption('compile-vm-options',
help: 'DART_VM_OPTIONS for the compilation VM.')
..addOption('packages',
help: 'Where to find a package spec file.')
..addOption('out',
help: 'Output file.');
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 kernel = options['kernel'] as bool;
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 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 = kernel
? p.relative(p.withoutExtension(entry)).replaceAll('/', '__')
: basename;
libname = libname.replaceAll('-', '_');
// 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));
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 Process.start(command, args,
mode: ProcessStartMode.inheritStdio,
environment: <String, String>{
if (options['compile-vm-options'] != null)
'DART_VM_OPTIONS': options['compile-vm-options']
});
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 = 'es6';
break;
case 'chrome':
chrome = true;
mod = 'amd';
break;
}
String sdkJsPath;
String requirePath;
String ddcSdk;
if (debug) {
var sdkRoot = p.dirname(p.dirname(ddcPath));
var buildDir = p.join(sdkRoot, Platform.isMacOS ? 'xcodebuild' : 'out');
dartSdk = p.join(buildDir, 'ReleaseX64', 'dart-sdk');
}
var suffix = kernel ? p.join('kernel', mod) : mod;
sdkJsPath = p.join(dartSdk, 'lib', 'dev_compiler', suffix);
requirePath = sdkJsPath;
ddcSdk = p.join(
dartSdk, 'lib', '_internal', kernel ? 'ddc_sdk.dill' : 'ddc_sdk.sum');
if (compile) {
var ddcArgs = [
if (kernel) '--kernel',
if (kernel && summarizeText)
'--summarize-text',
'--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',
if (options['packages'] != null) '--packages=${options['packages']}',
'-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._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 Process.start(
chromeBinary,
[
'--auto-open-devtools-for-tabs',
'--allow-file-access-from-files',
'--remote-debugging-port=$port',
'--user-data-dir=$tmp',
htmlFile
],
mode: ProcessStartMode.inheritStdio);
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\");
let main = require(\"./$basename\").$libname.main;
try {
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 Process.start(
nodeBinary, ['--inspect=localhost:$port', nodeFile],
environment: {'NODE_PATH': nodePath},
mode: ProcessStartMode.inheritStdio);
if (await process.exitCode != 0) exit(await process.exitCode);
} 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(out);
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 { $libname } from '$basename.js';
let main = $libname.main;
try {
_isolate_helper.startRootIsolate(() => {}, []);
main();
} catch(e) {
console.error(e);
}
''';
var d8File = p.setExtension(out, '.d8.js');
File(d8File).writeAsStringSync(runjs);
var d8Binary = binary ?? p.join(dartCheckoutPath, _d8executable);
var process = await Process.start(d8Binary, ['--module', d8File],
mode: ProcessStartMode.inheritStdio);
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.');
}