【鸿蒙 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 构建,因为:

  1. 统一构建流程:所有三方库都用 CMake,构建脚本才能通用
  2. 交叉编译支持:CMake 能配合 OpenHarmony SDK 的工具链文件,实现交叉编译
  3. 产物安装规范:CMake 的 install() 指令能把头文件、库文件、可执行文件安装到标准目录结构
  4. 包查找支持: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.csha2.chmac.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.hsha2.h — SHA 算法接口
  • hmac.h — HMAC 接口
  • pwd2key.h — 密钥派生接口
  • brg_endian.hbrg_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::shasha::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.txtPackageConfig.cmake.in。补丁的时间戳 1969-12-31(Unix 纪元)就是"文件不存在"的标志。

Q2:如果上游将来加了 CMakeLists.txt 怎么办?

如果上游新增了自己的 CMakeLists.txt,这个补丁应用时会冲突。届时需要:

  1. 对比上游的 CMakeLists.txt 和补丁中的版本
  2. 把 OpenHarmony 特有的修改(静态链接、-Wno-format-security)合并到上游版本
  3. 重新生成补丁

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 行 完整的构建系统定义

关键设计决策

  1. 只加不改:不修改任何原始源文件,升级友好
  2. 静态链接工具程序:解决 OpenHarmony 上动态库路径问题
  3. 平台条件化if(OHOS) / if(WIN32) 处理平台差异
  4. 标准化包配置:生成 CMake Config 文件,支持 find_package()

补丁的价值

  • 可追溯:所有修改一目了然,知道改了什么、为什么改
  • 可重现:配合 git reset --hard 精确版本,构建完全可重现
  • 可维护:上游升级时,只需重新应用补丁,不需要重新改代码
  • 可审查:补丁文件本身就是最好的代码审查材料

一个小小的补丁文件,体现了开源库跨平台适配的核心思路:不侵入原始代码,用最小改动解决平台差异,保持可追溯和可维护。


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