Make skip android emulator create much more robust#194
Make skip android emulator create much more robust#194dfabulich wants to merge 1 commit intoskiptools:mainfrom
skip android emulator create much more robust#194Conversation
1. Ensure we always pass `sdkmanager` and `avdmanager` a valid `JAVA_HOME`, because the stock cmdline-tools don't include one, and fails outright if the `/usr/bin/java` isn't set up.
Our Homebrew cask installs the `openjdk` formula, which does _not_ symlink into `/Library/Java/JavaVirtualMachines`. So that'll work, but only if we pass `JAVA_HOME` every time we want to use `sdkmanager` or `avdmanager`.
2. Ensure that we have a valid `ANDROID_HOME` (from the environment, or by guessing the path to Android Studio's SDK)
3. Check to see if cmdline-tools are installed in `ANDROID_HOME`; Android Studio doesn't install them by default
4. If cmdline-tools are not installed, we can "bootstrap" them by running _any_ `sdkmanager` we can find.
Our Homebrew cask installs `android-commandlinetools`, which installs its own Android SDK (probably _not_ the one the user wants to use). We can use that (passing in `JAVA_HOME`), to run `sdkmanager --sdk_root=$ANDROID_HOME "cmdline-tools;latest"` to install the command line tools into Android Studio's SDK; then we can use the cmdline-tools in `ANDROID_HOME`.
5. At that point, we have a known-good `sdkmanager` in ANDROID_HOME, which we can use to install platform-tools, the target Android platform, and the emulator.
6. We ask `emulator` for the list of AVDs, skipping creating the AVD if it already exists. (Despite the claim of `--force`, in my experiments, I found that it wasn't able to overwrite an existing AVD, e.g. to change the system_image.)
7. If there's no existing emulator with that name, we can finally create the emulator with a known-good `avdmanager` in `ANDROID_HOME`'s `cmdline-tools`.
Fixes skiptools#187
marcprux
left a comment
There was a problem hiding this comment.
This is looking good overall! It mostly just needs to cleanup and de-duplication of logic.
Have you tested locally in a clean UTM image to see if this creates and launches the emulator ?
|
|
||
| func sdkmanagerInstall(_ package: String) async throws { | ||
| try await run(with: out, "Install \(package)", ["sdkmanager", "--verbose", "--install", package]) | ||
| try await run(with: out, "Install \(package)", ["\(androidHome)/cmdline-tools/latest/bin/sdkmanager", "--verbose", "--install", package], additionalEnvironment: ["JAVA_HOME": javaHome]) |
There was a problem hiding this comment.
I think we should use launchTool here and centralize the tool lookup logic in toolPath(for:)
| /// - Returns: The JAVA_HOME path for Homebrew openjdk | ||
| /// - Throws: JavaNotFoundError if Homebrew openjdk cannot be found | ||
| @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) | ||
| func validateJava( |
There was a problem hiding this comment.
I think we should leave it to skip doctor's validation of the Java version, and potentially improve on that rather than duplicating the check here.
| // At this point, we need to "bootstrap" the cmdline-tools | ||
| // Our homebrew cask installs `android-commandlinetools` which contains a copy of sdkmanager, but that's probably not the user's ANDROID_HOME | ||
| // Nevertheless, if we can find any copy of sdkmanager, we can use it to install the cmdline-tools in the user's ANDROID_HOME | ||
| let homebrewRoot = environment["HOMEBREW_PREFIX"].flatMap { $0.isEmpty ? nil : $0 } |
There was a problem hiding this comment.
We already have logic for checking for the Homebrew location in ProcessInfo.homebrewRoot (which also handles Linux)
There was a problem hiding this comment.
For ProcessInfo.homebrewRoot and ProcessInfo.androidHome, I had to reimplement them so I could pass in a mock environment. I'll convert them from computed properties to functions with optional environment parameters so I can mock them out for testing.
| //if let deviceProfile { | ||
| createCommand += ["--device", deviceProfile] | ||
| //} | ||
| var createCommand = ["\(androidHome)/cmdline-tools/latest/bin/avdmanager", "create", "avd", "--force", "-n", emulatorName, "--package", emulatorSpec] |
There was a problem hiding this comment.
Use launchTool for this and handle avdmanager in toolPath(for:)
| // Use the passed HOME environment variable (for testing), or fall back to actual home directory | ||
| let home = environment["HOME"].flatMap { $0.isEmpty ? nil : $0 } ?? FileManager.default.homeDirectoryForCurrentUser.path | ||
|
|
||
| #if os(macOS) |
There was a problem hiding this comment.
This is duplicating logic already present in ProcessInfo. androidHome
I can't launch an emulator in UTM at all 😭 But I did verify that it creates an emulator. |
|
If you want to rebase this pull, you can get the enhancements to the CI ( It isn't quite the same as a clean UTM-installed image, but once we get it working, we can try running the commands after manually purging the GH runner's pre-installed tools, e.g. with: |
Fixes #187
Ensure we always pass
sdkmanagerandavdmanagera validJAVA_HOME, because the stock cmdline-tools don't include one, and fails outright if the/usr/bin/javaisn't set up. We're working aroundsdkmanagerdoesn't work after installing Skip homebrew-skip#1Our Homebrew cask installs the
openjdkformula, which does not symlink into/Library/Java/JavaVirtualMachines. So that'll work, but only if we passJAVA_HOMEevery time we want to usesdkmanageroravdmanager.Ensure that we have a valid
ANDROID_HOME(from the environment, or by guessing the path to Android Studio's SDK)Check to see if cmdline-tools are installed in
ANDROID_HOME; Android Studio doesn't install them by defaultIf cmdline-tools are not installed, we can "bootstrap" them by running any
sdkmanagerwe can find.Our Homebrew cask installs
android-commandlinetools, which installs its own Android SDK (probably not the one the user wants to use). We can use that (passing inJAVA_HOME), to runsdkmanager --sdk_root=$ANDROID_HOME "cmdline-tools;latest"to install the command line tools into Android Studio's SDK; then we can use the cmdline-tools inANDROID_HOME.At that point, we have a known-good
sdkmanagerin ANDROID_HOME, which we can use to install platform-tools, the target Android platform, and the emulator.We ask
emulatorfor the list of AVDs, skipping creating the AVD if it already exists. (Despite the claim of--force, in my experiments, I found that it wasn't able to overwrite an existing AVD, e.g. to change the system_image.)If there's no existing emulator with that name, we can finally create the emulator with a known-good
avdmanagerinANDROID_HOME'scmdline-tools.Skip Pull Request Checklist:
swift testI had Cursor generate the scaffolding for the tests and the implementation, but then I pretty much rewrote the whole thing by hand. 🤪 Having all of the tests gives me a lot of confidence that this code is robust.