创建您的第一个 Conan 包

在前面的章节中,我们使用了 Conan 包(例如 Zlib 包),首先使用 conanfile.txt,然后使用 conanfile.py。但 conanfile.py 配方文件不仅用于使用其他包,还可以用于创建您自己的包。在本节中,我们将解释如何使用 conanfile.py 配方创建一个简单的 Conan 包,以及如何使用 Conan 命令从源代码构建这些包。

重要提示

这是一个教程部分。鼓励您执行这些命令。对于这个具体的例子,您需要在您的路径中安装 CMake。Conan 创建包并非严格要求必须使用 CMake,您可以使用其他构建系统(例如 VS、Meson、Autotools,甚至您自己的构建系统)来完成此操作,而无需依赖 CMake。

使用 conan new 命令创建一个“Hello World” C++ 库示例项目

$ conan new cmake_lib -d name=hello -d version=1.0

这将创建一个具有以下结构的 Conan 包项目。

.
├── CMakeLists.txt
├── conanfile.py
├── include
│   └── hello.h
├── src
│   └── hello.cpp
└── test_package
    ├── CMakeLists.txt
    ├── conanfile.py
    └── src
        └── example.cpp

生成的文件包括

  • conanfile.py:在根文件夹中,有一个 conanfile.py,它是主要的配方文件,负责定义如何构建和使用包。

  • CMakeLists.txt:一个简单的通用 CMakeLists.txt,其中没有任何关于 Conan 的特定内容。

  • srcinclude 文件夹:包含简单的 C++ “hello” 库的文件夹。

  • test_package 文件夹:包含一个 example 应用程序,该应用程序将需要并链接到创建的包。这不是强制性的,但它对于检查我们的包是否正确创建很有用。

让我们看看包配方 conanfile.py

from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps


class helloRecipe(ConanFile):
    name = "hello"
    version = "1.0"

    # Optional metadata
    license = "<Put the package license here>"
    author = "<Put your name here> <And your email here>"
    url = "<Package recipe repository url here, for issues about the package>"
    description = "<Description of hello package here>"
    topics = ("<Put some tag here>", "<here>", "<and here>")

    # Binary configuration
    settings = "os", "compiler", "build_type", "arch"
    options = {"shared": [True, False], "fPIC": [True, False]}
    default_options = {"shared": False, "fPIC": True}

    # Sources are located in the same place as this recipe, copy them to the recipe
    exports_sources = "CMakeLists.txt", "src/*", "include/*"

    def config_options(self):
        if self.settings.os == "Windows":
            del self.options.fPIC

    def layout(self):
        cmake_layout(self)

    def generate(self):
        deps = CMakeDeps(self)
        deps.generate()
        tc = CMakeToolchain(self)
        tc.generate()

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

    def package(self):
        cmake = CMake(self)
        cmake.install()

    def package_info(self):
        self.cpp_info.libs = ["hello"]

让我们简要解释一下配方的不同部分

首先,您可以看到定义的 Conan 包的 名称和版本

  • name:一个字符串,最少 2 个,最多 100 个小写字符,用于定义包名称。它应该以字母数字或下划线开头,并且可以包含字母数字、下划线、+、.、- 字符。

  • version:它是一个字符串,可以取任何值,与 name 属性的约束条件相同。如果版本遵循 X.Y.Z-pre1+build2 形式的语义版本控制,则该值可能用于通过版本范围而不是确切版本来请求此包。

然后您可以看到一些定义 元数据 的属性。这些是可选但推荐的,用于定义诸如包的简短 description、打包库的 authorlicense、包仓库的 url 以及包相关的 topics

之后,有一个与二进制配置相关的部分。本节定义了包的有效设置和选项。正如我们在使用包章节中解释的那样

  • settings 是项目范围的配置,不能在配方中设置为默认值。诸如操作系统、编译器或构建配置之类的东西,对于多个 Conan 包来说是通用的

  • options 是特定于包的配置,可以在配方中设置为默认值,在本例中,我们可以选择将包创建为共享库或静态库,默认值为静态库。

之后,设置 exports_sources 属性以定义哪些源是 Conan 包的一部分。这些是您要打包的库的源。在本例中,是我们的“hello”库的源。

然后,声明了几个方法

  • config_options() 方法(与 configure() 方法一起)允许微调二进制配置模型,例如,在 Windows 中,没有 fPIC 选项,因此可以将其删除。

  • layout() 方法声明了我们希望在其中找到源文件的位置以及构建过程中生成的文件的目标位置。示例目标文件夹是生成的二进制文件和 Conan 生成器在 generate() 方法中创建的所有文件的文件夹。在本例中,由于我们的项目使用 CMake 作为构建系统,我们调用 cmake_layout()。调用此函数将设置 CMake 项目的预期位置。

  • generate() 方法准备从源代码构建包。在这种情况下,它可以简化为属性 generators = "CMakeToolchain",但保留了此重要方法。在这种情况下,执行 CMakeToolchain generate() 方法将创建一个 conan_toolchain.cmake 文件,该文件将 Conan 的 settingsoptions 转换为 CMake 语法。为了完整起见,添加了 CMakeDeps 生成器,但在将 requires 添加到配方之前,这不是严格必需的。

  • build() 方法使用 CMake 包装器来调用 CMake 命令,这是一个薄层,它将管理在这种情况下传入 -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake 参数。它将配置项目并从源代码构建它。

  • package() 方法将工件(头文件、库)从构建文件夹复制到最终的包文件夹。可以使用裸“copy”命令完成此操作,但在本例中,它利用了已存在的 CMake 安装功能(如果 CMakeLists.txt 没有实现它,则可以使用 copy() 工具package() 方法中轻松编写等效的实现)。

  • 最后,package_info() 方法定义了消费者在使用此包时必须链接到“hello”库。还可以定义其他信息,例如 include 或 lib 路径。此信息用于由生成器(如 CMakeDeps)创建的文件,以供消费者使用。这是关于当前包的通用信息,并且无论消费者使用哪种构建系统,以及我们在 build() 方法中使用了哪种构建系统,这些信息都可供消费者使用。

