配合Git为Debian系发行版打包的正确方式

配合Git为Debian系发行版打包的正确方式教你使用正确的姿势又好又快地得到一个deb安装包

Debian这个发行版历史悠久,其软件包管理工具apt和dpkg更是作为其特色沿用至今,在保留严谨传统的同时得到了长足的发展和演进。然而正是由于其不短的历史(虽然还没到三十年,但是这样的时间跨度对于IT行业的产物已经算惊人的长了),Debian的包管理工具不可避免地存在着稍显沉重的历史包袱。一些过去的真理和守则反而成为了现在的累赘,一代又一代地误导着后来者。为了让后来者少走弯路,我在这里记录一些摸索过程中的体会和技巧,希望能够让更多人体会到Debian的魅力所在。
如果你只想了解最佳的工作流的话,请直接翻到最后面查看。但是具体工具的使用方法需要自行查找资料,或者回到前文寻找相应的内容。


打包基础知识

要打包,首先应该了解其基本原理,这是在不同发行版甚至不同操作系统中都适用的知识。
说起打包,不可避免地会让人想到编译、链接、安装、复制文件等等关键词。毕竟说到底打包的目的是为了让用户更加方便可靠地获得一个软件,其目的为安装软件,那么其手段也和手工从源代码开始安装软件的做法大同小异。

手工安装软件包的流程

如果要手工从源代码开始将这个软件安装到系统中,常见的步骤包括:

  1. 获取源代码:一般是从互联网上下载一个压缩包,或者从其它途径(如光盘等)得到。
  2. 构建之前的准备:要从源代码得到二进制的可执行程序,一般需要一些工具进行辅助,典型的工具例如编译器、连接器等等。这些工具需要事先准备好,安装在系统中。
  3. 进行构建:使用软件原作者提供的构建系统进行构建,编译、链接等环节都在这一步完成。更宽泛地说,这一步从原始的源代码得到我们需要的格式的文件(例如二进制可执行程序)。
  4. 测试:如果软件原作者提供了某些测试工具,此时可以使用测试工具对刚刚构建完成的成果进行检验。
  5. 安装:这一步通常比较简单,简单到只是复制粘贴而已。我们把构建出来的成果复制放到系统合适的位置,那么安装就完成了。

打包者参与时安装软件包的流程

说完了手工安装,我们再考虑打包者存在的场景。此时,我们希望所有的脏活累活都是由不知道在世界上哪个地方的打包者干完,作为用户,只需要一条命令(或者点一下鼠标)就能好好地安装一个软件包。一键安装,就是这么舒心。我们将原有手工安装的步骤区分成打包者的工作和用户的工作两部分:

打包者的工作

  1. 获取源代码:打包者通常需要通过可靠的途径获取源代码,避免源代码的错误与不可靠。理想的情况是软件原作者提供源代码包的散列值进行验证,或者对源代码包进行数字签名。另外,直接将源代码仓库复制一份(例如用Git管理的仓库)也是很理想的方式。
  2. 处理源代码:打包者需要对获取到的源代码进行处理,具体包括检查源文件的版权信息、构建系统的情况,并根据具体情景编写打包用的指令和信息。如果发现了源代码中的问题,打包者还需要向上游进行反馈,并考虑在构建时添加这个软件包特有的补丁。这通常是打包者耗费心血最多的地方。
  3. 尝试构建软件包:打包者使用刚才编写的指令进行构建并打包。构建已经在上面说明,而打包则是将构建的成果按照一定的关系进行组织、压缩并放入一个特定格式的文件中的过程。如果出错,还需要返回上一步重新调整。
  4. 检查软件包:即便软件包成功构建,打包者也需要对其进行检查,确保质量可靠、符合要求。有问题则需要返工重新调整。
  5. 发布软件包:打包者在确定软件包适合发布后,会通过某种途径将其向外界发布出去,供用户使用。这种发布途径可能有很多,但常见的是所谓“发行版”提供的集中式发布渠道。
  6. 跟踪并修复软件缺陷:即便完成了打包,打包者的工作也没有完成。如果接到了用户的反馈认为这个软件包有问题,打包者还需要进行跟踪调查,检查问题的发生原因,并根据实际情况进行处理,或是向原作者反馈,或是调整自己的打包方式和指令。
  7. 跟踪并发布新版本:软件的原作者可能会不时地发布这个软件的新版本。打包者需要跟踪开发流程,并在新版本发布时检查变化,为新版本再次打包并发布。

