| // Copyright (c) 2016, 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. |
| |
| import 'dart:io'; |
| import 'package:analyzer/src/command_line/arguments.dart' |
| show defineAnalysisArguments, ignoreUnrecognizedFlagsFlag; |
| import 'package:analyzer/src/generated/source.dart' show Source; |
| import 'package:analyzer/src/summary/package_bundle_reader.dart' |
| show ConflictingSummaryException, InSummarySource; |
| import 'package:args/args.dart' show ArgParser, ArgResults; |
| import 'package:args/command_runner.dart' show UsageException; |
| import 'package:path/path.dart' as path; |
| |
| import '../compiler/module_builder.dart'; |
| import 'context.dart' show AnalyzerOptions; |
| import 'module_compiler.dart' show BuildUnit, CompilerOptions, ModuleCompiler; |
| |
| const _binaryName = 'dartdevc'; |
| |
| bool _verbose = false; |
| |
| /// Runs a single compile for dartdevc. |
| /// |
| /// This handles argument parsing, usage, error handling. |
| /// See bin/dartdevc.dart for the actual entry point, which includes Bazel |
| /// worker support. |
| int compile(List<String> args, {void printFn(Object obj)}) { |
| printFn ??= print; |
| |
| ArgResults argResults; |
| AnalyzerOptions analyzerOptions; |
| try { |
| var parser = ddcArgParser(); |
| if (args.contains('--$ignoreUnrecognizedFlagsFlag')) { |
| args = filterUnknownArguments(args, parser); |
| } |
| argResults = parser.parse(args); |
| analyzerOptions = AnalyzerOptions.fromArguments(argResults); |
| } on FormatException catch (error) { |
| printFn('$error\n\n$_usageMessage'); |
| return 64; |
| } |
| |
| _verbose = argResults['verbose'] as bool; |
| if (argResults['help'] as bool || args.isEmpty) { |
| printFn(_usageMessage); |
| return 0; |
| } |
| |
| if (argResults['version'] as bool) { |
| printFn('$_binaryName version ${_getVersion()}'); |
| return 0; |
| } |
| |
| try { |
| _compile(argResults, analyzerOptions, printFn); |
| return 0; |
| } on UsageException catch (error) { |
| // Incorrect usage, input file not found, etc. |
| printFn(error); |
| return 64; |
| } on ConflictingSummaryException catch (error) { |
| // Same input file appears in multiple provided summaries. |
| printFn(error); |
| return 65; |
| } on CompileErrorException catch (error) { |
| // Code has error(s) and failed to compile. |
| printFn(error); |
| return 1; |
| } catch (error, stackTrace) { |
| // Anything else is likely a compiler bug. |
| // |
| // --unsafe-force-compile is a bit of a grey area, but it's nice not to |
| // crash while compiling |
| // (of course, output code may crash, if it had errors). |
| // |
| printFn(''' |
| We're sorry, you've found a bug in our compiler. |
| You can report this bug at: |
| https://github.com/dart-lang/sdk/issues/labels/area-dev-compiler |
| Please include the information below in your report, along with |
| any other information that may help us track it down. Thanks! |
| $_binaryName arguments: ${args.join(' ')} |
| dart --version: ${Platform.version} |
| ``` |
| $error |
| $stackTrace |
| ```'''); |
| return 70; |
| } |
| } |
| |
| ArgParser ddcArgParser({bool hide = true}) { |
| var argParser = ArgParser(allowTrailingOptions: true) |
| ..addFlag('help', |
| abbr: 'h', |
| help: 'Display this message. Add -v to show hidden options.', |
| negatable: false) |
| ..addFlag('verbose', |
| abbr: 'v', negatable: false, help: 'Verbose help output.', hide: hide) |
| ..addFlag('version', |
| negatable: false, help: 'Print the $_binaryName version.', hide: hide) |
| ..addFlag(ignoreUnrecognizedFlagsFlag, |
| help: 'Ignore unrecognized command line flags.', |
| defaultsTo: false, |
| hide: hide) |
| ..addMultiOption('out', abbr: 'o', help: 'Output file (required).') |
| ..addOption('module-root', |
| help: 'Root module directory. Module paths are relative to this root.') |
| ..addOption('library-root', |
| help: 'Root of source files. Library names are relative to this root.'); |
| defineAnalysisArguments(argParser, hide: hide, ddc: true); |
| addModuleFormatOptions(argParser, allowMultiple: true, hide: hide); |
| AnalyzerOptions.addArguments(argParser, hide: hide); |
| CompilerOptions.addArguments(argParser, hide: hide); |
| return argParser; |
| } |
| |
| bool _changed(List<int> list1, List<int> list2) { |
| var length = list1.length; |
| if (length != list2.length) return true; |
| for (var i = 0; i < length; ++i) { |
| if (list1[i] != list2[i]) return true; |
| } |
| return false; |
| } |
| |
| void _compile(ArgResults argResults, AnalyzerOptions analyzerOptions, |
| void printFn(Object obj)) { |
| var compiler = ModuleCompiler(analyzerOptions); |
| var compilerOpts = CompilerOptions.fromArguments(argResults); |
| var outPaths = argResults['out'] as List<String>; |
| var moduleFormats = parseModuleFormatOption(argResults); |
| bool singleOutFile = argResults['single-out-file']; |
| if (singleOutFile) { |
| for (var format in moduleFormats) { |
| if (format != ModuleFormat.amd && format != ModuleFormat.legacy) { |
| _usageException('Format $format cannot be combined with ' |
| 'single-out-file. Only amd and legacy modes are supported.'); |
| } |
| } |
| } |
| |
| if (outPaths.isEmpty) { |
| _usageException('Please include the output file location. For example:\n' |
| ' -o PATH/TO/OUTPUT_FILE.js'); |
| } else if (outPaths.length != moduleFormats.length) { |
| _usageException('Number of output files (${outPaths.length}) must match ' |
| 'number of module formats (${moduleFormats.length}).'); |
| } |
| |
| // TODO(jmesserly): for now the first one is special. This will go away once |
| // we've removed the "root" and "module name" variables. |
| var firstOutPath = outPaths[0]; |
| |
| var libraryRoot = argResults['library-root'] as String; |
| if (libraryRoot != null) { |
| libraryRoot = path.absolute(libraryRoot); |
| } else { |
| libraryRoot = Directory.current.path; |
| } |
| var moduleRoot = argResults['module-root'] as String; |
| String modulePath; |
| if (moduleRoot != null) { |
| moduleRoot = path.absolute(moduleRoot); |
| if (!path.isWithin(moduleRoot, firstOutPath)) { |
| _usageException('Output file $firstOutPath must be within the module ' |
| 'root directory $moduleRoot'); |
| } |
| modulePath = |
| path.withoutExtension(path.relative(firstOutPath, from: moduleRoot)); |
| } else { |
| moduleRoot = path.dirname(firstOutPath); |
| modulePath = path.basenameWithoutExtension(firstOutPath); |
| } |
| |
| var unit = BuildUnit( |
| modulePath, |
| libraryRoot, |
| argResults.rest, |
| (source) => |
| _moduleForLibrary(moduleRoot, source, analyzerOptions, compilerOpts)); |
| |
| var module = compiler.compile(unit, compilerOpts); |
| module.errors.forEach(printFn); |
| |
| if (!module.isValid) { |
| throw compilerOpts.unsafeForceCompile |
| ? ForceCompileErrorException() |
| : CompileErrorException(); |
| } |
| |
| // Write JS file, as well as source map and summary (if requested). |
| for (var i = 0; i < outPaths.length; i++) { |
| module.writeCodeSync(moduleFormats[i], outPaths[i], |
| singleOutFile: singleOutFile); |
| } |
| if (module.summaryBytes != null) { |
| var summaryPaths = compilerOpts.summaryOutPath != null |
| ? [compilerOpts.summaryOutPath] |
| : outPaths.map((p) => |
| '${path.withoutExtension(p)}.${compilerOpts.summaryExtension}'); |
| |
| // place next to every compiled module |
| for (var summaryPath in summaryPaths) { |
| // Only overwrite if summary changed. This plays better with timestamp |
| // based build systems. |
| var file = File(summaryPath); |
| if (!file.existsSync() || |
| _changed(file.readAsBytesSync(), module.summaryBytes)) { |
| if (!file.parent.existsSync()) file.parent.createSync(recursive: true); |
| file.writeAsBytesSync(module.summaryBytes); |
| } |
| } |
| } |
| } |
| |
| String _moduleForLibrary(String moduleRoot, Source source, |
| AnalyzerOptions analyzerOptions, CompilerOptions compilerOpts) { |
| if (source is InSummarySource) { |
| var summaryPath = source.summaryPath; |
| |
| if (analyzerOptions.customSummaryModules.containsKey(summaryPath)) { |
| return analyzerOptions.customSummaryModules[summaryPath]; |
| } |
| |
| var ext = '.${compilerOpts.summaryExtension}'; |
| if (path.isWithin(moduleRoot, summaryPath) && summaryPath.endsWith(ext)) { |
| var buildUnitPath = |
| summaryPath.substring(0, summaryPath.length - ext.length); |
| return path.url |
| .joinAll(path.split(path.relative(buildUnitPath, from: moduleRoot))); |
| } |
| |
| _usageException('Imported file ${source.uri} is not within the module root ' |
| 'directory $moduleRoot'); |
| } |
| |
| _usageException( |
| 'Imported file "${source.uri}" was not found as a summary or source ' |
| 'file. Please pass in either the summary or the source file ' |
| 'for this import.'); |
| return null; // unreachable |
| } |
| |
| String get _usageMessage => |
| 'The Dart Development Compiler compiles Dart sources into a JavaScript ' |
| 'module.\n\n' |
| 'Usage: $_binaryName [options...] <sources...>\n\n' |
| '${ddcArgParser(hide: !_verbose).usage}'; |
| |
| String _getVersion() { |
| try { |
| // This is relative to bin/snapshot, so ../.. |
| String versionPath = Platform.script.resolve('../../version').toFilePath(); |
| File versionFile = File(versionPath); |
| return versionFile.readAsStringSync().trim(); |
| } catch (_) { |
| // This happens when the script is not running in the context of an SDK. |
| return "<unknown>"; |
| } |
| } |
| |
| void _usageException(String message) { |
| throw UsageException(message, _usageMessage); |
| } |
| |
| /// Thrown when the input source code has errors. |
| class CompileErrorException implements Exception { |
| toString() => '\nPlease fix all errors before compiling (warnings are okay).'; |
| } |
| |
| /// Thrown when force compilation failed (probably due to static errors). |
| class ForceCompileErrorException extends CompileErrorException { |
| toString() => |
| '\nForce-compilation not successful. Please check static errors.'; |
| } |
| |
| // TODO(jmesserly): fix this function in analyzer |
| List<String> filterUnknownArguments(List<String> args, ArgParser parser) { |
| Set<String> knownOptions = Set<String>(); |
| Set<String> knownAbbreviations = Set<String>(); |
| parser.options.forEach((String name, option) { |
| knownOptions.add(name); |
| String abbreviation = option.abbr; |
| if (abbreviation != null) { |
| knownAbbreviations.add(abbreviation); |
| } |
| }); |
| List<String> filtered = <String>[]; |
| for (int i = 0; i < args.length; i++) { |
| String argument = args[i]; |
| if (argument.startsWith('--') && argument.length > 2) { |
| int equalsOffset = argument.lastIndexOf('='); |
| int end = equalsOffset < 0 ? argument.length : equalsOffset; |
| if (knownOptions.contains(argument.substring(2, end))) { |
| filtered.add(argument); |
| } |
| } else if (argument.startsWith('-') && argument.length > 1) { |
| // TODO(jmesserly): fix this line in analyzer |
| // It was discarding abbreviations such as -Da=b |
| // Abbreviations must be 1-character (this is enforced by ArgParser), |
| // so we don't need to use `optionName` |
| if (knownAbbreviations.contains(argument[1])) { |
| filtered.add(argument); |
| } |
| } else { |
| filtered.add(argument); |
| } |
| } |
| return filtered; |
| } |