将系统需求包装在 Conan 包中

Conan 可以管理系统包,让您可以轻松安装特定于平台的依赖项。当您需要安装提供特定驱动程序或仅在特定平台上工作的图形库的系统包时,这非常有用。

Conan 提供了一种使用 系统包管理器 工具安装系统包的方法。

在本例中,我们将探讨围绕系统库创建包装包所需的步骤,以及如何在 Conan 包中使用它。请注意,该包不会包含二进制工件,它只会管理检查/安装它们,调用 system_requirements() 和相应的系统包管理器(例如 Apt、Yum)。在本例中,我们将创建一个 Conan 包来包装系统 ncurses 需求,然后展示如何在应用程序中使用此需求。

请先克隆源代码以重新创建此项目。您可以在 GitHub 上的examples2 仓库中找到它们。

$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/examples/tools/system/package_manager/

您将找到以下树形结构

.
├── conanfile.py
└── consumer
    ├── CMakeLists.txt
    ├── conanfile.py
    └── ncurses_version.c

conanfile.py 文件是包装 ncurses 系统库的配方。最后,**consumer** 目录包含一个简单的 C 应用程序,该应用程序使用 ncurses 库,我们稍后会访问它。

在包装预构建的系统库时,我们不需要从源代码构建项目,只需安装系统库并打包其信息。在这种情况下,我们将首先检查打包 ncurses 库的 **conanfile.py** 文件

from conan import ConanFile
from conan.tools.system import package_manager
from conan.tools.gnu import PkgConfig
from conan.errors import ConanInvalidConfiguration

required_conan_version = ">=2.0"


class SysNcursesConan(ConanFile):
    name = "ncurses"
    version = "system"
    description = "A textual user interfaces that work across a wide variety of terminals"
    topics = ("curses", "terminal", "toolkit")
    homepage = "https://invisible-mirror.net/archives/ncurses/"
    license = "MIT"
    package_type = "shared-library"
    settings = "os", "arch", "compiler", "build_type"

    def package_id(self):
        self.info.clear()

    def validate(self):
        supported_os = ["Linux", "Macos", "FreeBSD"]
        if self.settings.os not in supported_os:
            raise ConanInvalidConfiguration(f"{self.ref} wraps a system package only supported by {supported_os}.")

    def system_requirements(self):
        dnf = package_manager.Dnf(self)
        dnf.install(["ncurses-devel"], update=True, check=True)

        yum = package_manager.Yum(self)
        yum.install(["ncurses-devel"], update=True, check=True)

        apt = package_manager.Apt(self)
        apt.install(["libncurses-dev"], update=True, check=True)

        pacman = package_manager.PacMan(self)
        pacman.install(["ncurses"], update=True, check=True)

        zypper = package_manager.Zypper(self)
        zypper.install(["ncurses"], update=True, check=True)

        brew = package_manager.Brew(self)
        brew.install(["ncurses"], update=True, check=True)

        pkg = package_manager.Pkg(self)
        pkg.install(["ncurses"], update=True, check=True)

    def package_info(self):
        self.cpp_info.bindirs = []
        self.cpp_info.includedirs = []
        self.cpp_info.libdirs = []

        self.cpp_info.set_property("cmake_file_name", "Curses")
        self.cpp_info.set_property("cmake_target_name", "Curses::Curses")
        self.cpp_info.set_property("cmake_additional_variables_prefixes", ["CURSES",])

        pkg_config = PkgConfig(self, 'ncurses')
        pkg_config.fill_cpp_info(self.cpp_info, is_system=True)

在此 **conanfile.py** 文件中,我们使用 系统包管理器 工具,基于不同的包管理器,在 system_requirements 方法中安装 ncurses 库。重要的是要注意,system_requirements 方法始终被调用,无论是在构建时,还是即使包已经安装。这对于确保系统安装了包非常有用。

每个包管理器可能使用不同的包名称来安装 ncurses 库,因此我们需要检查包管理器文档以首先找到正确的包名称。

