Debian 的软件包以 .deb 作为后缀,它是一个Unixar的标准归档,将包文件信息以及包内容,经过gzip和tar打包而成。通常来说这个包里面含有 data.tar.*
实际的软件内容; control.tar.gz
记录了包名称,版本号,维护者,需要什么依赖,与什么冲突的信息; debian-binary
deb 格式版本号码.
对于我们平时在 Debian/Ubuntu 平台编译的软件来说,通常只会通过 make 之类的编译指令生成 deb 包中 data.tar.*
的内容,即使我们使用 checkinstall
之类的软件打包成 deb 包, 也不会自动填充 control.tar.gz
,这就会导致这个生成的 deb 包并不是适合分发的形式。这个包在使用例如 dpkg 包管理器的时候,既不会检查依赖,冲突,也不能正确显示软件包的具体信息 (例如 dpkg -l, 你会看到这种软件包上面写着 Package created with checkinstall 1.6.2
而不是相应的软件描述)。
因此这里就需要用到一个完整的 Debian 软件包打包流程。经过对前人留下的 Blog 文章以及官方文档的了解,我尝试了这个流程,并且整理成这几篇记录,内容仅仅是摸索过程中的体验和一些技巧,并不代表任何官方性的规则,具体文档请直接参考Debian 新维护者手册。
Contents
写在在打包之前:
在制作软件包之前,首先应该清楚打包是什么,需要哪些流程,需要什么工具:
:
如果要手工从源代码开始将这个软件安装到系统中,常见的步骤包括:
-
获取源代码
-
准备软件依赖以及工具链
-
从源码构建软件
-
测试
-
安装
打包者的工作:
相对于这些需要手动编译安装的软件,作为用户一般会更喜欢有一个一条指令就能完成的安装操作,并且会希望这些软件在安装或者清理时都尽可能的干净,不会造成冲突。那么打包者就需要完成上述除去安装以外的步骤。
-
获取源代码:打包者通常需要通过可靠的途径获取源代码,比如从官方 Git/SVN 库获取源码,或者官方的发布网页。(当开发者同时提供 Git 仓库和源代码包时,需要小心版本区别,例如 deluge 官网提供的 1.3.15 代码包和 Git 仓库里 Tag 为 1.3.15 的源码有细微差别)
-
处理源代码:打包者需要对获取到的源代码进行处理,具体包括检查源文件的版权信息、构建需求(有什么依赖,什么冲突) 。有必要的时候还需要制作补丁,与上游开发者沟通等。
-
尝试构建软件包:根据开发者文档对打过补丁的源码包进行构建,如果在此过程发现问题,则重复上一步直到问题解决。
-
检查软件包:即便软件包成功构建,打包者也需要对其进行检查,确保质量可靠、符合要求。有问题则需要返工重新调整。
-
发布软件包:打包者在确定软件包适合发布后,会通过某种途径将其向外界发布出去,供用户使用。比较常见的是在Debian.org 或者 ubuntu 的 launchpad 发布。也可以直接将打包好的 deb 直接通过 http 下载的方式提供给用户。
-
跟踪并修复软件缺陷:有些时候你会在你的发布页看到用户反馈的 issue,在确认到问题后,打包者应该尝试修复,或者与上游开发者沟通。
-
跟踪并发布新版本:软件的原作者可能会不时地发布这个软件的新版本。打包者需要跟踪开发流程,并在新版本发布时检查变化,为新版本再次打包并发布。
而对于用户而言,他将会得到一个可以安装直接使用的 deb 包,并且可以通过 deb 包里面的信息搞定所有剩余的依赖库。
:
首先安装称为 packaging-dev
的工具链,这里面包含了大部分打包需要使用的工具:
sudo apt-get install packaging-dev
pbuilder
sudo apt-get install pbuilder ubuntu-dev-tools debootstrap devscripts
pbuilder
使用 chroot
也就是所谓净室环境在不污染宿主系统的前提下,构建不同平台的包。
除去这些打包必备的工具以外,如果你还需要发布到 launchpad 之类的平台,那么还需要对你的安装包使用 gpg 签名,因此,需要安装 gpg:
sudo apt install gnupg
具体创建 gpg 密钥的方式可以参考这里。
与 Lanuchpad 相关的操作也将在后续文章里说明。
dh_make
的方法如下:首先设置两个 环境变量,$DEBEMAIL
和 $DEBFULLNAME
,这样大多数 Debian 维护工具就能够正确识别你用于维护软件包的姓名和电子邮件地址。然后修改并写入如下内容:
$ cat >>~/.bashrc <<EOF DEBEMAIL="[email protected]" DEBFULLNAME="Firstname Lastname" export DEBEMAIL DEBFULLNAME EOF $ . ~/.bashrc
编译的基础:
由于打包的前提条件是一个编译好的软件,所以不得不提一下编译。通常来说,简单的程序源码会带有 Makefile
,我们可以很容易的直接输入 make
来进行编译。略微复杂一点的现代化程序还可能会使用一个叫做 configure
的脚本来生成当前系统所需的 Makefile
。类似的还有 cmake
使用的 CMakeLists.txt
,cmake
会直接使用这个文本文件来生成相应的 Makefile
。另外还有比较常见的工具 Autotools
,它使用configure.ac
,Makefile.am
和 Makefile.in
等特征文件来识别使用 Autotools
作为编译系统的源代码。在使用 Autotools
生成 configure
后,同样运行configure
得到特定的 Makefile
。由此可见,在常见的编译系统中,一切的基石就是Makefile
。由于这篇文章的主题是打包而非编译,因此并不特别展开说明 Makefile
的写法。
软件包名称和版本:
Debian 的包管理基于软件包和版本号,因此在本地打包的时候仍需要特别注意。
我们直接参考 “Debian 新维护者手册” 中的描述进行总结:
例如 rtorrent-0.9.6.tar.gz
,rtorrent
就可以作为软件包名称, 0.9.6
就是上游版本号。它们会被debian/changelog
这个文件用到(后面内容将会详细说明)。更为详尽的说明可以参考 Debian 政策。我们可以参照中说明的内容调整命名满足规则。
在 软件包名 里只能含有 小写字母 (a-z), 数字 (0-9), 加号 (+) 和 减号 (-) , 以及 点号 (.)。 软件包名最短长度两个字符;它必须以字母开头;它不能与仓库软件包名发生冲突(对于需要上传到debian仓库或者launchpad而言)。
upstream version(上游版本号)0-9
)。
虽然在 Debian Policy 中提到可以使用基于日期的版本号,但是这并不是什么非常明智的方式。由于需要保证版本的唯一性,同时又满足平滑过渡的要求,以数字作为版本号会更加合适。此外还可以用 launchpad 手册中提到的 myapp_1.0-2~ppa1~ubuntu16.04.1
的形式,用波浪号 (~)将平台标识起来。
版本字符串可以用 dpkg 来进行比较:
$ dpkg --compare-versions ver1 op ver2
版本比较规则可总结为以下几点:
-
字符串要从头到尾进行比较。
-
字母比数字大。
-
数字作为整数进行比较。
-
字母按照 ASCII 编码顺序进行比较。
-
对于点号 (
.
),加号 (+
),以及波浪号 (~
) 则有对应的特殊规则,具体如下:0.0
<0.5
<0.10
<0.99
<1
<1.0~rc1
<1.0
<1.0+b1
<1.0+nmu1
<1.1
<2.0
源码包的工作目标:
在类似 launchpad 的软件源中除去 .deb
二进制包以外还存在以下文件:.orig.tar.xz
, .debian.tar.xz
和 .dsc
。这些文件是在构建软件包以后最终得到的。
.orig.tar.xz
存储的内容是软件包的源码,这一步可以通过初始化外来软件包来得到。例如:
$ cd ~/gentoo $ wget http://example.org/gentoo-0.9.12.tar.gz $ tar -xvzf gentoo-0.9.12.tar.gz $ cd gentoo-0.9.12 $ dh_make -f ../gentoo-0.9.12.tar.gz
dh_make
按照默认模板生成默认的文件,并且将已有的 tar 包拷贝并且重命名。$ cd ~/gentoo ; ls -F gentoo-0.9.12/ gentoo-0.9.12.tar.gz gentoo_0.9.12.orig.tar.gz
- 存储的是软件包的信息以及构建规则。在上一步的操作中你可以看到你的源码目录中还成成了一个
debian
文件夹,在这个目录下已经有了很多模板(后文还将详细说明这些文件的用途)。此压缩包就是归档该目录中的文件,在使用dpkg-buildpackage
或者debuild
等命令生成源码包的时候就会得到该归档文件。 - 存储的是从
control
文件生成的源代码概要,是构成软件打包的重要文件。
值得注意的是,这些操作生成的文件都是以 ${softwarename}_${version}
的形式出现,也就是说文件夹中用于分割软件名称的 -
被替换成了 _
。
debian 目录中的必须内容:
上面的章节已经又介绍过,在初始化一个外来的软件包以后,在源码目录下会生成一个带有默认模板文件的 debian
目录,这个目录中将会存储有软件包的信息(包括维护者,来源,内容等),同时有着构建规则(依赖,编译指令,安装指令,以及打包规则)。下面是自动生成的文件的例子:
./ ├── changelog ├── compat ├── control ├── copyright ├── manpage.1.ex ├── manpage.sgml.ex ├── manpage.xml.ex ├── menu.ex ├── postinst.ex ├── postrm.ex ├── preinst.ex ├── prerm.ex ├── README.Debian ├── README.source ├── rtorrent.cron.d.ex ├── rtorrent.doc-base.EX ├── rtorrent-docs.docs ├── rules ├── source │ └── format └── watch.ex
在这些文件中有如下必备的文件:control
,copyright
,changelog
,rules
。下面依次说明这几个文件的作用:
control
这个文件包含了很多供 dpkg、dselect、apt-get、apt-cache、aptitude 等包管理工具进行管理时所使用的许多变量。可以查看 Debian Policy Manual, 5 “Control files and their fields” 的定义。
继续以一个文件为例:
Source: rtorrent Section: unknown Priority: optional Maintainer: Amefs <[email protected]> Build-Depends: debhelper (>= 10), autotools-dev Standards-Version: 4.1.2 Homepage: <insert the upstream URL, if relevant> #Vcs-Git: https://anonscm.debian.org/git/collab-maint/rtorrent.git #Vcs-Browser: https://anonscm.debian.org/cgit/collab-maint/rtorrent.git Package: rtorrent Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Description: <insert up to 60 chars description> <insert long description, indented with spaces>
可以看到这里的信息被分成两个部分:一个是构建二进制包相关 ,另一个是安装二进制包相关。
简单解读一下这里面的内容:
-
Source: 软件名称。
-
Section: Debian 仓库被分为几个类别:
main
(自由软件)、non-free
(非自由软件)以及contrib
(依赖于非自由软件的自由软件)。也就是平时在 sourcelist 中看到的那些不同的源。在这些大分类下还有根据软件包用途区分的子分类:admin
为供系统管理员使用的程序,devel
为开发工具,doc
为文档,libs
为库,mail
为电子邮件阅读器或邮件系统守护程序,net
为网络应用程序或网络服务守护进程,x11
为不属于其他分类的为 X11 程序,等等。以这里的 rtorrent 软件为例,它是一个 p2p 的客户端因此分类就是net
。 -
Priority: 这里虽然还有
required
、important
或standard
等优先级,但是一般这类常规的软件保持optional
即可。 -
Maintainer: 软件包的维护者,考虑到可能会收到一些 issue 反馈,因此应该正确填写此信息以获取反馈。
-
Build-Depends: 这里需要添加编译必须的依赖包,有些被
build-essential
依赖的软件包,如gcc
和make
等,已经会被默认安装而不需再写到此处。但是类似bc
,dh-autoreconf
,libcurl4-openssl-dev
之类的编译工具链以及一些必须的库就应该被添加到此处。多个软件包可以通过半角逗号隔开。注:在这里我们是可以指定软件依赖的版本,通过=
,>=
之类的符号来指定特定版本或者最低版本。 -
Standards-Version: 这个是
control
文件的标准版本,一般来说无需修改。 -
Homepage: 上游代码的来源
-
Package: 二进制软件包名称,这个通常来说与源码包一致,但是并非必需,如果你想要分割多个软件包也可以在这里以多个不同名称的 Package 代码块来实现。实际举例:xmlrpc-c 的打包被分为很多个小的软件包,并通过添加
${Packagename.install}
实现向这些软件包安装不同的文件。 -
Architecture: 描述了可以编译本二进制包的体系结构。一般来说这个值是
any
或者all
。any
一般是编译型语言编写的程序生成的二进制,而all
则是脚本型或者文本,图片等二进制包。 -
Depends: 这个是软件包本身的依赖,一般来说是一些运行库或者依赖的其他软件。当然与 Depends 同级别的还有
Recommends
、Suggests
、Pre-Depends
、Breaks
、Conflicts
、Provides
和Replaces
等关系。具体可以参考以下说明。 -
Description: 软件用途的描述。每行的第一个格应当留空。描述中不应存在空行,如果必须使用空行,则在行中仅放置一个
.
(半角句点)来近似。同时,长描述后也不应有超过一行的空白。
到此我们就完成了 control 文件的修改。
Source: rtorrent Section: net Priority: optional Maintainer: Amefs <[email protected]> Build-Depends: bc, debhelper (>= 9), dh-autoreconf, libcppunit-dev, libcurl4-openssl-dev, libncurses5-dev, libncursesw5-dev, libtorrent-dev (>= 0.13.4), libxmlrpc-core-c3-dev, pkg-config Standards-Version: 3.9.6 Vcs-Git: git://git.debian.org/git/collab-maint/rtorrent.git Vcs-Browser: http://git.debian.org/?p=collab-maint/rtorrent.git Homepage: https://rakshasa.github.io/rtorrent/ Package: rtorrent Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends} Suggests: screen | dtach Description: ncurses BitTorrent client based on LibTorrent from rakshasa rtorrent is a BitTorrent client based on LibTorrent. It uses ncurses and aims to be a lean, yet powerful BitTorrent client, with features similar to the most complex graphical clients. . Since it is a terminal application, it can be used with the "screen"/"dtach" utility so that the user can conveniently logout from the system while keeping the file transfers active. . Some of the features of rtorrent include: * Use an URL or file path to add torrents at runtime * Stop/delete/resume torrents * Optionally loads/saves/deletes torrents automatically in a session directory * Safe fast resume support * Detailed information about peers and the torrent * Support for distributed hash tables (DHT) * Support for peer-exchange (PEX) * Support for initial seeding (Superseeding)
copyright
这个文件包含了上游软件的版权以及许可证信息。Debian Policy Manual, 12.5 “Copyright information” 掌控着它的内容,另外 DEP-5: Machine-parseable debian/copyright
提供了关于其格式的方针。这个虽然是必须的文件,但是在这里就不详细展开了。
changelog
这是一个必须的文件,它的特殊格式在 Debian Policy Manual, 4.4 “debian/changelog” 中有详细的描述。这种格式被 dpkg 和其他程序用以解析版本号信息、适用的发行版和紧急程度。这个文件可以用来记录你对软件包进行的哪些修改,不同的版本之间有什么区别,建议尽可能正确,详细的填写其中的内容。
还是以一个实际文件为例:
libtorrent (0.13.4-1ppa1~18.04) bionic; urgency=medium * Initial release -- Amefs <[email protected]> Fri, 14 Jun 2019 08:09:31 +0000
第一行内容是软件名称,版本号,发行版,紧急程度(正常情况下为 medium)
后面几行一般是描述这次更新的具体内容,可多可少。
最后一行则是维护者信息,以及发布这条 changelog 的时间戳。
除了直接编辑这个文件以外,还可以使用 dch -i
rules
这是指导 dpkg-buildpackage
构建软件包的 rules 文件,这个文件的本质是一个 Makefile。这与源码内的 Makefile 并不相同,它专门负责指导 dpkg-buildpackage
使用源码包中的各种信息构建二进制文件。
rules
文件, 就像其他的 Makefile
一样,包含着若干 rules,其中每一个都定义了一个 target 以及其具体 操作。 一个新的 rule 以自己的 target 声明(置于第一列)来起头。 后续的行都以 TAB 字符 (ASCII 9) 来开头,以指示 target 的具体行为。 空行和以井号 #
开头的行会被当作注释而被忽略。 在过去,这些 target 是需要手动编写的,但是现在这些任务一般会交给 debhelper 来完成。
dh_make
给出的默认 rules 文件就是以 debhelper 支持的命令生成的最简规则:
#!/usr/bin/make -f # See debhelper(7) (uncomment to enable) # output every command that modifies files on the build system. #DH_VERBOSE = 1 # see FEATURE AREAS in dpkg-buildflags(1) #export DEB_BUILD_MAINT_OPTIONS = hardening=+all # see ENVIRONMENT in dpkg-buildflags(1) # package maintainers to append CFLAGS #export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic # package maintainers to append LDFLAGS #export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed %: dh $@
第 16 和 17 行使用了 pattern rule,以此隐式地完成所有工作。 其中的百分号意味着“任何 targets”, 它会以 target 名称作参数调用单个程序 dh。 dh 命令是一个包装脚本,它会根据参数执行妥当的 dh_* 程序序列。而我们使用的 dpkg-buildpackage 实际会通过执行 debian/rules build-target 以及 fakeroot debian/rules binary-target 来生成二进制并安装打包到一个 deb 文件中。
-
debian/rules clean
运行了dh clean
,实际执行的命令为:dh_testdir dh_auto_clean dh_clean
-
debian/rules build
执行了dh build
,实际执行的命令为:dh_testdir dh_auto_configure dh_auto_build dh_auto_test
-
fakeroot debian/rules binary
执行了fakeroot dh binary
,其实际执行的命令为:dh_testroot dh_prep dh_installdirs dh_auto_install dh_install dh_installdocs dh_installchangelogs dh_installexamples dh_installman dh_installcatalogs dh_installcron dh_installdebconf dh_installemacsen dh_installifupdown dh_installinfo dh_installinit dh_installmenu dh_installmime dh_installmodules dh_installlogcheck dh_installlogrotate dh_installpam dh_installppp dh_installudev dh_installwm dh_installxfonts dh_bugfiles dh_lintian dh_gconf dh_icons dh_perl dh_usrlocal dh_link dh_compress dh_fixperms dh_strip dh_makeshlibs dh_shlibdeps dh_installdeb dh_gencontrol dh_md5sums dh_builddeb
可以看到这里有非常多 dh_* 命令,可以参考 debhelper 的说明。
我们可以通过为 dh $@
命令添加各种参数来添加 dh_python2 、dh_quilt_patch、dh_dkms 等命令支持。并且我们可以通过类似如下的参数来覆盖默认的配置。例如:
#!/usr/bin/make -f %: dh $@ --with autoreconf --parallel override_dh_auto_configure: dh_auto_configure -- --with-xmlrpc-c --enable-ipv6
这是 rtorrent 的 rules。我们简单解读一下,首先在 dh
命令初始化的是时候,加入 autoreconf 支持,这就会在编译的时候执行 autoreconf
,这样就会生成一个适用于这个平台的 configure
文件。parallel
命令将会打开并行编译支持。最后通过 override_dh_auto_configure
为 ./configure
命令添加 --with-xmlrpc-c --enable-ipv6
参数。这个过程就与我们在编译安装的时候执行 autogen.sh
、./configure --with-xmlrpc-c --enable-ipv6
的效果相同。根据这个 rules 就可以完成 Makefile 的生成,并利用生成的文件构建二进制,最后打包。
对于很多软件包来说,我们并不需要这样一步一步手动生成这些文件。常见的软件包往往已经实现了 debian 化,也就是说在一些软件源中可以找到这些东西,我们只需要下载它们的源码,提取出 .debian.tar.xz
中的这些必要文件,就可以按照我们的需求进行定制。
参考文档:
https://www.debian.org/doc/manuals/maint-guide/
https://hosiet.me/blog/2016/09/15/make-debian-package-with-git-the-canonical-way/