在 Alpine Linux 3.8 (x86_64, musl) 上把 apache/rocketmq-client-cpp 2.0.0
编译成动态链接库 librocketmq.so,并用 Alpine 原生打包工具 abuild 打成可直接 apk add 的安装包。
这不是片段示例,而是一套可复现的构建配方(APKBUILD + 构建脚本 + 补丁 + 验证脚本),
已在 alpine3.8builder(Alpine 3.8.0 / gcc 6.4.0 / cmake 3.11.1 / abuild 3.2.0)上从零跑通并完成安装验证。
| 包 | 大小 | 内容 |
|---|---|---|
rocketmq-client-cpp-2.0.0-r0.apk |
1.1 MB | /usr/lib/librocketmq.so.2.0.0 + librocketmq.so.2 + LICENSE |
rocketmq-client-cpp-dev-2.0.0-r0.apk |
18 KB | /usr/include/rocketmq/*.h(34 个头文件)、librocketmq.so 链接、rocketmq-client-cpp.pc |
rocketmq-client-cpp-examples-2.0.0-r0.apk |
138 KB | 上游示例程序 rocketmq-SyncProducer 等 |
librocketmq.so.2.0.0(strip 后 3.2 MB):
SONAME librocketmq.so.2
NEEDED libz.so.1
NEEDED libstdc++.so.6
NEEDED libgcc_s.so.1
NEEDED libc.musl-x86_64.so.1
libevent 2.1.11、jsoncpp 0.10.6、boost 1.58 以 -fPIC 静态编进 librocketmq.so
(与上游 build.sh 相同的依赖策略),因此目标机不需要装 boost/libevent,也不存在 ABI 版本冲突;
包依赖只有 so:libc.musl-x86_64.so.1 so:libstdc++.so.6 so:libgcc_s.so.1 so:libz.so.1。
.
├── .github/workflows/build.yml # CI:3.8 构建 + 多版本验证 + tag 发 Release
├── APKBUILD # 生成物(由 APKBUILD.in + versions/<ver>.conf 渲染)
├── APKBUILD.in # 模板
├── versions/
│ ├── 2.0.0.conf # 已验证的上游版本注册表(tag 只认这里有的版本)
│ └── patches/2.0.0/*.patch # 该版本专用的补丁(0001 SONAME/链接分组、0002 jsoncpp GCC≥6)
├── LICENSE # Apache-2.0
├── Makefile # setup / apk / install / verify
├── files/
│ ├── build.sh # 真正的构建逻辑:依赖 → 动态库 → staging
│ └── rocketmq-client-cpp.pc # pkg-config 文件(打进 -dev)
├── scripts/
│ ├── setup-build-host.sh # 装依赖 + 建 builder 用户 + 生成签名密钥
│ ├── build-apk.sh # 以非 root 身份跑 abuild,并收集产物到 dist/
│ ├── verify.sh # ELF/SONAME/符号/编译链接/dlopen 全链路验证
│ ├── test-python-binding.sh # rocketmq-client-python 2.0.0 回归测试
│ ├── test-alpine-matrix.sh # 在 3.19/3.20/3.22 chroot 里跑跨版本验证
│ ├── add-version.sh # 适配新上游:下载+复制补丁+dry-run+写 versions/<ver>.conf
│ ├── use-version.sh # 渲染 APKBUILD(pkgver 来自注册表,pkgrel=当天)
│ ├── smoke-test.c # dlopen(RTLD_NOW) 冒烟测试
│ └── link-test.c # 用 -dev 头文件 + -lrocketmq 编译链接测试
├── docs/
│ ├── BUILD-LOG.md # 本机实测的构建/打包日志摘录
│ ├── PYTHON-BINDING.md # Python 绑定测试:为什么会/不会 ImportError
│ ├── CROSS-VERSION.md # 3.8 构建的包能否装到更高版本(实测矩阵)
│ ├── GITHUB-ACTIONS.md # CI/发布策略:为什么只构建 3.8、怎么发版
│ └── UPGRADE.md # 上游升级指南(含 2.0.0→2.2.0 实测结论)
├── packages/ # 实测产出的三个 .apk
└── sources/ # 4 个上游源码包(离线构建用,见第 6 节)
# 0) 需要代理时(apk 只认 http_proxy;HTTPS 下载见下文“离线构建”)
export http_proxy=http://<proxy>:<port>
export https_proxy=$http_proxy
# 1) 装依赖 + 建 builder 用户 + 生成 abuild 签名密钥(一次性)
sh scripts/setup-build-host.sh
# 2) 打包(产物会收集到 ./dist)
sh scripts/build-apk.sh # 等价于 make apk
# 3) 安装 + 验证
apk add --allow-untrusted dist/rocketmq-client-cpp-2.0.0-r0.apk \
dist/rocketmq-client-cpp-dev-2.0.0-r0.apk
sh scripts/verify.sh /usr # 或 make verify也可以直接用 make:
make setup && make apk && make install && make verifyabuild 明确禁止 root 运行(拆子包时会以非 -F 参数重入)。scripts/build-apk.sh
因此在 root 下调 su - builder 执行,并带上 -d(跳过依赖分析:依赖已装好,且非 root
无法写 apk 数据库)。若你坚持用 root 单包构建,abuild -F -d 也能跑,但子包拆分阶段
仍会失败,不推荐。
- 动态库而非静态库:
-DBUILD_ROCKETMQ_SHARED=ON -DBUILD_ROCKETMQ_STATIC=OFF。 上游 CMake 的共享库目标直接链接${PROJECT_SOURCE_DIR}/bin/lib下的静态依赖, 所以脚本先把-fPIC静态依赖铺进<rocketmq-src>/bin/{include,lib},无需改依赖查找代码。 -fPIC是硬要求:把非 PIC 静态库链进.so会报R_X86_64_32S重定位错误, 因此 libevent/jsoncpp/boost 全部以-fPIC编译(b2 ... cflags=-fPIC cxxflags=-fPIC)。- SONAME 版本化(补丁 0001):
VERSION 2.0.0 / SOVERSION 2→librocketmq.so.2.0.0+librocketmq.so.2+librocketmq.so,符合 Alpine 库分包规范 (主包放.so.2,-dev放.so和头文件)。 - 静态依赖链接分组(补丁 0001):boost 静态库之间互相引用(
log_setup → log → thread等), 用-Wl,--start-group ... --end-group包住,规避链接顺序导致的undefined reference。 - 删掉
doc/安装规则(补丁 0001):上游有install(DIRECTORY .../doc/ ...),但发布包里没有doc/,make install会直接失败。 - jsoncpp 补丁(补丁 0002):0.10.6 在
__GNUC__ >= 6分支用了标准库里不存在的std::scoped_ptr(json_reader.cpp与json_writer.cpp各一处),改为std::unique_ptr。 - cmake 3.11 兼容:Alpine 3.8 的 cmake 没有
-S/-B,脚本统一mkdir build && cd build && cmake ..。 - boost 目标名:
--with-libraries不能写log_setup(b2 报 "wrong library name"), 写log会同时产出libboost_log.a与libboost_log_setup.a;iostreams需要bzip2-dev。 - abuild 源文件名:
v2.0.0.tar.gz、0.10.6.tar.gz这类通用名会被 abuild 拒绝, 用name::url重命名语法改成rocketmq-client-cpp-2.0.0.tar.gz、jsoncpp-0.10.6.tar.gz。
# pkg-config
cc -o demo demo.c $(pkg-config --cflags --libs rocketmq-client-cpp)
# 或者手工
cc -o demo demo.c -I/usr/include/rocketmq -L/usr/lib -lrocketmq#include <stdio.h>
#include "CProducer.h" /* -I/usr/include/rocketmq */
int main(void) {
CProducer *p = CreateProducer("my_group");
SetProducerNameServerAddress(p, "127.0.0.1:9876");
StartProducer(p);
/* ... 发送消息 ... */
ShutdownProducer(p);
DestroyProducer(p);
return 0;
}pip install rocketmq-client-python==2.0.0 后 import rocketmq.client 不会报
ImportError: rocketmq dynamic library not found —— 用本工程 apk 装到 /usr/lib 即可,
连 -dev 都不需要(Alpine 的 python3 打过补丁,find_library 会 glob .so.*)。
apk add --no-cache python3 py3-pip py3-setuptools
pip3 install rocketmq-client-python==2.0.0
python3 -c "import rocketmq.client; print('OK')" # OK
sh scripts/test-python-binding.sh # 一键回归测试机制、失败复现(库挪到非标准路径)与三种修复方式见 docs/PYTHON-BINDING.md。
sh scripts/add-version.sh 2.1.0 # 下载源码 + 复制补丁 + dry-run + 写 versions/2.1.0.conf
# 补丁 FAILED → 按 docs/UPGRADE.md 修 versions/patches/2.1.0/*.patch
sh scripts/use-version.sh 2.1.0 # 渲染 APKBUILD(pkgrel 默认当天)
sh scripts/build-apk.sh -f && sh scripts/verify.sh /usr
git add -A && git commit -m "add upstream 2.1.0"
git tag v2.1.0 && git push origin main --tags"换个 tag 就行"不成立:上游常改 CMakeLists.txt 和依赖,补丁需要重做(实测 2.2.0 新增
OpenSSL、jsoncpp 升到 0.10.7、补丁 hunk 失败)。add-version.sh 会把机械活干完并把要人工
处理的部分指出来,清单见 docs/UPGRADE.md。
abuild 的 SRCDEST 默认是项目目录;scripts/build-apk.sh 用 /var/cache/distfiles。
把下面 4 个文件放进去(文件名必须一致),abuild 校验 sha512sums 通过后就不会联网:
rocketmq-client-cpp-2.0.0.tar.gz # github.com/apache/rocketmq-client-cpp/archive/v2.0.0.tar.gz
boost_1_58_0.tar.gz # archives.boost.io/release/1.58.0/source/boost_1_58_0.tar.gz
libevent-2.1.11-stable.tar.gz # github.com/libevent/libevent/releases/download/release-2.1.11-stable/...
jsoncpp-0.10.6.tar.gz # github.com/open-source-parsers/jsoncpp/archive/0.10.6.tar.gz
构建基线固定在此版本(3.8):Alpine 二进制兼容是单向的,新版构建的包装不进 3.8, 而 3.8 构建的包能向上跑。所以只产出一套 3.8 制品,CI 负责验证它在新版本上的可用性。
实测 3.19.9 / 3.20.9 / 3.22.7 都能直接 apk add(签名校验通过)并正常运行,
3.22 + Python 3.12 下 import rocketmq.client 也正常。原因:
- musl 不做符号版本化,
.so里没有任何GLIBCXX_*最低版本约束;只依赖libc.musl-x86_64.so.1、libstdc++.so.6、libgcc_s.so.1、libz.so.1四个稳定 soname; - boost / libevent / jsoncpp 静态链入,不受目标机库版本影响;
- 实测
.so中 0 个 LFS64 / glibc 专有符号(musl 1.2.4 删除了stat64等符号,这是老二进制最常见的跨版本坑); - apk v2 包 + RSA 签名被 apk-tools 2.14.x 正常接受。
注意:只保证向上兼容(新系统装老包)。要长期维护,建议按目标 Alpine 版本重建
(本工程在任何版本上执行 abuild -r 即可)。完整矩阵、边界条件和复测方法见
docs/CROSS-VERSION.md;一键复测:
sh scripts/test-alpine-matrix.sh 3.19.9 3.20.9 3.22.7| 现象 | 原因与处理 |
|---|---|
Do not run abuild as root |
用 scripts/build-apk.sh(自动切到 builder 用户),或 abuild -F -d(不支持子包) |
User root/builder is not a member of group abuild |
adduser builder abuild,或构建时加 -d |
source ... needs to be renamed |
用 name::url 形式给源码改名 |
apk add 报 network error |
设 http_proxy;busybox wget 走 HTTPS 代理会握手失败,源码改用离线预置 |
CMake Error: The source directory .../build does not exist |
cmake 3.11 不支持 -S/-B |
'scoped_ptr' in namespace 'std' does not name a template type |
见补丁 0002 |
wrong library name 'log_setup' |
--with-libraries 只写 log |
bzlib.h: No such file or directory |
apk add bzip2-dev |
file INSTALL cannot find .../doc/ |
见补丁 0001 |
v3.8 没有 zlib-static 包 |
3.8 的静态库在 zlib-dev 里 |
| 构建慢 | 主要耗时是 boost(4 核约 10–15 分钟),用 JOBS= 调并发 |
用 Actions 构建 + Release 发制品,二进制不入库。 工作流 .github/workflows/build.yml:
| job | 内容 |
|---|---|
build |
在 alpine:3.8 容器里跑 setup-build-host.sh + build-apk.sh,产出 dist/(3 个 apk + SHA256SUMS + 签名公钥) |
verify |
矩阵 3.8 / 3.19 / 3.20 / 3.22(latest 允许失败):装公钥 → apk add(带签名校验)→ apk add build-base → scripts/verify.sh |
release |
仅 v* tag:把这些文件作为 Release 附件发布 |
tag 就是你选的上游版本:v2.0.0 → 构建 2.0.0,pkgrel 自动取当天日期
(2.0.0-r20261022)。版本必须先在 versions/<ver>.conf 注册,否则 CI 直接报错。
git tag v2.0.0 && git push origin main --tags # → 2.0.0-r<今天>| 想做什么 | 怎么做 |
|---|---|
| 重新发同一版本(第二天) | git tag -f v2.0.0 && git push -f origin v2.0.0 → 新 pkgrel,Release 资产被替换 |
| 发上游 2.1.0 | 先 sh scripts/add-version.sh 2.1.0(改补丁/依赖并验证),提交后再 tag v2.1.0 |
| 打未适配版本的 tag | ❌ CI 直接失败:upstream 2.1.0 is not validated |
详细的策略、签名密钥管理与风险见 docs/GITHUB-ACTIONS.md。
见 docs/BUILD-LOG.md 与 scripts/verify.sh 的输出:
SONAME = librocketmq.so.2
NEEDED: libz.so.1, libstdc++.so.6, libgcc_s.so.1, libc.musl-x86_64.so.1
ok: public C API symbols exported
ok: compiled and linked (against -dev headers + -lrocketmq)
CreateProducer() ok -> 0x...
ok: dlopen(librocketmq.so.2)
== verification passed