了解 Conan 包布局¶
在上一节中,我们介绍了可编辑包的概念,并提到它们在放入可编辑模式时可以开箱即用的原因在于 layout()
方法中当前的信息定义。让我们更详细地研究此功能。
在本教程中,我们将继续使用 say/1.0
包和 可编辑包教程中使用的 hello/1.0
使用者。
请首先克隆源代码以重新创建此项目。你可以在 GitHub 的 examples2 存储库中找到它们
$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/tutorial/developing_packages/package_layout
注意
我们在本示例中使用 CMake 预设。这需要 CMake >= 3.23,因为只有该版本才支持从 CMakeUserPresets.json
到 CMakePresets.json
的“include”。如果你不想使用预设,你可以使用类似的方法
cmake <path> -G <CMake generator> -DCMAKE_TOOLCHAIN_FILE=<path to
conan_toolchain.cmake> -DCMAKE_BUILD_TYPE=Release
Conan 将在你每次运行 conan install
时显示确切的 CMake 命令,以防你无法使用预设功能。
正如你所看到的,主文件夹结构是相同的
.
├── hello
│ ├── CMakeLists.txt
│ ├── conanfile.py
│ └── src
│ └── hello.cpp
└── say
├── CMakeLists.txt
├── conanfile.py
├── include
│ └── say.h
└── src
└── say.cpp
这里的主要区别在于,我们没有在 say/1.0
ConanFile 中使用预定义的 cmake_layout(),而是声明我们自己的自定义布局。让我们看看我们如何在 layout()
方法中描述信息,以便它在我们在 Conan 本地缓存中创建包时以及包处于可编辑模式时都能工作。
import os
from conan import ConanFile
from conan.tools.cmake import CMake
class SayConan(ConanFile):
name = "say"
version = "1.0"
exports_sources = "CMakeLists.txt", "src/*", "include/*"
...
def layout(self):
## define project folder structure
self.folders.source = "."
self.folders.build = os.path.join("build", str(self.settings.build_type))
self.folders.generators = os.path.join(self.folders.build, "generators")
## cpp.package information is for consumers to find the package contents in the Conan cache
self.cpp.package.libs = ["say"]
self.cpp.package.includedirs = ["include"] # includedirs is already set to 'include' by
# default, but declared for completion
self.cpp.package.libdirs = ["lib"] # libdirs is already set to 'lib' by
# default, but declared for completion
## cpp.source and cpp.build information is specifically designed for editable packages:
# this information is relative to the source folder that is '.'
self.cpp.source.includedirs = ["include"] # maps to ./include
# this information is relative to the build folder that is './build/<build_type>', so it will
self.cpp.build.libdirs = ["."] # map to ./build/<build_type> for libdirs
def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()
让我们回顾一下 layout()
方法。你可以看到我们正在为 self.folders
和 self.cpp
设置值。让我们解释一下这些值的作用。
self.folders¶
定义 say
项目的源代码结构以及 Conan 生成的文件和构建产物所在的文件夹。此结构与包是否处于可编辑模式或在 Conan 本地缓存中导出和构建无关。让我们为 say
包定义文件夹结构
say
├── CMakeLists.txt
├── conanfile.py
├── include
│ └── say.h
├── src
│ └── say.cpp
└── build
├── Debug --> Built artifacts for Debug
│ └── generators --> Conan generated files for Debug config
└── Release --> Built artifacts for Release
└── generators --> Conan generated files for Release config
由于我们的
CMakeLists.txt
在.
文件夹中,self.folders.source
设置为.
。我们将
self.folders.build
设置为 ./build/Release 或 ./build/Debug,具体取决于build_type
设置。这些是我们希望构建的二进制文件所在的文件夹。self.folders.generators
文件夹是我们为 Conan 生成器创建的所有文件设置的位置。在这种情况下,CMakeToolchain
生成器生成的所有文件都将存储在那里。
注意
请注意,以上值适用于单配置 CMake 生成器。要支持多配置生成器(例如 Visual Studio),你应该对此布局进行一些更改。有关支持单配置和多配置的完整布局,请查看 Conan 文档中的 cmake_layout()。
self.cpp¶
此属性用于定义使用者将在何处查找包内容(头文件、库等),具体取决于包是否处于可编辑模式。
cpp.package¶
首先,我们设置 cpp.package 的信息。这定义了包的内容及其相对于包在本地缓存中存储的文件夹的位置。请注意,定义此信息等效于在 package_info() 方法中定义 self.cpp_info。这是我们定义的信息
self.cpp.package.libs
:我们添加say
库,以便使用者知道他们应该链接它。这等效于在package_info()
方法中声明self.cpp_info.libs
。self.cpp.package.libdirs
:我们添加lib
文件夹,以便使用者知道他们应该在那里搜索库。这等效于在package_info()
方法中声明self.cpp_info.libdirs
。请注意,cpp_info
和cpp.package
中libdirs
的默认值均为["lib"]
,因此我们可以省略该声明。self.cpp.package.includedirs
:我们添加include
文件夹,以便使用者知道他们应该在那里搜索库头文件。这等效于在package_info()
方法中声明self.cpp_info.includedirs
。请注意,cpp_info
和cpp.package
中includedirs
的默认值均为["include"]
,因此我们可以省略该声明。
为了检查此信息如何影响使用者,我们将首先对 say
包执行 conan create
$ cd say
$ conan create . -s build_type=Release
当我们调用 conan create
时,Conan 会将配方和配方中声明的源代码移动到本地缓存中要导出的配方文件夹,然后创建一个单独的包文件夹来构建二进制文件并存储实际的包内容。如果你检查 [YOUR_CONAN_HOME]/p
文件夹,你会发现两个类似于以下内容的新文件夹
提示
你可以使用 conan cache 命令或检查 conan create 命令的输出,来获取此文件夹的确切位置。
<YOUR_CONAN_HOME>/p
├── sayb3ea744527a91 --> folder for sources
│ └── ...
│
└── say830097e941e10 --> folder for building and storing the package binaries
├── b
│ ├── build
│ │ └── Release
│ ├── include
│ │ └── say.h
│ └── src
│ ├── hello.cpp
│ └── say.cpp
└── p
├── include --> defined in cpp.package.includedirs
│ └── say.h
└── lib --> defined in cpp.package.libdirs
└── libsay.a --> defined in self.cpp.package.libs
你可以在那里识别我们在 layout()
方法中定义的结构。如果你现在构建 hello
使用者项目,它将在本地缓存中的该文件夹内由 cpp.package
定义的位置中搜索 say
的所有头文件和库
$ cd ../hello
$ conan install . -s build_type=Release
# Linux, MacOS
$ cmake --preset conan-release --log-level=VERBOSE
# Windows
$ cmake --preset conan-default --log-level=VERBOSE
...
-- Conan: Target declared 'say::say'
-- Conan: Library say found <YOUR_CONAN_HOME>p/say8938ceae216fc/p/lib/libsay.a
-- Created target CONAN_LIB::say_say_RELEASE STATIC IMPORTED
-- Conan: Found: <YOUR_CONAN_HOME>p/p/say8938ceae216fc/p/lib/libsay.a
-- Configuring done
...
$ cmake --build --preset conan-release
[ 50%] Building CXX object CMakeFiles/hello.dir/src/hello.cpp.o
[100%] Linking CXX executable hello
[100%] Built target hello
cpp.source 和 cpp.build¶
我们还在配方中定义了 cpp.source
和 cpp.build
属性。这些仅在包处于可编辑模式时使用,并指向使用者将用来查找头文件和二进制文件的位置。我们定义了
self.cpp.source.includedirs
设置为["include"]
。此位置相对于我们定义为.
的self.folders.source
。在可编辑包的情况下,此位置将是我们的项目所在的本地文件夹。self.cpp.build.libdirs
设置为["."]
。此位置相对于我们定义为 ./build/<build_type> 的self.folders.build
。在可编辑包的情况下,此位置将指向 <local_folder>/build/<build_type>。
请注意,其他的 cpp.source
和 cpp.build
定义也是可能的,具有不同的含义和用途,例如
如果我们有在源存储库中预编译的库(例如,已提交到 git),则可以使用
self.cpp.source.libdirs
和self.cpp.source.libs
。它们不是构建的产物,而是源的一部分。self.cpp.build.includedirs
可以用于存放在构建时生成的头文件的文件夹,这通常发生在某些代码生成器在开始编译项目之前被触发的情况。
为了检查此信息如何影响消费者,我们将首先将 say
包置于可编辑模式并在本地构建它。
$ cd ../say
$ conan editable add . --name=say --version=1.0
$ conan install . -s build_type=Release
$ cmake --preset conan-release
$ cmake --build --preset conan-release
您现在可以检查 say 项目的文件夹内容,您可以看到输出文件夹与我们使用 self.folders
定义的文件夹匹配。
.
├── CMakeLists.txt
├── CMakeUserPresets.json
├── build
│ └── Release --> defined in cpp.build.libdirs
│ ├── ...
│ ├── generators
│ │ ├── CMakePresets.json
│ │ ├── ...
│ │ └── deactivate_conanrun.sh
│ └── libsay.a --> no need to define
├── conanfile.py
├── include --> defined in cpp.source.includedirs
│ └── say.h
└── src
├── hello.cpp
└── say.cpp
现在我们已经将 say
包置于可编辑模式,如果我们构建 hello
消费者项目,它将在 cpp.source
和 cpp.build
定义的文件夹中搜索 say
的所有头文件和库。
$ cd ../hello
$ conan install . -s build_type=Release
# Linux, MacOS
$ cmake --preset conan-release --log-level=VERBOSE
# Windows
$ cmake --preset conan-default --log-level=VERBOSE
...
-- Conan: Target declared 'say::say'
-- Conan: Library say found <local_folder>/examples2/tutorial/developing_packages/package_layout/say/build/Release/libsay.a
-- Conan: Found: <local_folder>/examples2/tutorial/developing_packages/package_layout/say/build/Release/libsay.a
-- Configuring done
...
$ cmake --build --preset conan-release
[ 50%] Building CXX object CMakeFiles/hello.dir/src/hello.cpp.o
[100%] Linking CXX executable hello
[100%] Built target hello
$ conan editable remove --refs=say/1.0
注意
请注意,我们没有定义 self.cpp.build.libs = ["say"]
。这是因为在 self.cpp.source
和 self.cpp.build
中设置的信息将与 self.cpp.package
中设置的信息合并,以便您只需定义可编辑包中发生变化的内容。出于同样的原因,您也可以省略设置 self.cpp.source.includedirs = ["include"]
,但我们将其保留在那里是为了展示 cpp.source
的用法。
另请参阅
定义 layout() 当你打包第三方库时
定义 layout() 当你的 conanfile 在子文件夹中时
定义 layout() 当你想处理多个子项目时