用过 Arch Linux 及其衍生发行版的同学,都会被 AUR 中海量的软件包所打动。这些软件包都是由用户提供的。使用这些软件包后不免生出自己也要学着创建几个软件包。经过几次尝试后,我基本学会了一些简单的打包技术。Arch Linux 的打包还是很简单的。

环境配置

有一个能运行的 Arch Linux 电脑,并且装上了 base-devel 包组就足够了。Arch Linux 打包的基本原理是在一个 fakeroot (虚拟根目录)中编译安装软件。然后将这个 fakeroot 中的文件压缩到软件包里面,同时清除一些不需要的文件和符号,并且根据 PKGBUILD 文件自动生成基本信息,最后运行你事先写好的必要的脚本进行一些配置。

编写 PKGBUILD 文件

PKGBUILD 文件中需要有一些基本变量以及几个函数。

makepkg 默认的变量

srcdir

makepkg 将会把源文件解压到此文件夹或在此文件夹中生成指向 PKGBUILD 里 source 数组中文件的软连接。

pkgdir

makepkg 会把该文件夹当成系统根目录,并将软件安装在此文件夹下。

这些变量都是绝对路径, 即意味着, 如果你合适地使用这些变量, 就不用担心当前工作目录的影响.

PKGBUILD中基本变量

Maintainer

最开头一行注释是维护者的信息,按照它提供的格式填写上有效的信息即可。

Contributor

贡献者信息,其他参与贡献者的信息

注意: 如果你是接手别人的包,除了把你的名字列为包维护者(Maintainers)外,你还应当把之前的维护者列为贡献者(Contributors)

pkgname

软件包的名字。只能用 小写字母、数字和@ . _ + - 这些字符,且不允许用.或者-作开头。

千万不要和 AUR 甚至是官方仓库里面的软件包重名了

pkgver

软件包的版本,就是你打包的那个软件的版本。可以使用数字和小数点,以及其它字符。

pkgrel

软件包发行号,一般设为 1,如果你因为某些原因给同版本号的软件进行反复打包,那么每次打包的时候 pkgrel 就应该在原基础上递增一个数字,而在打包新的版本的时候,应该重新设为 1。

epoch

强行干涉包的新旧关系,拥有更大的 epoch 值的包会被认做更新的包(此时无视版本号),可以用在如版本号风格改变等需要的时候。默认值为 0,取值为正整数。一般不会用到。

pkgdesc

软件包的描述信息,最好一句话,且不包含软件的名字。

arch

表示支持的 Arch Linux 的架构,比如 i686x86_64,如果包与平台无关的话就填 any

url

与软件包相关的链接,一般是项目首页什么的。

license

软件发布协议,诸如 GPLMITLGPL等协议。

depends

依赖项。这是非常重要的一项,需要正确填写上软件的依赖。

对于直接发布可执行程序的话,可以通过 ldd 来看程序连接了哪些库文件,结合搜索判断出具体依赖是什么软件包。你可以在官方仓库索引中搜索上具体库的文件名,一般都能够找到对应的软件包。

如果你已经用 makepkg 构建出了 .tar.xz 的包,也可以用 Namcap 来检查依赖是否存在问题,它会提供一些有用的信息帮助修正依赖。对于他的输出含义可以直接参考 ArchWiki

合格的打包者需要多测试,确保依赖真的没问题。

makedepends

仅在软件构建生成时依赖的软件包列表。

source

构建软件包需要的文件。可以是一个本地文件,也可以是一个远程文件。 makepkg 会在构建包的时候自动下载填写的远程文件,并且会自动解包压缩文件。

md5sums

对应的 source 里面文件的 md5 校验码。

makepkg-g/--geninteg 选项可以自动生成校验值,通常可以通过 makepkg -g >> PKGBUILD 命令写入。

updpkgsums 也可以自动更新 PKGBUILD 中的校验码。

PKGBUILD 中的函数

一共有五个函数, 以下按照它们执行的先后顺序列出。package() 函数是每个 PKGBUILD 中必须的函数,其余不存在的函数可以跳过。

prepare()

此函数会执行用于预处理源文件以进行构建的命令, 例如 patching. 此函数执行在 build() 之前, 软件包解压之后. 如果解压过程被跳过 (makepkg -e), 那么 prepare() 函数就不会被执行.

注意:该函数运行在 bash -e 模式下, 意味着任何以非零状态退出的命令都会造成该函数中止.

pkgver()

pkgver() 会在抓取并解压源文件,执行 prepare() 后后执行此函数。

如果你正在制作 git/svn/hg 等构建过程相同, 但源文件可能每天甚至每小时更新一次的软件包的时候, 这一特性是十分有用的. 过去的方法是把日期写入到 pkgver 变量中, 但这样一来 makepkg 会在即使软件没有更新的情况下依然重新构建软件包, 因为它会认为软件包的版本改变了. 其他与此有关的命令有 git describe, hg identify -ni 等等. 请在提交 PKGBUILD 前做好测试, 因为如果 pkgver() 执行失败, 整个构建过程都会终止.

注意: pkgver 不能含有空格或连接符 (-). 通常都会用 sed 来进行修改.

build()

编译函数,现在你需要编写 PKGBUILD 文件中的 build() 函数。这个函数使用通用的 shell 命令来自动编译软件并创建软件的安装目录。这允许 makepkg 无需详查你的文件系统就可以打包你的软件。

在 build() 函数中第一步就是进入由解压源码包所生成的目录。 makepkg 会在执行 build() 函数之前更改当前目录为 $srcdir;同时会把你的 source 中提到的文件复制到这个目录,如果是压缩文件还会自动解压。因此, 大多数情况下第一条命令是这样的:

1
cd "$srcdir/$pkgname-$pkgver"

现在,你需要把你当时手动编译软件时用到的命令放在 build() 函数中。build() 就会在 fakeroot 中运行这些命令,对软件进行编译。如果你要打包的软件使用了一个配置脚本,最好在配置中加上--prefix=/usr。许多软件都将自己安装到 /usr/local 下,手动从源码安装时可以安装到 /usr/local。但是打包为 Arch Linux 软件包都应当使用 /usr 目录。

1
2
./configure --prefix=/usr
make

注意: 如果你的软件不需要构建任何东西, 请不要使用 build() 函数. 但package() 函数依然是必须的.

check()

用来执行 make check 和其他一些例行测试的地方。如果不需要可以通过在 PKGBUILD 或者 /makepkg.conf 中使用 BUILDENV+=('!check'), 也可以在 makepkg 时使用参数 --nocheck 来禁用它。

package()

最后一步就是把编译好的文件放到 pkg 文件夹——一个简单的 fakeroot 环境。pkg 目录复制了根目录下软件安装路径的继承关系。如果你需要手动把文件放到根目录下,那么在这里你需要把文件放在 pkg 下相同的文件层级结构中。比如,你想把一个文件安装到 /usr/bin,那么在 fakeroot 环境中对应的路径为 $pkgdir/usr/bin。极少情况下的安装步骤需要用户手动复制大量的文件到某个地方。大部分软件安装时只需要调用 make install 即可。为了将软件安装到正确的路径,最后一行一般应该这样写:

1
make DESTDIR="$pkgdir/" install

注意:有时候在 Makefile 里没有使用 DESTDIR,你可能需要使用 prefix 来替代。如果软件包是用 autoconf/automake 来创建的,那就使用 DESTDIR;如果 DESTDIR 不起作用,试试 make prefix="$pkgdir/usr/" install。如果这还不起作用的话,你就需要深入检查软件的安装命令了。

makepkg --repackage 命令只运行 package() 函数,它只是将文件打包成软件包,并不运行编译过程。如果你只是更改了 PKGBUILD 中的依赖,用这个命令来打包可以节省很多时间。

注意: build() 和 package() 函数在运行过程中都应当是非交互的。在这些函数中调用交互工具或脚本可能会中断 makepkg 的运行。

编写脚本

脚本文件的名称,一般为 $pkgname.install。在 PKGBUILD 文件中引用这些脚本,pacman 可以在安装、卸载或升级一个软件包时存储及执行一些特定的脚本。在不同的情况下,脚本包含了下面几个函数,并且在特定时刻执行它们:

pre_install - 安装前运行的脚本。可以传递一个参数:版本号。

post_install - 安装后运行的脚本。可以传递一个参数:版本号。

pre_upgrade - 升级前运行的脚本。可以按以下顺序传递两个参数:新版本号,旧版本号。

post_upgrade - 升级后运行的脚本。可以按以下顺序传递两个参数:新版本号,旧版本号。

pre_remove - 卸载前运行的脚本,可以传递一个参数:版本号。

post_remove - 卸载后运行的脚本,可以传递一个参数:版本号。

每一个函数都是在 pacman 安装目录下通过 chroot 运行。参见这个帖子.

Pacman 钩子也提供相似的功能。

注意: 脚本不要以 exit 结束,否则包含的函数无法被执行。

测试PKGBUILD文件

你在写 PKGBUILD 的 build() 方法时,会想频繁的测试你所做的改动以确保没有 bug。你可以在包含 PKGBUILD 的目录下运行 makepkg 命令来确保没有问题。如果 PKGBUILD 没有错误,将会生成一个包,但是如果 PKGBUILD 被破坏或未完成,它将抛出一个错误。

如果运行 makepkg 成功,在你工作的目录下将会生成一个名为 $pkgname-$pkgver.pkg.tar.gz 的新文件。这个文件可以使用 pacman -Upacman -A 安装,你也可以将它加到本地或网上的软件仓库中。注意,一个包被构建并不代表你的工作就完成了!只有当所有文件的结构都正确才能确保完成。你可以使用pacman 的查询功能显示软件包包含的文件及依赖的文件,然后将它于你认为正确情况的对比。pacman -Qlp <package file>pacman -Qip <package file> 命令可以完成这项工作。

如果包看起来是正确的,那你的工作就完成了。但是如果你打算发布这个包或 PKGBUILD,你就需要确认确认再确认包的依赖关系。

检查包的逻辑性

确定包可以正常使用后,再使用 namcap 来检查错误:

1
2
$ namcap PKGBUILD
$ namcap <package file name>.pkg.tar.zst

Namcap 将会做以下工作:

检查 PKGBUILD 文件里的一些常见错误

ldd 扫描包中所有的 ELF 文件,自动报告缺失或可去除的依赖。

启发式搜寻缺失或冗余的依赖。

要养成用 namcap 检查包的习惯,以避免提交包后再做修复的麻烦。