现在,test_package 文件夹对于理解如何创建包来说不是至关重要的。重要的部分是

  • test_package 文件夹不同于单元测试或集成测试。这些测试是“包”测试,用于验证包是否已正确创建以及包使用者是否能够链接到它并重用它。

  • 它本身是一个小的 Conan 项目,它包含自己的 conanfile.py,以及其源代码(包括构建脚本),这些代码依赖于正在创建的包,并构建和执行一个小的应用程序,该应用程序需要包中的库。

  • 它不属于包。它仅存在于源代码仓库中,而不存在于包中。

让我们使用当前默认配置从源代码构建包,然后让 test_package 文件夹测试该包

$ conan create .

======== Exporting recipe to the cache ========
hello/1.0: Exporting package recipe
...
hello/1.0: Exported: hello/1.0#dcbfe21e5250264b26595d151796be70 (2024-03-04 17:52:39 UTC)

======== Installing packages ========
-------- Installing package hello/1.0 (1 of 1) --------
hello/1.0: Building from source
hello/1.0: Calling build()
...
hello/1.0: Package '9bdee485ef71c14ac5f8a657202632bdb8b4482b' built

======== Testing the package: Building ========
...
[ 50%] Building CXX object CMakeFiles/example.dir/src/example.cpp.o
[100%] Linking CXX executable example
[100%] Built target example

======== Testing the package: Executing test ========
hello/1.0 (test package): Running test()
hello/1.0 (test package): RUN: ./example
hello/1.0: Hello World Release!
  hello/1.0: __aarch64__ defined
  hello/1.0: __cplusplus201703
  hello/1.0: __GNUC__4
  hello/1.0: __GNUC_MINOR__2
  hello/1.0: __clang_major__15
  hello/1.0: __apple_build_version__15000309
...

如果显示“Hello world Release!”,则表示它已成功运行。这是发生的事情

  • conanfile.py 以及 src 文件夹的内容已被复制(在 Conan 术语中为导出)到本地 Conan 缓存中。

  • hello/1.0 包开始新的源构建,调用 generate()build()package() 方法。这将在 Conan 缓存中创建二进制包。

  • 然后,Conan 移动到 test_package 文件夹,并执行 conan install + conan build + test() 方法,以检查包是否已正确创建。

我们现在可以验证配方和包二进制文件是否在缓存中

$ conan list hello
Local Cache
  hello
    hello/1.0

conan create 命令接收与 conan install 相同的参数,因此您可以向其传递相同的设置和选项。如果我们执行以下行,我们将为 Debug 配置创建新的包二进制文件,或将 hello 库构建为共享库

$ conan create . -s build_type=Debug
...
hello/1.0: Hello World Debug!

$ conan create . -o hello/1.0:shared=True
...
hello/1.0: Hello World Release!

这些新的包二进制文件也将存储在 Conan 缓存中,准备好供此计算机上的任何项目使用。我们可以使用以下命令查看它们

# list all the binaries built for the hello/1.0 package in the cache
$ conan list "hello/1.0:*"
Local Cache
  hello
    hello/1.0
      revisions
        dcbfe21e5250264b26595d151796be70 (2024-05-10 09:40:15 UTC)
          packages
            2505f7ebb5a4cca156b2d6b8534f415a4a48b5c9
              info
                settings
                  arch: armv8
                  build_type: Release
                  compiler: apple-clang
                  compiler.cppstd: gnu17
                  compiler.libcxx: libc++
                  compiler.version: 15
                  os: Macos
                options
                  shared: True
            39f48664f195e0847f59889d8a4cdfc6bca84bf1
              info
                settings
                  arch: armv8
                  build_type: Release
                  compiler: apple-clang
                  compiler.cppstd: gnu17
                  compiler.libcxx: libc++
                  compiler.version: 15
                  os: Macos
                options
                  fPIC: True
                  shared: False
            814ddaac84bc84f3595aa076660133b88e49fb11
              info
                settings
                  arch: armv8
                  build_type: Debug
                  compiler: apple-clang
                  compiler.cppstd: gnu17
                  compiler.libcxx: libc++
                  compiler.version: 15
                  os: Macos
                options
                  fPIC: True
                  shared: False

现在我们已经创建了一个简单的 Conan 包,我们将更详细地解释 Conanfile 的每个方法。您将学习如何修改这些方法来实现诸如从外部仓库检索源代码、向我们的包添加依赖项、自定义我们的工具链等操作。

关于 Conan 缓存的说明

当你执行 conan create 命令时,你的软件包构建并没有在本地文件夹中进行,而是在 Conan 缓存 内的其他文件夹中进行的。此缓存位于用户主文件夹下的 .conan2 文件夹中。Conan 将使用 ~/.conan2 文件夹来存储构建的软件包以及不同的配置文件。你已经使用 conan list 命令列出了本地缓存中存储的配方和二进制文件。

一个重要的提示:Conan 缓存对于 Conan 客户端是私有的 - 修改、添加、删除或更改 Conan 缓存中的文件是未定义的行为,可能会导致破坏。