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

5 6
import '../../src/android/android_sdk.dart';
import '../../src/android/android_studio.dart';
7
import '../android/java.dart';
8
import '../base/common.dart';
9
import '../convert.dart';
10
import '../features.dart';
11
import '../globals.dart' as globals;
12
import '../reporting/reporting.dart';
13
import '../runner/flutter_command.dart';
14
import '../runner/flutter_command_runner.dart';
15 16

class ConfigCommand extends FlutterCommand {
17
  ConfigCommand({ bool verboseHelp = false }) {
18 19 20 21 22
    argParser.addFlag(
      'list',
      help: 'List all settings and their current values.',
      negatable: false,
    );
23
    argParser.addFlag('analytics',
24 25 26 27
      hide: !verboseHelp,
      help: 'Enable or disable reporting anonymously tool usage statistics and crash reports.\n'
      '(An alias for "--${FlutterGlobalOptions.kEnableAnalyticsFlag}" '
            'and "--${FlutterGlobalOptions.kDisableAnalyticsFlag}" top level flags.)');
28
    argParser.addFlag('clear-ios-signing-cert',
29
      negatable: false,
30
      help: 'Clear the saved development certificate choice used to sign apps for iOS device deployment.');
31
    argParser.addOption('android-sdk', help: 'The Android SDK directory.');
32 33 34 35 36 37
    argParser.addOption('android-studio-dir', help: 'The Android Studio installation directory. If unset, flutter will search for valid installations at well-known locations.');
    argParser.addOption('jdk-dir', help: 'The Java Development Kit (JDK) installation directory. '
      'If unset, flutter will search for one in the following order:\n'
      '    1) the JDK bundled with the latest installation of Android Studio,\n'
      '    2) the JDK found at the directory found in the JAVA_HOME environment variable, and\n'
      "    3) the directory containing the java binary found in the user's path.");
38
    argParser.addOption('build-dir', help: 'The relative path to override a projects build directory.',
39
        valueHelp: 'out/');
40 41 42
    argParser.addFlag('machine',
      negatable: false,
      hide: !verboseHelp,
Josh Soref's avatar
Josh Soref committed
43
      help: 'Print config values as json.');
44
    for (final Feature feature in allFeatures) {
45 46
      final String? configSetting = feature.configSetting;
      if (configSetting == null) {
47 48 49
        continue;
      }
      argParser.addFlag(
50
        configSetting,
51 52 53 54 55 56 57 58
        help: feature.generateHelpMessage(),
      );
    }
    argParser.addFlag(
      'clear-features',
      help: 'Remove all configured features and restore them to the default values.',
      negatable: false,
    );
59 60 61 62 63 64 65 66
  }

  @override
  final String name = 'config';

  @override
  final String description =
    'Configure Flutter settings.\n\n'
67
    'To remove a setting, configure it to an empty string.\n\n'
68
    'The Flutter tool anonymously reports feature usage statistics and basic crash reports to help improve '
69
    "Flutter tools over time. See Google's privacy policy: https://www.google.com/intl/en/policies/privacy/";
70

71 72 73
  @override
  final String category = FlutterCommandCategory.sdk;

74 75 76
  @override
  final List<String> aliases = <String>['configure'];

77 78 79
  @override
  bool get shouldUpdateCache => false;

80
  @override
81
  String get usageFooter => '\n$analyticsUsage';
82

83
  /// Return null to disable analytics recording of the `config` command.
84
  @override
85
  Future<String?> get usagePath async => null;
86 87