可以看出,打包者的负担大大加重了。因为打包者是用户和软件开发者之间的桥梁,需要担负沟通、检查、质检等多种任务。

用户的工作

  1. 获取软件包:用户使用某些特定的途径获取到打包者的软件包。通常这种途径由特定的“发行版”集中提供。
  2. 安装软件包:用户使用安装工具把软件包安装到系统中。

用户的负担大大减轻了,一切都是现成的,只需要“拿来”“用”就行。

Debian采用的打包基础:Makefile

正式开始介绍打包之前,我们必须提及构建系统的基石:Makefile。我们来看看维基百科对Makefile是如何介绍的:

A makefile is a file containing a set of directives used with the make build automation tool.

翻译过来,就是说这个文件包含了一系列指导make这个构建自动化工具完成其工作的命令。它使用目标(target)组织起工作流程,即为了达成某个目标需要哪些源文件、需要哪些事先完成的目标、完成这个目标需要执行哪些指令,等等。Debian 利用了Makefile作为构建的中心,整个构建打包流程就是在执行Makefile的过程中完成的。这是一个特殊的文件,其名称为debian/rules
有一个需要注意的地方:一切Makefile中的指令都以一个制表符起始。千万不要用空格替换,会出错的。
关于Makefile的普遍编写方式,请参阅其它资料,例如陈皓的经典教程《跟我一起写Makefile》(原版PDF副本重制版)。


为Debian打包的基础知识

我们已经了解到,Debian 决定采用一个Makefile文件作为构建的核心,并围绕着这个文件开展工作。下面,我们转向更实际的例子,阐述究竟应该如何完成整个构建工作。

源码包的工作目标:.orig.tar.xz, .debian.tar.xz, .dsc

在深入具体过程之前,我们不妨先了解一下打包工作的成果应该是什么。正如标题所述,Debian 开发者辛辛苦苦工作的目标是得到这三个文件。广义上讲,它们构成了 Debian 的源码包。即,获取了这三个文件,配合工具链,任何一个人都可以从源代码按照标准化的流程进行构建并得到二进制软件包(即.deb包)。要深入了解这三个文件的内容,您可以自行查看,或者在下文中所述的 Debian 新维护人员手册中也有对应的章节。
我们知道了工作目标的外在形式,当然我们也能够认识到工作目标的内在含义。无外乎下面几个:

  • 软件开发者(上游)提供的软件源代码。
  • 目标软件包的信息。信息包括维护者、依赖关系、版本号、修订历史、等等。
  • 构建软件包的指令。大部分在debian/rules文件中给出。

在此之后我们仍然需要了解的是,为了得到目标,我们手头有哪些东西可以使用:

  • 软件开发者(上游)提供的软件源代码。这毫无疑问。
  • 软件开发者提供的构建说明。如果有的话,这将成为你打包工作中的一个重要参考。
  • 整个 Debian 为你提供的开发工具。这是打包的重要武器。
  • 互联网上的资料,尤其是 Debian 的文档。
  • 你脑海中的知识与创造力。

从后者得到前者,便是打包者的工作。

打包指令的存储位置:debian目录

如上所述,我们需要给出将软件包的各种信息和构建指令。显然,我们需要按照一定的格式给出这些信息和指令。并且将它们和源代码放在一起。简而言之,Debian 决定将所有打包相关的指令和信息分别写入不同的文件中,但这些文件都在一个统一的目录下:debian/目录。这个debian目录应当在获取到上游提供的源代码后被放置在源代码顶级目录下面。这样的目录关系在构建时不会改变。如果上游已经提供了一个叫做debian的目录,那么在打包时会强行删去原目录并将打包使用的目录覆盖,所以不用过于担心。

不使用Git的经典教程:Debian 新维护人员手册

无论是想要从零开始学习打包,还是想在 Debian 里维护一个软件包,或者是想成为 Debian 开发者的一员,总有一个必读的文章:Debian 新维护人员手册。这个手册阐述了基本的打包流程以及经典的构建方式。我们在这里说“经典”,是因为它使用了最基础的工具链进行操作,没有与Git这种代码版本管理工具做整合。即便您打算使用高级的工具来减轻负担(如,使用git-buildpackage工具),通读一遍手册全文仍然十分重要。在之后的文字中,我会假定您至少通读了这个手册的全文。

一切的基石:debian/rules文件

现在您已经知晓,debian/rules文件就是我们前文中所说的那个Makefile文件。毫无疑问,这个文件是构建软件包的基石,也是黑科技最容易出现的地方。

