Cpp 使用 Conan 包管理工具

前提知识

cmake 的基本流程

1
2
3
mkdir build
cmake -B build -S .         # 相当于 `cmake ..`
cmake --build build -j16    # 相当于 `make -j16`

cmake preset

  • CMakePresets.json: 一般是要 git 跟踪的。
  • CMakeUserPresets.json: 一般是 gitignore 的。

使用 cmake presets:

1
2
3
4
5
mkdir build

cmake --preset make-release -S .
cmake --build --preset make-release -j16
#cmake --build --preset make-release -j16 -- <target>

安装 conan2

1
2
3
sudo pacman -S conan

conan profile detect --force    # 生成 ~/.conan2/profiles/default 配置文件

conan 的基本使用

conan 添加第三方包

1
2
3
4
5
def requirements(self):
    self.requires("fmt/11.0.2", options={"header_only": True})
    self.requires("boost/1.86.0", visible=False)   # 不传递给下游
    self.requires("openssl/3.0.13", options={"shared": True})
    self.test_requires("catch2/3.5.0")             # 仅测试阶段可见。conan test/create 时, 会使用这个依赖包。发布到 conan 仓库时, 不会依赖该包。

当一个软件依赖不同版本的包时:

1
2
3
myapp/1.0
├─ libA/1.0 ── fmt/9.1.0
└─ libB/1.0 ── fmt/10.0.0

解决方案:

1
2
def requirements(self):
    self.requires("fmt/10.0.0", override=True)   # 强制使用 11.0.0

使用 conan2 的基本流程

新建一个 cmake_exe project:

1
2
3
4
5
6
7
8
9
mkdir demo_exe && cd demo_exe

conan new cmake_exe -d name=demo_exe -d version=0.1       # `conan new --help`
conan install . --build=missing     # 项目第一次运行 conan 时

cmake --preset conan-release
cmake --build --preset conan-release -j16

cmake --build --preset conan-release -- help    # 查看 makefile/ninja 有哪些 targets

新建一个 cmake_lib project:

1
2
3
4
5
6
mkdir demo_lib && cd demo_lib

conan new cmake_lib -d name=demo_lib -d version=0.1

# 接下来的流程和 `cmake_exe` 一样
# ...

conan test/create package

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
conan editable add .    # 让缓存指向源码目录
conan editable list     # 可以看到缓存指向源码目录

conan export .                              # 先让其他人能 find_package。可用 `conan list` 查看
conan test test_package <pkg_name>/0.1      # 测试 header_lib

# 修改代码并 build 之后, 即可生效, 不用再 export。因为 pkg 的缓存是指向源码的。
cmake --build --preset conan-release -j16

# 测试完毕后
conan editable remove .
conan create . --build=missing            # export + 现场编译/打包 + 可选 test_package

conan 编译 debug 版本

1
2
3
4
conan install . --build=missing -s build_type=Debug

cmake --preset conan-debug
cmake --build --preset conan-debug -j16

上面的编译, 编译出来的第三库也是 debug 版本的。有些时候如果机器存储比较小或者需要 adb 部署程序 (文件太大时, 部署会比较慢) 时, 是不适合的。
所以可以选择将第三方程序编译成 release 版本, 而自己的项目编译成 debug 版本:

1
2
# Ref: [Build the project with Debug while dependencies with Release](https://github.com/conan-io/conan/issues/13478#issuecomment-1475389368)
conan install . --build=missing -s "&:build_type=Debug" -s "build_type=Release"

查看包的信息

查看包的 options:

 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
# Usage: <func> <pkg_name> <pgk_version>
conan_pkg_opts() {
  if [ -z "$1" ]; then
    echo "用法: conan_pkg_opts <pkg-name> [version]"
    echo "示例: conan_pkg_opts fmt"
    echo "示例: conan_pkg_opts fmt 11.0.2"
    return 1
  fi

  local pkg="$1"
  local ver="$2"

  if [ -z "$ver" ]; then
    # 不指定版本:匹配该包的所有版本
    conan graph info . --format=json 2>/dev/null | jq --arg pkg "$pkg" '
      .graph.nodes[]
      | select((.ref // "") | type == "string")
      | select(.ref | startswith($pkg + "/"))
      | {ref, options, default_options}
    '
  else
    # 指定版本:严格匹配 pkg/ver 或 pkg/ver#rev
    conan graph info . --format=json 2>/dev/null | jq --arg pkg "$pkg" --arg ver "$ver" '
      .graph.nodes[]
      | select((.ref // "") | type == "string")
      | select(.ref == ($pkg + "/" + $ver) or (.ref | startswith($pkg + "/" + $ver + "#")))
      | {ref, options, default_options}
    '
  fi
}

或者到 conan center 查看

conan 的常用命令

1
2
3
4
5
6
7
8
# conan {new|list|search|install|remove|export|test|editable|remote}

# 查看包的缓存路径
conan cache path <pkg_name>/<version>

# ## 导出缓存包到另外的机器
conan cache save header_lib/0.1:*   # 支持通配符
conan cache load header_lib-0.1__xxx.tgz

vscode 使用 conan 项目

conanfile.py 指定 cmake 生成 command_compile.json。conanfile.py:

1
2
3
4
5
6
    def generate(self):
        deps = CMakeDeps(self)
        deps.generate()
        tc = CMakeToolchain(self)
        tc.variables["CMAKE_EXPORT_COMPILE_COMMANDS"] = True    # 加上这行
        tc.generate()
1
cmake --preset conan-release -S .

为 clangd 指定 command_compile.json 的位置

clangd 的配置指定。在项目的根目录, 创建 .clangd (推荐):

1
2
CompileFlags:
  CompilationDatabase: build/Release

OR. vscode 的项目配置指定。.vscode/settings.json:

OR. 使用软链接的方式指定:

1
ln -s build/Release/compile_commands.json ./compile_commands.json