fix(android): auto-detect the Android SDK when fresh worktrees lack local.properties (#101273)

This commit is contained in:
Peter Steinberger 2026-07-07 10:22:07 +01:00 committed by GitHub
parent 6e7ef631d2
commit bc5a82b26d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 84 additions and 4 deletions

View file

@ -1,5 +1,7 @@
#!/usr/bin/env node
import { spawn } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
@ -39,11 +41,35 @@ export function linuxArmAndroidGradleSkipMessage(platform = process.platform, ar
);
}
export function run(command, args, cwd) {
// Fresh git worktrees do not carry the gitignored apps/android/local.properties,
// so AGP tasks fail with "SDK location not found" even when an SDK is installed.
// Fall back to the Android Studio default install path when nothing names one.
export function resolveAndroidSdkEnv(options = {}) {
const env = options.env ?? process.env;
if (env.ANDROID_HOME || env.ANDROID_SDK_ROOT) {
return env;
}
const existsSync = options.existsSync ?? fs.existsSync;
if (existsSync(path.join(androidDir, "local.properties"))) {
return env;
}
const homeDir = options.homeDir ?? os.homedir();
const platform = options.platform ?? process.platform;
const defaultSdkDir =
platform === "darwin"
? path.join(homeDir, "Library", "Android", "sdk")
: path.join(homeDir, "Android", "Sdk");
if (!existsSync(defaultSdkDir)) {
return env;
}
return { ...env, ANDROID_HOME: defaultSdkDir };
}
export function run(command, args, cwd, env = process.env) {
return new Promise((resolve) => {
const child = spawn(command, args, {
cwd,
env: process.env,
env,
stdio: "inherit",
});
child.on("close", (status, signal) => {
@ -78,12 +104,13 @@ export async function main(argv = process.argv.slice(2)) {
return 0;
}
const gradleStatus = await run("./gradlew", gradleArgs, androidDir);
const env = resolveAndroidSdkEnv();
const gradleStatus = await run("./gradlew", gradleArgs, androidDir, env);
if (gradleStatus !== 0 || postArgs.length === 0) {
return gradleStatus;
}
return await run(postArgs[0], postArgs.slice(1), repoRoot);
return await run(postArgs[0], postArgs.slice(1), repoRoot, env);
}
if (isMain) {

View file

@ -1,6 +1,8 @@
import path from "node:path";
import { describe, expect, it } from "vitest";
import {
linuxArmAndroidGradleSkipMessage,
resolveAndroidSdkEnv,
shouldSkipLinuxArmAndroidGradle,
splitAndroidGradleArgs,
} from "../../scripts/run-android-gradle.mjs";
@ -37,4 +39,55 @@ describe("run-android-gradle", () => {
"OPENCLAW_ANDROID_GRADLE_ALLOW_LINUX_ARM=1",
);
});
describe("resolveAndroidSdkEnv", () => {
const macSdk = path.join("/Users/dev", "Library", "Android", "sdk");
const linuxSdk = path.join("/home/dev", "Android", "Sdk");
it("keeps env untouched when ANDROID_HOME or ANDROID_SDK_ROOT is set", () => {
const env = { ANDROID_HOME: "/opt/sdk" };
expect(resolveAndroidSdkEnv({ env, existsSync: () => true })).toBe(env);
const rootEnv = { ANDROID_SDK_ROOT: "/opt/sdk" };
expect(resolveAndroidSdkEnv({ env: rootEnv, existsSync: () => true })).toBe(rootEnv);
});
it("keeps env untouched when local.properties exists", () => {
const env = {};
const result = resolveAndroidSdkEnv({
env,
existsSync: (p: string) => p.endsWith("local.properties"),
homeDir: "/Users/dev",
platform: "darwin",
});
expect(result).toBe(env);
});
it("falls back to the Android Studio default SDK path per platform", () => {
const darwin = resolveAndroidSdkEnv({
env: {},
existsSync: (p: string) => p === macSdk,
homeDir: "/Users/dev",
platform: "darwin",
});
expect(darwin.ANDROID_HOME).toBe(macSdk);
const linux = resolveAndroidSdkEnv({
env: {},
existsSync: (p: string) => p === linuxSdk,
homeDir: "/home/dev",
platform: "linux",
});
expect(linux.ANDROID_HOME).toBe(linuxSdk);
});
it("keeps env untouched when no default SDK install exists", () => {
const env = {};
const result = resolveAndroidSdkEnv({
env,
existsSync: () => false,
homeDir: "/Users/dev",
platform: "darwin",
});
expect(result).toBe(env);
});
});
});