diff --git a/apps/desktop/src/shell/DesktopShellEnvironment.test.ts b/apps/desktop/src/shell/DesktopShellEnvironment.test.ts index aee5a0bc81b54..d0dd17fcdb3b3 100644 --- a/apps/desktop/src/shell/DesktopShellEnvironment.test.ts +++ b/apps/desktop/src/shell/DesktopShellEnvironment.test.ts @@ -220,6 +220,42 @@ describe("DesktopShellEnvironment", () => { }), ); + it.effect.each([ + { + name: "hydrates both Android SDK names when neither is inherited", + inherited: {}, + expected: { + ANDROID_HOME: "/Volumes/Work/android-sdk", + ANDROID_SDK_ROOT: "/Volumes/Work/android-sdk", + }, + }, + { + // The device host reads ANDROID_HOME before ANDROID_SDK_ROOT, so a shell + // ANDROID_HOME next to an inherited ANDROID_SDK_ROOT would change the SDK. + name: "keeps an inherited ANDROID_SDK_ROOT selected over a login-shell ANDROID_HOME", + inherited: { ANDROID_SDK_ROOT: "/inherited/android-sdk" }, + expected: { ANDROID_HOME: undefined, ANDROID_SDK_ROOT: "/inherited/android-sdk" }, + }, + ])("$name", ({ inherited, expected }) => + Effect.gen(function* () { + const env: NodeJS.ProcessEnv = { SHELL: "/bin/zsh", PATH: "/usr/bin", ...inherited }; + + yield* runShellEnvironment({ + env, + platform: "darwin", + handler: () => + envOutput({ + PATH: "/usr/bin", + ANDROID_HOME: "/Volumes/Work/android-sdk", + ANDROID_SDK_ROOT: "/Volumes/Work/android-sdk", + }), + }); + + assert.equal(env.ANDROID_HOME, expected.ANDROID_HOME); + assert.equal(env.ANDROID_SDK_ROOT, expected.ANDROID_SDK_ROOT); + }), + ); + it.effect("preserves inherited POSIX values when present", () => Effect.gen(function* () { const env: NodeJS.ProcessEnv = { diff --git a/apps/desktop/src/shell/DesktopShellEnvironment.ts b/apps/desktop/src/shell/DesktopShellEnvironment.ts index 8fa8a60b2e62b..095387f413d2e 100644 --- a/apps/desktop/src/shell/DesktopShellEnvironment.ts +++ b/apps/desktop/src/shell/DesktopShellEnvironment.ts @@ -71,6 +71,8 @@ export class DesktopShellEnvironment extends Context.Service< const LOGIN_SHELL_ENV_NAMES = [ "PATH", + "ANDROID_HOME", + "ANDROID_SDK_ROOT", "DBUS_SESSION_BUS_ADDRESS", "DISPLAY", "LANG", @@ -95,6 +97,7 @@ const LOGIN_SHELL_ENV_NAMES = [ ] as const; const WINDOWS_PROFILE_ENV_NAMES = ["PATH", "FNM_DIR", "FNM_MULTISHELL_PATH"] as const; const LOCALE_ENV_NAMES = ["LANG", "LC_ALL", "LC_CTYPE"] as const; +const ANDROID_SDK_ENV_NAMES = ["ANDROID_HOME", "ANDROID_SDK_ROOT"] as const; const FALLBACK_LC_CTYPE = "en_US.UTF-8"; const WINDOWS_SHELL_CANDIDATES = ["pwsh.exe", "powershell.exe"] as const; const LOGIN_SHELL_TIMEOUT = Duration.seconds(5); @@ -472,6 +475,17 @@ const installPosixEnvironment = Effect.fn("desktop.shellEnvironment.installPosix } } + // The device host picks the Android SDK from ANDROID_HOME before ANDROID_SDK_ROOT, + // so a profile-only ANDROID_HOME must not shadow an inherited ANDROID_SDK_ROOT. + if (ANDROID_SDK_ENV_NAMES.every((name) => Option.isNone(trimNonEmpty(config.env[name])))) { + for (const name of ANDROID_SDK_ENV_NAMES) { + const value = trimNonEmpty(shellEnvironment[name]); + if (Option.isSome(value)) { + config.env[name] = value.value; + } + } + } + // Locale variables form one precedence group: LC_ALL can override an inherited // LANG or LC_CTYPE, so only hydrate the group when the process has none of them. if (