AB
AiBoss
Tutorials

Debuild

Tutorials

用 debuild 把预编译二进制与配置打包成 deb 安装包

面向需要分发预编译产物的开发者,讲清如何用 debuild 与 debhelper 把 .so、.pyc、可执行脚本和配置文件组织成一个可安装的 deb 包:从安装 devscripts 与 build-essential、编写 changelog 与 control,到在 rules 里逐条 install 并控制文件权限,最后用 debuild -uc -us 出包,并说明 dh_strip、dh_fixperms 这类默认行为会带来的坑。

手里已经有一批编译好的产物——共享库、字节码、可执行脚本、配置文件——想把它交给别人用 apt 或 dpkg 安装,而不是让对方手工拷贝目录、手工 chmod,这时需要的就是一个 deb 包。本文讲的是用 debuild 配合 debhelper 完成这件事的完整流程,重点不在「从源码编译」,而在「把现成的文件按指定路径、指定权限装进系统」。Debuild 在这里扮演的是构建入口:它读取 debian/ 目录下的元数据与 rules 脚本,调用 debhelper 的一系列 dh_* 工具,最终产出 .deb 与相关的构建文件。

适合读这篇的人:需要把内部组件、插件、运行时库打包分发的开发者;需要给一组文件固定安装路径与权限的运维人员;以及第一次接触 Debian 打包、被 debian/rules 里那一堆 dh_ 调用绕晕的人。本文以一个名为 middleware 的组件为例,它的产物包括一个顶层 .so、若干子目录下的 .so 与 __init__.pyc、一个 bin 目录下的可执行文件,以及一份需要落到用户主目录下的配置文件。

准备工作

安装构建工具链

打包所需的命令分散在几个包里:debuild、dch 来自 devscripts,编译与基础构建工具来自 build-essential,包质量检查工具是 lintian。在 Debian 或 Ubuntu 系发行版上一次性装齐:

sudo apt-get install devscripts build-essential lintian

装完之后可以用 which debuild 与 which dch 确认命令已经在 PATH 里。如果系统提示找不到 debuild,基本就是 devscripts 没装上。

整理待打包的文件清单

动手写 debian/ 之前,先把要装进包里的文件、以及它们各自应有的权限列成一张表。这一步决定了后面 rules 里要写几条 install,也决定了每条用哪个 --mode。示例组件的清单如下:

文件权限说明
middleware/middleware.so755顶层共享库
middleware/a/__init__.pyc755包初始化字节码
middleware/a/b/__init__.pyc755子包初始化字节码
middleware/a/b/part1.so755子包共享库
middleware/a/b/part2.so755子包共享库
middleware/bin/valideLicense775可执行程序
middleware/config/auth644配置文件

注意清单里出现了三种权限:库与字节码是 755,可执行程序是 775,配置文件是 644。后面 rules 里会分别用 SCRIPTOPTS、BINARYOPTS 和显式的 -m 0644 来对应,不要图省事全部用同一个模式。

准备目录结构

约定 middleware-package 作为 Debian 打包的主目录,debian/ 放在它下面,用来存放所有打包元数据。先建目录:

mkdir -p middleware-package/debian

待打包的源文件(middleware.so、a/、bin/、config/)放在与 middleware-package 同级的 middleware/ 目录里。这个相对位置很关键,因为 rules 里会用 $(CURDIR)/../ 去引用它们。

操作步骤

第一步:生成 changelog

debian/changelog 是 deb 包的必备文件,debuild 会从里面读取版本号。用 dch 生成一份初始条目:

dch -i --create

生成的内容形如:

PACKAGE (VERSION) UNRELEASED; urgency=medium

  * Initial release. (Closes: #XXXXXX)

 -- name <info@company-tech.com>  Tue, 25 Oct 2016 05:03:45 +0000

需要手工改三处:把 PACKAGE 换成 middleware-package,把 VERSION 换成实际版本号(示例为 1.0.0-1),把 UNRELEASED 换成目标发行版代号(示例为 unstable)。urgency 从 medium 改成 low 也可以,它只影响升级提示的紧急程度。维护者姓名与邮箱按实际情况填写,格式必须是「名字 <邮箱>」,中间两个空格再跟日期行。

第二步:创建 debian/ 下的元数据文件

进入 debian/ 目录,先建出一批空文件,再逐个填内容:

cd middleware-package/debian
touch compat control copyright dirs postinst preinst rules conffiles

其中 compat 写 debhelper 的兼容级别,示例用 9:

echo 9 > compat

也可以改用 dh-make 生成一整套骨架,它会自动创建 control、rules、copyright 等模板文件,省去手工 touch。两种方式选一种即可,不要混着来,否则容易出现重复定义。

第三步:编写 control 文件

control 描述包的来源、维护者、构建依赖与二进制包信息。示例内容:

Source: middleware-package
Section: main
Priority: optional
Maintainer: COMPANY-TECH <info@company-tech.com>
Build-Depends: debhelper (>> 5.0.0)
Standards-Version: 1.0.0-1

Package: middleware-package
Architecture: amd64
Depends: ${perl:Depends}, ${misc:Depends}
Description: middleware package
 middleware provide the tools of control ...

几个字段值得留意:Source 是源码包名,Package 是二进制包名,两者可以相同;Architecture 写 amd64 表示这个包只适用于该架构,如果产物是纯脚本、与架构无关,可以改成 all;Depends 里的 ${perl:Depends} 与 ${misc:Depends} 是占位符,由 dh_perl 与 dh_gencontrol 在构建时替换成实际依赖;Description 的第一行是简短描述,后续行以空格开头作为长描述。

第四步:编写 rules 脚本

debian/rules 是一个 Makefile,也是整个打包过程的核心。它必须可执行,并且第一行是 shebang。示例开头:

#!/usr/bin/make -f
# Sample debian/rules that uses debhelper.
#export DH_VERBOSE=1

取消注释 export DH_VERBOSE=1 可以让构建过程输出更多细节,排查问题时很有用。

接着定义三组安装选项,对应清单里的三种权限:

SCRIPTOPTS="--mode=0755"
INCOPTS="--mode=0644"
BINARYOPTS="--mode=0775"

然后是 configure、build、clean 三个目标。由于本文的场景是打包预编译产物,没有真正的编译动作,configure 与 build 只需要打时间戳:

configure: configure-stamp
configure-stamp:
	dh_testdir
	touch configure-stamp

build: configure-stamp build-stamp
build-stamp:
	dh_testdir
	touch build-stamp

clean:
	dh_testdir
	dh_testroot
	rm -f build-stamp configure-stamp
	dh_clean

真正干活的是 install 目标。它先清理并创建安装目录,再用 install 命令把每个文件按指定权限拷到 debian/middleware-package/ 下的目标路径:

install: build
	dh_testdir
	dh_testroot
	dh_clean -k
	dh_installdirs

	install ${SCRIPTOPTS} $(CURDIR)/../middleware.so \
		$(CURDIR)/debian/middleware-package/usr/local/dsr/bin/middleware
	install ${SCRIPTOPTS} $(CURDIR)/../a/b/*.so \
		$(CURDIR)/debian/middleware-package/usr/local/dsr/bin/middleware/a/b/
	install ${SCRIPTOPTS} $(CURDIR)/../a/b/__init__.pyc \
		$(CURDIR)/debian/middleware-package/usr/local/dsr/bin/middleware/a/b/
	install ${SCRIPTOPTS} $(CURDIR)/../a/__init__.pyc \
		$(CURDIR)/debian/middleware-package/usr/local/dsr/bin/middleware/a/
	install ${BINARYOPTS} $(CURDIR)/../bin/* \
		$(CURDIR)/debian/middleware-package/usr/local/dsr/bin/
	install -m 0644 $(CURDIR)/../config/auth \
		$(CURDIR)/debian/middleware-package/root/.config/application/

这里的目标路径 debian/middleware-package/ 就是「暂存根目录」,它下面的 usr/local/dsr/bin/ 会原样成为安装后的 /usr/local/dsr/bin/。配置文件被放到 root/.config/application/,安装后即 /root/.config/application/auth。如果希望配置落到普通用户目录,需要相应调整路径,并考虑用 postinst 处理。

接下来是 binary-indep 目标,它调用一串 dh_* 工具完成元数据生成与打包。示例里保留的调用包括 dh_installdirs、dh_installdebconf、dh_installdocs、dh_installexamples、dh_installmenu、dh_installlogrotate、dh_installemacsen、dh_installpam、dh_installmime、dh_installinit、dh_installcron、dh_installman、dh_installinfo、dh_installchangelogs、dh_link、dh_compress、dh_makeshlibs、dh_installdeb、dh_perl、dh_shlibdeps、dh_gencontrol、dh_md5sums、dh_builddeb。其中被注释掉的 dh_undocumented 属于旧版工具,现代 debhelper 已不再需要。

示例里还定义了 override_dh_auto_install 目标,内容为空。这是 debhelper 的覆盖机制:声明一个空的 override_dh_auto_install,就能阻止 dh_auto_install 自动去跑 make install,因为本文的文件安装完全由 install 目标手工完成。如果没有这个空覆盖,构建时 debhelper 可能会尝试执行并不存在的 Makefile 安装规则而报错。

最后是目标声明:

binary-arch: build install override_dh_auto_install
binary: binary-indep binary-arch
.PHONY: build clean binary-indep binary-arch binary install configure override_dh_auto_install

别忘了给 rules 加执行权限,否则 debuild 会直接失败:

chmod +x rules

第五步:用 debuild 出包

回到 middleware-package 目录,写一个简单的构建脚本包住 debuild:

cd middleware/middleware-package
vim make_middleware_package.sh

脚本内容只有一行核心命令:

debuild -uc -us

-uc 表示不生成源码包的 .changes 签名,-us 表示不对源码包做 GPG 签名。两者合起来的效果是:不需要打包者的 GPG 证书就能完成构建。这在内部构建、CI 环境里很常用;如果要发布到需要签名的仓库,就得去掉这两个参数并配置好 GPG 密钥。

给脚本加执行权限并运行:

chmod +x make_middleware_package.sh
sudo ./make_middleware_package.sh

构建成功后,.deb 会出现在 middleware-package 的上一级目录(debuild 的默认输出位置)。可以用 dpkg-deb -c 查看包内文件与权限,确认每个文件都落到了预期路径、权限也符合清单。

一个完整示例

把上面的步骤串起来,从零到出包的最小流程如下。假设当前目录下已有 middleware/ 存放全部待打包文件。

# 1. 安装工具
sudo apt-get install devscripts build-essential lintian

# 2. 建打包目录
mkdir -p middleware-package/debian
cd middleware-package

# 3. 生成 changelog 并手工改包名、版本、发行版
dch -i --create

# 4. 建元数据文件
cd debian
touch compat control copyright dirs postinst preinst rules conffiles
echo 9 > compat

# 5. 编辑 control(内容见上文)
vim control

# 6. 编辑 rules(内容见上文),并加执行权限
chmod +x rules

# 7. 回到打包主目录出包
cd ..
cat > make_middleware_package.sh <<'EOF'
debuild -uc -us
EOF
chmod +x make_middleware_package.sh
sudo ./make_middleware_package.sh

# 8. 检查产物
dpkg-deb -c ../middleware-package_1.0.0-1_amd64.deb

第 8 步的输出里,每一行形如 -rwxr-xr-x root/root ... ./usr/local/dsr/bin/middleware,逐行对照前面的文件清单,就能确认权限与路径是否都正确。如果某个文件权限不对,回到 rules 里检查对应的 --mode 或 -m 参数。

注意事项

不要开启 dh_strip

示例里的 dh_strip 被注释掉了,原因很明确:valideLicense 是一个 C++ 二进制,dh_strip 会剥离符号表,从而改变文件内容,导致哈希值发生变化。如果下游有基于哈希的校验(例如许可证校验),剥离后就会对不上。除非确认不需要保持哈希一致,否则保持注释状态。

不要开启 dh_fixperms

dh_fixperms 同样建议保持注释。它会把包内文件的权限统一修正,默认行为是把文件改成 644 一类的常规权限。本文的清单里明确要求 755 与 775,一旦开启 dh_fixperms,这些权限会被系统默认值覆盖,可执行文件可能失去执行位,共享库也可能不再可执行。手工在 rules 里控制权限的场景,就应该关掉这个自动修正。

路径与目录的对应关系

debian/middleware-package/ 下的目录层级就是安装后的绝对路径,少一层或多一层都会让文件落到错误位置。示例中 install 命令的目标目录必须以斜杠结尾(如 .../a/b/),否则 install 会把源文件当成目标文件名,而不是拷进目录。另外,$(CURDIR)/../ 依赖构建时的工作目录,如果换了构建位置,相对路径就会失效,建议在脚本里先 cd 到固定位置再调用 debuild。

changelog 的格式要求

dch 生成的模板里,维护者行必须是「姓名 <邮箱>」后跟两个空格再跟日期,日期格式为 Day, DD Mon YYYY HH:MM:SS +0000。格式不对时 debuild 会报 changelog 解析错误。版本号里的 -1 是 Debian 修订号,改动打包脚本但不改上游版本时,应该递增这个数字而不是改主版本号。

签名参数的选择

-uc 与 -us 跳过了签名,适合内部构建。但要注意,跳过签名后生成的 .changes 文件不能直接用于需要签名校验的仓库上传流程。如果后续要进入正式仓库,需要配置 GPG 密钥并去掉这两个参数,重新构建。

构建依赖与版本

control 里的 Build-Depends: debhelper (>> 5.0.0) 与 compat 里的 9 是示例取值,实际使用时应与目标发行版自带的 debhelper 版本匹配。不同发行版的 debhelper 默认兼容级别不同,compat 值过高会导致构建机上的 debhelper 拒绝工作。具体可用的版本范围以目标系统上 debhelper 包的实际情况为准。

易变信息以官网为准

本文涉及的软件包版本、兼容级别、工具行为都可能随发行版更新而变化。安装前请以 devscripts、debhelper 及所用发行版当前官方信息为准,不要照抄示例中的版本号。