Skip to content

feat: Add android build - #1357

Draft
alekseifedotov wants to merge 11 commits into
mainfrom
aleksei/add-android-build
Draft

feat: Add android build#1357
alekseifedotov wants to merge 11 commits into
mainfrom
aleksei/add-android-build

Conversation

@alekseifedotov

@alekseifedotov alekseifedotov commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Build the projects for 🤖

Adds an Android NDK toolchain to the dev shell (nixpkgs androidenv,
NDK-only, license accepted, API level 24) and wires it into cargo via
env vars, following the same pattern as the existing OP-TEE
cross-toolchain setup. cargo-zigbuild can't be reused here since zig
doesn't bundle Android's bionic sysroot.

Adds `cargo x android-sweep` to attempt building every workspace crate
(or a given list) for aarch64-linux-android and report pass/fail - not
every crate is expected to build.

Verified end-to-end: orb-const-concat, orb-arc-pool,
orb-header-parsing, and orb-attest (with its full zbus/zenoh/dbus
dependency stack) all compile and link for aarch64-linux-android with
this toolchain, no source changes required.
Marks 24 crates as unsupported on aarch64-linux-android via the
existing `[package.metadata.orb] unsupported_targets` mechanism
(already used by ci/rust_ci_helper.py to skip Darwin-incompatible
crates in `cargo nextest run --workspace`), grouped by root cause:

- Bionic libc gaps: can-rs, orb-mcu-interface, orb-mcu-util,
  orb-update-agent, orb-update-verifier, orb-uart (no SocketCAN, no
  termios c_ispeed/c_ospeed)
- No EFI on Android: efivar, orb-slot-ctrl, orb-update-agent-core
- Vendor/native libs with no Android build: orb-gst-jpeg, orb-sound,
  orb-ui, orb-cone, orb-hil, seek-camera(-sys),
  orb-thermal-cam-ctrl, orb-connd, orb-secure-storage-ca
- squashfs toolchain not cross-built for Android:
  orb-bidiff-squashfs(-shim), orb-bidiff-cli, orb-tools
- Genuine bionic incompatibility: orb-update-agent-loader (the `nix`
  crate calls `fexecve`, which bionic doesn't implement)

Adds `cargo x android-build`, which reads that same metadata to
compute the exclude list and runs a single
`cargo build --workspace --target aarch64-linux-android --exclude ...`
(verified locally, exit 0). This is a CI gate, unlike the existing
`android-sweep` discovery command: `--exclude` only removes packages
as build roots, so anything depending on an excluded crate (e.g.
orb-tools -> orb-bidiff-squashfs-shim) needed its own entry too.

Wires this into a new `Android Build` job in rust-ci.yaml, matching
the existing job pattern.

Note: agentwire-macros failed when built in isolation (missing
syn/full feature) but was deliberately left off the exclude list -
verified it builds fine as part of the full `--workspace` build, once
feature unification pulls in syn/full from elsewhere in the graph.
@alekseifedotov
alekseifedotov requested a review from a team as a code owner August 20, 2026 22:04
@alekseifedotov alekseifedotov changed the title Add 🤖 build feat: Add 🤖 build Aug 20, 2026
@github-actions

Copy link
Copy Markdown

No concrete correctness, security, or merge-blocking issues found in the PR changes.

Adds `cargo x android-apex-payload`: for every Android-supported
binary crate, stages its own payload directory (bin/<binary>,
apex_manifest.json, a placeholder etc/init/<binary>.rc) - one per
project, not a single shared APEX. Reuses android-build's exclude
logic and rebuilds first so staged binaries are current.

Verified end-to-end: staged 14 crates (orbd, orb-attest,
orb-jobs-agent, etc.), confirmed correct directory layout and that
staged binaries are real aarch64 ELF Android binaries.

This stops short of invoking `apexer` to produce a real signed
.apex - that needs a cluster of AOSP-built host tools (avbtool,
mkfs.erofs, aapt2, apexer's own protobuf-generated manifest parser)
that this workspace doesn't have a sourcing story for yet. Manifest
naming and init.rc contents are marked TODO pending real
answers on APEX namespace and SELinux domain.
run_payload already authors every path in the payload (bin/<binary>,
etc/init/<binary>.rc); it can emit the canned_fs_config apexer needs
directly as it writes those paths, rather than re-deriving the same
information by walking the tree after the fact with a separate tool
(gen-canned-fs-config.nix). One source of truth for what's in the
payload instead of two.

Verified: re-ran `cargo x android-apex-payload`, inspected the
generated canned_fs_config for orbd - correct paths/uid/gid/mode.
Adds nix/packages/android-apex.nix, building `apexer` from AOSP source
(system/apex at android-16.0.0_r4, one repo - manifest.py and
mke2fs.conf are single-file fetches, not full extra repo clones) and
substituting nixpkgs/android-tools for the rest of its usual
AOSP-build-only toolchain: android-tools' mke2fs.android/e2fsdroid for
the ext4 payload plus avbtool, e2fsprogs' resize2fs, aapt's aapt2.

Payload filesystem is ext4, not erofs - matches apexer-for-clanker.nix.
Confirmed nixpkgs' erofs-utils can't produce a canned_fs_config-capable
mkfs.erofs even with selinuxSupport=true: --file-contexts needs only
libselinux, but --canned_fs_config needs a second, separate
-DWITH_ANDROID build plus AOSP's libcutils (system/core) for
load_canned_fs_config, which isn't implemented anywhere else and has
no standalone build path. android-tools' e2fsdroid already has this
built in as a working prebuilt, so ext4 avoids that whole dependency
chain.

Restructures run_payload's output: content actually going into the
APEX (bin/, etc/) now lives under a content/ subdirectory, sibling to
apex_manifest.json/canned_fs_config/file_contexts - apexer's e2fsdroid
scans whatever directory it's given as literal payload content, so the
previous flat layout made it choke trying to find our own sidecar
files (e.g. apex_manifest.json) in canned_fs_config.

Verified end-to-end: built a real signed orbd.apex from the staged
orbd payload. Confirmed with zipfile (AndroidManifest.xml,
resources.arsc, apex_manifest.pb, apex_payload.img all present) and
debugfs (ext4 image contains /bin/orbd at mode 755, uid/gid 1000, plus
apex_manifest.pb at the image root) and avbtool info_image (valid
SHA256_RSA4096 hashtree footer). apex_manifest.pb round-trips to
name=com.worldcoin.orb.orbd, version=1 as expected.

Signing key is a throwaway generated per build-apex invocation - fine
for local testing, needs a real managed key before this ships
anywhere near a device.
@github-actions

Copy link
Copy Markdown
  • xtask/src/cmd/android.rs:156 — Cargo package names contain hyphens (for example orb-connd), producing invalid Android/APEX names such as com.worldcoin.orb.orb-connd. aapt2 rejects these package names, so the generated payloads cannot be packaged. Sanitize/map crate names to valid reverse-DNS identifiers.

  • xtask/src/cmd/android.rs:98 — The payload output directory is never cleaned. Removed binaries, init scripts, or newly unsupported crates remain in content/, making subsequent APEX builds stale or inconsistent with canned_fs_config. Recreate each package output atomically or remove it before staging.

Switches the APEX payload filesystem back to erofs, using a real
AOSP-built mkfs.erofs binary fetched directly from
kernel/prebuilts/build-tools (android-16.0.0_r0.4) rather than trying
to build one ourselves. Patched via autoPatchelfHook to run under
nixpkgs' glibc; uses AOSP's own libc++.so (also fetched from the same
prebuilts repo) rather than nixpkgs' to avoid any ABI mismatch with a
binary Google built and tested against that exact one.

Confirmed via readelf that mkfs.erofs only NEEDS generic libc/libc++
(libcutils/libbase/liblog are statically linked in for the host
build), so no separate AOSP shared libs are needed beyond libc++.so.

This drops all the ext4-specific tooling from the previous commit
(mke2fs.android, e2fsdroid, resize2fs, the sefcontext_compile shim,
AOSP's mke2fs.conf) - erofs needs none of it, mkfs.erofs takes
--file-contexts/--fs-config-file directly.

Verified end-to-end: rebuilt orbd.apex using this toolchain. Confirmed
with zipfile (same structure as before) and, this time, actually
extracted the erofs payload with AOSP's own fsck.erofs and confirmed
apex_manifest.pb/bin/orbd/etc/init/orbd.rc are all present and orbd is
still executable. Image is ~46MB vs ext4's ~135MB, thanks to lz4hc
compression.
Exposes the `build-apex` tool from nix/packages/android-apex.nix as a
real flake package (packages.x86_64-linux.build-apex), and adds `cargo
x android-apex`: stages payloads (same as android-apex-payload), nix
builds build-apex once, then packages each staged crate into
target/android-apex/<crate>.apex.

android-apex-payload's run_payload now returns the list of staged
package names (Result<Vec<String>> instead of Result<()>) so
run_apex can reuse it directly instead of re-deriving the same
crate list a second time.

Also fixes a real bug this surfaced: the placeholder APEX name
(com.worldcoin.orb.<crate>) breaks for any crate with a hyphen in its
name (e.g. orb-attest) since Android package names are dot-joined
Java identifiers and aapt2 rejects hyphens outright. Sanitizes to `_`.

Verified end-to-end: `cargo x android-apex` packaged all 14
Android-supported binary crates into individual signed .apex files
(orb-attest, orbd, orb-supervisor, zorb, etc.), each confirmed with
zipfile to have the expected structure.
Replaces the Android Build job's plain `cargo x android-build` step
with `cargo x android-apex`, which is a strict superset (it calls the
same workspace build internally, then additionally packages every
Android-supported binary crate into a signed .apex) - running both
would just rebuild the same already-warm workspace a second time for
no benefit.

Uploads target/android-apex/*.apex as a build artifact (android-apex),
matching the existing `build` job's upload-artifact pattern.

Note: this makes CI depend on network access to
android.googlesource.com (to fetch the AOSP apexer source and Google's
prebuilt mkfs.erofs) on a cache miss - a new external dependency this
job didn't have before.
@alekseifedotov alekseifedotov changed the title feat: Add 🤖 build feat: Add android build Aug 21, 2026
@alekseifedotov
alekseifedotov marked this pull request as draft August 21, 2026 05:22
@github-actions

Copy link
Copy Markdown
  • [P1] Create the out-link parent before invoking Nixxtask/src/cmd/android.rs:232

    On a clean CI checkout, target/android-apex/ does not exist when nix build --out-link target/android-apex/build-apex runs. Nix cannot create that symlink without its parent, so the new Android job fails before packaging. Move create_dir_all(&out_dir) before this command, or place the out-link in an already-created directory.

Two real bugs from automated review:

- `run_apex` called `nix build --out-link target/android-apex/build-apex`
  before creating target/android-apex/ - nix can't create that symlink
  without its parent existing, so this failed outright on any clean
  checkout (no prior `target/` dir). Moved the create_dir_all before
  the nix build, and made it independent of the user-configurable
  `--out-dir` rather than conflating the two.

- `run_payload` never cleaned a package's payload directory between
  runs, so a binary/init script removed since the last run (or a
  crate that just became unsupported) would linger in `content/` and
  get bundled into the next APEX, inconsistent with the freshly
  generated canned_fs_config. Now removes each package's output dir
  before restaging it.

(A third comment, about crate names with hyphens producing invalid
Android package names, was already fixed in f785345e.)

Verified: reproduced the clean-checkout scenario (removed
target/android-apex and target/android-apex-payloads first) and
confirmed `cargo x android-apex` now succeeds, producing all 14
.apex files.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant