| // 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. |
| |
| library; |
| |
| import 'package:collection/collection.dart'; |
| // ignore: implementation_imports |
| import 'package:front_end/src/api_unstable/dart2js.dart' as fe; |
| |
| import 'commandline_options.dart' show Flags; |
| import 'util/util.dart'; |
| |
| enum FeatureStatus { shipped, shipping, canary } |
| |
| enum CompilerPhase { |
| cfe, |
| closedWorld, |
| globalInference, |
| codegen, |
| emitJs, |
| dumpInfo, |
| } |
| |
| enum CompilerStage { |
| all( |
| 'all', |
| phases: { |
| CompilerPhase.cfe, |
| CompilerPhase.closedWorld, |
| CompilerPhase.globalInference, |
| CompilerPhase.codegen, |
| CompilerPhase.emitJs, |
| }, |
| canCompileFromEntryUri: true, |
| ), |
| dumpInfoAll( |
| 'dump-info-all', |
| phases: { |
| CompilerPhase.cfe, |
| CompilerPhase.closedWorld, |
| CompilerPhase.globalInference, |
| CompilerPhase.codegen, |
| CompilerPhase.emitJs, |
| CompilerPhase.dumpInfo, |
| }, |
| canCompileFromEntryUri: true, |
| ), |
| cfe('cfe', phases: {CompilerPhase.cfe}, canCompileFromEntryUri: true), |
| deferredLoadIds( |
| 'deferred-load-ids', |
| dataOutputName: 'deferred_load_ids.data', |
| phases: {CompilerPhase.closedWorld}, |
| ), |
| closedWorld( |
| 'closed-world', |
| dataOutputName: 'world.data', |
| phases: {CompilerPhase.closedWorld}, |
| ), |
| globalInference( |
| 'global-inference', |
| dataOutputName: 'global.data', |
| phases: {CompilerPhase.globalInference}, |
| ), |
| codegenAndJsEmitter( |
| 'codegen-emit-js', |
| phases: {CompilerPhase.codegen, CompilerPhase.emitJs}, |
| ), |
| codegenSharded( |
| 'codegen', |
| dataOutputName: 'codegen', |
| phases: {CompilerPhase.codegen}, |
| ), |
| jsEmitter('emit-js', phases: {CompilerPhase.emitJs}), |
| dumpInfo( |
| 'dump-info', |
| dataOutputName: 'dump.data', |
| phases: {CompilerPhase.dumpInfo}, |
| ); |
| |
| const CompilerStage( |
| this._stageFlag, { |
| this.dataOutputName, |
| required this.phases, |
| this.canCompileFromEntryUri = false, |
| }); |
| |
| final Set<CompilerPhase> phases; |
| final String _stageFlag; |
| final String? dataOutputName; |
| final bool canCompileFromEntryUri; |
| |
| bool get emitsJs => phases.contains(CompilerPhase.emitJs); |
| bool get shouldOnlyComputeDill => this == CompilerStage.cfe; |
| bool get canEmitDill => |
| this == CompilerStage.cfe || this == CompilerStage.closedWorld; |
| bool get shouldReadPlatformBinaries => phases.contains(CompilerPhase.cfe); |
| bool get emitsDumpInfo => phases.contains(CompilerPhase.dumpInfo); |
| bool get emitsDeferredLoadIds => this == CompilerStage.deferredLoadIds; |
| |
| /// Global kernel transformations should be run in phase 0b, i.e. after |
| /// concatenating dills, but before serializing the output of phase 0. |
| // TODO(fishythefish): Add AST metadata to ensure transformations aren't rerun |
| // unnecessarily. |
| bool get shouldRunGlobalTransforms => phases.contains(CompilerPhase.cfe); |
| |
| bool get shouldReadClosedWorld => index > CompilerStage.closedWorld.index; |
| bool get shouldReadGlobalInference => |
| index > CompilerStage.globalInference.index; |
| bool get shouldReadCodegenShards => |
| index > CompilerStage.codegenSharded.index; |
| bool get shouldReadDumpInfoData => this == CompilerStage.dumpInfo; |
| bool get shouldWriteDumpInfoData => |
| this == CompilerStage.jsEmitter || |
| this == CompilerStage.codegenAndJsEmitter; |
| bool get shouldWriteClosedWorld => this == CompilerStage.closedWorld; |
| bool get shouldWriteGlobalInference => this == CompilerStage.globalInference; |
| bool get shouldWriteCodegen => this == CompilerStage.codegenSharded; |
| |
| // Only use deferred reads for the linker and dump info phase as most deferred |
| // entities will not be needed. In other phases we use most of this data so |
| // it's not worth deferring. |
| bool get shouldUseDeferredSourceReads => |
| this == CompilerStage.jsEmitter || this == CompilerStage.dumpInfo; |
| |
| String get toFlag => _stageFlag; |
| |
| static String get validFlagValuesString { |
| return CompilerStage.values.map((p) => '`${p._stageFlag}`').join(', '); |
| } |
| |
| static CompilerStage _fromFlagString(String stageFlag) { |
| for (final stage in CompilerStage.values) { |
| if (stageFlag == stage._stageFlag) { |
| return stage; |
| } |
| } |
| throw ArgumentError( |
| 'Invalid stage: $stageFlag. ' |
| 'Supported values are: $validFlagValuesString', |
| ); |
| } |
| |
| /// Can be used from outside the compiler to determine which stage will run |
| /// based on provided flag. |
| /// |
| /// Used for internal build systems. |
| static CompilerStage fromFlag(String? stageFlag) { |
| return stageFlag == null ? CompilerStage.all : _fromFlagString(stageFlag); |
| } |
| |
| static CompilerStage fromOptions(CompilerOptions options) { |
| final stageFlag = options._stageFlag; |
| if (stageFlag == null) { |
| return options._dumpInfoFormatOption != null |
| ? CompilerStage.dumpInfoAll |
| : CompilerStage.all; |
| } |
| return _fromFlagString(stageFlag); |
| } |
| } |
| |
| /// A [FeatureOption] is both a set of flags and an option. By default, creating |
| /// a [FeatureOption] will create two flags, `--$flag` and `--no-$flag`. The |
| /// default behavior for a [FeatureOption] in the [FeatureOptions.canary] set is |
| /// to be disabled by default unless explicity enabled or `--canary` is passed. |
| /// When the [FeatureOption] is moved to [FeatureOptions.shipping], the behavior |
| /// flips, and by default it is enabled unless explicitly disabled or |
| /// `--no-shipping` is passed. The [FeatureOption.isNegativeFlag] bool flips |
| /// things around so while in canary the [FeatureOption] is enabled unless |
| /// explicitly disabled, and while in [FeatureOptions.shipping] it is disabled |
| /// unless explicitly enabled. |
| /// |
| /// Finally, mature features can be moved to [FeatureOptions.shipped], at which |
| /// point we ignore the flag, but throw if the value of the flag is |
| /// unexpected(i.e. if a positive flag is disabled, or a negative flag is |
| /// enabled). |
| class FeatureOption { |
| final String flag; |
| final bool isNegativeFlag; |
| bool? _state; |
| bool get isEnabled => _state!; |
| bool get isDisabled => !isEnabled; |
| set state(bool value) { |
| assert(_state == null); |
| _state = value; |
| } |
| |
| set override(bool value) { |
| assert(_state != null); |
| _state = value; |
| } |
| |
| FeatureOption(this.flag, {this.isNegativeFlag = false}); |
| } |
| |
| /// A class to simplify management of features which will end up being enabled |
| /// by default. New features should be added as properties, and then to the |
| /// [canary] list. Features in [canary] default to disabled unless they are |
| /// explicitly enabled or unless `--canary` is passed on the commandline. When |
| /// a feature is ready to ship, it should be moved to the [shipping] list, |
| /// whereupon it will immediately default to enabled but can still be disabled. |
| /// Once a feature is shipped, it can be deleted from this class entirely. |
| class FeatureOptions { |
| /// Whether to restrict the generated JavaScript to features that work on the |
| /// oldest supported versions of JavaScript. This currently means IE11. If |
| /// `true`, the generated code runs on the legacy JavaScript platform. If |
| /// `false`, the code will fail on the legacy JavaScript platform. |
| FeatureOption legacyJavaScript = FeatureOption( |
| 'legacy-javascript', |
| isNegativeFlag: true, |
| ); |
| |
| /// Whether to use optimized holders. |
| FeatureOption newHolders = FeatureOption('new-holders'); |
| |
| /// Whether to generate code compliant with Content Security Policy. |
| FeatureOption useContentSecurityPolicy = FeatureOption('csp'); |
| |
| /// Whether to emit JavaScript encoded as UTF-8. |
| FeatureOption writeUtf8 = FeatureOption('utf8'); |
| |
| /// Experimental instrumentation to add tree shaking information to |
| /// dump-info's output. |
| FeatureOption newDumpInfo = FeatureOption('new-dump-info'); |
| |
| /// Whether to implement some simple async functions using Futures directly |
| /// to reduce generated code size. |
| FeatureOption simpleAsyncToFuture = FeatureOption('simple-async-to-future'); |
| |
| /// Whether or not the CFE should evaluate constants. |
| FeatureOption cfeConstants = FeatureOption('cfe-constants'); |
| |
| /// Whether or not to intern composite values during deserialization |
| /// (e.g. DartType). |
| FeatureOption internValues = FeatureOption('intern-composite-values'); |
| |
| /// [FeatureOption]s which are shipped and cannot be toggled. |
| late final List<FeatureOption> shipped = [newHolders, legacyJavaScript]; |
| |
| /// [FeatureOption]s which default to enabled. |
| late final List<FeatureOption> shipping = [ |
| useContentSecurityPolicy, |
| internValues, |
| ]; |
| |
| /// [FeatureOption]s which default to disabled. |
| late final List<FeatureOption> canary = [ |
| writeUtf8, |
| newDumpInfo, |
| simpleAsyncToFuture, |
| cfeConstants, |
| ]; |
| |
| /// Forces canary feature on. This must run after [Option].parse. |
| void forceCanary() { |
| for (var feature in canary) { |
| feature.override = feature.isNegativeFlag ? false : true; |
| } |
| } |
| |
| /// Returns a list of enabled features as a comma separated string. |
| String flavorString() { |
| bool shouldPrint(FeatureOption feature) { |
| return feature.isNegativeFlag ? feature.isDisabled : feature.isEnabled; |
| } |
| |
| String toString(FeatureOption feature) { |
| return feature.isNegativeFlag ? 'no-${feature.flag}' : feature.flag; |
| } |
| |
| Iterable<String> listToString(List<FeatureOption> options) { |
| return options.where(shouldPrint).map(toString); |
| } |
| |
| return listToString(shipping).followedBy(listToString(canary)).join(', '); |
| } |
| |
| /// Parses a [List<String>] and enables / disables features as necessary. |
| void parse(List<String> options) { |
| _verifyShippedFeatures(options, shipped); |
| _extractFeatures(options, shipping, FeatureStatus.shipping); |
| _extractFeatures(options, canary, FeatureStatus.canary); |
| } |
| } |
| |
| /// Options used for controlling diagnostic messages. |
| abstract class DiagnosticOptions { |
| const DiagnosticOptions(); |
| |
| /// If `true`, warnings cause the compilation to fail. |
| bool get fatalWarnings; |
| |
| /// Emit terse diagnostics without howToFix. |
| bool get terseDiagnostics; |
| |
| /// If `true`, warnings are not reported. |
| bool get suppressWarnings; |
| |
| /// If `true`, hints are not reported. |
| bool get suppressHints; |
| |
| /// Returns `true` if warnings and hints are shown for all packages. |
| bool get showAllPackageWarnings; |
| |
| /// Returns `true` if warnings and hints are hidden for all packages. |
| bool get hidePackageWarnings; |
| |
| /// Returns `true` if warnings should be should for [uri]. |
| bool showPackageWarningsFor(Uri uri); |
| } |
| |
| enum DumpInfoFormat { binary, json } |
| |
| /// Object for passing options to the compiler. Superclasses are used to select |
| /// subsets of these options, enabling each part of the compiler to depend on |
| /// as few as possible. |
| class CompilerOptions implements DiagnosticOptions { |
| /// The entry point of the application that is being compiled. |
| Uri? entryUri; |
| |
| /// The input dill to compile. |
| Uri? _inputDillUri; |
| |
| Uri get _defaultInputDillUri => |
| _outputDir.resolve('${_outputPrefix}out.dill'); |
| |
| Uri get inputDillUri { |
| return _inputDillUri != null |
| ? fe.nativeToUri(_inputDillUri.toString()) |
| : _defaultInputDillUri; |
| } |
| |
| /// Returns the compilation target specified by these options. |
| Uri get compilationTarget => |
| _inputDillUri ?? |
| (stage.canCompileFromEntryUri ? entryUri : null) ?? |
| _defaultInputDillUri; |
| |
| bool get shouldLoadFromDill => |
| entryUri == null || compilationTarget.path.endsWith('.dill'); |
| |
| /// Location of the package configuration file. |
| Uri? packageConfig; |
| |
| /// List of kernel files to load. |
| /// |
| /// This contains all kernel files that form part of the final program. The |
| /// dills passed here should contain full kernel ASTs, not just outlines. |
| List<Uri>? dillDependencies; |
| |
| /// Uses a memory mapped view of files for I/O. |
| bool memoryMappedFiles = false; |
| |
| /// Location from which serialized inference data is read/written. |
| Uri? _globalInferenceUri; |
| |
| /// Location from which the serialized closed world is read/written. |
| Uri? _closedWorldUri; |
| |
| /// Location from which codegen data is read/written. |
| Uri? _codegenUri; |
| |
| // TODO(natebiggs): Delete this once Flutter is using the stage flag. |
| /// Whether to run only the CFE and emit the generated kernel file in |
| /// [outputUri]. Equivalent to `--stage=cfe`. |
| bool _cfeOnly = false; |
| |
| /// Which stage of the compiler to run. Maps to a stage from [CompilerStage]. |
| String? _stageFlag; |
| |
| /// Flag only meant for dart2js developers to iterate on global inference |
| /// changes. |
| /// |
| /// When working on large apps this flag allows to load serialized data for |
| /// the app (via --read-data), reuse its closed world, and rerun the global |
| /// inference stage (even though the serialized data already contains a global |
| /// inference result). |
| bool debugGlobalInference = false; |
| |
| /// Resolved constant "environment" values passed to the compiler via the `-D` |
| /// flags. |
| Map<String, String> environment = const <String, String>{}; |
| |
| /// Flags enabling language experiments. |
| Map<fe.ExperimentalFlag, bool> explicitExperimentalFlags = {}; |
| |
| /// `true` if variance is enabled. |
| bool get enableVariance => fe.isExperimentEnabled( |
| fe.ExperimentalFlag.variance, |
| explicitExperimentalFlags: explicitExperimentalFlags, |
| ); |
| |
| /// A possibly null state object for kernel compilation. |
| fe.InitializedCompilerState? kernelInitializedCompilerState; |
| |
| /// Whether we allow mocking compilation of libraries such as dart:io and |
| /// dart:html for unit testing purposes. |
| bool allowMockCompilation = false; |
| |
| /// Sets a combination of flags for benchmarking 'production' mode. |
| bool benchmarkingProduction = false; |
| |
| /// Sets a combination of flags for benchmarking 'experiment' mode. |
| bool benchmarkingExperiment = false; |
| |
| /// ID associated with this sdk build. |
| String buildId = _undeterminedBuildID; |
| |
| /// Whether there is a build-id available so we can use it on error messages |
| /// and in the emitted output of the compiler. |
| bool get hasBuildId => buildId != _undeterminedBuildID; |
| |
| /// Whether to compile for the server category. This is used to compile to JS |
| /// that is intended to be run on server-side VMs like nodejs. |
| bool compileForServer = false; |
| |
| /// Location where to generate a map containing details of how deferred |
| /// libraries are subdivided. |
| Uri? deferredMapUri; |
| |
| /// Location to generate a map containing mapping from user-defined deferred |
| /// import to Dart2js runtime load ID name. |
| Uri? _deferredLoadIdMapUri; |
| |
| /// Location where to generate an internal format representing the deferred |
| /// graph. |
| Uri? deferredGraphUri; |
| |
| /// The maximum number of deferred fragments to generate. If the number of |
| /// fragments exceeds this amount, then they may be merged. |
| /// Note: Currently, we only merge fragments in a single dependency chain. We |
| /// will not merge fragments with unrelated dependencies and thus we may |
| /// generate more fragments than the 'mergeFragmentsThreshold' under some |
| /// situations. |
| int? mergeFragmentsThreshold; // default value, no max. |
| int? _mergeFragmentsThreshold; |
| |
| /// Whether to disable inlining during the backend optimizations. |
| // TODO(sigmund): negate, so all flags are positive |
| bool disableInlining = false; |
| |
| /// Disable deferred loading, instead generate everything in one output unit. |
| /// Note: the resulting program still correctly checks that loadLibrary & |
| /// checkLibrary calls are correct. |
| bool disableProgramSplit = false; |
| |
| /// Reads a program split json file and applies the parsed constraints to |
| /// deferred loading. |
| Uri? readProgramSplit; |
| |
| /// Diagnostic option: If `true`, warnings cause the compilation to fail. |
| @override |
| bool fatalWarnings = false; |
| |
| /// Diagnostic option: Emit terse diagnostics without howToFix. |
| @override |
| bool terseDiagnostics = false; |
| |
| /// Diagnostic option: If `true`, warnings are not reported. |
| @override |
| bool suppressWarnings = false; |
| |
| /// Diagnostic option: If `true`, hints are not reported. |
| @override |
| bool suppressHints = false; |
| |
| /// Diagnostic option: List of packages for which warnings and hints are |
| /// reported. If `null`, no package warnings or hints are reported. If |
| /// empty, all warnings and hints are reported. |
| List<String>? shownPackageWarnings; |
| |
| /// Whether to disable global type inference. |
| bool disableTypeInference = false; |
| |
| /// Whether to use the trivial abstract value domain. |
| bool useTrivialAbstractValueDomain = false; |
| |
| /// Whether to use the wrapped abstract value domain (experimental). |
| bool experimentalWrapped = false; |
| |
| /// Whether to use the powersets abstract value domain (experimental). |
| bool experimentalPowersets = false; |
| |
| /// Whether to disable optimization for need runtime type information. |
| bool disableRtiOptimization = false; |
| |
| /// Uri to read/write dump info requisite data after emitting JS. This |
| /// contains data captured from the JS printer and processed for the dump info |
| /// task. |
| /// The file emitted to the URI can then be read in using to run dump info as |
| /// a standalone task (without re-emitting JS). |
| Uri? _dumpInfoDataUri; |
| |
| /// Which format the user has chosen to emit dump info in if any. |
| DumpInfoFormat? _dumpInfoFormatOption; |
| DumpInfoFormat get dumpInfoFormat => |
| _dumpInfoFormatOption ?? DumpInfoFormat.json; |
| |
| /// If set, SSA intermediate form is dumped for methods with names matching |
| /// this RegExp pattern. |
| String? dumpSsaPattern; |
| |
| /// Whether to generate a `.resources.json` file detailing the use of resource |
| /// identifiers. |
| bool writeResources = false; |
| |
| /// Whether we allow passing an extra argument to `assert`, containing a |
| /// reason for why an assertion fails. (experimental) |
| /// |
| /// This is only included so that tests can pass the --assert-message flag |
| /// without causing dart2js to crash. The flag has no effect. |
| bool enableAssertMessage = true; |
| |
| /// Whether to enable minification |
| // TODO(sigmund): rename to minify |
| bool enableMinification = false; |
| |
| /// Flag to turn off minification even if enabled elsewhere, e.g. via |
| /// -O2. Both [enableMinification] and [_disableMinification] can be true, in |
| /// which case [_disableMinification] wins. |
| bool _disableMinification = false; |
| |
| /// Whether to omit names of late variables from error messages. |
| bool omitLateNames = false; |
| |
| /// Flag to turn off `omitLateNames` even if enabled elsewhere, e.g. via |
| /// `-O2`. Both [omitLateNames] and [_noOmitLateNames] can be true, in which |
| /// case [_noOmitLateNames] wins. |
| bool _noOmitLateNames = false; |
| |
| /// Whether to model which native classes are live based on annotations on the |
| /// core libraries. If false, all native classes will be included by default. |
| bool enableNativeLiveTypeAnalysis = true; |
| |
| /// Whether to generate code containing user's `assert` statements. |
| bool enableUserAssertions = false; |
| |
| /// Whether to generate code asserting that non-nullable return values of |
| /// `@Native` methods or `JS()` invocations are checked for being non-null. |
| bool nativeNullAssertions = false; |
| bool _noNativeNullAssertions = false; |
| |
| /// Whether to generate code asserting that return values of JS-interop APIs |
| /// with non-nullable return types are not null. |
| bool interopNullAssertions = false; |
| bool _noInteropNullAssertions = false; |
| |
| /// Whether to generate a source-map file together with the output program. |
| bool generateSourceMap = true; |
| |
| /// Location of the libraries specification file. |
| Uri? librariesSpecificationUri; |
| |
| /// Location of the kernel platform `.dill` files. |
| Uri? platformBinaries; |
| |
| /// URI where the compiler should generate the output source map file. |
| Uri? sourceMapUri; |
| |
| /// The compiler is run from the build bot. |
| bool testMode = false; |
| |
| /// Whether to use the development type inferrer. |
| bool experimentalInferrer = false; |
| |
| /// Whether to trust primitive types during inference and optimizations. |
| bool trustPrimitives = false; |
| |
| /// Whether to omit implicit strong mode checks. |
| bool omitImplicitChecks = false; |
| |
| /// Whether to omit as casts by default. |
| bool omitAsCasts = false; |
| |
| /// Whether to omit class type arguments only needed for `toString` on |
| /// `Object.runtimeType`. |
| bool laxRuntimeTypeToString = false; |
| |
| /// What should the compiler do with parameter type assertions. |
| /// |
| /// This is an internal configuration option derived from other flags. |
| late CheckPolicy defaultParameterCheckPolicy; |
| |
| /// What should the compiler do with implicit downcasts. |
| /// |
| /// This is an internal configuration option derived from other flags. |
| late CheckPolicy defaultImplicitDowncastCheckPolicy; |
| |
| /// What the compiler should do with a boolean value in a condition context |
| /// when the language specification says it is a runtime error for it to be |
| /// null. |
| /// |
| /// This is an internal configuration option derived from other flags. |
| late CheckPolicy defaultConditionCheckPolicy; |
| |
| /// What should the compiler do with explicit casts. |
| /// |
| /// This is an internal configuration option derived from other flags. |
| late CheckPolicy defaultExplicitCastCheckPolicy; |
| |
| /// What should the compiler do with List index bounds checks. |
| /// |
| /// This is an internal configuration option derived from other flags. |
| late CheckPolicy defaultIndexBoundsCheckPolicy; |
| |
| /// When obfuscating for minification, whether to use the frequency of a name |
| /// as an heuristic to pick shorter names. |
| bool useFrequencyNamer = true; |
| |
| /// Whether to generate source-information from both the old and the new |
| /// source-information engines. (experimental) |
| bool useMultiSourceInfo = false; |
| |
| /// Whether to use the new source-information implementation for source-maps. |
| /// (experimental) |
| bool useNewSourceInfo = false; |
| |
| /// Whether or not use simple load ids. |
| bool useSimpleLoadIds = false; |
| |
| /// Enable verbose printing during compilation. Includes a time-breakdown |
| /// between phases at the end. |
| bool verbose = false; |
| |
| /// On top of --verbose, enable more verbose printing, like progress messages |
| /// during each phase of compilation. |
| bool showInternalProgress = false; |
| |
| /// Enable printing of metrics at end of compilation. |
| // TODO(sra): Add command-line filtering of metrics. |
| bool reportPrimaryMetrics = false; |
| |
| /// Enable printing of more metrics at end of compilation. |
| // TODO(sra): Add command-line filtering of metrics. |
| bool reportSecondaryMetrics = false; |
| |
| /// Track allocations in the JS output. |
| /// |
| /// This is an experimental feature. |
| bool experimentalTrackAllocations = false; |
| |
| /// Experimental part file function generation. |
| bool experimentStartupFunctions = false; |
| |
| /// Experimental reliance on JavaScript ToBoolean conversions. |
| bool experimentToBoolean = false; |
| |
| // Experiment to make methods that are inferred as unreachable throw an |
| // exception rather than generate suspect code. |
| bool experimentUnreachableMethodsThrow = false; |
| |
| /// Experimental instrumentation to investigate code bloat. |
| /// |
| /// If [true], the compiler will emit code that logs whenever a method is |
| /// called. |
| bool experimentCallInstrumentation = false; |
| |
| /// If specified, a bundle of optimizations to enable (or disable). |
| int? optimizationLevel; |
| |
| /// The shard to serialize when running the codegen phase. |
| int? codegenShard; |
| |
| /// The number of shards to serialize when running the codegen phase or to |
| /// deserialize when running the emit-js phase. |
| int? codegenShards; |
| |
| /// Arguments passed to the front end about how it is invoked. |
| /// |
| /// This is used to selectively emit certain messages depending on how the |
| /// CFE is invoked. |
| /// |
| /// See `InvocationMode` in |
| /// `pkg/front_end/lib/src/api_prototype/compiler_options.dart` for all |
| /// possible options. |
| Set<fe.InvocationMode> cfeInvocationModes = {}; |
| |
| /// Verbosity level used for filtering messages during compilation. |
| fe.Verbosity verbosity = fe.Verbosity.all; |
| |
| // Whether or not to dump a list of unused libraries. |
| bool dumpUnusedLibraries = false; |
| |
| // Whether or not to disable byte cache for sources loaded from Kernel dill. |
| bool disableDiagnosticByteCache = false; |
| |
| bool enableProtoShaking = false; |
| bool enableProtoMixinShaking = false; |
| |
| bool get producesModifiedDill => |
| stage == CompilerStage.closedWorld && enableProtoShaking; |
| |
| late final CompilerStage stage = _calculateStage(); |
| |
| CompilerStage _calculateStage() => |
| _cfeOnly ? CompilerStage.cfe : CompilerStage.fromOptions(this); |
| |
| Uri? _outputUri; |
| Uri? outputUri; |
| |
| String get _outputFilename => _outputUri?.pathSegments.last ?? ''; |
| |
| String? get _outputExtension { |
| switch (stage) { |
| case CompilerStage.all: |
| case CompilerStage.dumpInfoAll: |
| case CompilerStage.jsEmitter: |
| case CompilerStage.codegenAndJsEmitter: |
| case CompilerStage.dumpInfo: |
| return '.js'; |
| case CompilerStage.cfe: |
| return '.dill'; |
| case CompilerStage.closedWorld: |
| if (producesModifiedDill) return '.dill'; |
| case CompilerStage.deferredLoadIds: |
| case CompilerStage.globalInference: |
| case CompilerStage.codegenSharded: |
| } |
| return null; |
| } |
| |
| /// Output prefix specified by the user via the `--out` flag. The prefix is |
| /// calculated from the final segment of the user provided URI. If the |
| /// extension does not match the expected extension for the current [stage] |
| /// then the last segment is treated as a prefix. Only set when `--stage` is |
| /// specified. |
| late final String _outputPrefix = |
| (() { |
| if (_stageFlag == null) return ''; |
| final extension = _outputExtension; |
| |
| return (extension != null && _outputFilename.endsWith(extension)) |
| ? '' |
| : _outputFilename; |
| })(); |
| |
| /// Output directory specified by the user via the `--out` flag. The directory |
| /// is calculated by resolving the substring prior to the final URI segment |
| /// (i.e. before the final slash) relative to [Uri.base]. Defaults to |
| /// [Uri.base] if `--out` is not provided or does not include a directory. |
| late final Uri _outputDir = |
| (() => |
| (_outputUri != null) |
| ? Uri.base.resolveUri(_outputUri!).resolve('.') |
| : Uri.base)(); |
| |
| /// Computes a resolved output URI based on value provided via the `--out` |
| /// flag. Updates [outputUri] based on the result and returns the value. |
| Uri? setResolvedOutputUri() { |
| final extension = _outputExtension; |
| if (extension == null) return null; |
| |
| if (_stageFlag == null) { |
| return outputUri = _outputDir.resolve( |
| _outputFilename.isEmpty ? 'out$extension' : _outputFilename, |
| ); |
| } |
| |
| String fullName = _outputFilename; |
| if (!fullName.endsWith(extension)) { |
| fullName += 'out$extension'; |
| } |
| return outputUri = _outputDir.resolve(fullName); |
| } |
| |
| /// Sets [outputUri] to the value provided via `--out` without any processing. |
| void setDefaultOutputUriForTesting() { |
| outputUri = _outputUri; |
| } |
| |
| Uri? _getSpecifiedDataPath(CompilerStage stage) { |
| switch (stage) { |
| case CompilerStage.all: |
| case CompilerStage.dumpInfoAll: |
| case CompilerStage.cfe: |
| case CompilerStage.jsEmitter: |
| case CompilerStage.codegenAndJsEmitter: |
| return null; |
| case CompilerStage.deferredLoadIds: |
| return _deferredLoadIdMapUri; |
| case CompilerStage.closedWorld: |
| return _closedWorldUri; |
| case CompilerStage.globalInference: |
| return _globalInferenceUri; |
| case CompilerStage.codegenSharded: |
| return _codegenUri; |
| case CompilerStage.dumpInfo: |
| return _dumpInfoDataUri; |
| } |
| } |
| |
| Uri dataUriForStage(CompilerStage stage) { |
| final dataUri = _getSpecifiedDataPath(stage); |
| if (dataUri != null) return dataUri; |
| |
| if (stage.dataOutputName != null) { |
| final filename = '$_outputPrefix${stage.dataOutputName}'; |
| return _outputDir.resolve(filename); |
| } |
| throw ArgumentError('No data input generated for stage: $stage'); |
| } |
| |
| late FeatureOptions features; |
| |
| // ------------------------------------------------- |
| // Options for deprecated features |
| // ------------------------------------------------- |
| |
| /// Create an options object by parsing flags from [options]. |
| static CompilerOptions parse( |
| List<String> options, { |
| FeatureOptions? featureOptions, |
| Uri? librariesSpecificationUri, |
| Uri? platformBinaries, |
| bool useDefaultOutputUri = false, |
| void Function(String)? onError, |
| void Function(String)? onWarning, |
| }) { |
| featureOptions ??= FeatureOptions(); |
| featureOptions.parse(options); |
| Map<fe.ExperimentalFlag, bool> explicitExperimentalFlags = |
| _extractExperiments(options, onError: onError, onWarning: onWarning); |
| |
| // We may require different experiments for compiling user code vs. the sdk. |
| // To simplify things, we prebuild the sdk with the correct flags. |
| platformBinaries ??= fe.computePlatformBinariesLocation(); |
| return CompilerOptions() |
| ..entryUri = _extractUriOption(options, '${Flags.entryUri}=') |
| .._inputDillUri = _extractUriOption(options, '${Flags.inputDill}=') |
| ..librariesSpecificationUri = librariesSpecificationUri |
| ..allowMockCompilation = _hasOption(options, Flags.allowMockCompilation) |
| ..benchmarkingProduction = _hasOption( |
| options, |
| Flags.benchmarkingProduction, |
| ) |
| ..benchmarkingExperiment = _hasOption( |
| options, |
| Flags.benchmarkingExperiment, |
| ) |
| ..buildId = |
| _extractStringOption(options, '--build-id=', _undeterminedBuildID)! |
| ..compileForServer = _hasOption(options, Flags.serverMode) |
| ..deferredMapUri = _extractUriOption(options, '--deferred-map=') |
| .._deferredLoadIdMapUri = _extractUriOption( |
| options, |
| '${Flags.deferredLoadIdMapUri}=', |
| ) |
| ..deferredGraphUri = _extractUriOption( |
| options, |
| '${Flags.dumpDeferredGraph}=', |
| ) |
| ..fatalWarnings = _hasOption(options, Flags.fatalWarnings) |
| ..terseDiagnostics = _hasOption(options, Flags.terse) |
| ..suppressWarnings = _hasOption(options, Flags.suppressWarnings) |
| ..suppressHints = _hasOption(options, Flags.suppressHints) |
| ..shownPackageWarnings = _extractOptionalCsvOption( |
| options, |
| Flags.showPackageWarnings, |
| ) |
| ..explicitExperimentalFlags = explicitExperimentalFlags |
| ..disableInlining = _hasOption(options, Flags.disableInlining) |
| ..disableProgramSplit = _hasOption(options, Flags.disableProgramSplit) |
| ..disableTypeInference = _hasOption(options, Flags.disableTypeInference) |
| ..useTrivialAbstractValueDomain = _hasOption( |
| options, |
| Flags.useTrivialAbstractValueDomain, |
| ) |
| ..experimentalWrapped = _hasOption(options, Flags.experimentalWrapped) |
| ..experimentalPowersets = _hasOption(options, Flags.experimentalPowersets) |
| ..disableRtiOptimization = _hasOption( |
| options, |
| Flags.disableRtiOptimization, |
| ) |
| .._dumpInfoDataUri = _extractUriOption( |
| options, |
| '${Flags.dumpInfoDataUri}=', |
| ) |
| .._dumpInfoFormatOption = _extractEnumOption( |
| options, |
| Flags.dumpInfo, |
| DumpInfoFormat.values, |
| emptyValue: DumpInfoFormat.binary, |
| ) |
| ..dumpSsaPattern = _extractStringOption( |
| options, |
| '${Flags.dumpSsa}=', |
| null, |
| ) |
| ..writeResources = _hasOption(options, Flags.writeResources) |
| ..enableMinification = _hasOption(options, Flags.minify) |
| .._disableMinification = _hasOption(options, Flags.noMinify) |
| ..omitLateNames = _hasOption(options, Flags.omitLateNames) |
| .._noOmitLateNames = _hasOption(options, Flags.noOmitLateNames) |
| ..enableNativeLiveTypeAnalysis = |
| !_hasOption(options, Flags.disableNativeLiveTypeAnalysis) |
| ..enableUserAssertions = |
| _hasOption(options, Flags.enableCheckedMode) || |
| _hasOption(options, Flags.enableAsserts) |
| ..nativeNullAssertions = _hasOption(options, Flags.nativeNullAssertions) |
| .._noNativeNullAssertions = _hasOption( |
| options, |
| Flags.noNativeNullAssertions, |
| ) |
| ..interopNullAssertions = _hasOption(options, Flags.interopNullAssertions) |
| .._noInteropNullAssertions = _hasOption( |
| options, |
| Flags.noInteropNullAssertions, |
| ) |
| ..experimentalTrackAllocations = _hasOption( |
| options, |
| Flags.experimentalTrackAllocations, |
| ) |
| ..experimentStartupFunctions = _hasOption( |
| options, |
| Flags.experimentStartupFunctions, |
| ) |
| ..experimentToBoolean = _hasOption(options, Flags.experimentToBoolean) |
| ..experimentUnreachableMethodsThrow = _hasOption( |
| options, |
| Flags.experimentUnreachableMethodsThrow, |
| ) |
| ..experimentCallInstrumentation = _hasOption( |
| options, |
| Flags.experimentCallInstrumentation, |
| ) |
| ..generateSourceMap = !_hasOption(options, Flags.noSourceMaps) |
| .._outputUri = _extractUriOption(options, '--out=') |
| ..platformBinaries = platformBinaries |
| ..sourceMapUri = _extractUriOption(options, '--source-map=') |
| ..omitImplicitChecks = _hasOption(options, Flags.omitImplicitChecks) |
| ..omitAsCasts = _hasOption(options, Flags.omitAsCasts) |
| ..laxRuntimeTypeToString = _hasOption( |
| options, |
| Flags.laxRuntimeTypeToString, |
| ) |
| ..enableProtoShaking = |
| _hasOption(options, Flags.enableProtoShaking) || |
| _hasOption(options, Flags.enableProtoMixinShaking) |
| ..enableProtoMixinShaking = _hasOption( |
| options, |
| Flags.enableProtoMixinShaking, |
| ) |
| ..testMode = _hasOption(options, Flags.testMode) |
| ..trustPrimitives = _hasOption(options, Flags.trustPrimitives) |
| ..useFrequencyNamer = |
| !_hasOption(options, Flags.noFrequencyBasedMinification) |
| ..useMultiSourceInfo = _hasOption(options, Flags.useMultiSourceInfo) |
| ..useNewSourceInfo = _hasOption(options, Flags.useNewSourceInfo) |
| ..useSimpleLoadIds = _hasOption(options, Flags.useSimpleLoadIds) |
| ..verbose = _hasOption(options, Flags.verbose) |
| ..reportPrimaryMetrics = _hasOption(options, Flags.reportMetrics) |
| ..reportSecondaryMetrics = _hasOption(options, Flags.reportAllMetrics) |
| ..showInternalProgress = _hasOption(options, Flags.progress) |
| ..dillDependencies = _extractUriListOption( |
| options, |
| Flags.dillDependencies, |
| ) |
| ..readProgramSplit = _extractUriOption( |
| options, |
| '${Flags.readProgramSplit}=', |
| ) |
| .._globalInferenceUri = _extractUriOption( |
| options, |
| '${Flags.globalInferenceUri}=', |
| ) |
| ..memoryMappedFiles = _hasOption(options, Flags.memoryMappedFiles) |
| .._closedWorldUri = _extractUriOption(options, '${Flags.closedWorldUri}=') |
| .._codegenUri = _extractUriOption(options, '${Flags.codegenUri}=') |
| ..codegenShard = _extractIntOption(options, '${Flags.codegenShard}=') |
| ..codegenShards = _extractIntOption(options, '${Flags.codegenShards}=') |
| .._cfeOnly = _hasOption(options, Flags.cfeOnly) |
| .._stageFlag = _extractStringOption(options, '${Flags.stage}=', null) |
| ..debugGlobalInference = _hasOption(options, Flags.debugGlobalInference) |
| .._mergeFragmentsThreshold = _extractIntOption( |
| options, |
| '${Flags.mergeFragmentsThreshold}=', |
| ) |
| ..dumpUnusedLibraries = _hasOption(options, Flags.dumpUnusedLibraries) |
| ..cfeInvocationModes = fe.InvocationMode.parseArguments( |
| _extractStringOption(options, '${Flags.cfeInvocationModes}=', '')!, |
| onError: onError, |
| ) |
| ..verbosity = fe.Verbosity.parseArgument( |
| _extractStringOption( |
| options, |
| '${Flags.verbosity}=', |
| fe.Verbosity.defaultValue, |
| )!, |
| onError: onError, |
| ) |
| ..disableDiagnosticByteCache = _hasOption( |
| options, |
| Flags.disableDiagnosticByteCache, |
| ) |
| ..features = featureOptions; |
| } |
| |
| String? validateStage() { |
| bool expectCodegenIn = false; |
| bool expectCodegenOut = false; |
| switch (stage) { |
| case CompilerStage.all: |
| case CompilerStage.dumpInfoAll: |
| case CompilerStage.cfe: |
| case CompilerStage.deferredLoadIds: |
| case CompilerStage.closedWorld: |
| case CompilerStage.globalInference: |
| case CompilerStage.codegenAndJsEmitter: |
| case CompilerStage.dumpInfo: |
| break; |
| case CompilerStage.codegenSharded: |
| expectCodegenOut = true; |
| break; |
| case CompilerStage.jsEmitter: |
| expectCodegenIn = true; |
| break; |
| } |
| |
| if (codegenShard == null && expectCodegenOut) { |
| return 'Must specify value for ${Flags.codegenShard} ' |
| 'in stage ${stage.name}.'; |
| } |
| |
| if (codegenShards == null && expectCodegenOut) { |
| return 'Must specify value for ${Flags.codegenShards} ' |
| 'in stage ${stage.name}.'; |
| } |
| if (codegenShards == null && expectCodegenIn) { |
| return 'Must specify value for ${Flags.codegenShards} ' |
| 'in stage ${stage.name}.'; |
| } |
| return null; |
| } |
| |
| void validate() { |
| if (librariesSpecificationUri == null) { |
| throw ArgumentError("[librariesSpecificationUri] is null."); |
| } |
| if (librariesSpecificationUri!.path.endsWith('/')) { |
| throw ArgumentError( |
| "[librariesSpecificationUri] should be a file: $librariesSpecificationUri", |
| ); |
| } |
| Map<fe.ExperimentalFlag, bool> experimentalFlags = Map.from( |
| fe.defaultExperimentalFlags, |
| ); |
| experimentalFlags.addAll(explicitExperimentalFlags); |
| if (platformBinaries == null && |
| equalMaps(experimentalFlags, fe.defaultExperimentalFlags)) { |
| throw ArgumentError("Missing required ${Flags.platformBinaries}"); |
| } |
| if (nativeNullAssertions && _noNativeNullAssertions) { |
| throw ArgumentError( |
| "'${Flags.nativeNullAssertions}' is incompatible with " |
| "'${Flags.noNativeNullAssertions}'", |
| ); |
| } |
| if (interopNullAssertions && _noInteropNullAssertions) { |
| throw ArgumentError( |
| "'${Flags.interopNullAssertions}' is incompatible with " |
| "'${Flags.noInteropNullAssertions}'", |
| ); |
| } |
| } |
| |
| // This should only be used to derive options to be used during compilation, |
| // not for options needed during set up of the compiler. |
| void deriveOptions() { |
| if (benchmarkingProduction) { |
| trustPrimitives = true; |
| omitImplicitChecks = true; |
| // TODO(53993): |
| // laxRuntimeTypeToString = true; |
| // omitLateNames = true; |
| } |
| |
| if (benchmarkingExperiment) { |
| // Set flags implied by '--benchmarking-x'. |
| features.forceCanary(); |
| } |
| |
| if (optimizationLevel != null) { |
| if (optimizationLevel == 0) { |
| disableInlining = true; |
| disableTypeInference = true; |
| disableRtiOptimization = true; |
| } |
| if (optimizationLevel! >= 2) { |
| enableMinification = true; |
| laxRuntimeTypeToString = true; |
| omitLateNames = true; |
| } |
| if (optimizationLevel! >= 3) { |
| omitImplicitChecks = true; |
| } |
| if (optimizationLevel == 4) { |
| trustPrimitives = true; |
| } |
| } |
| |
| // Strong mode always trusts type annotations (inferred or explicit), so |
| // assignments checks should be trusted. |
| if (omitImplicitChecks) { |
| defaultParameterCheckPolicy = CheckPolicy.trusted; |
| defaultImplicitDowncastCheckPolicy = CheckPolicy.trusted; |
| defaultConditionCheckPolicy = CheckPolicy.trusted; |
| } else { |
| defaultParameterCheckPolicy = CheckPolicy.checked; |
| defaultImplicitDowncastCheckPolicy = CheckPolicy.checked; |
| defaultConditionCheckPolicy = CheckPolicy.checked; |
| } |
| if (omitAsCasts) { |
| defaultExplicitCastCheckPolicy = CheckPolicy.trusted; |
| } else { |
| defaultExplicitCastCheckPolicy = CheckPolicy.checked; |
| } |
| if (trustPrimitives) { |
| defaultIndexBoundsCheckPolicy = CheckPolicy.trusted; |
| } else { |
| defaultIndexBoundsCheckPolicy = CheckPolicy.checked; |
| } |
| |
| if (_disableMinification) { |
| enableMinification = false; |
| } |
| |
| if (_noOmitLateNames) { |
| omitLateNames = false; |
| } |
| |
| if (_noNativeNullAssertions) { |
| // Never assert if the user tells us not to. |
| nativeNullAssertions = false; |
| } else if (!nativeNullAssertions && |
| (optimizationLevel != null && optimizationLevel! >= 3)) { |
| // If the user didn't tell us to assert and we're in >= -O3, optimize away |
| // the check. This should reduce issues in production. |
| nativeNullAssertions = false; |
| } else { |
| nativeNullAssertions = true; |
| } |
| |
| if (_noInteropNullAssertions) { |
| interopNullAssertions = false; |
| } |
| |
| if (_mergeFragmentsThreshold != null) { |
| mergeFragmentsThreshold = _mergeFragmentsThreshold; |
| } |
| |
| environment['dart.web.assertions_enabled'] = '$enableUserAssertions'; |
| environment['dart.tool.dart2js'] = '${true}'; |
| environment['dart.tool.dart2js.minify'] = '$enableMinification'; |
| // Eventually pragmas and commandline flags should be aligned so that users |
| // setting these flag is equivalent to setting the relevant pragmas |
| // globally. |
| // See: https://github.com/dart-lang/sdk/issues/49475 |
| // https://github.com/dart-lang/sdk/blob/main/pkg/compiler/doc/pragmas.md |
| environment['dart.tool.dart2js.primitives:trust'] = '$trustPrimitives'; |
| environment['dart.tool.dart2js.types:trust'] = '$omitImplicitChecks'; |
| } |
| |
| /// Returns `true` if warnings and hints are shown for all packages. |
| @override |
| bool get showAllPackageWarnings { |
| return shownPackageWarnings != null && shownPackageWarnings!.isEmpty; |
| } |
| |
| /// Returns `true` if warnings and hints are hidden for all packages. |
| @override |
| bool get hidePackageWarnings => shownPackageWarnings == null; |
| |
| /// Returns `true` if warnings should be should for [uri]. |
| @override |
| bool showPackageWarningsFor(Uri uri) { |
| if (showAllPackageWarnings) { |
| return true; |
| } |
| if (shownPackageWarnings != null) { |
| return uri.isScheme('package') && |
| shownPackageWarnings!.contains(uri.pathSegments.first); |
| } |
| return false; |
| } |
| } |
| |
| /// Policy for what to do with a type assertion check. |
| /// |
| /// This enum is used to configure how the compiler treats type assertions |
| /// during global type inference and codegen. |
| enum CheckPolicy { |
| trusted(isTrusted: true), |
| checked(isEmitted: true); |
| |
| /// Whether the type assertion should be trusted. |
| final bool isTrusted; |
| |
| /// Whether the type assertion should be emitted and checked. |
| final bool isEmitted; |
| |
| const CheckPolicy({this.isTrusted = false, this.isEmitted = false}); |
| |
| @override |
| String toString() => |
| 'CheckPolicy(isTrusted=$isTrusted,' |
| 'isEmitted=$isEmitted)'; |
| } |
| |
| String? _extractStringOption( |
| List<String> options, |
| String prefix, |
| String? defaultValue, |
| ) { |
| for (String option in options) { |
| if (option.startsWith(prefix)) { |
| return option.substring(prefix.length); |
| } |
| } |
| return defaultValue; |
| } |
| |
| Uri? _extractUriOption(List<String> options, String prefix) { |
| String? option = _extractStringOption(options, prefix, null); |
| return (option == null) ? null : Uri.parse(option); |
| } |
| |
| int? _extractIntOption(List<String> options, String prefix) { |
| String? option = _extractStringOption(options, prefix, null); |
| return (option == null) ? null : int.parse(option); |
| } |
| |
| /// Extracts an enum value for the flag given by [prefix]. |
| /// |
| /// [emptyValue] is used to provide a default value when the flag is given but |
| /// with no '=' value provided. |
| T? _extractEnumOption<T extends Enum>( |
| List<String> options, |
| String prefix, |
| List<T> values, { |
| T? emptyValue, |
| }) { |
| if (emptyValue != null && _hasOption(options, prefix)) return emptyValue; |
| String? option = _extractStringOption(options, '$prefix=', null); |
| if (option == null) return null; |
| return values.firstWhereOrNull((e) => e.name == option); |
| } |
| |
| bool _hasOption(List<String> options, String option) { |
| return options.contains(option); |
| } |
| |
| /// Extract list of comma separated values provided for [flag]. Returns an |
| /// empty list if [option] contain [flag] without arguments. Returns `null` if |
| /// [option] doesn't contain [flag] with or without arguments. |
| List<String>? _extractOptionalCsvOption(List<String> options, String flag) { |
| String prefix = '$flag='; |
| for (String option in options) { |
| if (option == flag) { |
| return const <String>[]; |
| } |
| if (option.startsWith(flag)) { |
| return option.substring(prefix.length).split(','); |
| } |
| } |
| return null; |
| } |
| |
| /// Extract list of comma separated Uris provided for [flag]. Returns an |
| /// empty list if [option] contain [flag] without arguments. Returns `null` if |
| /// [option] doesn't contain [flag] with or without arguments. |
| List<Uri>? _extractUriListOption(List<String> options, String flag) { |
| List<String>? stringUris = _extractOptionalCsvOption(options, flag); |
| if (stringUris == null) return null; |
| return stringUris.map(Uri.parse).toList(); |
| } |
| |
| Map<fe.ExperimentalFlag, bool> _extractExperiments( |
| List<String> options, { |
| void Function(String)? onError, |
| void Function(String)? onWarning, |
| }) { |
| List<String>? experiments = _extractOptionalCsvOption( |
| options, |
| Flags.enableLanguageExperiments, |
| ); |
| onError ??= (String error) => throw ArgumentError(error); |
| onWarning ??= (String warning) => print(warning); |
| return fe.parseExperimentalFlags( |
| fe.parseExperimentalArguments(experiments), |
| onError: onError, |
| onWarning: onWarning, |
| ); |
| } |
| |
| void _extractFeatures( |
| List<String> options, |
| List<FeatureOption> features, |
| FeatureStatus status, |
| ) { |
| bool hasCanaryFlag = _hasOption(options, Flags.canary); |
| bool hasNoShippingFlag = _hasOption(options, Flags.noShipping); |
| for (var feature in features) { |
| String featureFlag = feature.flag; |
| String enableFeatureFlag = '--$featureFlag'; |
| String disableFeatureFlag = '--no-$featureFlag'; |
| bool enableFeature = _hasOption(options, enableFeatureFlag); |
| bool disableFeature = _hasOption(options, disableFeatureFlag); |
| if (enableFeature && disableFeature) { |
| throw ArgumentError( |
| "'$enableFeatureFlag' incompatible with " |
| "'$disableFeatureFlag'", |
| ); |
| } |
| bool globalEnable = |
| hasCanaryFlag || |
| (status == FeatureStatus.shipping && !hasNoShippingFlag); |
| globalEnable = feature.isNegativeFlag ? !globalEnable : globalEnable; |
| feature.state = (enableFeature || globalEnable) && !disableFeature; |
| } |
| } |
| |
| void _verifyShippedFeatures( |
| List<String> options, |
| List<FeatureOption> features, |
| ) { |
| for (var feature in features) { |
| String featureFlag = feature.flag; |
| String enableFeatureFlag = '--$featureFlag'; |
| String disableFeatureFlag = '--no-$featureFlag'; |
| bool enableFeature = _hasOption(options, enableFeatureFlag); |
| bool disableFeature = _hasOption(options, disableFeatureFlag); |
| if (enableFeature && disableFeature) { |
| throw ArgumentError( |
| "'$enableFeatureFlag' incompatible with " |
| "'$disableFeatureFlag'", |
| ); |
| } |
| if (enableFeature && feature.isNegativeFlag) { |
| throw ArgumentError( |
| "$enableFeatureFlag has been removed and cannot be enabled.", |
| ); |
| } |
| if (disableFeature && !feature.isNegativeFlag) { |
| throw ArgumentError( |
| "$enableFeatureFlag has already shipped and cannot be disabled.", |
| ); |
| } |
| feature.state = !feature.isNegativeFlag; |
| } |
| } |
| |
| const String _undeterminedBuildID = "build number could not be determined"; |