【鸿蒙 PC三方库构建系统】sha_ohos.patch 深度解读:一个补丁文件背后的适配故事
如果你做过开源库的跨平台移植,一定遇到过这样的困境:原始代码在自己的平台上跑得好好的,到了新平台就各种水土不服——缺构建脚本、链接方式不对、编译器警告满天飞……直接改源码?那下次升级上游版本怎么办,改过的代码全被覆盖了。补丁文件(patch)就是解决这个问题的经典方案。它不碰原始代码,而是把"需要改什么"记录在一个文件里,构建时自动应用。原始代码保持干净,升级时只需重新应用补丁。SHA 库的就是这
【鸿蒙 PC三方库构建系统】sha_ohos.patch 深度解读:一个补丁文件背后的适配故事
欢迎大家加入开源鸿蒙PC社区
项目地址:https://atomgit.com/oh-tpc/pc_sha
前言
如果你做过开源库的跨平台移植,一定遇到过这样的困境:原始代码在自己的平台上跑得好好的,到了新平台就各种水土不服——缺构建脚本、链接方式不对、编译器警告满天飞……
直接改源码?那下次升级上游版本怎么办,改过的代码全被覆盖了。
补丁文件(patch)就是解决这个问题的经典方案。它不碰原始代码,而是把"需要改什么"记录在一个文件里,构建时自动应用。原始代码保持干净,升级时只需重新应用补丁。
SHA 库的 sha_ohos.patch 就是这样一个补丁文件。它看起来只有 120 多行,但里面藏着把一个"没有 CMake 构建系统"的纯 C 库适配到 OpenHarmony 平台的全部工作。这篇文章就来逐行拆解,看看这个补丁到底做了什么、为什么这么做。
背景:为什么需要这个补丁
上游仓库的状况
BrianGladman/sha 是一个纯 C 实现的 SHA 加密算法库,代码质量很高,但有一个关键问题:它没有 CMake 构建系统。
原始仓库的根目录下只有 .c 和 .h 源文件,没有 CMakeLists.txt,没有 Makefile,甚至没有 configure 脚本。在 Windows 上,作者可能用的是 Visual Studio 项目文件;在 Linux 上,用户需要自己写编译命令。
OpenHarmony 的构建要求
OpenHarmony 的 Lycium 构建系统要求三方库必须使用 CMake 构建,因为:
- 统一构建流程:所有三方库都用 CMake,构建脚本才能通用
- 交叉编译支持:CMake 能配合 OpenHarmony SDK 的工具链文件,实现交叉编译
- 产物安装规范:CMake 的
install()指令能把头文件、库文件、可执行文件安装到标准目录结构 - 包查找支持:CMake 的
find_package()机制让其他项目能方便地依赖这个库
补丁需要解决的问题
所以,sha_ohos.patch 的核心任务就是:给一个没有构建系统的库,补上完整的 CMake 构建系统,同时处理 OpenHarmony 平台的特定问题。
具体来说,它做了三件事:
| 任务 | 具体内容 |
|---|---|
| 新增 CMakeLists.txt | 定义库、可执行文件、测试、安装规则 |
| 新增 PackageConfig.cmake.in | 支持 find_package(sha) |
| 处理平台差异 | 静态链接、OpenHarmony 编译选项 |
补丁文件结构总览
整个补丁文件包含两个 diff 块:
sha_ohos.patch
├── diff 块 1: cmake/PackageConfig.cmake.in (新增 11 行)
└── diff 块 2: CMakeLists.txt (新增 103 行)
注意一个关键细节:两个 diff 块的原始文件时间戳都是 1969-12-31 16:00:00——这是 Unix 纪元时间 0 在太平洋时区的表示,意味着原始仓库中这两个文件不存在。所以这个补丁不是"修改"文件,而是"凭空创建"文件。
第一部分:PackageConfig.cmake.in
补丁内容
diff -aurN sha/cmake/PackageConfig.cmake.in sha_patch/cmake/PackageConfig.cmake.in
--- sha/cmake/PackageConfig.cmake.in 1969-12-31 16:00:00.000000000 -0800
+++ sha_patch/cmake/PackageConfig.cmake.in 2023-09-04 02:06:35.266667302 -0700
@@ -0,0 +1,11 @@
+@PACKAGE_INIT@
+
+set(@PROJECT_NAME@_INCLUDE_DIRS ${PACKAGE_PREFIX_DIR}/include ${PACKAGE_PREFIX_DIR}/include/@TARGET_NAME@)
+
+set(@PROJECT_NAME@_SHARED_LIBRARIES ${PACKAGE_PREFIX_DIR}/lib/lib@TARGET_NAME@.so)
+set(@PROJECT_NAME@_STATIC_LIBRARIES ${PACKAGE_PREFIX_DIR}/lib/lib@TARGET_NAME@_static.a)
+
+include(CMakeFindDependencyMacro)
+
+include(${CMAKE_CURRENT_LIST_DIR}/@TARGET_NAME@Targets.cmake)
+check_required_components(@TARGET_NAME@)
这个文件是干什么的?
通俗地说,PackageConfig.cmake.in 是一个**“自我介绍模板”**。当其他项目想用 find_package(sha) 来找到 SHA 库时,CMake 就会读取这个文件生成的配置,知道 SHA 库的头文件在哪、库文件在哪。
.in 后缀表示它是一个模板(input),CMake 在配置阶段会把 @变量名@ 替换成实际值,生成最终的 shaConfig.cmake 文件。
逐行解读
第 1 行:初始化
@PACKAGE_INIT@
CMake 的内置宏,展开后是一堆初始化代码,设置 PACKAGE_PREFIX_DIR 等变量。PACKAGE_PREFIX_DIR 就是库的安装根目录,比如 /usr/local 或 /path/to/ohos/sdk。
第 3 行:头文件路径
set(@PROJECT_NAME@_INCLUDE_DIRS ${PACKAGE_PREFIX_DIR}/include ${PACKAGE_PREFIX_DIR}/include/@TARGET_NAME@)
替换后变成:
set(sha_INCLUDE_DIRS ${PACKAGE_PREFIX_DIR}/include ${PACKAGE_PREFIX_DIR}/include/sha)
设置了两个头文件搜索路径:
${PACKAGE_PREFIX_DIR}/include— 公共头文件目录${PACKAGE_PREFIX_DIR}/include/sha— SHA 库专属头文件目录
这样使用者写 #include <sha1.h> 或 #include <sha/sha1.h> 都能找到。
第 5-6 行:库文件路径
set(@PROJECT_NAME@_SHARED_LIBRARIES ${PACKAGE_PREFIX_DIR}/lib/lib@TARGET_NAME@.so)
set(@PROJECT_NAME@_STATIC_LIBRARIES ${PACKAGE_PREFIX_DIR}/lib/lib@TARGET_NAME@_static.a)
替换后:
set(sha_SHARED_LIBRARIES ${PACKAGE_PREFIX_DIR}/lib/libsha.so)
set(sha_STATIC_LIBRARIES ${PACKAGE_PREFIX_DIR}/lib/libsha_static.a)
告诉使用者:动态库在 lib/libsha.so,静态库在 lib/libsha_static.a。
第 8-10 行:加载依赖和校验
include(CMakeFindDependencyMacro)
include(${CMAKE_CURRENT_LIST_DIR}/@TARGET_NAME@Targets.cmake)
check_required_components(@TARGET_NAME@)
CMakeFindDependencyMacro:CMake 内置的查找依赖宏(SHA 库无外部依赖,但这是标准写法)shaTargets.cmake:CMake 自动生成的目标配置文件,包含具体的链接信息check_required_components(sha):检查使用者请求的组件是否都存在
使用者怎么用?
在其他项目的 CMakeLists.txt 中:
find_package(sha REQUIRED)
# 方式一:用导入目标(推荐)
target_link_libraries(myapp PRIVATE sha::sha_static)
# 方式二:用变量
target_include_directories(myapp PRIVATE ${sha_INCLUDE_DIRS})
target_link_libraries(myapp PRIVATE ${sha_STATIC_LIBRARIES})
第二部分:CMakeLists.txt
这是补丁的核心,103 行代码定义了完整的构建系统。我们分段拆解。
2.1 基本设置(第 1-10 行)
cmake_minimum_required (VERSION 3.12)
project(SHA)
enable_language(C CXX)
set(TARGET_NAME sha)
set(TARGET_INSTALL_INCLUDEDIR include)
set(TARGET_INSTALL_BINDIR bin)
set(TARGET_INSTALL_LIBDIR lib)
set(TARGET_INSTALL_ELEMENT "")
通俗理解:这是"开工前的准备"——告诉 CMake 最低版本要求、项目名称、用什么语言,以及安装目录的命名约定。
| 变量 | 值 | 含义 |
|---|---|---|
TARGET_NAME |
sha |
贯穿整个脚本的核心名称 |
TARGET_INSTALL_INCLUDEDIR |
include |
头文件安装到 include/ |
TARGET_INSTALL_BINDIR |
bin |
可执行文件安装到 bin/ |
TARGET_INSTALL_LIBDIR |
lib |
库文件安装到 lib/ |
TARGET_INSTALL_ELEMENT |
"" |
待安装的目标列表,初始为空 |
TARGET_INSTALL_ELEMENT 是一个"购物车"——后面每定义一个库或可执行文件,就往里面加一个,最后统一安装。
2.2 构建库文件(第 12-17 行)
add_library(sha SHARED sha1.c sha2.c hmac.c)
list(APPEND TARGET_INSTALL_ELEMENT sha)
add_library(sha_static STATIC sha1.c sha2.c hmac.c)
list(APPEND TARGET_INSTALL_ELEMENT sha_static)
通俗理解:用同一份源码(sha1.c、sha2.c、hmac.c)同时编译出动态库和静态库。
| 目标 | 类型 | 输出文件 | 用途 |
|---|---|---|---|
sha |
SHARED(动态库) | libsha.so |
给其他程序动态链接用 |
sha_static |
STATIC(静态库) | libsha_static.a |
给工具程序静态链接用 |
为什么要同时构建两种库?因为不同场景有不同需求:
- 动态库:多个程序共享一份代码,节省内存
- 静态库:把库代码直接编进可执行文件,独立运行不依赖外部
2.3 构建可执行文件(第 19-44 行)— 关键修改
这是整个补丁最核心的部分,涉及四个可执行文件:
hmac — HMAC 测试工具
add_executable(hmac hmac_test.c)
target_link_libraries(hmac PRIVATE sha_static)
list(APPEND TARGET_INSTALL_ELEMENT hmac)
pwd2key — 密钥派生工具
add_executable(pwd2key pwd2key.c)
target_link_libraries(pwd2key PRIVATE sha_static)
target_compile_definitions(pwd2key PRIVATE -DTEST)
list(APPEND TARGET_INSTALL_ELEMENT pwd2key)
sha_test — SHA 算法测试工具
add_executable(sha_test sha_test.c)
target_link_libraries(sha_test PRIVATE sha_static)
if(OHOS)
target_compile_options(sha_test PRIVATE -Wno-format-security)
endif()
list(APPEND TARGET_INSTALL_ELEMENT sha_test)
sha256sum — SHA256 校验和工具
add_executable(sha256sum shasum.c)
target_link_libraries(sha256sum PRIVATE sha_static)
list(APPEND TARGET_INSTALL_ELEMENT sha256sum)
关键决策:为什么全部用静态链接?
注意看,四个可执行文件全部链接的是 sha_static(静态库),而不是 sha(动态库)。
这不是随意的选择,而是解决了一个实际问题:
问题场景:如果用动态链接,在 OpenHarmony 设备上运行 sha_test 时会报错:
Error loading shared library libsha.so: No such file or directory
因为可执行文件运行时需要找到 libsha.so,但这个 .so 文件不在系统默认的库搜索路径中。
解决方案:改用静态链接,把 SHA 库的代码直接编译进可执行文件。这样可执行文件是自包含的,运行时不需要找外部库。
为什么静态链接在这里是合理的?
| 考虑因素 | 分析 |
|---|---|
| 库的体积 | SHA 库很小,静态链接增加的体积可忽略 |
| 工具的性质 | 这些是独立运行的命令行工具,不是长期运行的服务 |
| 部署便利性 | 不需要管理库路径,拷贝一个文件就能用 |
| 版本冲突 | 不存在多个版本共存的问题 |
关键决策:OpenHarmony 特定编译选项
if(OHOS)
target_compile_options(sha_test PRIVATE -Wno-format-security)
endif()
这段代码只在 OpenHarmony 平台上生效(通过 OHOS 变量判断),给 sha_test 加了 -Wno-format-security 编译选项。
为什么需要这个?
OpenHarmony 使用的 Clang 编译器对格式化字符串的安全检查比 GCC 更严格。sha_test.c 中可能有类似这样的代码:
printf(variable_string); // 变量作为格式化字符串
GCC 可能只是警告,但 OpenHarmony 的 Clang 会把它当作错误。-Wno-format-security 告诉编译器:“我知道这里可能有风险,但请别警告了。”
if(OHOS) 的条件判断确保这个选项只在 OpenHarmony 上生效,不影响 Linux 或 Windows 上的编译。
2.4 Windows 特定代码(第 46-50 行)
if(WIN32)
add_executable(sha_time sha_time.c)
target_link_libraries(sha_time PRIVATE sha)
list(APPEND TARGET_INSTALL_ELEMENT sha_time)
endif()
sha_time 是性能计时工具,只在 Windows 上构建。注意它链接的是 sha(动态库)而不是 sha_static——这是因为 Windows 上的运行环境和 OpenHarmony 不同,动态库路径问题更容易解决。
2.5 测试配置(第 52-60 行)
enable_testing()
add_test(NAME test_hmac COMMAND hmac)
add_test(NAME test_pwd2key COMMAND pwd2key)
add_test(NAME test_sha COMMAND sha_test)
if(WIN32)
add_test(NAME test_time COMMAND sha_time)
endif()
通俗理解:注册了三个测试用例(Windows 上四个),运行 ctest 命令就能自动执行。
| 测试名 | 执行的程序 | 测试内容 |
|---|---|---|
test_hmac |
hmac |
HMAC 消息认证码功能 |
test_pwd2key |
pwd2key |
PBKDF2 密钥派生功能 |
test_sha |
sha_test |
SHA-1/224/256/384/512 算法正确性 |
test_time |
sha_time |
性能基准测试(仅 Windows) |
2.6 安装规则(第 62-86 行)
install(
TARGETS
${TARGET_INSTALL_ELEMENT}
EXPORT
${TARGET_NAME}
PUBLIC_HEADER DESTINATION
${TARGET_INSTALL_INCLUDEDIR}
PRIVATE_HEADER DESTINATION
${TARGET_INSTALL_INCLUDEDIR}
RUNTIME DESTINATION
${TARGET_INSTALL_BINDIR}
LIBRARY DESTINATION
${TARGET_INSTALL_LIBDIR}
ARCHIVE DESTINATION
${TARGET_INSTALL_LIBDIR})
通俗理解:把"购物车"里的所有东西安装到对应目录。
| 产物类型 | 安装目录 | 具体文件 |
|---|---|---|
| RUNTIME(可执行文件) | bin/ |
hmac, pwd2key, sha_test, sha256sum |
| LIBRARY(动态库) | lib/ |
libsha.so |
| ARCHIVE(静态库) | lib/ |
libsha_static.a |
EXPORT ${TARGET_NAME} 是给 find_package() 用的——它让 CMake 生成一个 shaTargets.cmake 文件,其他项目通过这个文件就能找到 SHA 库的导入目标。
2.7 安装头文件(第 88-93 行)
file(GLOB ALL_HEAD "*.h")
install(
FILES
${ALL_HEAD}
DESTINATION
${TARGET_INSTALL_INCLUDEDIR}/${TARGET_NAME})
通俗理解:把所有 .h 头文件安装到 include/sha/ 目录。
file(GLOB ALL_HEAD "*.h") 用通配符匹配当前目录下所有头文件,包括:
sha1.h、sha2.h— SHA 算法接口hmac.h— HMAC 接口pwd2key.h— 密钥派生接口brg_endian.h、brg_types.h— 内部辅助宏rdtsc.h— 时间戳计数器(x86 特有)
2.8 生成 CMake 包配置(第 95-121 行)
install(
EXPORT
${TARGET_NAME}
FILE
${TARGET_NAME}Targets.cmake
DESTINATION
${TARGET_INSTALL_LIBDIR}/cmake/${TARGET_NAME}
)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${TARGET_NAME}ConfigVersion.cmake
VERSION
${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}
COMPATIBILITY SameMajorVersion
)
configure_package_config_file(
cmake/PackageConfig.cmake.in ${TARGET_NAME}Config.cmake
INSTALL_DESTINATION
${TARGET_INSTALL_LIBDIR}/cmake/${TARGET_NAME}
)
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake
${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}ConfigVersion.cmake
DESTINATION
${TARGET_INSTALL_LIBDIR}/cmake/${TARGET_NAME}
)
通俗理解:生成三个 CMake 配置文件,让其他项目能用 find_package(sha) 找到这个库。
| 生成文件 | 作用 |
|---|---|
shaTargets.cmake |
定义导入目标(sha::sha、sha::sha_static),包含具体的编译和链接选项 |
shaConfigVersion.cmake |
版本检查逻辑,判断找到的库版本是否兼容 |
shaConfig.cmake |
主入口文件,由 PackageConfig.cmake.in 模板生成 |
这三个文件最终安装到 lib/cmake/sha/ 目录下。
补丁应用后的完整效果
应用前:原始仓库
sha/
├── sha1.c
├── sha1.h
├── sha2.c
├── sha2.h
├── hmac.c
├── hmac.h
├── hmac_test.c
├── pwd2key.c
├── pwd2key.h
├── sha_test.c
├── shasum.c
├── brg_endian.h
├── brg_types.h
└── rdtsc.h
只有源文件,没有构建系统。
应用后:补丁后的仓库
sha/
├── CMakeLists.txt ← 新增!完整的 CMake 构建系统
├── cmake/
│ └── PackageConfig.cmake.in ← 新增!包配置模板
├── sha1.c
├── sha1.h
├── sha2.c
├── sha2.h
├── hmac.c
├── hmac.h
├── hmac_test.c
├── pwd2key.c
├── pwd2key.h
├── sha_test.c
├── shasum.c
├── brg_endian.h
├── brg_types.h
└── rdtsc.h
只新增了 2 个文件,原始源文件完全不变。
构建后的安装目录
usr/sha/arm64-v8a/
├── bin/
│ ├── hmac ← HMAC 测试工具
│ ├── pwd2key ← 密钥派生工具
│ ├── sha_test ← SHA 算法测试
│ └── sha256sum ← SHA256 校验和
├── include/
│ └── sha/
│ ├── sha1.h
│ ├── sha2.h
│ ├── hmac.h
│ ├── pwd2key.h
│ ├── brg_endian.h
│ ├── brg_types.h
│ └── rdtsc.h
└── lib/
├── libsha.so ← 动态库
├── libsha_static.a ← 静态库
└── cmake/
└── sha/
├── shaConfig.cmake
├── shaConfigVersion.cmake
└── shaTargets.cmake
补丁在构建流程中的位置
HPKBUILD prepare() 函数
│
├─ 1. git clone https://github.com/BrianGladman/sha.git
│
├─ 2. git reset --hard 3ee0d88fc4f629b2e084f1b4cbf22cd3597542fb
│ (锁定到指定版本)
│
├─ 3. patch -p1 < ../sha_ohos.patch ← 补丁在这里应用!
│ (新增 CMakeLists.txt 和 PackageConfig.cmake.in)
│
└─ 4. mkdir -p $ARCH-build
(创建构建目录)
补丁应用发生在源码下载之后、编译之前。-p1 参数表示去掉路径的第一层(sha/),因为 patch 命令是在源码目录内执行的。
这个补丁的设计哲学
1. 只加不改
补丁只新增了 2 个文件,没有修改任何原始源文件。这是最佳实践——原始代码保持完整,升级上游版本时冲突最少。
2. 静态链接工具,动态链接库
- 库本身同时提供动态库和静态库,给使用者选择
- 工具程序(hmac、sha_test 等)统一用静态链接,保证独立运行
- 这是一个很实用的策略:库给选择,工具求独立
3. 平台条件化
if(OHOS) # OpenHarmony 特定配置
if(WIN32) # Windows 特定配置
不同平台的差异用条件判断处理,一份 CMakeLists.txt 适配所有平台。
4. 标准化的包配置
生成 shaConfig.cmake 等文件,支持 find_package(),让 SHA 库能被其他项目方便地集成。这是 CMake 生态的标准做法,虽然写起来麻烦,但长期收益很大。
常见问题
Q1:为什么补丁是"新增文件"而不是"修改文件"?
因为原始仓库根本没有 CMakeLists.txt 和 PackageConfig.cmake.in。补丁的时间戳 1969-12-31(Unix 纪元)就是"文件不存在"的标志。
Q2:如果上游将来加了 CMakeLists.txt 怎么办?
如果上游新增了自己的 CMakeLists.txt,这个补丁应用时会冲突。届时需要:
- 对比上游的 CMakeLists.txt 和补丁中的版本
- 把 OpenHarmony 特有的修改(静态链接、
-Wno-format-security)合并到上游版本 - 重新生成补丁
Q3:为什么 sha_time 在 Windows 上用动态链接?
Windows 上动态库的搜索机制(PATH 环境变量、同目录优先)和 Linux/OpenHarmony 不同,动态库路径问题更容易解决。而且 sha_time 只是性能测试工具,不是核心功能。
Q4:能不能不用补丁,直接把 CMakeLists.txt 提交到 fork 仓库?
可以,但补丁方式更好:
- 补丁方式:原始源码通过
git clone获取,保持和上游完全一致;补丁记录了所有修改 - fork 方式:fork 仓库中的代码和上游不同,升级时需要 merge,容易遗漏修改
Q5:如何验证补丁是否正确应用?
# 进入源码目录
cd sha-3ee0d88...
# 检查新增文件是否存在
ls CMakeLists.txt cmake/PackageConfig.cmake.in
# 尝试配置构建
mkdir build && cd build
cmake ..
# 运行测试
make
ctest
总结
sha_ohos.patch 看起来只是一个 120 多行的文本文件,但它完成了一项关键的工程任务:给一个没有构建系统的加密库,补上了完整的、跨平台的、符合 OpenHarmony 规范的 CMake 构建系统。
补丁做了什么
| 新增文件 | 行数 | 作用 |
|---|---|---|
cmake/PackageConfig.cmake.in |
11 行 | 包配置模板,支持 find_package() |
CMakeLists.txt |
103 行 | 完整的构建系统定义 |
关键设计决策
- 只加不改:不修改任何原始源文件,升级友好
- 静态链接工具程序:解决 OpenHarmony 上动态库路径问题
- 平台条件化:
if(OHOS)/if(WIN32)处理平台差异 - 标准化包配置:生成 CMake Config 文件,支持
find_package()
补丁的价值
- 可追溯:所有修改一目了然,知道改了什么、为什么改
- 可重现:配合
git reset --hard精确版本,构建完全可重现 - 可维护:上游升级时,只需重新应用补丁,不需要重新改代码
- 可审查:补丁文件本身就是最好的代码审查材料
一个小小的补丁文件,体现了开源库跨平台适配的核心思路:不侵入原始代码,用最小改动解决平台差异,保持可追溯和可维护。
更多推荐





所有评论(0)