Find the SDK without an environment, and say what broke

A service manager hands its process a scrubbed environment -- measured in
the Gentoo guest, an OpenRC user service gets 19 variables and neither
ANDROID_HOME nor ANDROID_NDK_HOME among them -- so the tool lookups have
to stand on their own. Two ways they did not:

  - $ANDROID_HOME was taken as given. This machine exports a system-wide
    /opt/android-sdk whose build-tools/36.0.0 holds nothing but
    package.xml, so the download failed with "failed to spawn
    .../zipalign", naming the tool rather than the root that was wrong.
    Each lookup now takes the first candidate that actually contains what
    it needs, and names every place it looked.

  - The NDK search took the newest child of ~/Android and filtered it
    afterwards, which can only reject that one directory. It worked by
    the luck of `Sdk` sorting before `android-ndk-r27c`; a lowercase
    neighbour would have hidden an NDK that was sitting right there.
    newest_child_where filters before taking the max.

And the failure had nowhere to go: ApiError::Internal answered with an
empty body, so a missing NDK reached the phone as "Server returned HTTP
500" while the message naming the path stayed in a log on a machine the
person holding the phone cannot see. It now answers with the same
{err:#} chain it logs. Every route is behind the bearer token of a
device somebody enrolled themselves, so there is no third party to
withhold it from.

Diagnosis from the tdep-survey session, which measured the OpenRC
environment and the /opt/android-sdk contents.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-01 10:30:02 -04:00
1 parent c35f84a373
commit 0e9c7842f7
3 files changed
+244 -31

No files matched your search

+33 -1
View File
@@ -240,6 +240,16 @@ mutable at runtime from the phone.
somebody asked. Stop and Uninstall do go
through the script, and do strand the phone; the app confirms them
rather than hiding them.
- **A 500 answers with its message.** `ApiError::Internal` used to be a
bare status with an empty body, on the grounds that an internal cause
is not safe to hand back -- but every route here is behind the bearer
token of a device somebody enrolled themselves, so there is no third
party to withhold it from, and what these actually say is which tool on
the build machine could not be found or would not run. Withheld, a
missing NDK reached the phone as "Server returned HTTP 500", with the
explanation in a log on a machine the person holding it cannot see; the
app has always shown a failure body when there is one, so the whole fix
was on this side. The log keeps the same `{err:#}` chain.
- **A remote git failure is shortened and explained in one place**
(`git::remote_failure`). Git's stderr runs to a paragraph of generic
advice, so the card gets the first line and the log gets all of it; and
@@ -830,4 +840,26 @@ session reads. What follows is what that means here.
what *is* attached instead of guessing which of the three situations it
is in.
- The server needs `aapt2` (SDK build-tools) to add an app, and
`llvm-strip` (NDK) only for apps that need stripping.
`llvm-strip` (NDK) only for apps that need stripping. **It has to find
both without an environment**, because the way this server usually
starts is from a service manager, and one hands its process a scrubbed
environment: measured in the Gentoo guest, an OpenRC user service gets
19 variables with neither `$ANDROID_HOME` nor `$ANDROID_NDK_HOME` among
them, and a systemd user unit inherits an equally bare one unless
somebody imported theirs. Neither branch of `service-default` passes any
through, so this is not a Gentoo quirk and "export it in the unit" is
not the fix. `HOME` does survive, which is what the home-relative
candidates in `sdk.rs` are for. The trap it produces is a build that
*succeeds* and a download that 500s -- a project's own build script
sources its `android-env.sh` and repairs the environment inside its own
process, while the strip runs in-process here and sources nothing.
So `sdk_roots` lists candidates and every lookup takes the first that
actually contains the tools it needs: an `$ANDROID_HOME` that exists is
not one with the tools in it (this machine's system-wide
`/opt/android-sdk` has a `build-tools/36.0.0` holding nothing but
`package.xml`), and taking it blamed `zipalign` for a root that should
never have been chosen. Filter *before* taking the newest, too --
`newest_child_where` exists because filtering the single newest child
can only reject that one directory, so `Sdk` sitting beside
`android-ndk-r27c` in `~/Android` hid the NDK from a message that said
"install one".