AB
AiBoss
チュートリアル

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 等取值均为示例。不同发行版对字段的接受范围不同,实际打包前请核对当前政策与工具版本,以官网当前信息为准。