需要牢记的是,Debian 打包(构建)的不同环节将会调用这个Makefile的不同目标(target)。上文提到的新维护人员手册中的4.4.1节给出了这个Makefile必须实现的目标。上古时期(debhelper 不存在的时候)这些目标是需要打包者逐个编写的。现在有了debhelper,一切都使用黑科技手段大大简化。请见下一节。

历史的印记:debhelper

传统上基于Makefile目标的打包实在是太累人了。如果不使用任何帮助工具,那么自己需要实现上一节提到的所有目标(target),这便不可避免地要求打包者熟悉上游软件原作者使用的各类构建工具(Autotools/Autoconf/AutomakeCMakewafninja、纯手写的MakefilePythonsetuptools,还有茫茫多的不同语言的特定构建工具……),而且要求打包者自己将构建好的文件复制组织成软件包的结构,这不可避免地要求打包者写无数行的install ...命令(进行文件复制),完全是体力劳动、重复劳动。这样不好。

我们希望能够简化打包流程,让一套帮助工具辅助进行打包。这样的工具可以具有以下特点:

  1. 自动处理各类构建工具:automake/cmake/makefile,自动识别,不需要打包者重复劳动。
  2. 命令式的一条条指令向声明式的说明性文字演变。可以的话,只告诉打包工具需要做什么,具体怎么做让打包工具自己去操心。没错我就是在说你PKGBUILD
  3. 提供统一接口管理buildflag(构建标志)。例如,统一对所有文件启用安全增强的-fPIC-fPIE标志。

debhelper是 Debian 钦点最常使用的打包帮助工具。实际上,它已经变成了打包中不可或缺的一部分。如果有空闲,我建议阅读一遍它的手册页:debhelper(7)。另外一个和它分庭抗礼的帮助工具叫做cdbs(The Common Debian Build System),然而它随着debhelper新版的开发,包括v7和v9的大变化、尤其是dh命令(大杀器)的引入而逐渐显得有些落伍。对于新的软件包,我只推荐用debhelper。要用9或者以后的版本,可能的话推荐使用v10的新版本新功能。

未完待续……

Git的引入、改进和新的工具链

动机:上游和工作流的变化

没有无缘无故的爱,没有无缘无故的恨。打包流程的改变,必然有其深刻原因。我个人认为,这样的改变主要体现在以下两个方面:

  1. 上游分发源代码的方式发生了变化。遥想上世纪七八十年代,互联网远远没有今日的成熟,源代码的分发也面临着困难。那时成熟的方案是,把自己的源代码打包(也有可能顺便压缩一下)、放在个人或者公共的 FTP 站点上,供其他人下载使用。如果出现了小问题,则使用diff/patch这样的传统工具给出补丁,以电子邮件和纯文本的形式在互联网上传来传去。这也许是 Debian 传统上重视 tarball、重视版本号、使用独立补丁文件的一个原因。时至今日,代码版本管理工具,尤其是Git这样的分布式代码版本管理工具的崛起,以及公共/免费源代码托管站点的出现,为打包流程引入了新的可能性。只要把开发历史全部获取到,发布版本给出的代码压缩包什么的,就已经不是那么重要了。
  2. 打包者工作流的变化。我们已经知道打包者需要耗费心血编写debian目录下的文件,给出打包指令,还要随着上游的开发时不时地修改指令和补丁。诶你看看你看看,这样的工作,其实也是需要进行代码版本管理的嘛!上游都使用 Git 管理代码了,为啥打包者就不能用呢?如果能够把打包者的开发和上游软件的开发历史放进同一个代码仓库中,这样的结合多美妙,岂不美哉!

前人看到了引入新工具的美好前景,于是产生了后文提到的一系列工具,让打包从体力劳动变成了真正优雅的工作。

官方的接纳:从alioth到anonscm

未完待续。作者自己对这一部分也暂时不清楚。


git-buildpackage的使用

终于,我们谈到了正题:使用git-buildpackage进行打包。为了简便,后文称其为gbp

基于分支和标签的工作流程

