mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
This relands https://github.com/flutter/flutter/pull/168437. The google3 fixes were landed in: https://github.com/flutter/flutter/pull/171547, https://critique.corp.google.com/cl/781275353, https://github.com/flutter/flutter/pull/171933. This PR is split into two commits: 1. d6253794e8982348c5c21cb63e8f6bf785664be6, code from https://github.com/flutter/flutter/pull/168437 without any changes 2. f35d29e4af630d2d4fdb0cda8686b6ff9f77227a, updates the PR to omit obvious types. Original PR description: ## Motivation We'd like to let users opt-in to experimental features so that they can give early feedback while we iterate on the feature. For example: Example feature flags: 1. Android sensitive content: https://github.com/flutter/flutter/pull/158473. When enabled, Flutter will tell Android when the view contains sensitive content like a password. 3. Desktop multi-window. When enabled, Flutter will use child windows to allow things like a context menu to "escape" outside of the current window. ### Use case Users will be able to turn on features by: * **Option 1**: Run `flutter config --enable-my-feature`. This enables the feature for all projects on the machine * **Option 2**: Add `enable-my-feature: true` in their `pubspec.yaml`, under the `flutter` section. This would enable the for a single project on the machine. Turning on a feature affects _both_ development-time (`flutter run`) and deployment-time (`flutter build x`). For example, I can `flutter build windows` to create an `.exe` with multi-window features enabled. ## How this works This adds a new [`runtimeId`](https://github.com/flutter/flutter/pull/168437/files#diff-0ded384225f19a4c34d43c7c11f7cb084ff3db947cfa82d8d52fc94c112bb2a7R243-R247) property to the tool's `Feature` class. If a feature is on and has a `runtimeId`, its `runtimeId` will be [stamped into the Dart application as a Dart define](https://github.com/flutter/flutter/pull/168437/files#diff-bd662448bdc2e6f50e47cd3b20b22b41a828561bce65cb4d54ea4f5011cc604eR293-R327). The framework uses this Dart define to [determine which features are enabled](https://github.com/flutter/flutter/pull/168437/files#diff-c8dbd5cd3103bc5be53c4ac5be8bdb9bf73e10cd5d8e4ac34e737fd1f8602d45). ### Multi-window example https://github.com/flutter/flutter/pull/168697 shows how this new feature flag system can be used to add a multi-window feature flag: 1. It adds a new [multi-window feature](https://github.com/flutter/flutter/pull/168697/files#diff-0ded384225f19a4c34d43c7c11f7cb084ff3db947cfa82d8d52fc94c112bb2a7R189-R198) to the Flutter tool. This can be turned on using `flutter config --enable-multi-window` or by putting `enable-multi-window: true` in an app's .pubspec, under the `flutter` section. 2. It adds a new [`isMultiWindowEnabled`](https://github.com/flutter/flutter/pull/168697/files#diff-c8dbd5cd3103bc5be53c4ac5be8bdb9bf73e10cd5d8e4ac34e737fd1f8602d45R7-R11) property to the framework. 4. The Material library can use this new property to determine whether it should create a new window. [Example](https://github.com/flutter/flutter/pull/168697/files#diff-2cbc1634ed6b61d61dfa090e7bfbbb7c60b74c8abc3a28df6f79eee691fd1b73). ## Limitations ### Tool and framework only For now, these feature flags are available only to the Flutter tool and Flutter framework. The flags are not automatically available to the embedder or the engine. For example, embedders need to configure their surfaces differently if Impeller is enabled. This configuration must happen before the Dart isolate is launched. As a result, the framework's feature flags is not a viable solution for this scenario for now. For these kinds of scenarios, we should continue to use platform-specific configuration like the `AndroidManifest.xml` or `Info.plist` files. This is a fixable limitation, we just need to invest in this plumbing :) ### Tree shaking Feature flags are not designed to help tree shaking. For example, you cannot conditionally import Dart code depending on the enabled feature flags. Code that is feature flagged off will still be imported into user's apps.
339 lines
11 KiB
Dart
339 lines
11 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
/// @docImport 'flutter_features.dart';
|
|
library;
|
|
|
|
import 'package:meta/meta.dart';
|
|
|
|
import 'base/context.dart';
|
|
|
|
/// The current [FeatureFlags] implementation.
|
|
FeatureFlags get featureFlags => context.get<FeatureFlags>()!;
|
|
|
|
/// The interface used to determine if a particular [Feature] is enabled.
|
|
///
|
|
/// This class is extended in google3. Whenever a new flag is added,
|
|
/// google3 must also be updated using a g3fix.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [FlutterFeatureFlags], Flutter's implementation of this class.
|
|
/// * https://github.com/flutter/flutter/blob/main/docs/contributing/Feature-flags.md,
|
|
/// docs on feature flags and how to add or use them.
|
|
abstract class FeatureFlags {
|
|
/// const constructor so that subclasses can be const.
|
|
const FeatureFlags();
|
|
|
|
/// Whether flutter desktop for linux is enabled.
|
|
bool get isLinuxEnabled;
|
|
|
|
/// Whether flutter desktop for macOS is enabled.
|
|
bool get isMacOSEnabled;
|
|
|
|
/// Whether flutter web is enabled.
|
|
bool get isWebEnabled;
|
|
|
|
/// Whether flutter desktop for Windows is enabled.
|
|
bool get isWindowsEnabled;
|
|
|
|
/// Whether android is enabled.
|
|
bool get isAndroidEnabled;
|
|
|
|
/// Whether iOS is enabled.
|
|
bool get isIOSEnabled;
|
|
|
|
/// Whether fuchsia is enabled.
|
|
bool get isFuchsiaEnabled;
|
|
|
|
/// Whether custom devices are enabled.
|
|
bool get areCustomDevicesEnabled;
|
|
|
|
/// Whether animations are used in the command line interface.
|
|
bool get isCliAnimationEnabled;
|
|
|
|
/// Whether native assets compilation and bundling is enabled.
|
|
bool get isNativeAssetsEnabled;
|
|
|
|
/// Whether Swift Package Manager dependency management is enabled.
|
|
bool get isSwiftPackageManagerEnabled;
|
|
|
|
/// Whether to stop writing the `{FLUTTER_ROOT}/version` file.
|
|
///
|
|
/// Tracking removal: <https://github.com/flutter/flutter/issues/171900>.
|
|
bool get isOmitLegacyVersionFileEnabled;
|
|
|
|
/// Whether a particular feature is enabled for the current channel.
|
|
///
|
|
/// Prefer using one of the specific getters above instead of this API.
|
|
bool isEnabled(Feature feature);
|
|
|
|
/// All current Flutter feature flags.
|
|
List<Feature> get allFeatures => const <Feature>[
|
|
flutterWebFeature,
|
|
flutterLinuxDesktopFeature,
|
|
flutterMacOSDesktopFeature,
|
|
flutterWindowsDesktopFeature,
|
|
flutterAndroidFeature,
|
|
flutterIOSFeature,
|
|
flutterFuchsiaFeature,
|
|
flutterCustomDevicesFeature,
|
|
cliAnimation,
|
|
nativeAssets,
|
|
swiftPackageManager,
|
|
omitLegacyVersionFile,
|
|
];
|
|
|
|
/// All current Flutter feature flags that can be configured.
|
|
///
|
|
/// [Feature.configSetting] is not `null`.
|
|
Iterable<Feature> get allConfigurableFeatures {
|
|
return allFeatures.where((Feature feature) => feature.configSetting != null);
|
|
}
|
|
|
|
/// All Flutter feature flags that are enabled.
|
|
// This member is overriden in google3.
|
|
Iterable<Feature> get allEnabledFeatures {
|
|
return allFeatures.where(isEnabled);
|
|
}
|
|
}
|
|
|
|
/// All current Flutter feature flags that can be configured.
|
|
///
|
|
/// [Feature.configSetting] is not `null`.
|
|
Iterable<Feature> get allConfigurableFeatures => featureFlags.allConfigurableFeatures;
|
|
|
|
/// The [Feature] for flutter web.
|
|
const flutterWebFeature = Feature.fullyEnabled(
|
|
name: 'Flutter for web',
|
|
configSetting: 'enable-web',
|
|
environmentOverride: 'FLUTTER_WEB',
|
|
);
|
|
|
|
/// The [Feature] for macOS desktop.
|
|
const flutterMacOSDesktopFeature = Feature.fullyEnabled(
|
|
name: 'support for desktop on macOS',
|
|
configSetting: 'enable-macos-desktop',
|
|
environmentOverride: 'FLUTTER_MACOS',
|
|
);
|
|
|
|
/// The [Feature] for Linux desktop.
|
|
const flutterLinuxDesktopFeature = Feature.fullyEnabled(
|
|
name: 'support for desktop on Linux',
|
|
configSetting: 'enable-linux-desktop',
|
|
environmentOverride: 'FLUTTER_LINUX',
|
|
);
|
|
|
|
/// The [Feature] for Windows desktop.
|
|
const flutterWindowsDesktopFeature = Feature.fullyEnabled(
|
|
name: 'support for desktop on Windows',
|
|
configSetting: 'enable-windows-desktop',
|
|
environmentOverride: 'FLUTTER_WINDOWS',
|
|
);
|
|
|
|
/// The [Feature] for Android devices.
|
|
const flutterAndroidFeature = Feature.fullyEnabled(
|
|
name: 'Flutter for Android',
|
|
configSetting: 'enable-android',
|
|
);
|
|
|
|
/// The [Feature] for iOS devices.
|
|
const flutterIOSFeature = Feature.fullyEnabled(
|
|
name: 'Flutter for iOS',
|
|
configSetting: 'enable-ios',
|
|
);
|
|
|
|
/// The [Feature] for Fuchsia support.
|
|
const flutterFuchsiaFeature = Feature(
|
|
name: 'Flutter for Fuchsia',
|
|
configSetting: 'enable-fuchsia',
|
|
environmentOverride: 'FLUTTER_FUCHSIA',
|
|
master: FeatureChannelSetting(available: true),
|
|
);
|
|
|
|
const flutterCustomDevicesFeature = Feature(
|
|
name: 'early support for custom device types',
|
|
configSetting: 'enable-custom-devices',
|
|
environmentOverride: 'FLUTTER_CUSTOM_DEVICES',
|
|
master: FeatureChannelSetting(available: true),
|
|
beta: FeatureChannelSetting(available: true),
|
|
stable: FeatureChannelSetting(available: true),
|
|
);
|
|
|
|
/// The [Feature] for CLI animations.
|
|
///
|
|
/// The TERM environment variable set to "dumb" turns this off.
|
|
const cliAnimation = Feature.fullyEnabled(
|
|
name: 'animations in the command line interface',
|
|
configSetting: 'cli-animations',
|
|
);
|
|
|
|
/// Enable native assets compilation and bundling.
|
|
const nativeAssets = Feature(
|
|
name: 'native assets compilation and bundling',
|
|
configSetting: 'enable-native-assets',
|
|
environmentOverride: 'FLUTTER_NATIVE_ASSETS',
|
|
master: FeatureChannelSetting(available: true, enabledByDefault: true),
|
|
beta: FeatureChannelSetting(available: true, enabledByDefault: true),
|
|
);
|
|
|
|
/// Enable Swift Package Manager as a darwin dependency manager.
|
|
const swiftPackageManager = Feature(
|
|
name: 'support for Swift Package Manager for iOS and macOS',
|
|
configSetting: 'enable-swift-package-manager',
|
|
environmentOverride: 'FLUTTER_SWIFT_PACKAGE_MANAGER',
|
|
master: FeatureChannelSetting(available: true),
|
|
beta: FeatureChannelSetting(available: true),
|
|
stable: FeatureChannelSetting(available: true),
|
|
);
|
|
|
|
/// Whether to continue writing the `{FLUTTER_ROOT}/version` legacy file.
|
|
///
|
|
/// Tracking removal: <https://github.com/flutter/flutter/issues/171900>.
|
|
const omitLegacyVersionFile = Feature(
|
|
name: 'stops writing the legacy version file',
|
|
configSetting: 'omit-legacy-version-file',
|
|
extraHelpText:
|
|
'If set, the file {FLUTTER_ROOT}/version is no longer written as part of '
|
|
'the flutter tool execution; a newer file format has existed for some '
|
|
'time in {FLUTTER_ROOT}/bin/cache/flutter.version.json.',
|
|
master: FeatureChannelSetting(available: true),
|
|
beta: FeatureChannelSetting(available: true),
|
|
stable: FeatureChannelSetting(available: true),
|
|
);
|
|
|
|
/// A [Feature] is a process for conditionally enabling tool features.
|
|
///
|
|
/// All settings are optional, and if not provided will generally default to
|
|
/// a "safe" value, such as being off.
|
|
///
|
|
/// The top level feature settings can be provided to apply to all channels.
|
|
/// Otherwise, more specific settings take precedence over higher level
|
|
/// settings.
|
|
class Feature {
|
|
/// Creates a [Feature].
|
|
const Feature({
|
|
required this.name,
|
|
this.environmentOverride,
|
|
this.configSetting,
|
|
this.runtimeId,
|
|
this.extraHelpText,
|
|
this.master = const FeatureChannelSetting(),
|
|
this.beta = const FeatureChannelSetting(),
|
|
this.stable = const FeatureChannelSetting(),
|
|
});
|
|
|
|
/// Creates a [Feature] that is fully enabled across channels.
|
|
const Feature.fullyEnabled({
|
|
required this.name,
|
|
this.environmentOverride,
|
|
this.configSetting,
|
|
this.runtimeId,
|
|
this.extraHelpText,
|
|
}) : master = const FeatureChannelSetting(available: true, enabledByDefault: true),
|
|
beta = const FeatureChannelSetting(available: true, enabledByDefault: true),
|
|
stable = const FeatureChannelSetting(available: true, enabledByDefault: true);
|
|
|
|
/// The user visible name for this feature.
|
|
final String name;
|
|
|
|
/// The settings for the master branch and other unknown channels.
|
|
final FeatureChannelSetting master;
|
|
|
|
/// The settings for the beta branch.
|
|
final FeatureChannelSetting beta;
|
|
|
|
/// The settings for the stable branch.
|
|
final FeatureChannelSetting stable;
|
|
|
|
/// The name of an environment variable that can override the setting.
|
|
///
|
|
/// The environment variable needs to be set to the value 'true'. This is
|
|
/// only intended for usage by CI and not as an advertised method to enable
|
|
/// a feature.
|
|
///
|
|
/// If not provided, defaults to `null` meaning there is no override.
|
|
final String? environmentOverride;
|
|
|
|
/// The name of a setting that can be used to enable this feature.
|
|
///
|
|
/// If not provided, defaults to `null` meaning there is no config setting.
|
|
final String? configSetting;
|
|
|
|
/// The unique identifier for this feature at runtime.
|
|
///
|
|
/// If not `null`, the Flutter framework's enabled feature flags will
|
|
/// contain this value if this feature is enabled.
|
|
final String? runtimeId;
|
|
|
|
/// Additional text to add to the end of the help message.
|
|
///
|
|
/// If not provided, defaults to `null` meaning there is no additional text.
|
|
final String? extraHelpText;
|
|
|
|
/// A help message for the `flutter config` command, or null if unsupported.
|
|
String? generateHelpMessage() {
|
|
if (configSetting == null) {
|
|
return null;
|
|
}
|
|
final buffer = StringBuffer('Enable or disable $name.');
|
|
final channels = <String>[
|
|
if (master.available) 'master',
|
|
if (beta.available) 'beta',
|
|
if (stable.available) 'stable',
|
|
];
|
|
// Add channel info for settings only on some channels.
|
|
if (channels.length == 1) {
|
|
buffer.write('\nThis setting applies only to the ${channels.single} channel.');
|
|
} else if (channels.length == 2) {
|
|
buffer.write('\nThis setting applies only to the ${channels.join(' and ')} channels.');
|
|
}
|
|
if (extraHelpText != null) {
|
|
buffer.write(' $extraHelpText');
|
|
}
|
|
return buffer.toString();
|
|
}
|
|
|
|
/// Retrieve the correct setting for the provided `channel`.
|
|
FeatureChannelSetting getSettingForChannel(String channel) {
|
|
return switch (channel) {
|
|
'stable' => stable,
|
|
'beta' => beta,
|
|
'master' || _ => master,
|
|
};
|
|
}
|
|
}
|
|
|
|
/// A description of the conditions to enable a feature for a particular channel.
|
|
@immutable
|
|
final class FeatureChannelSetting {
|
|
const FeatureChannelSetting({this.available = false, this.enabledByDefault = false});
|
|
|
|
/// Whether the feature is available on this channel.
|
|
///
|
|
/// If not provided, defaults to `false`. This implies that the feature
|
|
/// cannot be enabled even by the settings below.
|
|
final bool available;
|
|
|
|
/// Whether the feature is enabled by default.
|
|
///
|
|
/// If not provided, defaults to `false`.
|
|
final bool enabledByDefault;
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
return other is FeatureChannelSetting &&
|
|
available == other.available &&
|
|
enabledByDefault == other.enabledByDefault;
|
|
}
|
|
|
|
@override
|
|
int get hashCode => Object.hash(available, enabledByDefault);
|
|
|
|
@override
|
|
String toString() {
|
|
return 'FeatureChannelSetting <available: $available, enabledByDefault: $enabledByDefault>';
|
|
}
|
|
}
|