CMakeToolchain:在包内使用 xxx-config.cmake 文件

在一般情况下,Conan 依赖于 package_info() 抽象,以允许使用任何构建系统构建的包被使用任何其他构建系统构建的任何其他包使用。在 CMake 的情况下,Conan 依赖于 CMakeDeps 生成器为每个依赖项生成 xxxx-config.cmake 文件,即使这些依赖项没有生成文件或根本没有用 CMake 构建。

ConanCenter 用户使用这种抽象,不打包 xxx-config.cmake 文件,而是使用 package_info() 中的信息。这对于提供尽可能与构建系统无关的包,并公平对待不同的构建系统、供应商和用户至关重要。例如,许多 Conan 用户在完全不使用 CMake 的情况下,已经愉快地使用原生的 MSBuild (VS) 项目。如果 ConanCenter 包仅使用包内的 config.cmake 文件构建,则无法实现这一点。

但是,ConanCenter 这样做并不意味着这是不可能或强制的。完全有可能使用包内的 xxx-config.cmake 文件,而放弃使用 CMakeDeps 生成器。

您可以在 GitHub 上的 examples2 存储库 中找到重现此示例的源代码。

$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/examples/tools/cmake/pkg_config_files

如果我们查看 conanfile.py

class pkgRecipe(ConanFile):
    name = "pkg"
    version = "0.1"
    ...

    def package_info(self):
        # No information provided, only the in-package .cmake is used here
        # Other build systems or CMake via CMakeDeps will fail
        self.cpp_info.builddirs = ["pkg/cmake"]
        self.cpp_info.set_property("cmake_find_mode", "none")

这是一个非常典型的配方,主要区别在于 package_info() 方法。有三点重要说明:

  • 它不定义像 self.cpp_info.libs = ["mypkg"] 这样的字段。Conan 不会将此信息传播给消费者,此信息唯一的存储位置是在包内的 xxx-config.cmake 文件中。

  • 以防万一仍有用户实例化 CMakeDeps,它使用 set_property("cmake_find_mode", "none") 来禁用客户端生成 xxx-config.cmake 文件。

  • 它定义了将在该文件夹内包含构建脚本(如 xxx-config.cmake 包),以便消费者能够找到它们。

因此,定义包详细信息的责任已转移到包含的 CMakeLists.txt 文件中。

add_library(mylib src/pkg.cpp)  # Use a different name than the package, to make sure

set_target_properties(mylib PROPERTIES PUBLIC_HEADER "include/pkg.h")
target_include_directories(mylib PUBLIC
        $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
        $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
    )

# Use non default mypkgConfig name
install(TARGETS mylib EXPORT mypkgConfig)
export(TARGETS mylib
    NAMESPACE mypkg::  # to simulate a different name and see it works
    FILE "${CMAKE_CURRENT_BINARY_DIR}/mypkgConfig.cmake"
)
install(EXPORT mypkgConfig
    DESTINATION "${CMAKE_INSTALL_PREFIX}/pkg/cmake"
    NAMESPACE mypkg::
)

有了这些信息,当执行 conan create 命令时:

  • 将调用 build() 方法来构建包。

  • 将调用 package() 方法来调用 cmake install,这将创建 mypkgConfig.cmake 文件。

  • 它将被创建在包文件夹 pkg/cmake/mypkgConfig.cmake 文件中。

  • 它将包含足够的头文件信息,并创建一个 mypkg::mylib 目标。

请注意,配置文件名、命名空间和目标文件的详细信息 Conan 也不知道,因此这也是消费者构建脚本应该了解的内容。

这足以拥有一个带有内部 mypkgConfig.cmake 文件的包,该文件可供消费者使用。在此示例代码中,消费者只是 test_package/conanfile.py,但同样适用于任何任意消费者。

消费者 conanfile.py 不需要使用 CMakeDeps,只需要 generators = "CMakeToolchain"。请注意,CMakeToolchain 生成器仍然是必需的,因为 mypkgConfig.cmake 需要在 Conan 缓存中找到。生成的 CMakeToolchain conan_toolchain.cmake 文件包含这些定义的路径。

消费者 CMakeLists.txt 将是标准的。

find_package(mypkg CONFIG REQUIRED)

add_executable(example src/example.cpp)
target_link_libraries(example mypkg::mylib)

您可以使用以下命令进行验证:

$ conan create .

======== Testing the package: Executing test ========
pkg/0.1 (test package): Running test()
pkg/0.1 (test package): RUN: Release\example
pkg/0.1: Hello World Release!
pkg/0.1: _M_X64 defined
pkg/0.1: MSVC runtime: MultiThreadedDLL
pkg/0.1: _MSC_VER1939
pkg/0.1: _MSVC_LANG201402
pkg/0.1: __cplusplus199711
pkg/0.1 test_package

重要注意事项

所介绍的方法有一个限制,它不适用于多配置 IDE。采用此方法将无法允许开发人员直接在 Visual Studio 等 IDE 中切换 Release 和 Debug 配置,并且需要执行 conan install 来进行更改。对于单配置设置来说,这完全不是问题,但对于 VS 开发人员来说,这可能有点不方便。团队正在开发 VS 插件,这可能会帮助缓解这种情况。原因是 CMake 的限制,find_package() 只能找到一个配置,并且在此处删除了 CMakeDeps,Conan 无法避免此限制。

重要的是要知道,包作者和包的 CMakeLists.txt 也有责任正确管理与其他依赖项的传递性,在某些情况下这并非易事。如果处理不当,存在风险,包内的 xxx-config.cmake 文件可能会在其他地方(例如系统)找到其传递性依赖项,而不是在传递性 Conan 包依赖项中。

最后,请记住,这些包除了 CMake 之外,不能被其他构建系统使用。