HarmonyOS 鸿蒙PC三方库移植:vcpkg方式的 Port 脚本编写简明教程
文章摘要: 本文针对鸿蒙三方库移植适配,详细介绍了在vcpkg中维护或新增端口的开发规范。主要内容包括:1) 端口的基本组成结构(vcpkg.json元数据文件和portfile.cmake构建脚本);2) 脚本执行时的上下文变量及其典型应用场景;3) 依赖声明与特性管理的最佳实践;4) 源码获取的多种方式(特别是vcpkg_from_github的标准用法);5) 特性与构建选项的映射方法;6)
本文面向鸿蒙三方库移植适配,需要在 vcpkg 中维护或新增端口的开发者,结合通用约定与本仓库中
ports/libpng、ports/curl、ports/openssl、ports/libmediainfo等portfile.cmake里的常见写法,说明如何组织端口脚本、与vcpkg.json配合,以及处理特性、平台差异与安装收尾工作。
1. Port 是什么
一个 port 描述「如何从上游源码构建并安装某个库/工具到 vcpkg 的安装树」。每个端口通常包含:
| 文件 | 作用 |
|---|---|
vcpkg.json |
端口元数据:名称、版本、描述、依赖、可选 features、平台约束等 |
portfile.cmake |
构建脚本:下载源码、打补丁、调用构建系统、把产物放到 CURRENT_PACKAGES_DIR |
补丁、辅助 .cmake、usage 等 |
与端口同目录,由 portfile.cmake 引用 |
portfile.cmake 在 CMake 脚本模式 下由 vcpkg 执行,可使用 CMake 语法以及 vcpkg 提供的函数(如 vcpkg_from_github、vcpkg_cmake_configure 等)。
2. 执行时可依赖的上下文变量
编写 portfile.cmake 时最常接触的内置变量包括:
PORT:当前端口名(与vcpkg.json中name一致)。VERSION:当前要构建的版本字符串(来自vcpkg.json的version/version-date等)。FEATURES:用户启用的特性列表;可用"foo" IN_LIST FEATURES判断。VCPKG_LIBRARY_LINKAGE:static或dynamic,与 triplet 一致。VCPKG_TARGET_IS_WINDOWS、VCPKG_TARGET_IS_UWP、VCPKG_TARGET_IS_ANDROID、VCPKG_TARGET_IS_OHOS等:目标平台布尔变量,用于分支逻辑。VCPKG_TARGET_ARCHITECTURE:如x64、arm64、arm。CURRENT_PACKAGES_DIR:本端口本次安装的目标根目录(installed/<triplet>/下对应包目录)。CURRENT_BUILDTREES_DIR:构建树路径,适合放下载的补丁解压物等中间文件。CMAKE_CURRENT_LIST_DIR/CURRENT_PORT_DIR:当前portfile.cmake所在端口目录,用于file(INSTALL ...)引用同目录下的补丁、usage、vcpkg-cmake-wrapper.cmake等。
在 libpng 中,会根据 VCPKG_LIBRARY_LINKAGE 设置 PNG_STATIC / PNG_SHARED;在 curl 中会根据 FEATURES 与 Windows 条件追加 Schannel 等选项——这些都是典型的上下文用法。
3. 声明依赖与特性:vcpkg.json
脚本里的逻辑应与 vcpkg.json 一致:
- 普通依赖列在
dependencies;需要「仅在宿主机上参与配置」的工具链端口可加"host": true(例如vcpkg-cmake)。 - 可选能力放在
features里;portfile里用vcpkg_check_features或IN_LIST FEATURES与 CMake 选项对齐。
libpng 的 apng、tools 与 curl 的大量 SSL/协议相关 features 都是范例:JSON 声明依赖与描述,portfile.cmake 把 feature 名映射到上游的 -D... 开关。
4. 获取源码
4.1 vcpkg_from_github(最常见)
本仓库中 libpng、curl、openssl、libmediainfo 均使用 vcpkg_from_github:
OUT_SOURCE_PATH SOURCE_PATH:输出源码根路径变量名(惯例写SOURCE_PATH)。REPO/REF/SHA512:仓库与固定提交/标签及校验和,保证可复现构建。HEAD_REF:供--head模式使用。PATCHES:相对于端口目录的补丁列表(.patch/.diff)。
版本字符串处理:上游标签与 VERSION 不完全一致时,在 portfile 里用 string(REGEX REPLACE ...) 等先算出 REF 再传给 vcpkg_from_github。libmediainfo 将 VERSION 规整为 MEDIAINFO_VERSION 再拼 v${MEDIAINFO_VERSION};curl 用 string(REPLACE "." "_" ...) 生成 curl-${VERSION} 形式的 ref。
4.2 额外下载与解压(libpng 的 apng)
当某个 feature 需要额外资源时,可:
- 使用
vcpkg_download_distfile下载文件并校验SHA512; - 用
vcpkg_execute_required_process调用外部命令(如gzip -d)解压到CURRENT_BUILDTREES_DIR; - 在
vcpkg_from_github的PATCHES里加入该补丁路径(可为空字符串时需注意条件,本仓库通过变量在 feature 关闭时为空补丁路径的处理方式依端口而定)。
若 Windows 上需要 Unix 工具链,可配合 vcpkg_acquire_msys 与 vcpkg_add_to_path(libpng 的 apng 流程)。
5. 特性映射:vcpkg_check_features
将 vcpkg.json 中的 feature 名映射为 CMake 配置选项,推荐统一使用:
vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS
FEATURES
http2 USE_NGHTTP2
openssl CURL_USE_OPENSSL
openssl CURL_CA_FALLBACK
INVERTED_FEATURES
ldap CURL_DISABLE_LDAP
)
FEATURES:启用某 feature 时,向列表追加-D<OPTION>=ON(或等价行为,依实现而定)。INVERTED_FEATURES:未启用某 feature 时追加对应选项(如禁用 LDAP)。
在 OPTIONS 里展开 ${FEATURE_OPTIONS} 传给 vcpkg_cmake_configure(见 curl)。
互斥与组合校验:复杂端口用 if(...) + message(FATAL_ERROR "...") 明确禁止不支持的组合。curl 对 http3 与部分 TLS backend 的组合即如此;openssl 在开头检测已安装的 libressl/boringssl 并直接失败,避免链接冲突。
6. CMake 端口的标准流水线
以 libmediainfo、curl、libpng 为代表,典型顺序为:
vcpkg_find_acquire_program(如PKGCONFIG),必要时set(ENV{PKG_CONFIG} ...)。vcpkg_cmake_configureSOURCE_PATH指向上游 含顶层或子目录CMakeLists.txt的路径(libmediainfo使用"${SOURCE_PATH}/Project/CMake")。OPTIONS/OPTIONS_DEBUG/OPTIONS_RELEASE传入-D变量;可加入MAYBE_UNUSED_VARIABLES避免上游未使用某变量时产生警告(libpng)。
vcpkg_cmake_installvcpkg_cmake_config_fixup:修正*Config.cmake安装路径或包名(PACKAGE_NAME、CONFIG_PATH)。vcpkg_fixup_pkgconfig:修正.pc文件中的前缀等。- 按需
vcpkg_copy_pdbs(MSVC)、vcpkg_copy_tools(安装可执行文件到tools/<port>)。 file(REMOVE_RECURSE ...)删除不需要安装的目录(如重复的debug/include、debug/share)。vcpkg_install_copyright:安装许可证文件到share/<port>/copyright。- 可选:安装
usage、vcpkg-cmake-wrapper.cmake以改善find_package体验(curl、libpng)。
注入额外 CMake 逻辑:curl 通过 -DCMAKE_PROJECT_INCLUDE=... 在工程配置阶段包含自定义片段,用于与 vcpkg 环境对齐。
7. 非 CMake 或拆分式 portfile:openssl
openssl 说明了一种常见模式:顶层 portfile.cmake 只做共性逻辑(vcpkg_from_github、组装 CONFIGURE_OPTIONS、按 feature 追加 OpenSSL 的 Configure 参数),再通过
include("${CMAKE_CURRENT_LIST_DIR}/windows/portfile.cmake")
# 或
include("${CMAKE_CURRENT_LIST_DIR}/unix/portfile.cmake")
把平台相关的大段步骤拆到子文件,便于维护。
此类端口还会使用 vcpkg_cmake_get_vars + include("${cmake_vars_file}") 读取探测到的编译器信息(如 VCPKG_DETECTED_CMAKE_C_COMPILER_ID),用于决定是否启用某些优化或 OpenSSL 目标三元组(见 openssl 与 libpng 中 ARM/编译器相关分支)。
8. 平台与链接类型分支
典型模式:
- 静态库使用方式:
curl在VCPKG_LIBRARY_LINKAGE STREQUAL "static"时改写curl.h中的宏,使消费者默认按静态链接语义包含头文件。 - Windows 与
.pc/ 库名:libpng、curl在 Windows 上对pkgconfig中的-l名称做vcpkg_replace_string,区分debug与release、以及是否定义VCPKG_BUILD_TYPE(单一构建类型 triplet)。 - UWP / Android / OHOS:通过
VCPKG_TARGET_IS_*关闭不支持的选项或调整编译参数;libpng在 OHOS 上为VCPKG_C_FLAGS追加--target=...以配合交叉 sysroot,这是「工具链绕过 CMake 的execute_process调用编译器」类问题的典型处理思路。
9. 安装后修补:vcpkg_replace_string 与路径重写
上游生成的脚本或 .pc 常带有绝对路径,不利于包可移植性。curl 对 curl-config 的替换与移动到 tools/${PORT}/bin 是范例:
- 把安装目录占位符改为
${prefix}或基于脚本位置的相对推导; - 区分 debug 与 release 两套文件;
- 用
IGNORE_UNCHANGED避免在路径已替换时失败。
10. 链接方式约束与工具依赖
vcpkg_check_linkage(ONLY_STATIC_LIBRARY):在无法支持动态库的平台上强制静态库(openssl对 Emscripten)。vcpkg_find_acquire_program(PERL)、NASM、PKGCONFIG等:声明构建期可执行文件,由 vcpkg 获取或定位;必要时vcpkg_add_to_path。
11. 调试与质量检查建议
- 先最小化:默认关闭非必要
features,保证基线vcpkg install <port>可通过。 - 对齐
vcpkg.json:features的dependencies、supports与portfile中的FATAL_ERROR条件应一致,否则用户会在解析阶段或构建阶段才看到错误。 - 补丁:尽量小、注释清楚 issue/upstream 链接;命名放在端口目录,在
vcpkg_from_github或等价获取函数中列出。 - 版权:始终
vcpkg_install_copyright;若项目多许可证(如curl额外写入从源文件提取的声明),可组合多个FILE_LIST条目。
12. 实战:新增 mediainfo CLI 端口(依赖 libmediainfo)
你当前仓库已经有 libmediainfo(库),但没有 mediainfo(命令行工具)。这类“CLI 与库分仓、CLI 依赖库”是 vcpkg 中很常见的建模场景,推荐单独新建一个 mediainfo port。
12.1 目标与设计
- 端口拆分原则:
libmediainfo负责 SDK/链接库;mediainfo负责最终可执行程序。 - 依赖关系:
mediainfo的vcpkg.json依赖libmediainfo(以及其上游链条,如libzen)。 - 安装形态:CLI 可执行文件应安装到
tools/mediainfo(由vcpkg_copy_tools或上游安装路径配合移动完成)。 - 链接策略:通常允许 triplet 决定静/动态,不在端口里硬编码;若上游对纯静态有问题,再用
vcpkg_check_linkage限制。
12.2 新建目录结构
在 ports 下创建:
ports/
mediainfo/
vcpkg.json
portfile.cmake
usage
如果后续需要修补上游构建逻辑,再补充
*.patch/*.diff文件。
12.3 vcpkg.json 示例(CLI 端口)
下面是一个可作为起点的声明示例(版本号与哈希需按你实际选用的上游 release 调整):
{
"name": "mediainfo",
"version": "24.12",
"description": "Unified display of technical and tag data for video and audio files",
"homepage": "https://mediaarea.net/en/MediaInfo",
"license": "BSD-2-Clause",
"dependencies": [
{
"name": "vcpkg-cmake",
"host": true
},
{
"name": "vcpkg-cmake-config",
"host": true
},
"libmediainfo"
]
}
编写要点:
mediainfo不直接重复声明libzen,因为它已在libmediainfo依赖链里。- 若 CLI 仅支持部分平台,可在
supports字段提前约束(例如排除uwp)。
12.4 portfile.cmake 示例(重点)
mediainfo CLI 的上游仓库是 MediaArea/MediaInfo(与 libmediainfo 的 MediaInfoLib 不同)。可采用和现有端口一致的 CMake 流水线:
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO MediaArea/MediaInfo
REF "v${MEDIAINFO_VERSION}"
SHA512 <填写实际SHA512>
HEAD_REF master
PATCHES
find-mediainfolib-names.patch
)
vcpkg_cmake_configure(
SOURCE_PATH "${SOURCE_PATH}/Project/CMake/CLI"
OPTIONS
-DMEDIAINFO_CLI_STATIC=OFF
)
vcpkg_cmake_install()
vcpkg_copy_pdbs()
vcpkg_copy_tools(TOOL_NAMES mediainfo AUTO_CLEAN)
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include")
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share")
file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE")
这段示例体现了几个关键点:
- 分仓拉取:
REPO必须指向 CLI 仓库而不是MediaInfoLib。 - 入口目录正确:
MediaInfoCLI 的 CMake 入口是Project/CMake/CLI,不是Project/CMake。 - 关闭“源码子工程”静态路径:
-DMEDIAINFO_CLI_STATIC=OFF,否则会要求MediaInfoLib与MediaInfo在同一父目录下并排克隆,触发add_subdirectory ... MediaInfoLib/Project/CMake不存在。 - CMake 包名与文件名不一致:上游安装的是
MediaInfoLibConfig.cmake,而 CLI 里写的是find_package(mediainfolib),在区分大小写系统上 CONFIG 查找会失败。本仓库find-mediainfolib-names.patch将其改为find_package(mediainfolib NAMES MediaInfoLib REQUIRED)。不要对ZenLib使用NAMES zenlib:libzen安装目录里仍是ZenLibConfig.cmake(vcpkg_cmake_config_fixup(PACKAGE_NAME zenlib)主要修正路径与合并 debug/release,并不等于生成zenlib-config.cmake),写成NAMES zenlib反而会找不到包。ZenLib保持上游的find_package(ZenLib REQUIRED)即可。 - 不要在
mediainfo的portfile里向CURRENT_PACKAGES_DIR复制libmediainfo的 CMake 文件:配置mediainfo时,CURRENT_PACKAGES_DIR指向packages/mediainfo_*,此时还没有、也不应去写libmediainfo的share/mediainfolib/;把复制逻辑放在ports/libmediainfo/portfile.cmake(或仅用上述补丁)才对。 - 慎加
CMAKE_REQUIRE_FIND_PACKAGE_mediainfolib:若与上游find_package(... REQUIRED)叠加,vcpkg 会提示 “already called with REQUIRED, thus … has no effect”,且对解决 “找不到 Config” 无帮助。 - CLI 安装规范:
vcpkg_copy_tools(TOOL_NAMES mediainfo …)把可执行文件收敛到tools/${PORT}。 - 纯工具端口:一般不需要
vcpkg_cmake_config_fixup()(除非上游安装了需修正的 CMake package)。
许可证文件名以仓库为准(常见为根目录
LICENSE或License.html)。
12.5 usage 示例
ports/mediainfo/usage 可以写成:
The package mediainfo provides the command line tool:
mediainfo <media-file>
Example:
mediainfo sample.mp4
12.6 验证流程(建议按顺序)
- 安装并构建端口
vcpkg install mediainfo
- 验证可执行文件位置
- 检查
installed/<triplet>/tools/mediainfo/mediainfo(.exe)是否存在
- 检查
- 运行命令验证
installed/<triplet>/tools/mediainfo/mediainfo --Version
- 验证依赖解析
- 如果配置失败,先看 CMake 日志是否在
find_package(MediaInfoLib)处报错,再检查vcpkg.json依赖声明
- 如果配置失败,先看 CMake 日志是否在
12.7 常见坑位与处理
add_subdirectory ... MediaInfoLib ... not an existing directory:几乎总是MEDIAINFO_CLI_STATIC仍为默认 ON。在vcpkg_cmake_configure里加上-DMEDIAINFO_CLI_STATIC=OFF,让工程使用find_package(mediainfolib)(vcpkg 安装的libmediainfo),而不是在 buildtree 旁再克隆一份MediaInfoLib。CMAKE_REQUIRE_FIND_PACKAGE_*与 “already called with REQUIRED”**:对已在find_package(… REQUIRED)的包再写CMAKE_REQUIRE_FIND_PACKAGE_*往往只会报警、不能修复缺失的 Config;优先用补丁或 **NAMES` 修正查找名。Could not find a package configuration file provided by "mediainfolib":MediaInfoLib工程安装的是MediaInfoLibConfig.cmake,与 CLI 里find_package(mediainfolib)在大小写敏感系统上不匹配。处理:(A)libmediainfo端口用configure_file(... COPYONLY)生成mediainfolib-config.cmake(本仓库ports/libmediainfo/portfile.cmake);(B)mediainfo补丁find_package(mediainfolib NAMES MediaInfoLib REQUIRED)(本仓库ports/mediainfo/find-mediainfolib-names.patch)。切勿在mediainfo的portfile里向packages/mediainfo_*下的share/mediainfolib复制——那是错误目录,配置阶段永远找不到libmediainfo已安装的文件。Could not find … zenlibConfig.cmake(包名却写 ZenLib):多为把find_package(ZenLib …)改成了NAMES zenlib。libzen仍提供ZenLibConfig.cmake,应保留上游find_package(ZenLib REQUIRED),只对mediainfolib使用NAMES MediaInfoLib。MediaInfoDLL.h中unknown type name 'size_t'(OHOS / musl):动态链接路径下会包含该头文件;在部分 libc 上dlfcn.h不保证 已间接定义size_t。本仓库在libmediainfo中通过补丁mediainfodll-include-stddef.patch在extern "C"前加入#include <stddef.h>。- 版本标签不匹配:若上游 tag 不是
v${VERSION},参考curl/libmediainfo的做法先做字符串变换再传REF。 - 二进制名差异:某些平台产物可能不是
mediainfo,需按实际产物名修改vcpkg_copy_tools(TOOL_NAMES ...)。 - Windows 调试后缀:若上游生成
mediainfo_d.exe等命名,建议在安装后统一命名,减少下游脚本复杂度。 - 仅工具端口场景:若端口只提供 CLI,不提供库,可不强制
vcpkg_cmake_config_fixup(),但保留也通常无害(取决于是否安装了 config 文件)。 - OHOS SDK 的 CMake Deprecation Warning:来自
ohos.toolchain.cmake里偏旧的cmake_minimum_required,与mediainfo本身无关;升级 Harmony/OpenHarmony NDK/SDK 中的 toolchain 或本地忽略该警告即可。
13. 从本仓库四个端口可归纳的「检查清单」
| 步骤 | libpng | curl | openssl | libmediainfo |
|---|---|---|---|---|
| 获取源码 | GitHub + 条件额外下载 | GitHub | GitHub | GitHub |
| 版本/ref 变换 | 直接使用 v${VERSION} |
下划线替换 | openssl-${VERSION} |
正则修正次版本号 |
| 特性 | vcpkg_check_features + IN_LIST |
大量 feature + 冲突检测 | fips / tools 等 |
少量 + FIND_PACKAGE 锁定 |
| 构建系统 | CMake | CMake | Configure/NMake 等(分平台) | CMake(子目录) |
| 收尾 | pkgconfig、PDB、可选 tools | curl-config、静态头宏 |
拆分子 portfile + wrapper 模板 | pkgconfig debug 后缀 |
| 版权 | LICENSE | COPYING + 额外片段 | LICENSE.txt | LICENSE |
gitcode地址: https://gitcode.com/qq8864/vcpkg

14. 小结
编写 vcpkg 端口脚本的核心是:在 vcpkg.json 中诚实声明元数据与特性,在 portfile.cmake 中把「版本/平台/特性」翻译成上游构建系统能理解的选项,并在安装阶段 修正 CMake 配置、pkg-config、脚本路径与版权文件,使安装树对下游 CMake/pkg-config 用户一致且可维护。遇到复杂平台或互斥依赖时,尽早 message(FATAL_ERROR)、拆分 include() 子文件、用 vcpkg_cmake_get_vars 读取探测结果,与本仓库中成熟端口的写法保持一致,可显著降低维护成本。
最后,欢迎加入开源鸿蒙开发者社区交流:https://harmonypc.csdn.net/
更多推荐

所有评论(0)