正如标题所述,我们此时在打包时将大量应用 Git 的分支(branch)和标签(tag)。具体来说,我个人推荐的应用方式如下:

  1. 代码仓库的基础仍然是上游仓库。例如,应该以git clone得到的代码仓库开始工作。
  2. 上游主干版本可以由一个单独的分支管理,名称可以是upstream
  3. 上游发布的每一个版本都应当对应一个标签。标签可能是上游事先给出过的,也可能是打包者针对特定的一个提交(commit)进行的标记。在后者的情况下,这个标签(按 DEP-14 的推荐)应当取“upstream/x.y.z”的名称。“x.y.z”即为上游版本号。
  4. 打包者针对某个特定的上游版本开始打包工作,工作分支名称常见为master,但是按照DEP-14的推荐应当取名为<vendor>/master,具体名称看个人喜好。(其中的“vendor”一项指发行版名称,在 Debian 里应该替换为debian。)本节为简便起见,按照master的名称进行说明,请读者自行替换。请注意,所有打包相关的提交不得修改除debian/目录以外的任何文件和文件夹。如果有必要进行修改,请将其作为补丁存放在debian/patches目录下面,具体格式请参阅前文提及的新维护人员手册中有关quilt的内容、DEP-3以及下文提到的gbp pq的使用。
  5. 当打包者认为打包开发工作告一段落后,打包者应当在 master 分支的当前提交处打上标签。标签名称(按照 DEP-14 的推荐)应当取为“<vendor>/x.y.z-w”的形式。
  6. 软件开发者发布新版本x.y.zz后,打包者只需要:
    • 使用git fetch获取新的提交和标签;
    • upstream分支快进到最新的提交;
    • 再次为新版本打标签;
    • 切换到打包工作分支(master)上并使用git merge upstream/x.y.zz合并上游的更改
    • 再次更新打包指令直至满意;
    • 打上 vendor 的标签;
    • 构建。

    每次新版本发布,重复以上步骤。

至此,基本的开发便完成了。有了这样特别组织过的 Git 软件仓库,一些工具,例如git-buildpackage,便可以根据已有信息自动生成源码包,并完成打包的后续构建步骤。使用 Git 的开发工作流程便是如此清晰。

流程布局的正规化:DEP-14

介绍DEP-14之前,我们先简要介绍一下什么是DEP。这是一个缩写,全名是 Debian Enhancement Proposals,即 Debian 增强提议。这里存放了一些被前人提出的改进 Debian 的提议(通常比较重大,例如对 Debian Policy 的修订)。按照其生命周期,这些提议可能被补充完善、讨论、修改、接纳、拒绝或是废弃。
下面我们可以正式介绍DEP-14了。不要犹豫,点进去看一下。这是一个仍然在草案状态的提议,但是已经有 Debian 的打包团队声明将按照其中的工作流程进行工作,所以它具有参考价值。对于一些不复杂的项目,请不要犹豫,照着提案内容来是一个可行的方案。
精简一下,提案中和使用 Git 打包的相关内容如下所述:
未完待续。在作者完成这一部分之前,请自行去啃一下英文原文。其实不读也无所谓

pristine-tar是什么?为什么要用它?

按道理来说,上面的步骤已经可以提供打包所需要的全部信息了。我们有原始源代码,有打包信息,它们都存放在 Git 软件仓库的不同标签和分支指向的历史中。如果要求不高,到这一步故事就差不多结束了,下面是从源码包得到二进制包的内容了。

然而我们总有更高的要求。看到了现在,你已经知道整个利用 Git 打包的过程中我们并没有真正得到软件的源码压缩包。如果非要拿出一个的话,这个压缩包是从 Git 的某一个提交中检出,现场压缩得到的。这不一定是好事。我们可能有更高的需求:某些上游软件开发者可能同时提供 Git 仓库的访问和发布版本的 tarball(源码压缩包),并且提供源码包的散列值,甚至对散列值进行数字签名。我们希望在重新构建时,使用的源码包和上游经过验证、尤其是经过数字签名的压缩包完全相同。这样可以确保源码包的可靠性。

如何解决这个问题呢?pristine-tar这个小工具提供了解决方案。

pristine-tar使用三类信息进行工作:

  1. 从上游软件开发者处直接下载得到的源码压缩包;
  2. Git 历史中某一个指定的提交;
  3. Git 历史中的特殊分支,pristine-tar分支中存储的增量(delta)文件。

你可能已经猜到了,pristine-tar工具的职责是使用其中两类信息重建出缺失的那一类信息。当然,Git 提交涉及的东西太多,只作为信息来源,不作为重建目标。

有了pristine-tar,我们就可以解决上面提出的那个问题,下面给出三种典型应用场景。详细信息,请查阅手册页:pristine-tar(1)

场景一:使用上游源码包,得到增量信息

假设下载得到的源码包存放在工作目录的上一级目录中,名字为foobar_1.0.orig.tar.gz。对应1.0版本的 Git 提交已经被打上了标签,标签名字是upstream/1.0。这时候,我们只需要:

