[aosc-os-abbs]: https://github.com/AOSC-Dev/aosc-os-abbs
[aosc-ipg]: https://website-2023.aosc.io/guidelines
[mbai-github]: https://github.com/MingcongBai
[newsroom-readme]: https://github.com/AOSC-Dev/newsroom/blob/master/README.md
[fhs]: https://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.html
[htop]: https://htop.dev
[pcp]: https://pcp.io
[arch-pkgsite]: https://archlinux.org/packages
[fedora-pkgsite]: https://packages.fedoraproject.org
[debian-pkgsite]: https://www.debian.org/distrib/packages
# 从手动编译到 “Autobuild”——安同 OS 开发及贡献者入门
## 前言
本文章旨在循序渐进地让热衷于 Linux 开发的读者深入安同 OS 的开发环境、开发工具和开发习惯。本文章相对于技术说明较为友好,并且尽量做到讲出细节,因此读者将无需在阅读本文期间查阅技术参考文档。
## 目录
[TOC]
# 第一章 认识安同开源社区和安同 OS
安同,意为 “安于同学合作”,是一个独立的社区,基于志愿、兴趣与合作存在。在这里您可以认识到各位业界大佬,与他们一同维护安同 OS 及周边项目。
安同 OS 是茫茫大海之中的另一个 Linux 发行版,同时也是安同开源社区的主要项目。您也许已经知道,安同 OS 以 “塞爆” 闻名——不拆分系统依赖、一个软件包解决一切,且使用 Debian 的包管理套件 dpkg 和 APT。与此同时,基于 libapt-pkg 的小熊猫包管理器 (oma) 具有比 APT 更友好的设计和体验,因此正在逐渐取代 APT。
## 1.1 社区文化
在您加入安同开源社区之前,您需要阅读并了解[《安同开源社区人际关系准则(长期意见征集稿)》][aosc-ipg]。这是安同社区内交流与合作的基本原则,敬请遵守。
### 1.1.1 成员组成
社区不分组织架构——您在日常中听到的 “特首”(指[白铭骢][mbai-github])只是口口相传的称呼而已;尽管看上去决策基本由特首进行,但特首也正在将决策工作下放给各位贡献者。社区成员都是贡献者,不论资历深浅、年龄大小、能力强弱,都应该对各种事情表达自己的意见。
### 1.1.2 社区的工作
您和大家作为志愿付出时间的贡献者,社区无法、也不能要求您付出时间。但是您应该意识到社区的工作从来都不是一个人进行的,所以请在主群及贡献者群组内保持沟通。您可以在任意时间选择任意工作,但一定要保持沟通。作为(潜在或已经加入的)贡献者,您可以做的工作主要分以下几种:
- 技术工作:
- 安同 OS 维护:引入、更新及修缮软件包,协助更新重要组件、协助实现新的系统特性等
- 安同 OS 基础设施(如构建工具链)维护:配合实现新系统特性、修缮代码、实现新打包功能等
- 安同 OS 周边项目(小熊猫包管理器、系统发行及安装镜像生成器、包管理基础设施 `p-vector` 等)的维护
- 文书工作:
- 编写、翻译及完善文档(安同 OS 及周边项目的使用指南、构建及包管理基础设施的技术参考文档等;也包括这篇文档)
- 编写社区期刊《安记冰室》、特辑《聊斋》;具体内容请见[宣发内容说明][newsroom-readme]
- 编写及完善网页的文案
- 日常维护:
- 协助测试 PR 内的软件包,在 PR 内提交测试结果
- 审阅 PR 的代码,给出审阅建议
- 协助测试未完成的周边项目等
- 人际交流:
- 协调贡献者之间出现的分歧
- 协助调查用户报告的问题
- 协助与愿意合作的上游厂商交流
- 外宣工作:
- 协助维护社区各宣发平台(微信公众号、微博、哔哩哔哩、知乎、X (Twitter) 等)
- 协助维护社区门户
本文主要介绍安同 OS 的维护工作。
### 1.1.3 贡献者及社区内交流
成为贡献者后,您需要加入 AOSC 贡献者群组。贡献者群组主要讨论新的想法、社区内部事务及需要征求贡献者意见的事务。
在安同社区相关群组讨论交流时,请尽量保持文明,并注意以下几点:
- 礼貌用语,拒绝攻击。对方再怎么烦人,他们依旧是人。您可以拒绝回应,但您不能任性地言语攻击。
- 放下架子,心平气和。大家出于兴趣爱好、志愿付出时间维持社区工作。获得认可不等于高人一等,否则您的态度势必招来贡献者的厌恶。
- 尽量忍耐,听取意见。合作是社区的根本,无论意见尖锐与否,您都不应该在贡献者内挑起争端,这是毫无建设性的。耐心地听取意见有助于您的项目继续推进。
- 遇到问题,不懂就问。尽管您可以推进任何更改,但社区依旧需要坚持一些底线。您在遇到问题、需要做出一些改动时,请先征求贡献者的回复。拿捏不定时,也请不要擅自做主。
## 1.2 维护体系
尽管安同 OS 使用 Debian 的包管理体系,但在源码树方面却是独立的——安同 OS 不使用 debhelper 打包,因此软件包的源码也不是基于 debhelper 编写和构建的。安同 OS 有一套自己的开发环境和软件包维护体系。其中包括如下开发工具链:
- 源码构建系统 Autobuild:负责读取软件包定义、根据脚本编译源码,最终打包出软件包。
- 源码管理系统 ACBS (Autobuild CI Build System):负责读取源码树,下载、解压源码,然后调用 Autobuild。
- 构建环境容器管理器 Ciel:负责管理运行 ACBS 和 Autobuild 的环境。
- ABBS 源码树:这里存放所有软件包的源码信息、软件包元数据和构建脚本。
此外,社区的自动化构建设施也较为完善。成为贡献者后,您可以轻松地通过社区的自动化设施维护软件包。这些设施包括:
- 自动打包机器人 BuildIt!:您只需在聊天框中输入命令,构建作业将由机器人自动发起。集成了安同 OS 的开发工作流。
- 自动更新检查设施 AutoPR: 定期检查软件包的更新,发起 PR 然后自动执行构建。
- 更新追踪设施 Anicca:定期检查软件包更新,以便 AutoPR 自动发起更新作业。
安同 OS 迭代的方式较为特别,我们称之为 “主题制” 迭代方式。主题制迭代方式是安同 OS 处于 “半滚动” 迭代状态的主要原因。
## 1.3 了解主题 (Topic)
主题 (Topic) ,也叫测试源,是较其他发行版比较独特的概念。安同 OS 更新软件包的方式既不像 Debian 那么啰嗦,也不像 Arch Linux 那样直接。笼统地说,Topic 是一次安同 OS 软件包更新或变动:更新一个包,开一个 Topic;修软件包的 Bug,开一个 Topic;一系列组件更新,开一个 Topic。
Topic 如何开展取决于您改动(更新)软件包的目的,因此一个 Topic 可以包含一个、多个甚至很多个包。比如,您可能需要更新一个应用软件,该更新不会影响到其他软件包,因此您需要开一个只更新该应用软件的 Topic;如果某个库需要更新,则该 Topic 内需要带上它的反向依赖;如果更新的内容是一个软件包套件(如桌面环境),则 Topic 内需要带上整个套件所包含的软件包。
Topic 在源码上的体现是 ABBS 源码树中的分支。除 `stable` 分支之外,其余的分支均为 Topic 分支。同样地,Topic 在安同 OS 中的体现方式是软件仓库中单独的发行(相当于 Debian 中的 stable, unstable, oldstable);同时在浏览 Topic 时,用户还可以看到 Topic 的描述等信息。
Topic 最终会合并到稳定源(ABBS 树中的 `stable` 分支),以此将改动推送给所有用户。Topic 在合并前开放的软件仓库可供所有人加入,在 Topic 的生命周期内提供测试结果及反馈。
## 1.4 基于 Topic 的工作流
安同 OS 遵循一套基于主题的测试源维护机制。所有更新和改动均通过 PR 进行,且所有 Commit 历史都是线性的——这意味着您无法创建 Merge Commit,所有合并操作均为变基 (Rebase) 操作。并且在合并前,感兴趣的用户或开发者将有机会测试 PR 内的软件包,尽可能地避免合并后出问题的情况。
## 1.5 安同 OS 的开发工具链
前文简单介绍了安同 OS 开发中会接触到的工具,它们在安同 OS 维护过程中相辅相成、各司其职。因此我们也可以称这组工具为 “工具链”,以体现这些工具间相互承接的关系。
### 1.5.1 Autobuild4
Autobuild4 处于安同 OS 开发环节中最底层的一环。Autobuild4 负责读取软件包定义及构建脚本,根据定义编译软件,最终打出软件包。然而 Autobuild4 并不负责软件包源码的下载和管理。这些工作由 ACBS 负责。
### 1.5.2 ACBS
ACBS(全称 Autobuild CI Build System),处于 Autobuild 之上,是用于管理安同 OS 软件包源码的工具。
安同 OS 的所有软件包源码定义及构建配置都存放在 [ABBS 树][aosc-os-abbs]中。由于 ACBS 会自动根据软件包安装情况分析构建序列,因此,一次作业可以调用多次 Autobuild4(即构建出多个软件包)。
但是 ACBS 不会保证系统环境的清洁(如是否包含未进入主源的修改等)。此时,需要 Ciel 来管理构建环境,以保持构建环境干净。
### 1.5.3 Ciel
Ciel 处在开发工具链的最上层,是安同 OS 的构建容器管理器。Ciel 负责更新、回滚等容器管理工作,并调用 ACBS 构建软件包。Ciel 使用 OverlayFS,以在每次构建之间回滚容器,保持干净的构建环境。由于安同 OS 深度使用 Systemd,因此 Ciel 使用 systemd-nspawn 管理构建容器。
Ciel 负责为每次构建新建容器,并在每次构建成功后回滚容器。在容器启动后,Ciel 会自动根据命令调用 ACBS 拉取源码、发起构建。每次构建完毕后,Ciel 会收集构建出的软件包至 Ciel 工作区目录下,供开发者上传或测试。
安同 OS 开发环节中严格使用 Ciel 容器打包,确保构建环境可靠且可复现。
### 1.5.4 BuildIt!
BuildIt! 是安同 OS 的自动化构建工具,它负责将作业派发到合适的构建机上,自动调用 Ciel 发起构建。您只需向 Telegram 机器人下达构建指令,BuildIt 会自动为您构建,并将构建结果反馈到 PR 上。因此,它深度集成了安同 OS 的开发工作流。但是,BuildIt! 只有在您成为贡献者后才能使用,敬请注意。
接下来,我们将从手动执行构建三连(`configure`,`make` 及 `make install`)逐渐深入到 Autobuild 的使用。
# 第二章 手动构建三连
本章内容将先介绍构建系统相关概念,然后基于通用的编译 “三连” 来介绍一些主流的构建系统的用法。
> [!Note] 提示
> 您可以根据自己对相关概念的熟悉程度,选择性地阅读本章内容,或者完全跳过本章内容。
## 2.1 认识构建系统
构建系统是负责根据构建配置及系统环境自动生成编译序列的程序,是为了统一化软件的编译过程而设立的。由于软件可能会在多个平台、不同系统上运行,因此需要有程序能统一处理这类情况。如,Windows 下的编译器可能不是 MSVC、macOS 上可能只有 LLVM/Clang、Linux 下各种依赖库的路径可能不一样等等情况。这类程序会根据项目维护者编写的构建配置,自动生成一系列执行的编译器命令。同时,这类程序还能检测依赖是否满足,或根据依赖的存在情况自动开关项目特性。
目前主流的构建系统有如下几个:
- GNU Autoconf:与 GNU Automake 及 GNU libtool 合称为 GNU Autotools。是大多数软件包使用的构建系统,生成 Makefile,使用 `make` 命令编译。比较古老,但非常可靠。
- CMake:较为现代化的构建系统。支持多种编译型语言,可以为多种执行器生成构建序列,如 GNU Make、Ninja 等。也可以使用 `cmake --build` 发起编译。
- Meson:较为灵活的构建系统。Meson 与 CMake 类似,Meson 也支持多种编译型语言。
<!-- - PEP 517: 针对 Python 模块的 “构建系统”。在大多数情况下,Python 的包只是复制粘贴;但 PEP 517 可以统一这类操作;同时也支持编译针对其它语言的绑定 (Binding)。 -->
## 2.2 构建系统相关概念
在介绍构建系统的用法及构建三连之前,您需要先理解一些概念。理解这些概念有助于您理解并使用这些构建系统。
### 2.2.1 源码目录和构建目录
构建系统要从源码目录读取构建信息,并在构建目录中生成供构建系统和编译器使用的文件。因此,您需要能够区分源码目录和构建目录。绝大部分构建系统都会单独区分这两个目录,以保持源码目录不受生成的文件污染。
源码目录(**S**ou**rc**e **Dir**ectory,简称 srcdir),是软件(或项目)源码所在的文件夹。在源码目录中有项目的说明 (README)、构建系统配置(定义)文件、开源协议全文、安装或编译说明 (INSTALL) 等文件。有些项目会将源码直接放在源码目录中,也有些项目会将源码放在源码目录的 `src` 文件夹中,以表明项目本身的代码处于其中,保持源码目录结构清晰。
构建目录 (Build directory),是构建系统存放生成的构建信息及中间文件、以及编译器输出二进制文件的目录。这些内容可以直接存放于源码目录中,但会污染源码目录。因此构建系统支持将源码目录独立存放,以尽量减小对源码目录的污染。
如果要独立存放构建目录,一般会将其放置在源码目录中,如源码目录下的 `build` 文件夹。您也可以将构建目录放在源码目录之外,但在运行构建系统时需要指定源码目录的路径。
### 2.2.2 安同 OS 的文件系统层次结构约定 (FHS)
FHS 全称 *Filesystem Hierarchy Standard*,规定了 Linux 发行版中系统分区的目录结构,FHS 标准由 Linux 协会制定。有些发行版不完全遵守这个约定,但在一定程度上兼容。FHS 的全文可以在这里阅读。
安同 OS 所采用的目录结构与通用的 FHS 有一些区别。安同 OS 要求任何项目的安装目录前缀 (Prefix) 都必须为 `/usr`。接下来,各类文件按如下目录放置:
- `/usr`: 一切安装的文件的基础(配置文件及运行时数据除外)
- `/usr/bin`: 存放可执行文件,不管文件是二进制还是脚本
- `/usr/lib`: 存放共享库文件(如 `lib*.so.*`)
- `/usr/libexec`: 存放属于主程序的协助程序的可执行文件(不应该被直接执行)
- `/usr/share`: 存放程序所使用的资源文件
- `/etc`: 程序配置文件存放目录的基础
- `/var`: 可变数据文件存放目录的基础
其中,“可变数据文件” 一般指程序运行后产生的数据、数据库等程序需要频繁读取和写入的数据。运行时的临时数据一般存放在 `/run` 或 `/tmp` 中。
> [!Note]
> 对于一些规模较大的项目,它们有时会在 `/etc` 或 `/usr` 目录下安装文件,但程序本身一般集中存在于一个目录中,如 `/opt/程序名`。典型的软件包括任何基于 Electron 框架或 Chromium 的项目、VMware Workstation、NVIDIA 显卡驱动程序等闭源软件。
>
> 对于这些软件,安同 OS 一般将它们的程序目录安装至 `/usr/lib/应用程序名`,如 VMware Workstation 的程序主目录一般安装在 `/usr/lib/vmware` 中。
### 2.2.3 路径前缀 (prefix) 和安装目标路径 (DESTDIR)
在编译三连中,您会接触到两个路径相关的参数:一个是路径前缀 (prefix),一个是安装目标路径 (DESTDIR, **Dest**ination **Dir**ectory)。
路径前缀,指的是存放不同类型文件的目录(如存放可执行文件的目录 `bin`、存放共享库的目录 `lib`)必须以指定的前缀路径为起点,且必须是以根目录 `/` 开始的绝对路径,如上文的目录结构中,`/usr` 就是系统规定的路径前缀。路径前缀一般情况下在没有指定时默认为 `/usr/local`,以避免直接覆盖系统文件。但在打包的情景中,任何软件包打包时都必须指定 `/usr` 为路径前缀,因为最终编译出的文件要安装到系统中,而非本地测试所用。
当然,不是所有目录都要以 `/usr` 开始。例如,存放配置文件的目录是 `/etc`,存放可变数据文件的目录是 `/var`。
安装目标路径,指的是打包三连的安装阶段中按目录结构安装文件时的起始点。也就是说,安装的文件都以指定的 DESTDIR 为起点,再加上前缀。如可执行文件会被安装到 `$DESTDIR/usr/bin`。`DESTDIR` 不指定时默认为空,这样所有文件会直接安装到系统。
> [!Note]
> **区分路径前缀与 DESTDIR**
>
> 可以将 `DESTDIR` 视为一个假的(或临时的)系统目录:在 `DESTDIR` 中有 `etc`, `usr`(路径前缀)和 `var` 文件夹。安装时指定了 `DESTDIR` 的话,该目录中就会出现系统目录结构,软件的所有文件都将安装其中。
> [!Caution]
> **尤其注意**
>
> 除非在为发行版打包,否则您无论如何都不能将路径前缀设置为 `/usr`。将路径前缀设置为 `/usr` 意味着执行安装步骤时,程序将直接覆盖系统下的文件(因为系统本身使用了 `/usr` 路径前缀)。
> 您可以不设置路径前缀,此时路径前缀会保持默认 (`/usr/local`)。您也可以将路径前缀设置到您可以直接读写的目录(无需 `sudo` 就可以安装的位置,如家目录下的某个专门存放编译程序的文件夹 `~/apps`)。
### 2.2.4 项目的依赖组件
“依赖” (Dependency) 是项目利用的项目之外的代码。项目为了简化开发,通常会集成第一方或第三方实现的代码(称为库),或者利用第三方工具为本项目提供服务,这些库就是项目的依赖。
一般来说,软件有两类依赖,分别是编译软件时所需的构建时依赖 (build-time dependency) ,这包括运行构建系统脚本和通过编译、链接流程生成二进制或脚本时所需的库、头文件及工具;以及运行时依赖 (run-time dependency) ,即运行软件二进制或脚本时所需的库和工具。
在编译软件时,我们需要满足的是构建时依赖;由于软件构建时需要链接运行时需要用到的共享库,因此在编译项目前需要同时安装构建依赖和运行时依赖。
> [!Important]
> 虽然编译期间需要同时安装运行时依赖及构建依赖,您仍旧需要区分构建依赖和运行时依赖。用户安装软件包时不应该带上没有必要的依赖。
典型的运行时依赖包括共享库本身(如 `lib` 开头的包)以及项目运行期间可能会调用的程序。而典型的构建时依赖主要包括文档和图表生成器(Graphviz、GNU Plot、Jinja2、Pandoc 及 Doxygen 等)及辅助生成其他文件的工具,如归档及压缩工具、ImageMagick(生成位图图标)等。
以 Kodi 为例,Kodi 作为一款功能完备的家庭娱乐中心软件,其运行期间必须能够解码各类多媒体文件,因此需要 FFmpeg、dav1d 等解码器库的帮助;除此之外,Kodi 项目中也有一份完备的开发和用户文档。这些文档是在构建期间生成的,供用户及开发者阅览。同时,Kodi 在构建期间需要生成一些图片资源。这些在构建期间生成文件需要的工具就是构建依赖,因为在日常运行期间不需要这些工具。
### 2.2.5 调查项目依赖的方式
有以下几种方式可以调查软件项目所需要的依赖:
1. 阅读软件说明文档
维护状况较好的软件项目一般会在其说明文件中(自述文件 `README`、编译安装指南 `INSTALL` 等)明确标出依赖的软件项目。此时,按照文档中所描述的依赖列表,使用系统中的包管理程序安装相应依赖即可。
2. 参考其他发行版的打包信息
有些情况下软件项目的文档并不清晰,无法从文档中找到依赖的软件项目。此时最快的方法是参考其他发行版的打包信息。成规模的发行版都会有查阅软件包信息的网站(俗称 “包站”),您可以在其他发行版的包站中搜索对应的软件包,即可得出该软件项目的依赖。值得参考的发行版有 [Arch Linux][arch-pkgsite]、[Fedora][fedora-pkgsite] 及 [Debian][debian-pkgsite]。
当然,便利的方法不一定是最可靠的。Arch Linux 和其他发行版一样,多少会存在打包质量问题,有的时候依赖可能不完整。因此,一般的建议是以其他发行版为参考,而后以源码和编译结果为准,慢工出细活。
3. 阅读构建系统的定义文件
通常来说,检查各类构建系统的配置脚本也是整理依赖的一个办法。各类构建系统的配置脚本将在下文介绍。
4. 排除法
排除法是以上几种方法都无法整理出依赖的情况下才用到的方法,同时也是最笨的办法。简单来说,排除法就是 “编译,看错误,装依赖”。先尝试编译一次项目,出错时检查构建日志里的 “not found” 等字样,然后按特征文件查找对应的软件包,并记录下来。一般来说,最容易出现依赖缺失的是各类文档生成器(如 Doxygen 和 xmlto 等)和可选数据生成器(如 Appstream-Glib 等)。
## 2.3 识别构建系统
您只需要看一看项目的根目录下有哪个构建系统的配置文件,即可确定这个项目所使用的构建系统。
> [!NOTE]
> **注意甄别**
>
> 有些项目里包含了多个构建系统的构建配置文件。您需要阅读项目的说明文件(如 `README`, `INSTALL`, `HACKING` 等)来确定这个项目所推荐的构建系统。
### 2.3.1 GNU Autotools
对于 GNU Autotools 套件,您需要查找项目目录下的如下文件之一:
- `configure`: 生成好的构建系统配置脚本。
- `configure.ac`: 用于生成 `configure` 脚本的定义文件,包含项目的依赖、构建序列及接受的可选依赖、定制选项。
- `configure.in`: 同上,是 `configure.ac` 的平替,但已经不再于新项目中使用。
- `Makefile.in`: Automake 生成 Makefile 时采取的模板或定义文件。
- `autogen.sh`: 用于生成 `configure` 脚本的脚本。
- `bootstrap`(可执行): 用于获取完整的项目源码、资源等的脚本。会自动调用 Autoconf 生成 `configure` 脚本。
由于历史久远,且 Autotools 的扩展性非常强,才会有这么多特征文件。但这些项目中一定存在 `configure.ac` 或 `configure.in` 。
> [!NOTE]
> **提示**
>
> Autobuild 只依靠项目文件夹中的 `autogen.sh`, `configure.ac` 及 `bootstrap` 文件来确定该项目是否在使用 Autotools。
> [!Important]
> **注意区分**
>
> 有些项目编写了 `configure` 脚本,但它并不是 Autotools 套件生成的——这样做只是为了照应开发及打包者的习惯,它们的参数也不与 Autotools 兼容。因此您无法使用 Autotools 模板编译这些 “假的 Autotools 项目”。
>
> 要区分 configure 脚本真假与否,请检查项目中是否存在其他特征文件。有些项目中的 `configure` 脚本的名称也不是全小写的,如 OpenSSL 的构建脚本叫 `Configure`。这类项目主要所用的构建系统一定不是 Autotools!
### 2.3.2 CMake
要确定项目是否在用 CMake,您只需要找项目的根目录下是否存在文件 `CMakeLists.txt`,且大小写一致。
`CMakeLists.txt` 尽管使用了纯文本文件的后缀名 `.txt`,但里面的内容却是 CMake 脚本。该脚本里记录了项目所需的依赖、构建序列及接受的可选依赖和定制选项。
### 2.3.3 Meson
Meson 的构建系统定义文件名为 `meson.build`,内含配置逻辑、项目所需的依赖及构建序列。有时您可以在项目的根目录下看到另一个文件 `meson_options.txt`。该文件存放着运行构建系统时可以接受的定制选项。
一般来说,您只需检查项目中是否存在 `meson.build` 文件就能确定该项目是否为 Meson 项目。
<!-- ### PEP 517
PEP 517 统一了 Python 界混乱的构建系统。PEP 517 指定的项目定义文件是 `pyproject.toml`。对于任何 Python 项目,请检查这个文件是否存在。
> [!Note] 提示
> 有些项目同时提供 Setuptools 的定义文件 `setup.py`。请优先使用 PEP 517。
-->
## 2.4 认识构建三连
“构建三连” 指利用构建系统构建软件包时的通用环节,即配置 (configure)、编译 (build/make) 和安装 (install)。下面将先介绍三连本身,然后再针对一些常用的构建系统讲述如何运行构建三连。
### 2.4.1 构建三连之配置 (Configure) 环节
配置环节是构建三连的第一步。配置环境负责生成构建序列,以供构建环节使用。配置环节的流程简述如下:
- 检测系统基本信息(如系统平台、编译器及链接器的类型和版本)
- 检测编译器及链接器特性
- 检测编译需要的头文件的存在情况
- 检查编译依赖(库)的存在情况
- 根据执行时提供的参数确定及检查项目包含的特性和(可选及特性依赖的必选)依赖
- 生成与系统环境及定制选项强相关的头文件(如 `config.h`)
- 生成供第二阶段的构建系统(GNU Make, Ninja 等)使用的构建序列文件
构建序列文件内包含所有需要被编译的源码文件名、这些文件所属的模块以及模块之间的依赖关系,以及针对所有文件的编译器命令。
### 2.4.2 构建三连之构建 (make) 环节
之所以称之为 “make”,是因为广泛使用的 Autotools 的构建三连中,构建环节需要执行 `make` 命令。在 make 环节,构建系统会调用构建系统执行器(如 GNU make 及 Ninja,Windows 还可以有 MSBuild),编译整个项目。
为了最大化构建机器的利用率,这些构建系统的执行器一般都支持并行编译 (Parallel building/compilation)。并行编译是指,按系统所拥有的 CPU 核心数量,发起对应数量的构建作业。如,包含 2,000 个文件的项目,用单个线程编译(一个接一个地调用 GCC)和同时用 8 个线程(一次发起 8 个 GCC)编译所花的时间有天壤之别。
对于大部分的构建系统来说,构建步骤一定会开启并行编译。您也可以指定 `-j` 参数,设置并行编译期间同时执行的编译作业数量:
```sh
# 基本上所有构建系统均使用 `-j` 指定线程数量
make -j16 # 同时运行 16 个编译作业
make -j$(nproc) # 同时运行与处理器数量相同的编译作业
```
### 2.4.3 构建三连之安装 (install) 环节
“安装”,是指将编译好的二进制、生成的文档及其他资源(图片、数据定义等)复制到系统内或指定目录。这些文件的目录结构在一定程度上遵循文件系统层次约定 (FHS)。
与普通的 “安装到系统” 不同的是,编译三连中的 “安装” 会把各类文件按目录层次先安装到特定目录下,以便基于该目录打包,即将特定目录视为文件系统的起始点。通常来说,指定这种目录的参数都叫 `DESTDIR`,大小写不一。这个变量名也是从 GNU 那里继承来的——大多数生成 Makefile 的构建系统一般都遵循这个不成文的约定。
## 2.5 不同构建系统的构建三连
### 2.5.1 GNU Autotools
GNU Autotools 的构建三连非常直接,大多数接触过 Linux 系统开发的人应该或多或少地了解过。首先,进入项目的根目录,然后依次运行构建三连:
1. `./configure`: 执行 configure 脚本,配置项目,生成编译序列文件
2. `make`: 调用生成在项目根目录的 Makefile,编译项目。
3. `make install DESTDIR=/some/where`: 执行 Makefile 中的安装步骤,将项目文件安装至指定目录中。
> [!Important] 注意
> 除非您明确要将编译的项目安装到系统中,否则请在执行 `make install` 时指定 `DESTDIR` 参数,并且确保 DESTDIR 没有指向根目录!
> [!Note] 提示
> 您很可能需要在运行 `make` 时指定 `-j 线程数` 参数,好让编译更快地完成。
有些项目可能没有预生成 `configure` 脚本,尤其是克隆的 Git 源码。有些项目即便有源码包可供下载,但里面预生成的 `configure` 脚本可能是过期的。因此,在您执行 `configure` 脚本之前需要先生成该脚本:
1. 如果源码目录下有 `bootstrap` 脚本,您可以直接执行 `./bootstrap` 脚本;`configure` 脚本会自动生成。
2. 否则,如果源码目录下有 `autogen.sh` 脚本,您可以直接执行 `./autogen.sh` 脚本。`configure` 脚本会自动生成。
3. 否则,如果源码目录下有 `configure.ac` 定义文件,您需要执行 `autoreconf -f -i` 命令,手动运行 Autoconf 生成 `configure` 脚本。
### 2.5.2 CMake
CMake 的三连也很简单,但 CMake 相比于 Autotools 较为灵活。CMake 严格区分源码目录和构建目录,因此在运行时请注意参数。
CMake 的构建三连命令如下:
1. `cmake -S . -B build`: 读取当前目录 (`.`) 下的定义文件,执行配置步骤。该步骤将会在 `-B` 参数指定的目录 (`build`) 内生成构建序列文件。
2. `cmake --build build`: 读取 `build` 目录中的构建信息,调用对应的构建执行器,发起编译。
3. `cmake --install --prefix /somewhere/else/usr build`: 读取 `build` 目录中的构建信息,执行对应的安装步骤。安装的文件将安装在 `/somewhere/else/usr` 目录下。
> [!Important]
> 绝大多数 CMake 项目均不允许将编译文件直接放置在源码目录中。因此,在执行 CMake 的配置阶段时,需要用 `-S` 参数指定源码目录,以及用 `-B` 参数指定构建目录。
> 指定的构建目录一般为 `build`(存放在源码目录下),您也可以自行起名,或将其放置于源码目录外。
> 之后的步骤均以生成的构建目录为准,因此后续步骤均需要提供构建目录的路径。
>[!Important]
> **区分 CMake 的 prefix 和真正的系统前缀及 DESTDIR**
>
> CMake 安装步骤的 `--prefix` 参数不完全与 DESTDIR 等价。由于 CMake 只处理 `bin`, `lib`, `share` 等二级目录,因此在指定 `--prefix` 参数时务必加上 `/usr`,即 `--prefix=DESTDIR/usr`。
> [!Note]
> 您很可能需要在运行构建步骤时指定 `-j 线程数` 参数,好让编译更快地完成。
### 2.5.3 Meson
Meson 的构建三连和 CMake 类似,Meson 也严格区分源码目录和构建工作区,因此 Meson 的配置阶段要求您至少提供构建目录。Meson 的构建三连命令如下:
1. `meson setup build`: 读取当前目录的 `meson.build` 文件,运行配置步骤,随后在 `build` 目录下生成编译信息。
2. `meson compile -C build`: 进入编译目录 `build`,运行构建系统执行器,编译整个软件或项目。
3. `meson install --destdir /somewhere/else -C build`: 进入编译目录 `build`,将软件或项目安装至 `/somewhere/else`。
> [!Warning]
> Meson 的配置命令是 `meson setup`。Meson 拥有 `meson configure` 命令,但其与实际的配置步骤不同。
> `meson configure` 用于在已经生成的构建目录中更改配置的值。
> [!Note]
> Meson 在 Linux 中只会生成供 Ninja 使用的构建序列文件。除非需要手动指定,否则您无需在编译时指定并行作业数量——Ninja 默认按系统处理器的核心数发起并行作业。
> [!Note]
> 由于 Meson 在 Linux 下只生成 Ninja 构建序列,因此您也可以进入构建目录运行 `ninja` 命令来运行编译过程。
## 2.6 独立构建 (Shadow build)
我们在前文中讲述了源码目录和构建目录的区别,并强调大多数情况下构建系统均要求单独放置编译工作区目录。独立构建就是将构建目录单独放置,不直接在源码目录中生成文件,避免污染源码。
由于独立编译不受构建目录及源码目录的位置影响,因此您可以在任何位置发起编译,只要您指定了正确的源码目录。
这里将分构建系统介绍各个构建系统的独立构建的使用方法及注意事项。
### 2.6.1 GNU Autotools
GNU Autotools 不严格要求独立构建。但有一些项目可能要求独立构建,也有一些古老的项目无法独立构建。因此,您在编译 Autotools 项目时,请先使用独立构建,如果出错,再直接于源码目录下配置项目。
要执行独立构建,您需要先在源码目录中(或其他地方)新建一个空目录,然后进入该目录:
```sh
# 进入源码文件夹
cd /path/to/source
# 创建编译文件夹
mkdir build
cd build
```
接下来,调用源码目录中的 `configure` 脚本(相对或绝对路径均可),在当前编译工作目录中执行三连:
```sh
# 调用处于上级目录的源码目录中的 configure 脚本
../configure --prefix=/usr
make -j16
make install DESTDIR=/somewhere/else
```
> [!Important]
> **切记** 执行 `make` 和 `make install` 时,请确保当前目录是由 `configure` 脚本生成的构建目录。
> 不过,您也可以在其他位置执行 `make`,但您需要指定 `-C 构建目录` 参数,好让 Make 找到正确的地方执行。
如项目无法独立构建,则需要直接在源码目录中运行 `configure` 脚本:
```sh
# 进入源码目录
cd /path/to/source
# 直接在源码目录下运行三连
./configure
make -j16
make install DESTDIR=/somewhere/else
```
### 2.6.2 CMake
CMake 默认要求独立编译,无法直接在源码目录中生成构建序列文件。不过,您无需在运行 CMake 的配置命令时指定构建目录,CMake 会帮您按指定名称创建构建目录。
CMake 配置阶段的命令参数非常多样。您可以选择其中一种:
- `cmake 源码目录 -B 构建目录`
- `cmake -S 源码目录 -B 构建目录`
- 创建并进入构建目录,然后执行 `cmake 源码目录`
在编译及安装阶段,您必须指定源码目录。CMake 在这两个阶段只读取对应文件夹内的构建信息:
```sh
cmake --build 构建目录
cmake --install 构建目录 --install-prefix /somewhere/else/usr
```
### 2.6.3 Meson
Meson 和 CMake 一样严格区分源码目录和构建目录,也会自动帮您创建构建目录。
Meson 的调用方式也非常灵活。您可以选择其中一种来运行 Meson 的配置步骤:
- 在源码目录执行 `meson setup 构建目录`
- 在任意位置执行 `meson setup 构建目录 源码目录`
至于构建和安装步骤,调用方式也非常灵活。您也可以采取其中一种:
```sh
# 在任意位置运行构建,需要指定构建目录
meson compile -C 构建目录
# 或者进入构建目录,无需指定路径:
cd 构建目录
meson compile
# 或者进入构建目录,直接运行 ninja:
cd 构建目录
ninja
```
```sh
# 在任意位置运行安装步骤,需要指定构建目录
meson install -C 构建目录 --destdir=/somewhere/else
# 或者进入构建目录,无需指定路径:
cd 构建目录
meson install --destdir=/somewhere/else
```
## 2.7 提供定制选项
绝大多数项目都允许在项目配置阶段指定参数,定制项目。定制的范围大致有:
- 指定编译时使用的编译器:GCC 或 Clang、GCCGo 或 Golang
- 微调传给编译器的参数:启用特定指令集优化、链接期间使用链接时优化 (LTO)、加固 (hardening) 二进制等
- 选择项目所依赖的库的不同实现:例如,SSL 实现有 OpenSSL, mbedTLS, WolfSSL 等
- 开关项目的特性:有些使用正则表达式的项目默认不开 PCRE2 支持
- 开关项目需要编译的组件:如 LLVM 套件中 Clang 是可选的
- 调整路径前缀
- 调整文件安装的位置
- 选择是否编译调试逻辑及调试信息,也就是以调试模式 (Debug build) 或发布模式 (Release build) 编译
正是因为构建系统的存在,项目的构建过程才能如此灵活,适应各种发行版的需求。本节内容也将按构建系统分别讲述如何确定能使用哪些参数,以及如何指定定制选项及编译器参数。
### 2.7.1 指定编译器及链接器
有时您也许会优先使用 GCC 构建项目,有时您也许会优先使用 Clang 构建项目,在 Windows 下您可能会优先使用 MSVC 构建项目。幸运的是,主流的构建系统都允许您选择希望使用的编译器。在介绍如何指定之前,先介绍一些行业黑话:
- `CC`: 即 **C C**ompiler,C 语言编译器。
- `CXX`: 即 **C++** (Compiler),C++ 语言编译器(写作 `CXX` 是因为 `+` 普遍被用作特殊用途,因此采用与其外观相近的 X 作为替代)。
- `LD`: 即 Linker,链接器。Unix 界的链接器由于历史原因一直都称作 `ld`,取自二进制加载器 (**L**oa**d**er) 及二进制链接编辑器 (**L**ink E**d**itor) 之名。
- `CPP`: 即 **C P**re**p**rocessor,C 语言预处理器。CPP 负责展开所有的预处理宏。
- `AS`: 即 **As**sembler,汇编器。
> [!Important]
> **注意区分**
>
> C++ 有时也叫做 CPP (**CP**lus**P**lus),而且其源代码的扩展名也是 `.cpp`。但是切勿与这里的 CPP 混淆:一般在构建系统中,我们以 CXX 称呼 C++,因为 CPP 代表预处理器。
这些简写非常常见,因此需要您牢记。本节内容将使用以上简写代替完整的名称。
对于 Autotools,其配置阶段可以按如下方式指定编译器及链接器:
```sh
../configure CC=gcc CXX=g++ LD=ld.gold
```
对于 CMake,其配置阶段需要按照 CMake 的方式指定编译器及链接器:
```sh
cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_LINKER_TYPE=LLD
```
> [!Important]
> CMake 2.39 起才支持使用 `CMAKE_LINKER_TYPE` 指定项目使用的链接器。
> 较旧版本的 CMake 需要在指定编译器参数时指定,具体见下节内容。
对于 Meson,需要您设置对应的环境变量来指定编译器及链接器:
```sh
# 以一行流的方式指定变量,然后运行 Meson:
CC=gcc CC_LD=ld.gold meson setup build
CXX=g++ CXX_LD=ld.gold meson setup build
# 您也可以将它们合在一起:
CC=clang CC_LD=ld.lld CXX=clang++ CXX_LD=ld.lld meson setup build
# 或者显式地设置环境变量,然后运行 Meson:
export CC=gcc CXX=g++
export CC_LD=ld.bfd CXX_LD=ld.bfd
meson setup build
```
### 2.7.2 指定编译器参数
有时您想在编译时启用比较激进的优化,或者指定编译器生成特定指令集扩展的指令,以此在一定程度上提升软件的性能。绝大多数构建系统均允许您在配置阶段追加编译期间传递给编译器的参数。
除了编译器外,您也可以为其他构建期间调用的编译工具传递参数。这些参数均以某种变量的形式存在,且其变量名一般都以 `FLAGS` 结尾,我们将其统称为 `FLAGS` 变量:
| 编译工具 | 简写 | 传参变量名 |
|:------------------:|:------:|:-----------:|
| C 编译器 | `CC` | `CFLAGS` |
| C++ 编译器 | `CXX` | `CXXFLAGS` |
| Objective-C 编译器 | `OBJC` | `OBJCFLAGS` |
| Rust 编译器 | `RUST` | `RUSTFLAGS` |
| 汇编器 | `AS` | `ASFLAGS` |
| 链接器 | `LD` | `LDFLAGS` |
通常情况下,您需要为每个工具指定多个参数,如启用优化并设置编译器至多生成哪些指令集的指令。举个例子,按以上条件用 GCC 手动编译 C 源文件:
```sh
# 运行 GCC,启用优化,并让编译器生成至多到 AVX2 指令集的指令(Haswell 之前的处理器将无法运行程序):
gcc -O2 -march=x86-64-v2 -mtune=haswell -mavx2
```
上例中,`-O2`、`-march=x86-64-v2`、`-mtune=haswell` 和 `-mavx2` 都是要指定给编译器的参数,都是 `CFLAGS` 的内容。`FLAGS` 变量是一整个字符串,传递的参数间用空格隔开。因为字符串内有空格,所以设置 `FLAGS` 时需要将变量的内容用引号括起来:
```sh
CFLAGS="-O2 -march=x86-64-v2 -mtune=haswell -mavx2"
```
接下来将分别介绍前文中三个主流构建系统指定编译器参数的方法。
对于 Autotools,您有两种方法指定构建工具的参数。一种方法是将其导出至环境变量,另一种是作为 `configure` 脚本的参数传递:
```shell
$ # 导出至环境变量
$ export CFLAGS="-O2 -march=x86-64 -mtune=sandybridge"
$ export CXXFLAGS="-O2 -march=x86-64 -mtune=sandybridge"
$ export LDFLAGS="-flto"
$ export CC=gcc CXX=g++ LD=ld.bfd
$ ../configure --prefix=/usr ...
$ # 或者,作为 configure 的参数传递
$ ../configure --prefix=/usr ... \
CFLAGS="-O2 -march=x86-64 -mtune=sandybridge" \
CXXFLAGS="-O2 -march=x86-64 -mtune=sandybridge" \
LDFLAGS="-flto"
```
同样地,CMake 也有两种方式指定这些参数,环境变量或定制选项:
```shell
$ # 导出至环境变量
$ export CFLAGS="-O2 -march=x86-64 -mtune=sandybridge"
$ export CXXFLAGS="-O2 -march=x86-64 -mtune=sandybridge"
$ export LDFLAGS="-flto"
$ cmake -S . -B build ...
$ # 或者,按照 CMake 参数提供:
$ cmake -S . -B build \
-DCMAKE_C_FLAGS="-O2 -march=x86-64 -mtune=sandybridge" \
-DCMAKE_CXX_FLAGS="-O2 -march=x86-64 -mtune=sandybridge" \
-DCMAKE_EXE_LINKER_FLAGS="-flto" \
-DCMAKE_SHARED_LINKER_FLAGS="-flto"
```
对于 Meson,您需要将各编译工具的参数导出至环境变量中:
```shell
$ # 导出至环境变量
$ export CFLAGS="-O2 -march=x86-64 -mtune=sandybridge"
$ export CXXFLAGS="-O2 -march=x86-64 -mtune=sandybridge"
$ export LDFLAGS="-flto"
$ meson setup build --prefix=/usr
```
### 2.7.3 调查能够指定的定制选项列表
“定制选项” 是配置阶段向构建系统提供的、会影响项目功能特性的参数。因此前文中才有 “定制” 一说。由于每个项目能够定制的范围不同,因此除了一些通用的路径方面的参数(如系统前缀及各种目录)及编译器相关的参数外,不同项目会有不同的定制选项。在您执行配置步骤前,您需要了解要构建的软件项目接受的自定义参数。
总的来说,构建系统可以使用的参数分为如下几类:
- 系统路径定制参数:用于指定系统前缀,以及微调各类文件安装的位置。单独指定安装位置的参数可以无视系统前缀,如将系统前缀指定为 `/usr`,但将共享库的安装位置指定到 `/my/libs`。
- 项目特性定制参数:用于启用或禁用项目特性。这些特性包括额外的文件或数据格式支持、通信协议支持、与其他编程语言的集成组件、项目默认没有启用的功能等。
- 可选依赖定制参数:用于启用项目默认没有依赖的组件。这些可选依赖同样也会增强项目的功能,只不过需要额外的库才能实现。不是所有系统都有这些组件,因此不会默认开启。
- 构建模式参数:用于启用调试信息或通常不会用到的调试逻辑。也就是选择调试模式 (Debug build) 或发行模式 (Release build)。通常情况下,发行模式中不会包含调试逻辑,并且会启用更激进的编译器优化。
> [!Note]
> 有些构建系统会提供带调试信息的发行模式。这意味着项目会以发行模式构建,但也会带上调试符号信息——这有助于软件出问题时协助用户调查原因。由于一般情况下用户不需要调试信息,因此在打包时,这些调试信息会被分离出软件包,转而打成专门的调试符号包。
不同构建系统查看可接受的参数的方式不同,指定定制选项的方式也不同。因此需要按构建系统分别讲述。
#### 2.7.3.1 Autotools
对于 Autotools,您可以在执行 `configure` 脚本时加上 `--help` 参数,`configure` 脚本会输出项目能够接受的参数列表。下例是 dpkg 的 `configure --help` 的输出:
:::spoiler
```
$ ./configure --help
'configure' configures dpkg 1.22.11-30-ga45d7 to adapt to many kinds of systems.
Usage: ./configure [OPTION]... [VAR=VALUE]...
To assign environment variables (e.g., CC, CFLAGS...), specify them as
VAR=VALUE. See below for descriptions of some of the useful variables.
Defaults for the options are specified in brackets.
Configuration:
-h, --help display this help and exit
--help=short display options specific to this package
--help=recursive display the short help of all the included packages
-V, --version display version information and exit
-q, --quiet, --silent do not print 'checking ...' messages
--cache-file=FILE cache test results in FILE [disabled]
-C, --config-cache alias for '--cache-file=config.cache'
-n, --no-create do not create output files
--srcdir=DIR find the sources in DIR [configure dir or '..']
Installation directories:
--prefix=PREFIX install architecture-independent files in PREFIX
[/usr/local]
--exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
[PREFIX]
By default, 'make install' will install all the files in
'/usr/local/bin', '/usr/local/lib' etc. You can specify
an installation prefix other than '/usr/local' using '--prefix',
for instance '--prefix=$HOME'.
For better control, use the options below.
Fine tuning of the installation directories:
--bindir=DIR user executables [EPREFIX/bin]
--sbindir=DIR system admin executables [EPREFIX/sbin]
--libexecdir=DIR program executables [EPREFIX/libexec]
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
--runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
--datarootdir=DIR read-only arch.-independent data root [PREFIX/share]
--datadir=DIR read-only architecture-independent data [DATAROOTDIR]
--infodir=DIR info documentation [DATAROOTDIR/info]
--localedir=DIR locale-dependent data [DATAROOTDIR/locale]
--mandir=DIR man documentation [DATAROOTDIR/man]
--docdir=DIR documentation root [DATAROOTDIR/doc/dpkg]
--htmldir=DIR html documentation [DOCDIR]
--dvidir=DIR dvi documentation [DOCDIR]
--pdfdir=DIR pdf documentation [DOCDIR]
--psdir=DIR ps documentation [DOCDIR]
Program names:
--program-prefix=PREFIX prepend PREFIX to installed program names
--program-suffix=SUFFIX append SUFFIX to installed program names
--program-transform-name=PROGRAM run sed PROGRAM on installed program names
System types:
--build=BUILD configure for building on BUILD [guessed]
--host=HOST cross-compile to build programs to run on HOST [BUILD]
Optional Features:
--disable-option-checking ignore unrecognized --enable/--with options
--disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
--enable-FEATURE[=ARG] include FEATURE [ARG=yes]
--enable-dependency-tracking
do not reject slow dependency extractors
--disable-dependency-tracking
speeds up one-time build
--enable-silent-rules less verbose build output (undo: "make V=1")
--disable-silent-rules verbose build output (undo: "make V=0")
--disable-nls do not use Native Language Support
--disable-rpath do not hardcode runtime library paths
--enable-shared[=PKGS] build shared libraries [default=no]
--enable-static[=PKGS] build static libraries [default=yes]
--enable-fast-install[=PKGS]
optimize for fast installation [default=yes]
--disable-libtool-lock avoid locking (might break parallel builds)
--disable-dselect do not build or use dselect
--disable-start-stop-daemon
do not build or use start-stop-daemon
--disable-update-alternatives
do not build or use update-alternatives
--disable-devel-docs build release docs
--enable-coverage whether to enable code coverage
--disable-largefile omit support for large files
--disable-unicode do not use Unicode (wide chars) support
--enable-mmap enable usage of unrealiable mmap if available
--enable-disk-preallocate
enable usage of disk size pre-allocation
--disable-compiler-warnings
Disable (detected) additional compiler warnings
--enable-compiler-sanitizer
Enable compiler sanitizer support
--enable-compiler-analyzer
Enable compiler analyzer support
--disable-compiler-optimizations
Disable (detected) compiler optimizations
--disable-linker-optimizations
Disable (detected) linker optimizations
--enable-year2038 support timestamps after 2038
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
--with-libiconv-prefix[=DIR] search for libiconv in DIR/include and DIR/lib
--without-libiconv-prefix don't search for libiconv in includedir and libdir
--with-libintl-prefix[=DIR] search for libintl in DIR/include and DIR/lib
--without-libintl-prefix don't search for libintl in includedir and libdir
--with-pic[=PKGS] try to use only PIC/non-PIC objects [default=use
both]
--with-aix-soname=aix|svr4|both
shared library versioning (aka "SONAME") variant to
provide on AIX, [default=aix].
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
--with-sysroot[=DIR] Search for dependent libraries within DIR (or the
compiler's sysroot if not specified).
--with-perllibdir=DIR perl modules directory
--with-devlibdir=DIR dpkg development library directory [LIBDIR]
--with-pkgconfdir=DIR dpkg configuration directory [SYSCONFDIR/dpkg]
--with-docspecdir=DIR dpkg specifications directory [DOCDIR/spec]
--with-methodsdir=DIR dpkg download methods directory
[LIBEXECDIR/dpkg/methods]
--with-admindir=DIR dpkg database directory [LOCALSTATEDIR/lib/dpkg]
--with-backupsdir=DIR dpkg database backups directory
[LOCALSTATEDIR/backups]
--with-logdir=DIR system logging directory [LOCALSTATEDIR/log]
--with-pkgconfigdir=DIR pkg-config .pc fragments directory
[DEVLIBDIR/pkgconfig]
--with-aclocaldir=DIR aclocal m4 fragments files directory
[DATADIR/aclocal]
--with-polkitactionsdir=DIR
polkit .policy actions directory
[DATADIR/polkit-1/actions]
--with-bashcompletionsdir=DIR
bash completions directory
[DATADIR/bash-completion/completions]
--with-zshcompletionsdir=DIR
zsh vendor completions directory
[DATADIR/zsh/vendor-completions]
--with-deb-compressor=COMP
change default dpkg-deb build compressor
--with-libz use z library for compression and decompression
--with-libz-ng use z-ng library for compression and decompression
--with-libbz2 use bz2 library for compression and decompression
--with-liblzma use lzma library for compression and decompression
--with-libzstd use zstd library for compression and decompression
--with-libselinux use selinux library to set security contexts
Some influential environment variables:
CC C compiler command
CFLAGS C compiler flags
LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a
nonstandard directory <lib dir>
LIBS libraries to pass to the linker, e.g. -l<library>
CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
you have headers in a nonstandard directory <include dir>
CPP C preprocessor
LT_SYS_LIBRARY_PATH
User-defined run-time library search path.
PERL Perl interpreter
PERL_LIBDIR Perl library directory
DPKG_SHELL default POSIX shell interpreter used by dpkg
DPKG_PAGER default pager program used by dpkg
CXX C++ compiler command
CXXFLAGS C++ compiler flags
CXXCPP C++ preprocessor
PATCH GNU patch program
TAR GNU tar program
PO4A po4a program
POD2MAN pod2man program
RT_LIBS linker flags for rt library
MD_LIBS linker flags for md library
Z_LIBS linker flags for z library
Z_NG_LIBS linker flags for z-ng library
BZ2_LIBS linker flags for bz2 library
LZMA_LIBS linker flags for lzma library
ZSTD_LIBS linker flags for zstd library
PKG_CONFIG path to pkg-config utility
PKG_CONFIG_PATH
directories to add to pkg-config's search path
PKG_CONFIG_LIBDIR
path overriding pkg-config's built-in search path
SELINUX_LIBS
linker flags for selinux library
SELINUX_CFLAGS
C compiler flags for SELINUX, overriding pkg-config
CURSES_LIBS linker flags for curses library
SOCKET_LIBS linker flags for socket library
PS_LIBS linker flags for ps library
KVM_LIBS linker flags for kvm library
Use these variables to override the choices made by 'configure' or to help
it to find libraries and programs with nonstandard names/locations.
Report bugs to <debian-dpkg@lists.debian.org>.
dpkg home page: <https://wiki.debian.org/Teams/Dpkg>.
```
:::
Autotools 的参数大致分为三部分:定制系统路径的参数、开关软件特性的参数及引入可选依赖的参数。
- 系统路径定制参数需要指定值,否则没有意义。除了 `--prefix` 外,路径定制参数一般均以 `--XXXXdir` 的形式存在,如上例中的 `--bindir`、`--libdir` 等。
- 特性定制参数一般以 `--enable-X` 或 `--disable-X` 形式存在,因此参数本身足够表达其意义,无需指定 `yes` 或 `no`。
- 可选依赖定制参数一般以 `--with-X` 的形式存在,指定后项目就会依赖上对应的组件,如上例中的 `--with-libz`(zlib 压缩支持)、`--with-libzstd`(ZStandard 压缩支持)等。
> [!Important]
> 有些目录定制参数也会以 `--with-X=DIR` 的形式出现,如上例中指定 Bash 命令行补全文件路径的参数 `--with-bashcompletionsdir=DIR`。这类参数不属于依赖定制参数。
> [!Important]
> 有些依赖定制参数也会混进 `--enable-X` 的选项中,也有一部分会采用 `--with-X[=DIR]` 的形式出现。后者接受可选的路径,以方便 Autotools 查找不在标准路径下(共享库在 `/usr/lib`、头文件在 `/usr/include`)的组件,如 `--with-openssl`(自动检测)或 `--with-openssl=/home/my/custom/openssl`(非标准路径)。
#### 2.3.7.2 CMake
由于 CMake 是动态读取 CMake 脚本的,因此无法直接给出可以指定的选项。不过,CMake 项目所接受的定制选项均通过 `CMakeLists.txt` 定义。您可以通过以下几种方式确定该项目可接受的变量:
- 阅读项目的 README、INSTALL 等构建文档。通常情况下,项目的文档会列出所有项目定制相关的 CMake 变量。有些项目的文档可能会集中在项目知识库 (Wiki) 中,请仔细查找。
- 阅读 `CMakeLists.txt`:这是最直接的方式,同样也最麻烦,也较为考验技能。不过,大部分定制相关的变量均以如下形式存在于定义文件中:
```cmake
IF (DEFINED ENABLE_SOMETHING)
# ENABLE_SOMETHING 被设置时需要执行的操作,如设置额外的链接库参数
message("Something is enabled")
target_link_libraries(project PUBLIC somelibrary)
ENDIF()
```
- 使用 `ccmake` 命令行界面:`ccmake` 是 CMake 的终端图形界面 (TUI) 版。`ccmake` 在刷新一次缓存后会显示配置期间用到的所有变量。但是这些变量中大部分均与项目定制无关。
- 借鉴其他发行版:您可以查找对应软件包在其他发行版中的打包脚本。
#### 2.3.7.3 Meson
Meson 专门提供了参数定义文件 `meson_options.txt`,方便项目开发者定义可以接受的定制参数,同时也方便了维护者参考。`meson_options.txt` 的内容一般按如下形式组成:
```python
option(
'option_name', # 参数名
type : 'option_type', # 参数类型,可以是布尔值 boolean、字符串 string
# 及用于定制功能的类型 feature
value : default_value, # 该参数的默认值,可以是空,也可以是 auto
description: 'some' # 该参数的描述,如启用该参数后的效果
)
```
### 2.7.4 指定定制选项
只有在了解项目接受哪些定制参数后,才能执行配置步骤。如您所见,不同构建系统指定定制参数的方式也不同。
对于 Autotools,指定参数的方式非常直接。在了解可以指定哪些选项后,您只需要在执行配置脚本时加上您想要指定的选项即可:
```shell
../configure --prefix=/usr \
--with-libzstd \
--with-zlib \
--with-libbz2 \
--with-liblzma \
--enable-nls ...
```
> [!Important]
> 有些选项是以变量的形式出现的,如 `--prefix`、`--XXXdir`。 因此请勿忘记指定他们的值。
CMake 不采用 `--with`、`--enable` 或 `--disable` 等形式的选项。CMake 需要您在执行配置阶段时定义变量的值来定制项目。CMake 指定定制选项的方式是 `-D变量名=值`。也就是说,需要在 CMake 执行 CMake 脚本时定义需要的变量来完成项目定制:
```shell
cmake .. -GNinja \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER=/usr/bin/clang \
-DCMAKE_CXX_COMPILER=/usr/bin/clang++ \
-DLLVM_TARGETS_TO_BUILD="X86;Mips;ARM;AArch64" \
-DLLVM_ENABLE_PROJECTS="clang;lld;lldb" \
-DLLVM_ENABLE_ASSERTIONS=ON \
-DLLVM_USE_LINKER=lld ...
```
Meson 与 CMake 类似,但在打包 Meson 项目时需要用 `--prefix` 指定系统前缀,否则就用 `-D变量=值` 的形式配置项目:
```shell
meson setup build --prefix=/usr \
-Dlibmpv=true \
-Dcdda=enabled \
-Dlibarchive=enabled \
-Ddvbin=enabled \
-Dopenal=enabled \
-Dsdl2=enabled \
-Dpipewire=enabled \
-Dvapoursynth=disabled ...
```
## 2.8 总结
- 编译一个程序或项目大致分为配置 (configure)、构建 (build) 及安装 (install) 三个步骤。
- 绝大多数项目都会使用构建系统简化项目的构建过程。
- 借助构建系统提供的灵活度,大多数项目在不同程度上允许各位开发者及打包者定制项目。
- 开发者及打包者需要在执行配置阶段时指定配置选项来定制项目,不同构建系统提供定制选项的方式不同。
- 对于打包者来说,系统前缀 (prefix) 须指定为 `/usr`,并且需要额外指定安装目标目录 (DESTDIR) 来避免直接覆盖系统文件。
- 而对于开发者或偶尔编译试用的用户来说,可以不指定系统前缀 (`/usr/local`),或者将系统前缀指定到家目录中,避免安装期间使用 root 身份。同样地,也无需指定安装目标目录。
到这里您应该掌握了一些关于编译软件的知识。接下来,本文会将构建三连引入到安同 OS 的打包套件中。Autobuild 是安同 OS 打包套件中直接接触构建系统的一环。
# 第三章 将编译三连集成进 Autobuild
笼统地讲,为发行版 “打包” 就是按照 `/usr` 或发行版规定的系统前缀及分类路径、以发行版要求的依赖及打包标准构建软件包,然后将其安装至临时目录;再以临时目录为起点,将安装目录的内容压缩成一个压缩包,同时带上软件信息。打出的软件包随后会通过软件仓库等方式分发给最终用户,通过包管理器安装到用户的系统中。
本章内容将教您如何直接在源码目录中使用 Autobuild 构建软件包。
## 3.1 Autobuild 介绍
Autobuild 是安同 OS 的打包工具,负责读取软件包信息,自动运行构建三连,构建源码并打出 `deb` 软件包。因此,Autobuild 属于安同 OS 打包工具链中最底层的一环。十几年来,Autobuild 经过四个大版本,逐渐成为完善的自动构建系统,支持主流语言的包管理器,以及主流的构建系统。
Autobuild 的主要逻辑均采用 Bash 编写,给开发者及维护者提供了极大便利。少数需要用到的函数等 Shell 中难以处理的功能被移至 C++ 实现中,以换取稳定度及可维护性。
Autobuild 在全局范围定义了发行版中规定的标准,以及默认启用或禁用的编译参数:
1. 默认系统前缀及各类文件的安装路径
- `--prefix=/usr` 系统前缀
- `--bindir=/usr/bin`、`--sbindir=/usr/bin`、`--libdir=/usr/lib` 等各类文件的默认安装路径
2. 默认启用或禁用的编译器参数,如优化、LTO、加固等;这些参数可以通过软件包定义的变量再次启用或禁用
- `-flto`: 默认启用链接时优化 (LTO)
- `-O2`: 默认启用编译期优化
3. 默认的编译器参数(架构基线指令集)
- `-march=`: 基线指令集(或扩展),任何不支持该指令集的处理器均无法运行程序
- `-mtune=`: 针对优化指令集(或扩展),程序在支持该指令集(扩展)的处理器上性能最佳
4. 默认的链接器参数(共享库搜索路径 RPATH 等)
因此,您无需在定制参数中重复指定上述选项。不过,除系统标准路径外,每个软件都可以在软件包定义中修改或重新定义上述内容。
### 3.1.1 构建流程
Autobuild 简单来说就是构建三连的包装,使得开发者可以用更小的脚本完成打包工作。在此同时,开发者也能够在构建三连前后运行脚本,灵活控制构建过程。Autobuild 的运行流程如下:
1. 读取 `autobuild` 目录中的软件包定义,获得基本信息
2. 运行构建前的 QA 检查(确保必需定义都存在)
3. 根据软件包定义设置编译器参数及链接器参数
4. 如果有,按顺序应用源码补丁
5. 如果有,运行 `prepare` 脚本(构建三连前的自定义脚本)
6. 根据构件模板的探测情况运行对应的构建模板(或自定义构建脚本 `build`)
7. 如果有,运行 `beyond` 脚本(构建三连后的自定义脚本)
8. 运行构建后的 QA 检查(检查路径等)
9. 运行后处理步骤(分离调试符号、压缩手册页等)
10. 调用 `dpkg-deb` 工具打包
11. 尝试安装新打出的软件包
同时,Autobuild 也支持自定义用户安装软件包时执行的操作。这些操作是由包管理器运行的:
- `prerm`、`postrm`: 包管理器卸载软件包前后执行的操作
- `preinst`、`postinst`: 包管理器解压软件包内容前后执行的操作
Autobuild 的整个构建流程可以用下图表示:

### 3.1.2 目录结构
Autobuild 以源码目录为工作区,读取软件包定义,执行构建三连打包。除全局配置文件外,Autobuild 所需的文件全部存放于源码目录中的 `autobuild` 文件夹下。Autobuild 不支持将 `autobuild` 目录置于其他位置,请知悉。以 Htop 为例,Autobuild 所需的主要文件如下:
- `/etc/autobuild/ab4cfg.sh`: 全局 Autobuild 配置文件,记录贡献者信息及第二阶段自举模式等。
- `htop-3.3.0/`: Htop 3.3.0 版本的源码目录。
- `autobuild/`: Autobuild 构建定义文件所在目录。
- `defines`: 软件包描述信息。Autobuild 目录下必须存在该文件。
- `prepare`: 构建三连前执行的脚本,用于(选择性地)设置编译器参数(`CFLAGS` 等)。
- `beyond`: 构建三连后执行的脚本,用于安装额外的文件、设置软链接等。
- `build`: 自定义构建脚本。只有在构建模板无法满足需求、或者没有合适的构建模板时使用。
- `patches/`: 用于存放以 `.patch` 结尾的源码补丁文件。
在构建中,Autobuild 会以源码目录 `htop-3.3.0` 为起点自动创建及处理源码目录和安装目标目录:
- `abbuild`: 启用独立编译时创建的构建目录。
- `abdist`: 安装目标路径所在目录。
安同 OS 和 Autobuild 没有 fakeroot 支持,因此在打包时会直接使用 `root` 权限。
### 3.1.3 语言规范
安同 OS 开发中,用于记录软件包元信息的语言叫做 APML(ACBS 软件包元数据语言,ACBS Package Metadata Language)。APML 是安同 OS 开发工具链所使用的语言,是 Bash 的子集,因此 Autobuild 可以直接读取文件。除了 `prepare`、`build` 及 `beyond` 等构建脚本外,所有软件包定义文件均受 APML 的约束。
APML 规定了软件包定义中 Bash 语言的使用范围:
- 只允许存在变量定义。
- 可以使用注释。
- 变量的值可以是字符串,可以是数组。
- 定义变量时可以引用参数,可以使用参数变换 (Parameter expansion)。
- 允许在字符串中使用行接续符(`\` 加上换行符)将字符串分成多行。
- 不允许 Here Documents。
- 不允许分支条件及循环控制。
- 不允许执行命令。
- 不允许使用管道。
### 3.1.4 一般工作流程
一般情况下,用 Autobuild 打包软件项目的流程分为如下步骤:
1. 调查阶段:收集软件包信息
1.1 确定软件包名、版本、分类及描述
1.1 调查软件项目使用的构建系统
1.2 调查可以接受的定制参数(参考 2.2.5 节中的描述)
1.3 结合系统情况选择要启用的特性或扩展
1.4 收集需要指定的定制参数(`--prefix` 等路径相关的参数除外)
2. 定义阶段:编写软件包定义及构建脚本(如果有需要)
2.1 编写软件包定义 `autobuild/defines`
2.2 如果没有使用构建系统或构建系统不受 Autobuild 支持,则需要编写自定义脚本 `autobuild/build`
3. 构建测试阶段:确保编译通过
3.1 在源码目录中运行 Autobuild
3.2 如果出错,则需要调整定制参数或编译器参数,或者需要在构建前后运行处理脚本 (`autobuild/prepare`、`autobuild/beyond`)
## 3.2 Autobuild 的构建模板
构建模板是运行构建系统的逻辑或脚本。构建模板从构建过程中隐藏了构建三连命令本身,因此打包者无需再手动编写构建脚本,只需要在软件包定义中指定项目的定制参数即可。
Autobuild 支持多种构建系统,每种构建系统都有自己的构建模板(这些构建系统的使用方法不尽相同),构建模板中包含如下定义及逻辑:
- 系统路径范围的定制选项(系统前缀、各类文件安装的位置等)
- 针对构建系统编写的构建三连
- 控制构建模板行为的变量(如对于 Autotools 是否重新生成 `configure` 脚本)
Autobuild 中存在如下构建模板:
- `autotools`: 处理使用 Autotools 构建系统的项目。
- `cmakeninja`: 处理使用 CMake 构建系统的项目,并让 CMake 生成 Ninja 构建系统执行器的构建序列文件。
- `cmake`: 处理使用 CMake 构建系统的项目,生成供 GNU Make 执行的 Makefile。
- `meson`: 处理使用 Meson 构建系统的项目。
- `waf`: 处理使用 WAF 构建系统的项目。
- `dune`: 负责处理 OCaml 软件包。Dune 是 OCaml 程序的构建系统。
- `perl`: 负责处理 Perl 软件包。
- `python`: 负责处理使用 Setuptools (`setup.py`) 的 Python 项目。
- `rust`: 负责调用 Rust 的包管理器兼构建系统 Cargo。
- `pep517`: 负责处理使用 PEP 517 构建系统的项目,并调用`build` 模块和 `install` 模块。
- `qtproj`: 负责处理使用 QMake 构建系统的项目(源码目录包含任何 `.pro` 结尾的文件)。
然而有时 Autobuild 提供的模板可能不够灵活,或者暂时还没有某个构建系统的模板支持,抑或是项目只需要执行 `make` 即可编译。在这种情况下,您可以使用自定义脚本,绕过 Auotbuild 的构建模板。
## 3.3 热身
在您动手使用 Autobuild 之前,请先确保您已经搭建好了安同 OS 的开发环境。由于在系统中直接运行 Auotbuild 会影响系统本身,因此在接触 Ciel 之前,我们强烈建议您使用虚拟机。您可以利用下面的检查表来确认:
- [ ] 独立的系统环境(开发机或虚拟机)
- [ ] 安装了 `devel-base`
- [ ] 安装了 `autobuild4` 和 `acbs`
- [ ] 确定要使用的用户名和邮箱
- [ ] 能够下载或克隆源码
在着手操作 Autobuild 之前,您需要先真正地编译一次软件。本节我们以著名的进程管理器 htop 为例,带您手动构建,然后转移至 Autobuild。
### 3.3.1 构建热身
[htop][htop] 是一个酷炫实用的进程(任务)管理器,可以查看处理器、内存和 I/O 的使用情况,可以按照某种指标排序,可以管理进程。本节先带您动手执行 Autotools 构建系统的构建三连:
1. 找个位置,或创建一个文件夹,作为工作目录:
```shell
/tmp $ cd ~
~ $ mkdir aosc-build
~ $ cd aosc-build
```
2. 从网站或 GitHub 上下载其源码发行 (tarball) 并解压:
```shell
~/aosc-build $ wget https://github.com/htop-dev/htop/releases/download/3.3.0/htop-3.3.0.tar.xz
~/aosc-build $ tar xf htop-3.3.0.tar.xz
~/aosc-build $ cd htop-3.3.0/
```
3. 进入源码目录后,查找 Htop 选用的构建系统,确定可以指定的定制选项。
源码目录中存在 `configure.ac` 文件,因此 Htop 是一个 Autotools 项目。同时,源码目录中有生成好的 `configure` 脚本,因此我们不需要重新生成。先看看 `configure` 脚本提供哪些参数:
```shell
htop-3.3.0 $ ./configure --help
```
浏览脚本的输出,可以找到一些有关定制 Htop 的功能的信息:
- `--enable-pcp`: 启用 [PCP][pcp](一款系统性能数据分析工具)支持
- `--enable-unicode`: 启用 Unicode 支持
- `--enable-affinity`: 启用进程相关性支持(绑定进程到某个 CPU 核心)
- `--enable-capabilities`: 启用进程权限 (Capabilities) 支持
- `--enable-sensors`: 启用传感器支持(用于显示处理器温度)
4. 决定要启用哪些定制选项。
这里我们只启用能够显示处理器温度的选项 `--enable-sensors` 及 Unicode 支持。
5. 调查当前配置下所需要的依赖组件。
Htop 的 README 非常清晰,列出了 Htop 需要的必要依赖组件,以及启用功能时额外所依赖的组件。我们需要记录所有必要依赖,并且根据上面的定制情况记录其他依赖:
- 基本依赖有:编译器、Autotools 套件、NCurses 终端库
- 启用传感器支持后引入的额外依赖:`libsensors`
> [!Note]
> `libsensors` 中以 `lib` 开头。按照业界的软件包命名规律,libsensors 属于共享库 (Library)。调查依赖期间遇到共享库时,您可能需要了解该共享库是否属于项目的一部分。
> `libsensors` 是 [lm-sensors](https://hwmon.wiki.kernel.org/lm_sensors) 的一部分。lm-sensors 提供了查看传感器状态的工具,以及供其他程序实现温度监控的传感器库。
在安同 OS 中,这些组件对应的包名分别为 `gcc`、`autoconf`、`automake`、`ncurses` 及 `lm-sensors`。而安同 OS 提供更简便的安装常用构建工具链的方式:您可以直接安装 `devel-base`,编译器及常见的构建系统会同时引入。
6. 安装依赖组件。
将依赖组件与安同 OS 的软件包一一对应之后,用包管理器安装即可:
```shell
htop-3.3.0 $ oma install devel-base ncurses lm-sensors
```
7. 按照选用的参数运行构建三连。
> [!Note]
> 本次热身仅作构建说明之用,您无需也不应该将安装前缀指定到 `/usr`。同时,您也无需指定 `DESTDIR`。
> 在本次编译中,我们将系统前缀设置为家目录下的 `aosc-build` 文件夹,避免在构建阶段使用 `sudo`。
万事俱备,现在就可以运行构建三连了!
```shell
htop-3.3.0 $ mkdir build
htop-3.3.0 $ cd build
build $ ../configure --prefix=$HOME/aosc-build/apps \
--enable-sensors
build $ make -j$(nproc)
build $ make install
build $ cd ..
```
8. 安装完毕后运行程序:
由于安装的位置不属于标准路径,因此您无法直接使用 `htop` 命令运行刚才构建出的 Htop。您需要指定安装后的 Htop 的完整路径:
```shell
htop-3.3.0 $ ~/aosc-build/apps/bin/htop
```
以上介绍了自行构建期间大致需要的步骤。您为安同 OS 打包时也需要采取类似的步骤,具体的细节会在后面讲到。
### 3.3.2 配置 Autobuild
都准备好了吗?那么我们就继续吧!您需要先告诉 Autobuild 您的维护者身份。以 root 身份编辑 `/etc/autobuild/ab4cfg.sh`,将您的信息填写至此:
```shell
MTER="Some Packager <some@packager.com>"
```
我们将继续以 Htop 为例讲述如何用 Autobuild 软件包。您现在可以删除之前安装的 Htop 及构建目录了:
```shell
htop-3.3.0 $ make -C build uninstall # 进入构建目录,卸载安装到 ~/aosc-build 的 Htop
htop-3.3.0 $ rm -r build # 移除构建目录
htop-3.3.0 $ rm -r ~/aosc-build/apps
```
现在,请您创建 `autobuild` 文件夹,准备动手编写 Autobuild 文件。
## 3.4 编写软件包定义
软件包定义记录在 `autobuild/defines` 文件中。`defines` 文件遵循 APML 的约束,因此该文件的内容只包含变量定义,即 `变量名=值`。
> [!Important]
> 等号左右不允许有空格,否则会被认定为命令。下面的例子都是不正确的:
> ```bash
> PKGNAME = bash
> # Bash 会认为 PKGNAME 是一个命令,“=” 和 “bash” 是 PKGNAME 命令的参数
> PKGNAME =bash
> # Bash 会认为 PKGNAME 是一个命令,“=bash” 是 PKGNAME 命令的参数
> PKGNAME= bash
> # Bash 会设置一个值为空的环境变量 PKGNAME,然后执行命令 “bash”
> ```
Autobuild 的软件包定义中包含了除源码信息外的所有内容:
- 软件包信息:包名、软件版本等
- 构建系统参数:使用的构建模板、构建系统的定制参数等
- 编译器特性:LTO、优化、切换到 Clang 编译器等
### 3.4.1 软件包基本信息
`defines` 文件中必须存在如下定义,否则该包视为无效:
| 变量名 | 类型 | 约束 | 作用 |
|:---------:|:------:|:----------------------------------------------------:| ----------------------------------------------------------------------------- |
| `PKGVER` | 字符串 | 只允许出现小写字母、数字和符号;不允许出现短横 (`-`) | 记录软件包的版本。版本号遵循安同 OS 的版本记录规范,在本节中先忽略。 |
| `PKGNAME` | 字符串 | 一般只包含小写字母、数字、短横、加号及下划线 | 记录软件包的名称。 |
| `PKGSEC` | 字符串 | 特定值 | dpkg 包管理器规定的软件包分类。参考 `/usr/lib/autobuild4/sets/section` 文件。 |
| `PKGDES` | 字符串 | 大小写字母、空格和数字 | 一句简短的、对软件包功能的英文描述。不允许出现偏向广告或宣传的形容词。 |
> [!Important]
> 由于软件包描述目前没有任何明确的规范,您需要与其他贡献者一起讨论如何编写软件包描述,如修缮模糊的描述、移除广告说辞等。
> [!Warning]
> 由于 ACBS 负责下载源码,因此 `PKGVER` 是由 ACBS 自动注入的。但是我们还未接触 ACBS,因此 `PKGVER` 需要手动定义。引入 ACBS 后,您就不能指定 `PKGVER` 了。
### 3.4.2 软件包依赖信息
除此之外,软件包定义文件中还需要记录构建相关的信息,如依赖关系、构建参数及编译器功能开关等。以下列举一些常用的依赖关系定义:
| 变量名 | 类型 | 约束 | 作用 |
| :--: | :--: | :--: | :--:|
| `PKGDEP` | 字符串 | 空格隔开的包名 | 软件包的运行时依赖列表 |
| `BUILDDEP` | 字符串 | 空格隔开的包名 | 软件包的构建时依赖列表 |
| `PKGPROV` | 字符串 | 空格隔开的包名及其约束 | 软件包提供的别名列表 |
| `PKGREP` | 字符串 | 空格隔开的包名及其约束 | 软件包取代的包名列表 |
| `PKGRECOM` | 字符串 | 空格隔开的包名 | 软件包推荐的包名列表 |
| `PKGBREAK` | 字符串 | 空格隔开的包名及其约束 | 软件包冲突的包名列表 |
> [!Important]
> - 这些字符串均允许使用行接续符,以避免字符串将一行撑得太长。
> - 软件包的约束使用 dpkg 接受的格式,也就是 “包名 + 约束符 + 版本”。这些约束指定软件包会提供、取代或冲突满足特定条件的包,如取代大于某个版本的包、与大于某个版本的包冲突等,如 `llvm<=17.0.2`。
### 3.4.3 软件包构建参数
对于构建参数,Autobuild 的定义如下:
| 变量名 | 类型 | 适用的构建系统 | 作用 |
|:-----------------:|:------------:|:-----------------------:|:-----------------------------------------------------------------:|
| `ABTYPE` | 字符串 | - | 跳过自动检测步骤,手动指定要使用的构建系统 |
| `AUTOTOOLS_AFTER` | 数组或字符串 | GNU Autotools | 指定额外的 Autotools 参数(`--with`、`--enable`、`--disable` 等) |
| `CMAKE_AFTER` | 数组或字符串 | CMake | 指定额外的 CMake 定义(`-DSOMETHING=ON`) |
| `MESON_AFTER` | 数组或字符串 | Meson | 指定额外的 Meson 定义 |
| `QTPROJ_AFTER` | 数组或字符串 | QMake | 指定额外的 QMake 参数 |
| `ABMK` | 字符串 | Autotools、CMake、Meson | 指定执行 make 阶段时的构建目标 |
> [!Warning]
> 尽管 Autobuild 能够自动探测构建系统,我们依旧建议您手动指定,尤其是源码中出现多个构建系统的定义文件的情况。
> [!Important]
> 建议您使用数组定义这些变量,以避免空格、引号等引发的歧义。
### 3.4.4 编译器特性开关
定义文件中还允许您自定义构建时启用的编译器特性,如禁用 LTO、使用 Clang 作为编译器等。除非特殊说明,这些开关只接受布尔值,即 `yes` 或 `no` 和 `1` 或 `0`。一些常用的编译器特性开关如下:
| 变量名 | 默认值 | 描述 |
|:------------------:|:---------------------------------------:|:---------------------------------------------------------------:|
| `NOLTO` | `no` | 禁用 LTO |
| `RECONF` | `yes` | 是否自动重新生成 `configure` 脚本 |
| `USECLANG` | `no` | 将 Clang、Clang++ 作为 C 和 C++ 语言的编译器 |
| `ABSHADOW` | `yes` | 是否启用独立构建 |
| `NOSTATIC` | `yes` | 是否保留编译的静态库(`.a` 文件) |
| `AUTOTOOLS_STRICT` | `yes` | 是否启用 Autotools 的构建选项检查功能(遇到不认识的选项会报错) |
| `ABSPRIAL` | `yes` | 是否生成 Debian 兼容包名(Sprial 泛 Debian 兼容性支持) |
| `NOPARALLEL` | `no` | 是否启用并行编译 |
| `ABTHREADS` | `$((nproc + 1))` (处理器核心数量 + 1) | 发起的并行作业数量(整数) |
| `AB_FLAGS_O3` | `no` | 是否启用激进的编译器优化(`-O3` 参数) |
### 3.4.5 编写定义
继续使用前面的 Htop 例子。根据之前的例子,我们得知:
- 软件包名是 `htop`,版本是 `3.3.0`
- 构建系统是 Autotools
- 需要为 Htop 启用传感器及 Unicode 支持,因此:
- Htop 运行时除了必要的依赖 `ncurses` 之外还有 `sensors` 软件包
- 启用上述支持的参数分别为 `--enable-unicode` 和 `--enable-sensors`
我们还需访问 [Htop 官网][htop],了解官网中对 Htop 的描述:
> An Interactive Process Viewer
>
> _—— Htop 官网_
官网的描述有些模糊。由于 htop 的界面与经典 Unix 任务管理器 `top` 的界面类似,我们可以修缮官网的描述,使其符合规范:
> An top-like interactive process viewer
>
> _—— 修缮后的描述_
至此,我们已经拥有了全部的软件包定义。确保您当前处于 Htop 的源码目录后,您就可以新建 `autobuild` 文件夹,启动编辑器编写定义了。编辑 `autobuild/defines`,填入以下内容:
```bash
# 软件包基本信息
PKGNAME=htop
PKGSEC=admin
PKGDES="A top-like interactive process viewer"
PKGVER=3.3.0
# 依赖信息
PKGDEP="ncurses lm-sensors"
# 构建参数
ABTYPE=autotools
AUTOTOOLS_AFTER=(
--enable-sensors
--enable-unicode
)
```
以上就是本例的 Autobuild 软件包定义文件。
## 3.5 使用构建模板打包
在构建模板支持的情况下,编写软件包定义后可以立即开始打包。如果指定的定制参数正确,打包过程一般会非常顺利。您只需要在源码目录中以 root 身份执行 `autobuild`,坐等 Autobuild 出包即可:

<p style="text-align: center">
<i>Autobuild 自动编译 htop 进行时</i>
</p>

<p style="text-align: center">
<i>Autobuild 打包成功</i>
</p>
如果构建出错,Autobuild 会提前退出,并且给出错误点。如下图,软件包定义中指定了额外的定制参数,但没有安装对应的依赖。于是,`configure` 脚本就会因为找不到依赖报错退出,`autobuild` 因此无法继续,并报告运行 `configure` 脚本出错(退出状态码不是 0 即代表运行出错):

<p style="text-align: center">
<i>构建失败时 Autobuild 的提示</i>
</p>
要解决此类错误,只需要在软件包的依赖列表中加入需要的依赖即可:
```diff
- PKGDEP="ncurses lm-sensors"
+ PKGDEP="ncurses lm-sensors pcp"
```
## 3.6 自定义构建脚本
有时 Autobuild 的构建模板无法完全满足构建软件包的需求,如需要额外增减编译器参数 (`CFLAGS`、`CXXFLAGS` 等),或者需要额外安装文件。Autobuild 允许在构建三连前后运行脚本,方便开发者或打包者处理上述情况。
当然,Autobuild 的构建模板虽然多,但总有不支持的构建系统,或者有些项目不使用构建系统,直接使用 `Makefile` 编译。此时您需要手动编写构建脚本,代替 Autobuild 的构建模板完成构建三连。

<p style="text-align: center">
<i>Autobuild 的自定义脚本及流程</i>
</p>
> [!Important]
> 除非有必要,否则不建议使用 `patch` 脚本——用 `sed` 修改源码的方法并不稳定。
> 强烈建议修改源码后导出补丁,然后复制到 `autobuild/patches` 文件夹中。
>
> 导出为补丁有助于在更新期间发现补丁中的问题,因为 `sed` 等行编辑工具无法识别错误。同时用补丁可以清晰地描述补丁的目的。
### 3.6.1 Autobuild 提供的实用函数
Autobuild 提供了一些方便开发者的实用函数,可以在自定义脚本里使用:
| 函数名 | 参数 | 描述 |
|:---------------:|:----------:|:----------------------------------------------------------------------------------:|
| `abinfo` | 任意字符串 | 输出提示信息 |
| `abwarn` | 任意字符串 | 输出警告信息 |
| `aberr` | 任意字符串 | 输出错误信息 |
| `abdie` | 任意字符串 | 输出错误信息并以出错状态退出 |
| `ab_match_arch` | 架构名称 | 判断当前系统的架构是否为指定架构,用作逻辑判断条件:如 `ab_match_arch loongarch64` |
| `ab_apply_patch` | 文件路径 | 手动应用指定的补丁文件 |
### 3.6.2 prepare
prepare 脚本在构建三连之前运行。prepare 脚本主要的应用场景如下:
- 添加额外的编译器参数(`CFLAGS`、`CXXFLAGS` 等)
- 移除特定会导致编译错误的编译器参数
- 调整源码中的文件位置
- 移除部分不应出现的文件
以下是几个例子:
- `grub/autobuild/prepare`: 在构建之前需要将下载的翻译文件复制到构建系统期望的位置,并生成语言列表。这些步骤执行后方可使用构建模板执行构建三连:
```bash=
abinfo "Copying translation files ..."
find "$SRCDIR" -maxdepth 1 -type f -o -type l -name '*.po' -exec install -vt "$SRCDIR"/grub-2.12/po {} \;
abinfo "Generating LINGUAS file ..."
# See linguas.sh inside GRUB source tree.
autogenerated="en@quot en@hebrew de@hebrew en@cyrillic en@greek en@arabic en@piglatin de_CH"
for x in $autogenerated; do
rm -f "po/$x.po";
done
(
(
cd "$SRCDIR"/grub-2.12/po && ls *.po | tee | cut -d. -f1
for x in $autogenerated; do
echo "$x";
done
) | sort | uniq | xargs
) > "$SRCDIR"/grub-${__GRUBVER}/po/LINGUAS
```
- `qemu/autobuild/prepare`: 在构建之前需要针对特定架构关闭编译器特性,并设置时区,以使 Sphinx 正常运行:
```bash=
abwarn "FIXME: Hardening breaks build ..."
export CFLAGS="${CFLAGS} -fPIC"
export LDFLAGS="${LDFLAGS} -fPIC"
abinfo "tree vectorize is broken on ppc64"
if [[ "${CROSS:-$ARCH}" = "ppc64" ]]; then
export CFLAGS="${CFLAGS/-ftree-vectorize/}"
fi
abinfo "Sphinx really want TZ to be set..."
export TZ=Etc/UTC
```
### 3.6.3 build
build 脚本用于代替构建模板手动运行构建三连。一般情况下,绝大多数采用 Autobuild 支持的构建系统的项目,您无需对其自行编写构建脚本。但是遇到以下情况时,您必须自行编写构建脚本:
1. 项目所使用的构建系统不受 Autobuild 支持(如部分软件使用了 SConstruct,以及火狐浏览器有自己的构建系统)的
2. Autobuild 提供的模板无法完全满足需求,但可以复用模板中定义的过程的
3. 项目即便使用了支持的构建系统,但构建流程非常复杂,完全无法复用模板的(如 `glibc`、`gcc` 的自举阶段构建脚本)
4. 项目无需配置,直接调用 `make` 执行 Makefile 构建的——Autobuild 曾经提供 `plainmake` 模板,但由于大家的 Makefile 接受的参数及行为各不相同,因此该模板被弃用
5. 项目根本没有构建系统,需要手动逐个编译、手动链接的
6. 软件本身是二进制,只需在解压后以安同 OS 的路径标准及依赖包名重新打包的(如 NVIDIA 驱动、Discord 等各类私有软件)
上述情况中除第五条外,在安同 OS 中都可以找到例子,下面将详细解释这些情况。
**项目的构建系统不受 Autobuild 支持**:Autobuild 尚未提供对应构建系统的模板,因此需要将构建三连编写成脚本。例如,火狐浏览器及 Thunderbird 邮件客户端使用 `mozbuild` ,且构建流程较为复杂;Sunpinyin(拼音输入法引擎)等软件使用了一款较为小众的构建系统 SConstruct;Haskell 编写的软件(如 Pandoc,文档生成引擎)的构建流程也尚未总结成模板。下面是 Pandoc 的构建脚本:
```bash=
abinfo "Building pandoc ..."
cabal update
cabal v2-build pandoc-cli -j -v
abinfo "Installing pandoc ..."
cabal v2-install pandoc-cli \
-j -v \
--install-method=copy \
--installdir="$PKGDIR"/usr/bin
```
**Autobuild 提供的模板无法完全满足需求**:项目使用构建系统有对应的构建模板,但基于实际应用情况需要额外执行一些步骤,总体上又可以复用构建模板里定义的过程。例如,libxcrypt 需要针对新旧 API 分别构建两次,但同时也无需写两遍构建三连的命令,转而直接调用构建模板中包装的函数,因此将这种情况归类为 “不完全满足需求且可以复用模板” 。qbittorrent 也属于此类情况,因为需要分别编译带图形界面前端和不带图形界面前端 (`qbittorrent-nox`) 的程序。以下是 libxcrypt 的构建脚本:
```bash=
# FIXME: MAKE_AFTER must be set if reusing the routines from autobuild4
export MAKE_AFTER=""
abinfo "Configuring libxcrypt (new API) ..."
build_autotools_configure
abinfo "Building libxcrypt (new API) ..."
build_autotools_build
abinfo "Installing libxcrypt (new API) ..."
build_autotools_install
abinfo "Resetting source tree ..."
rm -rv "$BLDDIR"
abinfo "Configuring libxcrypt (old API) ..."
export AUTOTOOLS_AFTER=(${AUTOTOOLS_AFTER__COMPAT[@]})
build_autotools_configure
abinfo "Building libxcrypt (old API) ..."
build_autotools_build
abinfo "Installing libxcrypt (old API) ..."
# Save PKGDIR first
export SAVED_PKGDIR="$PKGDIR"
export PKGDIR="$SRCDIR"/compat
build_autotools_install
export PKGDIR="$SAVED_PKGDIR"
install -Dvm755 "$SRCDIR"/compat/usr/lib/libcrypt.so.1.1.0 \
-t "$PKGDIR"/usr/lib/
abinfo "Creating a symlink libxcrypt.so.1 => libxcrypt.so.1 ..."
ln -sv libcrypt.so.1.1.0 \
"$PKGDIR"/usr/lib/libcrypt.so.1
```
**项目即使用了支持的构建系统,但构建流程非常复杂无法复用构建模板**:项目即便采用了支持的构建系统,但项目的配置、构建及安装阶段间需要更为复杂的步骤,无法一次性执行某个步骤,因此无法复用模板中定义的过程。用于构建安同 OS i686 兼容环境的编译器就需要复杂的自举步骤:GCC 需要分别在 32 位 glibc 构建前后构建一次,因为 glibc 不存在时无法构建 GCC 运行时。
**项目无需配置,直接调用 Makefile 构建**:项目结构较为简单,因此没有采用构建系统,取而代之的是一系列自行编写的 Makefile。编译这类项目时,通常只需要执行 `make`。大部分 Makefile 均遵循一部分的 GNU Makefile 约定,即:
- 拥有 `all`(构建整个项目)及 `install`(用于安装文件)两个构建目标,并且没有指定目标时默认为 `all`
- 支持指定系统前缀 `PREFIX` 及安装目标路径 `DESTDIR` 变量
此类项目可能还接受其他参数,以启用或禁用项目特性。具体请参考项目的文档。ZStandard 就属于此类项目:
```bash=
abinfo "Building zstd ..."
make
abinfo "Installing zstd ..."
make install \
DESTDIR="$PKGDIR" \
PREFIX=/usr
abinfo "Building pzstd ..."
make -C "$SRCDIR"/contrib/pzstd
abinfo "Installing pzstd ..."
make install -C contrib/pzstd \
DESTDIR="$PKGDIR" \
PREFIX=/usr
```
**软件本身就是二进制,只需要重打包**:所有私有软件(不开放源代码的)只发行预编译的二进制。安同 OS 的软件仓库中有一些允许重分发 (Redistribution) 的私有软件,但这些包因为种种原因无法直接进入安同 OS 的软件仓库,因此需要将依赖信息转换成安同 OS 中对应的软件包,并适当修改其他信息,按照安同 OS 的路径标准重新打包。
安同 OS 中有许多这样的软件,其中包括 NVIDIA 显卡驱动、各类商业软件、Google Chrome 浏览器、Discord 语音聊天软件等。与此同时,安同 OS 也会重打包维护难度较高的开源软件的二进制,如 .NET 运行时。下面是 Google Chrome 的 “构建脚本”,可见其中只有解压和复制粘贴:
```bash=
abinfo "Extracting archive file ..."
dpkg -x "$SRCDIR"/google-chrome-stable_current_amd64.deb \
"$SRCDIR"/chrome/
abinfo "Deploying files ..."
mkdir -pv "$PKGDIR"/usr/{lib/google-chrome,share/pixmaps}
cp -arv "$SRCDIR"/chrome/opt/google/chrome/* \
"$PKGDIR"/usr/lib/google-chrome/
cp -rv "$SRCDIR"/chrome/usr \
"$PKGDIR"/
abinfo "Setting executable bits on shared objects ..."
chmod -v +x "$PKGDIR"/usr/lib/google-chrome/*.so*
abinfo "Installing icons ..."
ln -sfv ../../lib/google-chrome/product_logo_256.png \
"$PKGDIR"/usr/share/pixmaps/google-chrome.png
abinfo "Installing symlink to google-chrome ..."
ln -sfv ../lib/google-chrome/google-chrome \
"$PKGDIR"/usr/bin/google-chrome-stable
abinfo "Removing cron job for APT updates ..."
rm -fv /etc/cron.daily/google-chrome
```
### 3.6.4 beyond
beyond 脚本是在构建三连后执行的。beyond 脚本常见的用途有:
- 追加没有被构建系统安装的文件
- 修正文件的权限
- 调整部分文件的位置
- 生成或修改 pkg-config 配置文件
- 生成手册页和文档
- 生成 Shell 的命令补全
通常 beyond 文件所做的操作都属于小修小补,偶尔也有追加编译的情况。下面举几个例子。
1. btrfsprogs 默认不安装 Shell 命令补全文件,需要在 beyond 脚本中手动安装:
```bash=
abinfo "Installing bash completions ..."
install -Dvm644 "$SRCDIR"/btrfs-completion \
"$PKGDIR"/usr/share/bash-completion/completions/btrfs
```
2. Linux 的用户管理和鉴权套件 Shadow 需要在安装后修正 `su` 程序的权限,并且需要将 `/sbin` 里的可执行文件移动到 `/bin`(安同 OS 不使用 `/usr/sbin`):
```bash=
abinfo "Installing groupmems PAM configuration ..."
install -Dvm644 "$SRCDIR"/etc/pam.d/groupmems \
"$PKGDIR"/etc/pam.d/groupmems
abinfo "Dropping logoutd ..."
rm -v "$PKGDIR"/usr/sbin/logoutd
abinfo "Setting SUID for /usr/bin/su ..."
chmod u+s "$PKGDIR"/usr/bin/su
abinfo "Move everything else to /usr/bin, because this isn't handled by ./configure..."
mv "$PKGDIR"/usr/sbin/* "$PKGDIR"/usr/bin
rm -rv "$PKGDIR"/usr/sbin
```
## 3.7 总结
- Autobuild 是安同 OS 打包过程中直接接触软件源码、构建打包的一环。
- 供 Autobuild 读取使用的打包信息存储在源码目录中的 `autobuild` 文件夹下。
- 每个软件包必须有 `autobuild/defines` 文件,即软件包定义。
- Autobuild 能够处理很多种常见的构建系统,自动根据系统规定和打包者指定的参数执行构建三连。
- Autobuild 也允许打包者在构建三连前后运行自定义脚本(`prepare` 和 `beyond`),灵活定制构建过程。
- 在 Autobuild 不能自动处理构建过程的时候,开发者可以自行编写构建脚本(`build`)以代替构件模板执行构建。
- 自行编写构建脚本时需要尽可能复用模板中包装的函数和过程。
# 第四章 将软件包集成进 ABBS 树
安同 OS 软件源内有数千个软件包,因此安同 OS 也拥有组织这数千个软件包的打包信息的机制。安同 OS 管理打包信息的机制主要分为两部分,一端是由 Git 管理的软件包信息树 ABBS,一端是负责下载源码并发起构建的 ACBS。
## 4.1 什么是 ABBS?
ABBS 是安同 OS 管理和组织打包脚本的 Git 仓库,利用 Git 来处理和记录安同 OS 中所有打包脚本的变动。由于 ABBS 采用树状的组织方式(所有目录组织方式均是如此),因此经常称其为 “ABBS 树”。

<p style="text-align: center">
<i>ABBS 树的 GitHub 页面</i>
</p>
您可以前往 [ABBS 树的 GitHub 页面][aosc-os-abbs]浏览 ABBS 树的内容。您也可以把 ABBS 树克隆到您的电脑上:
```shell
$ git clone https://github.com/AOSC-Dev/aosc-os-abbs.git
$ git clone git@github.com:AOSC-Dev/aosc-os-abbs.git
```
### 4.1.1 组织方式
ABBS 按照软件包的类别分类软件包。ABBS 的分类分为两部分,一个是软件类型(应用程序、共享库等),一个是软件包主要面向的应用场景,也就是软件分类。
其中,软件类型包括:
- `app`: 以应用程序为主的软件。
- `core`: 安同 OS 的核心组件(GCC 编译器、GNU glibc 运行库及其依赖)
- `runtime`: 以运行时的共享库为主的软件。
- `lang`: 编程语言相关的软件(编译器、包管理器等)
- `desktop`: 桌面环境相关的应用程序、运行库等。
- `meta`: 元包(Metapackages,只记录了依赖信息、没有实际内容的软件包)
除了一些顾名思义的分类外,ABBS 针对 `app` 和 `runtime` 细分了一些软件分类:
- `admin`: 系统管理工具
- `a11y` (Accessibility): 辅助工具(又叫可访问性工具)
- `benchmarks`: 评测工具
- `cryptography`: 加密相关的程序
- `database`: 数据库服务端
- `doc`: 文档生成工具
- `devel`: 开发工具(编译器、链接器等)
- `editors`: 文本编辑器
- `emulation`:各类设备(游戏主机、其他硬件设备等)的模拟器
- `i18n` (Internationalization): 国际化相关的软件(翻译软件等)
- `productivity`: 生产力工具(办公套件等)
- `scientific`: 科学计算工具
- `utils`: 实用工具(文件管理工具、压缩解压缩工具等)
### 4.1.2 仓库的目录结构
所有软件包的打包脚本按照分类分别存放。下面将详细介绍软件包的组织方式。
- `aosc-os-abbs`: 仓库根目录
- `软件类型-软件分类/`: 一级分类文件夹,如 `app-admin`
- `软件包名称`: 存放软件包源码信息及 `autobuild` 文件夹,如 `shadow`
- `spec`: 软件包版本及源码信息
- `autobuild/`: 打包脚本所在文件夹
大多数软件包只包含一套打包信息。但有时软件包文件夹下会存在不止一套 Autobuild 打包信息:这说明该软件项目被拆分为多个子软件包。
### 4.1.3 拆包时的目录结构
有些软件包既有日常使用期间一定会依赖到的运行库,也包含日常用不到的部分。即便安同 OS 奉行 “不拆包” 的概念,但此类软件包不拆包是不现实的。以 GCC 为例,被拆包的软件的目录结构一般如下:
- `gcc/`: GCC 的打包信息所在文件夹
- `spec`: GCC 的源码信息
- `01-runtime/`: 拆分的第一个软件包的 `autobuild` 文件夹(GCC 运行时库)
- `defines`: 第一个拆分的软件包定义
- `02-compiler/`: 拆分的第二个软件包的 `autobuild` 文件夹(GCC 编译器本体)
- `defines`: 第二个拆分的软件包定义
> [!Note]
> GCC 身为编译器,在用户的日常使用场景中不会用到。但 GCC 的运行时库(`libgcc`、 `libstdc++` 等)却被几乎所有 C 和 C++ 语言编写的程序依赖。
>
> 其他 C 和 C++ 语言的编译器也是如此,如 Clang 有一套自己的 C++ 运行时 `libc++`。
### 4.1.4 ABBS 的提交准则
安同 OS 采用主题制的维护方案,因此安同 OS 的开发极度依靠 Git 的分支机制。所有改动最终都会合并到 `stable` 分支,又称为 “稳定源” 及 “主线”。`stable` 分支记录着最终分发给广大用户的软件包的构建信息。
因此,ABBS 不允许直接将软件包改动推送至 `stable` 分支。您必须先基于当前最新的 `stable` 分支派生出新的分支(Topic 分支),并在新分支上提交相应改动。经过一系列测试及审阅后,您的修改才会进入 `stable` 分支。
ABBS 始终保持线性的 Commit 历史,因此仓库中不允许使用 `git merge` 等会创建 Merge Commit 的形式合并分支。所有的分支最终都会变基 (Rebase) 到 `stable` 分支,然后将 `stable` 分支快进,完成合并,同时保留线性的提交历史。
不过由于种种原因,Topic 分支内可能需要重新变基。您可以随意整理您的 Topic 分支,此时会产生本地和远端仓库之间的分歧,因此所有的 Topic 分支均允许强制推送 (Force push)。您需要善用变基功能整理您的 Topic 分支。
## 4.2 ACBS 介绍
ACBS 全称 Autobuild CI Build System,是安同 OS 打包环节中的第二环,负责根据 ABBS 树记录的信息发起构建,即调用 Autobuild。具体地讲,ACBS 主要负责以下内容:
- 读取软件包的源码信息和依赖
- 根据依赖信息决定构建序列
- 对每个包:下载并解压缩源码包
- 复制对应包的构建脚本目录 (`autobuild`) 到源码目录中
- 进入源码目录,调用 Autobuild4
- 等待 Autobuild4 执行完毕,将软件包复制到 `/debs` 文件夹下
## 4.3 使用 ACBS 发起编译
> [!Important]
> 和手动运行 Autobuild 一样,手动执行 ACBS 也同样会修改您的系统。在接触容器管理器之前,我们强烈建议您在独立的环境中运行 Autobuild 和 ACBS,以避免污染您的系统。您可以在虚拟机、容器及备用机等独立环境中尝试安同 OS 的开发。
### 4.3.1 配置 ACBS
和 Autobuild 一样,ACBS 在运行前也需要配置。ACBS 需要知道您的 ABBS 树的所在位置。
ACBS 的配置文件在 `/etc/acbs/forest.conf`。以 Root 身份编辑该文件,您可以看到以下内容:
```ini=
[default]
location = /var/lib/acbs/repo
```
其中,`location` 指向 ABBS 树的位置。您可以不修改配置文件,直接将 ABBS 树克隆至默认位置即可:
```shell
# mkdir -p /var/lib/acbs/
# git clone https://github.com/AOSC-Dev/aosc-os-abbs /var/lib/acbs/repo
```
### 4.3.2 发起编译
准备好 ABBS 树之后,您就可以运行 ACBS 编译 ABBS 树中已有的软件包了:
```shell
# acbs-build 软件包名称
```
> [!Important]
> 和 Autobuild 一样,您需要以 root 身份运行 ACBS。

<p style="text-align: center; font-style: italic">
<code>acbs-build bash</code> 的执行结果
</p>
由上图可见,ACBS 会自动将产出的 `deb` 包复制到 `/debs` 目录下。
## 4.4 添加软件包
安同 OS 的所有软件包打包脚本均位于 ABBS 树中。您需要在 ABBS 树中的合适位置添加软件包的定义。本节中我们只讲述流程本身,有关 Git 的操作流程将在下文讲解。
添加新软件包一般需要经过以下步骤:
1. 调查软件项目,获得稳定的源码包下载链接及源码包校验信息;
2. 在 ABBS 中的合适位置新建文件夹;
3. 编写软件包源码信息 `spec`;
4. 编写 Autobuild 定义文件及打包脚本;
5. 打包测试,根据情况调整打包定义。
### 4.4.1 调查软件的源码获取途径
在您着手打包前,您需要先了解获取软件源码的途径。
一般情况下,您可以通过如下方式找到源码:
- 软件的官网。大部分知名软件都有自己的项目主页,且有自己的下载页面,发行的源代码通常也可以在站内直接下载到:
- 有些软件有自己的域名。如 86Box 注册了 86box.net
- 有些软件属于某个项目的一部分,因此可能没有自己的域名。如 GNU Emacs 是 GNU 项目的一部分,因此 Emacs 和其他 GNU 项目一样都在 gnu.org 下
- 托管项目代码的托管平台所对应的仓库页面。一些项目可能没有官网,取而代之的是项目在各大代码托管平台的仓库,可以直接使用对应的版本控制软件克隆到本地:
- GitHub:定位项目的链接一般遵循 `github.com/用户或组织/项目名称` 的格式。
- GitLab:定位项目的链接一般遵循 `gitlab.com/用户或组织/项目名称`、`gitlab.com/用户或组织/子项目/项目名称` 的格式。GitLab 允许在用户或组织的名下继续分类。
- cgit 或 gitweb:克隆仓库的链接会显示在仓库的主页(Summary)上。

<p style="text-align: center; font-style: italic">
cgit 的使用场景之一就是 Linux 内核的 Git 网页前端
</p>
- 有些软件有自己的官网,但其源码则托管在代码平台上。一般官网会给出源码的下载链接,但有些下载链接也会将您引导到代码托管平台的 Releasses 页面。
### 4.4.2 通过官网获得源码压缩包
绝大多数软件均会以压缩包(俗称 “tarball”,源码一般会以 tar 格式打包后压缩)的形式发布。Tarball 的获取途径一般是软件官网的下载页面。软件官网一般会将最新版本的发布说明或源码下载链接放置在首页或 “Downloads (下载)” 页面中。例如,qBittorrent 的官网 https://www.qbittorrent.org/ 就有提供源码包下载(如图所示):

<p style="text-align: center; font-style: italic">
qBittorrent 的官网主页,可见并没有源码链接的身影
</p>
由上图可见,qBittorrent 并未在首页放置下载链接,因此您需要点击 “Downloads” 前往下载页面,找到有关源码的一节:

<p style="text-align: center; font-style: italic">
qBittorrent 下载页面中 “Source Tarball” 一节
</p>
由上例可见,在官网发布 tarball 的软件不仅提供源码链接,同时还提供源码包的校验信息(SHA-256、MD5 等算法的校验值)。源码包的校验信息尤为重要,成功比对校验值就意味着下载的源码包没有损坏,且没有被中途修改。
> [!Important]
> 所有的源码包下载链接均为直链形式,也就是说,链接尾部必须为文件名,且链接中不能有问号、`&` 符号等 URL 参数。
> 如,下面的例子就是符合要求的链接:
>
> - `https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/snapshot/linux-6.10.10.tar.gz`
> - `https://github.com/htop-dev/htop/releases/download/3.3.0/htop-3.3.0.tar.xz`
> - `https://ftp.gnu.org/gnu/gcc/gcc-14.2.0/gcc-14.2.0.tar.xz`
> [!Note]
> 有些软件会发行由多种压缩算法压缩的 tar 包:
> - `.tar.gz`: Gzip
> - `.tar.xz`: LZMA (xz)
> - `.tar.bz2`: Bzip2
>
> 有时您可能也会见到 `.zip` 格式打包的源码。如果软件有发行多种格式的源码包,请采取如下顺序优先选择:
> 1. `.tar.xz`
> 2. `.tar.gz`
> 3. `.tar.bz2`
> 4. `.zip`
### 4.4.3 通过版本控制系统 (VCS) 和代码托管平台获得源码
<!-- 有些软件即便有官网,也可能不直接提供 tarball,而是指引开发者克隆软件的版本追踪系统仓库(Git、Mercurial、Subversion 等),然后检出期望的版本。直接在代码托管平台上安家的项目也采取这种获取源代码的形式。 -->
有些软件有自己的官网,但开发工作和代码托管却在代码托管平台上进行。也有软件在托管平台上安家,这类软件没有自己的官网,所有的开发工作、Bug 追踪工作都在代码托管平台上进行。
一般情况下,获取这些项目的源码最直接的方式是使用 Git 等版本控制软件克隆源码仓库。不过,为了方便发行版开发者,软件项目通常也会提供 tarball。有一部分此类项目会自行在代码托管平台的 Releases 页面上自行发布 tarball,有些则完全依赖代码托管平台生成 tarball。
对于 GitHub 和 GitLab,您可以直接前往项目主页(列出项目文件和 README 的页面),此时页面的链接一般就是仓库的 URL:
- 对于 GitHub,仓库 URL 的形式为 `https://github.com/用户名或组织名/项目名`,如
- `https://github.com/nginx/nginx`
- `https://github.com/felixonmars/fcitx5-pinyin-zhwiki`
- 对于 GitLab,大多数 URL 与 GitHub 保持一致,但有时仓库名与用户名之间会有子分类,如:
- `https://gitlab.com/qemu-project/qemu` (没有子分类)
- `https://gitlab.freedesktop.org/xorg/driver/xf86-input-libinput` (有 `driver` 子分类)
之后您就可以通过 Git 克隆项目的仓库了:
```shell
$ git clone https://gitlab.freedesktop.org/xorg/driver/xf86-input-libinput
```
> [!Important]
> ### 注意区分自行上传的和代码平台自动生成的 tarball
>
> 上文提到代码平台会自动生成对应版本的 tarball,这些 tarball 一般会与项目自行打包的 tarball 一起在代码托管平台的 Release 页面提供,**您应该避免利用这些链接**。
>
> 然而有些 tarball 是由维护者自行上传的(如下图由绿色框出的链接就指向的是维护者自行上传到 Releases 页面的 tarball),此时您应该使用这类链接下载 tarball;红色框出的链接就是您不应该使用的自动生成 tarball 的链接:
>
> 
>
> 通常情况下,代码托管平台自动生成的 tarball 可能无法复现——这意味着每隔一段时间,由同一版源码生成的 tarball 会得出不同的校验值。
>
> 这种情况会令自动打包系统失效,因为自动打包系统同样需要确保源码包的完整性 (Integrity)。造成这种情况的原因较为复杂,但其中最明显的一个原因是每次代码平台生成 tarball 时会记录文件的时间戳信息,而文件的创建时间、修改时间及访问时间等信息可能会随着生成的时间而改变。
>
> 因此,如果项目在 GitHub、GitLab、SourceForge 等平台托管了代码,并且没有在 Releases 页面自行发布 tarball,强烈建议您利用版本控制系统获取源码。
# 第五章 使用 Ciel 管理编译环境
# 第六章 安同 OS 的测试源机制
# 第七章 使用 BuildIt!
# 第八章