blob: aaee2fceeb068c7590297dc2bc462ae3c9c28b2f [file] [log] [blame] [edit]
// 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);
}