Conan 2.0 版本已经发布很久了,配套的 cmake-conan 工具也同时得到了更新,在原有 Conan 1.x 版本上增强了功能,对开源项目和公司内部已有的 CMake 项目非常友好,接入简单。本文主要介绍 cmake-conan 的应用场景以及交叉编译的实战。

cmake-conan 新特性

如果你尚未使用过 Conan 与 CMake 的组合,请参考我历史编写的一篇关于 cmake-conan v1 版本的文章,该文章描述了 Conan + CMake 在跨平台项目中的实战应用场景。而本文将主要介绍 cmake-conan v2 版本中一些新的特性和使用姿势,看看新版本中解决了历史哪些问题以及我们要如何在实际开发场景中替换新的方案。

工程实战

cmake-conan v2 维护在 develop2 分支,要求 conan 版本必须大于 2.0.5(使用 1.60.2 + CONAN_V2_MODE 也不行)。新版本集成使用非常简单,只需要在初始化 CMake 工程时增加一个参数 CMAKE_PROJECT_TOP_LEVEL_INCLUDES 来指定 develop2 分支里面的 conan_provider.cmake 就可以了,如:

1
cmake -Bbuild -GXcode -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=$(pwd)/conan_provider.cmake

$(pwd)/conan_provider.cmake 表示使用当前目录下的 conan_provider.cmake,我已经从 github 提前下载好了该文件放到工程目录

执行以上命令后,控制台会输出类似如下内容(只截取关键信息):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
-- CMake-Conan: cmake_system_name=Darwin
-- CMake-Conan: cmake_osx_deployment_target=
-- CMake-Conan: cmake_system_processor=armv8
-- CMake-Conan: CMake compiler=AppleClang
-- CMake-Conan: CMake compiler version=15.0.0.15000040
-- CMake-Conan: [settings] compiler=apple-clang
-- CMake-Conan: [settings] compiler.version=15
-- CMake-Conan: Creating profile /Users/jj.deng/Documents/open-source/conan-cmake-v2-example/build/conan_host_profile
-- CMake-Conan: Profile:
include(default)
[settings]
arch=armv8
os=Macos
compiler=apple-clang
compiler.version=15
compiler.cppstd=11
[conf]
tools.cmake.cmaketoolchain:generator=Xcode

-- CMake-Conan: Installing both Debug and Release
-- CMake-Conan: conan install /Users/jj.deng/Documents/open-source/conan-cmake-v2-example -of=/Users/jj.deng/Documents/open-source/conan-cmake-v2-example/build/conan -pr;/Users/jj.deng/Documents/open-source/conan-cmake-v2-example/build/conan_host_profile;-s;build_type=Release;--build=missing;-g;CMakeDeps

======== Input profiles ========
Profile host:
[settings]
arch=armv8
build_type=Release
compiler=apple-clang
compiler.cppstd=11
compiler.libcxx=libc++
compiler.version=15
os=Macos
[conf]
tools.cmake.cmaketoolchain:generator=Xcode

Profile build:
[settings]
arch=armv8
build_type=Release
compiler=clang
compiler.cppstd=gnu17
compiler.libcxx=libc++
compiler.version=16
os=Macos

这与历史版本的差异很大,可以看到 v2 版本的 cmake-conan 自动帮我们检测了环境信息并输出,不需要我们像在 v1 版本中使用 conan_cmake_autodetect 函数来做检测工作了。并且在检测完成后会自动生成一个 profile 配置文件,随后自动调用 conan install 根据生成的 profile 来编译依赖库。

通过 CMake 初始化指定配置文件的方式来与 Conan 交互解决了以前我们业务 CMake 代码中包含了大部分对于 Conan 包处理的逻辑,以前的使用场景下如果脱离 Conan 业务的 CMake 脚本基本是无法跑通的。但新版本中可能考虑到这个问题,首先你的脚本是可以独立的通过指定一些三方库的 prefix 来进行编译的,通过 Conan 关联编译只是一种渠道。这样做到业务 CMake 脚本与 Conan 关联关系解耦可以让你的项目(无论开源还是公司内部项目)都可以很好的独立使用。

随之带来的问题是,你必须要修改一些以前业务 CMake 脚本中与 Conan 强关联的代码,如链接三方库时,以前我们可能是这样写的:

1
2
3
project(.....)
add_executable(${CMAKE_PROJECT_NAME} main.cc)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ${CONAN_LIBS])

但现在,你应该修改为这样,比如你依赖了 fmt 库:

1
2
3
4
project(.....)
find_package(fmt REQUIRED)
add_executable(${CMAKE_PROJECT_NAME} main.cc)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE fmt::fmt)

通过使用更加通用的 find_package 的方式来查找依赖库文件,最后按需链接指定库。当这样设计你的 CMake 脚本后完全与 Conan 脱离,如果没有 Conan,我们一样可以通过初始化 CMake 时指定 CMAKE_MODULE_PATH 来告知可查找三方库的路径信息。Modern CMake 文章中也是推荐使用这种方式使脚本能适应更多场景。

