Skip to content

Go back

被 CGO 交叉编译折磨的一个周末

Published:  at  05:50 PM
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 有两点关键点:

// #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 模拟目标架构,所以镜像编译速度可以接受。

教训



Next Post
Indie Hacking Memo