Unverified Commit ba4177f6 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Find Android Studio installations with Spotlight query on macOS (#80475)

parent 0021a08c
......@@ -7,6 +7,7 @@ import '../base/io.dart';
import '../base/process.dart';
import '../base/utils.dart';
import '../base/version.dart';
import '../convert.dart';
import '../globals_null_migrated.dart' as globals;
import '../ios/plist_parser.dart';
......@@ -44,7 +45,7 @@ class AndroidStudio implements Comparable<AndroidStudio> {
Map<String, dynamic> plistValues = globals.plistParser.parseFile(plistFile);
// As AndroidStudio managed by JetBrainsToolbox could have a wrapper pointing to the real Android Studio.
// Check if we've found a JetBrainsToolbox wrapper and deal with it properly.
final String jetBrainsToolboxAppBundlePath = plistValues['JetBrainsToolboxApp'] as String;
final String? jetBrainsToolboxAppBundlePath = plistValues['JetBrainsToolboxApp'] as String?;
if (jetBrainsToolboxAppBundlePath != null) {
studioPath = globals.fs.path.join(jetBrainsToolboxAppBundlePath, 'Contents');
plistFile = globals.fs.path.join(studioPath, 'Info.plist');
......@@ -295,9 +296,28 @@ class AndroidStudio implements Comparable<AndroidStudio> {
}
}
// Query Spotlight for unexpected installation locations.
String spotlightQueryResult = '';
try {
final ProcessResult spotlightResult = globals.processManager.runSync(<String>[
'mdfind',
// com.google.android.studio, com.google.android.studio-EAP
'kMDItemCFBundleIdentifier="com.google.android.studio*"',
]);
spotlightQueryResult = spotlightResult.stdout as String;
} on ProcessException {
// The Spotlight query is a nice-to-have, continue checking known installation locations.
}
for (final String studioPath in LineSplitter.split(spotlightQueryResult)) {
final Directory appBundle = globals.fs.directory(studioPath);
if (!candidatePaths.any((FileSystemEntity e) => e.path == studioPath)) {
candidatePaths.add(appBundle);
}
}
return candidatePaths
.map<AndroidStudio>((FileSystemEntity e) => AndroidStudio.fromMacOSBundle(e.path))
.where((AndroidStudio s) => s != null)
.whereType<AndroidStudio>()
.toList();
}
......
......@@ -143,6 +143,11 @@ void main() {
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(0, 0, 'assembleRelease', '')));
when(mockProcessManager.runSync(<String>['mdfind', 'kMDItemCFBundleIdentifier="com.google.android.studio*"'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenReturn(ProcessResult(0, 0, '', ''));
// Fallback with error.
final Process process = createMockProcess(exitCode: 1);
when(mockProcessManager.start(any,
......
......@@ -15,6 +15,7 @@ import 'package:test/fake.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fake_process_manager.dart';
const String homeLinux = '/home/me';
const String homeMac = '/Users/me';
......@@ -99,6 +100,7 @@ void main() {
FileSystemUtils fsUtils;
Platform platform;
FakePlistUtils plistUtils;
FakeProcessManager processManager;
setUp(() {
plistUtils = FakePlistUtils();
......@@ -107,6 +109,7 @@ void main() {
fileSystem: fileSystem,
platform: platform,
);
processManager = FakeProcessManager.empty();
});
testUsingContext('Can discover Android Studio >=4.1 location on Mac', () {
......@@ -174,6 +177,91 @@ void main() {
PlistParser: () => plistUtils,
});
testUsingContext('Can discover installation from Spotlight query', () {
// One in expected location.
final String studioInApplication = fileSystem.path.join(
'/',
'Application',
'Android Studio.app',
);
final String studioInApplicationPlistFolder = fileSystem.path.join(
studioInApplication,
'Contents',
);
fileSystem.directory(studioInApplicationPlistFolder).createSync(recursive: true);
final String plistFilePath = fileSystem.path.join(studioInApplicationPlistFolder, 'Info.plist');
plistUtils.fileContents[plistFilePath] = macStudioInfoPlist4_1;
// Two in random location only Spotlight knows about.
final String randomLocation1 = fileSystem.path.join(
'/',
'random',
'Android Studio Preview.app',
);
final String randomLocation1PlistFolder = fileSystem.path.join(
randomLocation1,
'Contents',
);
fileSystem.directory(randomLocation1PlistFolder).createSync(recursive: true);
final String randomLocation1PlistPath = fileSystem.path.join(randomLocation1PlistFolder, 'Info.plist');
plistUtils.fileContents[randomLocation1PlistPath] = macStudioInfoPlist4_1;
final String randomLocation2 = fileSystem.path.join(
'/',
'random',
'Android Studio with Blaze.app',
);
final String randomLocation2PlistFolder = fileSystem.path.join(
randomLocation2,
'Contents',
);
fileSystem.directory(randomLocation2PlistFolder).createSync(recursive: true);
final String randomLocation2PlistPath = fileSystem.path.join(randomLocation2PlistFolder, 'Info.plist');
plistUtils.fileContents[randomLocation2PlistPath] = macStudioInfoPlist4_1;
final String javaBin = fileSystem.path.join('jre', 'jdk', 'Contents', 'Home', 'bin', 'java');
// Spotlight finds the one known and two random installations.
processManager.addCommands(<FakeCommand>[
FakeCommand(
command: const <String>[
'mdfind',
'kMDItemCFBundleIdentifier="com.google.android.studio*"',
],
stdout: '$randomLocation1\n$randomLocation2\n$studioInApplication',
),
FakeCommand(
command: <String>[
fileSystem.path.join(randomLocation1, 'Contents', javaBin),
'-version',
],
),
FakeCommand(
command: <String>[
fileSystem.path.join(randomLocation2, 'Contents', javaBin),
'-version',
],
),
FakeCommand(
command: <String>[
fileSystem.path.join(studioInApplicationPlistFolder, javaBin),
'-version',
],
),
]);
// Results are de-duplicated, only 3 installed.
expect(AndroidStudio.allInstalled().length, 3);
expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
FileSystemUtils: () => fsUtils,
ProcessManager: () => processManager,
// Custom home paths are not supported on macOS nor Windows yet,
// so we force the platform to fake Linux here.
Platform: () => platform,
PlistParser: () => plistUtils,
});
testUsingContext('finds latest valid install', () {
final String applicationPlistFolder = globals.fs.path.join(
'/',
......
......@@ -309,19 +309,17 @@ include ':app'
group('Gradle local.properties', () {
Artifacts localEngineArtifacts;
FakePlatform android;
FileSystem fs;
setUp(() {
fs = MemoryFileSystem.test();
localEngineArtifacts = Artifacts.test(localEngine: 'out/android_arm');
android = fakePlatform('android');
});
void testUsingAndroidContext(String description, dynamic Function() testMethod) {
testUsingContext(description, testMethod, overrides: <Type, Generator>{
Artifacts: () => localEngineArtifacts,
Platform: () => android,
Platform: () => FakePlatform(operatingSystem: 'linux'),
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
......@@ -626,12 +624,15 @@ flutter:
FakeAndroidSdk androidSdk;
AndroidGradleBuilder builder;
BufferLogger logger;
Platform platform;
setUp(() {
logger = BufferLogger.test();
fs = MemoryFileSystem.test();
fakeProcessManager = FakeProcessManager.empty();
androidSdk = FakeAndroidSdk();
platform = FakePlatform(operatingSystem: 'linux');
builder = AndroidGradleBuilder(
logger: logger,
processManager: fakeProcessManager,
......@@ -639,7 +640,7 @@ flutter:
artifacts: Artifacts.test(),
usage: TestUsage(),
gradleUtils: FakeGradleUtils(),
platform: FakePlatform(),
platform: platform,
);
});
......@@ -704,8 +705,8 @@ plugin2=${plugin2.path}
'aar_init_script.gradle',
);
fakeProcessManager
..addCommand(FakeCommand(
fakeProcessManager.addCommands(<FakeCommand>[
FakeCommand(
command: <String>[
'gradlew',
'-I=$initScript',
......@@ -721,8 +722,8 @@ plugin2=${plugin2.path}
'assembleAarRelease',
],
workingDirectory: plugin1.childDirectory('android').path,
))
..addCommand(FakeCommand(
),
FakeCommand(
command: <String>[
'gradlew',
'-I=$initScript',
......@@ -738,7 +739,7 @@ plugin2=${plugin2.path}
'assembleAarRelease',
],
workingDirectory: plugin2.childDirectory('android').path,
));
)]);
await builder.buildPluginsAsAar(
FlutterProject.fromDirectoryTest(androidDirectory),
......@@ -755,6 +756,7 @@ plugin2=${plugin2.path}
}, overrides: <Type, Generator>{
AndroidSdk: () => androidSdk,
FileSystem: () => fs,
Platform: () => platform,
ProcessManager: () => fakeProcessManager,
GradleUtils: () => FakeGradleUtils(),
});
......@@ -1008,14 +1010,6 @@ plugin1=${plugin1.path}
}, skip: true); // TODO(jonahwilliams): This is an integration test and should be moved to the integration shard.
}
FakePlatform fakePlatform(String name) {
return FakePlatform(
environment: <String, String>{'HOME': '/path/to/home'},
operatingSystem: name,
stdoutSupportsAnsi: false,
);
}
class FakeGradleUtils extends GradleUtils {
@override
String getExecutable(FlutterProject project) {
......
......@@ -884,6 +884,7 @@ void main() {
}, overrides: <Type, Generator>{
Cache: () => cache,
FileSystem: () => memoryFileSystem,
Platform: () => FakePlatform(operatingSystem: 'linux'),
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: <String>[
'/cache/bin/cache/flutter_gradle_wrapper.rand0/gradlew',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment