TOC
TL;DR
使用 zig cc 交叉编译 CGO 项目时,rpath 设置无法生效导致运行时无法加载 .so,最后改用 Docker buildx 绕过交叉编译问题。
背景
RawWeb 是我维护的一个专注于个人网站的搜索引擎,其中为了识别相似内容,需要先将内容分词然后计算 simhash 值。
除了 n-gram 等比较粗暴的分词方式,还用到了 charabia。这是一个 Meilisearch 维护的支持多种语言分词的 Rust 库,目前我主要用它来处理中文分词。
于是需要在 Go 中调用 Rust 代码。考虑到 WASM 和调用二进制文件的方式性能损耗都较大,最终选择了 CGO 的方式。我的开发环境是 ARM 架构的 macOS,服务器是 ARM 架构的 Linux,所以部署前还需要交叉编译。为了省事,之前都是在本地编译出二进制文件然后上传到服务器运行。
项目中交叉编译 CGO 有两点关键点:
- 如果是目标是 Linux ARM64,通过 LDFLAGS 设置不同的 .so 加载路径:在编译时用开发环境代码库的路径,运行时用二进制文件的同级
lib
目录 - 使用 zig 提供的交叉编译工具链
// #cgo linux,arm64 LDFLAGS: -L${SRCDIR}/charabia-rs/target/aarch64-unknown-linux-gnu/release -lcharabia_rs -Wl,-rpath,'$$ORIGIN/lib'
// #cgo darwin LDFLAGS: -L${SRCDIR}/charabia-rs/target/release -lcharabia_rs
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 \
CC="zig cc -target aarch64-linux" CXX="zig c++ -target aarch64-linux" \
go build -a -o build/server ./cmd/server
问题出现:rpath 无法生效
这套配置在过去几个月中一直运行良好。但在这次升级 charabia 并调整编译脚本后,生产环境下服务启动失败,报告找不到 .so。
通过 ldd 命令检查二进制文件可以发现问题:程序仍然试图从我本地开发环境的绝对路径加载 .so,rpath 的设置并未生效:
debian@debian:~/rawweb/build$ ldd server
linux-vdso.so.1 (0x0000ffff9ec46000)
/Users/rook1e/Projects/rawweb/backend/core/pkgs/simhash/tokenizer/charabia/charabia-rs/target/aarch64-unknown-linux-gnu/release/libcharabia_rs.so => not found
libc.so => not found
带着“为什么前几个月都正常”的疑惑,与 Claude Code 结伴尝试了各种方式,但都未能让 zig 正确设置 rpath。猜测可能与 zig issue #17373 - Issues with how rpaths are handled by the build system 有关。
时间有限,我决定不再继续深究此问题,转而寻找一种能绕过交叉编译的替代方案。
Docker buildx 绕过交叉编译
最后决定绕过交叉编译,用 Docker buildx 在一个 ARM64 的环境里编译。
这就到 CC 擅长的领域了:
# Build stage
FROM --platform=linux/arm64 golang:1.25 AS builder
# Install build dependencies
RUN apt-get update && apt-get install -y \
build-essential \
curl \
&& rm -rf /var/lib/apt/lists/*
# Install Rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
WORKDIR /app
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build Rust tokenizer
WORKDIR /app/core/pkgs/simhash/tokenizer/charabia/charabia-rs
RUN cargo build --release
RUN mkdir -p /app/build/lib && cp target/release/libcharabia_rs.so /app/build/lib/
# Build Go application
WORKDIR /app
RUN go run ./cmd/dbgen
ENV CGO_ENABLED=1 CGO_LDFLAGS="-Wl,-rpath,\$ORIGIN/lib"
RUN go build -a -trimpath -ldflags="-extldflags=-Wl,-rpath,\$ORIGIN/lib" -o /app/build/server ./cmd/server
# Runtime stage
FROM --platform=linux/arm64 debian:12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/build/server ./
COPY --from=builder /app/build/lib ./lib/
EXPOSE 3000
CMD [ "./server" ]
docker buildx build --platform linux/arm64 ...
推送到线上,顺利运行。因为本机也是 ARM64,不需要用 QEMU 模拟目标架构,所以镜像编译速度可以接受。
教训
- 遇到不重要且超出能力范围的问题时别犟,赶紧想办法绕过
- 远离 CGO(其实之前已经被折磨过一次了)