build(bazel): hermetic XiOne cross-build alongside the CMake build - #4
Conversation
Port the XiOne (armv7 NEON hard-float) cross-compile tooling onto the cmcd-cta5004 line: cmake/xione-armhf.cmake, cmake/FindEthanLog.cmake, and the CMAKE_MODULE_PATH append that lets find_package() see them. CMakeLists.txt already gates find_package(EthanLog REQUIRED) behind CMAKE_USE_ETHAN_LOG, but this branch's lineage never carried the module itself. Ported from 23a462e on feat/xione-build-tooling rather than merged: that branch descends from xione-release (the Sky 8.4 line) and this one from the sprint line, so their merge-base is ancient and a merge would drag in unrelated history. Only the two cmake modules and the module-path append are wanted here. Kept this branch's CMAKE_CXX_STANDARD 17 (the xione-release line was on 14). cmake/xione-armhf.cmake carries one addition over the version on feat/xione-build-tooling: a force-append of the multiarch -L onto the linker flag cache vars. The _INIT seeding it replaces is a no-op when those vars arrive pre-set on the cmake command line, which is what the Bazel build in the following commit does. See the comment at that block. The device-harvest sysroot scripts from that branch are deliberately not ported; the Bazel build that follows assembles a sysroot from pinned Debian packages instead, with no device in the loop.
The 12 RDK downstream patches for bitmovin libdash stable_3_0, byte-for-byte as scripts/install_libdash.sh fetches them from meta-rdk-ext. Vendoring them lets a hermetic build apply the patch set without cloning meta-rdk-ext at build time, which the Bazel libdash cross-build in the following commit relies on. Ported from 0e09706 on bazel/libdash-patches, which descends from the xione-release line rather than this one.
Add a Bazel module that drives this repo's own CMake build (via rules_foreign_cc and cmake/xione-armhf.cmake) to cross-compile AAMP for the Sky XiOne — armv7 NEON hard-float, glibc 2.35, new C++11 string ABI. CMake stays the interface RDK maintains and the only supported host build; Bazel exists here solely so CI can produce release artifacts reproducibly, with no device and no developer machine in the loop. Nothing in a normal CMake build is affected: //:aamp is target_compatible_with //bazel/constraints:xione-stb, so it is skipped in wildcards and only builds under --platforms=//bazel/platforms:xione. No device in the build. scripts/build-xione-sysroot.sh assembles its sysroot partly by harvesting ABI-exact .so files from a live XiOne over SSH, which cannot run in CI and is not reproducible. //third_party/xione_sysroot replaces that with 36 sha256-pinned Debian armhf .debs for headers/.pc/link stand-ins, ethanlog and libdash built from pinned source, and JSC reduced to a generated link stub. Ships all nine .so files the build emits, not just libaamp.so and libaampjsbindings.so. With CMAKE_INBUILT_AAMP_DEPENDENCIES=ON this branch builds middleware/ and support/ as separate shared libraries and libaamp.so carries a DT_NEEDED on five of them, so the two headline libraries alone cannot load on a device — its stock AAMP predates that split. The older xione-release line absorbed the middleware into a monolithic libaamp.so, which is why the earlier prototype only collected two. Two cross-build quirks are handled in //BUILD.bazel with comments: JSC_INCDIR is pre-seeded because find_path() cannot resolve it under CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY once pkg-config has already returned a sysroot-absolute hint, and the host pkg-config is preferred because bootstrapping it from source compiles a vendored glib that is invalid C23. Verified: builds nine ELF32 ARM hard-float libraries with a self-contained internal DT_NEEDED closure. Not yet verified on a device — see XIONE-BUILD.md.
Add CI for the cross-build and a release pipeline that publishes the libraries
as a GitHub Release asset, so downstream repos pin a tarball by URL + sha256
rather than reproducing the cross-build themselves.
tools/release/stage_artifacts.sh does the packaging, and two steps in it are
load-bearing rather than cosmetic:
- patchelf --set-rpath '$ORIGIN' on every .so. rules_foreign_cc bakes the build
sandbox path into DT_RPATH; left unpatched a bundled libaampjsbindings.so
silently loads the device's stock /usr/lib/libaamp.so and defeats the swap
with no error, just the wrong code running. Doing it in the producer makes
every published artifact correct by construction.
- the cross strip from the Bootlin toolchain, not the host's. Host binutils
cannot read ELF32 ARM at all ("Unable to recognise the architecture").
The tarball unpacks to usr/lib/*.so + manifest.json, laid out to drop straight
into a .wgt tree. manifest.json records the AAMP commit, the Bootlin toolchain
sha256 and the deb manifest hash, because the artifact is a function of all of
them and no upstream version identifies it.
xione-ci.yml asserts what actually breaks on a device rather than just that the
build exits zero: every library is ELF32 / EM_ARM / hard-float, the library count
matches, and the AAMP-internal DT_NEEDED closure is satisfied. It also runs the
staging script so an RPATH or packaging regression surfaces in CI rather than at
release time. Paths-scoped, since this is a full cross-compile of the player.
Release tags are prefixed xione-v* so they cannot collide with upstream
rdkcentral tags, and the version is packaging semver rather than an AAMP
version. Notes are passed via body_path: a `body:` YAML block scalar is not
shell-interpolated, so a $(...) placed there publishes literally — shipping
release notes whose stated checksums are absent while telling consumers to copy
hashes from them.
Runs on ubuntu-latest with no remote cache or cloud credentials: the Bootlin
cross-compiler is x86_64-host-only so the runner must be x86_64, and a warm
build is fast enough that actions/cache over ~/.cache/bazel is sufficient.
Verified locally end to end: produces a 2.1M tarball of nine stripped ELF32 ARM
libraries, all with RPATH=$ORIGIN, all assertions passing.
Add a `xione-build-<shortsha>` tag mode alongside `xione-v<semver>`, so a specific commit can be published and pinned downstream before anything is blessed as a version. Commit builds go out as GitHub prereleases and their tags are disposable — delete both when the commit is superseded. Promoting one is just tagging the same commit `xione-v<semver>` and repointing the consumer; the build, staging and notes are identical either way, and manifest.json records the inputs regardless. Driven by tag push rather than workflow_dispatch because dispatch only works once the workflow is on the default branch, and this needs to run from a feature branch. The dispatch path stays for later.
Removes the host `ar` and `nm` dependencies from the repository rules, and adopts
webrtc-sim's `.bazelrc` hermeticity block. Aligning with webrtc-sim was the driver:
its own repo rules contain zero `ctx.which`/`ctx.execute` calls, so these escapes
were a divergence from house style rather than an instance of it.
`nm` was the one that mattered. jsc_deb ran the HOST nm over an ELF32 ARM `.so` to
derive the JSC link-stub symbol list, which only works if the distro built binutils
with the ARM target in BFD — host `strip` already fails outright on these same files
("Unable to recognise the architecture of the input file"). On a leaner CI image the
*fetch* would break, which is a confusing place to fail. The symbol list is now
derived in a build action (//third_party/jsc:wpe_webkit_syms) using the cross nm
from the Bootlin toolchain we already fetch: hermetic and target-correct. It also
now fails the build on an empty symbol list, which previously would have produced a
stub exporting nothing — links fine, dies on the device.
`ar` turned out to be unnecessary rather than replaceable: Bazel's own extractor
understands the `ar` container a `.deb` is, so `ctx.extract` unwraps it and a second
extract unpacks the payload. Confirmed by probe before relying on it. This also
retires the last argument in the xione_sysroot README for preferring
rules_distroless, which had already been prototyped and rejected because its
transitive per-package closures drag Debian's glibc into a sysroot the cross
toolchain owns.
Verified: with shims that make host `ar` and `nm` exit 127 placed first on PATH, a
`clean --expunge` cold build succeeds, and both outputs are byte-identical to the
previous implementation — the 4257-entry sysroot tree (paths, file hashes and
symlink targets) and wpe-webkit.syms (sha256 5ef685e9…, 168 symbols). `bazel build
//...` on a host still skips the cross targets.
Still host-dependent, and deliberately left: `ln` for the multiarch fixups
(ctx.symlink produces absolute links, which do not survive rules_foreign_cc copying
the tree — webrtc-sim documents the same constraint), `mkdir`/`cp` for header
staging, host pkg-config, and the ~14 coreutils rules_foreign_cc's generated script
calls. That last one is the ceiling: this build will always assume a POSIX userland.
Completes the removal of host-tool escapes from the fetch phase: `deb_sysroot` and `jsc_deb` now use only ctx.download/extract/read/file, so `grep -rn 'ctx.which\| ctx.execute' bazel/` is empty. That matches webrtc-sim, whose own repo rules have never contained either call. The multiarch fixups move into assemble_sysroot rather than being rewritten. A repository rule cannot create a *relative* symlink without shelling out to `ln` (ctx.symlink produces absolute links, which do not survive rules_foreign_cc copying the tree — webrtc-sim documents the same constraint at bazel/extensions/zig.bzl), but assemble_sysroot is already a build action that creates relative symlinks with `ln -sfn`, so the fixups just belong there. No new dependency, and the mechanism was already in the codebase. Their order inside that action is load-bearing and commented as such: the include mirror must run against the deb tree alone, before the glibc overlay, because it only links names that do not already exist and glibc contributes colliding usr/include entries (sys/, bits/, gnu/). Running it after glibc would silently create fewer links. Header staging uses ctx.read/ctx.file instead of `cp`. The eight classic JSC C-API headers are pure US-ASCII, so the round-trip is byte-exact. The assembled sysroot tree legitimately CHANGES, in a strictly better direction: 11 relative directory symlinks (usr/include/curl -> arm-linux-gnueabihf/curl) replace 22 absolute per-file symlinks that previously pointed into the local Bazel cache. Those absolute links were an artifact of Bazel expanding a repo-side directory symlink into individual files, and are exactly the cross-runner fragility webrtc-sim warns about. Verified. Fetch phase: with shims making host ar/nm/ln/cp/mkdir all exit 127 first on PATH, a `clean --expunge` cold fetch succeeds. Compiled output: every library's size, dynamic-symbol count and DT_NEEDED list is unchanged, and all nine dynamic symbol-name sets hash identically to the CI-published artifact built before this change. Release staging and its assertions still pass; `bazel build //...` on a host still skips the cross targets. Remaining host dependencies are all build-phase and deliberately out of scope: the coreutils assemble_sysroot itself calls, host pkg-config, and the ~14 coreutils rules_foreign_cc's generated script uses. That is the ceiling while CMake drives the compile.
Completes the host-tool removal for everything this repo writes. `assemble_sysroot` was the last holdout: it shelled out to `ln -sfn` for the multiarch include mirror, the `libz.so` alias and the unversioned `.so` aliases, on the reasoning that only `ln` can make a *relative* symlink. That is true of repository rules, but not of build actions — `ctx.actions.declare_symlink` produces exactly that, and Bazel stages such an artifact as the symlink itself, so `cp -a` (already used throughout the action) places it with its link text intact. Which names get mirrored now comes from the deb filegroup at analysis time rather than from a shell loop over the assembled tree. Same 11 links, but the set is visible in the analysis graph, and the load-bearing ordering becomes a property of the inputs: the mirror must consider the deb tree alone, since it only links names that tree lacks (`openssl` is the live case) and the glibc overlay contributes colliding usr/include entries. Verified with a positive control, because the obvious way to test this is wrong: `--action_env=PATH` does not reach this action (a `run_shell` action gets an empty env, so PATH is bash's built-in default), and a shim under /tmp is invisible inside the sandbox, which mounts a fresh tmpfs there. With the shim outside /tmp and a PATH pinned into the action, the previous revision fails on `ln` (exit 127) and this one succeeds. Both sysroot trees come out with the same 13 relative links as before, and all nine libraries are unchanged in size, dynamic-symbol count, symbol-set hash and DT_NEEDED. One `ln` remains reachable in a full build and is not ours to remove: rules_foreign_cc's generated script stages its tool binaries with `ln -sf`, and the symlink is load-bearing rather than incidental — CMake locates CMAKE_ROOT by resolving argv[0] back to the real binary, so a *copy* of `cmake` dies with "Could not find CMAKE_ROOT" (confirmed directly). Dropping `ln` from PATH entirely therefore needs CMake out of the picture, not a patch to rules_foreign_cc. XIONE-BUILD.md now says so explicitly instead of listing `ln` among our own actions' coreutils. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…xione-bazel-build
The toolchain file set CMAKE_{C,CXX}_FLAGS_INIT to
"-I${XIONE_SYSROOT}/include/libdash" and promised, in a comment, that the libdash
include dir was added "to every compile". Neither held. The ABI block further down
re-sets the same two variables as CACHE entries, and creating a cache entry removes
the normal variable of the same name from scope, so the value CMake actually used
was only the -march/-mfpu/-mfloat-abi set. The path was wrong too:
assemble_sysroot stages those headers at usr/include/libdash, not include/libdash.
Dropped rather than corrected. It has never been in effect, and the build has
always resolved libdash headers through the sysroot's default include path plus
CPATH — so making it real now would add an unexercised compile flag to a
device-verified build in order to satisfy a comment. The comment now says that,
and says where to put the -I (with the usr/ prefix) if a consumer ever needs it.
All nine libraries are unchanged by this: same size, dynamic symbol set and
DT_NEEDED, as expected for a flag that was never applied.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nonical repo name Both cmake() targets spelled the Bootlin toolchain's path as `external/+http_archive+xione_bootlin_toolchain` — Bazel's internal canonical repo-name form, which has already changed shape once (`~http_archive~name`), and which moving the archive behind a module extension would change again. Either way the variable silently points at a directory that does not exist, and the symptom is CMake failing with "CMAKE_C_COMPILER not found", which names nothing near the cause. Derive it from a file inside the toolchain instead: `:nm` is a single-file filegroup at bin/<triple>-nm, so two dirnames give the root. That also retires the two `# buildifier: disable=external-path` suppressions, which existed only to silence this. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
postfix_script collected each of the nine libraries with `find $BUILD_TMPDIR -name $so -type f | head -1`. Two problems with `head -1`: on no match the command substitution is empty and cp fails with "missing destination file operand", which says nothing about which library is missing; and on more than one match — AAMP's CMake does copy libraries around its build tree — which one gets staged is filesystem-order dependent, so one source revision could produce different bytes on different machines with nothing in the log to say why. Require exactly one match per name, and fail with the name and the candidates. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two silent-wrong-result paths in assemble_sysroot, both of which only misbehave once something upstream moves — which is when a clear error is worth most. overlay_files staged the file whose basename matched the destination and otherwise fell back to `tfiles[0]`. A soname bump (libdash.so -> libdash.so.3) or a cmake() layout change makes the match fail, and the fallback then stages an arbitrary sibling — plausibly an include *directory* — at usr/lib/…/libdash.so. Nothing notices until AAMP's link reports "file format not recognized" about a path that looks right. It now requires exactly one match and names the label, destination, expected basename and what the target actually offered. _root_of indexed files[0] with no empty check, so a filegroup resolving to nothing crashed analysis with a bare Starlark "index out of range" naming neither the attribute nor the target. Callers now pass the label. Verified the new fail() fires rather than assuming it: pointing an overlay_files destination at a basename no file carries reports "expected exactly one file named ethanlog-bogus.h, found 0 among [\"ethanlog.h\"]" instead of staging the wrong file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`sed -i -E` is GNU-only: BSD/macOS sed reads the argument after -i as the backup suffix, so -E becomes the suffix and the expression becomes a filename. Repository rules always run on the local host — even under --config=rbe, which is how the macOS path would otherwise be argued — so a macOS client fetching this toolchain fails inside patch_cmds. Rewrite through a temp file, the same form assemble_sysroot already uses for the identical ld-script fixup. Behaviour is unchanged on Linux: the sysroot's ld scripts get the same `=`-prefixed paths, and all nine libraries build byte-for-byte the same size, symbol set and DT_NEEDED. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The README said the flag is applied "on compile + link". It is compile-only, and deliberately so — the rationale is entirely about how the compile step reports builtin header dirs, and putting it on the link action changed zig link output for no benefit, which is why it was reverted. Someone debugging a zig link would have trusted the README over the code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`bazel build //...` fetched @xione_bootlin_toolchain — a ~500 MB Linux-x86_64 archive, plus its patch_cmds — on every host, even though all six cross targets are skipped as incompatible. Measured with a fresh output base: the archive appears in external/ after a plain wildcard build, and it is the only cross repo that does (the deb sysroot, JSC, libdash and ethanlog stay lazy). Neither target in this package was gated, and both name a label inside the fetched repo, so wildcard expansion analyses them and the fetch follows. `target_compatible_with` on the toolchain() rule does not help: those constraints describe what the toolchain is *for*, they do not stop the target itself from being analysed. Verified the registration is not the trigger by removing it and re-running — the fetch still happened. On a macOS host this is the one thing that drags a wildcard build into the fetch phase's shell, for an archive nothing on that host can execute. Tagging both `manual` makes the cross build opt-in: `//...` no longer fetches it (verified, fresh output base), while `bazel build //:aamp --platforms=//bazel/platforms:xione` still does and `register_toolchains` resolves the label directly, which `manual` does not affect. All nine libraries remain identical to the device-verified build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces the `http_archive` + `patch_cmds` with a repository rule that does the same download and the same one ld-script rewrite, in Starlark. The fetch phase now uses no host tools at all, which is what XIONE-BUILD.md has been claiming while patch_cmds ran find, grep and sed. The shell form was also not portable. `sed -i` with no argument is GNU-only — BSD and macOS sed read the following argument as a backup suffix, so `-E` became the suffix and the expression became a filename — and repository rules always run on the local host, even under --config=rbe, so a macOS client failed inside the fetch rather than anywhere informative. Mechanically: `path.readdir()` walks the sysroot breadth-first (Starlark has neither recursion nor `while`, so the walk is a bounded loop that fails rather than half-scanning a deeper tree), and only `*.so` files are read, since an ld script always stands in for a shared library and reading all ~2300 files including multi-megabyte `.a` archives to find a marker would be gratuitous. Exactly one script needs the rewrite here, `usr/lib/libc.so`; `lib/libgcc_s.so` is an ld script too but names its members relatively. The rule fails if it rewrites nothing. An unrelocated script does not error at link time — it silently resolves against the host x86_64 libc — so a toolchain bump that moves or renames the scripts has to be loud at fetch. Verified: both ld scripts come out byte-identical to what sed produced, with the same 0644 mode; the fail() path was exercised by pointing the scan at a directory with no scripts; and all nine libraries are unchanged in size, symbol set and DT_NEEDED. Note this changes the canonical repo name to `+bootlin_toolchain+xione_bootlin_toolchain`, which is exactly the breakage the preceding XIONE_TOOLCHAIN_DIR commit removed — with the old hardcoded `external/+http_archive+…` this commit would have failed with "CMAKE_C_COMPILER not found". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Removes the last `sed` from the build. assemble_sysroot ended every run with a `find | grep | sed` pass to `=`-prefix absolute GROUP/INPUT paths in the merged tree; with the Bootlin fetch now doing that in Starlark, the only script it still had to fix was our own -lpthreads alias, which deb_sysroot generates. So the rewrite moves into deb_sysroot too, and the shared implementation lands in //bazel/repo_rules:ld_scripts.bzl. Both sysroot sources now guarantee the property at fetch time and the merged tree inherits it — which is strictly better than the old catch-all, because a `.deb` that ships its own ld script (libc6-dev would) is covered without anything downstream having to notice, and neither the fetch nor the assemble action needs a GNU coreutils userland for it. The `*.so`-only filter needs one non-obvious guard: a deb-derived sysroot is full of dangling dev symlinks whose versioned target lives in a runtime package the manifest omits (`libpcre2-32.so`), and `ctx.read` on a dangling symlink raises FileNotFoundException rather than returning empty. Found by the build, not by reading — the Bootlin tree has no such links, so the deb side was the first to hit it. Verified: both trees carry the same two `=`-prefixed scripts as before (Bootlin's usr/lib/libc.so, our usr/lib/arm-linux-gnueabihf/libpthreads.so), all nine libraries are unchanged in size, symbol set and DT_NEEDED, and `sed`, `find` and `grep` now appear in bazel/ only inside comments. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
stage_artifacts.sh built the path to the Bootlin `strip` out of
`external/+http_archive+xione_bootlin_toolchain`, so moving the toolchain to a repo
rule renamed the directory out from under it. The script fails hard when the strip
is missing, which is right — but it never fired, because every runner and every
developer machine that had built before still had the old directory in its Bazel
cache. CI's "Verify release staging" step passed on the very commit that broke it.
A clean build, or a cache miss, is the first thing that would have failed, and the
error ("cross strip not found at …") points at a path rather than at the rename.
Match the directory by suffix instead. The leading `+<rule>+` is Bazel's internal
canonical-repo form and changes whenever the fetch mechanism does — `~http_archive~`
before bzlmod's current scheme, `+http_archive+` until this week — while the
trailing apparent name is ours and stable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Switches both workflows off `ubuntu-latest`, which was only ever the fallback. Self-hosted ARC runners were registered for this repo specifically — not org-wide, because their docker-in-docker boundary is a concern for public/forked repos — and the instruction that came with that was to select them as `gke-arc-aamp` rather than webrtc-sim's `gke-arc-std` (rillanetwork/core-infra#133). Until now the enablement sat unused. `sudo` is no longer assumed: the ARC image runs as root without it, GitHub-hosted requires it, so the apt step picks whichever exists and the workflows still run on either. That keeps a revert to `ubuntu-latest` a one-line change if the label turns out not to be picked up — the failure mode there is a job that queues forever, which is exactly what happened on rdk-packages before the runners existed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The switch to the self-hosted label was tried and does not work: both XiOne jobs sat `queued` for over 8 minutes with no runner assigned, then were cancelled. That is the same failure rdk-packages hit before its runners existed — a label nobody listens on does not error, it hangs, and it would have left this PR's checks pending forever. rillanetwork/core-infra#133, the PR referenced when the runners were set up, is titled "add rdk-packages ARC runner scale set" — it provisions the scale set for rdk-packages, the repo the build moved *away* from. Whether anything was ever registered for this repo cannot be checked without runner-read permission on it. Keeps the sudo-optional apt step from the previous commit, so adopting the runners later is a one-line change per workflow rather than a re-litigation of the image assumptions. GitHub-hosted is also what was signed off for this in review ("since this is probably a one off thing, I'm ok to use github hosted runners too") and the cross-build completes there in about three minutes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds //tools/verify:xione_libs_ok, which checks the built libraries are actually
loadable on the device: every one ELF32 / EM_ARM / hard-float, all nine present, and
the AAMP-internal DT_NEEDED closure self-contained. These are the failure modes a
zero exit status misses — a build that silently targeted the wrong architecture, or
lost a middleware library that still links and then fails at dlopen on the box with
nothing reporting why.
The checks already existed, as ~40 lines of bash in xione-ci.yml. In Bazel they are
cached and only re-run when the libraries change, and — the reason I wanted them
here — they can be run locally with one command instead of reading a workflow file to
find out what CI enforces.
A genrule rather than a test, and not by preference: `bazel test
--platforms=//bazel/platforms:xione` fails toolchain resolution, because tests want
an execution platform matching their target platform and this one is an armv7
set-top box ("No matching toolchains found for
@bazel_tools//tools/test:default_test_toolchain_type"). The ways around that are
registering a fake exec platform, or transitioning the libraries into a host-side
test — and the transition has a real cost: it would make the check host-compatible,
so `bazel build //...` would start fetching the cross toolchain and running a
multi-minute build, which the preceding commit deliberately stopped. As a genrule the
check runs in the same configuration as the thing it checks.
Uses the cross readelf for the same reason //third_party/jsc uses the cross nm: the
host's may or may not have the ARM target in BFD.
Verified both directions rather than just the happy path: it reports "ok: 9
libraries…", and with the expected count set to 10 it fails the build with "expected
10 shared libraries, got 9". A host `bazel build //...` still skips it and still
fetches no toolchain.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two changes to the workflow, both about letting Bazel decide what can be reused. **The cache split.** `actions/cache` covered `~/.cache/bazel` wholesale, which also captured the *output_base* — so every run started from a previous run's `external/` tree. That is not a theoretical problem: it is how a renamed repository directory stayed alive across runs and let CI's staging step pass on the very commit that broke it, until a clean build failed locally. Now a user bazelrc points `--repository_cache` and `--disk_cache` at separate directories, cached separately: downloads (36 debs off snapshot.debian.org, ~500 MB toolchain — the genuinely slow part) stay shared, action outputs are keyed on action hashes rather than on a key I wrote by hand, and the output_base is never restored, so analysis always starts clean. The bazelrc lives at $HOME/.bazelrc so nested invocations pick it up too, including the ones inside stage_artifacts.sh. Saves are restricted to pushes. PR runs read the shared entry instead of each writing one and evicting it, which is the same split webrtc-sim's setup uses. **The assertions move to Bazel.** The inline bash is replaced by building //tools/verify:xione_libs_ok, which subsumes it and builds //:aamp on the way. Failures now come from a script that can be run locally. Verified with a fresh output_base and the two caches configured: the build succeeds and the disk cache populates (187 MB, ~3.5k entries), so the bazelrc is in effect for every invocation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@disconsented - Where did the dash patches come from? |
|
I swear there were docs on the AAMP repo, but anyway, they want patches for libdash for whatever reason |
|
I pushed a commit to remove the test output from bazel (fixes |
|
Ah yeah good catch, thanks! |
pulasthibandara
left a comment
There was a problem hiding this comment.
Too large to review and commit the effort as this is AI generated. I will get Claude to review it with the things that I'd be concerned about in a large scale bazel package.
Would you mind including Plan/Spec files Claude may have generated that I can feed into the review agent?
|
https://gist.github.com/disconsented/1b1491024daeeedfcd513b1a5137719e this should cover it |
pulasthibandara
left a comment
There was a problem hiding this comment.
Thanks for the analysis. Read through it but still hard to form an openion on the quality of the implementation. But I don't want to block the reset of your work on this review either. As once the AAMP binary is built the rest of the pipeline could be reasoned in isolation.
Just one last request:
Since this is a public repo lets clearly mention that:
Note
This is a Fork of AAMP with bazel build tooling as an experiment, until AAMP upstrams some of the changes needed for the integration. And the fact that this is not ready for production use
Public fork of rdkcentral/aamp carrying the Bazel build tooling; call that out at the top of README and XIONE-BUILD so readers do not mistake it for upstream or for something production ready.
|
51391bd should cover it |
build(bazel): hermetic XiOne cross-build alongside the CMake build
Adds a Bazel module that drives this repo's own CMake build to cross-compile AAMP for the Sky XiOne (armv7 NEON hard-float, glibc 2.35, new C++11 string ABI), and publishes the result as a GitHub Release asset. Downstream consumers pin the tarball by URL + sha256 instead of reproducing an armv7 cross-build.
CMake stays the interface RDK maintains and the only supported way to build AAMP for a host. Bazel exists here purely so CI can produce release artifacts reproducibly with no device and no developer machine in the loop.
What changed
cmake/xione-armhf.cmake,cmake/FindEthanLog.cmakefeat/xione-build-toolingCMakeLists.txtCMAKE_MODULE_PATHappend sofind_packagesees the abovescripts/libdash-patches/MODULE.bazel,BUILD.bazel,bazel/,third_party/tools/release/stage_artifacts.sh.github/workflows/xione-{ci,release}.ymlNothing about a normal CMake build changes.
//:aampistarget_compatible_with //bazel/constraints:xione-stb, so wildcards skip it and it only builds under--platforms=//bazel/platforms:xione.Why here, rather than in the consumer repo
This started life in
rillanetwork/webrtc-sim. Moving it here://...by a board constraint and therefore never built in CI — the same constraint that kept wildcards green kept it uncompiled. Here it is the repo's own primary target and runs on every relevant PR.rules_foreign_cclinker-flag workaround now lives incmake/xione-armhf.cmake, next to the target needing it..debs and a WebKitGTK.debit compiles nothing against.Design decisions
Drive the existing CMake build via
rules_foreign_cc; do not rewrite it incc_library. AAMP is large and upstream-tracking, and its CMake build (withCMAKE_INBUILT_AAMP_DEPENDENCIES=ONpullingmiddleware/,support/aampabr,support/aampmetrics,tsb/) is the interface RDK maintains. A Bazel re-expression would diverge and rot on every upstream merge.No device in the build.
scripts/build-xione-sysroot.shassembles its sysroot partly by harvesting ABI-exact.sos from a live XiOne over SSH — not CI-able and not reproducible.//third_party/xione_sysrootreplaces it with 36 sha256-pinned Debian armhf.debs for headers/.pc/link stand-ins, ethanlog and libdash built from pinned source, and JSC reduced to a generated link stub.Ship all nine libraries, not two. On this branch the middleware is built as separate shared libraries and
libaamp.socarries aDT_NEEDEDon five of them (libplayergstinterface,libplayerfbinterface,libplayerlogmanager,libbaseconversion,libmetrics). The device's stock AAMP predates that split and does not provide them, so the two-library bundle that worked on thexione-releaseline produces alibaamp.sothat cannot load. This was caught on-device.Release staging is load-bearing, not cosmetic.
rules_foreign_ccbakes the build sandbox path intoDT_RPATH; left unpatched, a bundledlibaampjsbindings.sosilently loads the stock/usr/lib/libaamp.soand defeats the whole swap — no error, just the wrong code running. Staging runspatchelf --set-rpath '$ORIGIN'on every.soso published artifacts are correct by construction. Stripping uses the crossstrip; host binutils cannot read ELF32 ARM at all.Port findings (sprint line vs
xione-release)Each of these is commented at the point of fix:
JSC_INCDIRmust be pre-seeded.find_pathcannot resolve it underCMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLYonce pkg-config has returned an already-sysroot-absolute hint (it gets re-rooted and doubled). The resultingJSC_INCDIR-NOTFOUNDreachesinclude_directories()and kills CMake's generate step viaaamp-cli/gstTestHarness— targets we never build, but generate covers every target in the project.goption.cdeclares a variable namedbool— valid C17, a syntax error under the C23 default of gcc ≥ 15, so it fails on any current distro.PKG_CONFIG_SYSROOT_DIR/PKG_CONFIG_LIBDIRpin the host binary to the sysroot, so its own search defaults never participate.CMAKE_CXX_STANDARD 17; the ported tooling came from a C++14 branch.Hermeticity: the fetch phase uses no host tools
Added after the initial review. The fetch phase started out shelling out in three places, and none of them was portable or necessary:
nm—jsc_debran the hostnmover an ELF32 ARM.soto derive the JSC link-stub symbol list, which only works where binutils was built with the ARM target in BFD (hoststripfails outright on the same files). On a leaner CI image the fetch broke, which is a baffling place to fail. The symbol list is now derived in a build action with the crossnmfrom the Bootlin toolchain we already fetch, and fails the build on an empty list rather than producing a stub that exports nothing.ar— turned out to be unnecessary rather than replaceable: Bazel's own extractor understands thearcontainer a.debis, soctx.extractunwraps both layers.lnandsed— the sysroot's relative symlinks are nowctx.actions.declare_symlinkartifacts staged withcp -a, and the ld-script relocation (GROUP ( /lib/libc.so.6 … )→=-prefixed, soldprepends whichever--sysrootis active) happens in Starlark at fetch time viapath.readdir()+ctx.read/ctx.file.sed -iwith no argument is GNU-only, and repository rules always run on the local host even under--config=rbe, so the old form failed on a macOS client.grep -rn 'ctx.which\|ctx.execute' bazel/is empty, andsed,findandgrepappear there only inside comments. Verified with a positive control rather than a green build: with hostar/nm/lnshimmed toexit 127, the previous revision fails and this one succeeds. Two traps make that test easy to get wrong —--action_env=PATHdoes not reach arun_shellaction (it gets an empty env, so PATH is bash's built-in default), and a shim under/tmpis invisible inside the sandbox's fresh tmpfs. Both make a hermeticity test silently pass.bazel build //...no longer fetches the cross toolchain. Both targets in//third_party/bootlinnamed a label inside the fetched repo, so wildcard expansion analysed them and pulled a ~500 MB Linux-x86_64 archive on every host — including macOS, where nothing can execute it, and where it was the only thing dragging a wildcard build into the fetch phase's shell. Both are nowmanual;register_toolchainsresolves by label and is unaffected.The remaining host dependencies are deliberate and recorded: host
pkg-configandmake(see Port findings), hostpatchfor the libdash patches (Bazel's built-in patch rejects their fuzz), hostpatchelf/binutils on the release path only, and the ~14 coreutils insiderules_foreign_cc's generated script. One of those is irreducible: the script stages its tool binaries withln -sf, and CMake locatesCMAKE_ROOTby resolvingargv[0], so a copiedcmakedies with "Could not find CMAKE_ROOT". That is the ceiling while CMake drives the compile.How it was verified
CI: green on every commit. (
Fossid Stateless Diff Scanwas red on this branch from the first commit — it calls an rdkcentral reusable workflow needing FOSSID credentials that cannot exist on a fork, so it failed template validation before any step ran. Guarded to upstream-only in #5, now merged, so it reports as skipped.)xione-ci.ymlasserts what actually breaks on a device rather than just a zero exit — every library ELF32/EM_ARM/hard-float, the library count, and a self-contained AAMP-internalDT_NEEDEDclosure — and runs the staging script so an RPATH or packaging regression surfaces in CI, not at release time.Clean build:
bazel clean --expungethen a full rebuild refetches all five repos (36 debs, Bootlin toolchain, libdash, JSC, ethanlog) and recompiles all 156 objects, producing nine libraries identical to the previously device-verified ones in size, dynamic symbol set andDT_NEEDED. Release staging runs from that state too, which is what caught the one regression the hermeticity work introduced:stage_artifacts.shresolved the crossstripthrough a hardcodedexternal/+http_archive+…path that the repo-rule rename invalidated, and it kept resolving on every machine whose Bazel cache still held the old directory — CI's staging step passed on the very commit that broke it. Fixed by matching the directory by suffix.On hardware (XiOne): verified twice, on
de5abcd8fa23(2026-07-27) and again on this branch's head asxione-build-d94dd8862dbb. Both times ripa'sWPEWebProcessloaded all nine libraries from/package/usr/libwith hashes matching the published tarball byte-for-byte,viper_iparan different hashes (isolation), and a tune played:state:"PLAYING"with position advancing in real time (28→53s of content over 25s wall on the latest run), fragment 200s offtest-streams.mux.dev, ABR climbing to the top profile (bitrate:6221600, 1920×1080 fps 25) through transientcurl error 18rampdowns without ever leaving PLAYING.Worth being precise about what the second run tests: the libraries are the same code as the first, so it does not re-validate the player. It validates the delivery path after the hermeticity work — the repo-rule fetch, the Starlark ld-script rewrite, the derived
XIONE_TOOLCHAIN_DIR, the new staging strip resolution and the.wgtpackaging — from a cold build through to bytes running in the container. That was worth doing: it is how the staging regression above surfaced.Building it
Linux x86_64 only — the Bootlin cross-compiler ships x86_64 host binaries, so
exec_compatible_withpins linux+x86_64. The bare command without--platformsfails at analysis by design; seeXIONE-BUILD.md.Releasing: tag
xione-v<semver>for a release, orxione-build-<shortsha>for a commit build published as a prerelease (for pinning a commit before anything is blessed as a version). Tag-push triggers rather thanworkflow_dispatch, because dispatch only works once the workflow is on the default branch.Limitations
__FILE__, so the absolute sandbox path is compiled in, and that path carries Bazel's per-action sandbox ordinal (processwrapper-sandbox/9vs/10) — which run, not which commit. When the ordinal changes digit count the string length shifts.dynstr/.rodataoffsets and cascades into every relocated address in.text. Measured between two artifacts whose compiled sources are identical: 7 of 9 libraries differ in bytes (~9% oflibaamp.so) while every library matches on size, dynamic symbol set andDT_NEEDED. The host path prefix (/home/runnervs a developer's home) does the same thing, just more of it. Two consequences: pin the sha256 from the published release, and judge "same code?" by size + symbol set +DT_NEEDED, never by comparing tarball hashes.-ffile-prefix-mapwould fix it; deliberately not done here, to avoid perturbing a verified build..debs are fetched fromsnapshot.debian.orgon a cold build. That host is slow and rate-limits;actions/cacheover~/.cache/bazelcovers the warm path, but mirroring the pinned debs to release assets would remove the reliability risk entirely. Not done yet.rules_foreign_cccaches the whole CMake invocation as one action, so any source change triggers a full AAMP rebuild (~20s warm, minutes cold with fetches).exec_compatible_withpins linux+x86_64 and asking for the cross build on macOS is a clean toolchain-resolution failure. What macOS does get right now is everything else:bazel build //...there fetches nothing XiOne-related, analyses nothing incompatible, and runs no shell from the fetch phase.Cross-dependencies
Consumed by
rillanetwork/webrtc-sim#1736(rdk/aamp-prebuilt), which pins the release tarball and packages it into the ripa.wgt. That PR is where the on-device verification above was performed.🤖 Generated with Claude Code