创建您的第一个 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 文件夹:包含一个 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
、打包库的 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"
,但保留它是为了展示这个重要的方法。 在本例中,执行CMakeToolchain
generate()
方法将创建一个 conan_toolchain.cmake 文件,该文件将 Conansettings
和options
转换为 CMake 语法。 添加CMakeDeps
生成器是为了完整性,但在向配方添加requires
之前,它不是严格必要的。build()
方法使用CMake
包装器来调用 CMake 命令,它是一个薄层,将设法在本例中传入-DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake
参数。 它将配置项目并从源代码构建它。package()
方法将工件(头文件、库文件)从构建文件夹复制到最终的包文件夹。 这可以使用裸 “copy” 命令来完成,但在本例中,它利用了已有的 CMake install 功能(如果 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 缓存内的文件是未定义的行为,很可能导致损坏。