照片:
Claudio Schwarz
on
Unsplash
今天,现代语言(如
Go
)通常提供集成的软件包管理,以提取库的所有依赖项。然而,很多软件都是用C/C++创建和维护的,并没有一个开箱即用的软件包管理器。
将一个软件移植到另一个目标平台(macOS、Windows、Linux)通常非常困难。庆幸的是,有第三方软件包管理器可用于此。其中一个叫
vcpkg
,是由微软提供的一个开源项目。在后文中,我将展示一些技巧,涵盖vcpkg中的一些困难。
vcpkg中的所有软件包都是从源代码中下载、打补丁和编译的。所以,当使用大型库,如boost、ITK或OpenCV时,会花费一些时间
开始使用vcpkg
首先,通过使用
Git
克隆仓库来检查vcpkg的最新版本。除此之外,你还需要一个C++编译器(GCC、MinGW、clang、Visual Studio cl、Xcode),具体取决于你的平台(参见
开始使用vcpkg
)和
CMake
。在macOS上,我推荐使用
homebrew
来获取依赖项。
brew install cmake
现在你可以安装vcpkg了。
git clone https://github.com/Microsoft/vcpkg.gitcd vcpkg./bootstrap-vcpkg.sh
如果你想减少版本库的大小(这样你就不会看到旧的历史),你可以在克隆命令中添加选项--depth=1
,然后读取
git clone --depth=1 https://github.com/Microsoft/vcpkg.git
现在更新vcpkg很简单:在你的vcpkg安装路径中调用git pull
。但当你匆忙时要小心这样做。你的项目和依赖的vcpkg库需要在这一步之后重新编译
建议保持vcpkg安装目录的路径简短且没有空白(例如:/opt/vcpkg
或C:\opt\vcpkg
),特别是对于Windows,因为有些包(boost)的层次相当深。
我们将在后文中把vcpkg的安装目录称为VCPK_ROOT
。下面的做法将使使用更容易,更便于携带。
创建一个环境变量VCPK_ROOT
,指向vcpkg的安装目录。
在你的CMake文件中参考这个变量,以取代硬编码的路径。
或者,你可以用以下调用CMake的方式来输入你的CMake安装:cmake -B cmake-build -DCMAKE_TOOLCHAIN_FILE=<VCPKG_ROOT>/scripts/buildsystems/vcpkg.cmake
。所以你最好使用我提供的vcpkg模板。
然后,将vcpkg安装目录添加到你的搜索中PATH
。
使用vcpkg命令行工具
对于消耗一个库来说,最重要的vcpkg命令是。
vcpkg help <command>
显示命令行中的一些帮助
vcpkg search
用于查找库(称为端口)
vcpkg install
用于安装库
一个例子是:。
vcpkg search catch
catch alias
如果你决定为catch2
,你可以用安装包。
vcpkg install catch2
该库将被编译并安装在你的VCPKG_ROOT
,从而落入$VCPKG_ROOT/packages/<LIBRARY>_<TRIPLET>
。 在安装命令的最后,会有一个使用提示。
catch2 provides CMake targets:
而这有时就是问题所在。如果不检查已安装的包,可能会产生误导,不清楚哪些CMake目标可以在target_link_libraries
。
现在,什么是三联体?
一个三联体反映了编译的目标系统(见三联体文件)。在我的例子中,它是x64-osx
。另一个三联体将是例如x64-windows-static
。调用vcpkg help triplet
,列出可能的值。
当对默认的三联体不满意时,你可以通过在你的CMake文件中指定VCPKG_TARGET_TRIPLET
来控制vcpkg install
,或者像这样用--triplet
选项来安装软件包(以下在macOS上不起作用)。
vcpkg install catch2 --triplet x64-windows
-static
三元组创建一个静态构建的库的版本。
有两种不同的模式来安装软件包。在经典模式下,新库可以通过调用vcpkg install <port-name>
全局安装,如上一节所示。在清单模式下,你需要在你的项目目录下创建一个vcpkg.json
文件,列出所有的依赖关系。更多细节请参见清单模式。
这有一个很大的好处,项目所需的所有信息都在一个地方,在编译项目时,所需的包会被自动下载和安装。这类似于Go的mod文件。下面是我的vcpkg模板中的小vcpkg.json
文件。
{ "name": "vcpkg-template", "version-string": "0.0.1", "dependencies": ["catch2"]}
现在当你通过调用git clean -dxf
来清理VCPKG_ROOT
目录时,所有安装的库和下载的文件都会消失,需要重新安装。除此之外,在我的系统(macOS)上,vcpkg在$HOME/.cache/vcpkg
下创建了缓存文件,为了清理,必须将其删除。
在manifest模式下,在你的项目目录下进行清理也会删除所有已安装的库。所以在下一次构建时,所有东西都将被重新编译。
控制 vcpkg 的编译
vcpkg 的行为可以通过 CMake 变量来控制,这些变量可以。
在CMakelists.txt
文件中设置(在project()
子句之前)或
在命令行中用cmake -D <VARIABLE>=<VALUE>
定义,或
设置为一个环境变量,如<VARIABLE>=<VALUE> make -C
关于目标架构和链接过程(静态或动态),以下变量是最重要的。 VCPKG_TARGET_TRIPLET
- 控制构建的目标,如:x64-windows-static
更多信息请参见CMake集成。
包的五个方面
在vcpkg中,为了使用一个库,你需要知道不同的名字。而这并不总是简单的,需要进行一些搜索
所以有以下几种情况。
1.在vcpkg.json
dependencies部分或你的vcpkg install <PORT>
命令中的vcpkg port name字段。
{ "dependencies": [ "boost-asio" ]}
注意,端口名不能包含除[a-zA-Z]
和破折号以外的其他字符-
。
2.vcpkg.json
中选择的特征看起来像这样。
{ "dependencies": [ { "name": "opencv", "features": [ "png" ] } ]}
3.要通过以下方式找到的CMake包名find_package(<PKG> REQUIRED)
4.中的CMake组件名称。find_package(<PKG> REQUIRED COMPONENTS <COMP1> <COMP2>)
5.target_link_library(myApp PRIVATE <DEP1> <DEP2>
的依赖性目标名称。有时target_include_directories()
,除此之外还必须设置!
这种差异的一个例子是以下情况。
vcpkg 端口名称。boost-asio
CMake包名:find_package(boost_asio CONFIG REQUIRED)
- 目标别名Boost::asio
,以找到链接target_link_library(myApp PRIVATE Boost::asio)
。经过搜索,你发现你可能需要与${Boost_LIBRARIES}
链接,而这并不是从 boost port 使用帮助中得出的。
一些软件包在CMake运行时发出或多或少的帮助信息,试图支持用户如何包含这样的特定库。
The package boost is compatible with built-in CMake targets:
但你不会轻易发现CMake目标的名字有趣的是,你在ports
目录中发现了一个使用文件,它显示了target_link_libraries()
的正确方法。
/opt/vcpkg/ports/boost> cat usageThe package boost is compatible with built-in CMake targets: find_package(Boost REQUIRED [COMPONENTS <libs>...]) target_link_libraries(main PRIVATE ${Boost_LIBRARIES}) target_include_directories(main PRIVATE ${Boost_INCLUDE_DIRS})
还有一篇很好的文章(见《如何用CMake找包:基础知识》)解决了在CMake中找包的一般问题,没有vcpkg。有一种方法是检查vcpkg安装目录下的Find<Package>.cmake
文件。
然而,boost库中的CMake文件要比这复杂得多。这些包并不都是同质的、直接的。好在所有具有良好的本地CMake支持的包通常都比较简单,可以添加。一个例子是用于JSON流的伟大的nlohmann-json
库。
在这里,安装后的用法打印真的很有帮助。
The package nlohmann-json provides CMake targets: find_package(nlohmann_json CONFIG REQUIRED) target_link_libraries(main PRIVATE nlohmann_json::nlohmann_json)The package nlohmann-json can be configured to not provide implicit conversions via a custom triplet file: set(nlohmann-json_IMPLICIT_CONVERSIONS OFF)For more information, see the docs here: https://json.nlohmann.me/features/macros/#json_use_implicit_conversions
那么问题来了。
我从哪里得到所有这些名字?
端口名称可以在网上找到vcpkg.io/en/packages…或者更好的vcpkg.info/,或者使用 vcpkg 命令行工具vcpkg search <name>
。一旦一个端口<PORT>
(我们假设你是用vcpkg install
全局安装的),你需要检查$VCPKG_ROOT/ports/<PORT>/portfile.cmake
文件,比如这个boost-asio
。
# Automatically generated by scripts/boost/generate-ports.ps1vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO boostorg/asio REF boost-1.78.0 SHA512 78c58a64d669eaeabb5ba003200c581065412d33912e641143186ee95c11e0fb0411ed8dbb9a9acced8c8ecd258e0de33872b2e22dfc4a572315cd9a665db8a6 HEAD_REF master PATCHES windows_alloca_header.patch)include(${CURRENT_INSTALLED_DIR}/share/boost-vcpkg-helpers/boost-modular-headers.cmake)boost_modular_headers(SOURCE_PATH ${SOURCE_PATH})
除此之外,为了找到可能的特性名称,你可以观察这个软件包的$VCPKG_ROOT/ports/<PORT>/vcpkg.json
文件。
为了好奇,你可以通过调用boost-asio
,列出所有的依赖项。
vcpkg depend-info boost-asio
当在vcpkg.json
中只指定了端口名称时,所安装的特性是所谓的默认特性。这种行为可以通过在vcpkg.json
文件的dependencies
部分中设置"default-features": false
选项来禁用。因此,一个vcpkg.json
文件可以这样写。
{ "name": "vcpkg-template", "version-string": "0.0.1", "dependencies": [ { "name": "boost", "default-features": false } ]}
见vcpkg.readthedocs.io/en/latest/u…
我在本文中对vcpkg的使用做了一个简短的调查,并给出了一些技巧和窍门。vcpkg的使用并不总是那么简单。我为新项目创建了一个小型的vcpkg模板。
你可以在GitHub上查看并重用我的vcpkg模板,了解一个C++项目的基本布局。所以,只要git clone https://github.com/andremueller/vcpkg-template
,就可以开始了。
另一个有趣的主题是vcpkg的版本支持(见利用版本支持控制你的vcpkg依赖关系)。还有一些vcpkg的替代品,如Conan或build2。
谢谢你的阅读。请继续关注更多信息。