如果你的项目依赖较少或是相对独立的开源项目,建议你优化 CMake 脚本使其更加适合其他三方包管理工具。如果你的项目依赖繁多且不是开源性质属于企业项目,那么在修改适配脚本的同时可能并不一定能完全解决依赖耦合三方包管理工具的问题,这需要你做出适当的取舍。

交叉编译

历史版本的交叉编译需要我们先写好本机和目标系统的 profile 文件然后传递给 Conan 让 Conan 做出抉择,而 cmake-conan v2 版本中,你只需要关注 CMake 脚本,没有 Conan 时你是怎么交叉编译的,那么使用 cmake-conan 时你只需要创建一个 conanfile.txt 或 conanfile.py,并在 CMake 初始化时多增加一个 CMAKE_PROJECT_TOP_LEVEL_INCLUDES 就可以了。已交叉编译到 Android 举例。

当没有 Conan 时,我们通过指定 CMAKE_MODULE_PATH 可以指定已经预编译好的依赖库 fmt 位置让工程脚本自动去搜索:

1
2
3
4
5
6
7
8
cmake -Bbuild-android-x86_64 -G"Unix Makefiles" \
-DCMAKE_MODULE_PATH=/Users/jj.deng/Downloads/fmt-10.1.1 \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_SYSTEM_NAME=Android \
-DANDROID_PLATFORM=21 \
-DANDROID_STL=c++_static \
-DCMAKE_ANDROID_ARCH_ABI=x86_64 \
-DCMAKE_ANDROID_NDK=/Users/jj.deng/Library/Android/sdk/ndk/21.4.7075529

而如果我们没有 fmt 的预编译库,期望 Conan 来帮我们管理,那么在创建好 conanfile.txt 或 conanfile.py 的前提下并下载好了 conan_provider.cmake 脚本,将 -DCMAKE_MODULE_PATH 替换为 -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=$(pwd)/conan_provider.cmake 就可以了:

1
2
3
4
5
6
7
8
cmake -Bbuild-android-x86_64 \
-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=$(pwd)/conan_provider.cmake \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_SYSTEM_NAME=Android \
-DANDROID_PLATFORM=21 \
-DANDROID_STL=c++_static \
-DCMAKE_ANDROID_ARCH_ABI=x86_64 \
-DCMAKE_ANDROID_NDK=$(HOME)/Library/Android/sdk/ndk/21.4.7075529

输出结果如下(只保留关键信息):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- CMake-Conan: first find_package() found. Installing dependencies with Conan
-- CMake-Conan: Checking if a default profile exists
/Users/jj.deng/.conan2/profiles/default
-- CMake-Conan: cmake_system_name=Android
-- CMake-Conan: android_platform=21
-- CMake-Conan: cmake_system_processor=x86_64
-- CMake-Conan: CMake compiler=Clang
-- CMake-Conan: CMake compiler version=9.0.9
-- CMake-Conan: [settings] compiler=clang
-- CMake-Conan: [settings] compiler.version=9
-- CMake-Conan: android_stl=c++_static
-- CMake-Conan: Creating profile /Users/jj.deng/Documents/open-source/conan-cmake-v2-example/build-android-x86_64/conan_host_profile
-- CMake-Conan: Profile:
include(default)
[settings]
arch=x86_64
os=Android
os.api_level=21
compiler=clang
compiler.version=9
compiler.cppstd=11
compiler.libcxx=c++_static
build_type=Debug
[conf]
tools.cmake.cmaketoolchain:generator=Unix Makefiles
tools.android:ndk_path=/Users/jj.deng/Library/Android/sdk/ndk/21.4.7075529

可以看到,不像 cmake-conan v1 一样需要指定交叉编译的两个 profile 文件,按我们平时编译的命令就可以了。这里有一点要注意的是,针对 Android 平台我们在 cmake-conan v1 时使用的一些 CMAKE 变量在 cmake-conan v2 中是无效的,比如 CMAKE_SYSTEM_VERSION 要使用 Gradle 支持的 ANDROID_PLATFORMCMAKE_ANDROID_STL_TYPE 要使用 ANDROID_STL 替换。其他平台的交叉编译也是一样如法炮制。

集成效果

我们在 GitHub 提交了一个简单的 Sample 用以演示集成的效果,可访问:https://github.com/nmgwddj/conan-cmake-v2-example 查看示例代码,以下为各类主流 IDE 工具集成效果,使用指定工具打开文件夹即可直接编译使用,无需敲入复杂的导入三方库指令:

Visual Studio Code

Visual Studio IDE

Qt Creator

CLion

总结

新版本的 cmake-conan 设计符合业界设计规则,当大部分 PaaS 厂商思考如何捆绑用户到自己业务中时,而他们却解除耦合使用真正的工具价值在吸引用户,也许这就是开源项目的魅力所在吧。在接入 cmake-conan v2 版本前我们还有很多事情要做,包括内部曾经制作的一些基于 Conan v1 的 recipe,要让整条内部链路全部的 recipe 都需要使用标准的 conan v2 方案。