Bazel can build bpf-linker and its LLVM dependency in a single graph without requiring a separately installed LLVM. It uses the pinned Rust toolchain and builds the pinned LLVM revision from source:
bazel build //:bpf-linker --config=releaseThe resulting executable is available under bazel-bin/.
To build release archives for every supported platform, run:
bazel build //:release-archives --config=releasebpf-linker is a bitcode linker that uses libLLVM to link bitcode inputs. That means the LLVM version used by bpf-linker must match the LLVM version used by the Rust toolchain you intend to use.
There are several recommended ways of obtaining an appropriate LLVM.
We regularly build LLVM in CI and publish the binary artifacts on ghcr.io. They can be retrieved using oras.
First, pick an appropriate image from our LLVM container page. The tags mention the LLVM version, the platform, and our custom revision, e.g.
23-x86_64-unknown-linux-gnu-4- LLVM 23, x86_64 Linux, glibc, revision 421-aarch64-unknown-linux-musl-4- LLVM 21, aarch64 Linux, musl, revision 422-aarch64-apple-darwin-4- LLVM 22, aarch64 macOS, revision 4
Always pick the latest revision available, if there are multiple.
After picking an appropriate image, it can be downloaded with oras, e.g.
oras pull ghcr.io/aya-rs/llvm:23-x86_64-unknown-linux-gnu-4And the resulting tarball unpacked to a directory:
mkdir llvm
tar --zstd -xpf llvm-archive-x86_64-unknown-linux-gnu.tar.zst \
-C llvm/The repository's Bazel graph builds the pinned LLVM revision from source and can package its outputs in the same layout as the ghcr.io artifact. It's recommended to disable ThinLTO to avoid linker errors if your system linker uses a different LLVM version than the one you're building.
bazel build //:llvm-archive --config=release --features=-thin_lto
mkdir llvm
tar --zstd -xpf bazel-bin/llvm-archive.tar.zst -C llvm/The archive contains FileCheck, the shared LLVM library, and its static component libraries. It is intended for building and debugging bpf-linker, rather than as a complete LLVM developer installation.
If you prefer to build LLVM the way it is built upstream, check out the appropriate branch from rust-lang/llvm-project for the LLVM version used by your Rust toolchain, then configure and install it with CMake:
cmake -S llvm-project/llvm -B llvm-build -G Ninja \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX="$PWD/llvm" \
-DCMAKE_INSTALL_LIBDIR=lib \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DLLVM_BUILD_LLVM_DYLIB=ON \
-DLLVM_ENABLE_ASSERTIONS=ON \
-DLLVM_ENABLE_PROJECTS= \
-DLLVM_ENABLE_RUNTIMES= \
-DLLVM_INSTALL_UTILS=ON \
-DLLVM_LINK_LLVM_DYLIB=ON \
-DLLVM_TARGETS_TO_BUILD=BPF \
-DLLVM_USE_LINKER=lld
cmake --build llvm-build --target install
export LLVM_PREFIX="$PWD/llvm"On Debian-based distributions, you can install the llvm-<version>-dev and
libclang-<version>-dev packages from the official LLVM repository at
https://apt.llvm.org.
Different operating systems and Linux distributions might provide their own LLVM packages. If you're interested in packaging bpf-linker, you may also need to ensure that the correct LLVM version is packaged for that environment.
bpf-linker uses Cargo features to select the LLVM version, via llvm-*
features such as llvm-23. By default, LLVM and its dependencies are linked
dynamically. Static linking can be enabled with the llvm-link-static feature.
If you are using either a prebuilt LLVM archive from ghcr.io or one built
locally with Bazel, set LLVM_PREFIX to the directory where you extracted the
archive. When linking statically, also configure the names and locations of
the Bazel-built libraries:
export LLVM_PREFIX="$PWD/llvm"
export PATH="$LLVM_PREFIX/bin:$PATH"
export CXXSTDLIB=libcxx,libcxxabi
export CXXSTDLIB_PATH="$PWD/llvm/lib"
export ZLIB_PATH="$PWD/llvm/lib"
export LIBZSTD_PATH="$PWD/llvm/lib"If LLVM_PREFIX is not set, build.rs searches PATH for llvm-config and
uses its location to find LLVM libraries installed under a versioned prefix,
as is common with Linux distribution packages. For example, if LLVM is
installed under /usr/lib/llvm/<VERSION> and its bin/llvm-config is exposed
through PATH, build.rs searches /usr/lib/llvm/<VERSION>/lib64 and
/usr/lib/llvm/<VERSION>/lib for the libraries.
Examples:
# Dynamic linking
cargo build --no-default-features --features llvm-23
cargo install bpf-linker --no-default-features --features llvm-23
cargo install --path . --no-default-features --features llvm-23
# Static linking
cargo build --no-default-features --features llvm-23,llvm-link-static
cargo install bpf-linker --no-default-features --features llvm-23,llvm-link-static
cargo install --path . --no-default-features --features llvm-23,llvm-link-static
bpf-linker comes with compiletests, similar to the ones in Rust and LLVM, that compile the code to LLVM IR (or BTF) and assert the output matches the expected IR.
Use cargo test with same arguments as used for build, e.g.:
cargo +nightly test --no-default-features --features llvm-23
BPF targets are Tier 3 in Rust and therefore rustup does not provide BPF targets in stable editions of Rust. There are two ways to overcome that.
Build the BPF sysroot with:
RUSTC_SRC="$(rustc --print sysroot)/lib/rustlib/src/rust/library"
BPFEL_SYSROOT_DIR="$(pwd)/bpf-sysroot"
RUSTC_BOOTSTRAP=1 cargo xtask build-std \
--rustc-src "$RUSTC_SRC" \
--sysroot-dir "$BPFEL_SYSROOT_DIR" \
--target bpfel-unknown-none
Then point the tests to the sysroot using the BPFEL_SYSROOT_DIR variable:
BPFEL_SYSROOT_DIR="$(pwd)/bpf-sysroot" \
cargo test --no-default-features --features llvm-23
It's done by the tests automatically when BPFEL_SYSROOT_DIR is not defined,
but in case of Rust stable it requires RUSTC_BOOTSTRAP=1:
RUSTC_BOOTSTRAP=1 cargo test --no-default-features --features llvm-23