🤖 roboto_origin_03 Wiki
首页 / 固件 / 构建流程与脚本编排

本文档聚焦 OrangePi 构建系统的脚本级执行流程与编排机制,面向已具备基础镜像编译经验、希望理解"一条 build.sh 命令背后发生了什么"的中级开发者。内容覆盖从入口脚本到主循环、从编译阶段到镜像打包的完整调用链,以及支撑这套流程的扩展钩子与配置聚合机制。本文不深入讲解具体板卡参数或内核补丁技术,这些主题将在同系列后续页面中展开。

整体架构概览

OrangePi 构建系统采用单入口、多阶段、插件化的 Bash 脚本架构。build.sh 作为唯一入口,负责环境预检与配置注入;随后将控制权移交 main.sh,由其内部的 do_default() 函数按序编排源码下载、交叉编译、根文件系统构建和镜像打包四大阶段。scripts/ 目录下的各子脚本按功能垂直拆分:通用工具、编译逻辑、系统初始化、镜像辅助、扩展管理等,彼此通过 source 聚合,而非独立进程调用。

flowchart TD
    A[build.sh<br/>入口脚本] -->|加载配置<br/>source extensions.sh| B[main.sh<br/>核心编排引擎]
    B -->|source| C[compilation.sh<br/>ATF/U-Boot/内核编译]
    B -->|source| D[debootstrap.sh<br/>根文件系统与镜像打包]
    B -->|source| E[configuration.sh<br/>配置聚合与扩展初始化]
    B -->|source| F[general.sh<br/>通用函数与主机准备]
    B -->|source| G[extensions.sh<br/>钩子管理与扩展编排]
    E -->|初始化| G
    C -->|输出 .deb| H[output/debs/]
    D -->|输出 .img| I[output/images/]
    F -->|准备主机环境| B

这套设计的核心优势在于:各子脚本通过 Bash 的 source 机制共享同一变量命名空间,使得跨阶段的编译产物路径、板级参数和工具链配置能够无缝传递;同时 extensions.sh 引入的钩子系统允许用户在不修改核心脚本的前提下,向任意阶段注入自定义逻辑。

Sources: build.sh, main.sh

入口脚本:build.sh 的职责边界

build.sh 并不直接执行编译动作,而是完成三项启动前准备工作

第一,源码路径与白名单检测。脚本通过 realpath 确定 SRC 根目录,并拒绝包含空格的路径,以避免后续交叉编译工具链解析异常。若当前为 Git 仓库且无本地修改,还会尝试自动拉取远端更新或切换到用户指定的 LIB_TAG 分支。

第二,配置文件的发现与加载build.sh 支持通过命令行参数指定配置(如 ./build.sh config-robopi1.conf),否则回退到 userpatches/config-default.conf。配置文件被 source 进当前 Shell 后,其内定义的 BOARDBRANCHBUILD_OPT 等变量立即生效。紧接着加载 scripts/extensions.sh,为后续扩展钩子做准备。

第三,分发到主引擎或批量构建器。根据 BUILD_ALL 变量的值,build.sh 在最后一步选择进入 main.sh(单镜像构建)或 build-all-ng.sh(批量构建)。所有命令行传入的 KEY=VALUE 参数也在此时被解析并覆盖配置文件中的同名变量。

Sources: build.sh, build.sh

核心编排引擎:main.sh 与 do_default()

main.sh 是构建系统的"中央调度器"。被 build.sh 加载后,它首先通过一系列 source 语句将 scripts/ 目录下的功能模块全部聚合到当前 Shell 上下文,包括负责编译的 compilation.sh、负责系统初始化的 debootstrap.sh、负责镜像辅助的 image-helpers.sh,以及提供扩展能力的 extensions.sh

随后,main.sh 在第 392 行加载 configuration.sh。该脚本不仅设置大量默认值(如镜像压缩算法、时区、缓存版本号),还会在第 165 行调用 initialize_extension_manager(),扫描所有已定义的钩子函数并按命名规则排序,生成最终的调用链。