  @override
88
  Future<FlutterCommandResult> runCommand() async {
89
    final List<String> rest = argResults!.rest;
90 91 92 93 94 95 96 97 98
    if (rest.isNotEmpty) {
      throwToolExit(exitCode: 2,
          'error: flutter config: Too many arguments.\n'
          '\n'
          'If a value has a space in it, enclose in quotes on the command line\n'
          'to make a single argument.  For example:\n'
          '    flutter config --android-studio-dir "/opt/Android Studio"');
    }

99 100 101 102 103
    if (boolArg('list')) {
      globals.printStatus(settingsText);
      return FlutterCommandResult.success();
    }

104
    if (boolArg('machine')) {
105
      await handleMachine();
106
      return FlutterCommandResult.success();
107
    }
108

109
    if (boolArg('clear-features')) {
110
      for (final Feature feature in allFeatures) {
111 112 113
        final String? configSetting = feature.configSetting;
        if (configSetting != null) {
          globals.config.removeValue(configSetting);
114 115
        }
      }
116
      globals.printStatus(requireReloadTipText);
117
      return FlutterCommandResult.success();
118 119
    }

120
    if (argResults!.wasParsed('analytics')) {
121
      final bool value = boolArg('analytics');
122 123
      // The tool sends the analytics event *before* toggling the flag
      // intentionally to be sure that opt-out events are sent correctly.
124
      AnalyticsConfigEvent(enabled: value).send();
125 126 127 128 129 130
      if (!value) {
        // Normally, the tool waits for the analytics to all send before the
        // tool exits, but only when analytics are enabled. When reporting that
        // analytics have been disable, the wait must be done here instead.
        await globals.flutterUsage.ensureAnalyticsSent();
      }
131
      globals.flutterUsage.enabled = value;
132
      globals.printStatus('Analytics reporting ${value ? 'enabled' : 'disabled'}.');
133 134 135 136 137

      // TODO(eliasyishak): Set the telemetry for the unified_analytics
      //  package as well, the above will be removed once we have
      //  fully transitioned to using the new package
      await globals.analytics.setTelemetry(value);
138 139
    }

140
    if (argResults!.wasParsed('android-sdk')) {
141
      _updateConfig('android-sdk', stringArg('android-sdk')!);
142
    }
143

144
    if (argResults!.wasParsed('android-studio-dir')) {
145
      _updateConfig('android-studio-dir', stringArg('android-studio-dir')!);
146
    }
147

148 149 150 151 152
    if (argResults!.wasParsed('jdk-dir')) {
      _updateConfig('jdk-dir', stringArg('jdk-dir')!);
    }

    if (argResults!.wasParsed('clear-ios-signing-cert')) {
153
      _updateConfig('ios-signing-cert', '');
154
    }
155

156
    if (argResults!.wasParsed('build-dir')) {
157
      final String buildDir = stringArg('build-dir')!;
158
      if (globals.fs.path.isAbsolute(buildDir)) {
159 160 161 162 163
        throwToolExit('build-dir should be a relative path');
      }
      _updateConfig('build-dir', buildDir);
    }

164
    for (final Feature feature in allFeatures) {
165 166
      final String? configSetting = feature.configSetting;
      if (configSetting == null) {
167 168
        continue;
      }
169
      if (argResults!.wasParsed(configSetting)) {
170
        final bool keyValue = boolArg(configSetting);
171 172
        globals.config.setValue(configSetting, keyValue);
        globals.printStatus('Setting "$configSetting" value to "$keyValue".');
173 174 175
      }
    }

176
    if (argResults == null || argResults!.arguments.isEmpty) {
177
      globals.printStatus(usage);
178
    } else {
179
      globals.printStatus('\n$requireReloadTipText');
180
    }
181

182
    return FlutterCommandResult.success();
183
  }
184

185
  Future<void> handleMachine() async {
186
    // Get all the current values.
187
    final Map<String, Object?> results = <String, Object?>{};
188
    for (final String key in globals.config.keys) {
189
      results[key] = globals.config.getValue(key);
190 191 192
    }

    // Ensure we send any calculated ones, if overrides don't exist.
193 194 195
    final AndroidStudio? androidStudio = globals.androidStudio;
    if (results['android-studio-dir'] == null && androidStudio != null) {
      results['android-studio-dir'] = androidStudio.directory;
196
    }
197 198 199
    final AndroidSdk? androidSdk = globals.androidSdk;
    if (results['android-sdk'] == null && androidSdk != null) {
      results['android-sdk'] = androidSdk.directory.path;
200
    }
201 202 203 204
    final Java? java = globals.java;
    if (results['jdk-dir'] == null && java != null) {
      results['jdk-dir'] = java.javaHome;
    }
205

206
    globals.printStatus(const JsonEncoder.withIndent('  ').convert(results));
207 208
  }

209 210
  void _updateConfig(String keyName, String keyValue) {
    if (keyValue.isEmpty) {
211 212
      globals.config.removeValue(keyName);
      globals.printStatus('Removing "$keyName" value.');
213
    } else {
214 215
      globals.config.setValue(keyName, keyValue);
      globals.printStatus('Setting "$keyName" value to "$keyValue".');
216 217
    }
  }
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263

  /// List all config settings. for feature flags, include whether they are available.
  String get settingsText {
    final Map<String, Feature> featuresByName = <String, Feature>{};
    final String channel = globals.flutterVersion.channel;
    for (final Feature feature in allFeatures) {
      final String? configSetting = feature.configSetting;
      if (configSetting != null) {
        featuresByName[configSetting] = feature;
      }
    }
    final Set<String> keys = <String>{
      ...allFeatures.map((Feature e) => e.configSetting).whereType<String>(),
      ...globals.config.keys,
    };
    final Iterable<String> settings = keys.map<String>((String key) {
      Object? value = globals.config.getValue(key);
      value ??= '(Not set)';
      final StringBuffer buffer = StringBuffer('  $key: $value');
      if (featuresByName.containsKey(key)) {
        final FeatureChannelSetting setting = featuresByName[key]!.getSettingForChannel(channel);
        if (!setting.available) {
          buffer.write(' (Unavailable)');
        }
      }
      return buffer.toString();
    });
    final StringBuffer buffer = StringBuffer();
    buffer.writeln('All Settings:');
    if (settings.isEmpty) {
      buffer.writeln('  No configs have been configured.');
    } else {
      buffer.writeln(settings.join('\n'));
    }
    return buffer.toString();
  }

  /// List the status of the analytics reporting.
  String get analyticsUsage {
    final bool analyticsEnabled =
        globals.flutterUsage.enabled && !globals.flutterUsage.suppressAnalytics;
    return 'Analytics reporting is currently ${analyticsEnabled ? 'enabled' : 'disabled'}.';
  }

  /// Raising the reload tip for setting changes.
  final String requireReloadTipText = 'You may need to restart any open editors for them to read new settings.';
264
}