config.dart 6.93 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
// @dart = 2.8

7
import '../base/common.dart';
8
import '../convert.dart';
9
import '../features.dart';
10
import '../globals_null_migrated.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 17
    argParser.addFlag('analytics',
      negatable: true,
18
      help: 'Enable or disable reporting anonymously tool usage statistics and crash reports.');
19
    argParser.addFlag('clear-ios-signing-cert',
20
      negatable: false,
21
      help: 'Clear the saved development certificate choice used to sign apps for iOS device deployment.');
22
    argParser.addOption('android-sdk', help: 'The Android SDK directory.');
23
    argParser.addOption('android-studio-dir', help: 'The Android Studio install directory.');
24
    argParser.addOption('build-dir', help: 'The relative path to override a projects build directory.',
25
        valueHelp: 'out/');
26 27 28
    argParser.addFlag('machine',
      negatable: false,
      hide: !verboseHelp,
Josh Soref's avatar
Josh Soref committed
29
      help: 'Print config values as json.');
30
    for (final Feature feature in allFeatures) {
31 32 33 34 35 36 37 38 39 40 41 42 43 44
      if (feature.configSetting == null) {
        continue;
      }
      argParser.addFlag(
        feature.configSetting,
        help: feature.generateHelpMessage(),
        negatable: true,
      );
    }
    argParser.addFlag(
      'clear-features',
      help: 'Remove all configured features and restore them to the default values.',
      negatable: false,
    );
45 46 47 48 49 50 51 52
  }

  @override
  final String name = 'config';

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

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

60 61 62
  @override
  bool get shouldUpdateCache => false;

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

95
  /// Return null to disable analytics recording of the `config` command.
96
  @override
97
  Future<String> get usagePath async => null;
98 99

  @override
100
  Future<FlutterCommandResult> runCommand() async {
101
    if (boolArg('machine')) {
102
      await handleMachine();
103
      return FlutterCommandResult.success();
104
    }
105

106
    if (boolArg('clear-features')) {
107
      for (final Feature feature in allFeatures) {
108
        if (feature.configSetting != null) {
109
          globals.config.removeValue(feature.configSetting);
110 111
        }
      }
112
      return FlutterCommandResult.success();
113 114
    }

115
    if (argResults.wasParsed('analytics')) {
116
      final bool value = boolArg('analytics');
117 118
      // The tool sends the analytics event *before* toggling the flag
      // intentionally to be sure that opt-out events are sent correctly.
119
      AnalyticsConfigEvent(enabled: value).send();
120 121 122 123 124 125
      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();
      }
126
      globals.flutterUsage.enabled = value;
127
      globals.printStatus('Analytics reporting ${value ? 'enabled' : 'disabled'}.');
128 129
    }

130
    if (argResults.wasParsed('android-sdk')) {
131
      _updateConfig('android-sdk', stringArg('android-sdk'));
132
    }
133

134
    if (argResults.wasParsed('android-studio-dir')) {
135
      _updateConfig('android-studio-dir', stringArg('android-studio-dir'));
136
    }
137

138
    if (argResults.wasParsed('clear-ios-signing-cert')) {
139
      _updateConfig('ios-signing-cert', '');
140
    }
141

142
    if (argResults.wasParsed('build-dir')) {
143
      final String buildDir = stringArg('build-dir');
144
      if (globals.fs.path.isAbsolute(buildDir)) {
145 146 147 148 149
        throwToolExit('build-dir should be a relative path');
      }
      _updateConfig('build-dir', buildDir);
    }

150
    for (final Feature feature in allFeatures) {
151 152 153 154
      if (feature.configSetting == null) {
        continue;
      }
      if (argResults.wasParsed(feature.configSetting)) {
155
        final bool keyValue = boolArg(feature.configSetting);
156 157
        globals.config.setValue(feature.configSetting, keyValue);
        globals.printStatus('Setting "${feature.configSetting}" value to "$keyValue".');
158 159 160
      }
    }

161
    if (argResults.arguments.isEmpty) {
162
      globals.printStatus(usage);
163
    } else {
164
      globals.printStatus('\nYou may need to restart any open editors for them to read new settings.');
165
    }
166

167
    return FlutterCommandResult.success();
168
  }
169

170
  Future<void> handleMachine() async {
171 172
    // Get all the current values.
    final Map<String, dynamic> results = <String, dynamic>{};
173
    for (final String key in globals.config.keys) {
174
      results[key] = globals.config.getValue(key);
175 176 177
    }

    // Ensure we send any calculated ones, if overrides don't exist.
178 179
    if (results['android-studio-dir'] == null && globals.androidStudio != null) {
      results['android-studio-dir'] = globals.androidStudio.directory;
180
    }
181
    if (results['android-sdk'] == null && globals.androidSdk != null) {
182
      results['android-sdk'] = globals.androidSdk.directory.path;
183
    }
184

185
    globals.printStatus(const JsonEncoder.withIndent('  ').convert(results));
186 187
  }

188 189
  void _updateConfig(String keyName, String keyValue) {
    if (keyValue.isEmpty) {
190 191
      globals.config.removeValue(keyName);
      globals.printStatus('Removing "$keyName" value.');
192
    } else {
193 194
      globals.config.setValue(keyName, keyValue);
      globals.printStatus('Setting "$keyName" value to "$keyValue".');
195 196
    }
  }
197
}