diff --git a/packages/flutter_tools/lib/src/android/android_studio.dart b/packages/flutter_tools/lib/src/android/android_studio.dart index f83fdd2463..72d987583b 100644 --- a/packages/flutter_tools/lib/src/android/android_studio.dart +++ b/packages/flutter_tools/lib/src/android/android_studio.dart @@ -236,16 +236,7 @@ class AndroidStudio { /// Android Studio found at that location is always returned, even if it is /// invalid. static AndroidStudio? latestValid() { - final String? configuredStudioPath = globals.config.getValue('android-studio-dir') as String?; - if (configuredStudioPath != null && !globals.fs.directory(configuredStudioPath).existsSync()) { - throwToolExit(''' -Could not find the Android Studio installation at the manually configured path "$configuredStudioPath". -Please verify that the path is correct and update it by running this command: flutter config --android-studio-dir '' - -To have flutter search for Android Studio installations automatically, remove -the configured path by running this command: flutter config --android-studio-dir '' -'''); - } + final Directory? configuredStudioDir = _configuredDir(); // Find all available Studio installations. final List studios = allInstalled(); @@ -255,8 +246,8 @@ the configured path by running this command: flutter config --android-studio-dir final AndroidStudio? manuallyConfigured = studios .where((AndroidStudio studio) => studio.configuredPath != null && - configuredStudioPath != null && - _pathsAreEqual(studio.configuredPath!, configuredStudioPath)) + configuredStudioDir != null && + _pathsAreEqual(studio.configuredPath!, configuredStudioDir.path)) .firstOrNull; if (manuallyConfigured != null) { @@ -323,16 +314,14 @@ the configured path by running this command: flutter config --android-studio-dir )); } - final String? configuredStudioDir = globals.config.getValue('android-studio-dir') as String?; - FileSystemEntity? configuredStudioDirAsEntity; + Directory? configuredStudioDir = _configuredDir(); if (configuredStudioDir != null) { - configuredStudioDirAsEntity = globals.fs.directory(configuredStudioDir); - if (configuredStudioDirAsEntity.basename == 'Contents') { - configuredStudioDirAsEntity = configuredStudioDirAsEntity.parent; + if (configuredStudioDir.basename == 'Contents') { + configuredStudioDir = configuredStudioDir.parent; } if (!candidatePaths - .any((FileSystemEntity e) => _pathsAreEqual(e.path, configuredStudioDirAsEntity!.path))) { - candidatePaths.add(configuredStudioDirAsEntity); + .any((FileSystemEntity e) => _pathsAreEqual(e.path, configuredStudioDir!.path))) { + candidatePaths.add(configuredStudioDir); } } @@ -357,13 +346,13 @@ the configured path by running this command: flutter config --android-studio-dir return candidatePaths .map((FileSystemEntity e) { - if (configuredStudioDirAsEntity == null) { + if (configuredStudioDir == null) { return AndroidStudio.fromMacOSBundle(e.path); } return AndroidStudio.fromMacOSBundle( e.path, - configuredPath: _pathsAreEqual(configuredStudioDirAsEntity.path, e.path) ? configuredStudioDir : null, + configuredPath: _pathsAreEqual(configuredStudioDir.path, e.path) ? configuredStudioDir.path : null, ); }) .whereType() @@ -493,6 +482,38 @@ the configured path by running this command: flutter config --android-studio-dir return studios; } + /// Gets the Android Studio install directory set by the user, if it is configured. + /// + /// The returned [Directory], if not null, is guaranteed to have existed during + /// this function's execution. + static Directory? _configuredDir() { + final String? configuredPath = globals.config.getValue('android-studio-dir') as String?; + if (configuredPath == null) { + return null; + } + final Directory result = globals.fs.directory(configuredPath); + + bool? configuredStudioPathExists; + String? exceptionMessage; + try { + configuredStudioPathExists = result.existsSync(); + } on FileSystemException catch (e) { + exceptionMessage = e.toString(); + } + + if (configuredStudioPathExists == false || exceptionMessage != null) { + throwToolExit(''' +Could not find the Android Studio installation at the manually configured path "$configuredPath". +${exceptionMessage == null ? '' : 'Encountered exception: $exceptionMessage\n\n'} +Please verify that the path is correct and update it by running this command: flutter config --android-studio-dir '' +To have flutter search for Android Studio installations automatically, remove +the configured path by running this command: flutter config --android-studio-dir +'''); + } + + return result; + } + static String? extractStudioPlistValueWithMatcher(String plistValue, RegExp keyMatcher) { return keyMatcher.stringMatch(plistValue)?.split('=').last.trim().replaceAll('"', ''); } diff --git a/packages/flutter_tools/test/general.shard/android/android_studio_test.dart b/packages/flutter_tools/test/general.shard/android/android_studio_test.dart index ee018bcc36..cc8d8c2f36 100644 --- a/packages/flutter_tools/test/general.shard/android/android_studio_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_studio_test.dart @@ -11,6 +11,7 @@ import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/ios/plist_parser.dart'; +import 'package:path/path.dart' show Context; // flutter_ignore: package_path_import -- We only use Context as an interface. import 'package:test/fake.dart'; import '../../src/common.dart'; @@ -1287,6 +1288,20 @@ void main() { Platform: () => platform, ProcessManager: () => FakeProcessManager.any(), }); + + testUsingContext('handles file system exception when checking for explicitly configured Android Studio install', () { + const String androidStudioDir = '/Users/Dash/Desktop/android-studio'; + config.setValue('android-studio-dir', androidStudioDir); + + expect(() => AndroidStudio.latestValid(), + throwsToolExit(message: RegExp(r'[.\s\S]*Could not find[.\s\S]*FileSystemException[.\s\S]*'))); + }, overrides: { + Config: () => config, + Platform: () => platform, + FileSystem: () => _FakeFileSystem(), + FileSystemUtils: () => _FakeFsUtils(), + ProcessManager: () => FakeProcessManager.any(), + }); }); } @@ -1298,3 +1313,33 @@ class FakePlistUtils extends Fake implements PlistParser { return fileContents[plistFilePath]!; } } + +class _FakeFileSystem extends Fake implements FileSystem { + @override + Directory directory(dynamic path) { + return _NonExistentDirectory(); + } + + @override + Context get path { + return MemoryFileSystem.test().path; + } +} + +class _NonExistentDirectory extends Fake implements Directory { + @override + bool existsSync() { + throw const FileSystemException('OS Error: Filename, directory name, or volume label syntax is incorrect.'); + } + + @override + String get path => ''; + + @override + Directory get parent => _NonExistentDirectory(); +} + +class _FakeFsUtils extends Fake implements FileSystemUtils { + @override + String get homeDirPath => '/home/'; +}