若用户未通过环境变量或配置文件指定 BOARDBUILD_OPT 等关键选项,main.sh 会启动基于 whiptail 的交互式菜单,依次引导选择板卡、构建目标(u-boot / kernel / rootfs / image)、内核分支和发行版。这种**"配置即代码,缺失则交互"**的策略兼顾了 CI 自动化与本地手工调试的两种场景。

当所有变量就绪后,main.sh 调用 do_default() 函数进入主构建循环。该函数按以下阶段严格顺序执行:

阶段 关键动作 对应脚本/函数
环境准备 检查并安装主机依赖、下载交叉编译工具链 prepare_host() in general.sh
源码获取 BUILD_OPT 条件拉取 U-Boot、Kernel、ATF 等源码 fetch_from_repo() in general.sh
扩展钩子 执行 fetch_sources_toolsbuild_host_tools 钩子 call_extension_method()
清理 根据 CLEAN_LEVEL 删除旧编译产物或缓存 cleaning() in general.sh
引导加载器 编译 ATF 与 U-Boot,输出 .deb compile_atf(), compile_uboot()
内核 配置并编译内核、DTB、模块、Headers compile_kernel()
用户空间包 编译固件、配置工具、BSP 包、桌面环境包 多个 compile_* 函数
根文件系统 创建或解压根文件系统缓存、安装分发特定软件包 debootstrap_ng()
镜像打包 分区、格式化、rsync、写入 bootloader、压缩 create_image()

每个阶段内部都嵌入了 REPOSITORY_INSTALL 检查机制:若某组件的 .deb 包已存在于本地缓存或远程仓库,且用户配置允许复用,则跳过该阶段的编译,直接采用预构建产物。这显著提升了重复构建的效率。

Sources: main.sh, main.sh, main.sh

配置加载与聚合:configuration.sh 的角色

