blob: d4fb08610b3531c0edba50f57a0a2967c1d53043 [file] [log] [blame] [edit]
// Copyright (c) 2014, 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.
/// Set of flags and options passed to the compiler
library dev_compiler.src.options;
import 'dart:io';
import 'package:args/args.dart';
import 'package:cli_util/cli_util.dart' show getSdkDir;
import 'package:logging/logging.dart' show Level;
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';
const String _V8_BINARY_DEFAULT = 'node';
const bool _CLOSURE_DEFAULT = false;
/// Options used to set up Source URI resolution in the analysis context.
class SourceResolverOptions {
/// Whether to resolve 'package:' uris using the multi-package resolver.
final bool useMultiPackage;
/// Custom URI mappings, such as "dart:foo" -> "path/to/foo.dart"
final Map<String, String> customUrlMappings;
/// Package root when resolving 'package:' urls the standard way.
final String packageRoot;
/// List of paths used for the multi-package resolver.
final List<String> packagePaths;
/// List of additional non-Dart resources to resolve and serve.
final List<String> resources;
// True if the resolver should implicitly provide an html entry point.
final bool useImplicitHtml;
static const String implicitHtmlFile = 'index.html';
/// Whether to use a mock-sdk during compilation.
final bool useMockSdk;
/// Path to the dart-sdk. Null if `useMockSdk` is true or if the path couldn't
/// be determined
final String dartSdkPath;
const SourceResolverOptions(
{this.useMockSdk: false,
this.dartSdkPath,
this.useMultiPackage: false,
this.customUrlMappings: const {},
this.packageRoot: 'packages/',
this.packagePaths: const <String>[],
this.resources: const <String>[],
this.useImplicitHtml: false});
}
// TODO(jmesserly): refactor all codegen options here.
class CodegenOptions {
/// Whether to emit the source map files.
final bool emitSourceMaps;
/// Whether to force compilation of code with static errors.
final bool forceCompile;
/// Output directory for generated code.
final String outputDir;
/// Emit Closure Compiler-friendly code.
final bool closure;
/// Whether to emit a workaround for missing arrow function bind-this in
/// other V8 builds
final bool arrowFnBindThisWorkaround;
const CodegenOptions(
{this.emitSourceMaps: true,
this.forceCompile: false,
this.closure: _CLOSURE_DEFAULT,
this.outputDir,
this.arrowFnBindThisWorkaround: false});
}
/// Options for devrun.
class RunnerOptions {
/// V8-based binary to be used to run the .js output (d8, iojs, node).
/// Can be just the executable name if it's in the path, or a path to the
/// executable.
final String v8Binary;
const RunnerOptions({this.v8Binary: _V8_BINARY_DEFAULT});
}
/// General options used by the dev compiler and server.
class CompilerOptions {
final SourceResolverOptions sourceOptions;
final CodegenOptions codegenOptions;
final RunnerOptions runnerOptions;
/// Whether to check the sdk libraries.
final bool checkSdk;
/// Whether to dump summary information on the console.
final bool dumpInfo;
final bool htmlReport;
/// If not null, path to a file that will store a json representation of the
/// summary information (only used if [dumpInfo] is true).
final String dumpInfoFile;
/// Whether to use colors when interacting on the console.
final bool useColors;
/// Whether the user asked for help.
final bool help;
/// Whether the user asked for the app version.
final bool version;
/// Minimum log-level reported on the command-line.
final Level logLevel;
/// Whether to run as a development server.
final bool serverMode;
/// Whether to enable hash-based caching of files.
final bool enableHashing;
/// Whether to serve the error / warning widget.
final bool widget;
/// Port used for the HTTP server when [serverMode] is on.
final int port;
/// Host name or address for HTTP server when [serverMode] is on.
final String host;
/// Location for runtime files, such as `dart_runtime.js`. By default this is
/// inferred to be under `lib/runtime/` in the location of the `dev_compiler`
/// package (if we can infer where that is located).
final String runtimeDir;
/// The files to compile.
final List<String> inputs;
/// The base directory for [inputs]. Module imports will be generated relative
/// to this directory.
final String inputBaseDir;
CompilerOptions(
{this.sourceOptions: const SourceResolverOptions(),
this.codegenOptions: const CodegenOptions(),
this.runnerOptions: const RunnerOptions(),
this.checkSdk: false,
this.dumpInfo: false,
this.htmlReport: false,
this.dumpInfoFile,
this.useColors: true,
this.help: false,
this.version: false,
this.logLevel: Level.WARNING,
this.serverMode: false,
this.enableHashing: false,
this.widget: true,
this.host: 'localhost',
this.port: 8080,
this.runtimeDir,
this.inputs,
this.inputBaseDir});
}
/// Parses options from the command-line
CompilerOptions parseOptions(List<String> argv, {bool forceOutDir: false}) {
ArgResults args = argParser.parse(argv);
bool showUsage = args['help'];
bool showVersion = args['version'];
var serverMode = args['server'];
var enableHashing = args['hashing'];
if (enableHashing == null) {
enableHashing = serverMode;
}
var logLevel = Level.WARNING;
var levelName = args['log'];
if (levelName != null) {
levelName = levelName.toUpperCase();
logLevel = Level.LEVELS
.firstWhere((l) => l.name == levelName, orElse: () => logLevel);
}
var useColors = stdioType(stdout) == StdioType.TERMINAL;
var sdkPath = args['dart-sdk'];
if (sdkPath == null && !args['mock-sdk']) {
sdkPath = getSdkDir(argv).path;
}
var runtimeDir = args['runtime-dir'];
if (runtimeDir == null) {
runtimeDir = _computeRuntimeDir();
}
var outputDir = args['out'];
if (outputDir == null && (serverMode || forceOutDir)) {
outputDir = Directory.systemTemp.createTempSync("dev_compiler_out_").path;
}
var dumpInfo = args['dump-info'];
if (dumpInfo == null) dumpInfo = serverMode;
var htmlReport = args['html-report'];
var v8Binary = args['v8-binary'];
if (v8Binary == null) v8Binary = _V8_BINARY_DEFAULT;
var customUrlMappings = <String, String>{};
for (var mapping in args['url-mapping']) {
var splitMapping = mapping.split(',');
if (splitMapping.length != 2) {
showUsage = true;
continue;
}
customUrlMappings[splitMapping[0]] = splitMapping[1];
}
if (serverMode && args.rest.length != 1) showUsage = true;
return new CompilerOptions(
codegenOptions: new CodegenOptions(
emitSourceMaps: args['source-maps'],
forceCompile: args['force-compile'] || serverMode,
closure: args['closure'],
outputDir: outputDir,
arrowFnBindThisWorkaround: args['arrow-fn-bind-this']),
sourceOptions: new SourceResolverOptions(
useMockSdk: args['mock-sdk'],
dartSdkPath: sdkPath,
useImplicitHtml: serverMode &&
args.rest.length == 1 &&
args.rest[0].endsWith('.dart'),
customUrlMappings: customUrlMappings,
useMultiPackage: args['use-multi-package'],
packageRoot: args['package-root'],
packagePaths: args['package-paths'].split(','),
resources:
args['resources'].split(',').where((s) => s.isNotEmpty).toList()),
runnerOptions: new RunnerOptions(v8Binary: v8Binary),
checkSdk: args['sdk-check'],
dumpInfo: dumpInfo,
htmlReport: htmlReport,
dumpInfoFile: args['dump-info-file'],
useColors: useColors,
help: showUsage,
version: showVersion,
logLevel: logLevel,
serverMode: serverMode,
enableHashing: enableHashing,
widget: args['widget'],
host: args['host'],
port: int.parse(args['port']),
runtimeDir: runtimeDir,
inputs: args.rest);
}
final ArgParser argParser = new ArgParser()
..addFlag('sdk-check',
abbr: 's', help: 'Typecheck sdk libs', defaultsTo: false)
..addFlag('mock-sdk',
abbr: 'm', help: 'Use a mock Dart SDK', defaultsTo: false)
// input/output options
..addOption('out', abbr: 'o', help: 'Output directory', defaultsTo: null)
..addOption('dart-sdk', help: 'Dart SDK Path', defaultsTo: null)
..addOption('dump-src-to', help: 'Dump dart src code', defaultsTo: null)
..addOption('package-root',
abbr: 'p',
help: 'Package root to resolve "package:" imports',
defaultsTo: 'packages/')
..addOption('url-mapping',
help: '--url-mapping=libraryUri,/path/to/library.dart uses library.dart\n'
'as the source for an import of of "libraryUri".',
allowMultiple: true,
splitCommas: false)
..addFlag('use-multi-package',
help: 'Whether to use the multi-package resolver for "package:" imports',
defaultsTo: false)
..addOption('package-paths',
help: 'if using the multi-package resolver, the list of directories to\n'
'look for packages in.',
defaultsTo: '')
..addOption('resources',
help: 'Additional resources to serve', defaultsTo: '')
..addFlag('source-maps',
help: 'Whether to emit source map files', defaultsTo: true)
..addOption('runtime-dir',
help: 'Where to find dev_compiler\'s runtime files', defaultsTo: null)
..addFlag('arrow-fn-bind-this',
help: 'Work around `this` binding in => functions')
// general options
..addFlag('help', abbr: 'h', help: 'Display this message')
..addFlag('version',
negatable: false, help: 'Display the Dev Compiler verion')
..addFlag('server', help: 'Run as a development server.', defaultsTo: false)
..addFlag('hashing',
help: 'Enable hash-based file caching.', defaultsTo: null)
..addFlag('widget',
help: 'Serve embedded widget with static errors and warnings.',
defaultsTo: true)
..addOption('host',
help: 'Host name or address to serve files from, e.g. --host=0.0.0.0\n'
'to listen on all interfaces (used only when --serve is on)',
defaultsTo: 'localhost')
..addOption('port',
help: 'Port to serve files from (used only when --serve is on)',
defaultsTo: '8080')
..addFlag('closure',
help: 'Emit Closure Compiler-friendly code (experimental)',
defaultsTo: _CLOSURE_DEFAULT)
..addFlag('force-compile',
abbr: 'f', help: 'Compile code with static errors', defaultsTo: false)
..addOption('log', abbr: 'l', help: 'Logging level (defaults to warning)')
..addFlag('dump-info',
abbr: 'i', help: 'Dump summary information', defaultsTo: null)
..addFlag('html-report',
help: 'Output compilation results to html', defaultsTo: false)
..addOption('v8-binary',
help: 'V8-based binary to run JavaScript output with (iojs, node, d8)',
defaultsTo: _V8_BINARY_DEFAULT)
..addOption('dump-info-file',
help: 'Dump info json file (requires dump-info)', defaultsTo: null);
// TODO: Switch over to the `pub_cache` package (or the Resource API)?
const _ENTRY_POINTS = const [
'dartdevc.dart',
'dev_compiler.dart',
'devrun.dart'
];
final _ENTRY_POINT_SNAPSHOTS = _ENTRY_POINTS.map((f) => "$f.snapshot");
/// Tries to find the `lib/runtime/` directory of the dev_compiler package. This
/// works when running devc from it's sources or from a snapshot that is
/// activated via `pub global activate`.
String _computeRuntimeDir() {
var scriptPath = path.fromUri(Platform.script);
var file = path.basename(scriptPath);
var dir = path.dirname(scriptPath);
var lastdir = path.basename(dir);
dir = path.dirname(dir);
// Both the source dartdevc.dart and the snapshot generated by pub global activate
// are under a bin folder.
if (lastdir != 'bin') return null;
// And both under a project directory containing a pubspec.lock file.
var lockfile = path.join(dir, 'pubspec.lock');
if (!new File(lockfile).existsSync()) return null;
// If running from sources we found it!
if (_ENTRY_POINTS.contains(file)) {
return path.join(dir, 'lib', 'runtime');
}
// If running from a pub global snapshot, we need to read the lock file to
// find where the actual sources are located in the pub cache.
if (_ENTRY_POINT_SNAPSHOTS.contains(file)) {
// Note: this depends on implementation details of pub.
var yaml = loadYaml(new File(lockfile).readAsStringSync());
var info = yaml['packages']['dev_compiler'];
if (info == null) return null;
var cacheDir;
if (info['source'] == 'hosted') {
cacheDir = path.join(
'hosted', 'pub.dartlang.org', 'dev_compiler-${info["version"]}');
} else if (info['source'] == 'git') {
var ref = info['description']['resolved-ref'];
cacheDir = path.join('git', 'dev_compiler-${ref}');
}
// We should be under "/path/to/pub-cache/global_packages/dev_compiler".
// The pub-cache directory is two levels up, but we verify that the layout
// looks correct.
if (path.basename(dir) != 'dev_compiler') return null;
dir = path.dirname(dir);
if (path.basename(dir) != 'global_packages') return null;
dir = path.dirname(dir);
return path.join(dir, cacheDir, 'lib', 'runtime');
}
return null;
}