Build one component instead of every one with a command

Pressing Update on one client of a multi-client project ran every
component's build to get the one that was actually asked for -- cheap
while a project had one APK, expensive the moment it had two and one of
them was slow (an ARM cross-compile, say). /prepare and /build now take
?component= to restrict a run to one named component; absent still means
the whole project, which is what Pull & Build, the project-row Rebuild,
and every single-component project 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 now runs
/prepare scoped to that component.

Verified against a live server driving a scratch two-Apk project:
building one component leaves the other's marker untouched, an unknown
?component= answers 404 naming the project and the component, and naming
none still builds both.

That verification surfaced a second, sharper bug the scoping change had
not caused but did make newly visible: component_is_stale's "never built
at all" check still asked find_apks of the whole project root, left
behind when per-component discovery (c4e19c2) moved everywhere else to
each component's own directory. A project with two Apk components has
one's output sitting under the root-anchored patterns too, so the moment
either component had ever been built, the whole project read as
"something is built here" -- and the other, never built, silently stopped
being offered its own first build. /prepare saw a component with a
command and no output and declared it current. Fixed by scoping the same
check to the component's own directory, guarded to Apk components only:
a Server never has an APK to find under its directory by definition, and
asking would have reported every server "never built" forever, which
broke two existing tests before the guard was added. Component::dir is
now the one definition of what a component's directory is, used by the
build command, the staleness check, and discovery alike.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Sonnet 5 committed 2026-09-01 00:28:36 -04:00
1 parent c4e19c2e2b
commit 7eaf79370e
7 files changed
+326 -49

No files matched your search

+36
View File
@@ -467,6 +467,42 @@ mutable at runtime from the phone.
builds a component has, a reused name is not the same APK, and handing
it the old one's package would be wrong until that component happened
to be downloaded.
- **`/prepare` and `/build` take `?component=` to build one component
instead of every one with a command.** Without it, pressing Update on
one client of a multi-client project ran every component's build to
get the one that was actually asked for -- fine when a project had one
APK, expensive the moment it had two and one of them was slow.
`named_component` in `routes.rs` is the one place a name from the phone
is checked against the project's own components, so `/prepare` and
`/build` cannot disagree about what an unknown name means, and
`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.
**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
still asked `find_apks` of the *project root* after per-component
discovery had already moved everywhere else to `component.dir()`. A
project with two `Apk` components has one's output sitting under the
root-anchored patterns too -- `*/build/outputs/apk/*/*.apk` matches any
one-level subdirectory, regardless of which component put it there --
so the moment *either* component had ever been built, the whole
project read as "something is built here," and the *other* component,
never built, silently stopped being offered its own first build:
`prepare` saw a component with a command and no output and declared it
current. Caught by testing the actual behaviour of a two-APK project
rather than trusting that scoping the build implied scoping the
staleness check that decides whether to run it -- they are two
different reads of "which directory is this component's," and only one
of them had been moved.
The same check is deliberately *not* asked of a `Server`: a service
never has an APK to find under its own directory by definition, so
asking would report every server "never built" forever. Guarded on
`matches!(component, Component::Apk { .. })` for that reason.
- **The self entry's project is the working directory itself**, so this
server has to be started from the root of its own checkout -- which is
where everything else here is driven from, and what the service unit