为消费者定义信息:package_info() 方法

在之前的教程章节中,我们解释了如何使用 package 方法 将库的头文件和二进制文件存储在 Conan 软件包中。依赖于该软件包的消费者将重用这些文件,但我们必须提供一些额外的信息,以便 Conan 可以将其传递给构建系统,并且消费者可以使用该软件包。

例如,在我们的示例中,我们正在构建一个名为 hello 的静态库,它将在 Linux 和 macOS 中生成一个 libhello.a 文件,或在 Windows 中生成一个 hello.lib 文件。此外,我们正在打包一个头文件 hello.h,其中包含库函数的声明。Conan 软件包最终在 Conan 本地缓存中具有以下结构

.
├── include
│   └── hello.h
└── lib
    └── libhello.a

然后,想要链接到此库的消费者将需要一些信息

  • Conan 本地缓存中 include 文件夹的位置,以搜索 hello.h 文件。

  • 要链接的库文件的名称(libhello.ahello.lib

  • Conan 本地缓存中 lib 文件夹的位置,以搜索库文件。

Conan 在 cpp_info ConanFile 属性中提供了对消费者可能需要的所有信息的抽象。此属性的信息必须在 package_info() 方法 中设置。让我们看看我们的 hello/1.0 Conan 软件包的 package_info() 方法

conanfile.py
...

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

    ...

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

我们可以看到几件事

  • 我们正在向 cpp_infolibs 属性添加一个 hello 库,以告知消费者他们应该链接该列表中的库。

  • 我们没有添加关于库和头文件打包所在的 libinclude 文件夹的信息。cpp_info 对象提供了 .includedirs.libdirs 属性来定义这些位置,但 Conan 默认将其值设置为 libinclude,因此在这种情况下不需要添加这些。如果您要将软件包文件复制到不同的位置,则必须显式设置这些。我们的 Conan 软件包中 package_info 方法的声明将等效于以下声明

conanfile.py
...

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

    ...

    def package_info(self):
        self.cpp_info.libs = ["hello"]
        # conan sets libdirs = ["lib"] and includedirs = ["include"] by default
        self.cpp_info.libdirs = ["lib"]
        self.cpp_info.includedirs = ["include"]

在 package_info() 方法中设置信息

除了上面我们解释的关于您可以在 package_info() 方法中设置的信息之外,还有一些典型的用例

  • 根据 settings 或 options 为消费者定义信息

  • 自定义生成器为消费者提供的某些信息,例如 CMake 的目标名称或 pkg-config 生成的文件名

  • 将配置值传播给消费者

  • 将环境信息传播给消费者

  • 为提供多个库的 Conan 软件包定义组件

让我们看看其中一些实际应用。首先,如果您尚未克隆项目源文件,请先克隆。您可以在 GitHub 上的 examples2 仓库中找到它们

$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/tutorial/creating_packages/package_information

根据 settings 或 options 为消费者定义信息

在本教程的这一部分中,我们对库和 recipe 引入了一些更改。让我们检查相关部分

库源文件中引入的更改

首先,请注意我们正在使用 libhello 库的 另一个分支。让我们检查库的 CMakeLists.txt

CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(hello CXX)

...

add_library(hello src/hello.cpp)

if (BUILD_SHARED_LIBS)
    set_target_properties(hello PROPERTIES OUTPUT_NAME hello-shared)
else()
    set_target_properties(hello PROPERTIES OUTPUT_NAME hello-static)
endif()

...

如您所见,我们正在根据我们将库构建为静态库 (hello-static) 还是共享库 (hello-shared) 来设置库的输出名称。现在让我们看看如何将这些更改转换为 Conan recipe。

recipe 中引入的更改

为了根据库的 CMakeLists.txt 中的更改更新我们的 recipe,我们必须根据 package_info() 方法中的 self.options.shared option 有条件地设置库名称

conanfile.py
class helloRecipe(ConanFile):
    ...

    def source(self):
        git = Git(self)
        git.clone(url="https://github.com/conan-io/libhello.git", target=".")
        # Please, be aware that using the head of the branch instead of an immutable tag
        # or commit is not a good practice in general
        git.checkout("package_info")

    ...

    def package_info(self):
        if self.options.shared:
            self.cpp_info.libs = ["hello-shared"]
        else:
            self.cpp_info.libs = ["hello-static"]

现在,让我们使用 shared=False (这是默认值,因此无需显式设置)创建 Conan 软件包,并检查我们是否正在打包正确的库(libhello-static.ahello-static.lib),以及我们是否在 test_package 中链接了正确的库。

$ conan create . --build=missing
...
-- Install configuration: "Release"
-- Installing: /Users/user/.conan2/p/tmp/a311fcf8a63f3206/p/lib/libhello-static.a
-- Installing: /Users/user/.conan2/p/tmp/a311fcf8a63f3206/p/include/hello.h
hello/1.0 package(): Packaged 1 '.h' file: hello.h
hello/1.0 package(): Packaged 1 '.a' file: libhello-static.a
hello/1.0: Package 'fd7c4113dad406f7d8211b3470c16627b54ff3af' created
...
-- Build files have been written to: /Users/user/.conan2/p/tmp/a311fcf8a63f3206/b/build/Release
hello/1.0: CMake command: cmake --build "/Users/user/.conan2/p/tmp/a311fcf8a63f3206/b/build/Release" -- -j16
hello/1.0: RUN: cmake --build "/Users/user/.conan2/p/tmp/a311fcf8a63f3206/b/build/Release" -- -j16
[ 25%] Building CXX object CMakeFiles/hello.dir/src/hello.cpp.o
[ 50%] Linking CXX static library libhello-static.a
[ 50%] Built target hello
[ 75%] Building CXX object tests/CMakeFiles/test_hello.dir/test.cpp.o
[100%] Linking CXX executable test_hello
[100%] Built target test_hello
hello/1.0: RUN: tests/test_hello
...
[ 50%] Building CXX object CMakeFiles/example.dir/src/example.cpp.o
[100%] Linking CXX executable example
[100%] Built target example

-------- Testing the package: Running test() --------
hello/1.0 (test package): Running test()
hello/1.0 (test package): RUN: ./example
hello/1.0: Hello World Release! (with color!)

如您所见,库的测试和 Conan test_package 都成功地链接到了 libhello-static.a 库。

属性模型:为特定生成器设置信息

CppInfo 对象提供了 set_property 方法,用于设置特定于每个生成器的信息。例如,在本教程中,我们使用 CMakeDeps 生成器来生成 CMake 构建项目所需的信息,该项目需要我们的库。CMakeDeps 默认情况下将使用与 Conan 软件包相同的名称为库设置目标名称。如果您查看 test_package 中的 CMakeLists.txt

test_package CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(PackageTest CXX)

find_package(hello CONFIG REQUIRED)

add_executable(example src/example.cpp)
target_link_libraries(example hello::hello)

您可以看到我们正在与目标名称 hello::hello 链接。Conan 默认设置此目标名称,但我们可以使用属性模型更改它。让我们尝试将其更改为名称 hello::myhello。为此,我们必须在 hello/1.0 Conan 软件包的 package_info 方法中设置属性 cmake_target_name

conanfile.py
class helloRecipe(ConanFile):
    ...

    def package_info(self):
        if self.options.shared:
            self.cpp_info.libs = ["hello-shared"]
        else:
            self.cpp_info.libs = ["hello-static"]

        self.cpp_info.set_property("cmake_target_name", "hello::myhello")

然后,将我们在 test_package 文件夹中的 CMakeLists.txt 中使用的目标名称更改为 hello::myhello

test_package CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(PackageTest CXX)
# ...
target_link_libraries(example hello::myhello)

并重新创建软件包

$ conan create . --build=missing
Exporting the recipe
hello/1.0: Exporting package recipe
hello/1.0: Using the exported files summary hash as the recipe revision: 44d78a68b16b25c5e6d7e8884b8f58b8
hello/1.0: A new conanfile.py version was exported
hello/1.0: Folder: /Users/user/.conan2/p/a8cb81b31dc10d96/e
hello/1.0: Exported revision: 44d78a68b16b25c5e6d7e8884b8f58b8
...
-------- Testing the package: Building --------
hello/1.0 (test package): Calling build()
...
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Conan: Target declared 'hello::myhello'
...
[100%] Linking CXX executable example
[100%] Built target example

-------- Testing the package: Running test() --------
hello/1.0 (test package): Running test()
hello/1.0 (test package): RUN: ./example
hello/1.0: Hello World Release! (with color!)

您可以看到 Conan 现在如何声明 hello::myhello 而不是默认的 hello::hello,并且 test_package 成功构建。

目标名称不是您可以在 CMakeDeps 生成器中设置的唯一属性。有关影响 CMakeDeps 生成器行为的属性的完整列表,请查看 参考

将环境或配置信息传播给消费者

您可以在 package_info() 中为消费者提供环境信息。为此,您可以使用 ConanFile 的 runenv_infobuildenv_info 属性

  • runenv_info Environment 对象,用于定义消费者在使用软件包时可能需要的环境信息,用于运行

  • buildenv_info Environment 对象,用于定义消费者在使用软件包时可能需要的环境信息,用于构建

请注意,无需将 cpp_info.bindirs 添加到 PATH 或将 cpp_info.libdirs 添加到 LD_LIBRARY_PATH,这些都由 VirtualBuildEnvVirtualRunEnv 自动添加。

您还可以在 package_info() 中定义配置值,以便消费者可以使用该信息。为此,请设置 ConanFile 的 conf_info 属性。

要了解有关此用例的更多信息,请查看 相应的示例

为提供多个库的 Conan 软件包定义组件

在某些情况下,一个 Conan 软件包可能提供多个库,对于这些情况,您可以使用 CppInfo 对象的 components 属性为每个库设置单独的信息。

要了解有关此用例的更多信息,请查看示例部分中的 components 示例