| // 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 = new 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 = new 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 = new ModuleCompiler(analyzerOptions); | |
| var compilerOpts = new 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 = new 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 | |
| ? new ForceCompileErrorException() | |
| : new 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 = new 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 = new 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 new 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 = new Set<String>(); | |
| Set<String> knownAbbreviations = new 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; | |
| } |