创建你的第一个 Conan 包¶
在之前的章节中,我们使用了 Conan 包(例如 Zlib 包),首先使用了 conanfile.txt,然后使用了 conanfile.py。但是 conanfile.py recipe 文件不仅用于使用其他包,它也可以用于创建你自己的包。在本节中,我们将解释如何使用 conanfile.py recipe 创建一个简单的 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,它是主要的 recipe 文件,负责定义如何构建和使用包。
CMakeLists.txt: 一个简单的通用 CMakeLists.txt,其中没有任何关于 Conan 的特定内容。
src 和 include 文件夹:包含简单的 C++ “hello” 库的文件夹。
test_package 文件夹:包含一个 example 应用程序,它将 require 并链接到创建的包。这不是强制性的,但它对于检查我们的包是否正确创建很有用。
让我们看一下包 recipe 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"]
让我们简要解释一下 recipe 的不同部分
首先,你可以看到定义的 Conan 包的 名称和版本
name
: 一个字符串,最少 2 个,最多 100 个小写字符,用于定义包名称。它应该以字母数字字符或下划线开头,并且可以包含字母数字字符、下划线、+、.、- 字符。version
: 这是一个字符串,可以取任何值,与name
属性的约束相同。如果版本遵循X.Y.Z-pre1+build2
形式的语义版本控制,则该值可以用于通过版本范围而不是确切版本来 require 此包。
然后你可以看到一些定义 元数据 的属性。这些是可选的但建议使用的,并定义了诸如包的简短 description
、打包库的 author
、license
、包仓库的 url
以及包相关的 topics
。
之后,有一个与二进制配置相关的部分。本节定义了包的有效 settings 和 options。正如我们在 使用包章节 中解释的那样
settings
是项目范围的配置,无法在 recipe 中设置默认值。例如操作系统、编译器或构建配置等,这些对于多个 Conan 包来说是通用的options
是包特定的配置,可以在 recipe 中设置默认值。在本例中,我们可以选择将包创建为共享库或静态库,默认情况下为静态库。
之后,设置了 exports_sources
属性,以定义哪些源文件是 Conan 包的一部分。这些是你想要打包的库的源文件。在本例中,是我们 “hello” 库的源文件。
然后,声明了几个方法
config_options()
方法(以及configure()
方法)允许微调二进制配置模型。例如,在 Windows 上,没有fPIC
option,因此可以将其删除。layout()
方法声明了我们期望找到源文件和构建过程中生成的文件的目标位置。示例目标文件夹是生成的二进制文件和 Conan 生成器在generate()
方法中创建的所有文件的文件夹。在本例中,由于我们的项目使用 CMake 作为构建系统,我们调用cmake_layout()
。调用此函数将设置 CMake 项目的预期位置。generate()
方法准备从源码构建包。在本例中,它可以简化为属性generators = "CMakeToolchain"
,但这里保留它是为了展示这个重要的方法。在本例中,执行CMakeToolchain
generate()
方法将创建一个 conan_toolchain.cmake 文件,该文件将 Conan 的settings
和options
转换为 CMake 语法。CMakeDeps
生成器为了完整性而添加,但在将requires
添加到 recipe 之前,它不是严格必需的。build()
方法使用CMake
包装器来调用 CMake 命令。它是一个薄层,在本例中,它将传递-DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake
参数。它将配置项目并从源码构建它。package()
方法将构建文件夹中的 artifacts(头文件、库文件)复制到最终的包文件夹。这可以使用简单的 “copy” 命令完成,但在本例中,它利用了已有的 CMake install 功能。如果 CMakeLists.txt 没有实现它,那么使用 copy() 工具 在package()
方法中编写一个等效的实现也很容易。最后,
package_info()
方法定义了使用者在使用此包时必须链接到 “hello” 库。还可以定义诸如 include 或 lib 路径之类的其他信息。此信息用于生成器(如CMakeDeps
)创建的文件,以供使用者使用。这是关于当前包的通用信息,使用者可以使用这些信息,而无需考虑他们正在使用的构建系统,也无需考虑我们在build()
方法中使用的构建系统。
test_package 文件夹对于现在理解如何创建包并不重要。重要的部分是
test_package 文件夹不同于单元测试或集成测试。这些测试是 “包” 测试,用于验证包是否已正确创建,以及包使用者是否能够链接到它并重用它。
它本身是一个小的 Conan 项目。它包含自己的
conanfile.py
及其源代码,包括构建脚本,它依赖于正在创建的包,并构建和执行一个小的应用程序,该应用程序 require 包中的库。它不属于包。它只存在于源代码仓库中,而不是在包中。
让我们使用当前的默认配置从源码构建包,然后让 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()
方法,以检查包是否已正确创建。
我们现在可以验证 recipe 和包二进制文件是否在缓存中
$ conan list hello
Local Cache
hello
hello/1.0
conan create 命令接收与 conan install 相同的参数,因此你可以将相同的 settings 和 options 传递给它。如果我们执行以下行,我们将为 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 命令列出了本地缓存中存储的 recipes 和二进制文件。
一个 **重要** 提示:Conan 缓存是 Conan 客户端私有的 - 修改、添加、删除或更改 Conan 缓存内的文件是未定义的行为,可能会导致损坏。