0029 — meson cross-compile toolchain¶
Status: IMPLEMENTED (Phase 4 of the implementation roadmap.
A build-system capability spec, born from a spike during 0019: a
second cross-compile toolchain — meson — in ffmpeg-wasi's build/deps.sh beside the existing
autotools/custom-configure path, so meson-only libraries cross-compile to wasm32-wasi. Delivered
as write_meson_cross + build_harfbuzz + build_fribidi (image gains meson/ninja/xz-utils);
its first consumer is 0019's burn-in (harfbuzz + fribidi → libass). Gotcha resolved: -DHB_NO_MT
for harfbuzz (meson pulls the threads dep → clang emits wasm atomics wazero rejects) and stripping
-pthread from the generated .pc files.)
Date: 2026-07-04
Parent: 0018 (the deps.sh external-lib cross-compile pattern);
0019 (the first consumer — burn-in fonts/shaping); 0022
Owns: R-BUILD-MESON — meson cross-compilation of external libs to wasm32-wasi.
1. Why¶
build/deps.sh cross-compiles every external library with autotools (./configure) or a
library's own configure (x264, libvpx). That covered every lib through 0018.
0019 (text & subtitles) is the first to need libraries that only
ship a meson build: harfbuzz and fribidi. And it is not optional — FFmpeg n8.1.2 raised
the bar:
drawtext_filter_deps="libfreetype libharfbuzz"— thedrawtextfilter now requires harfbuzz (not just freetype). Without libharfbuzz, configure silently soft-disablesdrawtext(the same class of quiet soft-disable as libvpx's encoders in 0018).- libass requires fribidi (a mandatory
PKG_CHECK_MODULES—--disable-fribidiis a no-op), so thesubtitles/assburn-in filters need it too.
harfbuzz and fribidi dropped autotools; their release tarballs carry only meson.build. So the
engine needs a meson cross-compile capability to ship any subtitle/text burn-in at all. This
spec adds it once, generically, so future meson-only deps (e.g. dav1d for 0023)
reuse it.
2. Scope¶
In: a reusable meson cross-compile path in build/deps.sh and its image prerequisites —
- image packages: meson, ninja-build, xz-utils (harfbuzz/fribidi ship .tar.xz);
- a generated meson cross-file for wasm32-wasi derived from toolchain.sh's $CFLAGS;
- the first two consumers: build_harfbuzz (C++) and build_fribidi (C), static, into $PREFIX.
Out (referenced, not owned): the autotools/own-configure paths (unchanged, still used for freetype/libass/the 0018 libs); which libs a profile bundles (0022); the burn-in filters + libass wiring themselves (0019 — this spec only supplies the toolchain they stand on).
3. Approach / design — the meson cross-file¶
fetch_tarball already handles .tar.xz (it uses tar xf, which auto-detects compression). A
write_meson_cross helper emits /tmp/wasi-cross.txt from the exported toolchain environment:
[binaries]
c = 'clang'
cpp = 'clang++'
ar = 'llvm-ar'
strip = 'llvm-strip'
pkg-config = 'pkg-config'
[host_machine]
system = 'wasi'
cpu_family = 'wasm32'
cpu = 'wasm32'
endian = 'little'
[properties]
needs_exe_wrapper = true # cross: never run the wasm output during the build
[built-in options]
c_args = [ <each word of $CFLAGS, single-quoted> ]
c_link_args = [ '--target=wasm32-wasip1', '--sysroot=…', <WASI_EMULATED_LIBS> ]
cpp_args / cpp_link_args = <same>
The load-bearing pieces: needs_exe_wrapper = true (meson must not try to run the wasm binaries
its sanity check builds — cross mode assumes success on compile+link); system = wasi +
cpu_family = wasm32; and threading the exact $CFLAGS (the --target, --sysroot, the WASM
feature flags, the SjLj lowering, the -I$PREFIX/include and -D_WASI_EMULATED_*) into
c_args/cpp_args so meson compiles identically to the rest of the build. A lib is then:
meson setup build --cross-file /tmp/wasi-cross.txt --prefix="$PREFIX" \
--default-library=static --buildtype=minsize <-Dfeature toggles>
ninja -C build install
harfbuzz needs -Dfreetype=enabled (built first, autotools) and everything else off
(glib/gobject/cairo/icu/tests/docs/utilities); it is C++, so the final engine link already
pulls -lc++/-lc++abi. fribidi needs only -Ddocs=false -Dtests=false -Dbin=false.
4. Build impact¶
build/Dockerfile:meson ninja-build xz-utilsadded to the apt layer.build/deps.sh:write_meson_cross+build_harfbuzz+build_fribidi;fetch_tarballusestar xf(xz-aware). All gated to the intermediate profile (they exist for 0019's burn-in).build/driver.sh:dep_rankorders the new archives (consumers before providers:ass<harfbuzz< thefreetype/fribidileaves), since wasm-ld has no--start-group.- No afmpeg / job-spec impact — this is purely a build capability.
5. Licensing & size¶
harfbuzz is MIT, fribidi LGPL-2.1 — both LGPL-compatible, so they ride the default
variant with no GPL trigger. Size: harfbuzz is the heavyweight of the pair (~3 MB static, a
shaping engine); fribidi is small. Both land in intermediate, not lean (0022).
Build-time cost: harfbuzz compiles slowly under -Oz (~5–8 min), lengthening CI — an accepted
cost, isolated to the cached deps layer.
6. Decisions & open questions¶
- D-0029-A — add meson as a second toolchain, don't migrate. The autotools/own-configure path stays for every lib that has one; meson is used only where a lib is meson-only.
- D-0029-B — derive the cross-file from
$CFLAGS, don't hand-maintain it. One source of truth for the wasm target flags; atoolchain.shchange flows into meson automatically. - D-0029-C —
needs_exe_wrapper = true. The sandbox can't run wasm during the build; meson's cross mode must assume compile+link success (which is what the rest ofdeps.shrelies on too). - Open: whether to pin meson/ninja versions (currently the image's apt versions); whether future
meson deps (dav1d for 0023) want a shared
build_meson_libhelper vs per-lib functions.
7. Requirements¶
- R-BUILD-MESON —
build/deps.shcan cross-compile a meson-based C or C++ library towasm32-wasi(static, asm-free, single-threaded, into$PREFIXwith a usable.pc), via a cross-file generated from the toolchain, with no impact on the autotools path.
8. Validation¶
Spike (2026-07-04, throwaway wasi-sdk container): fribidi (C, 139 KB), freetype (autotools,
725 KB), and harfbuzz (C++, 3.1 MB) all cross-compiled and installed with working .pc files.
The end-to-end proof is 0019's burn-in: the built module runs the
drawtext and subtitles filters against a mounted font, exercised by afmpeg's integration tests.
9. Definition of done¶
The meson cross-file recipe + write_meson_cross/build_harfbuzz/build_fribidi are in
build/deps.sh, the image carries meson/ninja/xz, the four build combos build, and the capability
is proven by 0019's burn-in filters working end-to-end. The decisions (D-0029-A…C) are recorded;
the open questions (version pinning, a shared meson helper) are deferred to the next meson consumer.