features.dart 8.97 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8 9
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'base/context.dart';

/// The current [FeatureFlags] implementation.
///
/// If not injected, a default implementation is provided.
10
FeatureFlags get featureFlags => context.get<FeatureFlags>()!;
11 12 13 14

/// The interface used to determine if a particular [Feature] is enabled.
///
/// The rest of the tools code should use this class instead of looking up
15
/// features directly. To facilitate rolls to google3 and other clients, all
16 17 18
/// flags should be provided with a default implementation here. Clients that
/// use this class should extent instead of implement, so that new flags are
/// picked up automatically.
19 20
abstract class FeatureFlags {
  /// const constructor so that subclasses can be const.
21 22 23
  const FeatureFlags();

  /// Whether flutter desktop for linux is enabled.
24
  bool get isLinuxEnabled => false;
25 26

  /// Whether flutter desktop for macOS is enabled.
27
  bool get isMacOSEnabled => false;
28 29

  /// Whether flutter web is enabled.
30
  bool get isWebEnabled => false;
31 32

  /// Whether flutter desktop for Windows is enabled.
33 34 35 36 37 38 39 40 41 42
  bool get isWindowsEnabled => false;

  /// Whether android is enabled.
  bool get isAndroidEnabled => true;

  /// Whether iOS is enabled.
  bool get isIOSEnabled => true;

  /// Whether fuchsia is enabled.
  bool get isFuchsiaEnabled => true;
43

44 45 46
  /// Whether custom devices are enabled.
  bool get areCustomDevicesEnabled => false;

47
  /// Whether fast single widget reloads are enabled.
48
  bool get isSingleWidgetReloadEnabled => false;
49

50 51 52
  /// Whether WebAssembly compilation for Flutter Web is enabled.
  bool get isFlutterWebWasmEnabled => false;

53 54 55
  /// Whether a particular feature is enabled for the current channel.
  ///
  /// Prefer using one of the specific getters above instead of this API.
56
  bool isEnabled(Feature feature);
57 58
}

59 60 61 62 63 64
/// All current Flutter feature flags.
const List<Feature> allFeatures = <Feature>[
  flutterWebFeature,
  flutterLinuxDesktopFeature,
  flutterMacOSDesktopFeature,
  flutterWindowsDesktopFeature,
65
  singleWidgetReload,
66 67 68
  flutterAndroidFeature,
  flutterIOSFeature,
  flutterFuchsiaFeature,
69
  flutterCustomDevicesFeature,
70
  flutterWebWasm,
71 72
];

73 74 75 76 77
/// All current Flutter feature flags that can be configured.
///
/// [Feature.configSetting] is not `null`.
Iterable<Feature> get allConfigurableFeatures => allFeatures.where((Feature feature) => feature.configSetting != null);

78
/// The [Feature] for flutter web.
79
const Feature flutterWebFeature = Feature.fullyEnabled(
80
  name: 'Flutter for web',
81 82 83 84 85
  configSetting: 'enable-web',
  environmentOverride: 'FLUTTER_WEB',
);

/// The [Feature] for macOS desktop.
86
const Feature flutterMacOSDesktopFeature = Feature.fullyEnabled(
87
  name: 'support for desktop on macOS',
88
  configSetting: 'enable-macos-desktop',
89
  environmentOverride: 'FLUTTER_MACOS',
90 91 92
);

/// The [Feature] for Linux desktop.
93
const Feature flutterLinuxDesktopFeature = Feature.fullyEnabled(
94
  name: 'support for desktop on Linux',
95
  configSetting: 'enable-linux-desktop',
96
  environmentOverride: 'FLUTTER_LINUX',
97 98 99
);

/// The [Feature] for Windows desktop.
100
const Feature flutterWindowsDesktopFeature = Feature.fullyEnabled(
101
  name: 'support for desktop on Windows',
102
  configSetting: 'enable-windows-desktop',
103
  environmentOverride: 'FLUTTER_WINDOWS',
104 105
);

106
/// The [Feature] for Android devices.
107
const Feature flutterAndroidFeature = Feature.fullyEnabled(
108 109 110 111 112
  name: 'Flutter for Android',
  configSetting: 'enable-android',
);

/// The [Feature] for iOS devices.
113
const Feature flutterIOSFeature = Feature.fullyEnabled(
114 115 116 117 118 119 120 121 122 123 124 125 126 127
  name: 'Flutter for iOS',
  configSetting: 'enable-ios',
);