pristine-tar commit ../foobar_1.0.orig.tar.gz upstream/1.0

即可自动在pristine-tar分支中存储增量文件。

场景二:使用增量信息,重新得到源码包

pristine-tar checkout foobar_1.0.orig.tar.gz

相当于检出这个源码包。

应用在git-buildpackage

pristine-tar分支可以直接应用在gbp buidpackage的构建过程中(见下文)。只需要在gbp buildpackage命令后面加上一个参数:--git-pristine-tar即可直接使用增量文件重构源码压缩包。


使用gbp构建的流程简介

我们都讲到构建了,但是git-buildpackage这个工具貌似一次都没有提到!前面所有内容都是在折腾 Git,折腾来折腾去不是加一个分支就是加一个标签,顶多是用pristine-tar工具在特殊的分支上做几个特殊的提交,仅此而已。

那些都是准备工作。下面,我们将见识到在git-buildpackage的顶层封装之下,从 Git 仓库到二进制deb包,一气呵成飞流直下三千尺的构建过程,同时体会到构建的层层封装和复杂性。

最后一步准备:从 Git 历史获取源码包

简而言之:你如果用gbp,这不是你应该关心的问题,工具帮你做好这一步,并自动在 Git 仓库对应的文件夹的上一层目录下放置对应文件。请一定确定当前用户有写入权限,因为这一步gbp还不会请求提权。

构建工具链一览

让我们梳理了一下你可能碰见的工具及其形成的工具链。一位Debian开发者绘制了一张图,如下所示:
Debian Build Tools with coding languages

构建过程

源码包到deb包的过程被称作“构建”(build)。主要的过程如下简述:

  1. 分析debian目录下的信息,生成.dsc文件;
  2. 按顺序调用debian/rules文件的各个目标,完成配置(configure)、编译构建(传统上的make步骤)、安装(不会直接安装到系统中,因为Debian提供了debian/tmp目录作为DESTDIR,专门为下游的安装提供一个目标目录,相当于安装到一个chroot目录中)和安装后处理。
  3. 根据指令将安装在debian/tmp.下面的文件再次安装到一个chroot目录中,这个目录是debian目录的子目录,目录名称是需要打包的软件包名称。
  4. 在上一步提到的以软件包名称为目录名的目录下面新建一个DEBIAN目录(注意,是全大写!),在里面放置一些二进制包的控制文件。主要还是copyright和control文件的经过调整后(例如,自动依赖分析后)的副本,以及一些维护脚本。
  5. 调用dpkg -b纯粹进行打包(压缩)生成deb包。
  6. 调用dpkg-genchanges生成.changes文件,为后续上传到官方源提供信息。

看起来眼花缭乱,实际上已经被层层封装自动化了。打包者可以不了解其中细节,但是有所了解可以帮助理解,尤其是碰到特殊情况时可以使用工具链中的某些单独的程序完成特别的工作。

中心问题:如何可靠地从源码包得到deb包?

我们这里说的“可靠”,指的是可重复性,即次次成功(或者次次失败,但这不是我们需要的)且生成的软件包工作效果相同,这是最低目标。我们还有一个终极目标,那就是在任何机器上安装辅助工具,提供相同的源码包,经过一系列工作,应该可以在任何时间、任何机器上得到完全一样,每个字节都相同的二进制包(deb包)。在最低目标和终极目标之间,我们有宽广的选择空间。最低目标显然必须达到,而最高目标在 Debian 官方2016年秋的工具链中已经接近实现(只剩四个 dpkg 相关的补丁)。

直接开工:dpkg-buildpackagedebuild

有的人会说:不就是编译么,就算不是 Debian,随便给我一台 Linux 机器我都能手动编译安装!著名的编译安装三部曲,稍微玩过 Linux 的人都知道:

./configure
make
[sudo] make install

说得不错。上面的三条命令是使用了autoconf/automake的软件最常用的编译安装三部曲。第一步搜集信息,生成合适的配置;第二步根据搜集到的信息进行编译;第三步纯粹是文件的复制粘贴。打包其实差不多,只不过环境可控,不会让这些过程影响到系统罢了。

dpkg-buildpackage便是最接近原始编译安装的打包工具,虽然它的封装层次已经比较高了。打包构建时,他会直接在当前系统中寻找构建需要的依赖(Build-Depends),使用fakeroot工具模拟root用户,确保一定的安全性,同时使得打包流程不需要超级用户权限;最终生成可上传至官方的各类文件和源码压缩包、二进制包。除此之外,它在配置、编译步骤中和那些原始的编译安装方式没有太大的区别。这个工具在需要打deb包的非 Debian 开发者中有一定的知名度,因为它可以比较方便地打deb包,又不需要额外的工具和配置。

