A Rebuild on every component, and the keystore AGP actually uses

Two things, both from the same stuck download.

Each component row now carries its own Rebuild, which is /build scoped to
it -- the route already took ?component=, only the button was missing.
Update builds only what the staleness rules call behind, and those rules
cannot see a command reading undeclared files, an output changed
underneath this server, or a signing key replaced since the APK was made;
in all of those Update does nothing and the only force was a project-wide
Rebuild that rebuilds every sibling. Same action at two scales, so it
takes the project Rebuild's word and colour. hasBuild is new on the
manifest component so a component with no command of its own draws none,
which the project-level needsBuild could not answer.

And debug_keystore_path() now resolves the preferences directory the way
AGP does instead of assuming ~/.android: ANDROID_USER_HOME as-is, then
ANDROID_PREFS_ROOT and ANDROID_SDK_HOME with .android appended, then
$XDG_CONFIG_HOME/.android when that directory exists, then $HOME/.android.
Measured by running real builds against AGP 32.3.2, including the
existence test on the XDG step, which the documentation does not mention.
An ordinary desktop machine with XDG_CONFIG_HOME set signs its APKs with
a keystore this server never looked at, so it reported "no debug
keystore" about a machine that had one and was using it -- and this VM
sets no XDG_CONFIG_HOME, which is why it could not happen here. The
failure now names a keystore found further down the list, since an inert
one and an absent one are otherwise identical from a phone.

Verified on the emulator (component Rebuild: press, that component's own
progress, back to resting with no sibling moving) and against a
throwaway server for both keystore branches.

Reported by the tdep-survey session, which found the two keystores on the
serving host.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-01 11:31:59 -04:00
1 parent b67c70fa2f
commit c7b3af4861
6 files changed
+358 -72

No files matched your search

+39 -4
View File
@@ -255,6 +255,23 @@ mutable at runtime from the phone.
that silence. It is part of the acceptance gate like every other
declared field -- `same_declaration` destructures exhaustively, which is
what forced the decision when the field was added.
- **The debug keystore is not `~/.android/debug.keystore`** -- that is the
last of five places, and `sdk::android_prefs_dirs` resolves it the way
AGP does, because the only thing that makes it the right key is being
the file Gradle signed with. Measured against AGP 32.3.2 by running real
builds: `$ANDROID_USER_HOME` is the preferences directory itself with no
`.android` under it, `$ANDROID_PREFS_ROOT` and `$ANDROID_SDK_HOME` get
`.android` appended, then `$XDG_CONFIG_HOME/.android` **but only when
that directory already exists** (the bytecode tests it and falls
through), then `$HOME/.android`. That fourth step is the whole bug: an
ordinary desktop Linux machine with `XDG_CONFIG_HOME` set signs its APKs
with a keystore this server never looked at, so it reported "no debug
keystore" about a machine that had one and was using it -- and this VM
sets no `XDG_CONFIG_HOME`, which is why the case could not appear here.
The failure names a keystore found further down the list, because an
inert one and an absent one look identical from a phone: mistaking one
for the other is what turned "this machine has none" into a signature
mismatch nobody could explain.
- **A stripped copy is only serveable if it is signed by the key the
build was.** `strip.rs` re-signs with `~/.android/debug.keystore`, and
nothing about a debug keystore says which builds it made -- it is per
@@ -633,10 +650,28 @@ mutable at runtime from the phone.
`BuildState::{trigger_if_needed,build_now,run_build,is_stale}` all take
the same `Option<&str>` -- `None` still means the whole project, which
is what `Pull & Build`, the project-row `Rebuild`, and every project
with a single component keep doing. There is deliberately no
component-scoped Rebuild: forcing one component's build without
touching the rest happens by pressing Update on it, which runs
`/prepare` scoped to that component.
with a single component keep doing. **Each component's row carries its
own Rebuild**, which is `/build` scoped to it. That used to be
deliberately absent, on the grounds that Update already builds a
component that needs it -- but Update runs `/prepare`, which builds only
when the staleness rules say the output is behind, and those rules are
blind to everything a commit does not describe: a command reading files
nobody declared, an output changed underneath this server, a signing key
replaced since the APK was made. In all of them nothing reads as stale,
so Update does nothing, and the only force was a project-wide Rebuild
that rebuilds every sibling too. Iris asked for the per-component
button on 2026-09-01. The two are the same action at two scales, so
they share a word and a colour, and each sits at the right-hand end of
its own row.
A component with no command of its own draws no Rebuild -- `hasBuild`
on the manifest component says so per component, which the project's
own `needsBuild` (the OR of every component's) cannot. Absent rather
than disabled, matching the project row, because having no build step
is not a state a component is in.
A server's Rebuild sits in its service row but outside the condition
that draws the service buttons: those wait for the script's answer,
while a server whose script cannot be reached is still one this machine
can compile.
**Where `component.dir()` belongs is now one definition**
(`Component::dir` in `config.rs`), because a second one very nearly
shipped a real bug: `component_is_stale`'s "never built at all" check