configuration.sh 并非传统意义上的静态配置表,而是一个动态配置生成器。它在 main.sh 的中期被加载,此时板级配置(boards/*.conf)已经生效,因此 configuration.sh 可以基于已知的 BOARDFAMILYBOARDRELEASE 等变量,完成以下工作:

  1. 默认值兜底:为 ROOTPWDMAINTAINERDEB_COMPRESSROOTFS_TYPE 等变量设置安全默认值。
  2. 镜像源路由:根据 REGIONAL_MIRRORDOWNLOAD_MIRROR 自动切换主线内核、GitHub、Debian/Ubuntu 软件源的国内镜像(如 TUNA、华为、BFSU 等),减少国内构建时的网络阻塞。
  3. 包列表聚合:将 DEBOOTSTRAP_LISTPACKAGE_LISTPACKAGE_LIST_DESKTOP 等基础列表与板级、家族级、桌面级追加/移除列表进行合并与去重。PACKAGE_LIST_RM 中的包会通过正则从所有列表中剔除。
  4. 用户自定义覆盖:在最后阶段 source 用户自定义的 userpatches/lib.config,并触发 user_configextension_prepare_config 两个扩展钩子,允许最终调整任何变量。

configuration.sh 的末尾还会将完整的构建环境信息(主机 OS、架构、挂载点、构建配置)写入 output/debug/output.log,为后续排障提供快照。

Sources: configuration.sh, configuration.sh

扩展机制与钩子编排:extensions.sh

extensions.sh 是构建系统的编排插件框架,其设计目标是在不修改核心脚本的前提下,让用户、板级配置或外部模块向构建流程注入自定义逻辑。

框架由两个核心函数驱动:

call_extension_method(hook_name, ...)(第 21 行) : 该函数负责在指定钩子点执行所有已注册的实现。调用时通过 stdin 传入 Markdown 风格的文档字符串,用于自动生成扩展开发文档。例如,在源码下载完成后,main.sh 会调用:

call_extension_method "fetch_sources_tools" <<- 'FETCH_SOURCES_TOOLS'
*fetch host-side sources needed for tools and build*
FETCH_SOURCES_TOOLS

所有命名符合 fetch_sources_tools__* 模式的函数将被按序执行。

initialize_extension_manager()(第 51 行) : 在 configuration.sh 的第 165 行被调用。它通过 compgen -A function 扫描当前 Shell 中所有已定义的函数,筛选出包含双下划线 __ 的函数名,将其解析为 钩子点__实现名 的结构。实现名若以前导三位数字开头(如 010_init),则按数字排序;否则默认赋予 500_ 前缀,确保无依赖扩展之间拥有稳定但可覆盖的执行顺序。

扩展机制在构建流程中分布广泛,从 add_host_dependencies(增加主机依赖)、build_host_tools(构建宿主机工具)到 pre_update_initramfs(修改 initramfs 前)、post_umount_final_image(卸载最终镜像后),几乎覆盖了编译与打包的每个关键节点。关于如何编写自定义扩展,请参阅 扩展机制与自定义集成

Sources: extensions.sh, extensions.sh, configuration.sh

主机环境准备:prepare_host()

prepare_host() 位于 general.sh,是 do_default() 调用的第一个实质性阶段。它的职责是将任意一台兼容的 Ubuntu 主机转化为具备交叉编译能力的构建工作站

函数首先检查宿主机发行版,目前仅正式支持 focaljammynoble 三个 Ubuntu LTS 版本,可通过 NO_HOST_RELEASE_CHECK=yes 强制跳过。随后根据架构(amd64arm64)安装数十个构建依赖,包括 debootstrapqemu-user-staticbinfmt-supportccachedevice-tree-compiler 等。若检测到容器环境(Docker/LXC),还会自动禁用 apt-cacher-ng 和嵌套 chroot 编译,避免权限与网络冲突。

工具链管理是 prepare_host() 的另一项核心任务。对于 amd64 宿主机,脚本会检查 SRC/toolchains/ 目录,按需下载 Linaro 及 ARM 官方交叉编译器(如 gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu),并清理过期版本。针对 Cix 等特定芯片家族,还会切换为专用的工具链列表。

最后,函数创建标准化的目录结构:output/debsoutput/imagesoutput/debugcache/sourcescache/rootfs 等,并启用 binfmt_misc 以支持在宿主机上运行为目标架构编译的二进制文件,为后续的 debootstrapchroot 操作奠定基础。

Sources: general.sh

编译阶段编排:从 ATF 到内核

当主机环境就绪且源码已获取后,main.shdo_default() 进入编译流水线。该流水线采用条件短路策略:仅当所需 .deb 包不存在于 output/debs/ 时,才触发实际编译。

ATF(Arm Trusted Firmware) : 若板级配置定义了 ATFSOURCEBUILD_OPT 包含 u-bootimagecompile_atf() 会被调用。它负责编译可信固件,产物通常作为 BL31 被后续 U-Boot 打包流程引用。

U-Boot : compile_uboot() 处理引导加载器的配置、补丁应用、交叉编译和 .deb 打包。脚本支持自定义后处理钩子 uboot_custom_postprocess,并会根据板级家族选择不同的打包策略(如 Rockchip 的 mkimageloaderimage)。

内核 : compile_kernel() 是编译阶段最复杂的函数。它接收 KERNEL_CONFIGURE 参数决定是否启动 menuconfig,随后依次编译内核镜像、DTB、内核模块、内核头文件和固件镜像。若 BUILD_OPTkernel,编译完成后即终止流程并输出 .deb 路径;若为 image,则产物留存于 output/debs/ 供后续根文件系统安装。

用户空间组件 : 在 BUILD_OPTrootfsimage 时,do_default() 还会依次编译 orangepi-configorangepi-zshethercat-ighplymouth-theme-orangepiorangepi-firmware 等组件,以及可选的外部包(EXTERNAL_NEW == compile)。随后调用 create_board_package 生成板级支持包(BSP),并视桌面环境选项构建桌面相关 Deb。

Sources: compilation.sh, compilation.sh, main.sh

镜像构建编排:debootstrap_ng()

根文件系统与最终镜像的构建由 debootstrap.sh 中的 debootstrap_ng() 函数主导,该函数仅在 BUILD_OPTrootfsimage 时触发。整个流程可概括为缓存复用 → 系统安装 → 用户定制 → 分区打包四步。

缓存复用 : create_rootfs_cache() 首先检查 cache/rootfs/ 中是否存在与当前发行版、桌面选项、架构匹配的缓存包(以 lz4 压缩)。若命中,直接解压到 $SDCARD;否则从网络执行 debootstrap 从零创建基础系统,并在完成后压缩缓存以供后续复用。

系统安装 : 解压或创建完成后,脚本调用 install_distribution_specific() 注入发行版特有配置(如 sources.listfstablocale),随后调用 install_common() 安装内核、U-Boot、BSP 等本地编译好的 .deb 包。若配置了 EXTERNAL_NEW=compileprebuilt,还会通过 chroot_installpackages_local() 在 chroot 内安装额外包。

用户定制 : 在系统基础就绪后,customize_image() 会调用用户自定义脚本 userpatches/customize-image.sh,允许向根文件系统注入额外软件、配置文件或启动服务。随后执行 apt-get autoremove 清理无用依赖,并导出已安装包清单到日志目录。

分区与打包 : prepare_partitions() 根据板级配置计算分区表(boot、rootfs、可选的 UEFI 或加密分区),创建空的 .raw 镜像文件并通过 losetup 挂载为块设备。create_image() 随后使用 rsync$SDCARD 内容同步到挂载点,更新 initramfs,写入 U-Boot 到镜像的偏移位置,最后卸载、分离 loop 设备并将 .raw 移动到 output/images/。多个扩展钩子如 pre_update_initramfspre_umount_final_imagepost_umount_final_image 穿插其中,供高级用户干预最终镜像内容。

Sources: debootstrap.sh, debootstrap.sh, debootstrap.sh

批量构建模式:build-all-ng.sh

BUILD_ALL=yes 时,build.sh 将控制权交给 build-all-ng.sh,进入批量矩阵构建模式。该脚本从 external/config/targets.conf(或用户自定义的 userpatches/targets.conf)读取构建矩阵,每行定义一组 BOARD BRANCH RELEASE BUILD_TARGET BUILD_STABILITY BUILD_IMAGE

build_all() 函数会遍历该矩阵,对每个条目执行以下操作:先 unset_all 清空上一轮的所有板级变量,再 source 对应板卡配置,随后直接调用 main.sh 中已被加载的 do_default() 完成单次构建。通过 BRANCH_OVERRELEASE_OVERBOARD_OVER 等变量,用户可以限定只构建矩阵中的某个子集。

批量模式特别适用于持续集成场景:一次执行即可生成多个板卡、多个分支、多个发行版的完整镜像矩阵。日志和产物按构建时间分子目录存放,避免冲突。

Sources: build-all-ng.sh, build-all-ng.sh

构建输出目录结构

理解脚本的编排逻辑后,有必要明确其产物落盘位置。所有输出均位于 output/ 目录下(若配置文件中指定了 DEST,则以该值为准):

目录 内容说明
output/debs/ 本地编译生成的所有 .deb 包,按 U-Boot、内核、固件、板级支持包分类存放
output/debs-beta/ BETA=yes 时的 Beta 仓库包存储位置
output/images/ 最终生成的可烧录镜像(.img 及压缩后的 .img.xz
output/debug/ 构建日志,包括 hostdeps.logdebootstrap.loginstall.log 及扩展管理器的 extensions.log
output/cache/ 源码缓存(sources/)、根文件系统缓存(rootfs/)、Deb 缓存(debs/
output/config/ 运行时的配置快照

Sources: main.sh, general.sh

下一步阅读指引

本文档从脚本编排视角勾勒了 OrangePi 构建系统的整体脉络。若你希望深入以下具体领域,可按顺序阅读同系列后续页面: