大家好,我是展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
文章目录
- 0. Vcpkg是什么?
- 1. Port 是什么
- 2. 执行时可依赖的上下文变量
- 3. 声明依赖与特性:`vcpkg.json`
- 4. 获取源码
- 4.1 `vcpkg_from_github`(最常见)
- 4.2 额外下载与解压(`libpng` 的 apng)
- 5. 特性映射:`vcpkg_check_features`
- 6. CMake 端口的标准流水线
- 7. 非 CMake 或拆分式 `portfile`:`openssl`
- 8. 平台与链接类型分支
- 9. 安装后修补:`vcpkg_replace_string` 与路径重写
- 10. 链接方式约束与工具依赖
- 11. 调试与质量检查建议
- 12. 实战:新增 `mediainfo` CLI 端口(依赖 `libmediainfo`)
- 12.1 目标与设计
- 12.2 新建目录结构
- 12.3 `vcpkg.json` 示例(CLI 端口)
- 12.4 `portfile.cmake` 示例(重点)
- 12.5 `usage` 示例
- 12.6 验证流程(建议按顺序)
- 12.7 常见坑位与处理
- 13. 从本仓库四个端口可归纳的「检查清单」
- 14. 小结
本文面向鸿蒙三方库移植适配,需要在 vcpkg 中维护或新增端口的开发者,结合通用约定与本仓库中
ports/libpng、ports/curl、ports/openssl、ports/libmediainfo等portfile.cmake里的常见写法,说明如何组织端口脚本、与vcpkg.json配合,以及处理特性、平台差异与安装收尾工作。
vcpkg鸿蒙仓地址:https://gitcode.com/OpenHarmonyPCDeveloper/ohos_vcpkg
0. Vcpkg是什么?
vcpkg是微软开源的C/C++ 包管理器,面向原生库与工具链生态,核心能力包括:
- 统一入口:用同一套命令与元数据(port)描述大量开源库的获取、配置、编译与安装。
- 跨平台与交叉编译:除 Windows、Linux、macOS 等主机平台外,社区与官方长期维护对Android、iOS等目标的交叉编译支持;各类 fork 也会为新兴平台补充 toolchain 与 triplet。
- 依赖解析:自动处理依赖树、版本约束与构建顺序,减少“手工拼 Makefile / 逐个改 CMake 交叉参数”的成本。
- 与 CMake 深度集成:通过
scripts/buildsystems/vcpkg.cmake等机制,让业务工程以标准 CMake 方式消费已安装的库与配置文件。
它已经能够处理 Android、iOS 和其他嵌入式目标的交叉编译。将 HarmonyOS 添加为一级平台意味着 OHOS 开发人员无需对每个库的构建系统进行修改,即可使用整个 vcpkg 移植目录。
QT官方2026年4月10号分享和贡献了支持HarmonyOS系统的vcpkg,这意味着海量的三方库(上千个)鸿蒙化移植从此变得简单了,借助QT官方修改后的 vcpkg 分支,只需一条命令即可完成三方库的安装。且vcpkg本身是跨平台的,跟windows下的使用习惯一致。
对鸿蒙而言,vcpkg 可以把“每个库一套脚本、一套坑”收敛为“平台 triplet + 少量 port 补丁 + 一条安装命令”。
鸿蒙的vcpkg已经通过了上游合并请求,合入了微软的 vcpkg主分支。详情参见这个pr: https://github.com/microsoft/vcpkg/pull/51470
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.1vcpkg_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. 实战:新增mediainfoCLI 端口(依赖libmediainfo)
你当前仓库已经有libmediainfo(库),但没有mediainfo(命令行工具)。这类“CLI 与库分仓、CLI 依赖库”是 vcpkg 中很常见的建模场景,推荐单独新建一个mediainfoport。
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.3vcpkg.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.4portfile.cmake示例(重点)
mediainfoCLI 的上游仓库是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.5usage示例
ports/mediainfo/usage可以写成:
The package mediainfo provides the command line tool: mediainfo <media-file> Example: mediainfo sample.mp412.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 |
14. 小结
编写 vcpkg 端口脚本的核心是:在vcpkg.json中诚实声明元数据与特性,在portfile.cmake中把「版本/平台/特性」翻译成上游构建系统能理解的选项,并在安装阶段修正 CMake 配置、pkg-config、脚本路径与版权文件,使安装树对下游 CMake/pkg-config 用户一致且可维护。遇到复杂平台或互斥依赖时,尽早message(FATAL_ERROR)、拆分include()子文件、用vcpkg_cmake_get_vars读取探测结果,与本仓库中成熟端口的写法保持一致,可显著降低维护成本。
最后,欢迎加入开源鸿蒙开发者社区交流:https://harmonypc.csdn.net/