|
| 1 | +// Copyright (c) 2024, 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 | +import 'package:args/args.dart'; |
| 6 | + |
| 7 | +import 'package:front_end/src/api_unstable/ddc.dart' |
| 8 | + show parseExperimentalArguments; |
| 9 | +import 'package:path/path.dart' as p; |
| 10 | + |
| 11 | +import '../compiler/module_builder.dart'; |
| 12 | + |
| 13 | +/// Compiler options for the `dartdevc` backend. |
| 14 | +class Options { |
| 15 | + /// Whether to emit the source mapping file. |
| 16 | + /// |
| 17 | + /// This supports debugging the original source code instead of the generated |
| 18 | + /// code. |
| 19 | + final bool sourceMap; |
| 20 | + |
| 21 | + /// Whether to emit the source mapping file in the program text, so the |
| 22 | + /// runtime can enable synchronous stack trace deobfuscation. |
| 23 | + final bool inlineSourceMap; |
| 24 | + |
| 25 | + /// Whether to emit the full compiled kernel. |
| 26 | + /// |
| 27 | + /// This is used by expression compiler worker, launched from the debugger |
| 28 | + /// in webdev and google3 scenarios, for expression evaluation features. |
| 29 | + /// Full kernel for compiled files is needed to be able to compile |
| 30 | + /// expressions on demand in the current scope of a breakpoint. |
| 31 | + final bool emitFullCompiledKernel; |
| 32 | + |
| 33 | + /// Whether to emit a summary file containing API signatures. |
| 34 | + /// |
| 35 | + /// This is required for a modular build process. |
| 36 | + final bool summarizeApi; |
| 37 | + |
| 38 | + // Whether to enable assertions. |
| 39 | + final bool enableAsserts; |
| 40 | + |
| 41 | + /// Whether to compile code in a more permissive REPL mode allowing access |
| 42 | + /// to private members across library boundaries. |
| 43 | + /// |
| 44 | + /// This should only set `true` by our REPL compiler. |
| 45 | + bool replCompile; |
| 46 | + |
| 47 | + /// Whether to emit the debug metadata |
| 48 | + /// |
| 49 | + /// Debugger uses this information about to construct mapping between |
| 50 | + /// modules and libraries that otherwise requires expensive communication with |
| 51 | + /// the browser. |
| 52 | + final bool emitDebugMetadata; |
| 53 | + |
| 54 | + /// Whether to emit the debug symbols |
| 55 | + /// |
| 56 | + /// Debugger uses this information about to construct mapping between |
| 57 | + /// dart and js objects that otherwise requires expensive communication with |
| 58 | + /// the browser. |
| 59 | + final bool emitDebugSymbols; |
| 60 | + |
| 61 | + final Map<String, String> summaryModules; |
| 62 | + |
| 63 | + final List<ModuleFormat> moduleFormats; |
| 64 | + |
| 65 | + /// The name of the module. |
| 66 | + /// |
| 67 | + /// This is used to support file concatenation. The JS module will contain its |
| 68 | + /// module name inside itself, allowing it to declare the module name |
| 69 | + /// independently of the file. |
| 70 | + final String moduleName; |
| 71 | + |
| 72 | + /// Custom scheme to indicate a multi-root uri. |
| 73 | + final String multiRootScheme; |
| 74 | + |
| 75 | + /// Path to set multi-root files relative to when generating source-maps. |
| 76 | + final String? multiRootOutputPath; |
| 77 | + |
| 78 | + /// Experimental language features that are enabled/disabled, see |
| 79 | + /// [the spec](https://github.com/dart-lang/sdk/blob/master/docs/process/experimental-flags.md) |
| 80 | + /// for more details. |
| 81 | + final Map<String, bool> experiments; |
| 82 | + |
| 83 | + final bool soundNullSafety; |
| 84 | + |
| 85 | + /// Whether or not the `--canary` flag was specified during compilation. |
| 86 | + final bool canaryFeatures; |
| 87 | + |
| 88 | + /// When `true` the [Component] will be compiled into a format compatible with |
| 89 | + /// hot reload. |
| 90 | + /// |
| 91 | + /// The output will still be a single file containing each library in an |
| 92 | + /// isolated namespace. |
| 93 | + final bool emitLibraryBundle; |
| 94 | + |
| 95 | + /// Whether the compiler is generating a dynamic module. |
| 96 | + final bool dynamicModule; |
| 97 | + |
| 98 | + /// When `true` stars "*" will appear to represent legacy types when printing |
| 99 | + /// runtime types in the compiled application. |
| 100 | + final bool printLegacyStars = false; |
| 101 | + |
| 102 | + /// Raw precompiled macro options, each of the format |
| 103 | + /// `<program-uri>;<macro-library-uri>`. |
| 104 | + /// |
| 105 | + /// Multiple library URIs may be provided separated by additional semicolons. |
| 106 | + final List<String> precompiledMacros; |
| 107 | + |
| 108 | + /// The serialization mode to use for macro communication. |
| 109 | + final String? macroSerializationMode; |
| 110 | + |
| 111 | + Options( |
| 112 | + {this.sourceMap = true, |
| 113 | + this.inlineSourceMap = false, |
| 114 | + this.summarizeApi = true, |
| 115 | + this.enableAsserts = true, |
| 116 | + this.replCompile = false, |
| 117 | + this.emitDebugMetadata = false, |
| 118 | + this.emitDebugSymbols = false, |
| 119 | + this.emitFullCompiledKernel = false, |
| 120 | + this.summaryModules = const {}, |
| 121 | + this.moduleFormats = const [], |
| 122 | + required this.moduleName, |
| 123 | + this.multiRootScheme = 'org-dartlang-app', |
| 124 | + this.multiRootOutputPath, |
| 125 | + this.experiments = const {}, |
| 126 | + this.soundNullSafety = true, |
| 127 | + this.canaryFeatures = false, |
| 128 | + this.dynamicModule = false, |
| 129 | + this.precompiledMacros = const [], |
| 130 | + this.macroSerializationMode}) |
| 131 | + : emitLibraryBundle = canaryFeatures && |
| 132 | + moduleFormats.length == 1 && |
| 133 | + moduleFormats.single == ModuleFormat.ddc; |
| 134 | + |
| 135 | + Options.fromArguments(ArgResults args) |
| 136 | + : this( |
| 137 | + sourceMap: args['source-map'] as bool, |
| 138 | + inlineSourceMap: args['inline-source-map'] as bool, |
| 139 | + summarizeApi: args['summarize'] as bool, |
| 140 | + enableAsserts: args['enable-asserts'] as bool, |
| 141 | + replCompile: args['repl-compile'] as bool, |
| 142 | + emitDebugMetadata: args['experimental-emit-debug-metadata'] as bool, |
| 143 | + emitDebugSymbols: args['emit-debug-symbols'] as bool, |
| 144 | + emitFullCompiledKernel: |
| 145 | + args['experimental-output-compiled-kernel'] as bool, |
| 146 | + summaryModules: |
| 147 | + _parseCustomSummaryModules(args['summary'] as List<String>), |
| 148 | + moduleFormats: parseModuleFormatOption(args), |
| 149 | + moduleName: _getModuleName(args), |
| 150 | + multiRootScheme: args['multi-root-scheme'] as String, |
| 151 | + multiRootOutputPath: args['multi-root-output-path'] as String?, |
| 152 | + experiments: parseExperimentalArguments( |
| 153 | + args['enable-experiment'] as List<String>), |
| 154 | + soundNullSafety: args['sound-null-safety'] as bool, |
| 155 | + canaryFeatures: args['canary'] as bool, |
| 156 | + dynamicModule: args['dynamic-module'] as bool, |
| 157 | + precompiledMacros: args['precompiled-macro'] as List<String>, |
| 158 | + macroSerializationMode: |
| 159 | + args['macro-serialization-mode'] as String?); |
| 160 | + |
| 161 | + Options.fromSdkRequiredArguments(ArgResults args) |
| 162 | + : this( |
| 163 | + summarizeApi: false, |
| 164 | + moduleFormats: parseModuleFormatOption(args), |
| 165 | + // When compiling the SDK use dart_sdk as the default. This is the |
| 166 | + // assumed name in various places around the build systems. |
| 167 | + moduleName: |
| 168 | + args['module-name'] != null ? _getModuleName(args) : 'dart_sdk', |
| 169 | + multiRootScheme: args['multi-root-scheme'] as String, |
| 170 | + multiRootOutputPath: args['multi-root-output-path'] as String?, |
| 171 | + experiments: parseExperimentalArguments( |
| 172 | + args['enable-experiment'] as List<String>), |
| 173 | + soundNullSafety: args['sound-null-safety'] as bool, |
| 174 | + canaryFeatures: args['canary'] as bool); |
| 175 | + |
| 176 | + static void addArguments(ArgParser parser, {bool hide = true}) { |
| 177 | + addSdkRequiredArguments(parser, hide: hide); |
| 178 | + |
| 179 | + parser |
| 180 | + ..addMultiOption('summary', |
| 181 | + abbr: 's', |
| 182 | + help: 'API summary file(s) of imported libraries, optionally\n' |
| 183 | + 'with module import path: -s path.dill=js/import/path') |
| 184 | + ..addFlag('summarize', |
| 185 | + help: 'Emit an API summary file.', defaultsTo: true, hide: hide) |
| 186 | + ..addFlag('source-map', |
| 187 | + help: 'Emit source mapping.', defaultsTo: true, hide: hide) |
| 188 | + ..addFlag('inline-source-map', |
| 189 | + help: 'Emit source mapping inline.', defaultsTo: false, hide: hide) |
| 190 | + ..addFlag('enable-asserts', |
| 191 | + help: 'Enable assertions.', defaultsTo: true, hide: hide) |
| 192 | + ..addFlag('repl-compile', |
| 193 | + help: 'Compile in a more permissive REPL mode, allowing access' |
| 194 | + ' to private members across library boundaries. This should' |
| 195 | + ' only be used by debugging tools.', |
| 196 | + defaultsTo: false, |
| 197 | + hide: hide) |
| 198 | + // TODO(41852) Define a process for breaking changes before graduating from |
| 199 | + // experimental. |
| 200 | + ..addFlag('experimental-emit-debug-metadata', |
| 201 | + help: 'Experimental option for compiler development.\n' |
| 202 | + 'Output a metadata file for debug tools next to the .js output.', |
| 203 | + defaultsTo: false, |
| 204 | + hide: true) |
| 205 | + ..addFlag('emit-debug-symbols', |
| 206 | + help: 'Experimental option for compiler development.\n' |
| 207 | + 'Output a symbols file for debug tools next to the .js output.', |
| 208 | + defaultsTo: false, |
| 209 | + hide: true) |
| 210 | + ..addFlag('experimental-output-compiled-kernel', |
| 211 | + help: 'Experimental option for compiler development.\n' |
| 212 | + 'Output a full kernel file for currently compiled module next to ' |
| 213 | + 'the .js output.', |
| 214 | + defaultsTo: false, |
| 215 | + hide: true) |
| 216 | + ..addMultiOption('precompiled-macro', |
| 217 | + help: |
| 218 | + 'Configuration for precompiled macro binaries or kernel files.\n' |
| 219 | + 'The expected format of this option is as follows: ' |
| 220 | + '<absolute-path-to-binary>;<macro-library-uri>\nFor example: ' |
| 221 | + '--precompiled-macro="/path/to/compiled/macro;' |
| 222 | + 'package:some_macro/some_macro.dart". Multiple library uris may be ' |
| 223 | + 'passed as well (separated by semicolons).', |
| 224 | + hide: true) |
| 225 | + ..addOption('macro-serialization-mode', |
| 226 | + help: 'The serialization mode for communicating with macros.', |
| 227 | + allowed: ['bytedata', 'json'], |
| 228 | + defaultsTo: 'bytedata') |
| 229 | + ..addFlag('dynamic-module', |
| 230 | + help: 'Compile to generate a dynamic module', |
| 231 | + negatable: false, |
| 232 | + defaultsTo: false); |
| 233 | + } |
| 234 | + |
| 235 | + /// Adds only the arguments used to compile the SDK from a full dill file. |
| 236 | + /// |
| 237 | + /// NOTE: The 'module-name' option will have a special default value of |
| 238 | + /// 'dart_sdk' when compiling the SDK. |
| 239 | + /// See [SharedOptions.fromSdkRequiredArguments]. |
| 240 | + static void addSdkRequiredArguments(ArgParser parser, {bool hide = true}) { |
| 241 | + addModuleFormatOptions(parser, hide: hide); |
| 242 | + parser |
| 243 | + ..addMultiOption('out', abbr: 'o', help: 'Output file (required).') |
| 244 | + ..addOption('module-name', |
| 245 | + help: 'The output module name, used in some JS module formats.\n' |
| 246 | + 'Defaults to the output file name (without .js).') |
| 247 | + ..addOption('multi-root-scheme', |
| 248 | + help: 'The custom scheme to indicate a multi-root uri.', |
| 249 | + defaultsTo: 'org-dartlang-app') |
| 250 | + ..addOption('multi-root-output-path', |
| 251 | + help: 'Path to set multi-root files relative to when generating' |
| 252 | + ' source-maps.', |
| 253 | + hide: true) |
| 254 | + ..addMultiOption('enable-experiment', |
| 255 | + help: 'Enable/disable experimental language features.', hide: hide) |
| 256 | + ..addFlag('sound-null-safety', |
| 257 | + help: 'Compile for sound null safety at runtime.', |
| 258 | + negatable: true, |
| 259 | + defaultsTo: true) |
| 260 | + ..addFlag('canary', |
| 261 | + help: 'Enable all compiler features under active development. ' |
| 262 | + 'This option is intended for compiler development only. ' |
| 263 | + 'Canary features are likely to be unstable and can be removed ' |
| 264 | + 'without warning.', |
| 265 | + defaultsTo: false, |
| 266 | + hide: true); |
| 267 | + } |
| 268 | + |
| 269 | + static String _getModuleName(ArgResults args) { |
| 270 | + var moduleName = args['module-name'] as String?; |
| 271 | + if (moduleName == null) { |
| 272 | + var outPaths = args['out'] as List<String>; |
| 273 | + if (outPaths.isEmpty) { |
| 274 | + throw UnsupportedError( |
| 275 | + 'No module name provided and unable to synthesize one without any ' |
| 276 | + 'output paths.'); |
| 277 | + } |
| 278 | + var outPath = outPaths.first; |
| 279 | + moduleName = p.basenameWithoutExtension(outPath); |
| 280 | + } |
| 281 | + // TODO(jmesserly): this should probably use sourcePathToUri. |
| 282 | + // |
| 283 | + // Also we should not need this logic if the user passed in the module name |
| 284 | + // explicitly. It is here for backwards compatibility until we can confirm |
| 285 | + // that build systems do not depend on passing windows-style paths here. |
| 286 | + return p.toUri(moduleName).toString(); |
| 287 | + } |
| 288 | +} |
| 289 | + |
| 290 | +/// Finds explicit module names of the form `path=name` in [summaryPaths], |
| 291 | +/// and returns the path to mapping in an ordered map from `path` to `name`. |
| 292 | +/// |
| 293 | +/// A summary path can contain "=" followed by an explicit module name to |
| 294 | +/// allow working with summaries whose physical location is outside of the |
| 295 | +/// module root directory. |
| 296 | +Map<String, String> _parseCustomSummaryModules(List<String> summaryPaths, |
| 297 | + [String? moduleRoot, String? summaryExt]) { |
| 298 | + var pathToModule = <String, String>{}; |
| 299 | + for (var summaryPath in summaryPaths) { |
| 300 | + var equalSign = summaryPath.indexOf('='); |
| 301 | + String modulePath; |
| 302 | + var summaryPathWithoutExt = summaryExt != null |
| 303 | + ? summaryPath.substring( |
| 304 | + 0, |
| 305 | + // Strip off the extension, including the last `.`. |
| 306 | + summaryPath.length - (summaryExt.length + 1)) |
| 307 | + : p.withoutExtension(summaryPath); |
| 308 | + if (equalSign != -1) { |
| 309 | + modulePath = summaryPath.substring(equalSign + 1); |
| 310 | + summaryPath = summaryPath.substring(0, equalSign); |
| 311 | + } else if (moduleRoot != null && p.isWithin(moduleRoot, summaryPath)) { |
| 312 | + // TODO: Determine if this logic is still needed. |
| 313 | + modulePath = p.url.joinAll( |
| 314 | + p.split(p.relative(summaryPathWithoutExt, from: moduleRoot))); |
| 315 | + } else { |
| 316 | + modulePath = p.basename(summaryPathWithoutExt); |
| 317 | + } |
| 318 | + pathToModule[summaryPath] = modulePath; |
| 319 | + } |
| 320 | + return pathToModule; |
| 321 | +} |
| 322 | + |
| 323 | +/// Taken from analyzer to implement `--ignore-unrecognized-flags` |
| 324 | +List<String> filterUnknownArguments(List<String> args, ArgParser parser) { |
| 325 | + if (!args.contains('--ignore-unrecognized-flags')) return args; |
| 326 | + |
| 327 | + var knownOptions = <String>{}; |
| 328 | + var knownAbbreviations = <String>{}; |
| 329 | + parser.options.forEach((String name, Option option) { |
| 330 | + knownOptions.add(name); |
| 331 | + var abbreviation = option.abbr; |
| 332 | + if (abbreviation != null) { |
| 333 | + knownAbbreviations.add(abbreviation); |
| 334 | + } |
| 335 | + if (option.negatable != null && option.negatable!) { |
| 336 | + knownOptions.add('no-$name'); |
| 337 | + } |
| 338 | + }); |
| 339 | + |
| 340 | + String optionName(int prefixLength, String arg) { |
| 341 | + var equalsOffset = arg.lastIndexOf('='); |
| 342 | + if (equalsOffset < 0) { |
| 343 | + return arg.substring(prefixLength); |
| 344 | + } |
| 345 | + return arg.substring(prefixLength, equalsOffset); |
| 346 | + } |
| 347 | + |
| 348 | + var filtered = <String>[]; |
| 349 | + for (var arg in args) { |
| 350 | + if (arg.startsWith('--') && arg.length > 2) { |
| 351 | + if (knownOptions.contains(optionName(2, arg))) { |
| 352 | + filtered.add(arg); |
| 353 | + } |
| 354 | + } else if (arg.startsWith('-') && arg.length > 1) { |
| 355 | + if (knownAbbreviations.contains(optionName(1, arg))) { |
| 356 | + filtered.add(arg); |
| 357 | + } |
| 358 | + } else { |
| 359 | + filtered.add(arg); |
| 360 | + } |
| 361 | + } |
| 362 | + return filtered; |
| 363 | +} |
0 commit comments