Skip to content

Commit 679b8c2

Browse files
authored
Fix Android Studio detection and plugins on Linux (flutter#75612)
1 parent dff42b8 commit 679b8c2

2 files changed

Lines changed: 163 additions & 13 deletions

File tree

‎packages/flutter_tools/lib/src/android/android_studio.dart‎

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@ AndroidStudio get androidStudio => context.get<AndroidStudio>();
1919

2020
// Linux/Windows:
2121
// $HOME/.AndroidStudioX.Y/system/.home
22+
// $HOME/.cache/Google/AndroidStudioX.Y/.home
2223

2324
// macOS:
2425
// /Applications/Android Studio.app/Contents/
2526
// $HOME/Applications/Android Studio.app/Contents/
2627

28+
// Match Android Studio >= 4.1 base folder (AndroidStudio*.*)
29+
// and < 4.1 (.AndroidStudio*.*)
2730
final RegExp _dotHomeStudioVersionMatcher =
28-
RegExp(r'^\.(AndroidStudio[^\d]*)([\d.]+)');
31+
RegExp(r'^\.?(AndroidStudio[^\d]*)([\d.]+)');
2932

3033
String get javaPath => androidStudio?.javaPath;
3134

@@ -104,14 +107,30 @@ class AndroidStudio implements Comparable<AndroidStudio> {
104107
if (studioAppName == null || version == null) {
105108
return null;
106109
}
110+
111+
final int major = version?.major;
112+
final int minor = version?.minor;
113+
114+
// The install path is written in a .home text file,
115+
// it location is in <base dir>/.home for Android Studio >= 4.1
116+
// and <base dir>/system/.home for Android Studio < 4.1
117+
String dotHomeFilePath;
118+
119+
if (major != null && major >= 4 && minor != null && minor >= 1) {
120+
dotHomeFilePath = globals.fs.path.join(homeDotDir.path, '.home');
121+
} else {
122+
dotHomeFilePath =
123+
globals.fs.path.join(homeDotDir.path, 'system', '.home');
124+
}
125+
107126
String installPath;
127+
108128
try {
109-
installPath = globals.fs
110-
.file(globals.fs.path.join(homeDotDir.path, 'system', '.home'))
111-
.readAsStringSync();
129+
installPath = globals.fs.file(dotHomeFilePath).readAsStringSync();
112130
} on Exception {
113131
// ignored, installPath will be null, which is handled below
114132
}
133+
115134
if (installPath != null && globals.fs.isDirectorySync(installPath)) {
116135
return AndroidStudio(
117136
installPath,
@@ -161,6 +180,24 @@ class AndroidStudio implements Comparable<AndroidStudio> {
161180
);
162181
}
163182
} else {
183+
// JetBrains Toolbox write plugins here
184+
final String toolboxPluginsPath = '$directory.plugins';
185+
186+
if (globals.fs.directory(toolboxPluginsPath).existsSync()) {
187+
return toolboxPluginsPath;
188+
}
189+
190+
if (major != null && major >= 4 && minor != null && minor >= 1 &&
191+
globals.platform.isLinux) {
192+
return globals.fs.path.join(
193+
globals.fsUtils.homeDirPath,
194+
'.local',
195+
'share',
196+
'Google',
197+
'$studioAppName$major.$minor',
198+
);
199+
}
200+
164201
return globals.fs.path.join(
165202
globals.fsUtils.homeDirPath,
166203
'.$studioAppName$major.$minor',
@@ -271,22 +308,43 @@ class AndroidStudio implements Comparable<AndroidStudio> {
271308
});
272309
}
273310

274-
// Read all $HOME/.AndroidStudio*/system/.home files. There may be several
275-
// pointing to the same installation, so we grab only the latest one.
276-
if (globals.fsUtils.homeDirPath != null &&
277-
globals.fs.directory(globals.fsUtils.homeDirPath).existsSync()) {
278-
final Directory homeDir = globals.fs.directory(globals.fsUtils.homeDirPath);
279-
for (final Directory entity in homeDir.listSync(followLinks: false).whereType<Directory>()) {
280-
if (!entity.basename.startsWith('.AndroidStudio')) {
281-
continue;
282-
}
311+
// Read all $HOME/.AndroidStudio*/system/.home
312+
// or $HOME/.cache/Google/AndroidStudio*/.home files.
313+
// There may be several pointing to the same installation,
314+
// so we grab only the latest one.
315+
final String homeDirPath = globals.fsUtils.homeDirPath;
316+
317+
if (homeDirPath != null && globals.fs.directory(homeDirPath).existsSync()) {
318+
final Directory homeDir = globals.fs.directory(homeDirPath);
319+
320+
final List<Directory> directoriesToSearch = <Directory>[homeDir];
321+
322+
// >=4.1 has new install location at $HOME/.cache/Google
323+
final String cacheDirPath =
324+
globals.fs.path.join(homeDirPath, '.cache', 'Google');
325+
326+
if (globals.fs.isDirectorySync(cacheDirPath)) {
327+
directoriesToSearch.add(globals.fs.directory(cacheDirPath));
328+
}
329+
330+
final List<Directory> entities = <Directory>[];
331+
332+
for (final Directory baseDir in directoriesToSearch) {
333+
final Iterable<Directory> directories =
334+
baseDir.listSync(followLinks: false).whereType<Directory>();
335+
entities.addAll(directories.where((Directory directory) =>
336+
_dotHomeStudioVersionMatcher.hasMatch(directory.basename)));
337+
}
338+
339+
for (final Directory entity in entities) {
283340
final AndroidStudio studio = AndroidStudio.fromHomeDot(entity);
284341
if (studio != null && !_hasStudioAt(studio.directory, newerThan: studio.version)) {
285342
studios.removeWhere((AndroidStudio other) => other.directory == studio.directory);
286343
studios.add(studio);
287344
}
288345
}
289346
}
347+
290348
// 4.1 has a different location for AndroidStudio installs on Windows.
291349
if (globals.platform.isWindows) {
292350
final File homeDot = globals.fs.file(globals.fs.path.join(

‎packages/flutter_tools/test/general.shard/android/android_studio_test.dart‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,4 +289,96 @@ void main() {
289289
FileSystem: () => windowsFileSystem,
290290
ProcessManager: () => FakeProcessManager.any(),
291291
});
292+
293+
group('Installation detection on Linux', () {
294+
FileSystemUtils fsUtils;
295+
296+
setUp(() {
297+
fsUtils = FileSystemUtils(
298+
fileSystem: fileSystem,
299+
platform: linuxPlatform,
300+
);
301+
});
302+
303+
testUsingContext('Discover Android Studio <4.1', () {
304+
const String studioHomeFilePath =
305+
'$homeLinux/.AndroidStudio4.0/system/.home';
306+
const String studioInstallPath = '$homeLinux/AndroidStudio';
307+
308+
globals.fs.file(studioHomeFilePath)
309+
..createSync(recursive: true)
310+
..writeAsStringSync(studioInstallPath);
311+
312+
globals.fs.directory(studioInstallPath).createSync();
313+
314+
final AndroidStudio studio = AndroidStudio.allInstalled().single;
315+
316+
expect(studio.version, Version(4, 0, 0));
317+
expect(studio.studioAppName, 'AndroidStudio');
318+
expect(
319+
studio.pluginsPath,
320+
'/home/me/.AndroidStudio4.0/config/plugins',
321+
);
322+
}, overrides: <Type, Generator>{
323+
FileSystem: () => fileSystem,
324+
FileSystemUtils: () => fsUtils,
325+
Platform: () => linuxPlatform,
326+
ProcessManager: () => FakeProcessManager.any(),
327+
});
328+
329+
testUsingContext('Discover Android Studio >=4.1', () {
330+
const String studioHomeFilePath =
331+
'$homeLinux/.cache/Google/AndroidStudio4.1/.home';
332+
const String studioInstallPath = '$homeLinux/AndroidStudio';
333+
334+
globals.fs.file(studioHomeFilePath)
335+
..createSync(recursive: true)
336+
..writeAsStringSync(studioInstallPath);
337+
338+
globals.fs.directory(studioInstallPath).createSync();
339+
340+
final AndroidStudio studio = AndroidStudio.allInstalled().single;
341+
342+
expect(studio.version, Version(4, 1, 0));
343+
expect(studio.studioAppName, 'AndroidStudio');
344+
expect(
345+
studio.pluginsPath,
346+
'/home/me/.local/share/Google/AndroidStudio4.1',
347+
);
348+
}, overrides: <Type, Generator>{
349+
FileSystem: () => fileSystem,
350+
FileSystemUtils: () => fsUtils,
351+
Platform: () => linuxPlatform,
352+
ProcessManager: () => FakeProcessManager.any(),
353+
});
354+
355+
testUsingContext('Discover when installed with Toolbox', () {
356+
const String studioHomeFilePath =
357+
'$homeLinux/.cache/Google/AndroidStudio4.1/.home';
358+
const String studioInstallPath =
359+
'$homeLinux/.local/share/JetBrains/Toolbox/apps/AndroidStudio/ch-0/201.7042882';
360+
const String pluginsInstallPath = '$studioInstallPath.plugins';
361+
362+
globals.fs.file(studioHomeFilePath)
363+
..createSync(recursive: true)
364+
..writeAsStringSync(studioInstallPath);
365+
366+
globals.fs.directory(studioInstallPath).createSync(recursive: true);
367+
globals.fs.directory(pluginsInstallPath).createSync();
368+
369+
final AndroidStudio studio = AndroidStudio.allInstalled().single;
370+
371+
expect(studio.version, Version(4, 1, 0));
372+
expect(studio.studioAppName, 'AndroidStudio');
373+
expect(
374+
studio.pluginsPath,
375+
pluginsInstallPath,
376+
);
377+
}, overrides: <Type, Generator>{
378+
FileSystem: () => fileSystem,
379+
FileSystemUtils: () => fsUtils,
380+
Platform: () => linuxPlatform,
381+
ProcessManager: () => FakeProcessManager.any(),
382+
});
383+
});
292384
}

0 commit comments

Comments
 (0)