diff --git a/packages/flutter_tools/lib/src/commands/config.dart b/packages/flutter_tools/lib/src/commands/config.dart index 9d0d3d6abfab..0b22f065c965 100644 --- a/packages/flutter_tools/lib/src/commands/config.dart +++ b/packages/flutter_tools/lib/src/commands/config.dart @@ -15,6 +15,11 @@ import '../runner/flutter_command_runner.dart'; class ConfigCommand extends FlutterCommand { ConfigCommand({ bool verboseHelp = false }) { + argParser.addFlag( + 'list', + help: 'List all settings and their current values.', + negatable: false, + ); argParser.addFlag('analytics', hide: !verboseHelp, help: 'Enable or disable reporting anonymously tool usage statistics and crash reports.\n' @@ -73,37 +78,7 @@ class ConfigCommand extends FlutterCommand { bool get shouldUpdateCache => false; @override - String get usageFooter { - // List all config settings. for feature flags, include whether they - // are available. - final Map featuresByName = {}; - final String channel = globals.flutterVersion.channel; - for (final Feature feature in allFeatures) { - final String? configSetting = feature.configSetting; - if (configSetting != null) { - featuresByName[configSetting] = feature; - } - } - String values = globals.config.keys - .map((String key) { - String configFooter = ''; - if (featuresByName.containsKey(key)) { - final FeatureChannelSetting setting = featuresByName[key]!.getSettingForChannel(channel); - if (!setting.available) { - configFooter = '(Unavailable)'; - } - } - return ' $key: ${globals.config.getValue(key)} $configFooter'; - }).join('\n'); - if (values.isEmpty) { - values = ' No settings have been configured.'; - } - final bool analyticsEnabled = globals.flutterUsage.enabled && - !globals.flutterUsage.suppressAnalytics; - return - '\nSettings:\n$values\n\n' - 'Analytics reporting is currently ${analyticsEnabled ? 'enabled' : 'disabled'}.'; - } + String get usageFooter => '\n$analyticsUsage'; /// Return null to disable analytics recording of the `config` command. @override @@ -121,6 +96,11 @@ class ConfigCommand extends FlutterCommand { ' flutter config --android-studio-dir "/opt/Android Studio"'); } + if (boolArg('list')) { + globals.printStatus(settingsText); + return FlutterCommandResult.success(); + } + if (boolArg('machine')) { await handleMachine(); return FlutterCommandResult.success(); @@ -133,6 +113,7 @@ class ConfigCommand extends FlutterCommand { globals.config.removeValue(configSetting); } } + globals.printStatus(requireReloadTipText); return FlutterCommandResult.success(); } @@ -195,7 +176,7 @@ class ConfigCommand extends FlutterCommand { if (argResults == null || argResults!.arguments.isEmpty) { globals.printStatus(usage); } else { - globals.printStatus('\nYou may need to restart any open editors for them to read new settings.'); + globals.printStatus('\n$requireReloadTipText'); } return FlutterCommandResult.success(); @@ -234,4 +215,50 @@ class ConfigCommand extends FlutterCommand { globals.printStatus('Setting "$keyName" value to "$keyValue".'); } } + + /// List all config settings. for feature flags, include whether they are available. + String get settingsText { + final Map featuresByName = {}; + 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 keys = { + ...allFeatures.map((Feature e) => e.configSetting).whereType(), + ...globals.config.keys, + }; + final Iterable settings = keys.map((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.'; } diff --git a/packages/flutter_tools/test/commands.shard/hermetic/config_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/config_test.dart index d3e86de9695e..4f98a5b37020 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/config_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/config_test.dart @@ -12,6 +12,7 @@ import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/config.dart'; +import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/version.dart'; @@ -48,6 +49,23 @@ void main() { } group('config', () { + testUsingContext('prints all settings with --list', () async { + final ConfigCommand configCommand = ConfigCommand(); + final CommandRunner commandRunner = createTestCommandRunner(configCommand); + await commandRunner.run(['config', '--list']); + expect( + testLogger.statusText, + 'All Settings:\n' + '${allFeatures + .where((Feature e) => e.configSetting != null) + .map((Feature e) => ' ${e.configSetting}: (Not set)') + .join('\n')}' + '\n\n', + ); + }, overrides: { + Usage: () => testUsage, + }); + testUsingContext('throws error on excess arguments', () { final ConfigCommand configCommand = ConfigCommand(); final CommandRunner commandRunner = createTestCommandRunner(configCommand); @@ -196,6 +214,7 @@ void main() { await commandRunner.run([ 'config', + '--list' ]); expect( @@ -270,20 +289,21 @@ void main() { Usage: () => testUsage, }); - testUsingContext('analytics reported disabled when suppressed', () async { + testUsingContext('analytics reported with help usages', () async { final ConfigCommand configCommand = ConfigCommand(); - final CommandRunner commandRunner = createTestCommandRunner(configCommand); + createTestCommandRunner(configCommand); testUsage.suppressAnalytics = true; - - await commandRunner.run([ - 'config', - ]); - expect( - testLogger.statusText, + configCommand.usage, containsIgnoringWhitespace('Analytics reporting is currently disabled'), ); + + testUsage.suppressAnalytics = false; + expect( + configCommand.usage, + containsIgnoringWhitespace('Analytics reporting is currently enabled'), + ); }, overrides: { Usage: () => testUsage, }); diff --git a/packages/flutter_tools/test/integration.shard/command_output_test.dart b/packages/flutter_tools/test/integration.shard/command_output_test.dart index 7b72ef67643b..60c34113dec4 100644 --- a/packages/flutter_tools/test/integration.shard/command_output_test.dart +++ b/packages/flutter_tools/test/integration.shard/command_output_test.dart @@ -71,11 +71,12 @@ void main() { expect(result.stdout, contains('Shutdown hooks complete')); }); - testWithoutContext('flutter config contains all features', () async { + testWithoutContext('flutter config --list contains all features', () async { final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter'); final ProcessResult result = await processManager.run([ flutterBin, 'config', + '--list' ]); // contains all of the experiments in features.dart