多说无益,我们现在就上手试试。

首先我们要拿到一份源代码,且这份源代码的顶层目录下有一个子目录叫做debian,里面装了写好的打包指令,这就够了。你可以随意去网上下载一份源码,或者用git这样的工具克隆一份代码仓库。我们拿shadowsocks-libev的源代码举个例子,因为它的README.md文件已经完全说明了使用dpkg-buildpackage打包的方式,请先进行阅读。在确保当前工作目录在源代码顶级目录下时,打包指令摘抄如下:

cd shadowsocks-libev # can be omitted if already finished
sudo apt-get install --no-install-recommends build-essential autoconf libtool libssl-dev \
    gawk debhelper dh-systemd init-system-helpers pkg-config asciidoc xmlto apg libpcre3-dev
dpkg-buildpackage -b -us -uc -i
cd ..
sudo dpkg -i shadowsocks-libev*.deb

上面第一行是切换当前工作目录,如果你已经切换完成的话请直接跳过;第二行是使用apt-get当前系统里安装编译依赖。具体安装什么,要看debian/control文件里对于Build-Depends一栏具体填写的内容,当然不同的软件包不一样。第三行是重点,我们在下面解释;第四、第五行只不过是将上一级目录中打包好的.deb文件进行安装而已。

dpkg-buildpackage工具的命令行选项很重要,我建议你读一遍手册页:dpkg-buildpackage(1),虽然内容写得很凌乱。我们用当前具体的例子解释一下出现的参数:

-b
不构建源码包,不使用上游tarball,只使用当前目录下已解压缩的源代码构建二进制包。
-i
这个参数实际上会原封不动地传给调用的dpkg-source工具,其作用是启用内置的一套正则表达式匹配,在处理软件包时忽略特定的内容。例如,SVN 目录、.git目录等与代码版本管理工具有关的文件。对于使用代码版本管理的源代码,这个参数是非常有用的,基本每次都应该加上。
-us
不要对源码包数字签名。不过我们不构建源码包的话,这个参数没什么作用。
-uc
不要对自动生成的.changes文件数字签名。

就是这样。看起来还是比较清晰的。挑一个项目自己动手试一下吧!

然而你可能已经注意到了,这样的打包工具只是摸到了上面提到的打包最低要求。为什么?因为构建依赖不可控。打包的构建依赖完全决定于当前运行的系统,如果正在运行 Ubuntu 13.10,那么依赖都是按照 Ubuntu 13.10 处理的,没有灵活性。更严重的是,如果打包用户的系统内没有安装构建的依赖,打包就不可能继续。而安装构建依赖又会给那些有洁癖的用户带来麻烦,让整个系统变得越来越臃肿。因此,我们需要继续封装工具,找到一个更有效的解决方案。

首先来说说小的改进。实际上正式打包人员通常不会直接使用dpkg-buildpackage的,不仅仅是因为名字太长的原因。有一个稍微高级一点的封装,叫做debuild。详细情况,可以查阅其手册页:debuild(1)。它只做三件事:

  1. 调用dpkg-buildpackage打包;
  2. 调用lintian检查打包错误并提出警告;
  3. 调用debsign为打包成果进行数字签名。

就完成了这么一点小小的工作,谢谢大家!
嘛,其实大家喜欢使用它的原因一是打包完成后立即可以进行检查,二是敲起来顺手。请试着在键盘上敲一遍:debuild,键位顺序是不是比dpkg-buildpackage舒服得多呢?

也有更大的改进,这就要从原理上下手了,改动比较大。请看下一节。

引入chroot:从dpkg-buildpackagepbuilder/cowbuilder

之前说过了,如果直接在当前系统中安装编译依赖会直接影响系统的运行。基于chroot的解决方案可以回避这些问题。

回顾一下非chroot的解决方案的核心缺陷:

  1. 依赖关系来自宿主机,打包依赖决定于宿主机的软件版本;
  2. 构建依赖要求在宿主机上安装,污染宿主机的工作环境;

于是下一步我们需要达到以下的目标和做法就很清楚了:

  1. 依赖关系不由宿主机确定,人为规定一系列版本。例如,打包时 chroot 环境中已安装的软件永远来自最新的 unstable/sid 发行版。
  2. 构建依赖单独安装。例如,在 chroot 环境中安装依赖软件包,不会影响宿主机。

