Skip to content

About

Apache RocketMQ C++ client as Alpine .apk (shared lib, built on 3.8, works on 3.8+)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

4 Commits

Folders and files

Repository files navigation

Apache RocketMQ Client C++ → 动态库 + Alpine .apk

在 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)上从零跑通并完成安装验证。


1. 产物

包 大小 内容
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。

2. 目录结构

.
├── .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 节)

3. 快速开始(在 Alpine 主机上)

# 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 verify

关于 root

abuild 明确禁止 root 运行(拆子包时会以非 -F 参数重入)。scripts/build-apk.sh 因此在 root 下调 su - builder 执行,并带上 -d(跳过依赖分析:依赖已装好,且非 root 无法写 apk 数据库)。若你坚持用 root 单包构建,abuild -F -d 也能跑,但子包拆分阶段 仍会失败,不推荐。

4. 实现要点

  1. 动态库而非静态库:-DBUILD_ROCKETMQ_SHARED=ON -DBUILD_ROCKETMQ_STATIC=OFF。 上游 CMake 的共享库目标直接链接 ${PROJECT_SOURCE_DIR}/bin/lib 下的静态依赖, 所以脚本先把 -fPIC 静态依赖铺进 <rocketmq-src>/bin/{include,lib},无需改依赖查找代码。
  2. -fPIC 是硬要求:把非 PIC 静态库链进 .so 会报 R_X86_64_32S 重定位错误, 因此 libevent/jsoncpp/boost 全部以 -fPIC 编译(b2 ... cflags=-fPIC cxxflags=-fPIC)。
  3. SONAME 版本化(补丁 0001):VERSION 2.0.0 / SOVERSION 2 → librocketmq.so.2.0.0 + librocketmq.so.2 + librocketmq.so,符合 Alpine 库分包规范 (主包放 .so.2,-dev 放 .so 和头文件)。
  4. 静态依赖链接分组(补丁 0001):boost 静态库之间互相引用(log_setup → log → thread 等), 用 -Wl,--start-group ... --end-group 包住,规避链接顺序导致的 undefined reference。
  5. 删掉 doc/ 安装规则(补丁 0001):上游有 install(DIRECTORY .../doc/ ...),但发布包里没有 doc/,make install 会直接失败。
  6. jsoncpp 补丁(补丁 0002):0.10.6 在 __GNUC__ >= 6 分支用了标准库里不存在的 std::scoped_ptr(json_reader.cpp 与 json_writer.cpp 各一处),改为 std::unique_ptr。
  7. cmake 3.11 兼容:Alpine 3.8 的 cmake 没有 -S/-B,脚本统一 mkdir build && cd build && cmake ..。
  8. boost 目标名:--with-libraries 不能写 log_setup(b2 报 "wrong library name"), 写 log 会同时产出 libboost_log.a 与 libboost_log_setup.a;iostreams 需要 bzip2-dev。
  9. 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。

5. 使用

# 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;
}

Python 绑定(rocketmq-client-python==2.0.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。

6. 升级上游版本

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。

7. 离线构建

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

8. 跨版本兼容性(3.8 构建 → 更高版本安装)

构建基线固定在此版本(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

9. 常见问题

现象 原因与处理
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= 调并发

10. CI 与发布(GitHub Actions)

用 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。

11. 实测结果

见 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

About

Apache RocketMQ C++ client as Alpine .apk (shared lib, built on 3.8, works on 3.8+)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages