From 98e29db45cd969a0a1cd913ef69e231307b17b02 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 4 Feb 2025 03:36:43 -0800 Subject: [PATCH] [native_assets] Disable experiment on stable and beta channel We want to avoid users passing `--enable-experiment=native-assets` on stable and beta, as we'd like to move fast and break things on the experiment. This aligns the experiment with how the experiment is working in Flutter: main and dev branch only. Before this CL, dartdev did not check experiment flags. Unknown experiments would fail in the VM. After this CL, dartdev checks the experiment flags and errors out early. Change-Id: I875ea3272f4b67342da19ea2e4be329a4b380573 Cq-Include-Trybots: luci.dart.try:pkg-linux-debug-try,pkg-linux-release-arm64-try,pkg-linux-release-try,pkg-mac-release-arm64-try,pkg-mac-release-try,pkg-win-release-arm64-try,pkg-win-release-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/406660 Commit-Queue: Daco Harkes Reviewed-by: Martin Kustermann --- .../lib/src/dart/analysis/experiments.g.dart | 38 +++++++++++++++++++ .../src/dart/analysis/experiments_impl.dart | 7 ++++ pkg/analyzer/tool/experiments/generate.dart | 5 +++ pkg/dartdev/lib/dartdev.dart | 7 ++++ pkg/dartdev/lib/src/experiments.dart | 26 +++++++++++++ pkg/dartdev/lib/src/sdk.dart | 2 +- .../commands/create_integration_test.dart | 2 + pkg/dartdev/test/experiments_test.dart | 25 ++++++++++++ .../test/native_assets/build_test.dart | 4 ++ .../test/native_assets/compile_test.dart | 4 ++ pkg/dartdev/test/native_assets/helpers.dart | 6 +++ pkg/dartdev/test/native_assets/run_test.dart | 24 ++++++++++++ pkg/dartdev/test/native_assets/test_test.dart | 4 ++ tools/experimental_features.yaml | 2 + 14 files changed, 155 insertions(+), 1 deletion(-) diff --git a/pkg/analyzer/lib/src/dart/analysis/experiments.g.dart b/pkg/analyzer/lib/src/dart/analysis/experiments.g.dart index 7f78b8407ea2..8e6d831c40a8 100644 --- a/pkg/analyzer/lib/src/dart/analysis/experiments.g.dart +++ b/pkg/analyzer/lib/src/dart/analysis/experiments.g.dart @@ -187,6 +187,7 @@ class ExperimentalFeatures { documentation: 'Augmentations - enhancing declarations from outside', experimentalReleaseVersion: Version.parse('3.6.0'), releaseVersion: null, + channels: ["stable", "beta", "dev", "main"], ); static final class_modifiers = ExperimentalFeature( @@ -197,6 +198,7 @@ class ExperimentalFeatures { documentation: 'Class modifiers', experimentalReleaseVersion: null, releaseVersion: Version.parse('3.0.0'), + channels: ["stable", "beta", "dev", "main"], ); static final const_functions = ExperimentalFeature( @@ -208,6 +210,7 @@ class ExperimentalFeatures { 'Allow more of the Dart language to be executed in const expressions.', experimentalReleaseVersion: null, releaseVersion: null, + channels: ["stable", "beta", "dev", "main"], ); static final constant_update_2018 = ExperimentalFeature( @@ -218,6 +221,7 @@ class ExperimentalFeatures { documentation: 'Enhanced constant expressions', experimentalReleaseVersion: null, releaseVersion: Version.parse('2.0.0'), + channels: ["stable", "beta", "dev", "main"], ); static final constructor_tearoffs = ExperimentalFeature( @@ -229,6 +233,7 @@ class ExperimentalFeatures { 'Allow constructor tear-offs and explicit generic instantiations.', experimentalReleaseVersion: null, releaseVersion: Version.parse('2.15.0'), + channels: ["stable", "beta", "dev", "main"], ); static final control_flow_collections = ExperimentalFeature( @@ -239,6 +244,7 @@ class ExperimentalFeatures { documentation: 'Control Flow Collections', experimentalReleaseVersion: null, releaseVersion: Version.parse('2.0.0'), + channels: ["stable", "beta", "dev", "main"], ); static final digit_separators = ExperimentalFeature( @@ -249,6 +255,7 @@ class ExperimentalFeatures { documentation: 'Number literals with digit separators.', experimentalReleaseVersion: null, releaseVersion: Version.parse('3.6.0'), + channels: ["stable", "beta", "dev", "main"], ); static final enhanced_enums = ExperimentalFeature( @@ -259,6 +266,7 @@ class ExperimentalFeatures { documentation: 'Enhanced Enums', experimentalReleaseVersion: null, releaseVersion: Version.parse('2.17.0'), + channels: ["stable", "beta", "dev", "main"], ); static final enhanced_parts = ExperimentalFeature( @@ -269,6 +277,7 @@ class ExperimentalFeatures { documentation: 'Generalize parts to be nested and have exports/imports.', experimentalReleaseVersion: Version.parse('3.6.0'), releaseVersion: null, + channels: ["stable", "beta", "dev", "main"], ); static final enum_shorthands = ExperimentalFeature( @@ -279,6 +288,7 @@ class ExperimentalFeatures { documentation: 'Shorter dot syntax for enum values.', experimentalReleaseVersion: null, releaseVersion: null, + channels: ["stable", "beta", "dev", "main"], ); static final extension_methods = ExperimentalFeature( @@ -289,6 +299,7 @@ class ExperimentalFeatures { documentation: 'Extension Methods', experimentalReleaseVersion: null, releaseVersion: Version.parse('2.6.0'), + channels: ["stable", "beta", "dev", "main"], ); static final generic_metadata = ExperimentalFeature( @@ -300,6 +311,7 @@ class ExperimentalFeatures { 'Allow annotations to accept type arguments; also allow generic function types as type arguments.', experimentalReleaseVersion: null, releaseVersion: Version.parse('2.14.0'), + channels: ["stable", "beta", "dev", "main"], ); static final getter_setter_error = ExperimentalFeature( @@ -311,6 +323,7 @@ class ExperimentalFeatures { 'Stop reporting errors about mismatching types in a getter/setter pair.', experimentalReleaseVersion: null, releaseVersion: null, + channels: ["stable", "beta", "dev", "main"], ); static final inference_update_1 = ExperimentalFeature( @@ -322,6 +335,7 @@ class ExperimentalFeatures { 'Horizontal type inference for function expressions passed to generic invocations.', experimentalReleaseVersion: null, releaseVersion: Version.parse('2.18.0'), + channels: ["stable", "beta", "dev", "main"], ); static final inference_update_2 = ExperimentalFeature( @@ -332,6 +346,7 @@ class ExperimentalFeatures { documentation: 'Type promotion for fields', experimentalReleaseVersion: null, releaseVersion: Version.parse('3.2.0'), + channels: ["stable", "beta", "dev", "main"], ); static final inference_update_3 = ExperimentalFeature( @@ -343,6 +358,7 @@ class ExperimentalFeatures { 'Better handling of conditional expressions, and switch expressions.', experimentalReleaseVersion: null, releaseVersion: Version.parse('3.4.0'), + channels: ["stable", "beta", "dev", "main"], ); static final inference_update_4 = ExperimentalFeature( @@ -353,6 +369,7 @@ class ExperimentalFeatures { documentation: 'A bundle of updates to type inference.', experimentalReleaseVersion: null, releaseVersion: null, + channels: ["stable", "beta", "dev", "main"], ); static final inference_using_bounds = ExperimentalFeature( @@ -364,6 +381,7 @@ class ExperimentalFeatures { 'Use type parameter bounds more extensively in type inference.', experimentalReleaseVersion: null, releaseVersion: Version.parse('3.7.0'), + channels: ["stable", "beta", "dev", "main"], ); static final inline_class = ExperimentalFeature( @@ -374,6 +392,7 @@ class ExperimentalFeatures { documentation: 'Extension Types', experimentalReleaseVersion: null, releaseVersion: Version.parse('3.3.0'), + channels: ["stable", "beta", "dev", "main"], ); static final macros = ExperimentalFeature( @@ -384,6 +403,7 @@ class ExperimentalFeatures { documentation: 'Static meta-programming', experimentalReleaseVersion: Version.parse('3.3.0'), releaseVersion: null, + channels: ["stable", "beta", "dev", "main"], ); static final named_arguments_anywhere = ExperimentalFeature( @@ -394,6 +414,7 @@ class ExperimentalFeatures { documentation: 'Named Arguments Anywhere', experimentalReleaseVersion: null, releaseVersion: Version.parse('2.17.0'), + channels: ["stable", "beta", "dev", "main"], ); static final native_assets = ExperimentalFeature( @@ -404,6 +425,7 @@ class ExperimentalFeatures { documentation: 'Compile and bundle native assets.', experimentalReleaseVersion: null, releaseVersion: null, + channels: ["main", "dev"], ); static final non_nullable = ExperimentalFeature( @@ -414,6 +436,7 @@ class ExperimentalFeatures { documentation: 'Non Nullable by default', experimentalReleaseVersion: Version.parse('2.10.0'), releaseVersion: Version.parse('2.12.0'), + channels: ["stable", "beta", "dev", "main"], ); static final nonfunction_type_aliases = ExperimentalFeature( @@ -424,6 +447,7 @@ class ExperimentalFeatures { documentation: 'Type aliases define a , not just a ', experimentalReleaseVersion: null, releaseVersion: Version.parse('2.13.0'), + channels: ["stable", "beta", "dev", "main"], ); static final null_aware_elements = ExperimentalFeature( @@ -434,6 +458,7 @@ class ExperimentalFeatures { documentation: 'Null-aware elements and map entries in collections.', experimentalReleaseVersion: null, releaseVersion: null, + channels: ["stable", "beta", "dev", "main"], ); static final patterns = ExperimentalFeature( @@ -444,6 +469,7 @@ class ExperimentalFeatures { documentation: 'Patterns', experimentalReleaseVersion: null, releaseVersion: Version.parse('3.0.0'), + channels: ["stable", "beta", "dev", "main"], ); static final record_use = ExperimentalFeature( @@ -454,6 +480,7 @@ class ExperimentalFeatures { documentation: 'Output arguments used by static functions.', experimentalReleaseVersion: null, releaseVersion: null, + channels: ["main", "dev"], ); static final records = ExperimentalFeature( @@ -464,6 +491,7 @@ class ExperimentalFeatures { documentation: 'Records', experimentalReleaseVersion: null, releaseVersion: Version.parse('3.0.0'), + channels: ["stable", "beta", "dev", "main"], ); static final sealed_class = ExperimentalFeature( @@ -474,6 +502,7 @@ class ExperimentalFeatures { documentation: 'Sealed class', experimentalReleaseVersion: null, releaseVersion: Version.parse('3.0.0'), + channels: ["stable", "beta", "dev", "main"], ); static final set_literals = ExperimentalFeature( @@ -484,6 +513,7 @@ class ExperimentalFeatures { documentation: 'Set Literals', experimentalReleaseVersion: null, releaseVersion: Version.parse('2.0.0'), + channels: ["stable", "beta", "dev", "main"], ); static final spread_collections = ExperimentalFeature( @@ -494,6 +524,7 @@ class ExperimentalFeatures { documentation: 'Spread Collections', experimentalReleaseVersion: null, releaseVersion: Version.parse('2.0.0'), + channels: ["stable", "beta", "dev", "main"], ); static final super_parameters = ExperimentalFeature( @@ -504,6 +535,7 @@ class ExperimentalFeatures { documentation: 'Super-Initializer Parameters', experimentalReleaseVersion: null, releaseVersion: Version.parse('2.17.0'), + channels: ["stable", "beta", "dev", "main"], ); static final test_experiment = ExperimentalFeature( @@ -515,6 +547,7 @@ class ExperimentalFeatures { 'Has no effect. Can be used for testing the --enable-experiment command line functionality.', experimentalReleaseVersion: null, releaseVersion: null, + channels: ["stable", "beta", "dev", "main"], ); static final triple_shift = ExperimentalFeature( @@ -525,6 +558,7 @@ class ExperimentalFeatures { documentation: 'Triple-shift operator', experimentalReleaseVersion: null, releaseVersion: Version.parse('2.14.0'), + channels: ["stable", "beta", "dev", "main"], ); static final unnamed_libraries = ExperimentalFeature( @@ -535,6 +569,7 @@ class ExperimentalFeatures { documentation: 'Unnamed libraries', experimentalReleaseVersion: null, releaseVersion: Version.parse('2.19.0'), + channels: ["stable", "beta", "dev", "main"], ); static final unquoted_imports = ExperimentalFeature( @@ -545,6 +580,7 @@ class ExperimentalFeatures { documentation: 'Shorter import syntax.', experimentalReleaseVersion: null, releaseVersion: null, + channels: ["stable", "beta", "dev", "main"], ); static final variance = ExperimentalFeature( @@ -555,6 +591,7 @@ class ExperimentalFeatures { documentation: 'Sound variance', experimentalReleaseVersion: null, releaseVersion: null, + channels: ["stable", "beta", "dev", "main"], ); static final wildcard_variables = ExperimentalFeature( @@ -566,6 +603,7 @@ class ExperimentalFeatures { 'Local declarations and parameters named `_` are non-binding.', experimentalReleaseVersion: null, releaseVersion: Version.parse('3.7.0'), + channels: ["stable", "beta", "dev", "main"], ); } diff --git a/pkg/analyzer/lib/src/dart/analysis/experiments_impl.dart b/pkg/analyzer/lib/src/dart/analysis/experiments_impl.dart index 2b3e4c91759a..18dc81a01e8d 100644 --- a/pkg/analyzer/lib/src/dart/analysis/experiments_impl.dart +++ b/pkg/analyzer/lib/src/dart/analysis/experiments_impl.dart @@ -317,6 +317,12 @@ class ExperimentalFeature implements Feature { @override final Version? releaseVersion; + /// The channels on which this experiment is available. + /// + /// Valid channels are "stable", "beta", and "main". The "dev" channel in Dart + /// is implied by main. + final List channels; + ExperimentalFeature({ required this.index, required this.enableString, @@ -325,6 +331,7 @@ class ExperimentalFeature implements Feature { required this.documentation, required this.experimentalReleaseVersion, required this.releaseVersion, + this.channels = const [], }) : assert(isEnabledByDefault ? releaseVersion != null : releaseVersion == null); diff --git a/pkg/analyzer/tool/experiments/generate.dart b/pkg/analyzer/tool/experiments/generate.dart index 3304ccc220e3..1bb584cbc860 100644 --- a/pkg/analyzer/tool/experiments/generate.dart +++ b/pkg/analyzer/tool/experiments/generate.dart @@ -155,6 +155,10 @@ class ExperimentalFeatures { var experimentalReleaseVersion = (features[key] as YamlMap)['experimentalReleaseVersion']; var enabledIn = (features[key] as YamlMap)['enabledIn']; + var channels = + (((features[key] as YamlMap)['channels']) as List?)?.cast(); + channels ??= ['stable', 'beta', 'dev', 'main']; + var channelsLiteral = '[${channels.map((e) => '"$e"').join(', ')}]'; out.write(''' static final $id = ExperimentalFeature( @@ -180,6 +184,7 @@ class ExperimentalFeatures { } else { out.write("releaseVersion: null,"); } + out.write("channels: $channelsLiteral,"); out.writeln(');'); ++index; } diff --git a/pkg/dartdev/lib/dartdev.dart b/pkg/dartdev/lib/dartdev.dart index bc33817e2620..aad08c812bb4 100644 --- a/pkg/dartdev/lib/dartdev.dart +++ b/pkg/dartdev/lib/dartdev.dart @@ -223,6 +223,13 @@ class DartdevRunner extends CommandRunner { log = Logger.verbose(ansi: ansi); } + late final List experimentErrors = + validateExperiments(vmEnabledExperiments); + if (experimentErrors.isNotEmpty) { + experimentErrors.forEach(io.stderr.writeln); + return 254; + } + var command = topLevelResults.command; final commandNames = []; while (command != null) { diff --git a/pkg/dartdev/lib/src/experiments.dart b/pkg/dartdev/lib/src/experiments.dart index 311e3d5f99c7..d13ffefa674a 100644 --- a/pkg/dartdev/lib/src/experiments.dart +++ b/pkg/dartdev/lib/src/experiments.dart @@ -7,6 +7,7 @@ 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'; @@ -101,3 +102,28 @@ bool nativeAssetsEnabled(List vmEnabledExperiments) => bool recordUseEnabled(List vmEnabledExperiments) => vmEnabledExperiments.contains(ExperimentalFeatures.record_use.enableString); + +List validateExperiments(List vmEnabledExperiments) { + final errors = []; + 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 channels) { + final channel = Runtime.runtime.channel; + return channels.contains(channel); +} diff --git a/pkg/dartdev/lib/src/sdk.dart b/pkg/dartdev/lib/src/sdk.dart index 6f105a4fa560..a49a282632ee 100644 --- a/pkg/dartdev/lib/src/sdk.dart +++ b/pkg/dartdev/lib/src/sdk.dart @@ -240,7 +240,7 @@ class Runtime { /// The SDK's semantic versioning version (x.y.z-a.b.channel). final String version; - /// The SDK's release channel (`be`, `dev`, `beta`, `stable`). + /// The SDK's release channel (`main`, `dev`, `beta`, `stable`). /// /// May be null if [Platform.version] does not have the expected format. final String? channel; diff --git a/pkg/dartdev/test/commands/create_integration_test.dart b/pkg/dartdev/test/commands/create_integration_test.dart index bbb3115c4cc6..fe4262d2231a 100644 --- a/pkg/dartdev/test/commands/create_integration_test.dart +++ b/pkg/dartdev/test/commands/create_integration_test.dart @@ -36,6 +36,8 @@ void defineCreateTests() { templateId, projectName, ]); + printOnFailure(createResult.stdout); + printOnFailure(createResult.stderr); expect(createResult.exitCode, 0, reason: createResult.stderr); // Validate that the project analyzes cleanly. diff --git a/pkg/dartdev/test/experiments_test.dart b/pkg/dartdev/test/experiments_test.dart index 0e730463f1af..681eafe5e77f 100644 --- a/pkg/dartdev/test/experiments_test.dart +++ b/pkg/dartdev/test/experiments_test.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:dartdev/src/experiments.dart'; +import 'package:dartdev/src/sdk.dart'; import 'package:test/test.dart'; void main() { @@ -14,5 +15,29 @@ void main() { contains('test-experiment'), ); }); + + test('unknown experiment', () { + final errors = validateExperiments(['foo']); + expect(errors, equals(['Unknown experiment: foo'])); + }); + + test('native assets experiment', () { + final errors = validateExperiments(['native-assets']); + final channel = Runtime.runtime.channel!; + switch (channel) { + case 'stable': + case 'beta': + expect( + errors, + equals([ + 'Unavailable experiment: native-assets (this experiment is only ' + 'available on the main, dev channels, this current channel is $channel)', + ]), + ); + + default: + expect(errors, isEmpty); + } + }); }); } diff --git a/pkg/dartdev/test/native_assets/build_test.dart b/pkg/dartdev/test/native_assets/build_test.dart index a813eaae43bb..c1ca33994392 100644 --- a/pkg/dartdev/test/native_assets/build_test.dart +++ b/pkg/dartdev/test/native_assets/build_test.dart @@ -22,6 +22,10 @@ final String hostOSMessage = 'Host OS: ${Platform.operatingSystem}'; String targetOSMessage(String targetOS) => 'Target OS: $targetOS'; void main(List args) async { + if (!nativeAssetsExperimentAvailableOnCurrentChannel) { + return; + } + final bool fromDartdevSource = args.contains('--source'); final hostOS = Platform.operatingSystem; final crossOS = Platform.isLinux ? 'macos' : 'linux'; diff --git a/pkg/dartdev/test/native_assets/compile_test.dart b/pkg/dartdev/test/native_assets/compile_test.dart index 8050aaf5a1c4..0c69fa54808b 100644 --- a/pkg/dartdev/test/native_assets/compile_test.dart +++ b/pkg/dartdev/test/native_assets/compile_test.dart @@ -12,6 +12,10 @@ import '../utils.dart'; import 'helpers.dart'; void main() async { + if (!nativeAssetsExperimentAvailableOnCurrentChannel) { + return; + } + test('dart compile not supported', timeout: longTimeout, () async { await nativeAssetsTest('dart_app', (dartAppUri) async { final result = await runDart( diff --git a/pkg/dartdev/test/native_assets/helpers.dart b/pkg/dartdev/test/native_assets/helpers.dart index 3e4efab9cf7e..267b2e05e279 100644 --- a/pkg/dartdev/test/native_assets/helpers.dart +++ b/pkg/dartdev/test/native_assets/helpers.dart @@ -5,6 +5,8 @@ import 'dart:async'; import 'dart:io'; +import 'package:analyzer/src/dart/analysis/experiments.dart'; +import 'package:dartdev/src/sdk.dart'; import 'package:file/local.dart'; import 'package:logging/logging.dart'; import 'package:native_assets_builder/src/utils/run_process.dart' @@ -247,3 +249,7 @@ Future runDart({ } return result; } + +final nativeAssetsExperimentAvailableOnCurrentChannel = ExperimentalFeatures + .native_assets.channels + .contains(Runtime.runtime.channel); diff --git a/pkg/dartdev/test/native_assets/run_test.dart b/pkg/dartdev/test/native_assets/run_test.dart index a7f92d2b54bf..cfd0fb3124e2 100644 --- a/pkg/dartdev/test/native_assets/run_test.dart +++ b/pkg/dartdev/test/native_assets/run_test.dart @@ -10,6 +10,30 @@ import '../utils.dart'; import 'helpers.dart'; void main(List args) async { + if (!nativeAssetsExperimentAvailableOnCurrentChannel) { + test('dart run', timeout: longTimeout, () async { + await nativeAssetsTest('dart_app', (dartAppUri) async { + final result = await runDart( + arguments: [ + '--enable-experiment=native-assets', + 'run', + ], + workingDirectory: dartAppUri, + logger: logger, + expectExitCodeZero: false, + ); + expect(result.exitCode, 254); + expect( + result.stderr, + stringContainsInOrder( + ['Unavailable experiment: native-assets'], + )); + }); + }); + + return; + } + // No --source option, `dart run` from source does not output target program // stdout. diff --git a/pkg/dartdev/test/native_assets/test_test.dart b/pkg/dartdev/test/native_assets/test_test.dart index f85c69cce111..c83e956c2b71 100644 --- a/pkg/dartdev/test/native_assets/test_test.dart +++ b/pkg/dartdev/test/native_assets/test_test.dart @@ -10,6 +10,10 @@ import '../utils.dart'; import 'helpers.dart'; void main(List args) async { + if (!nativeAssetsExperimentAvailableOnCurrentChannel) { + return; + } + // No --source option, `dart run` from source does not output target program // stdout. diff --git a/tools/experimental_features.yaml b/tools/experimental_features.yaml index da18eecebdcc..4f87f6484cc8 100644 --- a/tools/experimental_features.yaml +++ b/tools/experimental_features.yaml @@ -125,9 +125,11 @@ features: native-assets: help: "Compile and bundle native assets." + channels: [ "main", "dev" ] record-use: help: "Output arguments used by static functions." + channels: [ "main", "dev" ] null-aware-elements: help: "Null-aware elements and map entries in collections."