未完待续……

Git的引入,满意的解决方案:git-pbuildergbp buildpackage

未完待续……

生产环境部署的工具:sbuild

未完待续……


让打补丁变得优雅:gbp pq

打补丁,是常常出现的需求。最常见的应用实例是原来的源代码爆出漏洞,需要在上游释出新版本之前先打个补丁修复一下。传统的补丁工具当然是diffpatch,不过我相信在有条件的情况下,没有人会抱着这两个老掉牙的工具不放。让我们看看优雅一点的解决方案。

你已经读过上面提到的新维护人员手册了,所以quilt这个工具应该不陌生。在打包没有代码版本管理的时光里,它便是管理位于debian/patches/目录下的补丁的传统工具。不过这个工具并不好用。且不说它既无法维护DEP-3对应的元信息又只能干巴巴地处理diff -u格式的补丁,新维护人员手册更是竟然推荐你手动设置 alias 使用 dquilt 命令进行工作。不可以,这不优雅。

是时候展现什么是真正的优雅了。我们在使用 Git,那么git format-patch的输出便是优雅。我们喜欢diff --git格式的补丁。既然diff/patch都被 Git 这种代码版本管理软件替代了,我们当然要用 Git 管理补丁。补丁与 Git 提交一一对应,这才是真正的优雅。

让我来介绍一下gbp pq这个子工具。惯例地,请在需要了解详情时阅读其手册页:gbp-pq(1)。它应当和gbp的其它工具一起使用,不过独立使用也无妨。

gbp pq使用一个特殊的 Git 分支管理补丁:patch-queue/[base-branch-name]。它存储名字为[base-branch-name]的打包分支对应的补丁。请看下面几个应用实例:

场景一:初始化

最简单的情况下,只需要在需打包的软件包工作根目录下执行下面一句话:

gbp pq import

即可。如果以前没有补丁,这句话会创建一个与当前工作分支完全等效的分支,其名称为patch-queue/[base-branch-name]。如果以前有补丁,这些补丁会自动形成 Git 提交。不放心的话,检查一下 Git 提交历史,确认一下以前的补丁已经成功导入。

我必须在这里说一句,如果你已经有了patch-queue/[base-branch-name]这样名称的分支的话——gbp pq import这句命令是不行的,你也许要用到gbp pq rebase。但是这样会大大复杂化工作流程。我的建议是:初始化之前,干掉patch-queue分支,稍后重建也不迟。嗯,一切为了优雅和简单。

嗯?下面该干什么?下面就是开发补丁的时间!修改你想要修改的东西,在这个特殊的分支上像日常开发那样做 Git 提交,无论是自己修改做新的提交还是cherry-pick已经存在的提交都可以。注意事项有以下几点:

  1. 时刻牢记,每一个提交就是一个补丁,控制好各个提交之间的关系。
  2. 请认真填写 Git 提交的标题和日志信息。这些信息会成为自动生成的补丁的一部分。
  3. 尽量减少各个提交之间的耦合程度。
  4. 和打包工作流的原则相反,千万不要修改debian/目录下的文件。原因我想大家都可以理解。

如果你确信该打的补丁都在这个分支上体现了,请进入场景二继续工作。

场景二:在patch-queue上开发完成,导出补丁至原打包工作分支

接着上面的工作,我们仍然位于patch-queue/[base-branch-name]这个特殊 Git 分支上。运行一行命令:

gbp pq export

即可。这样会自动回到打包工作分支上,同时将刚才的工作成果自动导出,debian/patches目录下的文件得到了自动的修改。大功告成!

场景三:补丁的修改

修改这个嘛,其实可以按照先前两个场景的顺序再走一遍,在导出之前使用 Git 的技巧调整、修改 Git 提交的内容和顺序,导出后便实现了对补丁的修改。

实际上gbp pq工具还有更多高级的工作方法,这里不详述。如有兴趣,不妨自行查阅文档。

推荐工作流:gbp/dh-make/prinstine-tar/gbp pq/git-pbuilder/gbp pq/gbp buildpackage

这里需要补充内容……

有用的gbp buildpackage参数

这里需要补充内容……

实际项目示例

说是示例,我们还是拿现成的项目做例子,例如上面提到的shadowsocks-libev

首先,我们把项目的 Git 软件仓库从 GitHub 上拽下来:

git clone https://github.com/shadowsocks/shadowsocks-libev.git
cd shadowsocks-libev

