
Debuild
用 debuild 把 C 程序打成 Debian 安装包
从一份 hello world C 程序出发,手把手完成 debian/ 目录下 control、changelog、rules、copyright 四个文件的编写,用 debuild 构建出 .deb 与源码包,并处理 make install 写入系统目录导致的权限报错。
把一个自己写的程序交给别人安装,最省事的做法是打成系统原生安装包。在 Debian 系发行版上,这个原生格式就是 .deb。手工拼装 .deb 需要分别调用 dpkg-buildpackage、fakeroot、lintian、debsign 等一串工具,参数多、顺序容易错。Debuild 把这些环节包在一个命令里:它负责调用构建流程、跑打包规范检查、处理签名,你只需要准备好源码和 debian/ 目录下的几个描述文件。
这篇教程面向已经会写一点 C、想给自己或团队的小工具做正式安装包的读者。全文用一个只打印一行文字的 hello world 程序做例子,从零走到产出 .deb 文件,重点讲清 debian/ 目录里每个文件的作用、字段怎么写,以及第一次构建几乎一定会撞上的权限报错该怎么改。文中出现的版本号、字段取值只是示例,实际打包时请以你所用发行版和工具链的当前信息为准。
准备工作
系统与工具
构建需要在 Debian 系系统上进行,例如 Debian 本身或 Ubuntu。安装三个软件包即可:
sudo apt-get install devscripts build-essential lintian
- devscripts:提供 debuild、debsign、dch(即 debchange)等打包辅助脚本。
- build-essential:提供 gcc、make 等编译工具,例子里的 C 程序要用到。
- lintian:Debian 打包规范检查器。debuild 会在构建过程中调用它,所以必须装上,否则构建流程会中断。
debuild 到底做了什么
理解这一点能省掉很多困惑:debuild 本身不是编译器,也不是打包器,它是 dpkg-buildpackage、fakeroot、lintian、debsign 这几个工具的包装层。它按顺序完成构建、在伪 root 环境下执行安装步骤、跑规范检查、对结果签名。因此你在 debian/rules 里写的规则,最终是被 dpkg-buildpackage 调用的;你看到的 lintian 警告,也是 debuild 顺手帮你跑出来的。
一个重要的环境限制
debuild 的构建过程会使用符号链接。如果你的源码目录放在 VirtualBox 或 VMware 的共享文件夹里,构建会失败。遇到这种情况,把源码复制到虚拟机本地磁盘上的普通目录再构建。
源码准备
例子程序只有一个源文件 main.c:
#include <stdio.h>
int main (int argc, char *argv[]) {
printf("Hi\n");
return 0;
}
以及一个 Makefile,包含编译目标和安装目标:
helloworld:
gcc main.c -o helloworld
install: helloworld
install -m 0755 helloworld /usr/local/bin
注意 Makefile 里命令行必须以硬制表符开头,不能用空格。这一点在后面写 debian/rules 时同样适用。
操作步骤
第一步:先验证程序本身能跑
打包之前先把程序跑通,避免把编译错误和打包错误混在一起排查。
make
./helloworld
屏幕上应输出 Hi。接着验证安装目标:
sudo make install
此时 helloworld 应出现在 /usr/local/bin 下。验证完把测试产物删掉,免得干扰后续打包:
sudo rm -rf /usr/local/bin/helloworld
第二步:创建 debian/ 目录
所有 Debian 打包相关的文件都放在源码根目录下的 debian/ 子目录里。先建目录:
mkdir debian
第三步:编写 debian/control
control 文件用 Debian 控制字段描述你的包。它通常同时声明一个二进制包和一个对应的源码包,源码包是二进制包可以重新构建的来源。一个可用的最小版本如下:
Source: helloworld
Maintainer: Your Name <you@example.com>
Build-Depends: debhelper (>= 8.0.0)
Standards-Version: 3.9.3
Section: utils
Package: helloworld
Priority: extra
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: a simple helloworld package
Just prints "Hi", it's very useful.
几个字段的含义值得单独说明:
- Source:源码包名称,习惯上与二进制包同名。
- Maintainer:维护者姓名与邮箱,格式必须是「名字 <邮箱>」。
- Build-Depends:构建这个包需要哪些依赖。例子依赖 debhelper,版本下限 8.0.0。
- Standards-Version:所遵循的 Debian 政策版本,写你实际对照的版本号。
- Section:软件分类,utils 表示工具类。
- Package:二进制包名称。
- Priority:优先级,例子用 extra。
- Architecture:any 表示与架构相关,需要针对每种架构分别构建。
- Depends:运行时依赖。${shlibs:Depends} 和 ${misc:Depends} 是占位符,由构建工具在打包时自动展开成实际依赖,不要手动替换。
- Description:第一行是简短描述,后续以空格开头的行是详细描述。空行会把简短描述和详细描述分开,格式不能乱。
第四步:编写 debian/changelog
changelog 记录包的每次变更,构建时至少要有一条记录。这个文件的语法比较讲究,手写容易出错,推荐用 dch(debchange)生成:
dch -i --create
该命令会打开 $EDITOR 指定的编辑器让你填写字段。生成后务必把版本状态从 UNRELEASED 改成 unstable,否则构建出的包不会被当作正式发布版本。生成结果大致如下:
helloworld (0.0.1) unstable; urgency=low
* Initial release.
-- Your Name <you@example.com> Sun, 07 Jun 2015 22:07:56 -0700
格式要点:第一行是「包名 (版本号) 发行版; urgency=级别」;中间是变更条目,每条以两个空格加星号开头;最后一行是「-- 维护者 日期」,注意 -- 后面有两个空格,日期格式为 RFC 2822 风格。后续每次改版本,继续用 dch 追加条目即可,不要手工破坏这个结构。
第五步:编写 debian/rules
rules 文件告诉 Debian 如何构建你的包。最简版本把所有调用转交给源码里已有的 Makefile:
#!/usr/bin/make -f
%:
dh $@
第一行是 shebang,声明用 make 解释这个文件;%: 是通配目标,dh $@ 表示把任何目标交给 debhelper 的 dh 命令处理。这里同样必须使用硬制表符缩进,否则 make 会报错。写完记得给执行权限:
chmod +x debian/rules
第六步:编写 debian/copyright
lintian 检查要求存在版权文件,内容可以很简单,但不能没有:
Copyright 2015, Your Company LLC.
实际发布时建议写清许可证条款,这里只保证构建能通过检查。
第七步:运行 debuild
用 -us -uc 两个参数跳过对源码包和二进制包的 GPG 签名,先专注把包构建出来:
debuild -us -uc
如果一切顺利,构建会在源码目录的上一级目录留下产物。但按上面的文件直接跑,大概率会失败,见下一节。
第八步:处理安装阶段的权限报错
第一次构建常见的报错是:
install: cannot create regular file `/usr/local/bin/helloworld': Permission denied
原因不难理解:debuild 在构建过程中执行了 make install,而我们的 Makefile 把安装路径硬编码成了 /usr/local/bin,构建环境没有写这个系统目录的权限。绝大多数真实软件都不会开箱即用地打包成功,原因往往就是这类路径问题。
解决办法是在 debian/rules 里覆盖默认的安装步骤,把文件装进包自己的暂存目录,而不是系统目录:
#!/usr/bin/make -f
%:
dh $@
override_dh_auto_install:
install -D -m 0755 helloworld $$(pwd)/debian/helloworld/usr/local/bin/helloworld
这里有几个细节:
- override_dh_auto_install 是 debhelper 约定的覆盖目标名,用来替换默认的自动安装行为。
- $$(pwd) 中的双美元符号是因为这段内容会先经过 make 处理,$$ 转义后才是 shell 看到的 $(pwd)。
- debian/helloworld/ 是二进制包的暂存目录,包名与 control 里的 Package 字段一致。装进这个目录的文件,最终会按相同路径出现在用户系统上。
- install -D 会自动创建目标路径中缺失的目录层级,省去手动 mkdir。
改完再次执行 debuild -us -uc,构建应当成功。
一个完整示例
把上面的步骤串起来,从空目录到拿到 .deb 的完整流程如下。假设工作目录为 ~/helloworld。
# 1. 准备源码
mkdir -p ~/helloworld && cd ~/helloworld
cat > main.c <<'EOF'
#include <stdio.h>
int main (int argc, char *argv[]) {
printf("Hi\n");
return 0;
}
EOF
cat > Makefile <<'EOF'
helloworld:
gcc main.c -o helloworld
install: helloworld
install -m 0755 helloworld /usr/local/bin
EOF
# 2. 本地验证
make
./helloworld
# 3. 建立打包目录与文件
mkdir debian
cat > debian/control <<'EOF'
Source: helloworld
Maintainer: Your Name <you@example.com>
Build-Depends: debhelper (>= 8.0.0)
Standards-Version: 3.9.3
Section: utils
Package: helloworld
Priority: extra
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: a simple helloworld package
Just prints "Hi", it's very useful.
EOF
# 4. 生成 changelog(编辑器中把 UNRELEASED 改为 unstable)
dch -i --create
# 5. 写 rules 并赋权
cat > debian/rules <<'EOF'
#!/usr/bin/make -f
%:
dh $@
override_dh_auto_install:
install -D -m 0755 helloworld $$(pwd)/debian/helloworld/usr/local/bin/helloworld
EOF
chmod +x debian/rules
# 6. 写 copyright
cat > debian/copyright <<'EOF'
Copyright 2015, Your Company LLC.
EOF
# 7. 构建
debuild -us -uc
构建成功后,回到源码目录的上一级查看产物:
cd ..
ls helloworld_*
应当看到以下几类文件:
| 文件 | 作用 |
|---|---|
| helloworld_0.0.1_amd64.build | 构建日志,排查失败原因时先看它 |
| helloworld_0.0.1_amd64.changes | 变更记录与各产物的校验和 |
| helloworld_0.0.1_amd64.deb | 二进制安装包,用户实际安装的就是它 |
| helloworld_0.0.1.dsc | 源码包描述文件 |
| helloworld_0.0.1.tar.gz | 原始源码目录的归档 |
文件名中的版本号来自 changelog,架构名来自构建机器的架构。想确认 .deb 内容是否正确,可以用 dpkg 查看包信息,或直接安装测试:
sudo dpkg -i helloworld_0.0.1_amd64.deb
helloworld
注意事项
- 共享文件夹不可用:debuild 构建过程依赖符号链接,在 VirtualBox 或 VMware 的共享目录中构建会失败,请换到本地磁盘目录。
- 制表符与空格:debian/rules 与 Makefile 一样,命令行缩进必须是硬制表符。用编辑器把 tab 自动转成空格是常见故障源。
- changelog 状态:dch 生成的条目默认是 UNRELEASED,构建前要改成 unstable,否则产物的发行版字段不正确。
- 安装路径不要硬编码到系统目录:源码 Makefile 里写死 /usr/local/bin 这类路径,在打包环境下必然触发权限错误。正确做法是通过 override_dh_auto_install 把文件装进 debian/包名/ 暂存目录。
- lintian 必须安装:debuild 会调用它做规范检查,缺了它构建流程走不完。copyright 文件缺失也是 lintian 会拦下的问题之一。
- 签名参数:-us -uc 只是跳过签名以便快速迭代。要发布给他人安装时,需要用 GPG 密钥对包签名,再配合 debsign 处理;签名相关的密钥配置请以你所用发行版和 GnuPG 的当前文档为准。
- 架构相关:control 中 Architecture 为 any 时,每个目标架构都要单独构建一次,产物文件名会带上架构后缀。
- 版本与字段取值:文中出现的 debhelper 版本下限、Standards-Version、Priority 等取值均为示例。不同发行版对字段的接受范围不同,实际打包前请核对当前政策与工具版本,以官网当前信息为准。