From 60d7bb2588c7bfa0cfca9bb5371e43fae99ef25e Mon Sep 17 00:00:00 2001 From: Jesse Date: Sat, 3 Oct 2020 17:40:09 +0200 Subject: [PATCH] Use XDG_CONFIG_HOME dir by default for config files (#66645) This PR changes the Config class in flutter_tools to use the XDG Base directory specification instead of putting files directly in the user's home directory. If those files are already present in the home directory, they are used instead. --- .../flutter_tools/lib/src/base/config.dart | 56 +++++++++++++++---- .../lib/src/persistent_tool_state.dart | 2 +- .../test/general.shard/config_test.dart | 26 ++++++++- 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/packages/flutter_tools/lib/src/base/config.dart b/packages/flutter_tools/lib/src/base/config.dart index 529bb76c1a8..c14b2d2bf55 100644 --- a/packages/flutter_tools/lib/src/base/config.dart +++ b/packages/flutter_tools/lib/src/base/config.dart @@ -14,18 +14,23 @@ import 'utils.dart'; /// A class to abstract configuration files. class Config { /// Constructs a new [Config] object from a file called [name] in the - /// current user's home directory as determined by the [Platform] and - /// [FileSystem]. + /// current user's configuration directory as determined by the [Platform] + /// and [FileSystem]. + /// + /// The configuration directory defaults to $XDG_CONFIG_HOME on Linux and + /// macOS, but falls back to the home directory if a file named + /// `.flutter_$name` already exists there. On other platforms the + /// configuration file will always be a file named `.flutter_$name` in the + /// home directory. factory Config( String name, { @required FileSystem fileSystem, @required Logger logger, @required Platform platform, }) { - final File file = fileSystem.file(fileSystem.path.join( - _userHomePath(platform), - name, - )); + final String filePath = _configPath(platform, fileSystem, name); + final File file = fileSystem.file(filePath); + file.parent.createSync(recursive: true); return Config.createForTesting(file, logger); } @@ -35,7 +40,7 @@ class Config { String name, { @required Directory directory, @required Logger logger, - }) => Config.createForTesting(directory.childFile(name), logger); + }) => Config.createForTesting(directory.childFile('.${kConfigDir}_$name'), logger); /// Test only access to the Config constructor. @visibleForTesting @@ -65,8 +70,24 @@ class Config { } } + /// The default directory name for Flutter's configs. + + /// Configs will be written to the user's config path. If there is already a + /// file with the name `.${kConfigDir}_$name` in the user's home path, that + /// file will be used instead. + static const String kConfigDir = 'flutter'; + + /// Environment variable specified in the XDG Base Directory + /// [specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) + /// to specify the user's configuration directory. + static const String kXdgConfigHome = 'XDG_CONFIG_HOME'; + + /// Fallback directory in the user's home directory if `XDG_CONFIG_HOME` is + /// not defined. + static const String kXdgConfigFalback = '.config'; + /// The default name for the Flutter config file. - static const String kFlutterSettings = '.flutter_settings'; + static const String kFlutterSettings = 'settings'; final Logger _logger; @@ -104,9 +125,22 @@ class Config { // // Note that this is different from FileSystemUtils.homeDirPath. static String _userHomePath(Platform platform) { - final String envKey = platform.operatingSystem == 'windows' - ? 'APPDATA' - : 'HOME'; + final String envKey = platform.isWindows ? 'APPDATA' : 'HOME'; return platform.environment[envKey] ?? '.'; } + + static String _configPath( + Platform platform, FileSystem fileSystem, String name) { + final String homeDirFile = + fileSystem.path.join(_userHomePath(platform), '.${kConfigDir}_$name'); + if (platform.isLinux || platform.isMacOS) { + if (fileSystem.isFileSync(homeDirFile)) { + return homeDirFile; + } + final String configDir = platform.environment[kXdgConfigHome] ?? + fileSystem.path.join(_userHomePath(platform), '.config', kConfigDir); + return fileSystem.path.join(configDir, name); + } + return homeDirFile; + } } diff --git a/packages/flutter_tools/lib/src/persistent_tool_state.dart b/packages/flutter_tools/lib/src/persistent_tool_state.dart index bb3872b0f35..d5963906c32 100644 --- a/packages/flutter_tools/lib/src/persistent_tool_state.dart +++ b/packages/flutter_tools/lib/src/persistent_tool_state.dart @@ -73,7 +73,7 @@ class _DefaultPersistentToolState implements PersistentToolState { logger: logger, ); - static const String _kFileName = '.flutter_tool_state'; + static const String _kFileName = 'tool_state'; static const String _kRedisplayWelcomeMessage = 'redisplay-welcome-message'; static const Map _lastActiveVersionKeys = { Channel.master: 'last-active-master-version', diff --git a/packages/flutter_tools/test/general.shard/config_test.dart b/packages/flutter_tools/test/general.shard/config_test.dart index 295450d8771..425c19f5e9e 100644 --- a/packages/flutter_tools/test/general.shard/config_test.dart +++ b/packages/flutter_tools/test/general.shard/config_test.dart @@ -65,7 +65,7 @@ void main() { testWithoutContext('Config parse error', () { final BufferLogger bufferLogger = BufferLogger.test(); - final File file = memoryFileSystem.file('example') + final File file = memoryFileSystem.file('.flutter_example') ..writeAsStringSync('{"hello":"bar'); config = Config( 'example', @@ -106,6 +106,30 @@ void main() { // Also contains original error message: expect(bufferLogger.errorText, contains('The flutter tool cannot access the file or directory')); }); + + testWithoutContext('Config in home dir is used if it exists', () { + memoryFileSystem.file('.flutter_example').writeAsStringSync('{"hello":"bar"}'); + config = Config( + 'example', + fileSystem: memoryFileSystem, + logger: BufferLogger.test(), + platform: fakePlatform, + ); + expect(config.getValue('hello'), 'bar'); + expect(memoryFileSystem.file('.config/flutter/example').existsSync(), false); + }); + + testWithoutContext('Config is created in config dir if it does not already exist in home dir', () { + config = Config( + 'example', + fileSystem: memoryFileSystem, + logger: BufferLogger.test(), + platform: fakePlatform, + ); + + config.setValue('foo', 'bar'); + expect(memoryFileSystem.file('.config/flutter/example').existsSync(), true); + }); } class FakeFile extends Fake implements File {