Skip to content

Build multi-arch image (amd64 + arm64) on an Ubuntu base#2

Merged
nikicat merged 8 commits into
masterfrom
ci/multiarch-image
Jun 23, 2026
Merged

Build multi-arch image (amd64 + arm64) on an Ubuntu base#2
nikicat merged 8 commits into
masterfrom
ci/multiarch-image

Conversation

@nikicat

@nikicat nikicat commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Adds arm64 alongside amd64 and moves the container base from Arch to Ubuntu.

Why Ubuntu

  • Official Arch is x86_64-only; the community Arch-Linux-ARM images are stale (menci/archlinuxarm last built 2024-12, agners/archlinuxarm 2021) and would hit pacman keyring rot.
  • Ubuntu's official image is glibc + multi-arch + continuously rebuilt — one base for both arches.
  • glibc keeps the WebRTC/tgcalls compile low-risk; Alpine/musl would likely need source patches (execinfo/backtrace, glibc guards).

CI shape

  • 2-leg native matrix: amd64 on ubuntu-24.04, arm64 on the free public ubuntu-24.04-arm runner (emulation is not viable for a from-source WebRTC build).
  • PR/manual: build-only, --cache-from. master: build + --cache-to + push per-arch :amd64/:arm64 tags, then a manifest job combines them into :latest (for image and tools image).
  • Per-arch registry cache (buildcache-amd64/-arm64); layers aren't shareable across arches.

Containerfile port

  • Arch pacman → Ubuntu apt (build deps + the hardcoded /usr/include/opus and /usr/include/lame paths match Debian).
  • libyuv isn't packaged on Ubuntu → built from the existing vendor/libyuv submodule as a static PIC lib (verified target yuvlibyuv.a; the project finds it via find_library(yuv) and uses the vendored headers).
  • Runtime stage copies the binary's ldd shared-lib closure (minus glibc core) instead of hand-listing version-suffixed Debian runtime packages — minimal and arch-agnostic. ca-certificates added for TLS to Telegram.

Makefile

  • New BASE_IMAGE (default ubuntu:24.04) and TAG (default latest) knobs so CI builds/pushes per-arch tags via make image/make push; local make image behavior is unchanged.

Notes / risk

  • The arm64 leg is the first native aarch64 build of this stack — this PR run is its first real test. WebRTC supports arm64 (it targets Android arm64), so the compile is expected to work, but CI is the proof.
  • Local dev instructions in the README stay Arch-based; only the container image changed.

🤖 Generated with Claude Code

nikicat and others added 8 commits June 23, 2026 13:23
Adds arm64 alongside amd64. Emulation is not viable for a from-source
WebRTC build, so each arch builds natively: amd64 on ubuntu-24.04, arm64
on the ubuntu-24.04-arm hosted runner. A manifest job stitches the per-arch
tags into a multi-arch :latest for both the image and the tools image.

Switches the container base from Arch to Ubuntu. Official Arch is
x86_64-only and the community Arch-Linux-ARM images are stale (keyring rot
on pacman -Syu), whereas Ubuntu's official image is glibc + multi-arch and
continuously rebuilt. glibc keeps the WebRTC/tgcalls compile low-risk
(Alpine/musl would need source patching). The cmake build steps are
unchanged; only the package layer and base differ.

libyuv isn't packaged on Ubuntu, so it's built from the existing vendored
submodule as a static PIC lib (the project finds it via find_library(yuv)
and uses the vendored headers). The runtime stage copies the binary's
shared-library closure via ldd instead of hand-listing version-suffixed
Debian runtime packages, keeping the image minimal and arch-agnostic.

The Makefile gains BASE_IMAGE and TAG knobs so CI builds/pushes per-arch
tags through `make image`/`make push`; local `make image` is unchanged
(defaults to ubuntu:24.04, :latest).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The aarch64 leg failed with `unrecognized command-line option '-mavx'`:
the vendored Build*.cmake files select arch-specific sources, SIMD flags,
and defines off ANDROID_ABI, but CMakeLists.txt hardcoded it to "x86_64".
The WebRTC/openh264 cmake already have full arm64-v8a branches (NEON
sources, WEBRTC_ARCH_ARM64, HAVE_NEON_AARCH64), so mapping ANDROID_ABI to
the real target arch clears the -mavx/-mavx2/-mfma injection automatically.

