config.dart 8.04 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 '../base/common.dart';
8
import '../convert.dart';
9
import '../features.dart';
10
import '../globals.dart' as globals;
11
import '../reporting/reporting.dart';
12 13 14
import '../runner/flutter_command.dart';

class ConfigCommand extends FlutterCommand {
15
  ConfigCommand({ bool verboseHelp = false }) {
16
    argParser.addFlag('analytics',
17
      help: 'Enable or disable reporting anonymously tool usage statistics and crash reports.');
18
    argParser.addFlag('clear-ios-signing-cert',
19
      negatable: false,
20
      help: 'Clear the saved development certificate choice used to sign apps for iOS device deployment.');
21
    argParser.addOption('android-sdk', help: 'The Android SDK directory.');
22
    argParser.addOption('android-studio-dir', help: 'The Android Studio install directory. If unset, flutter will search for valid installs at well-known locations.');
23
    argParser.addOption('build-dir', help: 'The relative path to override a projects build directory.',
24
        valueHelp: 'out/');
25 26 27
    argParser.addFlag('machine',
      negatable: false,
      hide: !verboseHelp,
Josh Soref's avatar
Josh Soref committed
28
      help: 'Print config values as json.');
29
    for (final Feature feature in allFeatures) {
30 31
      final String? configSetting = feature.configSetting;
      if (configSetting == null) {
32 33 34
        continue;
      }
      argParser.addFlag(
35
        configSetting,
36 37 38 39 40 41 42 43
        help: feature.generateHelpMessage(),
      );
    }
    argParser.addFlag(
      'clear-features',
      help: 'Remove all configured features and restore them to the default values.',
      negatable: false,
    );
44 45 46 47 48 49 50 51
  }

  @override
  final String name = 'config';

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

56 57 58
  @override
  final String category = FlutterCommandCategory.sdk;

59 60 61
  @override
  final List<String> aliases = <String>['configure'];

62 63 64
  @override
  bool get shouldUpdateCache => false;

65
  @override
66
  String get usageFooter {
67 68 69
    // List all config settings. for feature flags, include whether they
    // are available.
    final Map<String, Feature> featuresByName = <String, Feature>{};
70
    final String channel = globals.flutterVersion.channel;
71
    for (final Feature feature in allFeatures) {
72 73 74
      final String? configSetting = feature.configSetting;
      if (configSetting != null) {
        featuresByName[configSetting] = feature;
75 76
      }
    }
77
    String values = globals.config.keys
78 79 80
        .map<String>((String key) {
          String configFooter = '';
          if (featuresByName.containsKey(key)) {
81
            final FeatureChannelSetting setting = featuresByName[key]!.getSettingForChannel(channel);
82 83 84 85
            if (!setting.available) {
              configFooter = '(Unavailable)';
            }
          }
86
          return '  $key: ${globals.config.getValue(key)} $configFooter';
87
        }).join('\n');
88
    if (values.isEmpty) {
89
      values = '  No settings have been configured.';
90
    }
91 92
    final bool analyticsEnabled = globals.flutterUsage.enabled &&
                                  !globals.flutterUsage.suppressAnalytics;
93
    return
94
      '\nSettings:\n$values\n\n'
95
      'Analytics reporting is currently ${analyticsEnabled ? 'enabled' : 'disabled'}.';
96
  }
97

98
  /// Return null to disable analytics recording of the `config` command.
99
  @override
100
  Future<String?> get usagePath async => null;
101 102

  @override
103
  Future<FlutterCommandResult> runCommand() async {
104 105 106 107 108 109 110 111 112 113
    final List<String> rest = argResults?.rest ?? <String>[];
    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"');
    }

114
    if (boolArg('machine')) {
115
      await handleMachine();
116
      return FlutterCommandResult.success();
117
    }
118

119
    if (boolArg('clear-features')) {
120
      for (final Feature feature in allFeatures) {
121 122 123
        final String? configSetting = feature.configSetting;
        if (configSetting != null) {
          globals.config.removeValue(configSetting);
124 125
        }
      }
126
      return FlutterCommandResult.success();
127 128
    }

129
    if (argResults?.wasParsed('analytics') ?? false) {
130
      final bool value = boolArg('analytics');
131 132
      // The tool sends the analytics event *before* toggling the flag
      // intentionally to be sure that opt-out events are sent correctly.
133
      AnalyticsConfigEvent(enabled: value).send();
134 135 136 137 138 139
      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();
      }
140
      globals.flutterUsage.enabled = value;
141
      globals.printStatus('Analytics reporting ${value ? 'enabled' : 'disabled'}.');
142 143 144 145 146

      // 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);
147 148
    }

149
    if (argResults?.wasParsed('android-sdk') ?? false) {
150
      _updateConfig('android-sdk', stringArg('android-sdk')!);
151
    }
152

153
    if (argResults?.wasParsed('android-studio-dir') ?? false) {
154
      _updateConfig('android-studio-dir', stringArg('android-studio-dir')!);
155
    }
156

157
    if (argResults?.wasParsed('clear-ios-signing-cert') ?? false) {
158
      _updateConfig('ios-signing-cert', '');
159
    }
160

161
    if (argResults?.wasParsed('build-dir') ?? false) {
162
      final String buildDir = stringArg('build-dir')!;
163
      if (globals.fs.path.isAbsolute(buildDir)) {
164 165 166 167 168
        throwToolExit('build-dir should be a relative path');
      }
      _updateConfig('build-dir', buildDir);
    }

169
    for (final Feature feature in allFeatures) {
170 171
      final String? configSetting = feature.configSetting;
      if (configSetting == null) {
172 173
        continue;
      }
174
      if (argResults?.wasParsed(configSetting) ?? false) {
175
        final bool keyValue = boolArg(configSetting);
176 177
        globals.config.setValue(configSetting, keyValue);
        globals.printStatus('Setting "$configSetting" value to "$keyValue".');
178 179 180
      }
    }

181
    if (argResults == null || argResults!.arguments.isEmpty) {
182
      globals.printStatus(usage);
183
    } else {
184
      globals.printStatus('\nYou may need to restart any open editors for them to read new settings.');
185
    }
186

187
    return FlutterCommandResult.success();
188
  }
189

190
  Future<void> handleMachine() async {
191
    // Get all the current values.
192
    final Map<String, Object?> results = <String, Object?>{};
193
    for (final String key in globals.config.keys) {
194
      results[key] = globals.config.getValue(key);
195 196 197
    }

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

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

210 211
  void _updateConfig(String keyName, String keyValue) {
    if (keyValue.isEmpty) {
212 213
      globals.config.removeValue(keyName);
      globals.printStatus('Removing "$keyName" value.');
214
    } else {
215 216
      globals.config.setValue(keyName, keyValue);
      globals.printStatus('Setting "$keyName" value to "$keyValue".');
217 218
    }
  }
219
}