/// The [Feature] for Fuchsia support.
const Feature flutterFuchsiaFeature = Feature(
  name: 'Flutter for Fuchsia',
  configSetting: 'enable-fuchsia',
  environmentOverride: 'FLUTTER_FUCHSIA',
  master: FeatureChannelSetting(
    available: true,
  ),
);

128 129 130 131 132 133 134
const Feature flutterCustomDevicesFeature = Feature(
  name: 'Early support for custom device types',
  configSetting: 'enable-custom-devices',
  environmentOverride: 'FLUTTER_CUSTOM_DEVICES',
  master: FeatureChannelSetting(
    available: true,
  ),
135 136 137 138 139 140
  beta: FeatureChannelSetting(
    available: true,
  ),
  stable: FeatureChannelSetting(
    available: true,
  ),
141 142
);

143 144 145 146
/// The fast hot reload feature for https://github.com/flutter/flutter/issues/61407.
const Feature singleWidgetReload = Feature(
  name: 'Hot reload optimization for changes to class body of a single widget',
  configSetting: 'single-widget-reload-optimization',
147
  environmentOverride: 'FLUTTER_SINGLE_WIDGET_RELOAD',
148 149
  master: FeatureChannelSetting(
    available: true,
150
    enabledByDefault: true,
151 152 153
  ),
  beta: FeatureChannelSetting(
    available: true,
154 155 156 157 158 159 160 161 162 163
  ),
);

/// Enabling WebAssembly compilation from `flutter build web`
const Feature flutterWebWasm = Feature(
  name: 'WebAssembly compilation from flutter build web',
  environmentOverride: 'FLUTTER_WEB_WASM',
  master: FeatureChannelSetting(
    available: true,
    enabledByDefault: true,
164 165 166
  ),
);

167 168 169 170 171 172
/// 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.
173
/// Otherwise, more specific settings take precedence over higher level
174 175 176 177
/// settings.
class Feature {
  /// Creates a [Feature].
  const Feature({
178
    required this.name,
179 180
    this.environmentOverride,
    this.configSetting,
181
    this.extraHelpText,
182 183
    this.master = const FeatureChannelSetting(),
    this.beta = const FeatureChannelSetting(),
184
    this.stable = const FeatureChannelSetting()
185 186
  });

187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
  /// Creates a [Feature] that is fully enabled across channels.
  const Feature.fullyEnabled(
      {required this.name,
      this.environmentOverride,
      this.configSetting,
      this.extraHelpText})
      : master = const FeatureChannelSetting(
          available: true,
          enabledByDefault: true,
        ),
        beta = const FeatureChannelSetting(
          available: true,
          enabledByDefault: true,
        ),
        stable = const FeatureChannelSetting(
          available: true,
          enabledByDefault: true,
        );

206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
  /// 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.
225
  final String? environmentOverride;
226 227 228 229

  /// 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.
230
  final String? configSetting;
231

232 233 234
  /// Additional text to add to the end of the help message.
  ///
  /// If not provided, defaults to `null` meaning there is no additional text.
235
  final String? extraHelpText;
236

237
  /// A help message for the `flutter config` command, or null if unsupported.
238
  String? generateHelpMessage() {
239 240 241
    if (configSetting == null) {
      return null;
    }
242 243
    final StringBuffer buffer = StringBuffer('Enable or disable $name. '
        'This setting will take effect on ');
244 245 246 247 248 249 250
    final List<String> channels = <String>[
      if (master.available) 'master',
      if (beta.available) 'beta',
      if (stable.available) 'stable',
    ];
    if (channels.length == 1) {
      buffer.write('the ${channels.single} channel.');
251 252
    } else if (channels.length == 2) {
      buffer.write('the ${channels.join(' and ')} channels.');
253
    } else {
254 255 256
      final String prefix = (channels.toList()
        ..removeLast()).join(', ');
      buffer.write('the $prefix, and ${channels.last} channels.');
257
    }
258 259 260
    if (extraHelpText != null) {
      buffer.write(' $extraHelpText');
    }
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
    return buffer.toString();
  }

  /// Retrieve the correct setting for the provided `channel`.
  FeatureChannelSetting getSettingForChannel(String channel) {
    switch (channel) {
      case 'stable':
        return stable;
      case 'beta':
        return beta;
      case 'master':
      default:
        return master;
    }
  }
}

/// A description of the conditions to enable a feature for a particular channel.
class FeatureChannelSetting {
  const FeatureChannelSetting({
    this.available = false,
    this.enabledByDefault = false,
  });

  /// Whether the feature is available on this channel.
  ///
287
  /// If not provided, defaults to `false`. This implies that the feature
288 289 290 291 292 293 294 295
  /// 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;
}