Remaining un-gated x86 assumptions in CMakeLists.txt, now per-arch:
- crc32c: the stub crc32c_config.h sets HAVE_SSE42 on x86_64 and
  HAVE_ARM64_CRC32C on aarch64, so compile the matching hardware impl
  (crc32c_sse42.cc with -msse4.2 / crc32c_arm64.cc with
  -march=armv8-a+crc+crypto) or the dispatcher hits an undefined reference.
- HAVE_SSE2 → WEBRTC_ARCH_ARM64 + WEBRTC_HAS_NEON on arm64.

Also bumps actions/checkout v4 → v5 to clear the Node 20 deprecation
warning (v4 runs on the now-deprecated Node 20; v5 uses Node 24).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WebRTC includes ffmpeg as "third_party/ffmpeg/libav{codec,format,util}/...",
resolved through the vendored vendor/third_party/ffmpeg symlink that targets
/usr/include (Arch's flat header layout). Debian/Ubuntu keep these headers
under a multiarch triplet dir and split libavformat into its own -dev package,
so the amd64 build failed with:

  fatal error: third_party/ffmpeg/libavcodec/avcodec.h: No such file or directory

Add libavformat-dev (libavcodec/libavutil were already present) and repoint the
ffmpeg symlink to /usr/include/<triplet> inside the build. avformat is only
included for types, not linked, so the runtime closure is unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On aarch64 the stub crc32c_config.h enables HAVE_ARM64_CRC32C, which makes
crc32c_arm64_check.h compile `#if defined(__linux__) && (HAVE_STRONG_GETAUXVAL
|| HAVE_WEAK_GETAUXVAL)`. HAVE_WEAK_GETAUXVAL was defined empty, so the arm64
build failed with "operator '||' has no right operand". Give it a value (0;
glibc provides the strong getauxval). x86 was unaffected since the whole block
is HAVE_ARM64_CRC32C-gated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The arm64 link failed with undefined references to openh264's aarch64 NEON
routines (WelsDctT4_AArch64_neon, SampleVariance16x16_AArch64_neon, ...).
HAVE_NEON_AARCH64 makes the C++ call them, and BuildOpenH264.cmake adds the
matching .S sources, but project() only enabled C/CXX, so CMake silently
skipped every .S file and their symbols never entered the archive.

Add ASM to the project languages. x86 is unaffected (openh264 has no x86
assembly branch; WebRTC's arm64 NEON paths are .c/.cc, already compiled).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openh264 does ship x86 SIMD, but as NASM/YASM .asm files this build
doesn't wire up; x86_64 uses the C++ fallbacks. The old comment claimed
there were no x86 assembly sources at all.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With ASM enabled, openh264's aarch64 .S files failed to assemble:
  bits/stdint-least.h: Error: unknown mnemonic `typedef'
target_compile_options applies to every language, so the GCC-compat
"-include stdint.h/stddef.h" force-includes were prepended to the assembler
input, feeding C typedefs to the assembler. Gate them to C/CXX via a
COMPILE_LANGUAGE generator expression so the .S sources assemble clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The arm64 runtime stage failed at `RUN ldconfig` with "exec /bin/sh: No such
file or directory (missing dynamic library?)". The shared-lib closure was
copied path-preserving (install -D {} /rootfs{} then COPY /rootfs/ /). On arm64
ldd reports some libs under /lib/aarch64-linux-gnu, so this recreated a real
/lib and copied it over the base image's /lib -> /usr/lib usrmerge symlink,
breaking the dynamic linker. amd64 only escaped because its libs all resolved
under /usr/lib.

Copy the closure flat (cp -L) and drop it into /usr/local/lib in the runtime
stage, then ldconfig. /usr/local/lib is already on the default loader path, so
no system lib dirs (or the /lib symlink) are touched. Works the same on both
arches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nikicat
nikicat merged commit 4788e01 into master Jun 23, 2026
3 checks passed
@nikicat
nikicat deleted the ci/multiarch-image branch June 23, 2026 13:57
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