软件发布实践 HOWTO

Eric Steven Raymond

Thyrsus 企业


    
    

修订历史
修订版 4.12013-01-14修订者:esr
从代码仓库检出以确保基于最新的代码进行修补。Freshmeat 更名了。USENET 主题组不再那么显眼了。
修订版 4.02010-04-11修订者:esr
在项目级别不再需要提供 RPMS 或 debs。打包基础设施在这方面已经做得很好了。关于配置和 autotools 的新警告。AsciiDOC 现在是文档主文件的可行替代方案。
修订版 3.92004-11-28修订者:esr
关于良好修补实践的新材料。推荐使用 Electric Fence 和 valgrind 而不是专有工具。
修订版 3.82003-02-17修订者:esr
站点移动后的 URL 修复。
修订版 3.72002-09-25修订者:esr
指向 DocBook Demystification HOWTO。
修订版 3.62002-09-12修订者:esr
采纳了 Keith Bostic 关于可移植性的材料。
修订版 3.62002-08-14修订者:esr
重写了关于文档实践的章节,因为 XML-Docbook 现在已经成熟。
修订版 3.52002-07-04修订者:esr
添加了关于提供校验和的章节。引用了 doclifter。
修订版 3.42002-01-04修订者:esr
更多关于良好修补实践的内容。
修订版 3.32001-08-16修订者:esr
关于如何发送良好补丁的新章节。
修订版 3.22001-07-11修订者:esr
关于不依赖专有组件的注释。
修订版 3.12001-02-22修订者:esr
LDP 风格指南修复。
修订版 3.02000-08-12修订者:esr
第一个 DocBook 版本。添加了关于 SourceForge 的建议和关于文档实践的主要章节。

本 HOWTO 描述了 Linux 和其他开源项目的良好发布实践。通过遵循这些实践,您将尽可能方便用户构建和使用您的代码,并方便其他开发人员理解您的代码并与您合作改进它。

本文档是新手开发人员的必读之物。经验丰富的开发人员在即将发布新项目时应回顾它。它将定期修订,以反映良好实践标准的演变。


目录
1. 引言
1.1. 本文档的目的?
1.2. 本文档的新版本
2. 良好的修补实践
2.1. 发送补丁,不要发送整个归档文件或文件
2.2. 针对代码的当前版本发送补丁。
2.3. 不要包含针对生成文件的补丁。
2.4. 不要发送仅调整版本控制 $-符号的补丁段。
2.5. 使用 -c 或 -u 格式,不要使用默认的 (-e) 格式
2.6. 在补丁中包含文档
2.7. 在补丁中包含解释
2.8. 在代码中包含有用的注释
2.9. 每个补丁只修复一个错误或添加一个新功能。
3. 良好的项目和归档文件命名实践
3.1. 使用 GNU 风格的名称,带有词干和 major.minor.patch 编号。
3.2. 但请在适当情况下尊重本地约定
3.3. 努力选择一个独特且易于键入的名称前缀
4. 良好的许可和版权实践:理论
4.1. 开源和版权
4.2. 什么符合开源的条件
5. 良好的许可和版权实践:实践
5.1. 让自己或 FSF 成为版权持有人
5.2. 使用符合开源定义的许可证
5.3. 如果可以避免,请不要编写自己的许可证。
5.4. 使您的许可证在标准位置可见。
6. 良好的开发实践
6.1. 选择您可以使用的最具可移植性的语言
6.2. 不要依赖专有代码
6.3. 构建系统
6.4. 在发布之前测试您的代码
6.5. 在发布之前进行代码的健全性检查
6.6. 在发布之前进行文档和 README 的健全性检查
6.7. 推荐的 C/C++ 可移植性实践
7. 良好的发行版制作实践
7.1. 确保 tarball 始终解压到单个新目录中
7.2. 拥有一个 README 文件
7.3. 尊重并遵循标准文件命名实践
7.4. 为可升级性而设计
7.5. 提供校验和
8. 良好的文档实践
8.1. 文档格式
8.2. 良好实践建议
9. 良好的沟通实践
9.1. 在 Freecode 上公告
9.2. 拥有一个网站
9.3. 托管项目邮件列表
9.4. 发布到主要归档站点
10. 良好的项目管理实践

1. 引言

1.1. 本文档的目的?

开源代码存在大量良好的实践传统,这些传统有助于其他人移植、使用和合作开发代码。其中一些约定在 Unix 世界中是传统的,并且早于 Linux;另一些约定是最近为了响应特定的新工具和技术(例如万维网)而发展起来的。

本文档将帮助您学习良好的实践。它分为主题部分,每个部分都包含一系列检查清单项。将这些视为您的补丁或软件发行版的飞行前检查清单。

提供 德语 翻译版本。


1.2. 本文档的新版本

本文档将每月发布到新闻组 comp.os.linux.answers。您还可以通过 URL https://tldp.cn/HOWTO/Software-Release-Practice.html 在万维网上查看此 HOWTO 的最新版本。

欢迎发送关于此 HOWTO 的任何问题或评论给 Eric S. Raymond,.


2. 良好的修补实践

大多数人参与开源软件都是从为别人的软件编写补丁开始,然后再发布自己的项目。假设您为别人的基线代码编写了一组源代码更改。现在设身处地为那个人着想。他如何判断是否包含该补丁?

事实是,判断代码的质量非常困难。因此,开发人员倾向于通过提交的质量来评估补丁。他们会关注提交者的风格和沟通行为中的线索——表明这个人曾经身处他们的境地,并且了解评估和合并传入补丁的感受。

这实际上是代码质量的一个相当可靠的替代指标。在多年处理来自数百位陌生人的补丁的过程中,我很少看到一个经过深思熟虑、尊重我的时间但技术上是错误的补丁。另一方面,经验告诉我,看起来粗心大意或以懒惰和不体谅的方式打包的补丁很可能实际上错误的。

以下是一些关于如何让您的补丁被接受的技巧


2.1. 发送补丁,不要发送整个归档文件或文件

如果您的更改包含代码中不存在的新文件,那么您当然必须发送整个文件。但是,如果您正在修改已存在的文件,请不要发送整个文件。而是发送 diff;具体来说,发送运行 diff(1) 命令以比较基线发行版本和您的修改版本后的输出。

diff 命令及其双胞胎 patch(1)(它自动将 diff 应用于基线文件)是开源开发最基本的工具。Diff 比整个文件更好,因为您要发送补丁的开发人员可能在您获得副本后更改了基线版本。通过向他发送 diff,您可以节省他将您的更改与他的更改分开的精力;您表现出对他的时间的尊重。


2.2. 针对代码的当前版本发送补丁。

针对几个版本之前的代码向维护者发送补丁,并期望他完成所有工作,以确定哪些更改与他之后所做的事情重复,哪些是您的补丁中真正新颖的东西,这既适得其反,也很粗鲁。

作为补丁提交者,有责任跟踪源代码的状态,并向维护者发送一个最小的补丁,表达您希望对主线代码库执行的操作。这意味着针对当前版本发送补丁。

如今,实际上所有开源项目都通过公共匿名访问项目的版本控制存储库来提供其源代码。确保您修补的是最新版本的代码的最有效方法是从项目的存储库中检出代码的 head 版本。所有版本控制系统都有一个命令,可让您在工作副本和 head 之间进行 diff;使用它。


2.3. 不要包含针对生成文件的补丁。

在发送补丁之前,请浏览一下补丁,并删除其中用于文件的任何补丁段,这些文件在他应用补丁并重新制作后将自动重新生成。此错误的经典示例是由 BisonFlex 生成的 C 文件。

如今,此类最常见的错误是发送一个 diff,其中包含一个巨大的补丁段,该补丁段只不过是您的 configure 脚本与他的脚本之间的更改栏。此文件由 autoconf 生成。

这是不体谅人的。这意味着您的接收者需要费力地将补丁的真实内容与大量冗余信息分开。这是一个小错误,不如我们稍后将要讨论的一些事情那么重要——但它会给您减分。

如果您已从项目代码仓库中检出代码,并使用版本控制系统的 diff 命令来生成补丁,那么您可能会自动避免这种情况。


2.4. 不要发送仅调整版本控制 $-符号的补丁段。

有些人将特殊标记放在其源文件中,当文件检入时,版本控制系统会扩展这些标记:例如,RCS、CVS 和 Subversion 使用的 $Id$ 构造。

如果您自己使用本地版本控制系统,您的更改可能会更改这些标记。这实际上并没有危害,因为当您的接收者在应用您的补丁后将他的代码检入时,它们将根据他的版本控制状态重新扩展。但是这些额外的补丁段是冗余信息。它们会分散注意力。最好不要发送它们。

这是另一个小错误。如果您将重要的事项做好,您将被原谅。但您仍然想避免它。


2.5. 使用 -c 或 -u 格式,不要使用默认的 (-e) 格式

diff(1) 的默认 (-e) 格式非常脆弱。它不包含任何上下文,因此如果自您获取修改后的副本以来,基线代码中插入或删除了任何行,则 patch 工具将无法处理。

收到 -e diff 是令人恼火的,并表明发送者要么是极端新手、粗心大意或一窍不通。大多数此类补丁都会被不假思索地扔掉。


2.6. 在补丁中包含文档

这非常重要。如果您的补丁对软件的功能进行了用户可见的添加或更改,请在您的补丁中包含对相应手册页和其他文档文件的更改。不要假设接收者会很乐意为您记录您的代码,或者让未记录的功能潜伏在代码中。

充分记录您的更改证明了一些好处。首先,它对您试图说服的人是体谅的。其次,它表明您充分理解了您的更改的影响,足以向看不到代码的人解释它。第三,它表明您关心最终将使用该软件的人。

良好的文档通常是区分可靠贡献和快速而肮脏的黑客行为的最明显的标志。如果您花费时间和精力来生成它,您会发现您已经完成了 85% 的工作,以使您的补丁被大多数开发人员接受。


2.7. 在补丁中包含解释

您的补丁应包含封面注释,解释您为什么认为补丁是必要或有用的。此解释不是针对软件的用户,而是针对您要发送补丁的维护者。

注释可以很短——事实上,我见过的最有效的封面注释只是说“请参阅此补丁中的文档更新”。但它应该表现出正确的态度。

正确的态度是有帮助的、尊重维护者的时间、沉稳自信但不自负。最好表现出对您正在修补的代码的理解。最好表明您可以认同维护者的问题。最好也坦诚地说明您认为应用补丁存在的任何风险。以下是我喜欢在封面注释中看到的一些解释性评论的示例

“我发现此代码存在两个问题,X 和 Y。我修复了问题 X,但我没有尝试解决问题 Y,因为我认为我不了解我认为涉及的代码部分。”

“修复了当 foo 输入之一过长时可能发生的 core dump。同时,我开始寻找其他地方类似的溢出。我在 blarg.c 的第 666 行附近发现了一个可能的溢出。您确定发送者每次传输不能生成超过 80 个字符吗?”

“您是否考虑过使用 Foonly 算法来解决此问题?在 <http://www.somesite.com/~jsmith/foonly.html> 上有一个很好的实现。”

“此补丁解决了当前的问题,但我意识到它以一种令人不快的方式使内存分配变得复杂。对我来说可以使用,但您可能应该在发货前在高负载下对其进行测试。”

“这可能是功能蔓延,但我还是发送了它。也许您会知道一种更简洁的方式来实现该功能。”


2.8. 在代码中包含有用的注释

通常作为维护者,我希望在合并您的更改之前,我非常有信心我理解您的更改。这不是一个一成不变的规则;如果您有良好的工作记录,对于我来说,我可能只会随便浏览一下更改,然后再半自动地检查它们。但是,您可以尽一切努力帮助我理解您的代码并减少我的不确定性,这将增加我接受您的补丁的机会。

代码中的良好注释有助于我理解它。糟糕的注释则不然。

这是一个糟糕的注释的例子

/* norman newbie fixed this 13 Aug 2009 */

这没有传达任何信息。这只不过是您在我代码中间留下的一个泥泞的领地脚印。如果我接受您的补丁(您已经降低了这种可能性),我几乎肯定会删除此注释。如果您想要署名,请为项目包含一个补丁段NEWSHISTORY文件。我可能会接受那个。

这是一个良好注释的例子

/*
 * This conditional needs to be guarded so that crunch_data()
 * never gets passed a NULL pointer.  <norman_newbie@foosite.com>
 */

此注释表明您不仅了解我的代码,而且了解我需要哪些类型的信息才能对您的更改充满信心。这种注释给予我对您的更改的信心。


2.9. 每个补丁只修复一个错误或添加一个新功能。

不要收集各种错误修复、新功能或其他内容,并将它们作为单个 diff 文件发送。相反,为每个错误修复或功能构建一个单独的 diff。每个单独的 diff 都应使用代码的当前版本生成,并且不应依赖于您或其他人之前发送的任何其他补丁。

这有助于维护者阅读和理解您的代码,并且他可以决定是否要接受或放弃此单个错误修复或功能。例如,如果您添加了一个为每个人保证完全 root 访问权限的功能,因为它在您的特殊设置中很有用,则维护者可能不会接受您补丁的这一部分。当您发送一个包含此 root 访问权限功能以及一些错误修复和其他有用内容的单个 diff 文件时,您很有可能维护者会忽略您的完整补丁。

对于每个作为单个 diff 文件的错误修复和新功能,维护者可以在几分钟内包含您的错误修复,并在稍后仔细查看您的新功能或安全问题。

依赖于其他补丁的补丁会引发类似的问题。如果维护者拒绝您的基本补丁,他将无法应用其他补丁。如果您无法避免此类依赖关系,请向维护者表示您将捆绑一个包含他想要添加的部分或功能的补丁。


3. 良好的项目和归档文件命名实践

随着 Metalab、PSA 站点和 CPAN 等归档站点的维护者负载增加,提交内容部分或全部由程序(而不是完全由人)处理的趋势越来越明显。

这使得项目和归档文件名更需要符合计算机程序可以解析和理解的规则模式。


3.1. 使用 GNU 风格的名称,带有词干和 major.minor.patch 编号。

如果您的归档文件都具有类似 GNU 的名称——全小写字母数字词干前缀,后跟一个破折号,后跟版本号、扩展名和其他后缀,这对所有人都有帮助。

假设您有一个名为“foobar”的项目,版本为 1,发行版为 2,级别为 3。如果它只有一个归档部分(大概是源代码),则其名称应如下所示

foobar-1.2.3.tar.gz

源代码归档

foobar.lsm

LSM 文件(假设您要提交到 Metalab)。

不要使用这些

foobar123.tar.gz

在许多程序看来,这就像一个名为“foobar123”的项目的归档文件,没有版本号。

foobar1.2.3.tar.gz

在许多程序看来,这就像一个名为“foobar1”的项目的归档文件,版本为 2.3。

foobar-v1.2.3.tar.gz

许多程序认为这与名为“foobar-v1”的项目相关。

foo_bar-1.2.3.tar.gz

下划线让人难以发音、键入和记忆。

FooBar-1.2.3.tar.gz

除非您喜欢看起来像个营销人员。这也让人难以发音、键入和记忆。

如果您必须区分源代码归档和二进制归档,或不同类型的二进制文件,或在文件名中表达某种构建选项,请将其视为版本号之后的文件扩展名。也就是说,请这样做

foobar-1.2.3.src.tar.gz

源代码

foobar-1.2.3.bin.tar.gz

二进制文件,未指定类型

foobar-1.2.3.bin.ELF.tar.gz

ELF 二进制文件

foobar-1.2.3.bin.ELF.static.tar.gz

静态链接的 ELF 二进制文件

foobar-1.2.3.bin.SPARC.tar.gz

SPARC 二进制文件

不要使用类似“foobar-ELF-1.2.3.tar.gz”的名称,因为程序很难区分类型中缀(如“-ELF”)和词干。

良好的通用名称形式按顺序包含以下部分

  1. 项目前缀

  2. 破折号

  3. 版本号

  4. “src”或“bin”(可选)

  5. 点或破折号(首选点)

  6. 二进制类型和选项(可选)

  7. 归档和压缩扩展名


3.2. 但请在适当情况下尊重本地约定

某些项目和社区对于名称和版本号具有明确定义的约定,这些约定不一定与上述建议兼容。例如,Apache 模块通常命名为 mod_foo,并且既有自己的版本号,也有与之配合使用的 Apache 版本。同样,Perl 模块的版本号可以视为浮点数(例如,您可能会看到 1.303 而不是 1.3.3),并且发行版通常命名为 Foo-Bar-1.303.tar.gz,表示模块 Foo::Bar 的 1.303 版本。(另一方面,Perl 本身在 1999 年末切换为使用本文档中描述的约定。)

查找并尊重专门社区和开发人员的约定;对于一般用途,请遵循上述指南。


3.3. 努力选择一个独特且易于键入的名称前缀

词干前缀应易于阅读、键入和记忆。因此请不要使用下划线。除非有极其充分的理由,否则不要大写或使用大小写混合——它会打乱自然的眼球搜索顺序,等等。

当两个不同的项目具有相同的词干名称时,会使人们感到困惑。因此,请在首次发布之前检查是否存在冲突。Google 是您的朋友——如果您正在考虑的词干获得大量 Google 搜索结果,那么这本身可能就足以让您想出一个不同的词干。另一个值得检查的好地方是 FreecodeSourceForge 的应用程序索引;在那里进行名称搜索。


4. 良好的许可和版权实践:理论

您选择的许可证定义了您希望在共同开发者和用户之间建立的社会契约。您在软件上添加的版权主要充当您对软件和软件的衍生作品设置许可条款的权利的法律主张。


4.1. 开源和版权

任何不是公共领域的东西都具有版权,可能不止一个。根据伯尔尼公约(自 1978 年以来一直是美国法律),版权不必是明确的。也就是说,即使没有版权声明,作品的作者也拥有版权。

谁算作作者可能非常复杂,特别是对于许多人参与开发的软件而言。这就是许可证重要的原因。通过规定可以使用材料的条款,它们授予用户权利,保护他们免受版权持有人的任意行为。

在专有软件中,许可证条款旨在保护版权。它们是一种在尽可能为所有者(版权持有人)保留尽可能多的法律领域的同时,向用户授予少量权利的方式。版权持有人非常重要,并且许可证逻辑如此严格,以至于许可证条款的确切技术性通常并不重要。

在开源软件中,情况通常恰恰相反;版权的存在是为了保护许可证。版权持有人始终保留的唯一权利是执行许可证。否则,只保留少量权利,并且大多数选择权都转移给用户。特别是,版权持有人不能更改您已拥有的副本的条款。因此,在开源软件中,版权持有人几乎无关紧要——但许可证条款非常重要。

通常,项目的版权持有人是当前的项目负责人或赞助组织。将项目转移给新的负责人通常通过更改版权持有人来表示。但是,这不是一个硬性规定;许多开源项目都有多个版权持有人,并且没有记录表明这会导致法律问题。

一些项目选择将版权转让给自由软件基金会,理论是它有兴趣捍卫开源并有律师可以做到这一点。


4.2. 什么符合开源的条件

出于许可目的,我们可以区分许可证可以传达的几种不同类型的权利。复制和再分发的权利、使用的权利、为个人使用而修改的权利以及再分发修改后的副本的权利。许可证可以限制或附加条件于任何这些权利。

开源促进会是对什么使软件成为 “开源” 或(在较旧的术语中)“自由软件” 进行了大量思考的结果。其对许可的约束要求

  1. 授予无限的复制权。

  2. 授予无限的使用权。

  3. 授予无限的为个人使用而修改的权利。

指南禁止限制修改后的二进制文件的再分发;这满足了软件发行商的需求,他们需要能够无负担地发布工作代码。它允许作者要求修改后的源代码作为原始源代码加上补丁重新分发,从而确立作者的意图以及其他人进行的任何更改的 “审计跟踪”

OSD 是“OSI 认证开源”认证标志的法律定义,并且与任何人提出的 “自由软件” 定义一样好。所有标准许可证(MIT、BSD、Artistic 和 GPL/LGPL)都符合它(尽管有些许可证,例如 GPL,还有其他限制,您应该在选择它之前了解这些限制)。

请注意,仅允许非商业用途的许可证符合开源许可证的条件,即使它们装饰有 “GPL” 或其他一些标准许可证。它们歧视特定的职业、个人和群体。它们使 CD-ROM 发行商和其他试图以商业方式传播开源软件的人的生活变得过于复杂。


5. 良好的许可和版权实践:实践

以下是如何将上述理论转化为实践


5.1. 让自己或 FSF 成为版权持有人

在某些情况下,如果您背后有拥有律师的赞助组织,您可能希望将版权授予该组织。


5.2. 使用符合开源定义的许可证

开源定义是社区许可证的黄金标准。OSD 本身不是许可证;相反,它定义了许可证必须保证才能被视为开源许可证的最低权利集。OSD 和支持材料可以在 开源促进会的网站上找到。


5.3. 如果可以避免,请不要编写自己的许可证。

广为人知的符合 OSD 的许可证具有完善的解释传统。开发人员(以及在一定程度上关心的用户)知道它们意味着什么,并且对它们涉及的风险和权衡有合理的了解。因此,如果可能,请使用 OSI 站点上携带的标准许可证之一。

如果您必须编写自己的许可证,请务必让 OSI 对其进行认证。这将避免大量争论和开销。除非您经历过,否则您无法想象许可证口水战会有多糟糕;人们变得热情,因为许可证被视为几乎神圣的盟约,触及开源社区的核心价值观。

此外,如果您的许可证在法庭上受到考验,那么既定的解释传统的存在可能会被证明很重要。在撰写本文时(2002 年初),尚无支持或否定任何开源许可证的判例法。但是,至少在美国,可能在其他英联邦普通法国家(如英国和英国联邦的其他地区)存在一种法律原则,即法院应根据许可证和合同的起源社区的期望和惯例来解释许可证和合同。


5.4. 使您的许可证在标准位置可见。

随着部署的开源软件的数量越来越大,审核这些软件的数量以确定哪些许可证涵盖它们的问题变得不容忽视——事实上,它变得比一个没有帮助的人能够执行的更大。因此,拥有支持许可证信息的机械化查询的约定非常有价值。幸运的是,现有的社区实践已经趋向于这个方向。

首先,您的软件的许可证信息应位于源发行版顶级目录中名为 COPYING 或 LICENSE 的文件中。如果单个许可证适用于整个发行版,则该文件应包含许可证的副本。如果适用多个许可证,则该文件应列出适用的许可证,并指示它们适用于哪些文件和子目录。


6. 良好的开发实践

其中大多数都与确保在所有 POSIX 和类 POSIX 系统上的可移植性有关。广泛的可移植性不仅是一种值得称赞的专业精神和黑客礼貌形式,而且是对 Linux 未来变化的宝贵保险。

最后,其他人尝试在非 Linux 系统上构建您的代码;可移植性减少了您收到的支持电子邮件的数量。


6.1. 选择您可以使用的最具可移植性的语言

选择一种开发语言,以最大限度地减少运行它的底层环境的差异。C/C++ 程序可能比 Fortran 更具可移植性。Java 优于 C/C++,而 Perl、Python 或 Ruby 等高级脚本语言是最佳选择(因为脚本语言只有一个跨平台实现)。

符合条件的脚本语言包括 Python、Perl、Tcl、Emacs Lisp 和 PHP。历史悠久的 Bourne shell (/bin/sh) 符合条件;有太多不同的实现具有细微的特性,并且 shell 环境容易受到用户自定义(例如 shell 别名)的干扰。

如果您选择 C/C++ 等编译语言,请不要使用编译器扩展(例如在堆栈上分配可变长度数组,或通过 void 指针间接寻址)。定期使用尽可能多的不同编译器和测试机器进行构建。


6.2. 不要依赖专有代码

不要依赖专有语言、库或其他代码。在开源社区中,这被认为是粗鲁的。开源开发人员不信任他们无法查看源代码的代码。


6.3. 构建系统

开源发行版的一个显着优势是它们允许每个源软件包在编译时适应它找到的环境。您需要做出的选择之一是“构建系统”,即您(和其他人)将用来将您的源代码转换为可执行文件的工具包。

构建脚本无法做的一件事是在编译时向用户询问系统信息。安装软件包的用户不一定知道您的问题的答案,因此这种方法从一开始就注定要失败。您的软件(调用构建系统工具)必须能够自行确定在编译时或安装时可能需要的任何信息。

关于构建系统中最佳实践的社区概念目前(2010 年初)正处于某种动荡状态。


6.3.1. GNU autotools:逐渐衰落但仍然是标准

本 HOWTO 的早期版本曾敦促使用 GNU autotools 来处理可移植性问题、进行系统配置探测以及定制您的 makefile。这仍然是标准且最流行的方法,但它正变得越来越成问题,因为 GNU autotools 正在显现其老态。Autotools 一直是一堆建立在黑客技术和蹩脚方法之上的蹩脚方法,以混乱的语言混合实现,并存在一些严重的设计缺陷。对于许多年来说,由此产生的混乱是可以容忍的,但随着项目变得越来越复杂,它越来越显得力不从心。

尽管如此,从 C 源代码构建的人们仍然希望能够键入“configure; make; make install”并获得干净的构建。假设您选择一个非 autotools 系统,您可能希望模拟这种行为(这应该很容易)。

这里有一个关于 autotools 的优秀教程:这里


6.3.2. SCons:在拥挤的领域中领先

取代 autotools 的竞赛尚未产生赢家,但它有一个领跑者:SCons。SCons 废除了 makefile;它将 autotools 构建序列的“configure”和“make”部分组合成一个步骤。它在 Unix/Linux、Maoc OS X 和 Windows 上通过一个配方提供跨平台构建。它用 Python 编写,可以用 Python 扩展,并且在某种程度上搭上了该语言日益流行的顺风车。


6.3.3. CMake 和其他工具

SCons 仍然是一个少数派选择,并且面临来自其他几个工具的激烈竞争,其中 CMake 和 WAF 可能是最突出的。考虑到来源,可以在 SCons wiki 上找到相当公正的交叉比较。


6.4. 在发布之前测试您的代码

一个好的测试套件允许团队购买廉价的硬件进行测试,然后在发布之前轻松运行回归测试。创建一个强大、可用的测试框架,以便您可以逐步向您的软件添加测试,而无需培训开发人员掌握测试套件的复杂性。

分发测试套件允许用户社区在将他们的移植贡献回团队之前对其进行测试。

鼓励您的开发人员使用各种各样的平台作为他们的桌面和测试机器,以便在正常开发过程中不断测试代码的可移植性缺陷。


6.5. 在发布之前进行代码的健全性检查

如果您正在使用 GCC 编写 C/C++,请使用 -Wall 进行测试编译,并在每次发布之前清理所有警告消息。使用您可以找到的每个编译器编译您的代码——不同的编译器通常会发现不同的问题。特别是,在真正的 64 位机器上编译您的软件。底层数据类型在 64 位机器上可能会发生变化,您经常会在那里发现新的问题。找到 UNIX 供应商的系统并在您的软件上运行 lint 实用程序。

运行用于内存泄漏和其他运行时错误的工具;Electric Fence 和 Valgrind 是两个在开源中可用的好工具。

对于 Python 项目,PyChecker 程序可能是一个有用的检查工具。它尚未脱离 beta 版,但仍然经常捕获重要的错误。

如果您正在编写 Perl,请使用 perl -c(可能还有 -T,如果适用)检查您的代码。虔诚地使用 perl -w 和 'use strict'。(有关更多讨论,请参阅 Perl 文档。)


6.6. 在发布之前对您的文档和 README 进行健全性检查

对您的文档、README 文件和软件中的错误消息进行拼写检查。草率的代码、编译时产生警告消息的代码以及 README 文件或错误消息中的拼写错误,都会让用户认为其背后的工程也是随意和草率的。


6.7. 推荐的 C/C++ 可移植性实践

如果您正在编写 C,请随意使用完整的 ANSI 功能。特别是,请使用函数原型,这将帮助您发现跨模块的不一致性。旧式的 K&R 编译器已成为历史。

不要假定可以使用编译器特定的功能,例如 GCC 的“-pipe”选项或嵌套函数。一旦有人移植到非 Linux、非 GCC 系统,这些功能就会反噬您。

可移植性所需的代码应隔离到单个区域和一组源文件(例如,“os”子目录)。具有可移植性问题的编译器、库和操作系统接口应抽象到此目录中的文件中。这包括诸如“errno”之类的变量、诸如“malloc”之类的库接口以及诸如“mmap”之类的操作系统接口。

可移植性层使执行新的软件移植更容易。通常情况下,开发团队的成员不了解移植平台(例如,实际上有数百种不同的嵌入式操作系统,没有人了解其中的很大一部分)。通过创建单独的可移植性层,了解移植平台的人员可以在不必理解您的软件的情况下移植它。

可移植性层简化了应用程序。软件很少需要诸如 mmap 或 stat 之类的更复杂的系统调用的全部功能,并且程序员通常会错误地配置如此复杂的接口。具有抽象接口(“__file_exists”而不是调用 stat)的可移植性层允许您仅从系统中导出有限的、必要的功能,从而简化应用程序中的代码。

始终编写您的可移植性层以基于特性进行选择,而不是基于平台。尝试为每个支持的平台创建单独的可移植性层会导致多重更新问题维护噩梦。“平台”始终在至少两个轴上选择:编译器和库/操作系统版本。在某些情况下,有三个轴,例如当 Linux 供应商独立于操作系统版本选择 C 库时。拥有 M 个供应商、N 个编译器和 O 个操作系统版本,“平台”的数量很快就会扩展到任何规模较小的开发团队都无法企及的地步。通过使用语言和系统标准(如 ANSI 和 POSIX 1003.1),特性的集合相对受到约束。

可移植性选择可以在代码行或编译文件之间进行。如果您在平台上选择备用代码行,或者选择几个不同的文件之一,则没有区别。经验法则是,当不同平台的实现差异很大时(UNIX 与 Windows 上的共享内存映射),将不同平台的可移植性代码移动到单独的文件中,而当差异最小时(使用 gettimeofday、clock_gettime、ftime 或 time 来查找当前时间),则将可移植性代码保留在单个文件中。

避免使用诸如“off_t”和“size_t”之类的复杂类型。它们的尺寸因系统而异,尤其是在 64 位系统上。将您对“off_t”的使用限制在可移植性层,将您对“size_t”的使用仅限于表示内存中字符串的长度,而不是其他任何内容。

永远不要侵犯系统任何其他部分的命名空间(包括文件名、错误返回值和函数名)。在共享命名空间的地方,记录您使用的命名空间部分。

选择一个编码标准。关于标准选择的争论可能会永无止境——无论如何,维护使用多个编码标准构建的软件都太困难且成本太高,因此必须选择某种编码标准。严格执行您的编码标准,因为代码的一致性和整洁性是最高优先级;编码标准本身的细节则远居其次。


7. 良好的发行版制作实践

这些指南描述了当有人下载、检索和解压缩您的发行版时,您的发行版应该是什么样子。


7.1. 确保 tarball 始终解压缩到单个新目录中

新手开发人员犯下的最令人恼火的错误是将发行版中的文件和目录解压缩到当前目录中的 tarball,这可能会覆盖已位于该目录中的文件。永远不要这样做!

相反,请确保您的存档文件都有一个以项目命名的公共目录部分,以便它们直接解压缩到当前目录下方的单个顶层目录中。

这是一个 makefile 技巧,假设您的发行版目录名为 `foobar',并且 SRC 包含您的发行版文件的列表,则可以完成此操作。SRC 还可以包含要完整包含的子目录的名称。

foobar-$(VERS).tar.gz:
	@find $(SRC) -type f | sed s:^:foobar-$(VERS)/: >MANIFEST
	@(cd ..; ln -s foobar foobar-$(VERS))
	(cd ..; tar -czvf foobar/foobar-$(VERS).tar.gz `cat foobar/MANIFEST`)
	@(cd ..; rm foobar-$(VERS))

7.2. 拥有一个 README 文件

拥有一个名为READMEREAD.ME的文件,它是您源代码发行版的路线图。按照古老的惯例,这是勇敢的探险家在解压缩源代码后将阅读的第一个文件。

README 中应包含的良好内容包括

  1. 项目的简要描述。

  2. 项目网站的指针(如果有)。

  3. 关于开发人员构建环境和潜在可移植性问题的注释。

  4. 描述重要文件和子目录的路线图。

  5. 构建/安装说明或指向包含相同内容的文件的指针(通常是INSTALL).

  6. 维护人员/贡献者列表或指向包含相同内容的文件的指针(通常是CREDITS).

  7. 最近的项目新闻或指向包含相同内容的文件的指针(通常是NEWS).


7.3. 尊重并遵循标准文件命名惯例

在查看 README 之前,您勇敢的探险家将扫描您解压缩的发行版的顶层目录中的文件名。这些名称本身可以传达信息。通过遵守某些标准命名惯例,您可以为探险家提供关于下一步查看内容的宝贵线索。

以下是一些标准的顶层文件名及其含义。并非每个发行版都需要所有这些。

README 或 READ.ME

路线图文件,首先阅读

INSTALL

配置、构建和安装说明

AUTHORS

项目贡献者列表。

一个较旧但仍然可以接受的约定是将其命名为 CREDITS

NEWS

最近的项目新闻

HISTORY

项目历史

COPYING

项目许可条款(GNU 惯例)

LICENSE

项目许可条款

MANIFEST

发行版中文件的列表

FAQ

项目的纯文本常见问题解答文档

TAGS

为 Emacs 或 vi 生成的标签文件

请注意,整体约定是,名称全大写的文件名是关于软件包的人类可读的元信息,而不是构建组件(TAGS 是第一个的例外,但不是第二个的例外)。

拥有 FAQ 可以为您节省很多麻烦。当经常出现关于项目的问题时,将其放在 FAQ 中;然后指导用户在发送问题或错误报告之前阅读 FAQ。一个精心维护的 FAQ 可以将项目维护人员的支持负担减少一个数量级或更多。

拥有一个带有每个版本时间戳的 HISTORY 或 NEWS 文件非常有价值。除其他外,如果您遇到专利侵权诉讼,它可能会帮助确定现有技术(这种情况尚未发生在任何人身上,但最好做好准备)。


7.4. 为可升级性而设计

随着您发布新版本,您的软件会随着时间的推移而变化。其中一些更改将不向后兼容。因此,您应该认真考虑设计您的安装布局,以便您的代码的多个已安装版本可以共存于同一系统上。这对于库来说尤其重要——您不能指望您的所有客户端程序都与您的 API 更改同步升级。

Emacs、Python 和 Qt 项目在处理此问题方面有一个很好的约定;版本编号的目录。以下是已安装的 Qt 库层次结构的外观(${ver} 是版本号)

/usr/lib/qt
/usr/lib/qt-${ver}
/usr/lib/qt-${ver}/bin          # Where you find moc
/usr/lib/qt-${ver}/lib          # Where you find .so
/usr/lib/qt-${ver}/include      # Where you find header files

通过这种组织方式,您可以拥有多个共存的版本。客户端程序必须指定他们想要的库版本,但这对于避免接口中断来说是一个很小的代价。


7.5. 提供校验和

为您的二进制文件(tarball、RPM 等)提供校验和。这将允许人们验证它们是否已损坏或已插入特洛伊木马代码。

虽然您可以使用几个命令来实现此目的(例如 sumcksum),但最好使用加密安全的哈希函数。GPG 软件包通过—detach-sign选项提供此功能;GNU 命令 md5sum 也是如此。

对于您发布的每个二进制文件,您的项目网页应列出校验和以及您用于生成它的命令。


8. 良好的文档实践

最重要的良好文档实践是实际编写一些文档!太多程序员忽略了这一点。但这里有两个很好的理由来这样做

  1. 您的文档可以是您的设计文档。 编写它的最佳时间是在您键入一行代码之前,当您思考您想做什么时。您会发现,用自然语言描述您希望程序如何工作的过程会将您的注意力集中在关于它应该做什么以及应该如何工作的高级问题上。这可能会为您节省以后的很多精力。

  2. 您的文档是您代码质量的广告。 许多人将程序中糟糕、稀少或文盲的文档视为程序员草率或不关心潜在用户需求的迹象。另一方面,良好的文档传达了智慧和专业精神的信息。如果您的程序必须与其他程序竞争,最好确保您的文档至少与他们的文档一样好,以免潜在用户看都不看就将您排除在外。

即使这样做是可行的,本 HOWTO 也不是技术写作课程的地方。因此,我们将在这里重点介绍用于编写和呈现文档的格式和工具。

尽管 Unix 和开源社区长期以来一直托管强大的文档格式化工具,但不同格式的过多意味着文档往往是零散的,并且用户难以以连贯的方式浏览或索引。我们将总结常用文档格式的用途、优点和缺点。然后,我们将为良好实践提出一些建议。


8.1. 文档格式

以下是开源开发人员目前广泛使用的文档标记格式。当我们谈到“表示”标记时,我们指的是显式控制文档外观的标记(例如字体更改)。当我们谈到“结构”标记时,我们指的是描述文档逻辑结构的标记(如节断或强调标记)。当我们谈到“索引”时,我们指的是从文档集合中提取可搜索的主题指针集合的过程,用户可以使用这些指针来可靠地查找整个集合中感兴趣的材料。

man 页面

最常见的格式,继承自 Unix,一种原始的表示标记形式。man(1) 命令提供分页器和石器时代的搜索工具。不支持图像或超链接或索引。可以很好地渲染为 Postscript 以进行打印。完全无法很好地渲染为 HTML(本质上是纯文本)。工具预装在所有 Linux 系统上。

Man 页面格式对于命令摘要或旨在唤起经验丰富的用户记忆的简短参考文档来说还不错。对于具有复杂接口和许多选项的程序,它开始在压力下吱吱作响,如果您需要维护一组具有丰富交叉引用的文档,则它会完全崩溃(标记仅具有微弱且通常未使用的超链接支持)。

HTML

自 1993-1994 年 Web 爆炸以来,越来越常见。标记部分是结构的,大部分是表示的。可以通过任何 Web 浏览器浏览。对图像和超链接的良好支持。内置索引功能有限,但存在良好索引和搜索引擎技术并已广泛部署。可以很好地渲染为 Postscript 以进行打印。HTML 工具现在已普遍可用。

HTML 非常灵活,适用于多种类型的文档。实际上,它灵活了;它与 man 页面格式有相同的问题,即很难自动索引,因为很多标记描述的是表示而不是文档结构。

Texinfo

Texinfo 是自由软件基金会使用的文档格式。它是一组基于强大的 TeX 格式引擎的宏。主要是结构的,部分是表示的。可以通过 Emacs 或独立的 info 程序浏览。对超链接的良好支持,对图像不支持。对打印和在线形式的良好索引;当您安装 Texinfo 文档时,会自动将指向它的指针添加到可浏览的“dir”文档列表中,其中列出了系统上的所有 Texinfo 文档。可以渲染为优秀的 Postscript 和可用的 HTML。Texinfo 工具预装在大多数 Linux 系统上,并且可以在 自由软件基金会 网站上找到。

Texinfo 是一个好的设计,非常适合排版书籍以及小型在线文档,但像 HTML 一样,它是一种两栖动物——标记部分是结构的,部分是表示的,而表示部分为渲染带来了问题。

DocBook

DocBook 是一种基于 SGML 的大型、精细的标记格式(更新的版本基于 XML)。与此处描述的其他格式不同,它是完全结构的,没有表示标记。对图像和超链接的优秀支持。对索引的良好支持。可以很好地渲染为 HTML,可以接受地渲染为 Postscript 以进行打印(随着工具的发展,质量正在提高)。工具和文档可在 DocBook 网站 上找到。

DocBook 非常适合大型、复杂的文档;它专门设计用于支持技术手册并以多种输出格式渲染它们。它的主要缺点是它的冗长性。幸运的是,现在有好的工具和入门级文档可用;有关介绍,请参阅 DocBook Demystification HOWTO

asciidoc

DocBook 的一个严重缺点是其标记相当繁重且突兀。最近一个巧妙的解决方法是 AsciiDOC。此工具是 DocBook 的前端,具有更简单、更自然的输入语法。用户根本不需要了解 DocBook,但仍然可以获得这些工具的几乎全部功能。

AsciiDOC(通常以它附带的格式化程序的全部小写名称来称呼)最近在以前已迁移到 DocBook 的项目中得到了非常迅速的普及。


8.2. 良好实践建议

自 2000 年以来,文档实践一直在发生变化,当时一些关键的开源项目组(包括 Linux 内核项目、GNOME、KDE、自由软件基金会和 Linux 文档项目)就一种比传统 Unix 面向打印的工具更友好的 Web 方法达成了一致。自 2001 年中期 XML-DocBook 工具链达到生产状态以来,今天的最佳实践是这样的

  1. 将您的文档主文件维护在 XML-DocBook 或 asciidoc 中。即使您的 man 页面也可以是 DocBookRefEntry文档。有一个非常好的 HOWTO,介绍了编写手册页,其中解释了您的用户期望看到的章节和组织结构。

  2. 发布 XML 或 asciidoc 主文件。此外,为了以防用户的系统没有 xmlto(1)(自 7.3 以来所有 Red Hat 发行版上的标准配置),请发布您通过对主文件运行转换获得的 troff 源代码。您的软件发行版的安装过程应以正常方式安装这些文件,但如果人们想编写文档补丁,则应指导他们使用 XML/asciidoc 文件。

    很容易告诉 make(1) 使生成的 man 文件保持最新。只需在您的 makefile 中执行类似以下操作

    foo.1: foo.xml
    	xmlto man foo.xml
    

    如果您使用的是 asciidoc,则应使用类似这样的代码

    foo.1: foo.txt
    	asciidoc --backend=docbook foo.txt
    	xmlto man foo.xml
    
  3. 从您的主文件生成 XHTML(使用 xmlto xhtml,或直接使用 asciidoc)并使其可从您的项目网页获得,人们可以在其中浏览它,以决定是否下载您的代码并加入您的项目。

有关将 troff 格式的旧文档转换为 DocBook 的信息,请查看 doclifter。如果您不愿意放弃使用 man 源作为主格式,至少尝试清理它们,以便 doclifter 可以自动将它们提升为 XML。


9. 良好的沟通实践

如果除了您之外没有人知道您的软件和文档的存在,那么它们对世界没有多大好处。此外,在 Internet 上为项目建立可见的存在将有助于您招募用户和共同开发者。以下是执行此操作的标准方法。


9.1. 在 Freecode 上发布公告

请参阅 Freecode。发行版监视此频道以查看何时发布新版本。


9.2. 拥有一个网站

如果您打算围绕您的项目建立任何重要的用户或开发者社区,它应该有一个网站。网站上要有的标准内容包括

  • 项目章程(它存在的原因、受众是谁等)。

  • 项目源代码的下载链接。

  • 关于如何加入项目邮件列表的说明。

  • FAQ(常见问题解答)列表。

  • 项目文档的 HTML 版本

  • 指向相关和/或竞争项目的链接。

一些项目站点甚至具有用于匿名访问主源代码树的 URL。


9.3. 托管项目邮件列表

标准做法是拥有一个私有开发列表,项目协作者可以通过该列表进行沟通和交换补丁。您可能还需要一个公告列表,供想要随时了解项目流程的人员使用。

如果您正在运行一个名为“foo”的项目。您的开发者列表可能是 foo-dev 或 foo-friends;您的公告列表可能是 foo-announce。


9.4. 发布到主要存档站点

自 1999 年秋季推出以来,SourceForge 的受欢迎程度呈爆炸式增长。它不仅仅是一个存档和分发站点,尽管您可以将其用作该用途。它是一个完整的免费项目托管服务,旨在为开源开发团队提供一整套工具——Web 和存档空间、邮件列表、错误跟踪、聊天论坛、CVS 存储库和其他服务。

其他重要位置包括

  • Python 软件活动站点(适用于用 Python 编写的软件)。

  • CPAN,综合 Perl 存档网络(适用于用 Perl 编写的软件)。

  • githubgitorious,两个非常受欢迎的站点,托管免费 git 存储库访问。


10. 良好的项目管理实践

当所有参与者都是志愿者时,良好地管理项目会带来一些独特的挑战。这是一个太大的主题,无法在 HOWTO 中涵盖。幸运的是,有一些有用的白皮书可供使用,这将帮助您理解主要问题。

有关基本开发组织和“尽早发布,经常发布”的“集市模式”的讨论,请参阅 大教堂与集市

有关激励心理学、社区习俗和冲突解决的讨论,请参阅 开拓 Noosphere

有关经济学和适当的商业模式的讨论,请参阅 魔法大锅

这些论文不是关于开源开发的最终定论。但它们是第一批严肃的分析文章,并且尚未被取代(尽管作者希望它们有一天会被取代)。