Skip to content

Commit d135076

Browse files
committed
ci(edriver): static musl build for x86_64 and aarch64
Build the edriver BPF plugin as a fully static musl binary for both x86_64 and aarch64. Both jobs run in ubuntu:24.04 containers using musl-gcc; vendored-libelf compiles elfutils --without-zstd so the resulting libelf.a carries no zstd dependency. Key issues solved along the way: 1. elfutils ./configure (invoked by libbpf-sys build.rs with CC=musl-gcc) requires argp_parse, fts_close, and _obstack_free, which are absent from musl libc. Fix: add a 'Build musl compat libs' CI step that compiles the three void-linux musl-compat shims (identical to Alpine's argp-standalone / musl-fts-dev / musl-obstack-dev packages) with musl-gcc and installs them into musl-gcc's default sysroot search path /usr/lib/${ARCH}-linux-musl/ before cargo build runs. 2. libbpf C compilation (make -C libbpf/src) needs gelf.h at BPF- skeleton-generation time; add libelf-dev and zlib1g-dev to apt install so clang can find the header. 3. .cargo/config.toml: set CC_*_musl=musl-gcc, linker=musl-gcc, target-feature=+crt-static for both musl targets. Remove stale -lzstd link flags (vendored elfutils is --without-zstd). CI matrix: x86_64 ubuntu-latest → x86_64-unknown-linux-musl aarch64 ubuntu-24.04-arm → aarch64-unknown-linux-musl (ubuntu:24.04 container for both; Alpine ARM64 has no Node.js so JS GitHub Actions don't work there)
1 parent 6fca81b commit d135076

39 files changed

Lines changed: 5540 additions & 4094 deletions

.github/workflows/ci-edriver.yaml

Lines changed: 166 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,53 +17,203 @@ jobs:
1717
edriver:
1818
name: "Build and test edriver / ${{ matrix.arch }}"
1919
runs-on: ${{ matrix.runner }}
20+
container:
21+
image: ${{ matrix.container }}
2022
strategy:
2123
fail-fast: false
2224
matrix:
2325
include:
2426
- arch: x86_64
2527
runner: ubuntu-latest
2628
target: x86_64-unknown-linux-musl
29+
# Ubuntu + musl-gcc + vendored-libelf (--without-zstd).
30+
# elfutils ./configure needs argp/fts/obstack, which are absent from
31+
# musl libc. We pre-build them (argp-standalone, musl-fts,
32+
# musl-obstack) using musl-gcc and install to /usr/lib/x86_64-linux-musl
33+
# before running `cargo build`, so configure finds them.
34+
container: ubuntu:24.04
35+
cc: musl-gcc
36+
lib_path: ""
37+
extra_cflags: "-idirafter /usr/include -idirafter /usr/include/x86_64-linux-gnu"
38+
cargo_extra_features: ",vendored-libelf"
39+
rustflags: "-C target-feature=+crt-static"
2740
- arch: aarch64
2841
runner: ubuntu-24.04-arm
2942
target: aarch64-unknown-linux-musl
43+
# Same approach as x86_64. ubuntu:24.04 on arm64 supports JS
44+
# actions; Alpine ARM64 does not (no Node.js ARM64 in Alpine).
45+
container: ubuntu:24.04
46+
cc: musl-gcc
47+
lib_path: ""
48+
# -mno-outline-atomics: GCC 13 on aarch64 defaults to
49+
# -moutline-atomics, emitting __aarch64_ldadd4/8_acq_rel etc.
50+
# These live in libatomic, not musl libc, causing undefined-reference
51+
# link errors. Disabling outline atomics uses inline LL/SC
52+
# sequences instead, which musl supports natively.
53+
extra_cflags: "-idirafter /usr/include -idirafter /usr/include/aarch64-linux-gnu -mno-outline-atomics"
54+
cargo_extra_features: ",vendored-libelf"
55+
rustflags: "-C target-feature=+crt-static"
3056
steps:
31-
- name: "Git checkout"
57+
- name: Install build dependencies
58+
env:
59+
DEBIAN_FRONTEND: noninteractive
60+
run: |
61+
apt-get update -qq
62+
apt-get install -y --no-install-recommends \
63+
bash curl git ca-certificates \
64+
build-essential linux-headers-generic \
65+
clang llvm \
66+
libelf-dev zlib1g-dev \
67+
musl-tools musl-dev \
68+
protobuf-compiler \
69+
pkg-config \
70+
autoconf automake libtool autopoint gettext flex bison gawk
71+
72+
- name: Git checkout
3273
uses: actions/checkout@v4
3374
with:
34-
submodules: true
75+
submodules: false # libbpf submodule cloned explicitly below
76+
77+
- name: Clone libbpf submodule
78+
# The submodule is pinned to v1.1.0 (2023); a shallow clone of that
79+
# old commit is unreliable. Clone at the v1.2.2 tag instead, which
80+
# matches the intended version recorded in .gitmodules (tags = v1.2.2).
81+
run: |
82+
git clone --depth=1 --branch v1.2.2 \
83+
https://github.com/libbpf/libbpf.git plugins/libs/libbpf
3584
3685
- name: Install Rust toolchain
3786
uses: dtolnay/rust-toolchain@stable
3887
with:
3988
targets: ${{ matrix.target }}
89+
components: rustfmt
4090

41-
- name: Cache cargo registry & build
91+
- name: Cache cargo registry
4292
uses: actions/cache@v4
4393
with:
4494
path: |
4595
~/.cargo/registry
4696
~/.cargo/git
47-
plugins/edriver/target
48-
key: ${{ runner.os }}-${{ matrix.arch }}-cargo-edriver-${{ hashFiles('plugins/edriver/Cargo.lock') }}
49-
restore-keys: ${{ runner.os }}-${{ matrix.arch }}-cargo-edriver-
97+
key: ${{ matrix.arch }}-cargo-edriver-v10-${{ hashFiles('plugins/edriver/Cargo.lock') }}
98+
restore-keys: ${{ matrix.arch }}-cargo-edriver-v10-
5099

51-
- name: Install build dependencies
100+
# elfutils ./configure (invoked by libbpf-sys vendored-libelf build.rs)
101+
# runs with CC=musl-gcc. musl libc lacks argp_parse, fts_close, and
102+
# _obstack_free, causing configure to abort. We pre-build the three
103+
# void-linux musl-compat shims (identical to Alpine's argp-standalone,
104+
# musl-fts-dev, musl-obstack-dev packages) and install the .a + headers
105+
# into musl-gcc's default sysroot search path so configure finds them.
106+
- name: Build musl compat libs (argp / fts / obstack)
52107
run: |
53-
sudo apt-get update -qq
54-
sudo apt-get install -y --no-install-recommends build-essential pkgconf libelf-dev libzstd-dev musl-tools llvm-14 clang-14 protobuf-compiler
55-
for tool in clang llc llvm-strip
56-
do
57-
sudo rm -f /usr/bin/$tool
58-
sudo ln -s /usr/bin/${tool}-14 /usr/bin/$tool
59-
done
108+
set -eux
109+
ARCH=$(uname -m) # x86_64 or aarch64
110+
MUSL_LIB=/usr/lib/${ARCH}-linux-musl
111+
MUSL_INC=/usr/include/${ARCH}-linux-musl
112+
113+
# 1. argp-standalone – provides argp_parse
114+
# NOTE: Makefile.am uses noinst_LIBRARIES so `make install` does NOT
115+
# install libargp.a. We build and copy manually.
116+
git clone --depth=1 https://github.com/ericonr/argp-standalone /tmp/argp-standalone
117+
cd /tmp/argp-standalone
118+
autoreconf -fiv
119+
CC=musl-gcc ./configure --prefix=/usr
120+
make -j$(nproc)
121+
cp libargp.a ${MUSL_LIB}/
122+
cp argp.h ${MUSL_INC}/
123+
124+
# 2. musl-fts – provides fts_close (NetBSD implementation)
125+
git clone --depth=1 https://github.com/void-linux/musl-fts /tmp/musl-fts
126+
cd /tmp/musl-fts
127+
./bootstrap.sh
128+
CC=musl-gcc ./configure --enable-static --disable-shared \
129+
--prefix=/usr --libdir=${MUSL_LIB} --includedir=${MUSL_INC}
130+
make -j$(nproc) && make install
131+
132+
# 3. musl-obstack – provides _obstack_free (from gcc libiberty)
133+
git clone --depth=1 https://github.com/void-linux/musl-obstack /tmp/musl-obstack
134+
cd /tmp/musl-obstack
135+
./bootstrap.sh
136+
CC=musl-gcc ./configure --enable-static --disable-shared \
137+
--prefix=/usr --libdir=${MUSL_LIB} --includedir=${MUSL_INC}
138+
make -j$(nproc) && make install
139+
140+
# Verify all three libs landed in the musl sysroot
141+
ls -la ${MUSL_LIB}/libargp.a ${MUSL_LIB}/libfts.a ${MUSL_LIB}/libobstack.a
142+
143+
- name: Verify toolchain
144+
run: |
145+
which protoc && protoc --version
146+
which clang && clang --version | head -1
147+
musl-gcc --version | head -1
148+
149+
- name: Build libbpf headers
150+
# Build libbpf.a separately so failures here are easily diagnosed.
151+
# Then touch the fake-target file so `make build` skips the rebuild.
152+
run: |
153+
cd plugins/edriver
154+
LIBBPF_LOG=/tmp/libbpf-build.log
155+
echo "clang: $(clang --version | head -1)"
156+
if ! CC=clang CFLAGS=-fPIC make \
157+
-C ../libs/libbpf/src \
158+
BUILD_STATIC_ONLY=1 \
159+
DESTDIR=$(pwd)/src/bpf/headers/libbpf/ \
160+
OBJDIR=$(pwd)/src/bpf/headers/libbpf/obj \
161+
INCLUDEDIR= LIBDIR= UAPIDIR= prefix= libdir= \
162+
install install_uapi_headers > "$LIBBPF_LOG" 2>&1; then
163+
echo "=== libbpf build failed, last 60 lines ==="
164+
tail -60 "$LIBBPF_LOG"
165+
grep -E "error:|fatal error:|undefined" "$LIBBPF_LOG" | head -20 \
166+
| while IFS= read -r line; do echo "::error::libbpf: $line"; done
167+
exit 2
168+
fi
169+
install -m 0640 ./src/bpf/headers/libbpf/bpf/*.h ./src/bpf/headers/
170+
mkdir -p headers/libbpf
171+
# Use a far-future mtime so make considers this target always up-to-date.
172+
touch -d "2030-01-01" headers/libbpf/libbpf.a
60173
61174
- name: Build
62-
run: cd plugins/edriver && make build
175+
run: |
176+
export PROTOC=$(which protoc)
177+
BUILD_LOG=/tmp/edriver-build.log
178+
if ! ( cd plugins/edriver && make build ) > "$BUILD_LOG" 2>&1; then
179+
echo "=== Build failed, last 200 lines ==="
180+
tail -200 "$BUILD_LOG"
181+
echo "## Build failure" >> "$GITHUB_STEP_SUMMARY"
182+
echo '```' >> "$GITHUB_STEP_SUMMARY"
183+
tail -200 "$BUILD_LOG" >> "$GITHUB_STEP_SUMMARY"
184+
echo '```' >> "$GITHUB_STEP_SUMMARY"
185+
# Emit key error lines as annotations (readable via check-runs API)
186+
grep -E "error:|panicked at|make: \*\*\*|error\[|undefined reference" "$BUILD_LOG" \
187+
| grep -v "^warning" | head -20 \
188+
| while IFS= read -r line; do
189+
echo "::error::$line"
190+
done
191+
exit 2
192+
fi
63193
env:
64194
PLATFORM: ${{ matrix.arch }}
195+
LIBBPF_SYS_LIBRARY_PATH: ${{ matrix.lib_path }}
196+
# Use -idirafter (not -I) so musl sysroot headers keep priority;
197+
# linux/bpf.h etc. are still found via /usr/include as last resort.
198+
LIBBPF_SYS_EXTRA_CFLAGS: ${{ matrix.extra_cflags }}
199+
CC_x86_64_unknown_linux_musl: ${{ matrix.cc }}
200+
CC_aarch64_unknown_linux_musl: ${{ matrix.cc }}
201+
CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER: ${{ matrix.cc }}
202+
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: ${{ matrix.cc }}
203+
RUSTFLAGS: ${{ matrix.rustflags }}
204+
CARGO_EXTRA_FEATURES: ${{ matrix.cargo_extra_features }}
65205

66206
- name: Test
67-
run: cd plugins/edriver && make test
207+
run: |
208+
export PROTOC=$(which protoc)
209+
cd plugins/edriver && make test
68210
env:
69211
PLATFORM: ${{ matrix.arch }}
212+
LIBBPF_SYS_LIBRARY_PATH: ${{ matrix.lib_path }}
213+
LIBBPF_SYS_EXTRA_CFLAGS: ${{ matrix.extra_cflags }}
214+
CC_x86_64_unknown_linux_musl: ${{ matrix.cc }}
215+
CC_aarch64_unknown_linux_musl: ${{ matrix.cc }}
216+
CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER: ${{ matrix.cc }}
217+
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: ${{ matrix.cc }}
218+
RUSTFLAGS: ${{ matrix.rustflags }}
219+
CARGO_EXTRA_FEATURES: ${{ matrix.cargo_extra_features }}

.github/workflows/co-re.yaml

Lines changed: 0 additions & 59 deletions
This file was deleted.

.github/workflows/release-driver.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ jobs:
1818
with:
1919
targets: x86_64-unknown-linux-musl
2020

21+
- name: Add musl target
22+
run: rustup target add x86_64-unknown-linux-musl
23+
2124
- name: Cache cargo registry & build
2225
uses: actions/cache@v4
2326
with:
@@ -43,7 +46,8 @@ jobs:
4346
cd plugins/edriver
4447
make build
4548
cd ../..
46-
49+
env:
50+
LIBBPF_SYS_LIBRARY_PATH: /usr/lib/x86_64-linux-gnu
4751
- name: Strip & checksum
4852
run: |
4953
strip plugins/edriver/target/x86_64-unknown-linux-musl/release/edriver

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ agent/deploy/hades-agent
1818

1919
# server
2020
server/webconsole/frontend
21+
server/webconsole/frontend_backup
22+
server/webconsole/frontend_v2
2123
server/frontend
2224

2325
# ignore certs

plugins/edriver/.cargo/config.toml

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
[env]
2-
LIBBPF_SYS_LIBRARY_PATH_x86_64_unknown_linux_musl = "/usr/lib/x86_64-linux-gnu:/usr/lib64:/usr/lib"
3-
LIBBPF_SYS_LIBRARY_PATH_aarch64_unknown_linux_musl = "/usr/lib/aarch64-linux-gnu:/usr/lib64:/usr/lib"
2+
LIBBPF_SYS_LIBRARY_PATH_x86_64_unknown_linux_gnu = "/usr/lib/x86_64-linux-gnu:/usr/lib64:/usr/lib"
3+
LIBBPF_SYS_LIBRARY_PATH_aarch64_unknown_linux_gnu = "/usr/lib/aarch64-linux-gnu:/usr/lib64:/usr/lib"
4+
LIBBPF_SYS_LIBRARY_PATH_x86_64_unknown_linux_musl = "/usr/lib/x86_64-linux-musl:/usr/lib/x86_64-linux-gnu:/usr/lib64:/usr/lib"
5+
LIBBPF_SYS_LIBRARY_PATH_aarch64_unknown_linux_musl = "/usr/lib/aarch64-linux-musl:/usr/lib/aarch64-linux-gnu:/usr/lib64:/usr/lib"
6+
# libbpf-sys compiles libbpf C sources using the native gcc.
7+
# For musl targets, we must use musl-gcc so libbpf C code is compiled against
8+
# musl headers (not glibc headers). With glibc headers, _FORTIFY_SOURCE
9+
# injects __snprintf_chk/__sprintf_chk/__realpath_chk etc., which musl doesn't
10+
# provide. musl-gcc wraps gcc with the musl specs (-specs .../musl-gcc.specs)
11+
# and points include paths to musl headers.
12+
CC_x86_64_unknown_linux_musl = "musl-gcc"
13+
CC_aarch64_unknown_linux_musl = "musl-gcc"
414

15+
# vendored-libelf builds elfutils --without-zstd; no zstd dependency anywhere.
516
[target.x86_64-unknown-linux-musl]
6-
linker = "x86_64-linux-musl-gcc"
17+
linker = "musl-gcc"
718
rustflags = ["-C", "target-feature=+crt-static"]
819

920
[target.aarch64-unknown-linux-musl]
10-
linker = "aarch64-linux-musl-gcc"
21+
linker = "musl-gcc"
1122
rustflags = ["-C", "target-feature=+crt-static"]
12-
13-
[target.aarch64-unknown-linux-gnu]
14-
linker = "aarch64-linux-gnu-gcc"

plugins/edriver/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Cargo build output
2+
/target/
3+
4+
# Intermediate libbpf build artifacts (generated by `make headers/libbpf/libbpf.a`)
5+
# The static objects and duplicate archives are not needed in version control.
6+
src/bpf/headers/libbpf/obj/staticobjs/

plugins/edriver/Cargo.toml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ description = "Rust version of hades edriver"
88
[features]
99
debug = ["sdk/debug"]
1010
static = ["libbpf-rs/static"]
11+
# Enable vendored elfutils compilation via libbpf-sys.
12+
# Only needed on glibc hosts (Ubuntu) where the system libelf.a is compiled
13+
# with USE_ZSTD. On Alpine/musl the system libelf-static is already
14+
# musl-native and zstd-free, so this is unnecessary (and won't build on musl
15+
# because elfutils configure requires glibc-only argp/fts).
16+
vendored-libelf = ["libbpf-sys/vendored-libelf"]
1117

1218
[dependencies]
1319
anyhow = "1.0"
@@ -19,9 +25,31 @@ lazy_static = "1.5.0"
1925
twox-hash = "2.1"
2026
hex = "0.4"
2127
libbpf-rs = { version = "0.26.2", features = ["static"] }
28+
# Add vendored-zlib directly so libbpf-sys compiles zlib from its vendored
29+
# source (using CC_x86_64_unknown_linux_musl=musl-gcc set in .cargo/config.toml)
30+
# instead of linking the system libz.a. The system libz.a on Ubuntu is
31+
# compiled with glibc headers (_FORTIFY_SOURCE=2), which generates
32+
# __snprintf_chk / __vsnprintf_chk calls that musl doesn't provide.
33+
# Compiling zlib from source with musl-gcc uses musl headers and avoids these.
34+
# vendored-zlib: compile zlib from source so the .a is musl-compatible on
35+
# both Alpine and Ubuntu. vendored-libelf is NOT listed here; it is gated
36+
# behind the optional `vendored-libelf` feature above and activated only on
37+
# glibc CI (ubuntu:24.04 aarch64) via CARGO_EXTRA_FEATURES in the Makefile.
38+
libbpf-sys = { version = "1.7", features = ["vendored-zlib"] }
2239
bitflags = "2.11.1"
2340
moka = { version = "0.12", features = ["sync"] }
2441
sdk = { path = "../../SDK/rust" }
2542

2643
[build-dependencies]
2744
libbpf-cargo = "0.26.2"
45+
# libbpf-cargo default → libbpf-rs/default → libbpf-sys/vendored-libbpf →
46+
# static-libbpf for the HOST build-script. libbpf's make install
47+
# BUILD_STATIC_ONLY=y creates a fat liblibbpf.a that embeds libelf object
48+
# files. Without vendored-libelf here, it embeds the SYSTEM libelf.a which
49+
# on Ubuntu 24.04+ is built with USE_ZSTD, causing ZSTD_* undefined symbol
50+
# errors when linking the HOST build-script binary.
51+
# vendored-libelf forces elfutils to be built with --without-zstd so the
52+
# embedded libelf objects are ZSTD-free.
53+
# NOTE: Do NOT add features=["static"] here (without vendored-libelf) since
54+
# that would make the HOST link system libelf.a directly with ZSTD refs.
55+
libbpf-sys = { version = "1.7", features = ["vendored-libelf", "vendored-zlib"] }

0 commit comments

Comments
 (0)