diff --git a/packages/flutter_tools/lib/src/commands/build_web.dart b/packages/flutter_tools/lib/src/commands/build_web.dart index 42ce09872ca..0bb0d9b3410 100644 --- a/packages/flutter_tools/lib/src/commands/build_web.dart +++ b/packages/flutter_tools/lib/src/commands/build_web.dart @@ -31,6 +31,9 @@ class BuildWebCommand extends BuildSubCommand { @override bool get hidden => true; + @override + bool get isExperimental => true; + @override final String description = '(EXPERIMENTAL) build a web application bundle.'; diff --git a/packages/flutter_tools/lib/src/desktop.dart b/packages/flutter_tools/lib/src/desktop.dart index 153e2fbe063..e1af6698e73 100644 --- a/packages/flutter_tools/lib/src/desktop.dart +++ b/packages/flutter_tools/lib/src/desktop.dart @@ -3,11 +3,12 @@ // found in the LICENSE file. import 'base/platform.dart'; +import 'version.dart'; // Only launch or display desktop embedding devices if // `FLUTTER_DESKTOP_EMBEDDING` environment variable is set to true. bool get flutterDesktopEnabled { _flutterDesktopEnabled ??= platform.environment['FLUTTER_DESKTOP_EMBEDDING']?.toLowerCase() == 'true'; - return _flutterDesktopEnabled; + return _flutterDesktopEnabled && !FlutterVersion.instance.isStable; } bool _flutterDesktopEnabled; diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 4341b1d6f8e..dd8480260f7 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -27,6 +27,7 @@ import '../doctor.dart'; import '../globals.dart'; import '../project.dart'; import '../usage.dart'; +import '../version.dart'; import 'flutter_command_runner.dart'; export '../cache.dart' show DevelopmentArtifact; @@ -464,6 +465,11 @@ abstract class FlutterCommand extends Command { } } + /// Whether this feature should not be usable on stable branches. + /// + /// Defaults to false, meaning it is usable. + bool get isExperimental => false; + /// Additional usage values to be sent with the usage ping. Future> get usageValues async => const {}; @@ -635,6 +641,12 @@ abstract class FlutterCommand extends Command { @protected @mustCallSuper Future validateCommand() async { + // If we're on a stable branch, then don't allow the usage of + // "experimental" features. + if (isExperimental && FlutterVersion.instance.isStable) { + throwToolExit('Experimental feature $name is not supported on stable branches'); + } + if (_requiresPubspecYaml && !PackageMap.isUsingCustomPackagesPath) { // Don't expect a pubspec.yaml file if the user passed in an explicit .packages file path. if (!fs.isFileSync('pubspec.yaml')) { diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart index 3df1df0e841..d571f37be55 100644 --- a/packages/flutter_tools/lib/src/version.dart +++ b/packages/flutter_tools/lib/src/version.dart @@ -32,6 +32,11 @@ class FlutterVersion { return _repositoryUrl; } + /// Whether we are currently on the stable branch. + bool get isStable { + return getBranchName() == 'stable'; + } + static const Set officialChannels = { 'master', 'dev', diff --git a/packages/flutter_tools/lib/src/web/web_device.dart b/packages/flutter_tools/lib/src/web/web_device.dart index 4b34393e937..fd787299860 100644 --- a/packages/flutter_tools/lib/src/web/web_device.dart +++ b/packages/flutter_tools/lib/src/web/web_device.dart @@ -13,6 +13,7 @@ import '../build_info.dart'; import '../device.dart'; import '../globals.dart'; import '../project.dart'; +import '../version.dart'; import '../web/compile.dart'; ChromeLauncher get chromeLauncher => context[ChromeLauncher]; @@ -21,7 +22,7 @@ ChromeLauncher get chromeLauncher => context[ChromeLauncher]; /// environment variable is set to true. bool get flutterWebEnabled { _flutterWebEnabled = platform.environment['FLUTTER_WEB']?.toLowerCase() == 'true'; - return _flutterWebEnabled; + return _flutterWebEnabled && !FlutterVersion.instance.isStable; } bool _flutterWebEnabled; diff --git a/packages/flutter_tools/test/runner/flutter_command_test.dart b/packages/flutter_tools/test/runner/flutter_command_test.dart index e1cfc2ac7b9..e91b95b22d4 100644 --- a/packages/flutter_tools/test/runner/flutter_command_test.dart +++ b/packages/flutter_tools/test/runner/flutter_command_test.dart @@ -7,6 +7,7 @@ import 'package:flutter_tools/src/base/time.dart'; import 'package:flutter_tools/src/usage.dart'; import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart'; +import 'package:flutter_tools/src/version.dart'; import 'package:mockito/mockito.dart'; import '../src/common.dart'; @@ -155,4 +156,43 @@ void main() { }); }); + + group('Experimental commands', () { + final MockVersion stableVersion = MockVersion(); + final MockVersion betaVersion = MockVersion(); + final FakeCommand fakeCommand = FakeCommand(); + when(stableVersion.isStable).thenReturn(true); + when(betaVersion.isStable).thenReturn(false); + + testUsingContext('Can be disabled on stable branch', () async { + expect(() => fakeCommand.run(), throwsA(isA())); + }, overrides: { + FlutterVersion: () => stableVersion, + }); + + testUsingContext('Works normally on regular branches', () async { + expect(fakeCommand.run(), completes); + }, overrides: { + FlutterVersion: () => betaVersion, + }); + }); } + + +class FakeCommand extends FlutterCommand { + @override + String get description => null; + + @override + String get name => 'fake'; + + @override + bool get isExperimental => true; + + @override + Future runCommand() async { + return null; + } +} + +class MockVersion extends Mock implements FlutterVersion {} diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart index d89d322414d..72036760fcb 100644 --- a/packages/flutter_tools/test/src/context.dart +++ b/packages/flutter_tools/test/src/context.dart @@ -304,7 +304,10 @@ class MockXcodeProjectInterpreter implements XcodeProjectInterpreter { } } -class MockFlutterVersion extends Mock implements FlutterVersion {} +class MockFlutterVersion extends Mock implements FlutterVersion { + @override + bool get isStable => false; +} class MockClock extends Mock implements SystemClock {}