OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library fasta.tool.command_line; |
| 6 |
| 7 import 'dart:io' show exit; |
| 8 |
| 9 import 'package:front_end/compiler_options.dart' show CompilerOptions; |
| 10 |
| 11 import 'package:front_end/src/base/processed_options.dart' |
| 12 show ProcessedOptions; |
| 13 |
| 14 import 'package:front_end/src/fasta/compiler_context.dart' show CompilerContext; |
| 15 |
| 16 import 'package:front_end/src/fasta/fasta_codes.dart' |
| 17 show |
| 18 Message, |
| 19 templateFastaCLIArgumentRequired, |
| 20 messageFastaUsageLong, |
| 21 messageFastaUsageShort, |
| 22 templateUnspecified; |
| 23 |
| 24 import 'package:front_end/src/fasta/problems.dart' show unhandled; |
| 25 |
| 26 import 'package:front_end/src/fasta/severity.dart' show Severity; |
| 27 |
| 28 import 'package:kernel/target/targets.dart' |
| 29 show Target, getTarget, TargetFlags, targets; |
| 30 |
| 31 class CommandLineProblem { |
| 32 final Message message; |
| 33 |
| 34 CommandLineProblem(this.message); |
| 35 |
| 36 CommandLineProblem.deprecated(String message) |
| 37 : this(templateUnspecified.withArguments(message)); |
| 38 } |
| 39 |
| 40 class ParsedArguments { |
| 41 final Map<String, dynamic> options = <String, dynamic>{}; |
| 42 final List<String> arguments = <String>[]; |
| 43 |
| 44 toString() => "ParsedArguments($options, $arguments)"; |
| 45 |
| 46 /// Parses a list of command-line [arguments] into options and arguments. |
| 47 /// |
| 48 /// An /option/ is something that, normally, starts with `-` or `--` (one or |
| 49 /// two dashes). However, as a special case `/?` and `/h` are also recognized |
| 50 /// as options for increased compatibility with Windows. An option can have a |
| 51 /// value. |
| 52 /// |
| 53 /// An /argument/ is something that isn't an option, for example, a file name. |
| 54 /// |
| 55 /// The specification is a map of options to one of the type literals `Uri`, |
| 56 /// `int`, `bool`, or `String`, or a comma (`","`) that represents option |
| 57 /// values of type [Uri], [int], [bool], [String], or a comma-separated list |
| 58 /// of [String], respectively. |
| 59 /// |
| 60 /// If [arguments] contains `"--"`, anything before is parsed as options, and |
| 61 /// arguments; anything following is treated as arguments (even if starting |
| 62 /// with, for example, a `-`). |
| 63 /// |
| 64 /// Anything that looks like an option is assumed to be a `bool` option set |
| 65 /// to true, unless it's mentioned in [specification] in which case the |
| 66 /// option requires a value, either on the form `--option value` or |
| 67 /// `--option=value`. |
| 68 /// |
| 69 /// This method performs only a limited amount of validation, but if an error |
| 70 /// occurs, it will print [usage] along with a specific error message. |
| 71 static ParsedArguments parse( |
| 72 List<String> arguments, Map<String, dynamic> specification) { |
| 73 specification ??= const <String, dynamic>{}; |
| 74 ParsedArguments result = new ParsedArguments(); |
| 75 int index = arguments.indexOf("--"); |
| 76 Iterable<String> nonOptions = const <String>[]; |
| 77 Iterator<String> iterator = arguments.iterator; |
| 78 if (index != -1) { |
| 79 nonOptions = arguments.skip(index + 1); |
| 80 iterator = arguments.take(index).iterator; |
| 81 } |
| 82 while (iterator.moveNext()) { |
| 83 String argument = iterator.current; |
| 84 if (argument.startsWith("-")) { |
| 85 var valueSpecification = specification[argument]; |
| 86 String value; |
| 87 if (valueSpecification != null) { |
| 88 if (!iterator.moveNext()) { |
| 89 throw new CommandLineProblem( |
| 90 templateFastaCLIArgumentRequired.withArguments(argument)); |
| 91 } |
| 92 value = iterator.current; |
| 93 } else { |
| 94 index = argument.indexOf("="); |
| 95 if (index != -1) { |
| 96 value = argument.substring(index + 1); |
| 97 argument = argument.substring(0, index); |
| 98 valueSpecification = specification[argument]; |
| 99 } |
| 100 } |
| 101 if (valueSpecification == null) { |
| 102 if (value != null) { |
| 103 throw new CommandLineProblem.deprecated( |
| 104 "Argument '$argument' doesn't take a value: '$value'."); |
| 105 } |
| 106 result.options[argument] = true; |
| 107 } else { |
| 108 if (valueSpecification is! String && valueSpecification is! Type) { |
| 109 return throw new CommandLineProblem.deprecated( |
| 110 "Unrecognized type of value " |
| 111 "specification: ${valueSpecification.runtimeType}."); |
| 112 } |
| 113 switch ("$valueSpecification") { |
| 114 case ",": |
| 115 result.options |
| 116 .putIfAbsent(argument, () => <String>[]) |
| 117 .addAll(value.split(",")); |
| 118 break; |
| 119 |
| 120 case "int": |
| 121 case "bool": |
| 122 case "String": |
| 123 case "Uri": |
| 124 if (result.options.containsKey(argument)) { |
| 125 return throw new CommandLineProblem.deprecated( |
| 126 "Multiple values for '$argument': " |
| 127 "'${result.options[argument]}' and '$value'."); |
| 128 } |
| 129 var parsedValue; |
| 130 if (valueSpecification == int) { |
| 131 parsedValue = int.parse(value, onError: (_) { |
| 132 return throw new CommandLineProblem.deprecated( |
| 133 "Value for '$argument', '$value', isn't an int."); |
| 134 }); |
| 135 } else if (valueSpecification == bool) { |
| 136 if (value == "true" || value == "yes") { |
| 137 parsedValue = true; |
| 138 } else if (value == "false" || value == "no") { |
| 139 parsedValue = false; |
| 140 } else { |
| 141 return throw new CommandLineProblem.deprecated( |
| 142 "Value for '$argument' is '$value', " |
| 143 "but expected one of: 'true', 'false', 'yes', or 'no'."); |
| 144 } |
| 145 } else if (valueSpecification == Uri) { |
| 146 parsedValue = Uri.base.resolve(value); |
| 147 } else if (valueSpecification == String) { |
| 148 parsedValue = value; |
| 149 } else if (valueSpecification is String) { |
| 150 return throw new CommandLineProblem.deprecated( |
| 151 "Unrecognized value specification: " |
| 152 "'$valueSpecification', try using a type literal instead."); |
| 153 } else { |
| 154 // All possible cases should have been handled above. |
| 155 return unhandled("${valueSpecification.runtimeType}", |
| 156 "CommandLine.parse", -1, null); |
| 157 } |
| 158 result.options[argument] = parsedValue; |
| 159 break; |
| 160 |
| 161 default: |
| 162 return throw new CommandLineProblem.deprecated( |
| 163 "Unrecognized value specification: '$valueSpecification'."); |
| 164 } |
| 165 } |
| 166 } else if (argument == "/?" || argument == "/h") { |
| 167 result.options[argument] = true; |
| 168 } else { |
| 169 result.arguments.add(argument); |
| 170 } |
| 171 } |
| 172 result.arguments.addAll(nonOptions); |
| 173 return result; |
| 174 } |
| 175 } |
| 176 |
| 177 const Map<String, dynamic> optionSpecification = const <String, dynamic>{ |
| 178 "--compile-sdk": Uri, |
| 179 "--fatal": ",", |
| 180 "--output": Uri, |
| 181 "-o": Uri, |
| 182 "--packages": Uri, |
| 183 "--platform": Uri, |
| 184 "--sdk": Uri, |
| 185 "--target": String, |
| 186 "-t": String, |
| 187 }; |
| 188 |
| 189 ProcessedOptions analyzeCommandLine( |
| 190 String programName, |
| 191 ParsedArguments parsedArguments, |
| 192 bool areRestArgumentsInputs, |
| 193 bool verbose) { |
| 194 final Map<String, dynamic> options = parsedArguments.options; |
| 195 |
| 196 final List<String> arguments = parsedArguments.arguments; |
| 197 |
| 198 final bool help = options.containsKey("--help") || |
| 199 options.containsKey("-h") || |
| 200 options.containsKey("/h") || |
| 201 options.containsKey("/?"); |
| 202 |
| 203 if (help) { |
| 204 print(computeUsage(programName, verbose).message); |
| 205 exit(0); |
| 206 } |
| 207 |
| 208 if (options.containsKey("-o") && options.containsKey("--output")) { |
| 209 return throw new CommandLineProblem.deprecated( |
| 210 "Can't specify both '-o' and '--output'."); |
| 211 } |
| 212 |
| 213 if (options.containsKey("-t") && options.containsKey("--target")) { |
| 214 return throw new CommandLineProblem.deprecated( |
| 215 "Can't specify both '-t' and '--target'."); |
| 216 } |
| 217 |
| 218 if (options.containsKey("--compile-sdk") && |
| 219 options.containsKey("--platform")) { |
| 220 return throw new CommandLineProblem.deprecated( |
| 221 "Can't specify both '--compile-sdk' and '--platform'."); |
| 222 } |
| 223 |
| 224 if (programName == "compile_platform") { |
| 225 if (arguments.length != 3) { |
| 226 return throw new CommandLineProblem.deprecated( |
| 227 "Expected three arguments."); |
| 228 } |
| 229 if (options.containsKey("--compile-sdk")) { |
| 230 return throw new CommandLineProblem.deprecated( |
| 231 "Cannot specify '--compile-sdk' option to compile_platform."); |
| 232 } |
| 233 options['--compile-sdk'] = Uri.base.resolveUri(new Uri.file(arguments[0])); |
| 234 } else if (arguments.isEmpty) { |
| 235 return throw new CommandLineProblem.deprecated("No Dart file specified."); |
| 236 } |
| 237 |
| 238 final bool strongMode = options.containsKey("--strong-mode"); |
| 239 |
| 240 final String targetName = options["-t"] ?? options["--target"] ?? "vm_fasta"; |
| 241 |
| 242 final Target target = |
| 243 getTarget(targetName, new TargetFlags(strongMode: strongMode)); |
| 244 if (target == null) { |
| 245 return throw new CommandLineProblem.deprecated( |
| 246 "Target '${targetName}' not recognized. " |
| 247 "Valid targets are:\n ${targets.keys.join("\n ")}"); |
| 248 } |
| 249 |
| 250 final bool verify = options.containsKey("--verify"); |
| 251 |
| 252 final bool dumpIr = options.containsKey("--dump-ir"); |
| 253 |
| 254 final bool excludeSource = options.containsKey("--exclude-source"); |
| 255 |
| 256 final Uri defaultOutput = Uri.base.resolve("${arguments.first}.dill"); |
| 257 |
| 258 final Uri output = options["-o"] ?? options["--output"] ?? defaultOutput; |
| 259 |
| 260 final Uri platform = options.containsKey("--compile-sdk") |
| 261 ? null |
| 262 : options["--platform"] ?? Uri.base.resolve("platform.dill"); |
| 263 |
| 264 final Uri packages = options["--packages"]; |
| 265 |
| 266 final Uri sdk = options["--sdk"] ?? options["--compile-sdk"]; |
| 267 |
| 268 final Set<String> fatal = |
| 269 new Set<String>.from(options["--fatal"] ?? <String>[]); |
| 270 |
| 271 final bool errorsAreFatal = fatal.contains("errors"); |
| 272 |
| 273 final bool warningsAreFatal = fatal.contains("warnings"); |
| 274 |
| 275 final bool nitsAreFatal = fatal.contains("nits"); |
| 276 |
| 277 CompilerOptions compilerOptions = new CompilerOptions() |
| 278 ..compileSdk = options.containsKey("--compile-sdk") |
| 279 ..sdkRoot = sdk |
| 280 ..sdkSummary = platform |
| 281 ..packagesFileUri = packages |
| 282 ..strongMode = strongMode |
| 283 ..target = target |
| 284 ..throwOnErrors = errorsAreFatal |
| 285 ..throwOnWarnings = warningsAreFatal |
| 286 ..throwOnNits = nitsAreFatal |
| 287 ..embedSourceText = !excludeSource |
| 288 ..debugDump = dumpIr |
| 289 ..verbose = verbose |
| 290 ..verify = verify; |
| 291 |
| 292 // TODO(ahe): What about chase dependencies? |
| 293 |
| 294 var inputs = <Uri>[]; |
| 295 if (areRestArgumentsInputs) { |
| 296 inputs = arguments.map(Uri.base.resolve).toList(); |
| 297 } |
| 298 return new ProcessedOptions(compilerOptions, false, inputs, output); |
| 299 } |
| 300 |
| 301 dynamic withGlobalOptions( |
| 302 String programName, |
| 303 List<String> arguments, |
| 304 bool areRestArgumentsInputs, |
| 305 dynamic f(CompilerContext context, List<String> restArguments)) { |
| 306 ParsedArguments parsedArguments; |
| 307 ProcessedOptions options; |
| 308 bool verbose = true; |
| 309 CommandLineProblem problem; |
| 310 try { |
| 311 parsedArguments = ParsedArguments.parse(arguments, optionSpecification); |
| 312 verbose = parsedArguments.options.containsKey("-v") || |
| 313 parsedArguments.options.containsKey("--verbose"); |
| 314 options = analyzeCommandLine( |
| 315 programName, parsedArguments, areRestArgumentsInputs, verbose); |
| 316 } on CommandLineProblem catch (e) { |
| 317 options = new ProcessedOptions(new CompilerOptions()); |
| 318 problem = e; |
| 319 } |
| 320 |
| 321 return CompilerContext.runWithOptions(options, (c) { |
| 322 if (problem != null) { |
| 323 print(computeUsage(programName, verbose).message); |
| 324 print(c.formatWithoutLocation(problem.message, Severity.error)); |
| 325 exit(1); |
| 326 } |
| 327 |
| 328 f(c, parsedArguments.arguments); |
| 329 }); |
| 330 } |
| 331 |
| 332 Message computeUsage(String programName, bool verbose) { |
| 333 String basicUsage = "Usage: $programName [options] dartfile\n"; |
| 334 String summary; |
| 335 String options = |
| 336 (verbose ? messageFastaUsageLong.message : messageFastaUsageShort.message) |
| 337 .trim(); |
| 338 switch (programName) { |
| 339 case "outline": |
| 340 summary = |
| 341 "Creates an outline of a Dart program in the Dill/Kernel IR format."; |
| 342 break; |
| 343 |
| 344 case "compile": |
| 345 summary = "Compiles a Dart program to the Dill/Kernel IR format."; |
| 346 break; |
| 347 |
| 348 case "run": |
| 349 summary = "Runs a Dart program."; |
| 350 break; |
| 351 |
| 352 case "compile_platform": |
| 353 summary = "Compiles Dart SDK platform to the Dill/Kernel IR format."; |
| 354 basicUsage = "Usage: $programName [options] patched_sdk fullOutput " |
| 355 "outlineOutput\n"; |
| 356 } |
| 357 StringBuffer sb = new StringBuffer(basicUsage); |
| 358 if (summary != null) { |
| 359 sb.writeln(); |
| 360 sb.writeln(summary); |
| 361 sb.writeln(); |
| 362 } |
| 363 sb.write(options); |
| 364 // TODO(ahe): Don't use [templateUnspecified]. |
| 365 return templateUnspecified.withArguments("$sb"); |
| 366 } |
OLD | NEW |