| // Copyright (c) 2020, 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/dart/analysis/experiments.dart'; |
| import 'package:args/args.dart'; |
| import 'package:collection/collection.dart' show IterableExtension; |
| import 'package:dartdev/src/sdk.dart'; |
| |
| const experimentFlagName = 'enable-experiment'; |
| |
| /// Return a list of all the non-expired Dart experiments. |
| List<ExperimentalFeature> get experimentalFeatures => |
| ExperimentStatus.knownFeatures.values |
| .where((feature) => !feature.isExpired) |
| .toList() |
| ..sort((a, b) => a.enableString.compareTo(b.enableString)); |
| |
| extension ArgParserExtensions on ArgParser { |
| void addExperimentalFlags({bool verbose = false}) { |
| List<ExperimentalFeature> features = experimentalFeatures; |
| |
| Map<String, String> allowedHelp = {}; |
| for (ExperimentalFeature feature in features) { |
| String suffix = |
| feature.isEnabledByDefault ? ' (no-op - enabled by default)' : ''; |
| allowedHelp[feature.enableString] = '${feature.documentation}$suffix'; |
| } |
| |
| addMultiOption( |
| experimentFlagName, |
| valueHelp: 'experiment', |
| allowedHelp: verbose ? allowedHelp : null, |
| help: 'Enable one or more experimental features ' |
| '(see dart.dev/go/experiments).', |
| hide: !verbose, |
| ); |
| } |
| } |
| |
| extension ArgResultsExtensions on ArgResults { |
| List<String> get enabledExperiments { |
| List<String> enabledExperiments = []; |
| // Check to see if the ArgParser which generated this result accepts |
| // `--enable-experiment` as an option. If so, return the result if it was |
| // provided. |
| if (options.contains(experimentFlagName)) { |
| if (wasParsed(experimentFlagName)) { |
| enabledExperiments = multiOption(experimentFlagName); |
| } |
| } else { |
| // In the case where a command uses `ArgParser.allowAnything()` as its |
| // parser, the valid set of options for the command isn't specified and |
| // isn't enforced. Instead, we have to manually parse the arguments to |
| // look for `--enable-experiment=`. Currently, this path is only taken for |
| // the `pub` and `test` commands, as well as when we are trying to send |
| // analytics. |
| final experiments = arguments.firstWhereOrNull( |
| (e) => e.startsWith('--$experimentFlagName='), |
| ); |
| if (experiments == null) { |
| return []; |
| } |
| enabledExperiments = experiments.split('=')[1].split(','); |
| } |
| |
| for (final feature in experimentalFeatures) { |
| // We allow default true flags, but complain when they are passed in. |
| if (feature.isEnabledByDefault && |
| enabledExperiments.contains(feature.enableString)) { |
| stderr.writeln("'${feature.enableString}' is now enabled by default; " |
| 'this flag is no longer required.'); |
| } |
| } |
| return enabledExperiments; |
| } |
| } |
| |
| List<String> parseVmEnabledExperiments(List<String> vmArgs) { |
| var experiments = <String>[]; |
| var itr = vmArgs.iterator; |
| while (itr.moveNext()) { |
| var arg = itr.current; |
| if (arg == '--$experimentFlagName') { |
| if (!itr.moveNext()) break; |
| experiments.add(itr.current); |
| } else if (arg.startsWith('--$experimentFlagName=')) { |
| var parts = arg.split('='); |
| if (parts.length == 2) { |
| experiments.addAll(parts[1].split(',')); |
| } |
| } |
| } |
| return experiments; |
| } |
| |
| bool nativeAssetsEnabled(List<String> vmEnabledExperiments) => |
| vmEnabledExperiments |
| .contains(ExperimentalFeatures.native_assets.enableString) || |
| (_availableOnCurrentChannel(ExperimentalFeatures.native_assets.channels) && |
| ExperimentalFeatures.native_assets.isEnabledByDefault); |
| |
| bool recordUseEnabled(List<String> vmEnabledExperiments) => |
| vmEnabledExperiments.contains(ExperimentalFeatures.record_use.enableString); |
| |
| List<String> validateExperiments(List<String> vmEnabledExperiments) { |
| final errors = <String>[]; |
| for (final enabledExperiment in vmEnabledExperiments) { |
| final experiment = experimentalFeatures.firstWhereOrNull( |
| (feature) => feature.enableString == enabledExperiment); |
| if (experiment == null) { |
| errors.add('Unknown experiment: $enabledExperiment'); |
| } else if (!_availableOnCurrentChannel(experiment.channels)) { |
| final availableChannels = experiment.channels.join(', '); |
| final s = experiment.channels.length >= 2 ? 's' : ''; |
| errors.add( |
| 'Unavailable experiment: ${experiment.enableString} (this experiment ' |
| 'is only available on the $availableChannels channel$s, ' |
| 'this current channel is ${Runtime.runtime.channel})', |
| ); |
| } |
| } |
| return errors; |
| } |
| |
| bool _availableOnCurrentChannel(List<String> channels) { |
| final channel = Runtime.runtime.channel; |
| return channels.contains(channel); |
| } |