Skip to content

Commit f6ee901

Browse files
kixelatedclaude
andcommitted
Link system libva on Linux (fix undefined va* symbols when linked into a binary)
0.0.1's "dlopen" only skipped the link step, but the bindings are static extern calls with no runtime loader, so any executable that pulls this crate (e.g. moq-video's test binary) failed to link with undefined vaCreateBuffer/vaPutImage/ ... symbols. An rlib tolerated it; a binary does not. Resolve the symbols by linking the system libva / libva-drm via pkg-config, gated to Linux (the only platform with VA-API). Bindings are still generated from the pinned vendored headers, so the binding ABI can't drift; VA-API's ABI is stable so a newer system libva links fine. On non-Linux hosts the crate only builds as an rlib (symbols left for the final binary), so it stays compilable for dev. A vaapi-enabled binary now requires libva.so at runtime — acceptable since the feature targets VA-API hardware, which has libva. Updated README/Cargo.toml to drop the (never-implemented) dlopen claim. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d60e6e2 commit f6ee901

3 files changed

Lines changed: 39 additions & 20 deletions

File tree

Cargo.toml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "moq-vaapi"
3-
version = "0.0.1"
3+
version = "0.0.2"
44
description = "(AI GENERATED) VA-API H.264 hardware encoder, derived from discord/cros-libva + discord/cros-codecs"
55
authors = ["The ChromiumOS Authors", "Discord", "Luke Curley <kixelated@gmail.com>"]
66
repository = "https://github.com/moq-dev/vaapi"
@@ -14,12 +14,12 @@ rust-version = "1.85"
1414
# Dev-only tooling; not needed to build the crate from the published tarball.
1515
exclude = ["flake.nix", "flake.lock", "justfile", "deny.toml", "rust-toolchain.toml", ".rustfmt.toml"]
1616

17-
# The build is hermetic: bindgen reads the vendored libva headers checked into
18-
# `libva/` (refreshed by `just vendor`, pinned to a known VA-API version so a
19-
# system libva bump can't drift the bindings), and libva is dlopen'd at runtime
20-
# rather than linked (so a built binary links on a libva-less host and starts on
21-
# machines without libva). There is no system-libva build path, so there are no
22-
# features to toggle: the build needs only libclang (for bindgen) on any OS.
17+
# bindgen reads the vendored libva headers checked into `libva/` (refreshed by
18+
# `just vendor`, pinned to a known VA-API version so a system libva bump can't
19+
# drift the bindings); libva itself is linked on Linux via pkg-config (VA-API's
20+
# ABI is stable, so a newer system libva links fine against the pinned bindings).
21+
# The rlib build needs only libclang + the vendored headers, so it compiles on any
22+
# OS (e.g. macOS) for development; the libva link only happens on Linux.
2323

2424
[dependencies]
2525
anyhow = "1"
@@ -29,4 +29,5 @@ log = { version = "0", features = ["release_max_level_debug"] }
2929

3030
[build-dependencies]
3131
bindgen = "0.70.1"
32+
pkg-config = "0.3.31"
3233
regex = "1.11.1"

README.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,16 @@ pulled in as a git dependency.
2121

2222
- **H.264 encode** over VA-API: tightly-packed NV12 in, an Annex-B elementary
2323
stream out (packed SPS/PPS + slice headers, low-latency IPPP, rate control).
24-
- **Hermetic build.** libva's headers are vendored into [`libva/`](./libva) (see
25-
`just vendor`) and fed to bindgen, so the build needs **no system libva-dev**
26-
only `libclang`. The header version is pinned, so a system libva bump can't
27-
drift the generated bindings.
28-
- **Runtime `dlopen`.** libva is loaded at runtime rather than linked, so a built
29-
binary links on a libva-less builder and starts on machines without libva
30-
(callers fall back to a software encoder).
24+
- **Pinned, vendored headers.** libva's headers are vendored into
25+
[`libva/`](./libva) (see `just vendor`) and fed to bindgen, so generating the
26+
bindings needs **no system libva-dev** — only `libclang` — and the pinned
27+
version means a system libva bump can't drift the bindings.
28+
- **Links system libva on Linux.** The generated `va*` symbols are resolved by
29+
linking the system `libva` / `libva-drm` (via pkg-config), so a `vaapi`-enabled
30+
binary requires `libva.so` present at runtime — fine for a build that targets
31+
VA-API hardware. VA-API's ABI is stable, so a newer system libva links against
32+
the pinned bindings. (On non-Linux hosts the crate only builds as an rlib, which
33+
leaves the symbols for the final binary, so it stays compilable for development.)
3134

3235
## Layout
3336

@@ -47,8 +50,8 @@ nix develop --command just ci # check + cargo-deny
4750
nix develop --command just vendor # refresh libva/ headers from upstream
4851
```
4952

50-
The crate compiles on any OS (macOS included) because the build is header-only +
51-
dlopen; runtime use is Linux-only.
53+
The crate compiles on any OS (macOS included) the rlib build is header-only and
54+
defers the libva link to the final binary, which is Linux-only.
5255

5356
## Licensing
5457

build.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ fn main() {
6666
// libclang and the vendored headers, both available on docs.rs — so always generate.
6767
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("`OUT_DIR` is not set"));
6868

69-
// Always build against the vendored libva headers (checked into `libva/`,
70-
// pinned to a known VA-API version) and dlopen libva at runtime rather than
71-
// link it. There is no system-libva path, so the build needs only libclang for
72-
// bindgen and works on any OS.
69+
// Bindings are generated from the vendored libva headers (checked into `libva/`,
70+
// pinned to a known VA-API version so a system libva bump can't drift them).
71+
// libva itself is linked on Linux (see below). The rlib compile needs only
72+
// libclang + the vendored headers, so it builds on any OS for development.
7373
let (major, minor) = generate_vendored_version_header(&out_dir);
7474
println!("cargo:rerun-if-changed=libva/meson.build");
7575
println!("cargo:rerun-if-changed=libva/va/va_version.h.in");
@@ -122,6 +122,21 @@ fn main() {
122122
println!("cargo::rustc-cfg=libva_1_10_or_higher");
123123
}
124124

125+
// Bindings are generated from the vendored headers (pinned, drift-proof), but
126+
// the va* symbols still have to resolve, so link the system libva on Linux (the
127+
// only platform with VA-API). VA-API's ABI is stable, so a system libva newer
128+
// than the vendored headers links fine. On other hosts (e.g. macOS) this crate
129+
// only ever builds as an rlib, which leaves the symbols for the final binary, so
130+
// skip linking and keep it compilable for development.
131+
if env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("linux") {
132+
pkg_config::Config::new()
133+
.probe("libva")
134+
.expect("libva not found via pkg-config (install libva / libva-dev)");
135+
pkg_config::Config::new()
136+
.probe("libva-drm")
137+
.expect("libva-drm not found via pkg-config (install libva / libva-dev)");
138+
}
139+
125140
let bindings = vaapi_gen_builder(bindgen::builder())
126141
.header(WRAPPER_PATH)
127142
.clang_arg(format!("-I{}", va_h_path.display()))

0 commit comments

Comments
 (0)