Conan-CMake 透明集成
在 Conan 1.4 中,我们引入了两个新的 Conan 生成器,允许您在不更改 CMakeList.txt 文件中任何与 Conan 相关的行的情况下链接您的依赖项。
首先,让我们回顾一下 Conan 生成器是什么,并展示经典生成器和新生成器的优缺点。
以下示例中的所有代码都位于此 GitHub 存储库中:https://github.com/lasote/transparent_cmake_examples.git
经典 CMake 集成:“cmake” 生成器
如果您在项目中使用 Conan 包,您通常会声明一个 conanfile.txt
文件,其中包含一些依赖项。在本例中,我们正在构建一个应用程序,该应用程序使用 libcurl
检查 Conan 存储库中的 Github 星标。
(存储库中的 classic_approach 文件夹)
conanfile.txt
[requires]
libcurl/7.52.1@bincrafters/stable
[generators]
cmake
[options]
libcurl:with_openssl=True
CMakeLists.txt
project(myapp)
cmake_minimum_required(VERSION 3.1)
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup(TARGETS)
add_executable(myapp main.cpp)
target_link_libraries(myapp CONAN_PKG::libcurl)
main.cpp
#include <stdio.h>
#include <curl/curl.h>
int main(void)
{
CURL *curl;
CURLcode res;
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
struct curl_slist *list = NULL;
list = curl_slist_append(list, "user-agent: libcurl");
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(curl, CURLOPT_URL, "https://api.github.com/repos/conan-io/conan/stargazers");
res = curl_easy_perform(curl);
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}
创建一个“build”文件夹并调用“conan install”
$ mkdir build && cd build
$ conan install ..
Conan 生成了一个 conanbuildinfo.cmake
文件(对应于“cmake”生成器),其中包含有关 libcurl 依赖项和所有传递依赖项(在本例中为 OpenSSL 和 ZLib)的所有信息,以及一些我们可以调用的宏来简化与依赖项链接的任务。
在我们的 CMakeLists.txt
(如上所示)中,我们包含了该文件并调用了 conan_basic_setup()
。
此宏将为我们执行以下操作
- 检查 conan install 中指定的编译器是否与 CMake 检测到的编译器匹配。
- 根据 conan install 命令中指定的设置(本例中的默认配置文件)调整输出目录、rpaths 配置、标准库、运行时(仅限 Visual Studio)、fPIC 标志等。
- 准备传递目标(现代 CMake)和所需变量(全局方法)以链接依赖项。
可以 单独调用 这些自动调整中的任何一个,而不是调用 conan_basic_setup()
,这样我们就可以精确控制我们希望 Conan 为我们执行的操作。
现在我们可以只调用 CMake 来构建应用程序
Linux/Mac
$ cmake .. -DCMAKE_BUILD_TYPE=Release
$ cmake --build .
$ ./bin/myapp
# a JSON will be outputted here
Windows
$ cmake .. -G “Visual Studio 15 2017 Win64”
$ cmake --build . --config Release
$ ./bin/myapp
# a JSON will be outputted here
这种方法非常有用且使用非常简单,但您需要更改 CMakelists.txt
文件以包含 conanbuildinfo.cmake
文件。一些用户更喜欢依赖 CMake find_package()
功能来解耦构建系统和包管理器。
透明 CMake 集成:“cmake_paths” 生成器
(存储库中的 cmake_paths 文件夹)
我们可以调整我们的项目以使用与 CMake 的透明集成
conanfile.txt 将生成器更改为 cmake_paths
[requires]
libcurl/7.52.1@bincrafters/stable
[generators]
cmake_paths
[options]
libcurl:with_openssl=True
CMakeLists.txt 在这里我们不包含 conanbuildinfo.cmake
文件,只调用 find_package
project(myapp)
cmake_minimum_required(VERSION 3.1)
add_executable(myapp main.cpp)
find_package(CURL)
include_directories(${CURL_INCLUDE_DIRS})
target_link_libraries(myapp ${CURL_LIBRARIES})
清理“build”目录并重新安装,它将生成 conan_paths.cmake
文件
$ rm -rf build && mkdir build && cd build
$ conan install ..
现在调用 CMake,将 conan_paths.cmake
作为工具链包含在内
Linux/Mac:
$ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=./conan_paths.cmake
$ cmake --build .
$ ./bin/myapp
# a JSON will be outputted here
Windows:
$ cmake .. -G “Visual Studio 15 2017 Win64” -DCMAKE_TOOLCHAIN_FILE=./conan_paths.cmake
$ cmake --build . --config Release
$ ./release/myapp
# a JSON will be outputted here
不幸的是,它不起作用,将发生许多链接器错误
...
"_sk_value", referenced from:
_ossl_connect_common in libcurl.a(libcurl_la-openssl.o)
"_zlibVersion", referenced from:
_Curl_version_init in libcurl.a(libcurl_la-version.o)
_curl_version in libcurl.a(libcurl_la-version.o)
_curl_version_info in libcurl.a(libcurl_la-version.o)
_Curl_unencode_gzip_write in libcurl.a(libcurl_la-content_encoding.o)
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
为什么?因为即使使用 find_package(CURL)
,我们也需要管理传递依赖项 OpenSSL 和 Zlib
(存储库中的 cmake_paths_attempt2 文件夹)
PROJECT(myapp)
cmake_minimum_required(VERSION 3.1)
ADD_EXECUTABLE(myapp main.cpp)
find_package(ZLIB)
find_package(OpenSSL)
find_package(CURL)
include_directories(${ZLIB_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS})
target_link_libraries(myapp ${CURL_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES})
同样重要的是,我们需要以正确的顺序进行 target_link_libraries
。因此,在这种情况下,透明集成远非理想,我们丢失了来自包管理器的宝贵信息,例如传递性和链接顺序。
如果您未使用 Windows,则前面的示例可能会起作用。否则,您仍然会看到由于 findCURL.cmake
(由 CMake 提供)导致的链接器错误。
-
它没有链接到
Ws2_32
:请记住,CMake findXXX 模块不是传递的,因此您必须在CMakeLists.txt
文件中声明所有依赖项树。 -
它没有传播正确链接静态库所需的定义
CURL_STATICLIB
。
查看存储库中 cmake_paths_attempt3_windows 文件夹中的代码。
我们可以看到,使用 CMake 提供的 findXXX
模块远非理想,因为包管理器已经知道的大量信息完全丢失了:传递依赖项和定义都在 libcurl 配方的 package_info
方法中声明,但是如果您使用 CMake 提供的 findXXX 模块,则永远不会应用它们。
如何改进这一点?
透明 CMake 集成 (2):“cmake_find_package” 生成器
cmake_find_package
是一种不同的方法。它将为 Conan 关于依赖项树的信息中的每个依赖项生成一个 find<package_name>.cmake
。我们可以将其与现代 CMake 目标方法一起使用。由于每个目标都是传递的,因此 libcurl 目标也将包含 OpenSSL 和 zlib 信息。
(存储库中的 cmake_find_package 文件夹)
conanfile.txt 将生成器更改为 cmake_find_package
[requires]
libcurl/7.52.1@bincrafters/stable
[generators]
cmake_find_package
[options]
libcurl:with_openssl=True
libcurl:darwin_ssl=False # Force use openssl in OSX too
CMakeLists.txt Conan 将生成 libcurl::libcurl
目标,我们只需要修改 CMAKE_MODULE_PATH
以让 CMake 找到我们的自定义脚本
project(myapp)
cmake_minimum_required(VERSION 3.1)
set(CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR})
message(${CMAKE_BINARY_DIR})
add_executable(myapp main.cpp)
find_package(libcurl)
target_link_libraries(myapp libcurl::libcurl)
清理build目录并重新安装后,我们将得到以下文件
- FindOpenSSL.cmake
- Findzlib.cmake
- Findlibcurl.cmake
$ rm -rf build && mkdir build && cd build
$ conan install ..
现在让我们再次调用 CMake
Linux/Mac:
$ cmake .. -DCMAKE_BUILD_TYPE=Release
$ cmake --build .
$ ./bin/myapp
# a JSON will be outputted here
Windows:
$ cmake .. -G “Visual Studio 15 2017 Win64”
$ cmake --build . --config Release
$ ./release/myapp
# a JSON will be outputted here
如果我们想避免 CMAKE_MODULE_PATH
操作,我们还可以使用 cmake_paths
和 cmake_find_package
生成器,并将其用作工具链,就像在前面的示例中一样,它也将调整模块路径以在当前目录中找到我们的 find<package_name>.cmake
脚本。
CMake 将尝试按以下顺序找到 find find<package_name>.cmake
脚本:首先是 packages 文件夹,然后是我们在其中构建项目的目录,最后是 CMake 安装/Modules 目录。
等等……这真的透明吗?
您可能已经注意到,find_package(CURL)
已被 find_package(libcurl)
替换。也许您正在想:好吧,但是 findXXX
文件的名称与我在 CMakelists.txt
中的经典 find_package
调用不对应。我不想更改 CMakeLists.txt
文件,而这正是我阅读这篇博文的唯一原因!
您是对的,但此生成器并非旨在替换原始 CMake 安装 find_package
模块,因为它们的行为方式大不相同。如您所见,使用经典的 find_package
模块,从包管理器传输到构建系统的信息很少,而使用此生成器,目标的所有信息都会自动传播
-
目标是传递的,因此您只需指定您直接依赖的依赖项。您不需要知道 libcurl 是否依赖于 OpenSSL。实际上,Mac OSX 中的先前示例默认使用内部 Apple SSL 实现。我们的
CMakeLists.txt
将在任何系统中都以完全相同的方式工作。 -
传播定义:如果没有
CURL_STATICLIB
定义,构建将失败。此定义在 libcurl 包的package_info
方法中声明。 -
传播链接器和编译器标志:例如,要链接 Mac OSX 中的 SSL 框架,配方会注入:
-framework Security
和-framework Cocoa
,但仅在 OSX 中,并且仅当您不强制它使用 OpenSSL 时。
-
如果您正在使用包并且对更改
CMakeLists.txt
具有很高的限制,那么cmake_paths
可能是最佳选择。 -
如果您正在使用包并且正在寻找一种以非侵入方式连接包管理器和 CMake 构建系统的方法,请选择
cmake_find_package
。 -
如果您正在创建 Conan 包,我们强烈建议您在
CMakeLists.txt
文件中包含conanbuildinfo.cmake
(您可以始终从配方中修补您的CMakeLists.txt
!)。经典的cmake
生成器将来自包管理器的更多信息引入我们的构建脚本:应用的设置和选项、Visual Studio 运行时、rpaths、编译器检查、标准库版本和 fPIC 标志。
您可以在此存储库中找到这篇博文中示例的源代码:https://github.com/lasote/transparent_cmake_examples
您可以在 Conan 文档中找到有关与 CMake 集成的更多信息:https://docs.conan.org.cn/en/latest/integrations/cmake.html#cmake