创建您的第一个 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 的特定内容。
src 和 include 文件夹:包含简单的 C++“hello”库的文件夹。
test_package 文件夹:包含一个需要并链接到已创建包的示例应用程序。这不是强制性的,但有助于检查我们的包是否已正确创建。
让我们看看包配方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、打包库的author、license、包存储库的url以及包相关的topics等信息。
之后,有一个与二进制配置相关的部分。这部分定义了包的有效设置和选项。正如我们在消费包部分中所解释的那样
settings是项目范围的配置,不能在配方中默认设置。例如操作系统、编译器或构建配置,这些将是多个 Conan 包共有的。options是包特定的配置,可以在配方中默认设置。在这种情况下,我们可以选择将包创建为共享库或静态库,其中静态是默认选项。
之后,设置了exports_sources属性来定义哪些源文件是 Conan 包的一部分。这些是您想要打包的库的源文件。在这种情况下,是我们的“hello”库的源文件。
然后,声明了几个方法
该
config_options()方法(与configure()方法一起)允许对二进制配置模型进行微调。例如,在 Windows 上,没有fPIC选项,因此可以将其移除。该
layout()方法声明了我们期望找到源文件的位置以及在构建过程中生成的文件目标位置。示例目标文件夹是生成的二进制文件的位置以及 Conan 生成器在generate()方法中创建的所有文件的位置。在这种情况下,由于我们的项目使用 CMake 作为构建系统,我们调用了cmake_layout()。调用此函数将设置 CMake 项目的预期位置。该
generate()方法准备从源构建包。在这种情况下,可以将其简化为一个属性generators = "CMakeToolchain",但保留它是为了展示这个重要的方法。在这种情况下,执行CMakeToolchaingenerate()方法将创建一个conan_toolchain.cmake文件,该文件将 Conan 的settings和options转换为 CMake 语法。为了完整起见,添加了CMakeDeps生成器,但在配方中添加requires之前,它并不是严格必需的。该
build()方法使用CMake包装器调用 CMake 命令。这是一个简单的层,在这种情况下,它将传递-DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake参数。它将配置项目并从源代码构建它。该
package()方法将构件(头文件、库)从构建文件夹复制到最终的包文件夹。这可以通过裸的“copy”命令完成,但在这种情况下,它利用了现有的 CMake install 功能。如果 CMakeLists.txt 没有实现它,那么在package()方法中使用copy() 工具编写等效的代码会很容易。最后,
package_info()方法定义了消费者在使用此包时必须链接“hello”库。还可以定义其他信息,如包含或库路径。这些信息用于生成器(如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 缓存中的文件是未定义行为,很可能导致故障。