另一个重要细节是 **package_info** 方法。在此方法中,我们使用 PkgConfig 工具,基于系统包管理器安装的 ncurses.pc 文件,填充 **cpp_info** 数据。

现在,让我们使用 **conanfile.py** 文件安装 ncurses 库

$ conan create . --build=missing -c tools.system.package_manager:mode=install -c tools.system.package_manager:sudo=true

请注意,我们正在使用 Conan 配置 tools.system.package_manager:mode 作为 **install**,否则 Conan 不会安装系统包,而只会检查它是否已安装。同样,tools.system.package_manager:sudo 作为 **True** 以使用 root 权限运行包管理器。作为此命令的结果,如果尚未安装,您应该能够在您的系统中看到安装的 **ncurses** 库。

现在,让我们检查 **consumer** 目录。该目录包含一个简单的 C 应用程序,该应用程序使用 ncurses 库。

**consumer** 目录中的 **conanfile.py** 文件是

from conan import ConanFile
from conan.tools.build import can_run
from conan.tools.cmake import cmake_layout, CMake
import os


class AppNCursesVersionConan(ConanFile):
    settings = "os", "compiler", "build_type", "arch"
    generators = "CMakeDeps", "CMakeToolchain"
    package_type = "application"
    exports_sources = "CMakeLists.txt", "ncurses_version.c"

    def requirements(self):
        if self.settings.os in ["Linux", "Macos", "FreeBSD"]:
            self.requires("ncurses/system")

    def layout(self):
        cmake_layout(self)

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

        app_path = os.path.join(self.build_folder, "ncurses_version")
        self.output.info(f"The example application has been successfully built.\nPlease run the executable using: '{app_path}'")

配方很简单。它需要我们刚刚创建的 **ncurses** 包,并使用 **CMake** 工具构建应用程序。应用程序构建完成后,它会显示 **ncurses_version** 应用程序路径,您可以根据需要手动运行它并检查其输出。

**ncurses_version.c** 文件是一个简单的 C 应用程序,它使用 ncurses 库以白色背景和蓝色文本打印 ncurses 版本

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <ncurses.h>


int main(void) {
    int max_y, max_x;
    char message [256] = {0};

    initscr();

    start_color();
    init_pair(1, COLOR_BLUE, COLOR_WHITE);
    getmaxyx(stdscr, max_y, max_x);

    snprintf(message, sizeof(message), "Conan 2.x Examples - Installed ncurses version: %s\n", curses_version());
    attron(COLOR_PAIR(1));
    mvprintw(max_y / 2, max_x / 2 - (strlen(message) / 2), "%s", message);
    attroff(COLOR_PAIR(1));

    refresh();

    return EXIT_SUCCESS;
}

**CMakeLists.txt** 文件是一个简单的 CMake 文件,用于构建 **ncurses_version** 应用程序

cmake_minimum_required(VERSION 3.15)
project(ncurses_version C)

find_package(Curses CONFIG REQUIRED)

add_executable(${PROJECT_NAME} ncurses_version.c)
target_link_libraries(${PROJECT_NAME} PRIVATE Curses::Curses)

CMake 目标 **Curses::Curses** 由我们刚刚创建的 **ncurses** 包提供。它遵循 FindCurses 的官方 CMake 模块。有关库和包含目录的信息现在可在 **cpp_info** 对象中获得,正如我们使用 **PkgConfig** 工具填充它一样。

现在,让我们使用 **conanfile.py** 文件构建应用程序

$ cd consumer/
$ conan build . --name=ncurses-version --version=0.1.0
  ...
  conanfile.py (ncurses-version/0.1.0): The example application has been successfully built.
  Please run the executable using: '/tmp/consumer/build/Release/ncurses_version'

构建应用程序后,它将显示可执行文件路径。您可以运行它以检查输出

$ /tmp/consumer/build/Release/ncurses_version

Conan 2.x Examples - Installed ncurses version: ncurses 6.0.20160213

如果显示的版本与此处显示的版本不同,或者可执行文件路径不同,请不要担心。它取决于安装在您系统中的版本以及您构建应用程序的位置。

就这样!您已成功打包系统库并在 Conan 包中使用它。