默认情况下你正处在开发的master分支(主分支),如果开发者没处理好的话可能会出一点问题。如果要保守一点的话,使用git checkout检出最近的一个发布版本吧,这样可能会减少出问题的几率。

假设我们现在开始进行打包的开发。首先,我们需要创建一个上游分支并在特定的提交(commit)上打上标签,这个提交便是我们对特定版本开发的基础:

git checkout -b upstream # create upstream branch based on current commit
git tag 'upstream/2.5.3' # assume current branch is the 2.5.3 version of upstream software
git checkout - # return to master branch
git branch --set-upstream upstream origin/master # let upstream branch track master branch of remote "origin" repository

打标签的原则在前面“基于分支和标签的工作流程”一节已经讲过。另外,最后一步更改追踪分支的操作不是必要的。

下面便是对debian/正常的修改、提交了。注意事项也在前面的工作流程部分讲过。

vim debian/control # or some other files
git add debian/*
git commit
# repeat again and again until you are satisfied
dch -i

注意一下最后的dch -i。这里,我们再次提出一个新小工具:dch。它属于devscripts软件包的一部分,我推荐你阅读一下它的手册页:debchange(1)。它可以自动生成、处理符合要求的changelog文件,避免手写changelog的尴尬。dch -i的参数是”增量“的意思,即增量填写软件包新版本的修订信息。有关changelog的具体写法,请参考上文提到的新维护人员手册,这里不赘述。

值得注意的是,gbp提供了使用git提交信息生成changelog的便利工具:gbp dch。这样,只要在打包分支上的每个提交都认真书写提交信息,最后生成changelog时就可以完全自动化了。具体用法请自行探索,和dch没有什么两样。

终于终于,我们的开发接近尾声。该写的信息都写好了,下面只要使用gbp一气呵成地构建就大功告成。作为示例,我们只需要一行命令即可:

GBP_CONF_FILES="$HOME/.gbp.conf:debian/gbp.conf" gbp buildpackage --git-pbuilder --git-submodules \
--git-upstream-branch='upstream/2.5.3' --git-debian-branch=master --git-no-pristine-tar \
--git-upstream-tree=tag -sa

貌似有点长。不过不要紧,我只是把一些可以调整的参数都写进命令行了。如果你需要详细了解,请查看gbp buildpackage的手册页。下面我们一点一点解释:

GBP_CONF_FILES
环境变量,指定配置文件位置。配置文件语法请见gbp.conf文件的手册页。
–git-pbuilder
使用git-pbuilder,而后者默认会使用cowbuilder。只要这些软件都安装好,使用都不是问题。
–git-submodules
自动处理带有子模块的情况。即使这个仓库本来就没有子模块,这个参数带上也是无害的。要求Debian版本大于等于7。
–git-debian-branch
指定打包用分支。根据实际情况填写,通常是master。
–git-[no-]pristine-tar

是否使用pristine-tar提供上游源代码。看情况,如果先前没有使用pristine-tar的话就无视这一项吧。

-sa
这一项会直接传递到最底层的dpkg-source工具,对于打包版本大于1的情况仍然强制生成源代码而不是diff文件。如果是第一次打包,带上也无妨。
–git-upstream-tree
指定上游代码究竟应该如何检出。这里我们使用标签(tag),即使上游代码没有对应的分支也可以。当然,这还是要看你的具体情况。
–git-upstream-branch
指定上游分支/标签名称。这个名称需要事先了解并在这里填入。这里我们填入了一个标签名称。

如何?我觉得意义还是比较清晰的。

由于我们使用了cowbuilder,编译开始前会使用sudo向你请求超级用户权限。编译完成后,一切成果将放置在上一层目录中。

总结

这里需要补充内容……

5 thoughts to “配合Git为Debian系发行版打包的正确方式”

  1. 发现一个错误:倒数几行的 –git-upstream-tree 应该是 –git-upstream-branch 吧 🙂
    另外对打包分支的描述感觉不够清楚,直接在master分支下打包好像并不是 dep14 的提议啊

    1. –git-upstream-tree 的写法是可以接受的,详情请 man gbp-buildpackage 看看。
      重新看了一下 dep14 的提议,打包分支的建议貌似的确不是 master。这个我后面改一下。但是实际上我看到的很多项目都是这样做的,vcswatch(https://qa.debian.org/cgi-bin/vcswatch)也接受这种做法。我后续试验一下严格按照 DEP14 打包是否可以被接受。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注