1// Copyright 2019 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'package:meta/meta.dart'; 6 7import 'base/config.dart'; 8import 'base/context.dart'; 9import 'base/platform.dart'; 10import 'version.dart'; 11 12/// The current [FeatureFlags] implementation. 13/// 14/// If not injected, a default implementation is provided. 15FeatureFlags get featureFlags => context.get<FeatureFlags>(); 16 17/// The interface used to determine if a particular [Feature] is enabled. 18/// 19/// The rest of the tools code should use this class instead of looking up 20/// features directly. To faciliate rolls to google3 and other clients, all 21/// flags should be provided with a default implementation here. Clients that 22/// use this class should extent instead of implement, so that new flags are 23/// picked up automatically. 24class FeatureFlags { 25 const FeatureFlags(); 26 27 /// Whether flutter desktop for linux is enabled. 28 bool get isLinuxEnabled => _isEnabled(flutterLinuxDesktopFeature); 29 30 /// Whether flutter desktop for macOS is enabled. 31 bool get isMacOSEnabled => _isEnabled(flutterMacOSDesktopFeature); 32 33 /// Whether flutter web is enabled. 34 bool get isWebEnabled => _isEnabled(flutterWebFeature); 35 36 /// Whether flutter desktop for Windows is enabled. 37 bool get isWindowsEnabled => _isEnabled(flutterWindowsDesktopFeature); 38 39 /// Whether plugins are built as AARs in app projects. 40 bool get isPluginAsAarEnabled => _isEnabled(flutterBuildPluginAsAarFeature); 41 42 // Calculate whether a particular feature is enabled for the current channel. 43 static bool _isEnabled(Feature feature) { 44 final String currentChannel = FlutterVersion.instance.channel; 45 final FeatureChannelSetting featureSetting = feature.getSettingForChannel(currentChannel); 46 if (!featureSetting.available) { 47 return false; 48 } 49 bool isEnabled = featureSetting.enabledByDefault; 50 if (feature.configSetting != null) { 51 final bool configOverride = Config.instance.getValue(feature.configSetting); 52 if (configOverride != null) { 53 isEnabled = configOverride; 54 } 55 } 56 if (feature.environmentOverride != null) { 57 if (platform.environment[feature.environmentOverride]?.toLowerCase() == 'true') { 58 isEnabled = true; 59 } 60 } 61 return isEnabled; 62 } 63} 64 65/// All current Flutter feature flags. 66const List<Feature> allFeatures = <Feature>[ 67 flutterWebFeature, 68 flutterLinuxDesktopFeature, 69 flutterMacOSDesktopFeature, 70 flutterWindowsDesktopFeature, 71 flutterBuildPluginAsAarFeature, 72]; 73 74/// The [Feature] for flutter web. 75const Feature flutterWebFeature = Feature( 76 name: 'Flutter for web', 77 configSetting: 'enable-web', 78 environmentOverride: 'FLUTTER_WEB', 79 master: FeatureChannelSetting( 80 available: true, 81 enabledByDefault: false, 82 ), 83 dev: FeatureChannelSetting( 84 available: true, 85 enabledByDefault: false, 86 ), 87); 88 89/// The [Feature] for macOS desktop. 90const Feature flutterMacOSDesktopFeature = Feature( 91 name: 'Flutter for desktop on macOS', 92 configSetting: 'enable-macos-desktop', 93 environmentOverride: 'ENABLE_FLUTTER_DESKTOP', 94 master: FeatureChannelSetting( 95 available: true, 96 enabledByDefault: false, 97 ), 98); 99 100/// The [Feature] for Linux desktop. 101const Feature flutterLinuxDesktopFeature = Feature( 102 name: 'Flutter for desktop on Linux', 103 configSetting: 'enable-linux-desktop', 104 environmentOverride: 'ENABLE_FLUTTER_DESKTOP', 105 master: FeatureChannelSetting( 106 available: true, 107 enabledByDefault: false, 108 ), 109); 110 111/// The [Feature] for Windows desktop. 112const Feature flutterWindowsDesktopFeature = Feature( 113 name: 'Flutter for desktop on Windows', 114 configSetting: 'enable-windows-desktop', 115 environmentOverride: 'ENABLE_FLUTTER_DESKTOP', 116 master: FeatureChannelSetting( 117 available: true, 118 enabledByDefault: false, 119 ), 120); 121 122/// The [Feature] for building plugins as AARs in an app project. 123const Feature flutterBuildPluginAsAarFeature = Feature( 124 name: 'Build plugins independently as AARs in app projects', 125 configSetting: 'enable-build-plugin-as-aar', 126 master: FeatureChannelSetting( 127 available: true, 128 enabledByDefault: false, 129 ), 130); 131 132/// A [Feature] is a process for conditionally enabling tool features. 133/// 134/// All settings are optional, and if not provided will generally default to 135/// a "safe" value, such as being off. 136/// 137/// The top level feature settings can be provided to apply to all channels. 138/// Otherwise, more specific settings take precedence over higher level 139/// settings. 140class Feature { 141 /// Creates a [Feature]. 142 const Feature({ 143 @required this.name, 144 this.environmentOverride, 145 this.configSetting, 146 this.master = const FeatureChannelSetting(), 147 this.dev = const FeatureChannelSetting(), 148 this.beta = const FeatureChannelSetting(), 149 this.stable = const FeatureChannelSetting(), 150 }); 151 152 /// The user visible name for this feature. 153 final String name; 154 155 /// The settings for the master branch and other unknown channels. 156 final FeatureChannelSetting master; 157 158 /// The settings for the dev branch. 159 final FeatureChannelSetting dev; 160 161 /// The settings for the beta branch. 162 final FeatureChannelSetting beta; 163 164 /// The settings for the stable branch. 165 final FeatureChannelSetting stable; 166 167 /// The name of an environment variable that can override the setting. 168 /// 169 /// The environment variable needs to be set to the value 'true'. This is 170 /// only intended for usage by CI and not as an advertised method to enable 171 /// a feature. 172 /// 173 /// If not provided, defaults to `null` meaning there is no override. 174 final String environmentOverride; 175 176 /// The name of a setting that can be used to enable this feature. 177 /// 178 /// If not provided, defaults to `null` meaning there is no config setting. 179 final String configSetting; 180 181 /// A help message for the `flutter config` command, or null if unsupported. 182 String generateHelpMessage() { 183 if (configSetting == null) { 184 return null; 185 } 186 final StringBuffer buffer = StringBuffer('Enable or disable $name. ' 187 'This setting will take effect on '); 188 final List<String> channels = <String>[ 189 if (master.available) 'master', 190 if (dev.available) 'dev', 191 if (beta.available) 'beta', 192 if (stable.available) 'stable', 193 ]; 194 if (channels.length == 1) { 195 buffer.write('the ${channels.single} channel.'); 196 } else if (channels.length == 2) { 197 buffer.write('the ${channels.join(' and ')} channels.'); 198 } else { 199 final String prefix = (channels.toList() 200 ..removeLast()).join(', '); 201 buffer.write('the $prefix, and ${channels.last} channels.'); 202 } 203 return buffer.toString(); 204 } 205 206 /// Retrieve the correct setting for the provided `channel`. 207 FeatureChannelSetting getSettingForChannel(String channel) { 208 switch (channel) { 209 case 'stable': 210 return stable; 211 case 'beta': 212 return beta; 213 case 'dev': 214 return dev; 215 case 'master': 216 default: 217 return master; 218 } 219 } 220} 221 222/// A description of the conditions to enable a feature for a particular channel. 223class FeatureChannelSetting { 224 const FeatureChannelSetting({ 225 this.available = false, 226 this.enabledByDefault = false, 227 }); 228 229 /// Whether the feature is available on this channel. 230 /// 231 /// If not provded, defaults to `false`. This implies that the feature 232 /// cannot be enabled even by the settings below. 233 final bool available; 234 235 /// Whether the feature is enabled by default. 236 /// 237 /// If not provided, defaults to `false`. 238 final bool enabledByDefault; 239} 240