泰晓科技 -- 聚焦 Linux - 追本溯源,见微知著!
网站地址:https://tinylab.org

泰晓RISC-V实验箱,转战RISC-V,开箱即用
请稍侯

TinyBPT 和面向 buildroot 的二进制包管理服务(1):设计简介与框架

Petalzu 创作于 2024/11/19

Corrector: TinyCorrect v0.2-rc2 - [tounix spaces header urls] Author: 柴子轩 petalzu@outlook.com Date: 2024/09/27 Revisor: falcon falcon@tinylab.org Project: RISC-V Linux 内核剖析 Sponsor: PLCT Lab, ISCAS

前言

在 buildroot 的使用过程中,我们经常会遇到一些问题,比如因为网络链接导致的编译失败,编译时间过长,或者想要使用某些特定软件时需要重新编译整个系统等。

本项目旨在解决这些问题,为 buildroot 搭建国内镜像站并新建一套面向 buildroot 的二进制包管理服务。并提供一个简单的、易于使用的 buildroot 包管理工具,TinyBPT 因此诞生了。

软件源与包管理

软件源镜像方法是确认上游源,同步到本地然后设置下载链接配置,还需要给同步程序设置选项使其忽略错误,防止因为某个软件包的链接失效导致整个同步过程失败,也可增加镜像站稳定性。同时,要补充 buildroot 源代码镜像的文档。

在搭建镜像站的过程中,一般会确定镜像站的上游源,使用 rsync 或者 wget 等工具将上游源的数据同步到镜像站上,然后通过 HTTP 或者 FTP 等协议对外提供服务。添加 buildroot 的镜像源,需要将 buildroot 的软件包源同步到镜像站上,然后通过设置 buildroot 的配置文件,将软件包的下载地址指向镜像站的地址。

为了提供一个稳定并可以持续更新的 buildroot 国内镜像源,在兰州大学开源镜像站上,我使用 tsumugu 工具同步 buildroot 的官方镜像源,将同步后的数据上传到镜像站上,并对外提供服务。

在此基础上,我尝试设计了 buildroot 包管理工具 TinyBPT,全称为 Tiny Buildroot Packaging Tool,其设计初衷是一个 buildroot 的二进制包管理工具,主要处理 buildroot 的包依赖关系,提供包的安装、卸载等功能。

依赖和协议

我在调研了一些现有的包管理器,例如 apt 、 yum 、 dnf 、 pacman 等后认为,它们都提供了类似的功 能,但实现方式可能有所不同。例如, apt 使用 dpkg 来管理软件包,而 yum 使用 rpm ,等等。

在 apt 实现中,软件包的信息存储在 /var/lib/dpkg/status 文件中,这个文件包含了软件包的名称、版本、描述、依赖关系等信息。apt-get update 命令会从软件源获取最新的软件包信息,并更新 /var/lib/dpkg/status 文件。

因此在整个项目中,最核心的部分在于编译后二进制包的依赖问题。首先,buildroot 软件包并没有规范的依赖文件,大多数配置被写在 .mk 以及 .in,甚至 .in.host 文件中。这就导致了我需要自己去解析这些文件,提取出依赖关系。在通过对配置文件的简单分析后,我认为大部分都可以通过正则表达式来提取依赖关系。

在大多数 package 目录下的软件包配置文件中,.mk 文件格式大多不同,解析困难。但在大多数 .hash 文件中却有非常规范的 sha256、包名(含版本号)信息,例如:

# Locally calculated after checking pgp signature
sha256  97203a72cae99ab89a067fe2210c1cbf052bc492b479eca7d226d9830883b0bd  acl-2.3.2.tar.xz

# Locally calculated
sha256  a45a845012742796534f7e91fe623262ccfb99460a2bd04015bd28d66fba95b8  doc/COPYING
sha256  01b1f9f2c8ee648a7a596a1abe8aa4ed7899b1c9e5551bda06da6e422b04aa55  doc/COPYING.LGPL

因此可以直接使用切片函数来获得信息:

with open(hash_file_path, "r", encoding="utf-8") as hash_file:
    lines = hash_file.readlines()
    if len(lines) > 1:
        line = lines[1].strip()
        pos = line.rfind(" ")
        if pos != -1 and pos + 1 < len(line):
            return line[pos + 1:]

同样的,大多数 .in 文件也包含着软件包的依赖信息,例如:

config BR2_PACKAGE_BASH
	bool "bash"
	# uses fork()
	depends on BR2_USE_MMU
	depends on BR2_PACKAGE_BUSYBOX_SHOW_OTHERS
	select BR2_PACKAGE_NCURSES
	select BR2_PACKAGE_READLINE

因此,可以通过正则表达式来提取依赖信息:

dep_regex = re.compile(r"BR2_PACKAGE_\w+")

处理提取出的依赖数据等过程则用到了不同的脚本,最终将依赖数据存储在一个 JSON 文件中,以便后续的使用。因为有许多的配置文件并非按照上述格式,所以部分数据可能会有遗漏。

协议问题的解决则尤为烦人,因为默认配置编译出的 buildroot 系统中自带 busybox,其中的 wget 仅支持 http 协议,而大部分的软件包都是通过 https 协议下载的。因此,我需要使 TinyBPT 支持 https 协议的下载,以保证兼容性。我使用了基于 MIT 协议的第三方库 cpp-httplib 支持 HTTPS 传输,并配置 CA certificates extracted from Mozilla 作为 CA 证书。

httplib::SSLClient cli(mirror_url.c_str());
cli.set_ca_cert_path("/etc/ssl/certs/cacert.pem");

我也使用了 nlohmann/json 库来处理 JSON 文件。

TinyBPT 基本框架

TinyBPT 的结构如下:

                            +-------------------+       +-------------------+
                            |                   |       |                   |
                            |    main.cpp       |       |    utils.cpp      |
                            |                   |       |                   |
                            |  - Handle CLI args|       |  - String handling|
                            |  - Call pkg funcs |       |  - Time retrieval |
                            |                   |       |  - Pkg info lookup|
                            +---------+---------+       +---------+---------+
                                    |                           |
                    cacert.pem      |                           |
                         |          v                           v
+---------+---------+    |  +---------+---------+       +---------+---------+
|      server       |    |  |                   |       |                   |
|                   |    v  |  tinybpt.cpp      |<------|  json_utils.cpp   |
| - Metadata Service|------>|                   |       |                   |
| - Download Service|       |  - Install pkg    |       |  - Handle JSON    |
| - Package Storage |       |  - Uninstall pkg  |       |  - List pkgs      |
| - Compile Package |       |  - Download pkg   |       |  - Find pkgs      |
|                   |       |  - Manage deps    |       |  - Update pkg info|
|                   |       |                   |       |  - Read config    |
+---------+---------+       +---------+---------+       +---------+---------+
                                    |                           |
                                    v                           v
                            +---------+---------+       +---------+---------+
                            |                   |       |                   |
                            |  Config struct    |       |  Package struct   | <--- tinybpt_db.json
                            |                   |       |                   |
                            |  - Mirror URL     |       |  - Name           |
                            |  - Version info   |       |  - Version        |
                            |  - config.json    |       |  - Info           |
                            |                   |       |  - Download time  |
                            |                   |       |  - Dependencies   |
                            +-------------------+       +-------------------+

源码结构说明如下:

  • src/
    • main.cpp: 处理命令行参数,调用包管理功能。
    • utils.cpp: 提供字符串处理、时间获取和包信息查找等功能。
    • tinybpt.cpp: 实现包的安装、卸载、下载和依赖管理功能。
    • json_utils.cpp: 处理 JSON 格式的包元数据,读取配置文件。
  • tinybpt_db.json: 存储配置信息,如镜像 URL 和版本信息。
  • cacert.pem: CA 证书,用于安全连接。

main.cpp 中的主函数负责处理命令行参数,调用包管理功能,其分析命令行输入的相关参数,然后调用 tinybpt.cpp 中的函数来执行相应的操作。

int main(int argc, char *argv[]) {
    if (argc < 2) {
        print_help();
        return 1;
    }
    ···

tinybpt.cpp 中包含了实现 TinyBPT 主要功能的函数。

例如函数 download_and_install_package,用于下载并安装指定的软件包。通过 find_version_by_name 查找软件包版本,构建下载地址并指定下载路径。调用 download_package 下载软件包,如果下载失败,输出错误信息并返回。读取软件包信息 read_package_info,遍历 packages 列表,找到匹配的软件包并更新本地信息。

void download_and_install_package(const std::string &name, const std::string &url) {
    std::string version = find_version_by_name(name, db_file);
    std::string package_url = url + version + ".tar.gz";
    std::string output_path = download_path + "/" + name + "/";
    bool download_success = download_package(package_url, output_path, name);
    if (!download_success) {
        std::cerr << "Download and installation failed for package: " << name << std::endl;
        return;
    }
    read_package_info(db_file, "packages");
    for (const auto &pkg : packages) {
        if (pkg.name == name) {
            update_local_package_info(db_file, pkg);
            break;
        }
    }
}

因为处理 JSON 文件的操作较为繁琐,所以我将其封装在 json_utils.cpp 中,以便于调用。

例如函数 find_package 通过读取数据库文件中的软件包信息,查找并输出指定名称的软件包的详细信息(包括版本、信息和依赖项),如果未找到则输出提示信息。

void find_package(const std::string &name, const std::string &key) {
    read_package_info(db_file, key);
    for (const auto &pkg : packages) {
        if (pkg.name != name)
            continue;
        std::cout << "Found package: " << pkg.name << " (" << pkg.version << ")" << std::endl;
        std::cout << "Info: " << pkg.info << std::endl;
        std::cout << "Dependencies: " << pkg.dependencies << std::endl;
        return;
    }
    std::cout << "Package not found: " << name << std::endl;
}

utils.cpp 则提供了一些功能相关的辅助函数。

例如函数 get_current_time 用于获取当前时间,返回一个格式化的时间字符串。

std::string get_current_time() {
    std::time_t now = std::time(nullptr);
    char buf[80];
    std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&now));
    return std::string(buf);
}

二进制包编译与服务端配置

使用 buildroot 的软件包进行交叉编译成 RISC-V 架构的二进制包,则需要借助 buildroot 编译过程中的中间文件,其保存在 output/build/ 目录下。在编译过程中,buildroot 会将软件包的源码下载到 output/build/ 目录下,并在此目录下进行编译。编译完成后,buildroot 会将编译好的二进制包安装到 output/target/ 目录下。

基于此,我使用脚本将 output/build/ 目录下编译成功的二进制包 make install 到指定目录下,然后将其打包成 tar.gz 文件,并在与配置文件进行对比处理后,上传到服务器上。

服务器端的配置如下:

buildroot/ -- 源码镜像,直接镜像 buildroot 官方所有包的源码库

buildroot-pkgs/  -- 二进制包发布目录
  riscv64/       -- 处理器架构,独立的话方便第三方镜像
    main/2024.02.6/   -- main 用于存放所有 buildroot lts 版本的二进制包(动态编译)
    tools/v6.10/     -- tools 用于存放 Linux lts 内核版本下的 tools 二进制包(以静态编译)
    rootfs/           -- rootfs 用于存放 rootfs 包
    tinybpt/
      tinybpt-v0.1.0.tar.gz    -- tinybpt 二进制包
  aarch64/
    main/2024.02.6/
    tools/v6.10/
  x86_64/
    main/2024.02.6/
    tools/v6.10/

总结

本文介绍了 TinyBPT 以及面向 buildroot 的二进制包管理服务的设计简介与框架。TinyBPT 的设计初衷是一个 buildroot 的二进制包管理工具,主要处理 buildroot 的包依赖关系,提供包的安装、卸载等功能,主要包括了主程序、包管理、JSON 文件处理和辅助函数等模块。TinyBPT 的设计思路是通过解析依赖文件,提取出软件包的依赖关系,然后通过 HTTPS 协议下载软件包,最后安装软件包。

其他文章将继续介绍客户端和服务端的使用方法。

参考资料



Read Album:

Read Related:

Read Latest: