GNU 编码标准

目录

下一节: , 上一节: (dir)   [目录][索引]

GNU 编码标准

GNU 编码标准,最后更新于 2024 年 5 月 26 日。

版权所有 © 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 自由软件基金会。

在 GNU 自由文档许可证 1.3 或自由软件基金会发布的任何后续版本的条款下,允许复制、分发和/或修改本文档;不包含不变章节、封面文字和封底文字。许可证副本包含在题为“GNU 自由文档许可证”的章节中。


下一节: , 上一节: , 上一节: 顶部   [目录][索引]

1 关于 GNU 编码标准

GNU 编码标准由 Richard Stallman 和其他 GNU 项目志愿者编写。其目的是使 GNU 系统清晰、一致且易于安装。本文档还可以作为编写可移植、健壮和可靠程序的指南。它侧重于用 C 语言编写的程序,但即使您使用其他编程语言编写,许多规则和原则也很有用。这些规则通常会说明以某种方式编写的原因。

如果您没有直接从 GNU 项目且最近获得此文件,请检查是否有较新版本。您可以从 GNU Web 服务器以多种不同格式获取 GNU 编码标准,包括 Texinfo 源代码、PDF、HTML、DVI、纯文本等,网址为:https://gnu.ac.cn/prep/standards/

如果您正在维护一个官方 GNU 包,除了本文档之外,请阅读并遵循 GNU 维护人员信息(请参阅 GNU 软件维护人员信息中的目录)。

如果您想接收这些 GNU 文档每次更改的差异,请通过 https://lists.gnu.org/mailman/listinfo/gnustandards-commit 上的 Web 界面加入邮件列表 [email protected]。档案也可用。

请将本文档的更正或建议发送至 [email protected]。如果您提出建议,请提供建议的新措辞,以帮助我们有效地考虑该建议。我们更喜欢 Texinfo 源代码的上下文差异,但如果您觉得困难,可以对本文档的某些其他版本进行上下文差异,或以任何明确的方式提出。本文档的源代码存储库位于 https://savannah.gnu.org/projects/gnustandards

这些标准涵盖了编写 GNU 包时最重要的最低限度要求。可能需要其他标准。有时,您可能会建议将此类标准添加到本文档中。如果您认为您的标准普遍有用,请提出建议。

您还应该为您的软件包设置这里未解决或未明确指定的许多问题的标准。最重要的一点是保持自身一致——尽量坚持您选择的约定,并尽量记录它们。这样,您的程序将更容易被其他人维护。

GNU Hello 程序是一个如何为一个简单的程序遵循 GNU 编码标准的示例。https://gnu.ac.cn/software/hello/hello.html

此版本的 GNU 编码标准最后更新于 2024 年 5 月 26 日。


下一节: , 上一节: , 上一节: 顶部   [目录][索引]

2 保持自由软件的自由

本章讨论如何确保 GNU 软件避免法律上的困难和其他相关问题。


下一节: , 上一节: 法律问题   [目录][索引]

2.1 引用专有程序

在任何情况下,都不要在您对 GNU 的工作中使用或引用 Unix 源代码!(或任何其他专有程序。)

如果您对 Unix 程序的内部结构有模糊的记忆,这并不绝对意味着您不能编写它的模仿版本,但请尽量在内部以不同的方式组织模仿版本,因为这可能会使 Unix 版本的细节变得无关紧要,并且与您的结果不同。

例如,Unix 实用程序通常经过优化以最大限度地减少内存使用;如果您追求速度而不是内存使用,您的程序将会非常不同。您可以将整个输入文件保存在内存中并在那里扫描,而不是使用 stdio。使用比 Unix 程序更新发现的更智能的算法。消除临时文件的使用。一次完成而不是两次(我们在汇编程序中这样做了)。

或者,相反,强调简单性而不是速度。对于某些应用程序,当今计算机的速度使更简单的算法足够用。

或者追求通用性。例如,Unix 程序通常具有静态表或固定大小的字符串,这会导致任意限制;请改为使用动态分配。确保您的程序能够处理输入文件中的 NUL 和其他有趣的字符。添加一种编程语言以实现可扩展性,并使用该语言编写部分程序。

或者将程序的某些部分转换为独立可用的库。或者使用简单的垃圾收集器,而不是精确跟踪何时释放内存,或者使用新的 GNU 工具(例如 obstack)。


下一节: , 上一节: , 上一节: 法律问题   [目录][索引]

2.2 接受贡献

如果您正在处理的程序由自由软件基金会拥有版权,那么当其他人向您发送一段代码以添加到该程序时,我们需要法律文件才能使用它——就像我们最初要求您签署文件一样。每个 对程序做出重要贡献的人都必须签署某种法律文件,以便我们拥有该程序的明确所有权;仅有主要作者是不够的。

因此,在添加任何其他人的贡献之前,请告诉我们,以便我们安排获得文件。然后,请等待我们告知您我们已收到已签署的文件,然后您才能实际使用该贡献。

这适用于发布程序之前和之后。如果您收到用于修复错误的差异,并且它们进行了重大更改,我们需要该更改的法律文件。

这也适用于注释和文档文件。根据版权法,注释和代码只是文本。版权适用于所有类型的文本,因此我们需要所有类型的法律文件。

我们知道要求提供法律文件令人沮丧;这对我们来说也令人沮丧。但是,如果您不等待,您就冒险了——例如,如果贡献者的雇主不签署免责声明怎么办?您可能必须再次删除该代码!

对于此处或那里的几行更改,您不需要文件,因为它们对于版权目的而言并不重要。此外,如果您从建议中获得的所有内容是一些想法,而不是您使用的实际代码,则无需文件。例如,如果有人向您发送了一个实现,但您编写了相同想法的不同实现,则无需获取文件。

最糟糕的情况是您忘记告诉我们其他贡献者。将来有一天,我们可能会因此在法庭上感到非常尴尬。

我们为 GNU 软件包的维护人员提供了更详细的建议。如果您已达到维护 GNU 程序(无论是否已发布)的阶段,请查看:请参阅GNU 维护人员信息中的法律事项


上一篇:,上一级:法律问题   [目录][索引]

2.3 商标

请不要在 GNU 软件包或文档中包含任何商标声明。

商标声明是指诸如“某某是某某的商标”之类的语句。GNU 项目并不反对商标的基本概念,但是这些声明感觉像是卑躬屈膝,而且法律上也没有要求这样做,所以我们不使用它们。

在法律方面,关于他人的商标,需要避免以读者可能合理理解为命名或标记我们自己的程序或活动的方式使用它们。例如,由于“Objective C”是(或至少曾经是)一个商标,我们确保说我们提供的是“Objective C 语言的编译器”,而不是“Objective C 编译器”。后者本意是前者的简短表达方式,但它没有明确说明这种关系,因此可能会被误解为将“Objective C”用作编译器的标签,而不是语言的标签。

请不要在 GNU 软件或文档中使用“win”作为 Microsoft Windows 的缩写。在黑客术语中,称某物为“win”是一种赞美。如果你愿意,你可以在你自己的空间里赞美 Microsoft Windows,但请不要在 GNU 软件包中这样做。请完整地写出“Windows”,或将其缩写为“w.”。请参阅 系统可移植性


下一篇:,上一篇:,上一级:顶部   [目录][索引]

3 通用程序设计

本章讨论在设计程序时应考虑的一些问题。


下一篇:,上一级:设计建议   [目录][索引]

3.1 使用哪种语言

当你想使用一种编译后以高速运行的语言时,最好的语言是 C。C++ 也可以,但请不要大量使用模板。Java 也可以,如果你编译它的话。

当不需要最高效率时,自由软件社区中常用的其他语言,如 Lisp、Scheme、Python、Ruby 和 Java 也可以。Scheme,由 GNU Guile 实现,在 GNU 系统中扮演着特殊的角色:它是扩展用 C/C++ 编写的程序的首选语言,也是各种应用程序的优秀语言。使用 Guile 和 Scheme 的 GNU 组件越多,用户就越能扩展和组合它们(参见《Emacs 论题》,出自 GNU Guile 参考手册)。

许多程序被设计为可扩展的:它们包含一个比 C 更高级的语言的解释器。通常,程序的很大一部分也是用该语言编写的。Emacs 编辑器开创了这种技术。

GNU 软件的标准可扩展性解释器是 Guile(https://gnu.ac.cn/software/guile/),它实现了 Scheme 语言(Lisp 的一种特别干净和简单的方言)。Guile 还包括 GTK+/GNOME 的绑定,使得在 Guile 中编写现代 GUI 功能成为可能。我们不拒绝用其他“脚本语言”(如 Perl 和 Python)编写的程序,但使用 Guile 是实现 GNU 系统整体一致性的途径。


下一篇:,上一篇:,上一级:设计建议   [目录][索引]

3.2 与其他实现的兼容性

在偶尔的例外情况下,GNU 的实用程序和库应与 Berkeley Unix 中的实用程序和库向上兼容,如果标准 C 指定了它们的行为,则应与标准 C 向上兼容,如果 POSIX 指定了它们的行为,则应与 POSIX 向上兼容。

当这些标准发生冲突时,为每个标准提供兼容模式是很有用的。

标准 C 和 POSIX 禁止许多类型的扩展。你可以随意进行扩展,并包含一个 ‘--ansi’、‘--posix’ 或 ‘--compatible’ 选项来关闭它们。但是,如果扩展有很大可能破坏任何实际的程序或脚本,那么它就不是真正的向上兼容。因此,你应该尝试重新设计它的接口,使其向上兼容。

如果定义了环境变量 POSIXLY_CORRECT(即使它定义为空值),许多 GNU 程序会抑制与 POSIX 冲突的扩展。如果合适,请让你的程序识别此变量。

当一个特性只被用户使用(而不是被程序或命令文件使用),并且它在 Unix 中做得不好时,可以随意用完全不同且更好的东西完全替换它。(例如,vi 被 Emacs 替换。)但提供一个兼容的特性也是不错的。(有一个免费的 vi 克隆,所以我们提供它。)

无论是否有先例,都欢迎其他有用的特性。


下一篇:,上一篇:,上一级:设计建议   [目录][索引]

3.3 使用非标准特性

许多现有的 GNU 工具都支持许多方便的扩展,这些扩展比得上 Unix 工具。是否在实现你的程序时使用这些扩展是一个难题。

一方面,使用扩展可以使程序更简洁。另一方面,除非其他 GNU 工具可用,否则人们将无法构建该程序。这可能会导致该程序在更少的机器上工作。

对于某些扩展,可能很容易提供两种选择。例如,你可以使用“关键字” INLINE 定义函数,并将该关键字定义为宏,根据编译器将其扩展为 inline 或空。

一般来说,如果你可以简单地不使用它们,最好不要使用这些扩展,但如果它们有很大的改进,则可以使用这些扩展。

该规则的一个例外是大型的、已建立的程序(如 Emacs),这些程序在各种系统上运行。在这些程序中使用 GNU 扩展会使许多用户不高兴,所以我们不这样做。

另一个例外是作为编译一部分使用的程序:任何必须与其他编译器一起编译才能引导 GNU 编译工具的程序。如果这些程序需要 GNU 编译器,那么没有人可以在没有安装它们的情况下编译它们。在某些情况下,这将非常麻烦。


下一篇:,上一篇:,上一级:设计建议   [目录][索引]

3.4 标准 C 和前标准 C

1989 年的标准 C 已经足够普及,现在可以在程序中使用它的特性了。有一个例外:永远不要使用标准 C 的“三字母词”特性。

1999 年和 2011 年的标准 C 版本并非在所有平台上都完全支持。如果你旨在支持除 GCC 之外的编译器进行编译,则不应在程序中要求这些 C 特性。当编译器支持这些特性时,可以有条件地使用它们。

如果你的程序仅用于使用 GCC 进行编译,那么当它们提供实质性的好处时,你可以在 GCC 支持它们的情况下使用这些特性。

但是,在大多数程序中支持预标准编译器很容易,因此如果你知道如何做到这一点,请随意这样做。

为了支持预标准 C,而不是以标准原型形式编写函数定义,

int
foo (int x, int y)
…

请以预标准样式编写定义,如下所示,

int
foo (x, y)
     int x, y;
…

并使用单独的声明来指定参数原型

int foo (int, int);

无论如何,你都需要在头文件中进行这样的声明,以便在调用该函数的所有文件中获得原型的优势。一旦你有了声明,通常以预标准样式编写函数定义不会有任何损失。

此技术不适用于比 int 更窄的整数类型。如果将参数视为比 int 更窄的类型,请将其声明为 int

在某些特殊情况下,此技术很难使用。例如,如果函数参数需要保存系统类型 dev_t,则会遇到麻烦,因为在某些机器上 dev_tint 短;但是你不能使用 int 代替,因为在某些机器上 dev_tint 宽。在非标准定义中,没有可以在所有机器上安全使用的类型。支持非标准 C 并传递此类参数的唯一方法是使用 Autoconf 检查 dev_t 的宽度,并相应地选择参数类型。这可能不值得费力。

为了支持不识别原型的预标准编译器,你可能需要使用类似这样的预处理器宏

/* Declare the prototype for a general external function.  */
#if defined (__STDC__) || defined (WINDOWSNT)
#define P_(proto) proto
#else
#define P_(proto) ()
#endif

上一篇:,上一级:设计建议   [目录][索引]

3.5 条件编译

当支持构建程序时已知的配置选项时,我们更喜欢使用 if (... ) 而不是条件编译,因为在前一种情况下,编译器能够对所有可能的代码路径执行更广泛的检查。

例如,请编写

  if (HAS_FOO)
    ...
  else
    ...

而不是

  #ifdef HAS_FOO
    ...
  #else
    ...
  #endif

像 GCC 这样的现代编译器将在两种情况下生成完全相同的代码,并且我们已经在多个项目中成功地使用了类似的技术。当然,前一种方法假设 HAS_FOO 定义为 0 或 1。

虽然这不是解决所有可移植性问题的灵丹妙药,并且并非总是合适,但遵循此策略将使 GCC 开发人员每年节省许多小时,甚至几天的时间。

对于像 GCC 中的 REVERSIBLE_CC_MODE 这样的类似函数的宏,不能简单地在 if (...) 语句中使用,有一个简单的解决方法。只需引入另一个宏 HAS_REVERSIBLE_CC_MODE,如下例所示

  #ifdef REVERSIBLE_CC_MODE
  #define HAS_REVERSIBLE_CC_MODE 1
  #else
  #define HAS_REVERSIBLE_CC_MODE 0
  #endif

下一篇:,上一篇:,上一级:顶部   [目录][索引]

4 所有程序的程序行为

本章介绍编写健壮软件的约定。它还介绍了错误消息、命令行界面以及库应如何行为的一般标准。


下一个:, 上一个:程序行为   [目录][索引]

4.1 非 GNU 标准

GNU 项目将其他组织发布的标准视为建议,而不是命令。我们会考虑这些标准,但不会“遵守”它们。在开发 GNU 程序时,当遵守外部标准规范能够从客观上使 GNU 系统整体更好时,你应该实现它。如果不能,就不应该这样做。

在大多数情况下,遵循已发布的标准对用户来说是方便的——这意味着他们的程序或脚本将更具可移植性。例如,GCC 实现了标准 C 规范中几乎所有的特性。如果它没有这样做,C 程序开发者会感到不满意。而 GNU 工具大多遵循 POSIX.2 的规范;如果我们的程序不兼容,shell 脚本编写者和用户会感到不满意。

但我们并没有严格遵守这些规范中的任何一个,并且在某些特定方面,我们决定不遵循它们,以便使 GNU 系统对用户更好。

例如,标准 C 说几乎所有的 C 扩展都被禁止。多么愚蠢!GCC 实现了许多扩展,其中一些后来被采纳为标准的一部分。如果你想让这些结构按照标准“要求”给出错误消息,你必须指定 ‘--pedantic’,这个选项的实现仅仅是为了我们可以说“GCC 是标准的 100% 实现”,而不是因为有任何实际使用它的理由。

POSIX.2 规定 ‘df’ 和 ‘du’ 默认情况下必须以 512 字节为单位输出大小。用户想要的是以 1k 为单位,所以这是我们默认的做法。如果你想要 POSIX“要求”的荒谬行为,你必须设置环境变量 ‘POSIXLY_CORRECT’(最初打算命名为 ‘POSIX_ME_HARDER’)。

当 GNU 工具支持长名称的命令行选项,以及将选项与普通参数混合使用时,它们也偏离了 POSIX.2 规范的字面意思。这种与 POSIX 的轻微不兼容在实践中从来都不是问题,而且非常有用。

特别是,不要仅仅因为某个标准说某个新特性是“禁止的”或“已弃用的”就拒绝它,或者删除一个旧特性。


下一个:, 上一个:, 上一个:程序行为   [目录][索引]

4.2 编写健壮的程序

避免对 任何 数据结构(包括文件名、行、文件和符号)的长度或数量设置任意限制,通过动态分配所有数据结构来实现。在大多数 Unix 工具中,“长行会被静默截断”。这在 GNU 工具中是不可接受的。

读取文件的工具不应丢弃 NUL 字符或任何其他非打印字符。程序应使用多字节字符编码(如 UTF-8)正确工作。你可以使用 libiconv 来处理各种编码。

检查每次系统调用的错误返回,除非你明确希望忽略错误。在每次系统调用失败导致的每个错误消息中,都包含系统错误文本(来自 strerror 或等效函数),以及文件名(如果有)和工具的名称。仅仅 “无法打开 foo.c” 或 “stat 失败” 是不够的。

检查每次对 mallocrealloc 的调用,看它是否返回 NULL。即使你正在缩小块的大小,也要检查 realloc;在一个将块大小四舍五入为 2 的幂的系统中,如果你要求更少的空间,realloc 可能会得到一个不同的块。

你必须预期 free 会改变已释放的块的内容。任何你想从该块获取的东西,你必须在调用 free 之前获取。

如果 malloc 在非交互式程序中失败,则将其设为致命错误。在交互式程序(从用户读取命令的程序)中,最好中止该命令并返回到命令读取循环。这允许用户杀死其他进程以释放虚拟内存,然后再次尝试该命令。

使用 getopt_long 来解码参数,除非参数语法使其不合理。

当在程序执行期间写入静态存储时,使用显式的 C 代码来初始化它。这样,重新启动程序(不重新加载它)或其中的一部分将重新初始化这些变量。为不会更改的数据保留 C 初始化声明。

尽量避免使用底层接口来访问晦涩的 Unix 数据结构(如文件目录、utmp 或内核内存的布局),因为这些接口不太可能兼容地工作。如果你需要查找目录中的所有文件,请使用 readdir 或其他一些高级接口。GNU 兼容地支持这些接口。

首选的信号处理工具是 BSD 版本的 signal,以及 POSIX sigaction 函数;另一种 USG signal 接口是一种劣等设计。

如今,使用 POSIX 信号函数可能是使程序可移植的最简单方法。如果你使用 signal,那么在运行 GNU libc 版本 1 的 GNU/Linux 系统上,你应该包含 bsd/signal.h 而不是 signal.h,以便获得 BSD 行为。是否支持 signal 仅具有 USG 行为的系统,或者放弃它们,由你决定。

在检测“不可能”条件的错误检查中,只需中止即可。通常没有必要打印任何消息。这些检查表明存在错误。任何想要修复这些错误的人都必须阅读源代码并运行调试器。因此,在源代码中使用注释来解释问题。相关数据将在变量中,这些变量很容易用调试器检查,因此没有必要将它们移动到其他地方。

不要使用错误计数作为程序的退出状态。那不起作用,因为退出状态值限制为 8 位(0 到 255)。程序的单次运行可能产生 256 个错误;如果你尝试返回 256 作为退出状态,则父进程将看到 0 作为状态,并且看起来该程序已成功。

如果你创建临时文件,请检查 TMPDIR 环境变量;如果定义了该变量,则使用指定的目录而不是 /tmp

此外,请注意,在全局可写目录中创建临时文件时,可能存在安全问题。在 C 中,你可以通过以下方式创建临时文件来避免此问题

fd = open (filename, O_WRONLY | O_CREAT | O_EXCL, 0600);

或者使用来自 Gnulib 的 mkstemps 函数(请参阅 Gnulib 中的 mkstemps)。

在 bash 中,使用 set -C(长名称 noclobber)来避免此问题。此外,mktemp 工具是从 shell 脚本创建临时文件的更通用的解决方案(请参阅 GNU Coreutils 中的 mktemp 调用)。


下一个:, 上一个:, 上一个:程序行为   [目录][索引]

4.3 库的行为

尽量使库函数是可重入的。如果它们需要进行动态存储分配,至少尽量避免 malloc 本身之外的任何不可重入性。

以下是库的一些命名约定,以避免名称冲突。

为库选择一个超过两个字符长的名称前缀。所有外部函数和变量名称都应以此前缀开头。此外,在任何给定的库成员中,应该只有一个这样的前缀。这通常意味着将每个前缀放在单独的源文件中。

当两个外部符号总是一起使用时可以例外,因此没有任何合理的程序会在没有另一个的情况下使用一个;然后它们可以都放在同一个文件中。

未记录为用户入口点的外部符号应以 ‘_’ 开头。 ‘_’ 后面应跟库的所选名称前缀,以防止与其他库冲突。如果需要,这些可以与用户入口点放在同一个文件中。

静态函数和变量可以随意使用,不需要符合任何命名约定。


下一个:, 上一个:, 上一个:程序行为   [目录][索引]

4.4 格式化错误消息

来自编译器的错误消息应如下所示

sourcefile:lineno: message

如果要提及列号,请使用以下格式之一

sourcefile:lineno:column: message
sourcefile:lineno.column: message

行号应从文件开头的 1 开始,列号应从行开头的 1 开始。(这两个约定都是为兼容性而选择的。)计算列号时,假设空格和所有 ASCII 打印字符具有相同的宽度,并假设制表符每 8 列停止一次。对于非 ASCII 字符,当在 UTF-8 区域设置中时,应使用 Unicode 字符宽度;GNU libc 和 GNU gnulib 提供了合适的 wcwidth 函数。

错误消息还可以给出错误文本的起始和结束位置。存在多种格式,以便您可以避免冗余信息,例如重复的行号。以下是可能的格式:

sourcefile:line1.column1-line2.column2: message
sourcefile:line1.column1-column2: message
sourcefile:line1-line2: message

当错误跨越多个文件时,您可以使用此格式:

file1:line1.column1-file2:line2.column2: message

来自其他非交互式程序的错误消息应如下所示:

program:sourcefile:lineno: message

当存在合适的源文件时,或者如下所示:

program: message

当没有相关的源文件时。

如果您想提及列号,请使用此格式:

program:sourcefile:lineno:column: message

在交互式程序(从终端读取命令的程序)中,最好不要在错误消息中包含程序名称。指示哪个程序正在运行的地方应该在提示符或屏幕布局中。(当同一个程序从终端以外的源接收输入时,它不是交互式的,最好使用非交互式风格打印错误消息。)

当字符串 message 跟在程序名称和/或文件名之后时,不应以大写字母开头,因为这不是句子的开头。(这个句子在概念上是从行的开头开始的。)此外,它不应以句点结尾。

来自交互式程序的错误消息以及其他消息(例如使用说明消息)应以大写字母开头。但它们不应以句点结尾。


下一项:,上一项:,上一级:程序行为   [目录][索引]

4.5 通用接口标准

请不要使实用程序的行为依赖于用于调用它的名称。有时使用不同的名称创建指向实用程序的链接很有用,并且不应更改它的功能。因此,如果您使 foo 成为指向 ls 的链接,则无论使用哪个名称调用该程序,其行为都应相同。

而是使用运行时选项或编译开关或两者都用来选择备用行为。您还可以构建两个版本的程序,具有不同的默认行为,并将它们以两个不同的名称安装。

同样,请不要使命令行程序的行为依赖于它获得的作为标准输出或标准输入的输出设备的类型。设备独立性是系统设计的一个重要原则;不要仅仅为了让某人偶尔少输入一个选项而损害它。(使用终端时错误消息语法的变化是可以的,因为这是一个人们不依赖的次要问题。)

如果您认为当输出到终端时一种行为最有用,而当输出到文件或管道时另一种行为最有用,那么通常最好使默认行为是输出到终端时有用的行为,并为另一种行为提供一个选项。您还可以构建两个具有不同名称的不同版本的程序。

对于在某些情况下输出为二进制数据的程序,有一个例外。将此类输出发送到终端是无用的,并且可能导致问题。如果这样的程序通常将其输出发送到 stdout,则在这些情况下,它应该检测到输出是终端并改为给出错误消息。-f 选项应覆盖此异常,从而允许输出转到终端。

兼容性要求某些程序依赖于输出设备的类型。如果 lssh 不按照所有用户期望的方式进行操作,那将是灾难性的。在某些情况下,我们为程序补充一个首选的替代版本,该版本不依赖于输出设备类型。例如,我们提供了一个 dir 程序,它很像 ls,只是它的默认输出格式始终是多列格式。


下一项:,上一项:,上一级:程序行为   [目录][索引]

4.6 查找程序的可执行文件和关联文件

程序可能需要找到它启动的可执行文件,以便重新启动同一个程序。它可能需要找到相关文件,无论是源文件还是构建时构造的文件,它在运行时使用这些文件。

查找它们的方法首先是查看 argv[0]

如果该字符串包含斜杠,则按照约定,它是可执行文件的文件名,并且其目录部分是包含可执行文件的目录。当程序不是通过 PATH 找到时,通常是这种情况,这通常意味着它已构建但未安装,并从构建目录运行。程序可以使用 argv[0] 文件名重新启动自身,并且可以在其目录部分查找相关文件。如果该文件名不是绝对的,则它是相对于程序启动时的工作目录的。

如果 argv[0] 不包含斜杠,则它是一个命令名称,其可执行文件是通过 PATH 找到的。程序应在 PATH 中的目录中搜索该名称,将 . 解释为程序启动时当前的工作目录。

如果此过程找到可执行文件,我们将其找到的目录称为调用目录。程序应检查该目录中是否存在它需要的相关文件。

如果程序的 可执行文件通常构建在主构建目录的子目录中,并且主构建目录包含相关文件(可能包括子目录),则程序应查看调用目录的父目录,检查主构建目录应包含的相关文件和子目录。

如果调用目录不包含所需内容,但可执行文件名是符号链接,则程序应尝试使用链接目标的包含目录作为调用目录。

如果此过程没有找到有效的调用目录(通常对于通过 PATH 找到的已安装程序是这种情况),则程序应在程序的 makefile 安装它们的目录中查找相关文件。请参阅目录变量

argv[0] 中提供有效信息是一种约定,而不是保证。行为良好的启动其他程序的程序(如 shell)遵循该约定;您的代码在启动其他程序时也应遵循该约定。但是,始终可以启动程序并在 argv[0] 中提供一个无意义的值。

因此,任何需要知道其可执行文件或其其他关联文件的位置的程序都应提供用户环境变量来显式指定这些位置。

不要向以这种方式调用时会启发式搜索相关文件或自身可执行文件的程序授予特殊权限,例如使用 setuid 位。将该权限限制为在硬编码的安装位置(例如 /usr/etc 下)查找相关文件的程序。

有关 PATH 的更多信息,请参阅Bash 参考手册中的Bourne Shell 变量


下一项:,上一项:,上一级:程序行为   [目录][索引]

4.7 图形界面标准

当您编写提供图形用户界面的程序时,请使其与 X Window System 一起工作,使用 GTK+ 工具包或 GNUstep 工具包,除非该功能明确要求其他替代方案(例如,“在控制台模式下显示 jpeg 图像”)。

此外,请提供一个命令行界面来控制该功能。(在许多情况下,图形用户界面可以是一个单独的程序,它调用命令行程序。)这是为了可以从脚本中完成相同的工作。

还请考虑提供一个 D-bus 接口,以便从其他正在运行的程序中使用,例如在 GNOME 中。(GNOME 过去使用 CORBA 来实现此目的,但现在正在逐步淘汰。)此外,请考虑提供一个库接口(供 C 使用),以及一个键盘驱动的控制台界面(供用户从控制台模式使用)。一旦您完成了提供功能和图形界面的工作,这些就不会是额外的工作。

请使您的程序与屏幕阅读器等访问技术进行互操作(请参阅 https://gnu.ac.cn/accessibility/accessibility.html)。如果您使用 GTK+,这应该是自动的。


下一项:,上一项:,上一级:程序行为   [目录][索引]

4.8 命令行界面标准

遵循 POSIX 关于程序命令行选项的指导原则是一个好主意。执行此操作的最简单方法是使用 getopt 来解析它们。请注意,除非使用特殊参数 ‘--’,否则 GNU 版本的 getopt 通常允许在参数中的任何位置使用选项。这不是 POSIX 指定的;它是 GNU 扩展。

请定义与单字母 Unix 风格选项等效的长名称选项。我们希望通过这种方式使 GNU 更易于用户使用。使用 GNU 函数 getopt_long 可以轻松完成此操作。

长名称选项的优点之一是它们可以在程序之间保持一致。例如,用户应该能够期望任何拥有“verbose”选项的 GNU 程序的“verbose”选项都精确地拼写为 ‘--verbose’。为了实现这种统一性,请在为您的程序选择选项名称时查看常用长选项名称的表(请参阅选项表)。

通常,将作为普通参数给出的文件名仅作为输入文件是一个好主意;任何输出文件都将使用选项(最好是 ‘-o’ 或 ‘--output’)指定。即使您允许将输出文件名作为普通参数以实现兼容性,也请尝试提供一个选项作为指定它的另一种方式。这将导致 GNU 实用程序之间更加一致,并减少用户需要记住的特殊之处。

所有程序都应支持两个标准选项:‘--version’ 和 ‘--help’。CGI 程序应接受这些作为命令行选项,如果作为 PATH_INFO 给出,也应接受;例如,在浏览器中访问 ‘http://example.org/p.cgi/--help’ 应输出与从命令行调用 ‘p.cgi --help’ 相同的信息。


下一项:,上一级:命令行界面   [目录][索引]

4.8.1 --version

标准的 --version 选项应指示程序在标准输出上打印有关其名称、版本、来源和法律状态的信息,然后成功退出。一旦看到此选项,其他选项和参数都应被忽略,并且程序不应执行其正常功能。

第一行旨在便于程序解析;版本号本身在最后一个空格后开始。此外,它包含此程序的规范名称,格式如下:

GNU Emacs 19.30

程序的名称应该是一个常量字符串;不要argv[0] 计算得出。 这里的想法是声明程序的标准或规范名称,而不是它的文件名。 还有其他方法可以找出命令在 PATH 中找到的精确文件名。

如果程序是一个大型软件包的附属部分,请在括号中提及软件包名称,例如这样

emacsserver (GNU Emacs) 19.30

如果软件包的版本号与此程序的版本号不同,您可以在右括号之前提及软件包的版本号。

如果您需要提及与包含此程序的软件包分开分发的库的版本号,您可以通过为要提及的每个库打印额外的版本信息行来实现。 这些行的格式与第一行相同。

请不要“为了完整性”而提及程序使用的所有库——这会产生大量无益的混乱。 只有当您在实践中发现库的版本号对调试非常重要时,才提及它。

在版本号行之后,接下来的行应为版权声明。 如果需要多个版权声明,请将每个声明放在单独的行上。

接下来应该是一行声明许可的行,最好使用下面的缩写之一,并简要说明该程序是自由软件,用户可以自由复制和更改它。 还要提到在法律允许的范围内不提供任何保证。 请参阅下面推荐的措辞。

可以以列出程序的主要作者的方式来结束输出,作为一种给予认可的方式。

以下是遵循这些规则的输出示例

GNU hello 2.3
Copyright (C) 2007 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

当然,您应该根据您的程序进行调整,填写正确的年份、版权所有者、程序名称以及对分发条款的引用,并在必要时更改其余措辞。

此版权声明只需要提及最近进行更改的年份——无需列出以前版本更改的年份。 如果不方便,您不必在这些声明中提及程序名称,因为它已出现在第一行。(源代码文件中的版权声明规则不同;请参阅版权声明,在GNU 维护者信息中。)

以上行的翻译必须保留版权声明的有效性(请参阅国际化)。 如果翻译的字符集支持,则应将‘(C)’替换为版权符号,如下所示

©

用英语准确地写出“Copyright”一词。 不要将其翻译成其他语言。 国际条约承认英文单词“Copyright”; 翻译成其他语言不具有法律意义。

最后,这是我们建议的许可缩写表。 任何缩写都可以后跟‘vversion[+]’,表示该特定版本或带有‘+’的更高版本,如上所示。 对于 GNU 许可,请始终以这种方式指示允许的版本。

对于 GPL 的额外许可例外情况,我们使用‘/’作为分隔符; 版本号可以像往常一样跟在许可缩写之后,如下面的示例所示。

GPL

GNU 通用公共许可,https://gnu.ac.cn/licenses/gpl.html

LGPL

GNU 宽通用公共许可,https://gnu.ac.cn/licenses/lgpl.html

GPL/Ada

带有 Ada 例外的 GNU GPL。

Apache

Apache 软件基金会许可,https://directory.fsf.org/wiki/License:Apache2.0

Artistic

用于 Perl 的 Artistic 许可,https://directory.fsf.org/wiki/License:ArtisticLicense2.0

Expat

Expat 许可,https://directory.fsf.org/wiki/License:Expat

MPL

Mozilla 公共许可,https://directory.fsf.org/wiki/License:MPLv2.0

OBSD

最初的(4 条款)BSD 许可,与 GNU GPL 不兼容,
https://directory.fsf.org/wiki/License:BSD_4Clause.

PHP

用于 PHP 的许可,https://directory.fsf.org/wiki/License:PHPv3.01

public domain

属于公有领域的非许可,
https://gnu.ac.cn/licenses/license-list.html#PublicDomain.

Python

Python 的许可,https://directory.fsf.org/wiki/License:Python2.0.1

RBSD

修订的(3 条款)BSD,与 GNU GPL 兼容,
https://directory.fsf.org/wiki/License:BSD_3Clause.

X11

用于大多数 X Window System 版本的简单非 Copyleft 许可,https://directory.fsf.org/wiki/License:X11

Zlib

Zlib 的许可,https://directory.fsf.org/wiki/License:Zlib

有关这些许可和更多许可的更多信息,请访问 GNU 许可网页,https://gnu.ac.cn/licenses/license-list.html


上一页:,上一级:命令行界面   [目录][索引]

4.8.2 --help

标准的 --help 选项应在标准输出上输出关于如何调用程序的简短文档,然后成功退出。 一旦看到此选项,应忽略其他选项和参数,并且程序不应执行其正常功能。

在 ‘--help’ 选项输出的末尾附近,请放置提供错误报告的电子邮件地址、软件包的主页(通常是 ‘https://gnu.ac.cn/software/pkg’)以及使用 GNU 程序的通用帮助页面的行。 格式应如下所示

Report bugs to: mailing-address
pkg home page: <https://gnu.ac.cn/software/pkg/>
General help using GNU software: <https://gnu.ac.cn/gethelp/>

可以提及其他合适的邮件列表和网页。


下一页:,上一页:,上一级:程序行为   [目录][索引]

4.9 动态插件接口标准

保持自由程序自由的另一个方面是鼓励开发自由插件,并阻止开发专有插件。 许多 GNU 程序根本没有任何类似插件的东西,但有插件的程序应遵循这些做法。

首先,通用插件架构设计应将插件与原始代码紧密联系起来,从而使插件和基本程序成为一个扩展程序的一部分。 例如,对于 GCC,插件接收和修改 GCC 的内部数据结构,因此显然与基本 GCC 形成一个扩展程序。

其次,您应该要求插件开发人员确认他们的插件是在适当的许可下发布的。 这应该通过一个简单的编程检查来强制执行。 例如,对于 GCC,插件必须定义全局符号 plugin_is_GPL_compatible,从而断言该插件是在与 GPL 兼容的许可下发布的(请参阅 插件,在 GCC 内部 中)。

通过向您的程序添加此检查,您并没有创建新的法律要求。 GPL 本身要求插件必须是自由软件,并以兼容的方式许可。 只要您遵循了上面的第一条规则,使插件与原始程序紧密联系,GPL 和 AGPL 已经要求这些插件在兼容的许可下发布。 插件中的符号定义(或在您的程序中最有效的任何等效项)使任何可能分发专有插件的人更难在法律上为自己辩护。 如果关于此事的案件提交到法庭,我们可以指出该符号作为证据,证明插件开发人员理解该许可具有此要求。


下一页:,上一页:,上一级:程序行为   [目录][索引]

4.10 长选项表

这是 GNU 程序使用的长选项表。 这当然是不完整的,但我们的目标是列出新程序可能希望兼容的所有选项。 如果您使用表中尚未存在的名称,请向 [email protected] 发送一个包含其含义的列表,以便我们可以更新该表。

after-date

tar 中的 ‘-N’。

all

dulsnmsttyunameunexpand 中的 ‘-a’。

all-text

diff 中的 ‘-a’。

almost-all

ls 中的 ‘-A’。

append

etagsteetime 中的 ‘-a’;tar 中的 ‘-r’。

archive

cp 中的 ‘-a’。

archive-name

shar 中的 ‘-n’。

arglength

m4 中的 ‘-l’。

ascii

diff 中的 ‘-a’。

assign

gawk 中的 ‘-v’。

assume-new

make 中的 ‘-W’。

assume-old

make 中的 ‘-o’。

auto-check

recode 中的 ‘-a’。

auto-pager

wdiff 中的 ‘-a’。

auto-reference

ptx 中的 ‘-A’。

avoid-wraps

wdiff 中的 ‘-n’。

background

对于服务器程序,在后台运行。

backward-search

ctags 中的 ‘-B’。

basename

shar 中的 ‘-f’。

batch

在 GDB 中使用。

baud

在 GDB 中使用。

before

tac 中的 ‘-b’。

binary

cpiodiff 中的 ‘-b’。

bits-per-code

shar 中的 ‘-b’。

block-size

cpiotar 中使用。

blocks

headtail 中的 ‘-b’。

break-file

ptx 中的 ‘-b’。

brief

在各种程序中使用,使输出更短。

bytes

headsplittail 中的 ‘-c’。

c++

etags 中的 ‘-C’。

catenate

tar 中的 ‘-A’。

cd

在各种程序中使用,以指定要使用的目录。

changes

chgrpchown 中的 ‘-c’。

classify

ls 中的 ‘-F’。

colons

recode 中的 ‘-c’。

command

su 中的 ‘-c’;GDB 中的 ‘-x’。

compare

tar 中的 ‘-d’。

compat

gawk 中使用。

compress

tarshar 中的 ‘-Z’。

concatenate

tar 中的 ‘-A’。

confirmation

tar 中的 ‘-w’。

context

diff 中使用。

copyleft

gawk 中的 ‘-W copyleft’。

copyright

ptxrecodewdiff 中的 ‘-C’;gawk 中的 ‘-W copyright’。

core

在 GDB 中使用。

count

who 中的 ‘-q’。

count-links

du 中的 ‘-l’。

create

tarcpio 中使用。

cut-mark

shar 中的 ‘-c’。

cxref

ctags 中的 ‘-x’。

date

touch 中的 ‘-d’。

debug

makem4 中的 ‘-d’;Bison 中的 ‘-t’。

define

m4 中的 ‘-D’。

defines

Bison 和 ctags 中的 ‘-d’。

delete

tar 中的 ‘-D’。

dereference

chgrpchowncpiodulstar 中的 ‘-L’。

dereference-args

du 中的 ‘-D’。

device

指定一个 I/O 设备(特殊文件名)。

diacritics

recode 中的 ‘-d’。

dictionary-order

look 中的 ‘-d’。

diff

tar 中的 ‘-d’。

digits

csplit 中的 ‘-n’。

directory

在各种程序中,指定要使用的目录。在 ls 中,它表示显示目录本身,而不是它们的内容。在 rmln 中,它表示不要特殊对待指向目录的链接。

discard-all

strip 中的 ‘-x’。

discard-locals

strip 中的 ‘-X’。

dry-run

make 中的 ‘-n’。

ed

diff 中的 ‘-e’。

elide-empty-files

csplit 中的 ‘-z’。

end-delete

wdiff 中的 ‘-x’。

end-insert

wdiff 中的 ‘-z’。

entire-new-file

diff 中的 ‘-N’。

environment-overrides

make 中的 ‘-e’。

eof

xargs 中的 ‘-e’。

epoch

在 GDB 中使用。

error-limit

makeinfo 中使用。

error-output

m4 中的 ‘-o’。

escape

ls 中的 ‘-b’。

exclude-from

tar 中的 ‘-X’。

exec

在 GDB 中使用。

exit

xargs 中的 ‘-x’。

exit-0

unshar 中的 ‘-e’。

expand-tabs

diff 中的 ‘-t’。

expression

sed 中的 ‘-e’。

extern-only

nm 中的 ‘-g’。

extract

cpio 中的 ‘-i’;tar 中的 ‘-x’。

faces

finger 中的 ‘-f’。

fast

su 中的 ‘-f’。

fatal-warnings

m4 中的 ‘-E’。

file

gawkinfomakemtsedtar 中的 ‘-f’。

field-separator

gawk 中的 ‘-F’。

file-prefix

Bison 中的 ‘-b’。

file-type

ls 中的 ‘-F’。

files-from

tar 中的 ‘-T’。

fill-column

makeinfo 中使用。

flag-truncation

ptx 中的 ‘-F’。

fixed-output-files

Bison 中的 ‘-y’。

follow

tail 中的 ‘-f’。

footnote-style

makeinfo 中使用。

force

cplnmvrm 中的 ‘-f’。

force-prefix

shar 中的 ‘-F’。

foreground

对于服务器程序,在前台运行;换句话说,不要做任何特殊的操作来在后台运行服务器。

format

lstimeptx 中使用。

freeze-state

m4 中的 ‘-F’。

fullname

在 GDB 中使用。

gap-size

ptx 中的 ‘-g’。

get

tar 中的 ‘-x’。

graphic

ul 中的 ‘-i’。

graphics

recode 中的 ‘-g’。

group

install 中的 ‘-g’。

gzip

tarshar 中的 ‘-z’。

hashsize

m4 中的 ‘-H’。

header

objdumprecode 中的 ‘-h

heading

who 中的 ‘-H’。

help

用于请求简短的用法信息。

here-delimiter

shar 中的 ‘-d’。

hide-control-chars

ls 中的 ‘-q’。

html

makeinfo 中,输出 HTML。

idle

who 中的 ‘-u’。

ifdef

diff 中的 ‘-D’。

ignore

ls 中的 ‘-I’;recode 中的 ‘-x’。

ignore-all-space

diff 中的 ‘-w’。

ignore-backups

ls 中的 ‘-B’。

ignore-blank-lines

diff 中的 ‘-B’。

ignore-case

lookptx 中的 ‘-f’;diffwdiff 中的 ‘-i’。

ignore-errors

make 中的 ‘-i’。

ignore-file

ptx 中的 ‘-i’。

ignore-indentation

etags 中的 ‘-I’。

ignore-init-file

Oleo 中的 ‘-f’。

ignore-interrupts

tee 中的 ‘-i’。

ignore-matching-lines

diff 中的 ‘-I’。

ignore-space-change

diff 中的 ‘-b’。

ignore-zeros

tar 中的 ‘-i’。

include

etags 中的 ‘-i’;m4 中的 ‘-I’。

include-dir

make 中的 ‘-I’。

incremental

tar 中的 ‘-G’。

info

Finger 中的 ‘-i’、‘-l’ 和 ‘-m’。

init-file

在某些程序中,指定要读取为用户初始化文件的文件名。

initial

expand 中的 ‘-i’。

initial-tab

diff 中的 ‘-T’。

inode

ls 中的 ‘-i’。

interactive

cplnmvrm 中的 ‘-i’;m4 中的 ‘-e’;xargs 中的 ‘-p’;tar 中的 ‘-w’。

intermix-type

shar 中的 ‘-p’。

iso-8601

date 中使用

jobs

make 中的 ‘-j’。

just-print

make 中的 ‘-n’。

keep-going

make 中的 ‘-k’。

keep-files

csplit 中的 ‘-k’。

kilobytes

duls 中的 ‘-k’。

language

etags 中的 ‘-l’。

less-mode

wdiff 中的 ‘-l’。

level-for-gzip

shar 中的 ‘-g’。

line-bytes

split 中的 ‘-C’。

lines

splitheadtail 中使用。

link

cpio 中的 ‘-l’。

lint
lint-old

gawk 中使用。

list

cpio 中的 ‘-t’;recode 中的 ‘-l’。

list

tar 中的 ‘-t’。

literal

ls 中的 ‘-N’。

load-average

make 中的 ‘-l’。

login

su 中使用。

machine

uname 中使用。

macro-name

ptx 中的 ‘-M’。

mail

hellouname 中的 ‘-m’。

make-directories

cpio 中的 ‘-d’。

makefile

make 中的 ‘-f’。

mapped

在 GDB 中使用。

max-args

xargs 中的 ‘-n’。

max-chars

xargs 中的 ‘-n’。

max-lines

xargs 中的 ‘-l’。

max-load

make 中的 ‘-l’。

max-procs

xargs 中的 ‘-P’。

mesg

who 中的 ‘-T’。

message

who 中的 ‘-T’。

minimal

diff 中的 ‘-d’。

mixed-uuencode

shar 中的 ‘-M’。

mode

installmkdirmkfifo 中的 ‘-m’。

modification-time

tar 中的 ‘-m’。

multi-volume

tar 中的 ‘-M’。

name-prefix

Bison 中的 ‘-a’。

nesting-limit

m4 中的 ‘-L’。

net-headers

shar 中的 ‘-a’。

new-file

make 中的 ‘-W’。

no-builtin-rules

make 中的 ‘-r’。

no-character-count

shar 中的 ‘-w’。

no-check-existing

shar 中的 ‘-x’。

no-common

wdiff 中的 ‘-3’。

no-create

touch 中的 ‘-c’。

no-defines

etags 中的 ‘-D’。

no-deleted

wdiff 中的 ‘-1’。

no-dereference

cp 中的 ‘-d’。

no-inserted

wdiff 中的 ‘-2’。

no-keep-going

make 中的 ‘-S’。

no-lines

Bison 中的 ‘-l’。

no-piping

shar 中的 ‘-P’。

no-prof

gprof 中的 ‘-e’。

no-regex

etags 中的 ‘-R’。

no-sort

nm 中的 ‘-p’。

no-splash

不要打印启动画面。

no-split

makeinfo 中使用。

no-static

gprof 中的 ‘-a’。

no-time

gprof 中的 ‘-E’。

no-timestamp

shar 中的 ‘-m’。

no-validate

makeinfo 中使用。

no-wait

emacsclient 中使用。

no-warn

在各种程序中使用以抑制警告。

node

info 中的 ‘-n’。

nodename

uname 中的 ‘-n’。

nonmatching

cpio 中的 ‘-f’。

nstuff

objdump 中的 ‘-n’。

null

xargs 中的 ‘-0’。

number

cat 中的 ‘-n’。

number-nonblank

cat 中的 ‘-b’。

numeric-sort

nm 中的 ‘-n’。

numeric-uid-gid

cpiols 中的 ‘-n’。

nx

在 GDB 中使用。

old-archive

tar 中的 ‘-o’。

old-file

make 中的 ‘-o’。

one-file-system

tarcpdu 中的 ‘-l’。

only-file

ptx 中的 ‘-o’。

only-prof

gprof 中的 ‘-f’。

only-time

gprof 中的 ‘-F’。

options

getoptfdlistfdmountfdmountdfdumount 中的 ‘-o’。

output

在各种程序中,指定输出文件名。

output-prefix

shar 中的 ‘-o’。

override

rm 中的 ‘-o’。

overwrite

unshar 中的 ‘-c’。

owner

install 中的 ‘-o’。

paginate

diff 中的 ‘-l’。

paragraph-indent

makeinfo 中使用。

parents

mkdirrmdir 中的 ‘-p’。

pass-all

ul 中的 ‘-p’。

pass-through

cpio 中的 ‘-p’。

port

finger 中的 ‘-P’。

portability’(可移植性)

cpiotar 中的 ‘-c’。

posix

gawk 中使用。

prefix-builtins’(前缀内置)

m4 中的 ‘-P’。

prefix’(前缀)

csplit 中的 ‘-f’。

preserve’(保留)

tarcp 中使用。

preserve-environment’(保留环境变量)

su 中的 ‘-p’。

preserve-modification-time’(保留修改时间)

cpio 中的 ‘-m’。

preserve-order’(保留顺序)

tar 中的 ‘-s’。

preserve-permissions’(保留权限)

tar 中的 ‘-p’。

print’(打印)

diff 中的 ‘-l’。

print-chars’(打印字符)

cmp 中的 ‘-L’。

print-data-base’(打印数据库)

make 中的 ‘-p’。

print-directory’(打印目录)

make 中的 ‘-w’。

print-file-name’(打印文件名)

nm 中的 ‘-o’。

print-symdefs’(打印符号定义)

nm 中的 ‘-s’。

printer’(打印机)

wdiff 中的 ‘-p’。

prompt’(提示)

ed 中的 ‘-p’。

proxy’(代理)

指定 HTTP 代理。

query-user’(询问用户)

shar 中的 ‘-X’。

question’(问题)

make 中的 ‘-q’。

quiet’(安静)

在许多程序中使用,以抑制通常的输出。每个接受 ‘--quiet’ 的程序都应接受 ‘--silent’ 作为同义词。

quiet-unshar’(安静 unshar)

shar 中的 ‘-Q’。

quote-name’(引用名称)

ls 中的 ‘-Q’。

rcs

diff 中的 ‘-n’。

re-interval’(重间隔)

gawk 中使用。

read-full-blocks’(读取完整块)

tar 中的 ‘-B’。

readnow’(立即读取)

在 GDB 中使用。

recon

make 中的 ‘-n’。

record-number’(记录号)

tar 中的 ‘-R’。

recursive’(递归)

chgrpchowncplsdiffrm 中使用。

reference’(参考)

touch 中的 ‘-r’。

references’(引用)

ptx 中的 ‘-r’。

regex’(正则表达式)

tacetags 中的 ‘-r’。

release’(发布)

uname 中的 ‘-r’。

reload-state’(重新加载状态)

m4 中的 ‘-R’。

relocation’(重定位)

objdump 中的 ‘-r’。

rename’(重命名)

cpio 中的 ‘-r’。

replace’(替换)

xargs 中的 ‘-i’。

report-identical-files’(报告相同文件)

diff 中的 ‘-s’。

reset-access-time’(重置访问时间)

cpio 中的 ‘-a’。

reverse’(反向)

lsnm 中的 ‘-r’。

reversed-ed’(反向ed)

diff 中的 ‘-f’。

right-side-defs’(右侧定义)

ptx 中的 ‘-R’。

same-order’(相同顺序)

tar 中的 ‘-s’。

same-permissions’(相同权限)

tar 中的 ‘-p’。

save’(保存)

stty 中的 ‘-g’。

se

在 GDB 中使用。

sentence-regexp’(句子正则表达式)

ptx 中的 ‘-S’。

separate-dirs’(分离目录)

du 中的 ‘-S’。

separator’(分隔符)

tac 中的 ‘-s’。

sequence’(序列)

recode 用于选择用于序列传递的文件或管道。

shell’(shell)

su 中的 ‘-s’。

show-all’(显示全部)

cat 中的 ‘-A’。

show-c-function’(显示 C 函数)

diff 中的 ‘-p’。

show-ends’(显示结尾)

cat 中的 ‘-E’。

show-function-line’(显示函数行)

diff 中的 ‘-F’。

show-tabs’(显示制表符)

cat 中的 ‘-T’。

silent’(静默)

在许多程序中使用,以抑制通常的输出。每个接受 ‘--silent’ 的程序都应接受 ‘--quiet’ 作为同义词。

size’(大小)

ls 中的 ‘-s’。

socket’(套接字)

指定一个文件描述符,供网络服务器用于其套接字,而不是打开和绑定新的套接字。这提供了一种在非特权进程中运行服务器的方法,该服务器通常需要保留的端口号。

sort’(排序)

ls 中使用。

source’(源)

gawk 中的 ‘-W source’。

sparse’(稀疏)

tar 中的 ‘-S’。

speed-large-files’(加速大文件)

diff 中的 ‘-H’。

split-at’(分割于)

unshar 中的 ‘-E’。

split-size-limit’(分割大小限制)

shar 中的 ‘-L’。

squeeze-blank’(压缩空白)

cat 中的 ‘-s’。

start-delete’(开始删除)

wdiff 中的 ‘-w’。

start-insert’(开始插入)

wdiff 中的 ‘-y’。

starting-file’(起始文件)

tardiff 中使用,以指定从目录中的哪个文件开始处理。

statistics’(统计)

wdiff 中的 ‘-s’。

stdin-file-list’(标准输入文件列表)

shar 中的 ‘-S’。

stop’(停止)

make 中的 ‘-S’。

strict’(严格)

recode 中的 ‘-s’。

strip’(剥离)

install 中的 ‘-s’。

strip-all’(剥离全部)

strip 中的 ‘-s’。

strip-debug’(剥离调试信息)

strip 中的 ‘-S’。

submitter’(提交者)

shar 中的 ‘-s’。

suffix’(后缀)

cplnmv 中的 ‘-S’。

suffix-format’(后缀格式)

csplit 中的 ‘-b’。

sum’(总和)

gprof 中的 ‘-s’。

summarize’(总结)

du 中的 ‘-s’。

symbolic’(符号链接)

ln 中的 ‘-s’。

symbols’(符号)

在 GDB 和 objdump 中使用。

synclines’(同步行)

m4 中的 ‘-s’。

sysname’(系统名)

uname 中的 ‘-s’。

tabs’(制表符)

expandunexpand 中的 ‘-t’。

tabsize’(制表符大小)

ls 中的 ‘-T’。

terminal’(终端)

tputul 中的 ‘-T’。wdiff 中的 ‘-t’。

text’(文本)

diff 中的 ‘-a’。

text-files’(文本文件)

shar 中的 ‘-T’。

time’(时间)

lstouch 中使用。

timeout’(超时)

指定在放弃某些操作之前等待的时间。

to-stdout’(输出到标准输出)

tar 中的 ‘-O’。

total’(总计)

du 中的 ‘-c’。

touch

makeranlibrecode 中的 ‘-t’。

trace’(跟踪)

m4 中的 ‘-t’。

traditional’(传统)

hello 中的 ‘-t’;gawk 中的 ‘-W traditional’;edm4ptx 中的 ‘-G’。

tty

在 GDB 中使用。

typedefs’(类型定义)

ctags 中的 ‘-t’。

typedefs-and-c++’(类型定义和 C++)

ctags 中的 ‘-T’。

typeset-mode’(排版模式)

ptx 中的 ‘-t’。

uncompress’(解压缩)

tar 中的 ‘-z’。

unconditional’(无条件)

cpio 中的 ‘-u’。

undefine’(取消定义)

m4 中的 ‘-U’。

undefined-only’(仅未定义)

nm 中的 ‘-u’。

update’(更新)

cpctagsmvtar 中的 ‘-u’。

usage’(用法)

gawk 中使用;与 ‘--help’ 相同。

uuencode

shar 中的 ‘-B’。

vanilla-operation’(原始操作)

shar 中的 ‘-V’。

verbose’(详细)

打印有关进度的更多信息。许多程序支持此选项。

verify’(验证)

tar 中的 ‘-W’。

version’(版本)

打印版本号。

version-control’(版本控制)

cplnmv 中的 ‘-V’。

vgrind

ctags 中的 ‘-v’。

volume’(卷)

tar 中的 ‘-V’。

what-if’(假设)

make 中的 ‘-W’。

whole-size-limit’(整体大小限制)

shar 中的 ‘-l’。

width’(宽度)

lsptx 中的 ‘-w’。

word-regexp’(单词正则表达式)

ptx 中的 ‘-W’。

writable’(可写)

who 中的 ‘-T’。

zeros’(零)

gprof 中的 ‘-z’。


下一项:,上一项:,向上:程序行为   [目录][索引]

4.11 OID 分配

OID(对象标识符)1.3.6.1.4.1.11591 已分配给 GNU 项目(感谢 Sergey Poznyakoff)。这些用于 SNMP、LDAP、X.509 证书等。网站 https://www.alvestrand.no/objectid 有一个(自愿的)许多 OID 分配列表。

如果你的 GNU 软件包需要一个新的槽,请写信给 [email protected]。 这是当前分配的弧列表

1.3.6.1.4.1.11591 GNU

1.3.6.1.4.1.11591.1 GNU Radius

1.3.6.1.4.1.11591.2 GnuPG
  1.3.6.1.4.1.11591.2.1   notation
  1.3.6.1.4.1.11591.2.1.1 pkaAddress

1.3.6.1.4.1.11591.3 GNU Radar

1.3.6.1.4.1.11591.4 GNU GSS

1.3.6.1.4.1.11591.5 GNU Mailutils

1.3.6.1.4.1.11591.6 GNU Shishi

1.3.6.1.4.1.11591.7 GNU Radio

1.3.6.1.4.1.11591.8 GNU Dico

1.3.6.1.4.1.11591.9 GNU Rush

1.3.6.1.4.1.11591.12 digestAlgorithm
  1.3.6.1.4.1.11591.12.2 TIGER/192

1.3.6.1.4.1.11591.13 encryptionAlgorithm
  1.3.6.1.4.1.11591.13.2 Serpent
    1.3.6.1.4.1.11591.13.2.1 Serpent-128-ECB
    1.3.6.1.4.1.11591.13.2.2 Serpent-128-CBC
    1.3.6.1.4.1.11591.13.2.3 Serpent-128-OFB
    1.3.6.1.4.1.11591.13.2.4 Serpent-128-CFB
    1.3.6.1.4.1.11591.13.2.21 Serpent-192-ECB
    1.3.6.1.4.1.11591.13.2.22 Serpent-192-CBC
    1.3.6.1.4.1.11591.13.2.23 Serpent-192-OFB
    1.3.6.1.4.1.11591.13.2.24 Serpent-192-CFB
    1.3.6.1.4.1.11591.13.2.41 Serpent-256-ECB
    1.3.6.1.4.1.11591.13.2.42 Serpent-256-CBC
    1.3.6.1.4.1.11591.13.2.43 Serpent-256-OFB
    1.3.6.1.4.1.11591.13.2.44 Serpent-256-CFB

1.3.6.1.4.1.11591.14 CRC algorithms
  1.3.6.1.4.1.11591.14.1 CRC 32

1.3.6.1.4.1.11591.15 ellipticCurve
  1.3.6.1.4.1.11591.15.1 Ed25519

下一项:,上一项:,向上:程序行为   [目录][索引]

4.12 内存使用

如果一个程序通常只使用几兆内存,则不必费力地减少内存使用量。例如,如果出于其他原因无法处理超过几兆字节的文件,则将整个输入文件读入内存以对其进行操作是合理的。

但是,对于诸如 cattail 之类的可以有效地处理非常大的文件的程序,避免使用会人为限制其可以处理的文件大小的技术非常重要。如果程序按行工作,并且可以应用于任意用户提供的输入文件,则它应该只在内存中保留一行,因为这不是很困难,并且用户将希望能够对大于一次性放入内存的输入文件进行操作。

如果你的程序创建了复杂的数据结构,只需在内存中创建它们,如果 malloc 返回 NULL,则会产生致命错误。

诸如 valgrind 之类的内存分析工具可能很有用,但不要仅仅为了避免其误报而使程序复杂化。 例如,如果内存一直使用到进程退出之前,请不要仅仅为了使此类工具静音而释放它。


上一项:,向上:程序行为   [目录][索引]

4.13 文件使用

程序应准备好在 /usr/etc 文件系统为只读时运行。因此,如果程序管理日志文件、锁文件、备份文件、得分文件或任何其他为内部目的而修改的文件,这些文件不应存储在 /usr/etc 中。

有两个例外。/etc 用于存储系统配置信息;当程序的工作是更新系统配置时,修改 /etc 中的文件是合理的。此外,如果用户明确要求修改目录中的一个文件,则程序将其他文件存储在同一目录中也是合理的。


下一页: , 上一页: , 上一层: 顶层   [目录][索引]

5 充分利用 C 语言

本章提供关于在编写 GNU 软件时如何最好地使用 C 语言的建议。


下一页: , 上一层: 编写 C 代码   [目录][索引]

5.1 格式化源代码

请将源代码行的长度保持在 79 个字符或更少,以便在最广泛的环境中获得最大的可读性。

将开始 C 函数体的左大括号放在第一列非常重要,这样它们将开始一个 defun。一些工具会查找第一列的左大括号来查找 C 函数的开头。这些工具不适用于未以这种方式格式化的代码。

当左大括号、左圆括号或左方括号位于函数内部时,避免将其放置在第一列,这样它们就不会开始一个 defun。如果您觉得将该定义视为 defun 有用,则开始 struct 体的左大括号可以放在第一列。

函数定义也必须从第一列开始的函数名称开始。这有助于人们搜索函数定义,也可能有助于某些工具识别它们。因此,使用标准 C 语法,格式如下

static char *
concat (char *s1, char *s2)
{
  …
}

或者,如果您想使用传统的 C 语法,请将定义格式化如下

static char *
concat (s1, s2)        /* Name starts in column one here */
     char *s1, *s2;
{                     /* Open brace in column one here */
  …
}

在标准 C 中,如果参数不能很好地放在一行中,请像这样分割它

int
lots_of_args (int an_integer, long a_long, short a_short,
              double a_double, float a_float)
…

对于 structenum 类型,同样将大括号放在第一列,除非整个内容适合放在一行中

struct foo
{
  int a, b;
}
or
struct foo { int a, b; }

本节的其余部分给出了我们对 C 格式化风格的其他方面的建议,这也是版本 1.2 及更高版本中 indent 程序的默认风格。它对应于以下选项

-nbad -bap -nbc -bbo -bl -bli2 -bls -ncdb -nce -cp1 -cs -di2
-ndj -nfc1 -nfca -hnl -i2 -ip5 -lp -pcs -psl -nsc -nsob

我们不认为这些建议是要求,因为如果两个不同的程序具有不同的格式化风格,则不会给用户带来问题。

但是,无论您使用哪种风格,请保持一致,因为一个程序中混合使用多种风格往往看起来很丑陋。如果您要对现有程序进行更改,请遵循该程序的风格。

对于函数体,我们推荐的风格如下

if (x < foo (y, z))
  haha = bar[4] + 5;
else
  {
    while (z)
      {
        haha += foo (z, z);
        z--;
      }
    return ++x + bar ();
  }

当在左圆括号之前和逗号之后有空格时,我们发现更容易阅读程序。尤其是在逗号之后。

当您将表达式拆分为多行时,请在运算符之前而不是之后拆分。这是正确的方法

if (foo_this_is_long && bar > win (x, y, z)
    && remaining_condition)

尽量避免在同一缩进级别有两个不同优先级的运算符。例如,不要这样写

mode = (inmode[j] == VOIDmode
        || GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE (inmode[j])
        ? outmode[j] : inmode[j]);

相反,请使用额外的括号,以便缩进显示嵌套

mode = ((inmode[j] == VOIDmode
         || (GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE (inmode[j])))
        ? outmode[j] : inmode[j]);

插入额外的括号,以便 Emacs 正确缩进代码。例如,如果手动执行,则以下缩进看起来不错,

v = rup->ru_utime.tv_sec*1000 + rup->ru_utime.tv_usec/1000
    + rup->ru_stime.tv_sec*1000 + rup->ru_stime.tv_usec/1000;

但是 Emacs 会更改它。添加一组括号会产生看起来同样不错的东西,并且 Emacs 会保留它

v = (rup->ru_utime.tv_sec*1000 + rup->ru_utime.tv_usec/1000
     + rup->ru_stime.tv_sec*1000 + rup->ru_stime.tv_usec/1000);

像这样格式化 do-while 语句

do
  {
    a = foo (a);
  }
while (a > 0);

请使用换页符(control-L)在逻辑位置将程序划分为页面(但不要在函数内部)。页面的长度无关紧要,因为它们不必适合打印页面。换页符应单独显示在行上。


下一页: , 上一页: , 上一层: 编写 C 代码   [目录][索引]

5.2 注释你的工作

每个程序都应该以一个简短说明其用途的注释开始。示例:‘fmt - 用于简单填充文本的过滤器’。此注释应位于包含程序 ‘main’ 函数的源文件的顶部。

此外,请在每个源文件的开头编写一个简短的注释,其中包含文件名以及关于该文件整体用途的一两行。

请用英文编写 GNU 程序中的注释,因为英语是几乎所有国家/地区的程序员都能阅读的一种语言。如果您英语写得不好,请尽力用英语编写注释,然后请其他人帮助重写它们。如果您无法用英语编写注释,请找人与您合作并将您的注释翻译成英语。

请在每个函数上添加注释,说明该函数的作用、获取的参数类型,以及参数的可能值意味着什么以及用于什么。如果 C 类型以其习惯方式使用,则无需用文字重复 C 参数声明的含义。如果它的使用方式有任何不标准的地方(例如,类型为 char * 的参数实际上是字符串的第二个字符的地址,而不是第一个字符的地址),或者任何可能的值都无法按预期工作(例如,不保证包含换行符的字符串可以工作),请务必说明。

如果存在返回值,也请解释返回值的意义。

请在注释中的句子末尾添加两个空格,以便 Emacs 句子命令可以工作。此外,请编写完整的句子并大写第一个单词。如果小写标识符出现在句子的开头,请不要大写它!更改拼写会使其成为不同的标识符。如果您不喜欢以小写字母开头句子,请以不同的方式编写句子(例如,“标识符小写是…”)。

如果您使用参数名称来谈论参数值,则函数上的注释会更加清晰。变量名本身应为小写,但在谈论值而不是变量本身时,请以大写形式书写。因此,“inode 号 NODE_NUM”而不是“一个 inode”。

在函数之前的注释中重复该函数的名称通常没有意义,因为读者可以自己看到。当注释太长以至于函数本身会超出屏幕底部时,可能会有一个例外。

每个静态变量也应该有一个注释,如下所示

/* Nonzero means truncate lines in the display;
   zero means continue them.  */
int truncate_lines;

每个 ‘#endif’ 都应该有一个注释,除非是简短的条件(只有几行)并且没有嵌套的情况。该注释应说明正在结束的条件的条件,包括其含义。‘#else’ 应该有一个注释,描述后面代码的条件和含义。例如

#ifdef foo
  …
#else /* not foo */
  …
#endif /* not foo */
#ifdef foo
  …
#endif /* foo */

但是,相反,请为 ‘#ifndef’ 以这种方式编写注释

#ifndef foo
  …
#else /* foo */
  …
#endif /* foo */
#ifndef foo
  …
#endif /* not foo */

下一页: , 上一页: , 上一层: 编写 C 代码   [目录][索引]

5.3 清晰地使用 C 构造

请显式声明所有对象的类型。例如,您应该显式声明函数的所有参数,并且您应该声明函数返回 int 而不是省略 int

一些程序员喜欢使用 GCC ‘-Wall’ 选项,并在每次发出警告时更改代码。如果您想这样做,那就这样做。其他程序员不喜欢使用 ‘-Wall’,因为它会为他们不想更改的有效且合法的代码发出警告。如果您想这样做,那就这样做。编译器应该是您的仆人,而不是您的主人。

不要仅仅为了安抚诸如 lintclang 和 GCC 等静态分析工具而使程序变得丑陋,并使用额外的警告选项(如 -Wconversion-Wundef)。这些工具可以帮助查找错误和不清晰的代码,但它们也可能生成如此多的误报,以至于使用不必要的强制转换、包装器和其他复杂性来抑制它们会损害可读性。例如,请不要仅仅为了安抚 lint 检查器而插入强制转换为 void 或调用无操作函数。

外部函数和稍后在源文件中出现的函数的声明都应放在文件开头附近的一个位置(在文件中的第一个函数定义之前的某个位置),或者应放在头文件中。不要将 extern 声明放在函数内部。

过去,在同一个函数中为不同的值重复使用相同的局部变量(名称如 tem)是一种常见的做法。与其这样做,不如为每个不同的目的声明一个单独的局部变量,并为其赋予有意义的名称。这不仅使程序更容易理解,还有利于优秀编译器进行优化。您还可以将每个局部变量的声明移动到包含其所有用法的最小作用域中。这会使程序更加简洁。

不要使用与全局标识符同名的局部变量或参数。GCC 的 ‘-Wshadow’ 选项可以检测到这个问题。

不要在一个跨行的声明中声明多个变量。应该在每一行开始一个新的声明。例如,不要这样写:

int    foo,
       bar;

应该这样写:

int foo, bar;

或者这样写:

int foo;
int bar;

(如果是全局变量,无论如何都应该在每个变量前面加上注释。)

当你在另一个 if 语句中嵌套一个 if-else 语句时,总是要用花括号把 if-else 括起来。因此,永远不要这样写:

if (foo)
  if (bar)
    win ();
  else
    lose ();

总是要这样写:

if (foo)
  {
    if (bar)
      win ();
    else
      lose ();
  }

如果你在 else 语句中嵌套一个 if 语句,要么在一行写 else if,像这样:

if (foo)
  …
else if (bar)
  …

它的 then 部分缩进得与前面的 then 部分一样,要么像这样用花括号将嵌套的 if 括起来:

if (foo)
  …
else
  {
    if (bar)
      …
  }

不要在同一个声明中同时声明结构体标签和变量或 typedef。应该分开声明结构体标签,然后再用它来声明变量或 typedef。

尽量避免在 if 条件语句中进行赋值(在 while 条件语句中赋值是可以的)。例如,不要这样写:

if ((foo = (char *) malloc (sizeof *foo)) == NULL)
  fatal ("virtual memory exhausted");

应该这样写:

foo = (char *) malloc (sizeof *foo);
if (foo == NULL)
  fatal ("virtual memory exhausted");

下一节:,上一节:,上一级:编写 C 代码   [目录][索引]

5.4 命名变量、函数和文件

程序中全局变量和函数的名称本身就起到了一种注释的作用。所以不要选择简洁的名称,而应该选择能够提供关于变量或函数含义的有用信息的名称。在 GNU 程序中,名称应该使用英语,就像其他注释一样。

局部变量的名称可以短一些,因为它们只在一个上下文中使用,并且(假设)注释会解释它们的用途。

尽量限制你在符号名称中使用缩写。可以采用一些缩写,解释它们的含义,然后频繁地使用它们,但是不要使用大量晦涩的缩写。

请使用下划线来分隔名称中的单词,以便 Emacs 的单词命令可以在其中使用。坚持使用小写;保留大写用于宏和 enum 常量,以及遵循统一约定的名称前缀。

例如,你应该使用像 ignore_space_change_flag 这样的名称;不要使用像 iCantReadThis 这样的名称。

指示命令行选项是否已被指定的变量,应该以选项的含义命名,而不是以选项字母命名。注释应该说明该选项的确切含义及其字母。例如:

/* Ignore changes in horizontal whitespace (-b).  */
int ignore_space_change_flag;

当你想用常量整数值定义名称时,使用 enum 而不是 ‘#define’。GDB 知道枚举常量。

你可能需要确保,如果将这些文件加载到会缩短文件名的 MS-DOS 文件系统中,所有文件名都不会冲突。你可以使用 doschk 程序来测试这一点。

一些 GNU 程序被设计为将文件名限制为 14 个字符或更少,以避免在将它们读入旧的 System V 系统时发生文件名冲突。请在现有的具有此特性的 GNU 程序中保留此功能,但在新的 GNU 程序中没有必要这样做。doschk 还会报告长度超过 14 个字符的文件名。


下一节:,上一节:,上一级:编写 C 代码   [目录][索引]

5.5 系统类型之间的可移植性

在 Unix 世界中,“可移植性”指的是移植到不同的 Unix 版本。对于 GNU 程序,这种可移植性是可取的,但不是最重要的。

GNU 软件的主要目的是作为 GNU 操作系统的一部分运行,使用 GNU 编译器在各种类型的硬件上编译。因此,绝对必要的可移植性种类非常有限。支持基于 Linux 的 GNU 系统非常重要,因为它们是人们主要使用的 GNU 形式。

使 GNU 程序在 GNU 系统之外的操作系统上运行,并不是开发 GNU 软件包的核心目标。你永远不必这样做。然而,用户会要求你这样做,并且配合这些请求是有用的——只要你不让它主导项目或阻碍主要目标即可。

支持其他自由或几乎自由的操作系统(例如,*BSD)是很好的。支持各种类 Unix 系统是可取的,但不是最重要的。这通常不是太难,所以你最好还是这样做。但是,如果事实证明很困难,你就不必将其视为义务。

在大多数情况下,将程序移植到更多平台是好的,但是你不应该花费太多时间,以至于它妨碍你在更核心的方面改进程序。如果开始出现这种情况,请告诉用户你不想在这方面花费更多时间——必须由其他人编写代码、调试代码、编写文档等等,然后你才能安装它。

你也可以出于技术原因拒绝移植补丁,就像对待用户提交的任何其他补丁一样。这取决于你。

实现与大多数类 Unix 系统兼容性的最简单方法是使用 Autoconf。你的程序不太可能需要知道比 Autoconf 可以提供的更多关于主机平台的信息,仅仅是因为大多数需要这种知识的程序已经编写好了。

当有更高级的替代方案 (readdir) 时,避免使用半内部数据库(例如,目录)的格式。

至于像 MS-DOS、Windows、VMS、MVS 和较旧的 Macintosh 系统等与 Unix 不同的系统,支持它们通常需要做很多工作。在这种情况下,最好将时间花在添加在 GNU 和 GNU/Linux 上有用的功能上,而不是花在支持其他不兼容的系统上。

如果你支持 Windows,请不要将其缩写为 “win”。请参阅商标

通常我们完整地写出“Windows”这个名称,但是当简洁非常重要时(例如在文件名和一些符号名称中),我们会将其缩写为 “w”。例如,在 GNU Emacs 中,我们在 Windows 特定文件的文件名中使用 ‘w32’,但是 Windows 条件的宏被称为 WINDOWSNT。原则上也可以有 ‘w64’。

在编译 C 文件时,定义 “功能测试宏” _GNU_SOURCE 是一个好主意。当你在 GNU 或 GNU/Linux 上编译时,这将启用 GNU 库扩展函数的声明,并且如果你在程序中以其他方式定义相同的函数名,通常会给你一个编译器错误消息。(如果你喜欢让程序更容易移植到其他系统,则不必实际使用这些函数。)

但是,无论你是否使用这些 GNU 扩展,都应该避免将它们的名字用于任何其他含义。这样做会使你的代码难以移动到其他 GNU 程序中。


下一节:,上一节:,上一级:编写 C 代码   [目录][索引]

5.6 CPU 之间的可移植性

即使是 GNU 系统也会因为 CPU 类型的差异而有所不同——例如,字节顺序和对齐要求的差异。处理这些差异是绝对必要的。但是,不要努力满足 int 将小于 32 位的可能性。我们不支持 GNU 中的 16 位机器。

你无需满足 long 将小于指针和 size_t 的可能性。我们知道一个这样的平台:Microsoft Windows 上的 64 位程序。如果你关心使用 Mingw64 让你的软件包在 Windows 上运行,你将需要处理 8 字节指针和 4 字节 long,这将破坏此代码:

printf ("size = %lu\n", (unsigned long) sizeof array);
printf ("diff = %ld\n", (long) (pointer2 - pointer1));

是否在你的软件包中支持 Mingw64 以及一般的 Windows,由你选择。GNU 项目没有说你有任何义务这样做。我们的目标是取代包括 Windows 在内的专有系统,而不是增强它们。如果有人迫使你让你的程序在 Windows 上运行,而你对此不感兴趣,你可以回答说:“切换到 GNU/Linux —— 你的自由依赖于它。”

off_t 这样的预定义文件大小类型是一个例外:它们在许多平台上比 long 长,因此上面的代码不能与它们一起使用。一种可移植地打印 off_t 值的方法是自己逐个打印其数字。

不要假设 int 对象的地址也是其最低有效字节的地址。这在 Big-endian 机器上是错误的。因此,不要犯以下错误:

int c;
…
while ((c = getchar ()) != EOF)
  write (file_descriptor, &c, 1);

相反,使用 unsigned char 如下。(unsigned 是为了移植到不寻常的系统,在这些系统中 char 是有符号的,并且存在整数溢出检查。)

int c;
while ((c = getchar ()) != EOF)
  {
    unsigned char u = c;
    write (file_descriptor, &u, 1);
  }

如果可以,避免将指针强制转换为整数。这种强制转换会大大降低可移植性,并且在大多数程序中它们很容易避免。在将指针强制转换为整数必不可少的情况下——例如,Lisp 解释器在一个字中存储类型信息和地址——你必须明确规定处理不同的字大小。你还需要为那些你可以从 malloc 获取的地址的正常范围远离零的系统做好准备。


下一节:,上一节:,上一级:编写 C 代码   [目录][索引]

5.7 调用系统函数

历史上,C 实现存在很大差异,许多系统缺乏 ANSI/ISO C89 的完整实现。然而,如今,所有实际系统都有 C89 编译器,并且 GNU C 支持几乎所有 C99 和部分 C11。类似地,大多数系统都实现了 POSIX.1-2001 库和工具,并且许多系统都有 POSIX.1-2008。

因此,几乎没有理由支持旧的 C 或非 POSIX 系统,你可能希望利用标准的 C 和 POSIX 来编写更清晰、更可移植或更快的代码。你应该尽可能使用标准接口;但是,如果 GNU 扩展使你的程序更易于维护、功能更强大或更好,请毫不犹豫地使用它们。在任何情况下,都不要自己声明系统函数;这是一个冲突的根源。

尽管有标准,但几乎每个库函数在某些系统上都存在某种可移植性问题。以下是一些示例:

open

在许多平台上,带有尾随 / 的名称会被错误处理。

printf

long double 可能未实现;浮点值 Infinity 和 NaN 通常被错误处理;大型精度的输出可能不正确。

readlink

可能返回 int 而不是 ssize_t

scanf

在 Windows 上,失败时不会设置 errno

在这方面,Gnulib 是一个很大的帮助。Gnulib 为许多缺少标准接口的系统提供了标准接口的实现,包括增强的 GNU 接口的可移植实现,从而使其使用可移植,以及 POSIX-1.2008 接口的实现,其中一些接口甚至在最新的 GNU 系统中也缺失。

Gnulib 还提供了许多有用的非标准接口;例如,标准数据结构(哈希表、二叉树)的 C 实现,用于内存分配函数的错误检查类型安全包装器(xmallocxrealloc)以及错误消息的输出。

Gnulib 与 GNU Autoconf 和 Automake 集成,从而减轻了程序员编写可移植代码的大部分负担:Gnulib 使您的 configure 脚本能够自动确定缺少哪些功能,并使用 Gnulib 代码来提供缺失的部分。

Gnulib 和 Autoconf 手册都有关于可移植性的详细章节:请参阅 Gnulib 中的 简介Autoconf 中的 可移植的 C 和 C++。请查阅它们以获取更多详细信息。


下一节:,上一节:,上级:编写 C   [目录][索引]

5.8 国际化

GNU 有一个名为 GNU gettext 的库,它可以轻松地将程序中的消息翻译成各种语言。您应该在每个程序中使用此库。使用英语作为消息在程序中显示的内容,并让 gettext 提供将其翻译成其他语言的方法。

使用 GNU gettext 涉及在每个可能需要翻译的字符串周围放置一个 gettext 宏调用,如下所示

printf (gettext ("Processing file '%s'..."), file);

这允许 GNU gettext 将字符串 "Processing file '%s'..." 替换为翻译后的版本。

一旦程序使用了 gettext,请在添加需要翻译的新字符串时注意编写对 gettext 的调用。

在包中使用 GNU gettext 涉及为包指定一个文本域名称。文本域名称用于将此包的翻译与其他包的翻译分隔开。通常,文本域名称应与包的名称相同,例如,GNU 核心实用程序的 ‘coreutils’。

为了使 gettext 能够良好地工作,请避免编写对单词或句子的结构做出假设的代码。当您希望句子的精确文本根据数据而变化时,请使用两个或多个包含完整句子的备用字符串常量,而不是将条件化的单词或短语插入到单个句子框架中。

这是一个不应该做的例子

printf ("%s is full", capacity > 5000000 ? "disk" : "floppy disk");

如果您像这样将 gettext 应用于所有字符串,

printf (gettext ("%s is full"),
        capacity > 5000000 ? gettext ("disk") : gettext ("floppy disk"));

翻译人员几乎不知道 “disk” 和 “floppy disk” 旨在替换到另一个字符串中。更糟糕的是,在某些语言(如法语)中,这种结构将不起作用:单词 “full” 的翻译取决于句子第一部分的性别;它碰巧与 “disk” 的不一样,与 “floppy disk” 的也不一样。

完整的句子可以毫无问题地翻译

printf (capacity > 5000000 ? gettext ("disk is full")
        : gettext ("floppy disk is full"));

以下代码在句子结构层面也出现了类似的问题

printf ("#  Implicit rule search has%s been done.\n",
        f->tried_implicit ? "" : " not");

对此代码添加 gettext 调用无法为所有语言提供正确的结果,因为某些语言中的否定需要在句子中的多个位置添加单词。相比之下,如果代码像这样开始,添加 gettext 调用可以顺利完成工作

printf (f->tried_implicit
        ? "#  Implicit rule search has been done.\n",
        : "#  Implicit rule search has not been done.\n");

另一个例子是这个

printf ("%d file%s processed", nfiles,
        nfiles != 1 ? "s" : "");

此示例的问题在于它假设复数是通过添加 ‘s’ 来形成的。如果您像这样将 gettext 应用于格式字符串,

printf (gettext ("%d file%s processed"), nfiles,
        nfiles != 1 ? "s" : "");

该消息可以使用不同的单词,但仍然会被迫为复数使用 ‘s’。这是一种更好的方法,将 gettext 独立应用于这两个字符串

printf ((nfiles != 1 ? gettext ("%d files processed")
         : gettext ("%d file processed")),
        nfiles);

但这仍然不适用于像波兰语这样的语言,它有三种复数形式:一种用于 nfiles == 1,一种用于 nfiles == 2, 3, 4, 22, 23, 24, ...,一种用于其余情况。GNU 的 ngettext 函数解决了这个问题

printf (ngettext ("%d files processed", "%d file processed", nfiles),
        nfiles);

下一节:,上一节:,上级:编写 C   [目录][索引]

5.9 字符集

除非有充分的理由因为应用领域而这样做,否则在 GNU 源代码注释、文本文档和其他上下文中,首选坚持使用 ASCII 字符集(纯文本,7 位字符)。例如,如果源代码处理法国革命历法,则其文字字符串中包含诸如 “Floréal” 之类的月份名称的重音字符是可以的。此外,在变更日志中使用非 ASCII 字符来表示贡献者的姓名也是可以的(但不要求)(请参阅变更日志)。

如果您需要使用非 ASCII 字符,通常应坚持使用一种编码,当然是在单个文件中。UTF-8 可能是最佳选择。


下一节:,上一节:,上级:编写 C   [目录][索引]

5.10 引号字符

在 C 语言环境中,GNU 程序的输出应坚持使用纯 ASCII 作为发送给用户的消息中的引号字符:最好是 0x22(‘"’)或 0x27(‘'’)作为开始和结束引号。尽管 GNU 程序传统上使用 0x60(‘`’)作为开始引号,而使用 0x27(‘'’)作为结束引号,但如今,引号 ‘`像这样'’ 通常是不对称呈现的,因此引号 ‘"像这样"’ 或 ‘'像这样'’ 通常看起来更好。

GNU 程序在非 C 语言环境中生成特定于语言环境的引号是可以的,但不要求这样做。例如

printf (gettext ("Processing file '%s'..."), file);

在这里,法语翻译可能会导致 gettext 返回字符串 "Traitement de fichier ‹ %s ›...",从而产生更适合法语语言环境的引号。

有时程序可能需要直接使用开始和结束引号。按照惯例,gettext 将字符串 ‘"`"’ 翻译为开始引号,将字符串 ‘"'"’ 翻译为结束引号,程序可以使用这些翻译。但是,通常,最好在较长字符串的上下文中翻译引号字符。

如果您的程序的输出有可能被另一个程序解析,那么最好提供一个选项来使此解析可靠。例如,您可以使用 C 语言或 Bourne shell 中的约定来转义特殊字符。例如,请参阅 GNU ls--quoting-style 选项。


上一节:,上级:编写 C   [目录][索引]

5.11 Mmap

如果您使用 mmap 读取或写入文件,请不要假设它在所有文件上都有效或在所有文件上都失败。它可能在某些文件上有效,而在其他文件上失败。

使用 mmap 的正确方法是在您想要使用它的特定文件上尝试它,如果 mmap 不起作用,则退回到使用 readwrite 以另一种方式完成这项工作。

需要这种预防措施的原因是,GNU 内核(HURD)提供了一个用户可扩展的文件系统,其中可以有许多不同类型的“普通文件”。它们中的许多都支持 mmap,但有些不支持。重要的是要使程序能够处理所有这些类型的文件。


下一节:,上一节:,上级:顶部   [目录][索引]

6 编写程序文档

一个 GNU 程序理想情况下应该附带完整的免费文档,足以用于参考和教程目的。如果该软件包可以被编程或扩展,则文档应涵盖编程或扩展该软件包,以及如何使用它。


下一节:,上级:文档   [目录][索引]

6.1 GNU 手册

GNU 系统首选的文档格式是 Texinfo 格式化语言。每个 GNU 包(理想情况下)都应该有 Texinfo 格式的参考文档和学习文档。Texinfo 可以使用 TeX 生成高质量的格式化书籍,并生成 Info 文件。也可以从 Texinfo 源生成 HTML 输出。请参阅 Texinfo 手册,无论是精装本还是通过 info 或 Emacs Info 子系统 (C-h i) 提供的在线版本。

如今,一些其他格式(如 Docbook 和 Sgmltexi)可以自动转换为 Texinfo。只要效果良好,就可以通过这种转换方式生成 Texinfo 文档。

确保您的手册对于对该主题一无所知并直接阅读的读者来说是清晰的。这意味着在开头涵盖基本主题,而仅在稍后涵盖高级主题。这也意味着在首次使用每个专业术语时对其进行定义。

请记住,GNU 手册(和其他 GNU 文档)的受众是全球性的,它将使用多年,甚至几十年。这意味着读者可能具有非常不同的文化参考点。几十年后,除了老人,所有人都将具有非常不同的文化参考点;今天“每个人都知道”的许多事情可能会被大部分遗忘。

因此,请尽量避免以依赖文化参考点进行正确理解的方式进行写作,或者以会阻碍不认识它们的人阅读的方式引用它们。

同样,在选择单词(技术术语除外)、语言结构和拼写时要保守:旨在使十年前的读者能够理解它们。在任何潮流的竞争中,GNU 的写作甚至都不应该有资格进入。

偶尔提及一次在空间或时间上局部化的参考点或事实是可以的,如果它与主题直接相关或作为题外话。当这些少数内容(无论如何它们都很突出)不再有意义时,更改它们不会花费太多精力。

相比之下,当提及 GNU 和自由软件运动的概念时,只要它们与主题相关,就总是恰当的。这些是我们信息的核心部分,因此我们应该利用机会提及它们。它们是基本的道德立场,因此它们很少会改变,甚至永远不会改变。

程序员倾向于将程序的结构作为其文档的结构。但是,这种结构不一定适合解释如何使用该程序;它可能与用户无关,并且会使他们感到困惑。

相反,组织文档的正确方法是根据用户在阅读时会想到的概念和问题。此原则适用于每个级别,从最低级别(段落中句子的顺序)到最高级别(手册中章节主题的顺序)。有时,这种思想结构与被记录的软件的实现结构相匹配,但通常情况下它们是不同的。学习编写优秀文档的一个重要部分是学会注意到何时你不假思索地将文档结构化为类似于实现结构,然后停下来,寻找更好的替代方案。

例如,GNU 系统中的每个程序都应该在一个手册中进行文档记录;但这并不意味着每个程序都应该有自己的手册。那样做会遵循实现的结构,而不是帮助用户理解的结构。

相反,每个手册都应该涵盖一个连贯的主题。例如,与其为 diff 提供一个手册,为 diff3 提供一个手册,我们不如为“文件比较”提供一个手册,其中涵盖这两个程序以及 cmp。通过将这些程序一起记录,我们可以使整个主题更加清晰。

讨论某个程序的手册当然应该记录该程序的所有命令行选项和所有命令。它应该给出它们用法的示例。但不要将手册组织为功能的列表。相反,按逻辑方式按子主题组织它。解决用户在思考程序所做的工作时会提出的问题。不要只是告诉读者每个功能可以做什么,而是要说明它擅长做什么工作,并展示如何使用它来完成这些工作。解释推荐的用法,以及用户应该避免的用法类型。

一般来说,GNU 手册应同时作为教程和参考。它应该设置为可以通过 Info 方便地访问每个主题,并可以从头到尾阅读(附录除外)。GNU 手册应该为从头开始阅读的初学者提供一个很好的入门,并且还应该提供黑客想要的所有详细信息。《Bison 手册》就是一个很好的例子——请看一看,了解我们的意思。

这并不像乍一看那么难。将每一章组织成其主题的逻辑分解,但对各节进行排序并编写其文本,以便通读本章有意义。在将书籍组织成章节以及将一节组织成段落时,也要这样做。需要记住的是,在每个点上,解决前面文本提出的最基本和最重要的问题。

如有必要,可以在手册开头添加一些纯粹的教程章节,涵盖该主题的基础知识。这些为初学者理解手册的其余部分提供了框架。《Bison 手册》提供了一个如何做到这一点的很好的例子。

为了作为参考,手册应包含一个索引,其中列出程序的所有函数、变量、选项和重要概念。一个组合索引对于短手册来说就足够了,但有时对于复杂的软件包,最好使用多个索引。《Texinfo 手册》包含有关准备良好索引条目的建议,请参阅 制作索引条目(位于 GNU Texinfo 中),并参阅 定义索引的条目(位于 GNU Texinfo 中)。

不要使用 Unix man 页面作为如何编写 GNU 文档的模型;它们中的大多数都很简洁、结构不良,并且对基本概念的解释不足。(当然,也有一些例外。)此外,Unix man 页面使用一种特定的格式,这与我们在 GNU 手册中使用的格式不同。

请在手册中包含一个电子邮件地址,用于报告手册文本中的错误。

请不要使用 Unix 文档中使用的术语“pathname”;而是使用“file name”(两个词)。我们仅将术语“path”用于搜索路径,搜索路径是目录名称的列表。

请不要使用术语“illegal”来指代计算机程序的错误输入。请使用“invalid”来指代此内容,并将术语“illegal”保留给法律禁止的活动。

请不要在函数名称后写 '()' 仅仅是为了表明它是一个函数。foo () 不是一个函数,它是一个没有参数的函数调用。

请尽可能坚持使用主动语态,避免被动语态,并使用现在时,而不是将来时。例如,写“函数 foo 返回一个包含 ab 的列表”,而不是“将返回一个包含 ab 的列表。”主动语态的一个优点是它要求您声明句子的主语;使用被动语态,您可以省略主语,这会导致模糊不清。

在语法要求时,可以使用将来时,例如,“如果您键入 x,计算机将在 10 秒内自毁。”


下一页:,上一页:,向上:文档   [目录][索引]

6.2 文档字符串和手册

一些编程系统(例如 Emacs)为每个函数、命令或变量提供文档字符串。您可能会很想通过编译文档字符串并编写一些额外的文字来围绕它们来编写参考手册,但是您一定不能这样做。这种方法是一个根本性的错误。编写良好的文档字符串的文本对于手册来说是完全错误的。

文档字符串需要独立存在——当它出现在屏幕上时,将没有其他文本来介绍或解释它。同时,它的风格可以相当随意。

手册中描述函数或变量的文本不能独立存在;它出现在节或小节的上下文中。该节开头处的其他文本应解释一些概念,并且通常应提出一些适用于多个函数或变量的一般性观点。该节中先前对函数和变量的描述也将提供有关该主题的信息。编写为独立存在的描述将重复其中的一些信息;这种冗余看起来很糟糕。同时,文档字符串中可以接受的随意性在手册中是完全不可接受的。

在编写优秀手册时,使用文档字符串的唯一好方法是将它们用作编写优秀文本的信息来源。


下一页:,上一页:,向上:文档   [目录][索引]

6.3 手册结构细节

手册的标题页应说明手册中记录的程序或软件包的版本。手册的顶层节点也应包含此信息。如果手册的更改频率高于或独立于程序,则还应在这两个位置都说明手册的版本号。

手册中记录的每个程序都应具有一个名为“程序 调用”或“调用程序”的节点。此节点(及其子节点,如果有)应描述程序的命令行参数以及如何运行它(人们会在 man 页面中查找的那种信息)。从包含程序使用的所有选项和参数模板的“@example”开始。

或者,在某个菜单中放置一个菜单项,其项目名称符合上述模式之一。这会识别该项目指向的节点,以作为此目的的节点,而不管节点的实际名称如何。

Info 阅读器的“--usage”功能会查找此类节点或菜单项,以便查找相关文本,因此每个 Texinfo 文件都必须有一个。

如果一个手册描述了多个程序,则应为手册中描述的每个程序都设置这样一个节点。


下一页:,上一页:,向上:文档   [目录][索引]

6.4 手册许可证

请为所有超过几页的 GNU 手册使用 GNU 自由文档许可证。对于短文档的集合也是如此——您只需要整个集合的一份 GNU FDL 副本。对于单个短文档,您可以使用非常宽松的非版权许可,以避免占用较长许可证的空间。

请参阅 https://gnu.ac.cn/copyleft/fdl-howto.html,以了解有关如何使用 GFDL 的更多说明。

请注意,如果手册的许可证既不是 GPL 也不是 LGPL,则没有义务在手册中包含 GNU GPL 或 GNU LGPL 的副本。在大型手册中包含程序的许可证可能是一个好主意;在短手册中,如果包含程序的许可证会大大增加其大小,则最好不要包含它。


下一页:,上一页:,向上:文档   [目录][索引]

6.5 手册致谢

请在手册的标题页上将手册的主要人类作者列为作者。如果一家公司赞助了这项工作,请在手册的适当位置感谢该公司,但不要将该公司列为作者。


下一篇:,上一篇:,上一级:文档   [目录][索引]

6.6 印刷版手册

自由软件基金会(FSF)会出版一些 GNU 手册的印刷版本。为了鼓励这些手册的销售,在线版本的手册应该在最开始就提到印刷版手册是可用的,并且应该指出获取手册的信息——例如,提供一个链接到 https://gnu.ac.cn/order/order.html 页面。但是,这不应该包含在印刷版手册中,因为在那里它是多余的。

在手册的在线版本中解释用户如何从源代码打印出手册也是有用的。


下一篇:,上一篇:,上一级:文档   [目录][索引]

6.7 NEWS 文件

除了手册之外,软件包还应该有一个名为 NEWS 的文件,其中包含值得提及的用户可见更改的列表。在每个新版本中,将项目添加到文件的前面,并标识它们所属的版本。不要丢弃旧的项目;在较新的项目之后将它们保留在文件中。这样,从任何先前版本升级的用户都可以看到新内容。

如果 NEWS 文件变得很长,请将一些较旧的项目移动到一个名为 ONEWS 的文件中,并在末尾添加一个注释,引导用户查阅该文件。


下一篇:,上一篇:,上一级:文档   [目录][索引]

6.8 变更日志

保留一个更改日志来描述对程序源代码文件所做的所有更改。这样做的目的是,将来调查错误的人们将了解可能引入错误的更改。通常,可以通过查看最近更改的内容来发现新错误。更重要的是,更改日志可以帮助您消除程序不同部分之间的概念不一致性,方法是向您提供冲突概念是如何产生的,它们来自哪里以及为什么进行冲突更改的历史记录。

因此,更改日志应该足够详细和准确,以提供此类软件取证通常所需的信息。具体来说,更改日志应该使查找以下问题的答案变得容易

历史上,更改日志保存在经过特殊格式化的文件中。如今,项目通常将其源代码文件保存在版本控制系统(VCS)下,例如 Git、Subversion 或 Mercurial。如果 VCS 存储库是公开可访问的,并且更改是单独提交给它的(每个逻辑更改集一个提交),并记录每个更改的作者,那么 VCS 记录的信息可以用来从 VCS 日志生成更改日志,并通过使用合适的 VCS 命令来回答上述问题。(但是,VCS 日志消息仍然需要提供一些支持信息,如下所述。)维护此类 VCS 存储库的项目可以决定不维护单独的更改日志文件,而是依赖 VCS 来保存更改日志。

如果您决定不维护单独的更改日志文件,您仍然应该考虑在发布 tarball 中提供它们,以方便希望在不访问项目 VCS 存储库的情况下查看更改日志的用户。存在一些脚本可以从 VCS 日志生成 ChangeLog 文件;例如,Gnulib 的 gitlog-to-changelog 脚本可以为 Git 存储库执行此操作。在 Emacs 中,命令 C-x v a (vc-update-change-log) 可以从 VCS 日志中增量更新 ChangeLog 文件。

如果维护了单独的更改日志文件,它们通常被称为 ChangeLog,并且每个此类文件涵盖整个目录。每个目录都可以有自己的更改日志文件,或者目录可以使用其父目录的更改日志——这取决于您。


下一篇:,上一级:更改日志   [目录][索引]

6.8.1 变更日志的概念和约定

您可以将更改日志视为一个概念上的“撤消列表”,其中说明了早期版本与当前版本的不同之处。人们可以看到当前版本;他们不需要更改日志来告诉他们其中有什么。他们从更改日志中想要的是对早期版本如何不同的清晰解释。更改日志中的每个条目都描述了单个更改或属于一起的最小一批更改,也称为更改集

最好以标题行开始更改日志条目:一个完整的句子,总结了更改集。如果您将更改日志保存在 VCS 中,这应该是一项要求,因为以缩写形式显示更改日志的 VCS 命令(例如 git log --oneline)会特殊处理标题行。(在 ChangeLog 文件中,标题行遵循一行,其中说明了更改的作者以及何时安装该更改。)

在更改日志条目的标题行之后,描述总体更改。这应该尽可能长,以给出清晰的描述。特别注意更改集的各个方面,这些方面不容易从差异或修改过的文件和函数的名称中收集到:更改的总体思路及其必要性,以及对不同文件/函数所做的更改之间的关系(如果有)。如果更改或其原因在某些公共论坛(例如项目的问题跟踪器或邮件列表)上进行了讨论,那么最好在更改的描述中总结该讨论的要点,并为那些想完整阅读的人提供指向该讨论或问题 ID 的指针。

解释新代码的各部分如何与其他代码一起工作的最佳位置是在代码中的注释中,而不是在更改日志中。

如果您认为更改需要解释为什么需要进行更改——也就是说,旧代码有什么问题导致需要进行此更改——您可能是对的。请将解释放在代码的注释中,这样人们在看到代码时就会看到它。这种解释的一个例子是,“此函数以前是迭代的,但是当 MUMBLE 是树时它失败了。”(尽管如此简单的原因不需要这种解释。)

其他类型的更改解释的最佳位置是在更改日志条目中。特别是,注释通常不会说明为什么某些代码被删除或移动到其他位置——这属于进行该操作的更改的描述。

在对更改进行自由文本描述之后,最好根据它们所在的文件给出您更改的实体或定义的名称列表,以及每个文件中更改的内容。请参阅更改日志的样式。如果一个项目使用现代 VCS 来保存更改日志信息,如更改日志中所述,则不必显式列出已更改的文件和函数,在某些情况下(例如在许多地方进行相同的机械更改)甚至很繁琐。您可以自行决定是否允许项目的开发人员从日志条目中省略已更改的文件和函数的列表,以及是否在某些特定条件下允许此类省略。但是,在做出此决定时,请考虑以下为每次更改提供已更改实体列表的好处

由于这些原因,在每次更改中提供修改过的文件和函数列表会使更改日志更有用,因此我们建议尽可能地包含它们。

也可以通过运行脚本来生成命名修改过的实体的列表。其中一个脚本是 mklog.py(用 Python 3 编写);它被 GCC 项目使用。Gnulib 提供了另一个这样的脚本变体,称为 vcs-to-changelog.py,它是 vcs-to-changelog 模块的一部分。请注意,这些脚本目前支持的编程语言比 Emacs 提供的手动命令少(请参阅 更改日志的样式)。因此,从 VCS 提交历史生成 ChangeLog 文件(例如通过 gitlog-to-changelog 脚本)的上述方法通常会产生更好的结果——前提是贡献者坚持提供良好的提交消息。


下一节:,上一节:,上一级:更改日志   [目录][索引]

6.8.2 变更日志的样式

以下是一些简单的更改日志条目示例,首先是指出谁进行了更改以及何时安装的标题行,然后是特定更改的描述。(这些示例来自 Emacs。)请记住,显示更改日期以及作者姓名和电子邮件地址的行仅在单独的 ChangeLog 文件中需要,而不是在将更改日志保存在 VCS 中时需要。

2019-08-29  Noam Postavsky  <[email protected]>

	Handle completely undecoded input in term (Bug#29918)

	* lisp/term.el (term-emulate-terminal): Avoid errors if the whole
	decoded string is eight-bit characters.  Don't attempt to save the
	string for next iteration in that case.
	* test/lisp/term-tests.el (term-decode-partial)
	(term-undecodable-input): New tests.

2019-06-15  Paul Eggert  <[email protected]>

	Port to platforms where tputs is in libtinfow

	* configure.ac (tputs_library): Also try tinfow, ncursesw (Bug#33977).

2019-02-08  Eli Zaretskii  <[email protected]>

	Improve documentation of 'date-to-time' and 'parse-time-string'

	* doc/lispref/os.texi (Time Parsing): Document
	'parse-time-string', and refer to it for the description of
	the argument of 'date-to-time'.

	* lisp/calendar/time-date.el (date-to-time): Refer in the doc
	string to 'parse-time-string' for more information about the
	format of the DATE argument.  (Bug#34303)

如果您提及修改过的函数或变量的名称,请务必完整地命名它们。不要缩写函数或变量名,也不要组合它们。后续维护者经常会搜索函数名以查找与其相关的所有更改日志条目;如果您缩写了名称,他们搜索时将找不到它。

例如,有些人倾向于通过写“* register.el ({insert,jump-to}-register)”来缩写函数名组;这不是一个好主意,因为搜索 jump-to-registerinsert-register 将找不到该条目。

用空行分隔不相关的更改日志条目。不要在条目的各个更改之间放置空行。当连续的各个更改在同一个文件中时,您可以省略文件名和星号。

通过用 ‘)’ 关闭延续行,而不是 ‘,’,并用 ‘(’ 打开延续行,来分割长的函数名列表。这使得 Emacs 中的高亮显示效果更好。这是一个例子

* src/keyboard.c (menu_bar_items, tool_bar_items)
(Fexecute_extended_command): Deal with 'keymap' property.

ChangeLog 添加条目的最简单方法是使用 Emacs 命令 M-x add-change-log-entry,或其变体 C-x 4 a (add-change-log-entry-other-window)。这会自动收集已更改的文件名和已更改的函数或变量,并根据上述约定格式化更改日志条目,让您来描述您对该函数或变量所做的更改。

当您安装其他人的更改时,请将贡献者的姓名放在更改日志条目中,而不是放在条目的文本中。换句话说,写成这样

2002-07-14  John Doe  <[email protected]>

        * sewing.c: Make it sew.

而不是这样

2002-07-14  Usual Maintainer  <[email protected]>

        * sewing.c: Make it sew.  Patch by [email protected].

当将其他人的更改提交到 VCS 中时,请使用 VCS 功能来指定作者。例如,使用 Git 时,请使用 git commit --author=作者

至于日期,应该是您应用更改的日期。(使用 VCS 时,请使用相应的命令行开关,例如 git commit --date=日期。)

现代 VCS 具有应用通过电子邮件发送的更改的命令(例如,Git 具有 git am);在这种情况下,变更集的作者和创建日期将自动从电子邮件消息中收集并记录在存储库中。如果补丁是使用合适的 VCS 命令(例如 git format-patch)准备的,则电子邮件消息正文也将具有变更集的原始作者,因此重新发送或转发消息不会干扰将更改归因于其作者。因此,我们建议您要求您的贡献者使用诸如 git format-patch 之类的命令来准备补丁。


下一节:,上一节:,上一级:更改日志   [目录][索引]

6.8.3 简单变更

某些简单的更改不需要在更改日志中提供太多细节。

如果更改的描述足够简短,则可以将其用作自身的标题行

2019-08-29  Eli Zaretskii  <[email protected]>

	* lisp/simple.el (kill-do-not-save-duplicates): Doc fix.  (Bug#36827)

当您以简单的方式更改函数的调用序列,并且您更改了函数的所有调用者以使用新的调用序列时,无需为所有已更改的调用者单独创建条目。只需在被调用函数的条目中写入“所有调用者已更改”— 像这样

* keyboard.c (Fcommand_execute): New arg SPECIAL.
All callers changed.

当您仅更改注释或文档字符串时,为文件编写条目就足够了,而无需提及函数。仅“文档修复”就足以满足更改日志的要求。

当您在许多文件中进行更改,这些更改机械地遵循一个基础更改时,描述基础更改就足够了。这是一个影响存储库中所有文件的更改示例

2019-01-07  Paul Eggert  <[email protected]>

	Update copyright year to 2019

	Run 'TZ=UTC0 admin/update-copyright $(git ls-files)'.

测试套件文件是软件的一部分,因此我们建议出于更改日志的目的将其视为代码。

对于非软件文件(手册、帮助文件、媒体文件等),没有技术上的需要来创建更改日志条目。这是因为它们不易受到难以理解的错误的影响。要纠正错误,您无需知道错误段落的历史记录;将文件所说的内容与实际情况进行比较就足够了。

但是,当项目从其贡献者那里获得版权分配时,您应该保留非软件文件的更改日志,以便使作者记录更准确。因此,我们建议为项目手册的 Texinfo 来源保留更改日志。


下一节:,上一节:,上一级:更改日志   [目录][索引]

6.8.4 条件变更

源文件通常可以包含在构建时或静态条件下有条件的代码。例如,C 程序可以包含编译时 #if 条件;用解释型语言实现的程序可以包含模块导入,其中函数定义仅针对特定版本的解释器执行;并且 Automake Makefile.am 文件可以包含变量定义或目标声明,只有在配置时的 Automake 条件为 true 时才考虑这些定义或声明。

许多更改也是有条件的:有时您添加一个新的变量、函数,甚至是完全取决于构建时条件的新程序或库。在更改日志中指示更改所适用的条件非常有用。

我们指示条件更改的约定是在条件名称周围使用方括号

条件更改可能发生在许多场景中并有许多变化,因此这里有一些示例来帮助澄清。第一个示例描述了 C、Perl 和 Python 文件中的更改,这些更改是有条件的,但没有关联的函数或实体名称

* xterm.c [SOLARIS2]: Include <string.h>.
* FilePath.pm [$^O eq 'VMS']: Import the VMS::Feature module.
* framework.py [sys.version_info < (2, 6)]: Make "with" statement
  available by importing it from __future__,
  to support also python 2.5.

为了简单起见,我们的其他示例将仅限于 C,因为将它们调整为其他语言所需的小改动应该是显而易见的。

接下来,这里是一个描述完全有条件的新定义的条目:C 宏 FRAME_WINDOW_P 仅在定义了宏 HAVE_X_WINDOWS 时才定义(和使用)

* frame.h [HAVE_X_WINDOWS] (FRAME_WINDOW_P): Macro defined.

接下来,一个条目描述了函数 init_display 中的更改,该函数的定义作为一个整体是无条件的,但更改本身包含在 ‘#ifdef HAVE_LIBNCURSES’ 条件中

* dispnew.c (init_display) [HAVE_LIBNCURSES]: If X, call tgetent.

最后,这里是一个条目,描述了仅当某个宏定义时才生效的更改

* host.c (gethostname) [!HAVE_SOCKETS]: Replace with winsock version.

上一节:,上一级:更改日志   [目录][索引]

6.8.5 指示变更的部分

使用尖括号括起来的指示来指示函数中更改的部分,该指示指示更改的部分的作用。这是一个条目,描述了函数 sh-while-getopts 中处理 sh 命令的部分的更改

* progmodes/sh-script.el (sh-while-getopts) <sh>: Handle case that
user-specified option string is empty.

下一节:,上一节:,上一级:文档   [目录][索引]

6.9 手册页

在 GNU 项目中,man 手册页是次要的。并非每个 GNU 程序都必须或期望有 man 手册页,但其中一些有。是否在程序中包含 man 手册页由您选择。

当您做出此决定时,请考虑支持 man 手册页需要每次程序更改时不断付出努力。您花在 man 手册页上的时间是用于更有用的工作的时间。

对于更改很少的简单程序,更新 man 手册页可能是一项小工作。那么,如果您有 man 手册页,则没有理由不包含它。

对于更改很大的大型程序,更新 man 手册页可能是一项繁重的负担。如果用户提出捐赠 man 手册页,您可能会发现接受这份礼物代价高昂。除非同一个人同意承担维护它的全部责任,否则最好拒绝 man 手册页 - 这样您就可以完全不用管它。如果这位志愿者稍后停止工作,那么您不必觉得自己有义务自己接手;最好将 man 手册页从发行版中撤回,直到其他人同意更新它。

当程序仅发生少量更改时,您可能会认为差异足够小,以至于 man 手册页在不更新的情况下仍然有用。如果是这样,请在 man 手册页的开头附近放置一个显眼的注释,说明您不维护它,并且 Texinfo 手册更权威。该注释应说明如何访问 Texinfo 文档。

请确保 man 手册页包含版权声明和免费许可。简单的完全许可适用于简单的 man 手册页(请参阅《GNU 维护者信息》中的其他文件的许可声明)。

对于较长的 man 手册页,其中包含足够的解释和文档,可以将其视为真正的手册,请使用 GFDL(请参阅手册的许可)。

最后,GNU help2man 程序 (https://gnu.ac.cn/software/help2man/) 是一种自动生成 man 手册页的方法,在这种情况下,是从 --help 输出生成的。这在许多情况下都足够了。


上一节:,上一级:文档   [目录][索引]

6.10 阅读其他手册

可能存在描述您正在记录的程序的非免费书籍或文档文件。

使用这些文档作为参考是可以的,就像新的代数教科书的作者可以阅读其他关于代数的书一样。任何非小说类书籍的大部分内容都包含事实,在这种情况下,是关于某个程序如何工作的事实,对于每个撰写该主题的人来说,这些事实必然是相同的。但是请注意不要从预先存在的非免费文档中复制您的轮廓结构、措辞、表格或示例。从免费文档复制可能是可以的;请与 FSF 核实个别情况。


下一主题:,上一主题:,上级:顶层   [目录][索引]

7 发布过程

发布版本不仅仅是将你的源文件打包成 tar 文件并放到 FTP 上。你应该设置你的软件,使其可以在各种系统上配置运行。你的 Makefile 应该符合下面描述的 GNU 标准,你的目录布局也应该符合下面讨论的标准。这样做可以很容易地将你的软件包纳入更大的 GNU 软件框架中。


下一主题:,上级:管理发布   [目录][索引]

7.1 配置应如何工作

每个 GNU 发行版都应该带有一个名为 configure 的 shell 脚本。这个脚本接受一些参数,描述你想编译程序的目标机器和系统类型。configure 脚本必须记录配置选项,以便它们影响编译。

这里的描述是 GNU 软件包中 configure 脚本接口的规范。许多软件包使用 GNU Autoconf(参见 Autoconf 中的简介)和/或 GNU Automake(参见 Automake 中的简介)来实现它,但你不必使用这些工具。你可以用你喜欢的任何方式实现它;例如,通过使 configure 成为完全不同的配置系统的包装器。

configure 脚本的另一种操作方式是从标准名称(如 config.h)创建一个链接到所选系统的正确配置文件。如果你使用这种技术,发行版应该包含名为 config.h 的文件。这样做是为了防止人们在没有配置的情况下构建程序。

configure 可以做的另一件事是编辑 Makefile。如果你这样做,发行版应该包含名为 Makefile 的文件。相反,它应该包含一个文件 Makefile.in,其中包含用于编辑的输入。同样,这样做是为了防止人们在没有配置的情况下构建程序。

如果 configure 确实写入了 Makefile,那么 Makefile 应该有一个名为 Makefile 的目标,该目标会导致 configure 重新运行,设置上次设置的相同配置。configure 读取的文件应该列为 Makefile 的依赖项。

所有从 configure 脚本输出的文件都应该在开头加上注释,说明它们是使用 configure 自动生成的。这样做是为了防止用户想尝试手动编辑它们。

configure 脚本应该写入一个名为 config.status 的文件,该文件描述了上次配置程序时指定的配置选项。这个文件应该是一个 shell 脚本,如果运行,它将重新创建相同的配置。

configure 脚本应该接受 ‘--srcdir=dirname’ 形式的选项,以指定查找源文件的目录(如果它不是当前目录)。这使得在单独的目录中构建程序成为可能,以便不修改实际的源目录。

如果用户没有指定 ‘--srcdir’,那么 configure 应该检查 ...,看看是否可以找到源文件。如果它在这些地方之一找到源文件,它应该从那里使用它们。否则,它应该报告无法找到源文件,并以非零状态退出。

通常支持 ‘--srcdir’ 的简单方法是在 Makefile 中编辑 VPATH 的定义。某些规则可能需要显式引用指定的源目录。为了使这成为可能,configure 可以在 Makefile 中添加一个名为 srcdir 的变量,其值正是指定的目录。

此外,‘configure’ 脚本应该接受与大多数标准目录变量对应的选项(参见 目录变量)。这是列表

--prefix --exec-prefix --bindir --sbindir --libexecdir --sysconfdir
--sharedstatedir --localstatedir --runstatedir
--libdir --includedir --oldincludedir
--datarootdir --datadir --infodir --localedir --mandir --docdir
--htmldir --dvidir --pdfdir --psdir

configure 脚本还应该接受一个参数,指定要构建程序的系统类型。此参数应如下所示

cpu-company-system

例如,基于 Athlon 的 GNU/Linux 系统可能是 ‘i686-pc-linux-gnu’。

configure 脚本需要能够解码如何描述机器的所有合理的替代方案。因此,‘athlon-pc-gnu/linux’ 将是一个有效的别名。有一个名为 config.sub 的 shell 脚本,你可以将其用作子例程来验证系统类型并将别名规范化。

configure 脚本还应该接受选项 --build=buildtype,它应该等同于一个普通的 buildtype 参数。例如,‘configure --build=i686-pc-linux-gnu’ 等同于 ‘configure i686-pc-linux-gnu’。当构建类型没有通过选项或参数指定时,configure 脚本通常应该使用 shell 脚本 config.guess 来猜测它。

允许其他选项更详细地指定机器上存在的软件或硬件,以包含或排除软件包的可选部分,或调整某些工具或其参数的名称。

--enable-feature[=parameter]

配置软件包以构建和安装名为 feature 的可选用户级设施。这允许用户选择要包含的可选功能。如果默认构建 feature,则给出可选参数 ‘no’ 应该省略 feature

任何 ‘--enable’ 选项都不应该导致一个功能替换另一个功能。任何 ‘--enable’ 选项都永远不应该用另一种有用的行为来替代一种有用的行为。‘--enable’ 的唯一正确用法是关于是否构建程序的一部分或排除它的问题。

--with-package

将安装软件包 package,因此配置此软件包以与 package 一起工作。

package 的可能值包括 ‘gnu-as’(或 ‘gas’)、‘gnu-ld’、‘gnu-libc’、‘gdb’、‘x’ 和 ‘x-toolkit’。

不要使用 ‘--with’ 选项来指定用于查找某些文件的文件名。这超出了 ‘--with’ 选项的范围。

variable=value

将变量 variable 的值设置为 value。这用于覆盖构建过程中命令或参数的默认值。例如,用户可以发出 ‘configure CFLAGS=-g CXXFLAGS=-g’ 以使用调试信息构建,而不使用默认优化。

像这样,将变量指定为 configure 的参数

./configure CC=gcc

优于在环境变量中设置它们

CC=gcc ./configure

因为它有助于稍后使用 config.status 重新创建相同的配置。但是,应该支持这两种方法。

所有 configure 脚本都应该接受所有的“详细”选项和变量设置,无论它们是否对特定软件包产生任何影响。特别是,它们应该接受任何以 ‘--with-’ 或 ‘--enable-’ 开头的选项。这样做是为了使用户能够使用一组选项一次配置整个 GNU 源代码树。

你会注意到 ‘--with-’ 和 ‘--enable-’ 类别是狭窄的:它们没有为你可能想到的任何类型的选项提供位置。这是故意的。我们想限制 GNU 软件中可能的配置选项。我们不希望 GNU 程序具有特殊的配置选项。

执行部分编译过程的软件包可能支持交叉编译。在这种情况下,程序的主机和目标机器可能不同。

configure 脚本通常应将指定的系统类型同时视为主机和目标,从而生成一个在其上运行的同一类型机器上工作的程序。

要编译一个在与构建类型不同的主机类型上运行的程序,请使用 configure 选项 --host=hosttype,其中 hosttype 使用与 buildtype 相同的语法。主机类型通常默认为构建类型。

要配置交叉编译器、交叉汇编器等,你应该指定一个与主机不同的目标,使用 configure 选项 ‘--target=targettype’。targettype 的语法与主机类型相同。因此,命令如下所示

./configure --host=hosttype --target=targettype

目标类型通常默认为主机类型。对于交叉操作没有意义的程序,不需要接受 ‘--target’ 选项,因为配置用于交叉操作的整个操作系统不是有意义的操作。

某些程序具有自动配置自身的方法。如果你的程序设置为执行此操作,则你的 configure 脚本可以简单地忽略其大多数参数。


下一主题:,上一主题:,上级:管理发布   [目录][索引]

7.2 Makefile 约定

这描述了为 GNU 程序编写 Makefile 的约定。使用 Automake 将帮助你编写遵循这些约定的 Makefile。有关可移植 Makefile 的更多信息,请参阅 POSIX可移植的 Make 编程 in Autoconf


下一主题:,上级:Makefile 约定   [目录][索引]

7.2.1 Makefile 的通用约定

每个 Makefile 都应该包含这一行

SHELL = /bin/sh

以避免在 SHELL 变量可能从环境继承的系统上出现问题。(这对于 GNU make 来说永远不是问题。)

不同的 make 程序具有不兼容的后缀列表和隐式规则,这有时会造成混淆或行为不当。因此,最好使用仅在特定 Makefile 中需要的后缀来显式设置后缀列表,如下所示

.SUFFIXES:
.SUFFIXES: .c .o

第一行清除后缀列表,第二行引入此 Makefile 中可能受隐式规则约束的所有后缀。

不要假设 . 在命令执行的路径中。当需要在 make 期间运行属于软件包一部分的程序时,请确保如果该程序是作为 make 的一部分构建的,则使用 ./,如果该文件是源代码中不变的一部分,则使用 $(srcdir)/。如果没有这些前缀之一,则使用当前搜索路径。

./构建目录)和 $(srcdir)/源目录)之间的区别很重要,因为用户可以使用 configure 的 ‘--srcdir’ 选项在单独的目录中进行构建。形式如下的规则

foo.1 : foo.man sedscript
        sed -f sedscript foo.man > foo.1

当构建目录不是源目录时将会失败,因为 foo.mansedscript 位于源目录中。

当使用 GNU make 时,依赖 ‘VPATH’ 来查找源文件,在只有一个依赖文件的情况下是可行的,因为 make 自动变量 ‘$<’ 将表示源文件所在的位置。(许多版本的 make 仅在隐式规则中设置 ‘$<’。)一个类似以下的 Makefile 目标

foo.o : bar.c
        $(CC) -I. -I$(srcdir) $(CFLAGS) -c bar.c -o foo.o

应该改写为

foo.o : bar.c
        $(CC) -I. -I$(srcdir) $(CFLAGS) -c $< -o $@

以便 ‘VPATH’ 能够正确工作。当目标有多个依赖项时,使用显式的 ‘$(srcdir)’ 是使规则良好工作的最简单方法。例如,上面 foo.1 的目标最好写成:

foo.1 : foo.man sedscript
        sed -f $(srcdir)/sedscript $(srcdir)/foo.man > $@

GNU 发行版通常包含一些非源文件——例如,Info 文件,以及来自 Autoconf、Automake、Bison 或 Flex 的输出。由于这些文件通常出现在源目录中,因此它们应该始终出现在源目录中,而不是构建目录中。因此,更新这些文件的 Makefile 规则应该将更新后的文件放在源目录中。

但是,如果文件没有出现在发行版中,那么 Makefile 就不应该将其放在源目录中,因为在通常情况下构建程序不应该以任何方式修改源目录。

尝试使构建和安装目标(至少以及它们的所有子目标)在并行 make 下正确工作。


下一节:,上一节:,上一级:Makefile 约定   [目录][索引]

7.2.2 Makefile 中的实用程序

编写 Makefile 命令(以及任何 shell 脚本,如 configure),使其在 sh (传统的 Bourne shell 和 POSIX shell)下运行,而不是 csh。不要使用 kshbash 的任何特殊功能,或传统 Bourne sh 中不广泛支持的 POSIX 功能。

用于构建和安装的 configure 脚本和 Makefile 规则不应该直接使用任何实用程序,除了以下这些:

awk cat cmp cp diff echo expr false grep install-info ln ls
mkdir mv printf pwd rm rmdir sed sleep sort tar test touch tr true

压缩程序(如 gzip)可以在 dist 规则中使用。

通常,坚持使用这些程序广泛支持的(通常是 POSIX 指定的)选项和功能。例如,不要使用 ‘mkdir -p’,尽管它很方便,因为一些系统根本不支持它,而在其他一些系统中,它对于并行执行是不安全的。有关已知的不兼容性列表,请参阅 Autoconf 中的 可移植的 Shell 编程

最好避免在 makefile 中创建符号链接,因为一些文件系统不支持它们。

用于构建和安装的 Makefile 规则也可以使用编译器和相关程序,但应该通过 make 变量来实现,以便用户可以替换其他选择。以下是我们所指的一些程序:

ar bison cc flex install ld ldconfig lex
make makeinfo ranlib texi2dvi yacc

使用以下 make 变量来运行这些程序:

$(AR) $(BISON) $(CC) $(FLEX) $(INSTALL) $(LD) $(LDCONFIG) $(LEX)
$(MAKE) $(MAKEINFO) $(RANLIB) $(TEXI2DVI) $(YACC)

当您使用 ranlibldconfig 时,应确保如果系统没有相关程序,则不会发生任何错误。安排忽略来自该命令的错误,并在命令之前打印消息以告知用户此命令的失败并不意味着有问题。(Autoconf 的 ‘AC_PROG_RANLIB’ 宏可以帮助解决这个问题。)

如果您使用符号链接,您应该为不支持符号链接的系统实现一个回退方案。

可以通过 Make 变量使用的其他实用程序包括:

chgrp chmod chown mknod

在仅适用于您知道这些实用程序存在的特定系统的 Makefile 部分(或脚本)中使用其他实用程序是可以的。


下一节:,上一节:,上一级:Makefile 约定   [目录][索引]

7.2.3 用于指定命令的变量

Makefile 应提供变量以覆盖某些命令、选项等。

特别是,您应该通过变量运行大多数实用程序。因此,如果您使用 Bison,请创建一个名为 BISON 的变量,其默认值设置为 ‘BISON = bison’,并在需要使用 Bison 时用 $(BISON) 引用它。

诸如 lnrmmv 等文件管理实用程序不需要以这种方式通过变量引用,因为用户不需要用其他程序替换它们。

每个程序名称变量都应该带有一个选项变量,用于向该程序提供选项。在程序名称变量的名称后附加 ‘FLAGS’ 即可得到选项变量名称——例如,BISONFLAGS。(C 编译器的名称 CFLAGS、yacc 的 YFLAGS 和 lex 的 LFLAGS 是此规则的例外,但我们保留它们,因为它们是标准的。)在任何运行预处理器的编译命令中使用 CPPFLAGS,并在任何进行链接的编译命令以及直接使用 ld 时使用 LDFLAGS

如果有 C 编译器选项必须用于正确编译某些文件,则不要将它们包含在 CFLAGS 中。用户希望能够自由地指定自己的 CFLAGS。相反,通过在编译命令中显式写入必要的选项或定义隐式规则,安排将必要的选项独立于 CFLAGS 传递给 C 编译器,如下所示:

CFLAGS = -g
ALL_CFLAGS = -I. $(CFLAGS)
.c.o:
        $(CC) -c $(CPPFLAGS) $(ALL_CFLAGS) $<

将 ‘-g’ 选项包含在 CFLAGS 中,因为这不是正确编译必需的。您可以将其视为仅推荐的默认值。如果软件包设置为默认使用 GCC 编译,那么您不妨也将 ‘-O’ 包含在 CFLAGS 的默认值中。

CFLAGS 放在编译命令中的最后,放在其他包含编译器选项的变量之后,以便用户可以使用 CFLAGS 来覆盖其他选项。

CFLAGS 应该在每次调用 C 编译器时使用,无论是进行编译还是链接。

每个 Makefile 都应该定义变量 INSTALL,这是将文件安装到系统中的基本命令。

每个 Makefile 还应该定义变量 INSTALL_PROGRAMINSTALL_DATA。(INSTALL_PROGRAM 的默认值应该是 $(INSTALL)INSTALL_DATA 的默认值应该是 ${INSTALL} -m 644。)然后,它应该将这些变量用作实际安装可执行文件和非可执行文件的命令。这些变量的最小使用方式如下:

$(INSTALL_PROGRAM) foo $(bindir)/foo
$(INSTALL_DATA) libfoo.a $(libdir)/libfoo.a

但是,最好支持目标文件上的 DESTDIR 前缀,如下一节所述。

在一个命令中安装多个文件是可接受的,但不是必需的,最后一个参数是目录,例如:

$(INSTALL_PROGRAM) foo bar baz $(bindir)

下一节:,上一节:,上一级:Makefile 约定   [目录][索引]

7.2.4 DESTDIR:对分阶段安装的支持

DESTDIR 是一个预先附加到每个已安装目标文件的变量,如下所示:

$(INSTALL_PROGRAM) foo $(DESTDIR)$(bindir)/foo
$(INSTALL_DATA) libfoo.a $(DESTDIR)$(libdir)/libfoo.a

DESTDIR 变量由用户在 make 命令行上指定为绝对文件名。例如:

make DESTDIR=/tmp/stage install

DESTDIR 应该仅在 install*uninstall* 目标中支持,因为这些是唯一有用的目标。

如果您的安装步骤通常会安装 /usr/local/bin/foo/usr/local/lib/libfoo.a,那么按照上面的示例调用的安装将改为安装 /tmp/stage/usr/local/bin/foo/tmp/stage/usr/local/lib/libfoo.a

以这种方式将变量 DESTDIR 前置到每个目标,提供了分阶段安装,其中已安装的文件不会直接放置到它们的预期位置,而是被复制到临时位置 (DESTDIR)。但是,已安装的文件会保留其相对目录结构,并且任何嵌入的文件名都不会被修改。

您根本不应该在 Makefile 中设置 DESTDIR 的值;这样,文件默认安装到它们的预期位置。此外,指定 DESTDIR 不应该以任何方式更改软件的操作,因此它的值不应包含在任何文件内容中。

DESTDIR 支持通常用于包创建。对于想要了解给定包将安装在何处的用户,以及允许那些通常没有权限安装到受保护区域的用户在获得这些权限之前构建和安装,它也很有帮助。最后,它可以与诸如 stow 之类的工具一起使用,在这些工具中,代码安装在一个位置,但通过使用符号链接或特殊的挂载操作使其看起来安装在其他位置。因此,我们强烈建议 GNU 包支持 DESTDIR,尽管这不是绝对要求。


下一节:,上一节:,上一级:Makefile 约定   [目录][索引]

7.2.5 用于安装目录的变量

安装目录应始终由变量命名,因此可以很容易地安装在非标准位置。下面描述了这些变量的标准名称以及它们在 GNU 包中应具有的值。它们基于标准文件系统布局;GNU/Linux 和其他现代操作系统中使用了它的变体。

安装程序在调用 make(例如,make prefix=/usr install)或 configure(例如,configure --prefix=/usr)时应该覆盖这些值。GNU 包不应该尝试猜测这些变量在它们正在安装到的系统上应该使用哪个值:使用此处指定的默认设置,以便所有 GNU 包的行为都相同,允许安装程序实现任何所需的布局。

所有安装目录及其父目录都应该在安装之前创建(如果需要)。

前两个变量设置安装的根目录。所有其他安装目录都应该是这两个目录之一的子目录,并且不应该将任何内容直接安装到这两个目录中。

prefix

一个用于构建下面列出的变量的默认值的前缀。 prefix 的默认值应该是 /usr/local。当构建完整的 GNU 系统时,前缀将为空,并且 /usr 将是 / 的符号链接。(如果您正在使用 Autoconf,请将其写为 ‘@prefix@’。)

使用与构建程序时不同的 prefix 值运行 ‘make install’ 时,不应重新编译程序。

exec_prefix

一个用于构建下面列出的一些变量的默认值的前缀。exec_prefix 的默认值应为 $(prefix)。(如果使用 Autoconf,请将其写为 ‘@exec_prefix@’。)

通常,$(exec_prefix) 用于包含特定于计算机的文件(如可执行文件和子例程库)的目录,而 $(prefix) 直接用于其他目录。

使用与构建程序时不同的 exec_prefix 值运行 ‘make install’ 时,不应重新编译程序。

可执行程序安装在以下目录之一中。

bindir

用于安装用户可以运行的可执行程序的目录。这通常应为 /usr/local/bin,但应写为 $(exec_prefix)/bin。(如果使用 Autoconf,请将其写为 ‘@bindir@’。)

sbindir

用于安装可以从 shell 运行,但通常只对系统管理员有用的可执行程序的目录。这通常应为 /usr/local/sbin,但应写为 $(exec_prefix)/sbin。(如果使用 Autoconf,请将其写为 ‘@sbindir@’。)

libexecdir

用于安装由其他程序而不是用户运行的可执行程序的目录。此目录通常应为 /usr/local/libexec,但应写为 $(exec_prefix)/libexec。(如果使用 Autoconf,请将其写为 ‘@libexecdir@’。)

所有软件包的 ‘libexecdir’ 的定义都相同,因此您应该将数据安装在其子目录中。大多数软件包将其数据安装在 $(libexecdir)/package-name/ 下,可能在其额外的子目录中,例如 $(libexecdir)/package-name/machine/version

程序执行期间使用的数据文件以两种方式分为几类。

这构成了六种不同的可能性。但是,我们不鼓励使用与架构相关的文件,除了目标文件和库。将其他数据文件设为与架构无关的要干净得多,而且通常并不难。

以下是 Makefile 应使用的变量,用于指定放置这些各种类型文件的目录

datarootdir

只读的与架构无关的数据文件的目录树的根。这通常应为 /usr/local/share,但应写为 $(prefix)/share。(如果使用 Autoconf,请将其写为 ‘@datarootdir@’。)‘datadir’ 的默认值基于此变量;‘infodir’、‘mandir’和其他也是如此。

datadir

用于安装此程序的独特的只读的与架构无关的数据文件的目录。这通常与 ‘datarootdir’ 相同,但我们使用两个单独的变量,以便您可以在不更改 Info 文件、man 页面等位置的情况下移动这些特定于程序的文件。

这通常应为 /usr/local/share,但应写为 $(datarootdir)。(如果使用 Autoconf,请将其写为 ‘@datadir@’。)

所有软件包的 ‘datadir’ 的定义都相同,因此您应该将数据安装在其子目录中。大多数软件包将其数据安装在 $(datadir)/package-name/ 下。

sysconfdir

用于安装与单个计算机相关的只读数据文件的目录,也就是说,用于配置主机的文件的目录。邮件程序和网络配置文件、/etc/passwd 等都属于此处。此目录中的所有文件都应是普通的 ASCII 文本文件。此目录通常应为 /usr/local/etc,但应写为 $(prefix)/etc。(如果使用 Autoconf,请将其写为 ‘@sysconfdir@’。)

此目录不是安装通过运行 ‘make’ 构建的可执行文件的正确位置——它们可能属于 $(libexecdir)$(sbindir)。也不要在此处安装在正常使用过程中会被修改的文件(旨在更改系统配置的程序除外)。这些文件可能属于 $(localstatedir)

sharedstatedir

用于安装程序在运行时修改的与架构无关的数据文件的目录。这通常应为 /usr/local/com,但应写为 $(prefix)/com。(如果使用 Autoconf,请将其写为 ‘@sharedstatedir@’。)

localstatedir

用于安装程序在运行时修改的,并且与特定机器相关的数据文件的目录。用户永远不需要修改此目录中的文件来配置软件包的操作;请将此类配置信息放在单独的文件中,这些文件位于 $(datadir)$(sysconfdir) 中。$(localstatedir) 通常应为 /usr/local/var,但应写为 $(prefix)/var。(如果使用 Autoconf,请将其写为 ‘@localstatedir@’。)

runstatedir

用于安装程序在运行时修改的、与特定机器相关的数据文件的目录,并且这些数据文件不需要比程序的执行时间更长——通常是长时间运行的,例如,直到下次重新启动。系统守护进程的 PID 文件是典型的用途。此外,除了可能在重新启动时之外,不应清理此目录,而普通的 /tmp (TMPDIR) 可以随意清理。这通常应为 /var/run,但应写为 $(localstatedir)/run。将其作为单独的变量允许在需要时使用 /run,例如。(如果使用 Autoconf 2.70 或更高版本,请将其写为 ‘@runstatedir@’。)

这些变量指定用于安装某些特定类型文件的目录(如果您的程序有这些文件)。每个 GNU 软件包都应具有 Info 文件,因此每个程序都需要 ‘infodir’,但并非所有程序都需要 ‘libdir’ 或 ‘lispdir’。

includedir

用于安装 C ‘#include’ 预处理器指令的用户程序要包含的头文件的目录。这通常应为 /usr/local/include,但应写为 $(prefix)/include。(如果使用 Autoconf,请将其写为 ‘@includedir@’。)

除了 GCC 之外的大多数编译器都不会在 /usr/local/include 目录中查找头文件。因此,以这种方式安装头文件仅对 GCC 有用。有时这并不是问题,因为某些库实际上只打算与 GCC 一起使用。但是某些库打算与其他编译器一起使用。它们应将头文件安装在两个位置,一个由 includedir 指定,另一个由 oldincludedir 指定。

oldincludedir

用于安装 ‘#include’ 头文件以用于 GCC 以外的编译器的目录。这通常应为 /usr/include。(如果使用 Autoconf,可以将其写为 ‘@oldincludedir@’。)

Makefile 命令应检查 oldincludedir 的值是否为空。如果为空,则不应尝试使用它;他们应该取消第二次头文件安装。

除非头文件来自同一软件包,否则软件包不应替换此目录中的现有头文件。因此,如果您的 Foo 软件包提供了一个头文件 foo.h,则如果 (1) 没有 foo.h 或 (2) 存在的 foo.h 来自 Foo 软件包,则它应将头文件安装在 oldincludedir 目录中。

要判断 foo.h 是否来自 Foo 软件包,请在文件中放置一个魔术字符串(注释的一部分),并 grep 搜索该字符串。

docdir

用于安装此软件包的文档文件(Info 文件除外)的目录。默认情况下,它应为 /usr/local/share/doc/yourpkg,但应将其写为 $(datarootdir)/doc/yourpkg。(如果使用 Autoconf,请将其写为 ‘@docdir@’。)yourpkg 子目录(可能包含版本号)可防止具有通用名称(如 README)的文件之间发生冲突。

infodir

用于安装此软件包的 Info 文件的目录。默认情况下,它应为 /usr/local/share/info,但应将其写为 $(datarootdir)/info。(如果使用 Autoconf,请将其写为 ‘@infodir@’。)infodirdocdir 分开是为了与现有实践兼容。

htmldir
dvidir
pdfdir
psdir

用于安装特定格式的文档文件的目录。默认情况下,它们都应设置为 $(docdir)。(如果使用 Autoconf,请将其写为 ‘@htmldir@’、‘@dvidir@’ 等。)提供其文档的多个翻译的软件包应将它们安装在 ‘$(htmldir)/ll、‘$(pdfdir)/ll 等中,其中 ll 是区域设置缩写,例如 ‘en’ 或 ‘pt_BR’。

libdir

用于安装目标文件和目标代码库的目录。不要在此处安装可执行文件,它们可能应该改为放在 $(libexecdir) 中。libdir 的值通常应为 /usr/local/lib,但应写为 $(exec_prefix)/lib。(如果使用 Autoconf,请将其写为 ‘@libdir@’。)

lispdir

用于安装此软件包中的任何 Emacs Lisp 文件的目录。默认情况下,它应为 /usr/local/share/emacs/site-lisp,但应将其写为 $(datarootdir)/emacs/site-lisp

如果您使用 Autoconf,请将默认值写为 ‘@lispdir@’。为了使 ‘@lispdir@’ 工作,您需要在 configure.ac 文件中使用以下几行

lispdir='${datarootdir}/emacs/site-lisp'
AC_SUBST(lispdir)
localedir

用于安装此软件包的特定于区域设置的消息目录的目录。默认情况下,它应为 /usr/local/share/locale,但应将其写为 $(datarootdir)/locale。(如果使用 Autoconf,请将其写为 ‘@localedir@’。)此目录通常每个区域设置都有一个子目录。

Unix 风格的 man 页面安装在以下位置之一

mandir

用于安装此软件包的 man 页面的顶层目录(如果有)。它通常为 /usr/local/share/man,但您应该将其写为 $(datarootdir)/man。(如果使用 Autoconf,请将其写为 ‘@mandir@’。)

man1dir

用于安装第 1 节 man 页面的目录。将其写为 $(mandir)/man1

man2dir

用于安装第 2 节 man 页面的目录。将其写为 $(mandir)/man2

不要将任何 GNU 软件的主要文档设为 man 页面。请改用 Texinfo 编写手册。Man 页面只是为了在 Unix 上运行 GNU 软件的人们,这只是一个次要应用程序。

manext

已安装的 man 页面的文件名扩展名。这应包含一个句点后跟适当的数字;它通常应为 ‘.1’。

man1ext

已安装的第 1 节 man 页面的文件名扩展名。

man2ext

已安装的第 2 节 man 页面的文件名扩展名。

如果软件包需要在手册的多个部分中安装 man 页面,请使用这些名称而不是 ‘manext’。

最后,您应该设置以下变量

srcdir

正在编译的源文件目录。此变量的值通常由 configure shell 脚本插入。(如果您正在使用 Autoconf,请使用 ‘srcdir = @srcdir@’。)

例如

# Common prefix for installation directories.
# NOTE: This directory must exist when you start the install.
prefix = /usr/local
datarootdir = $(prefix)/share
datadir = $(datarootdir)
exec_prefix = $(prefix)
# Where to put the executable for the command 'gcc'.
bindir = $(exec_prefix)/bin
# Where to put the directories used by the compiler.
libexecdir = $(exec_prefix)/libexec
# Where to put the Info files.
infodir = $(datarootdir)/info

如果您的程序将大量文件安装到用户指定的标准目录之一,将其分组到该程序特定的子目录中可能很有用。如果您这样做,您应该编写 install 规则来创建这些子目录。

不要期望用户在上面列出的任何变量的值中包含子目录名称。为安装目录设置统一变量名称的想法是为了让用户能够为几个不同的 GNU 软件包指定完全相同的值。为了使其有用,所有软件包都必须进行设计,以便在用户这样做时它们能够正常工作。

有时,并非所有这些变量都可能在当前版本的 Autoconf 和/或 Automake 中实现;但截至 Autoconf 2.60,我们认为它们全部都已实现。当有任何变量缺失时,此处的描述将作为 Autoconf 将要实现的内容的规范。作为程序员,您可以使用 Autoconf 的开发版本,或者避免使用这些变量,直到发布支持它们的稳定版本。


下一节:,上一节:,向上:Makefile 约定   [目录][索引]

7.2.6 用户的标准目标

所有 GNU 程序都应在其 Makefile 中包含以下目标

all

编译整个程序。这应该是默认目标。此目标无需重建任何文档文件;Info 文件通常应包含在发行版中,而 DVI(和其他文档格式)文件仅在明确要求时才应生成。

默认情况下,Make 规则应使用 ‘-g’ 进行编译和链接,以便可执行程序具有调试符号。否则,在发生崩溃时您基本上无能为力,并且通常很难通过全新的构建进行重现。

install

编译程序并将可执行文件、库等复制到它们实际使用的文件名位置。如果有一个简单的测试来验证程序是否正确安装,则此目标应运行该测试。

安装时不要剥离可执行文件。这有助于以后可能需要的最终调试,而且现在磁盘空间很便宜,动态加载程序通常确保在正常执行期间不会加载调试部分。需要剥离二进制文件的用户可以调用 install-strip 目标来执行此操作。

如果可能,请编写 install 目标规则,以便在刚刚执行 ‘make all’ 后,它不会修改程序构建目录中的任何内容。这对于在一个用户名下构建程序并在另一个用户名下安装程序很方便。

如果文件要安装到的目录尚不存在,则命令应创建所有这些目录。这包括指定为变量 prefixexec_prefix 的值的目录,以及所有需要的子目录。一种方法是通过下面描述的 installdirs 目标来实现。

在任何安装手册页面的命令之前使用 ‘-’,这样 make 将忽略任何错误。这是为了防止某些系统没有安装 Unix 手册页面文档系统。

安装 Info 文件的方法是使用 $(INSTALL_DATA) 将它们复制到 $(infodir)(请参阅 命令变量),然后运行 install-info 程序(如果存在)。install-info 是一个编辑 Info dir 文件以添加或更新给定 Info 文件的菜单条目的程序;它是 Texinfo 软件包的一部分。

以下是一个示例规则,用于安装 Info 文件,该规则还尝试处理一些其他情况,例如 install-info 不存在的情况。

do-install-info: foo.info installdirs
        $(NORMAL_INSTALL)
# Prefer an info file in . to one in srcdir.
        if test -f foo.info; then d=.; \
         else d="$(srcdir)"; fi; \
        $(INSTALL_DATA) $$d/foo.info \
          "$(DESTDIR)$(infodir)/foo.info"
# Run install-info only if it exists.
# Use 'if' instead of just prepending '-' to the
# line so we notice real errors from install-info.
# Use '$(SHELL) -c' because some shells do not
# fail gracefully when there is an unknown command.
        $(POST_INSTALL)
        if $(SHELL) -c 'install-info --version' \
           >/dev/null 2>&1; then \
          install-info --dir-file="$(DESTDIR)$(infodir)/dir" \
                       "$(DESTDIR)$(infodir)/foo.info"; \
        else true; fi

在编写 install 目标时,您必须将所有命令分为三类:普通命令、预安装命令和后安装命令。请参阅 安装命令类别

install-html
install-dvi
install-pdf
install-ps

这些目标安装 Info 之外的其他格式的文档;它们旨在由安装软件包的人员显式调用,如果需要该格式的话。GNU 更喜欢 Info 文件,因此这些文件必须由 install 目标安装。

当您有许多文档文件要安装时,我们建议您通过安排这些目标安装到适当安装目录(如 htmldir)的子目录中,来避免冲突和混乱。例如,如果您的软件包有多个手册,并且您希望安装具有许多文件的 HTML 文档(例如 makeinfo --html 的“拆分”模式输出),您肯定会希望使用子目录,否则不同手册中具有相同名称的两个节点将相互覆盖。

请使这些 install-format 目标调用 format 目标的命令,例如,通过使 format 成为依赖项。

uninstall

删除所有已安装的文件——由 ‘install’ 和 ‘install-*’ 目标创建的副本。

此规则不应修改完成编译的目录,而只修改文件安装到的目录。

卸载命令分为三个类别,就像安装命令一样。请参阅 安装命令类别

install-strip

install 类似,但在安装时剥离可执行文件。在简单情况下,此目标可以简单地使用 install 目标

install-strip:
        $(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' \
                install

但是,如果软件包同时安装脚本和真实的可执行文件,则 install-strip 目标不能只引用 install 目标;它必须剥离可执行文件,但不能剥离脚本。

install-strip 不应剥离正在复制以进行安装的构建目录中的可执行文件。它只应剥离已安装的副本。

通常,除非您确定程序没有错误,否则我们不建议剥离可执行文件。但是,安装剥离的可执行文件以进行实际执行,同时将未剥离的可执行文件保存在其他位置以防出现错误,可能是合理的。

clean

删除当前目录中通常由构建程序创建的所有文件。如果此 Makefile 创建了其他目录中的文件,则也删除它们。但是,不要删除记录配置的文件。还要保留可以通过构建生成但通常不会生成的文件,因为发行版附带这些文件。没有必要删除使用 ‘mkdir -p’ 创建的父目录,因为它们无论如何都可能存在。

如果 .dvi 文件不是发行版的一部分,则在此处删除它们。

distclean

删除当前目录中(或由此 Makefile 创建的)由配置或构建程序创建的所有文件。如果您在未创建任何其他文件的情况下解压缩了源代码并构建了程序,则 ‘make distclean’ 只应保留发行版中的文件。但是,没有必要删除使用 ‘mkdir -p’ 创建的父目录,因为它们无论如何都可能存在。

mostlyclean

与 ‘clean’ 类似,但可能会避免删除一些人们通常不希望重新编译的文件。例如,GCC 的 ‘mostlyclean’ 目标不会删除 libgcc.a,因为重新编译它很少是必要的,并且需要花费大量时间。

maintainer-clean

删除几乎所有可以通过此 Makefile 重建的文件。这通常包括 distclean 删除的所有内容,以及更多内容:Bison 生成的 C 源文件、标签表、Info 文件等。

我们之所以说“几乎所有”,是因为运行命令 ‘make maintainer-clean’ 不应删除 configure,即使 configure 可以使用 Makefile 中的规则重新生成。更一般地说,‘make maintainer-clean’ 不应删除运行 configure 然后开始构建程序所需的任何内容。此外,没有必要删除使用 ‘mkdir -p’ 创建的父目录,因为它们无论如何都可能存在。这些是唯一的例外;maintainer-clean 应删除所有其他可以重建的内容。

maintainer-clean’ 目标旨在由软件包的维护者使用,而不是由普通用户使用。您可能需要特殊工具来重建 ‘make maintainer-clean’ 删除的某些文件。由于这些文件通常包含在发行版中,因此我们不关心使它们易于重建。如果您发现需要再次解压缩完整的发行版,请不要责怪我们。

为了帮助用户了解这一点,特殊 maintainer-clean 目标的命令应以以下两个开始

@echo 'This command is intended for maintainers to use; it'
@echo 'deletes files that may need special tools to rebuild.'
TAGS

更新此程序的标签表。

info

生成所需的任何 Info 文件。编写规则的最佳方法如下

info: foo.info

foo.info: foo.texi chap1.texi chap2.texi
        $(MAKEINFO) $(srcdir)/foo.texi

您必须在 Makefile 中定义变量 MAKEINFO。它应该运行 makeinfo 程序,该程序是 Texinfo 发行版的一部分。

通常,GNU 发行版附带 Info 文件,这意味着 Info 文件存在于源目录中。因此,Info 文件的 Make 规则应在源目录中更新它。当用户构建软件包时,通常 Make 不会更新 Info 文件,因为它们已经是最新的。

dvi
html
pdf
ps

生成给定格式的文档文件。这些目标应始终存在,但如果无法生成给定的输出格式,则任何或全部目标都可以是空操作。这些目标不应是 all 目标的依赖项;用户必须手动调用它们。

以下是从 Texinfo 生成 DVI 文件的示例规则

dvi: foo.dvi

foo.dvi: foo.texi chap1.texi chap2.texi
        $(TEXI2DVI) $(srcdir)/foo.texi

您必须在 Makefile 中定义变量 TEXI2DVI。它应该运行程序 texi2dvi,该程序是 Texinfo 发行版的一部分。(texi2dvi 使用 TeX 来完成格式化的实际工作。TeX 不与 Texinfo 一起分发。)或者,只编写依赖项,并允许 GNU make 提供命令。

这是另一个示例,用于从 Texinfo 生成 HTML

html: foo.html

foo.html: foo.texi chap1.texi chap2.texi
        $(TEXI2HTML) $(srcdir)/foo.texi

同样,您将在 Makefile 中定义变量 TEXI2HTML;例如,它可能运行 makeinfo --no-split --htmlmakeinfo 是 Texinfo 发行版的一部分)。

dist

为此程序创建一个发行版 tar 文件。应设置 tar 文件,以便 tar 文件中的文件名以子目录名称开头,该子目录名称是它作为发行版的软件包的名称。此名称可以包括版本号。

例如,GCC 1.40 版本的发行版 tar 文件解压缩到名为 gcc-1.40 的子目录中。

执行此操作的最简单方法是创建一个适当命名的子目录,使用 lncp 将正确的文件安装到其中,然后 tar 该子目录。

使用 gzip 压缩 tar 文件。例如,GCC 1.40 版本的实际发行文件名为 gcc-1.40.tar.gz。支持其他免费压缩格式也是可以的。

dist 目标应显式依赖于发行版中的所有非源文件,以确保它们在发行版中是最新的。请参阅 制作发行版

check

执行自测(如果有)。用户必须先构建程序,然后才能运行测试,但无需安装程序;您应该编写自测,以便它们在构建程序但未安装时工作。

建议将以下目标用作常用名称,用于它们有用的程序。

installcheck

执行安装测试(如果有)。用户必须先构建并安装程序,然后才能运行测试。您不应假设 $(bindir) 在搜索路径中。

installdirs

添加一个名为“installdirs”的目标来创建文件安装目录及其父目录是很有用的。有一个名为 mkinstalldirs 的脚本可以方便地实现这一点;你可以在 Gnulib 包中找到它。你可以使用像这样的规则:

# Make sure all installation directories (e.g. $(bindir))
# actually exist by making them if necessary.
installdirs: mkinstalldirs
        $(srcdir)/mkinstalldirs $(bindir) $(datadir) \
                                $(libdir) $(infodir) \
                                $(mandir)

或者,如果你希望支持 DESTDIR (强烈推荐),

# Make sure all installation directories (e.g. $(bindir))
# actually exist by making them if necessary.
installdirs: mkinstalldirs
        $(srcdir)/mkinstalldirs \
            $(DESTDIR)$(bindir) $(DESTDIR)$(datadir) \
            $(DESTDIR)$(libdir) $(DESTDIR)$(infodir) \
            $(DESTDIR)$(mandir)

此规则不应修改编译完成的目录。它应该只创建安装目录。


上一篇:,上一级:Makefile 约定   [目录][索引]

7.2.7 安装命令类别

在编写 install 目标时,你必须将所有命令分为三类:普通命令、预安装 命令和后安装 命令。

普通命令将文件移动到它们正确的位置,并设置它们的模式。它们不得更改任何文件,除了那些完全来自它们所属的软件包的文件。

预安装和后安装命令可以更改其他文件;特别是,它们可以编辑全局配置文件或数据库。

预安装命令通常在普通命令之前执行,而后安装命令通常在普通命令之后执行。

后安装命令最常见的用途是运行 install-info。这不能用普通命令完成,因为它会更改一个并非完全且仅来自正在安装的软件包的文件(Info 目录)。它是一个后安装命令,因为它需要在安装软件包 Info 文件的普通命令之后执行。

大多数程序不需要任何预安装命令,但我们提供此功能只是以防万一需要。

要将 install 规则中的命令分为这三类,请在它们之间插入类别行。类别行指定后续命令的类别。

类别行由一个制表符和一个对特殊 Make 变量的引用组成,末尾可以有一个可选的注释。你可以使用三个变量,每个类别一个;变量名指定类别。类别行在普通执行中是空操作,因为这三个 Make 变量通常是未定义的(并且你不应该在 makefile 中定义它们)。

以下是三个可能的类别行,每个类别行都有一个解释其含义的注释:

        $(PRE_INSTALL)     # Pre-install commands follow.
        $(POST_INSTALL)    # Post-install commands follow.
        $(NORMAL_INSTALL)  # Normal commands follow.

如果你不在 install 规则的开头使用类别行,则所有命令都被归类为普通命令,直到第一个类别行。如果你不使用任何类别行,则所有命令都被归类为普通命令。

这些是 uninstall 的类别行:

        $(PRE_UNINSTALL)     # Pre-uninstall commands follow.
        $(POST_UNINSTALL)    # Post-uninstall commands follow.
        $(NORMAL_UNINSTALL)  # Normal commands follow.

通常,预卸载命令将用于删除 Info 目录中的条目。

如果 installuninstall 目标有任何充当安装子程序的依赖项,那么你应该使用类别行启动每个依赖项的命令,并使用类别行启动主目标的命令。这样,无论实际运行哪个依赖项,你都可以确保每个命令都被放置在正确的类别中。

预安装和后安装命令不应运行除以下程序之外的任何程序:

[ basename bash cat chgrp chmod chown cmp cp dd diff echo
expand expr false find getopt grep gunzip gzip
hostname install install-info kill ldconfig ln ls md5sum
mkdir mkfifo mknod mv printenv pwd rm rmdir sed sort tee
test touch true uname xargs yes

以这种方式区分命令的原因是为了制作二进制软件包。通常,二进制软件包包含所有需要安装的可执行文件和其他文件,并且有其自己的安装方法 - 因此它不需要运行普通的安装命令。但是安装二进制软件包确实需要执行预安装和后安装命令。

构建二进制软件包的程序通过提取预安装和后安装命令来工作。这是提取预安装命令的一种方法(需要 make-s 选项来静默进入子目录的消息):

make -s -n install -o all \
      PRE_INSTALL=pre-install \
      POST_INSTALL=post-install \
      NORMAL_INSTALL=normal-install \
  | gawk -f pre-install.awk

其中文件 pre-install.awk 可能包含以下内容:

$0 ~ /^(normal-install|post-install)[ \t]*$/ {on = 0}
on {print $0}
$0 ~ /^pre-install[ \t]*$/ {on = 1}

上一篇:,上一级:管理发布   [目录][索引]

7.3 发布版本

你应该使用一对版本号(主版本号和次版本号)来标识每个版本。我们不反对使用两个以上的数字,但你实际上不太可能需要它们。

Foo 69.96 版本 的发行版打包到一个名为 foo-69.96.tar.gz 的 gzip 压缩 tar 文件中。它应该解压到名为 foo-69.96 的子目录中。

构建和安装程序绝不应修改发行版中包含的任何文件。这意味着以任何方式构成程序的所有文件都必须分为源文件非源文件。源文件由人编写,并且永远不会自动更改;非源文件是由 Makefile 控制下的程序从源文件生成的。

发行版应包含一个名为 README 的文件,其中包含该软件包的概述:

当然,所有源文件都必须在发行版中。在发行版中包含非源文件及其生成的源文件是可以的,前提是它们与从中生成的源文件保持最新,并且与机器无关,以便发行版的正常构建永远不会修改它们。我们通常会包含由 Autoconf、Automake、Bison、flex、TeX 和 makeinfo 生成的非源文件;这有助于避免我们的发行版之间不必要的依赖关系,以便用户可以安装他们喜欢的任何软件包的任何版本。不要轻易地引入对其他软件的新依赖关系。

可能会通过构建和安装程序实际修改的非源文件绝不应包含在发行版中。因此,如果你确实分发非源文件,请始终确保它们在你创建新发行版时是最新的。

确保发行版中的所有文件都是世界可读的,并且目录是世界可读和世界可搜索的(八进制模式 755)。我们过去曾建议发行版中的所有目录也都是世界可写的(八进制模式 777),因为旧版本的 tar 在以非特权用户身份提取存档时会无法正常工作。但是,在创建存档时,这很容易导致安全问题,因此现在我们不建议这样做。

不要在发行版本身中包含任何符号链接。如果 tar 文件包含符号链接,那么人们甚至无法在不支持符号链接的系统上解压缩它。此外,不要在不同的目录中使用一个文件的多个名称,因为某些文件系统无法处理此问题,从而阻止解压缩发行版。

尽量确保所有文件名在 MS-DOS 上都是唯一的。MS-DOS 上的名称最多包含 8 个字符,可选地后跟一个句点和最多 3 个字符。MS-DOS 将截断句点之前和之后的多余字符。因此,foobarhacker.cfoobarhacker.o 没有歧义;它们被截断为 foobarha.cfoobarha.o,它们是不同的。

在发行版中包含你用于测试打印任何 *.texinfo*.texi 文件的 texinfo.tex 的副本。

同样,如果你的程序使用小的 GNU 软件包,如 regex、getopt、obstack 或 termcap,请将它们包含在发行版文件中。忽略它们会使发行版文件稍微小一些,但代价是用户可能不知道要获取哪些其他文件而带来不便。


下一篇:,上一篇:,上一级:顶部   [目录][索引]

8 引用非自由软件和文档

GNU 程序不应推荐、推广或授予任何非自由程序的使用合法性。专有软件是一个社会和道德问题,我们的目标是结束这个问题。我们不能阻止某些人编写专有程序,也不能阻止其他人使用它们,但我们能够并且应该拒绝向新的潜在客户宣传它们,或者给公众留下它们的存在是合法的印象。

GNU 对自由软件的定义可在 GNU 网站 https://gnu.ac.cn/philosophy/free-sw.html 上找到,自由文档的定义可在 https://gnu.ac.cn/philosophy/free-doc.html 上找到。本文档中使用的术语“自由”和“非自由”是指这些定义。

https://gnu.ac.cn/licenses/license-list.html 中列出了重要的许可证及其是否符合自由软件的资格。如果不清楚许可证是否符合自由软件的资格,请写信给 [email protected] 向 GNU 项目咨询。我们会答复,如果该许可证很重要,我们会将其添加到列表中。

当非自由程序或系统广为人知时,你可以顺便提及一下 - 这是无害的,因为可能想要使用它的用户可能已经知道了它。例如,在首先说明如何在 GNU 系统上使用你的软件包之后,可以解释如何在一些广泛使用的非自由操作系统之上构建你的软件包,或者如何将其与一些广泛使用的非自由程序一起使用。

然而,您应该只提供必要的信息,以帮助那些已经使用非自由程序的人将其与您的程序一起使用——不要提供或提及关于该专有程序的任何进一步信息,也不要暗示该专有程序会增强您的程序,或者它的存在在任何方面都是一件好事。目标应该是,已经使用该专有程序的人将获得关于如何将其与您的自由程序一起使用的必要建议,而尚未使用该专有程序的人不会看到任何可能导致他们对它产生兴趣的东西。

您不应该推荐该非自由程序的任何非自由插件,但是可以提及有助于它与您的程序一起工作的自由插件,以及如何安装这些自由插件,即使这需要运行一些非自由程序。

如果一个非自由程序或系统在您的程序领域中是晦涩难懂的,您的程序根本不应该提及或支持它,因为这样做会倾向于使该非自由程序比您的程序更受欢迎。(如果Foobar的存在在可能想使用您的程序的人中并不普遍为人所知,那么您不能指望在Foobar的用户中找到许多额外的用户。)

有时,一个程序本身是自由软件,但为了运行而依赖于一个非自由平台。例如,过去许多Java程序依赖于一些非自由的Java库。(请参阅https://gnu.ac.cn/philosophy/java-trap.html。)推荐或推广这样的程序,就是推广它需要的其他程序;因此,对前者的提及应视作对后者的提及。出于这个原因,我们在自由软件目录中列出Java程序时非常谨慎:我们想避免推广非自由的Java库。

Java不再有这个问题,但总的原则仍然相同:不要推荐、推广或使依赖非自由软件运行的程序合法化。

一些自由程序强烈鼓励使用非自由软件。一个典型的例子是mplayer。它本身是自由软件,并且自由代码可以处理某些类型的文件。然而,mplayer建议使用非自由编解码器来处理其他类型的文件,而安装mplayer的用户很可能同时安装这些编解码器。推荐mplayer实际上是在推广使用非自由编解码器。

因此,您不应该推荐强烈鼓励使用非自由软件的程序。这就是我们不在自由软件目录中列出mplayer的原因。

一个GNU软件包不应引导用户使用任何关于自由软件的非自由文档。可以包含在自由操作系统中的自由文档对于完成GNU系统或任何自由操作系统至关重要,因此鼓励它是当务之急;推荐使用我们不允许包含的文档会削弱社区生成我们可以包含的文档的动力。因此,GNU软件包永远不应该推荐非自由文档。

相比之下,在程序的注释中引用期刊文章和教科书来解释其功能是可以的,即使它们是非自由的。这是因为我们不会将这些东西包含在GNU系统中,即使它们是自由的——它们超出了软件发行版需要包含的范围。

引用一个描述或推荐非自由程序的网站就是在推广该程序,因此请不要链接到(或提及名称)包含此类材料的网站。这项政策尤其适用于GNU软件包的网页。

那么链接链呢?从几乎任何网站跟踪链接最终都可能导致推广非自由软件;这是网络本质固有的。以下是我们如何处理它的。

如果AT&T的网站推荐AT&T的非自由软件包,您不应该引用该网站;您不应该引用一个链接到AT&T网站的页面p,将其呈现为获取某些非自由程序的地方,因为页面p的这一部分本身推荐并认可了该非自由程序。

但是,如果p出于其他目的(例如长途电话服务)包含指向AT&T网站的链接,则没有理由不链接到p

如果一个网页需要用户运行该程序才能使用该网页,那么该网页会以一种隐含但特别强烈的方式推荐该程序。许多页面都包含他们以这种方式推荐的JavaScript代码。此JavaScript代码可能是自由的也可能不是自由的,但非自由的情况很常见。

如果运行非自由JavaScript代码对于您要引用该页面的目的来说是不可避免的,那么您不应该引用它。因此,如果引用该页面的目的是让人们观看视频或订阅邮件列表,而如果用户的浏览器阻止了非自由JavaScript代码,则观看或订阅将无法工作,那么请不要引用该页面。

极端情况是,有些网站甚至依赖非自由JavaScript代码来查看页面的内容。任何托管在“wix.com”上的网站都存在这个问题,其他一些网站也是如此。引导人们访问这些页面以阅读其内容实际上是在敦促他们运行那些非自由程序——所以请不要引用这些页面。(这样的页面也破坏了网络,因此它们应该因这两个原因受到谴责。)

相反,请引用该页面中的摘录以阐明您的观点,或找到其他地方引用该信息。


下一页: ,上一页:,上一级:顶部   [目录][索引]

附录 A GNU 自由文档许可证

版本 1.3,2008 年 11 月 3 日
Copyright © 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.
https://fsf.org/

Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
  1. 序言

    本许可证的目的是使手册、教科书或其他功能性和有用的文档在自由的意义上是自由的:确保每个人都拥有复制和重新分发它的有效自由,无论是否进行修改,无论商业用途还是非商业用途。其次,本许可证为作者和出版商保留了一种为其工作获得认可的方式,同时不对他人进行的修改负责。

    本许可证是一种“著作权”,这意味着文档的衍生作品本身必须以相同的意义是自由的。它补充了GNU通用公共许可证,该许可证是为自由软件设计的著作权许可证。

    我们设计本许可证是为了将其用于自由软件的手册,因为自由软件需要自由文档:自由程序应该附带提供与该软件相同自由的手册。但是本许可证不仅限于软件手册;它可以用于任何文本作品,无论主题如何,也无论是否以印刷书籍的形式出版。我们主要为目的在于指导或参考的作品推荐此许可证。

  2. 适用性和定义

    本许可证适用于任何手册或其他作品,无论以何种媒介呈现,其中包含版权所有者放置的通知,说明它可以在本许可证的条款下分发。此类通知授予在全球范围内的、免版税的、无限期的许可证,以按照此处所述的条件使用该作品。“文档”(如下)指任何此类手册或作品。任何公众成员均为被许可人,并被称为“您”。如果您以需要版权法许可的方式复制、修改或分发该作品,则表示您接受该许可证。

    文档的“修改版本”是指任何包含文档或其一部分的作品,无论是以逐字复制、修改和/或翻译成另一种语言的方式。

    “次要部分”是指文档的命名附录或前言部分,该部分专门处理文档的出版商或作者与文档的整体主题(或相关事项)之间的关系,并且不包含任何可以直接归入该整体主题的内容。(因此,如果文档部分是一本数学教科书,则次要部分不得解释任何数学内容。)这种关系可能涉及与主题或相关事项的历史联系,或者关于它们的法律、商业、哲学、道德或政治立场。

    “不变部分”是指某些次要部分,其标题在声明文档根据本许可证发布的通知中被指定为不变部分的标题。如果一个部分不符合以上次要部分的定义,则不允许将其指定为不变部分。文档可以包含零个不变部分。如果文档未识别任何不变部分,则不存在不变部分。

    “封面文字”是指在声明文档根据本许可证发布的通知中列出的某些短文本段落,作为封面文字或封底文字。封面文字最多可以有5个单词,封底文字最多可以有25个单词。

    文档的“透明”副本是指机器可读的副本,以其规范可供公众使用的格式表示,该格式适用于使用通用文本编辑器(对于由像素组成的图像)或通用绘图程序(对于绘图)直接修改文档,并且适用于文本格式化程序的输入或自动翻译为各种适用于文本格式化程序输入的格式。以其他透明文件格式制作的副本,其标记或缺少标记的排列方式旨在阻止或劝阻读者随后进行修改,则不是透明的。如果图像格式用于大量的文本,则该格式不是透明的。不“透明”的副本称为“不透明”。

    透明副本的合适格式示例包括不带标记的纯ASCII、Texinfo输入格式、LaTeX输入格式、使用公开可用的DTD的SGML或XML,以及符合标准要求的简单HTML、PostScript或为人工修改而设计的PDF。透明图像格式的示例包括PNG、XCF和JPG。不透明格式包括只能由专有文字处理器读取和编辑的专有格式,其DTD和/或处理工具不可普遍使用的SGML或XML,以及某些文字处理器仅出于输出目的而生成的机器生成的HTML、PostScript或PDF。

    “标题页”对于印刷书籍而言,指的是标题页本身,以及为了清晰地呈现本许可证要求在标题页上出现的内容而需要的后续页面。对于没有标题页的格式的作品,“标题页”指的是作品标题最显著出现附近的文本,位于正文开始之前。

    “出版者”指的是向公众分发文档副本的任何个人或实体。

    标题为“XYZ”的章节指的是文档中一个已命名的子单元,其标题要么就是精确的“XYZ”,要么包含“XYZ”,且“XYZ”之后紧跟着用另一种语言翻译的“XYZ”的文本(这里,XYZ代表下文提到的特定章节名称,例如“致谢”、“献词”、“背书”或“历史”)。当你修改文档时,“保留”此类章节的“标题”是指根据此定义,它仍然是一个标题为“XYZ”的章节。

    文档可能在声明本许可证适用于文档的声明旁边包含免责声明。这些免责声明被视为通过引用包含在本许可证中,但仅限于免除担保:这些免责声明可能产生的任何其他含义都是无效的,并且对本许可证的含义没有影响。

  3. 逐字复制

    您可以在任何媒介中复制和分发文档,无论是商业用途还是非商业用途,前提是所有副本都必须复制本许可证、版权声明以及声明本许可证适用于该文档的许可证声明,并且您不得在本许可证的条款之外添加任何其他条件。您不得使用技术措施来阻碍或控制您制作或分发的副本的阅读或进一步复制。但是,您可以接受有偿复制。如果您分发的副本数量足够大,您还必须遵守第 3 节中的条件。

    您也可以在上述相同条件下借出副本,并且可以公开展示副本。

  4. 批量复制

    如果您出版印刷版(或通常带有印刷封面的媒体中的副本)的文档,数量超过 100 份,并且该文档的许可证声明要求封面文字,您必须将副本装在封面上,封面上必须清晰易读地载有所有这些封面文字:正面封面的正面封面文字和背面封面的背面封面文字。两个封面还必须清晰易读地标明您是这些副本的出版者。正面封面必须以同等突出和可见的方式呈现完整标题的所有单词。您可以在封面上添加其他材料。对封面的修改仅限于保留文档标题并满足这些条件的,在其他方面可以视为逐字复制。

    如果任一封面的必要文字过于繁多而无法清晰地容纳,您应该将第一个列出的文字(尽可能多地容纳)放在实际封面上,并将其余文字继续放在相邻的页面上。

    如果您出版或分发数量超过 100 份的文档的不透明副本,您必须在每个不透明副本中包含一个机器可读的透明副本,或者在每个不透明副本中或与其一起声明一个计算机网络位置,普通网络用户可以使用公共标准网络协议从该位置下载完整的文档透明副本,且不包含其他材料。如果您使用后一种选项,当您开始批量分发不透明副本时,您必须采取合理谨慎的措施,以确保此透明副本在所述位置保持可访问,直到您最后一次(直接或通过您的代理或零售商)向公众分发该版本的至少一年之后。

    建议(但不是必须)您在重新分发大量副本之前尽早联系文档的作者,让他们有机会为您提供文档的更新版本。

  5. 修改

    您可以在上述第 2 节和第 3 节的条件下复制和分发文档的修改版本,前提是您根据本许可证发布修改版本,并让修改版本扮演文档的角色,从而将修改版本的发布和修改许可给任何拥有副本的人。此外,您必须在修改版本中执行以下操作:

    1. 在标题页(以及封面,如果有的话)上使用一个与文档标题不同的标题,并与以前版本的标题不同(如果以前有任何版本,则应在文档的历史记录部分中列出)。如果该版本的原始出版者允许,您可以与以前的版本使用相同的标题。
    2. 在标题页上,将修改版本中负责修改的个人或实体列为作者,以及至少五位文档的主要作者(如果少于五位,则列出所有主要作者),除非他们免除您的此项要求。
    3. 在标题页上,将修改版本的出版者名称声明为出版者。
    4. 保留文档的所有版权声明。
    5. 在其他版权声明旁边添加您修改的相应版权声明。
    6. 在版权声明之后立即添加一份许可证声明,允许公众按照本许可证的条款使用修改版本,格式如下文的附录所示。
    7. 在该许可证声明中保留文档的许可证声明中给出的不变章节的完整列表和所需的封面文字。
    8. 包含本许可证的未修改副本。
    9. 保留标题为“历史”的章节,保留其标题,并向其中添加一项,至少说明标题页上给出的修改版本的标题、年份、新作者和出版者。如果文档中没有标题为“历史”的章节,则创建一个章节,其中说明标题页上给出的文档的标题、年份、作者和出版者,然后添加一个项目,描述上一句中说明的修改版本。
    10. 保留文档中给出的公众访问文档透明副本的网络位置(如果有),以及文档中给出的之前版本(其基于之前版本)的网络位置。这些可以放在“历史”部分中。您可以省略至少在文档本身之前四年发布的某个作品的网络位置,或者如果其引用的版本的原始出版者允许。
    11. 对于任何标题为“致谢”或“献词”的章节,保留该章节的标题,并保留该章节中给出的每项贡献者致谢和/或献词的所有实质内容和语气。
    12. 保留文档的所有不变章节,文本和标题均不作修改。章节编号或等效编号不被视为章节标题的一部分。
    13. 删除任何标题为“背书”的章节。修改版本中不得包含此类章节。
    14. 不要将任何现有章节重新命名为标题为“背书”,或与任何不变章节的标题冲突。
    15. 保留任何免责声明。

    如果修改版本包含符合二级章节资格且不包含从文档复制的材料的新前页章节或附录,您可以选择将部分或全部这些章节指定为不变章节。为此,请将它们的标题添加到修改版本的许可证声明中不变章节的列表中。这些标题必须与任何其他章节标题不同。

    您可以添加一个标题为“背书”的章节,前提是它只包含各方对您的修改版本的认可,例如同行评审声明或该文本已被某个组织批准为标准的权威定义。

    您可以在修改版本中封面文字列表的末尾添加最多五个单词作为正面封面文字,添加最多 25 个单词作为背面封面文字。任何一个实体只能添加(或通过安排)一段正面封面文字和一段背面封面文字。如果文档已经包含您之前添加的或由您所代表的同一实体安排添加的相同封面的封面文字,您不能再添加一个;但是,您可以在之前添加旧封面文字的先前出版者的明确许可下替换旧封面文字。

    文档的作者和出版者不通过本许可证允许使用他们的姓名宣传或断言或暗示认可任何修改版本。

  6. 合并文档

    您可以在上述第 4 节中定义的修改版本条款下,将文档与本许可证下发布的其他文档合并,前提是您在合并中包含所有原始文档的所有不变章节,且不进行修改,并在合并作品的许可证声明中将它们全部列为不变章节,并且您保留它们的所有免责声明。

    合并的作品只需要包含本许可证的一个副本,并且多个相同的不变章节可以用一个副本替换。如果存在多个名称相同但内容不同的不变章节,则在每个此类章节的末尾,用括号括起来添加该章节的原始作者或出版者的姓名(如果已知),或者添加一个唯一的编号,从而使每个此类章节的标题都是唯一的。对合并作品的许可证声明中不变章节列表中的章节标题进行相同的调整。

    在合并中,您必须合并各个原始文档中标题为“历史”的任何章节,形成一个标题为“历史”的章节;同样合并任何标题为“致谢”的章节,以及任何标题为“献词”的章节。您必须删除所有标题为“背书”的章节。

  7. 文档集合

    您可以创建一个由文档和本许可证下发布的其他文档组成的集合,并将各个文档中本许可证的单独副本替换为一个包含在集合中的副本,前提是您在所有其他方面都遵循本许可证关于逐字复制每个文档的规则。

    您可以从此类集合中提取单个文档,并在本许可证下单独分发,前提是您在提取的文档中插入本许可证的副本,并在其他所有方面都遵循本许可证关于逐字复制该文档的规定。

  8. 与独立作品的聚合

    在存储或分发介质的卷中或之上,将文档或其衍生作品与其他单独的独立文档或作品汇编在一起,如果汇编产生的版权没有用于限制汇编用户的合法权利超出单个作品允许的范围,则称为“聚合”。当文档包含在聚合中时,本许可证不适用于聚合中不是文档本身衍生作品的其他作品。

    如果第 3 节的封面文字要求适用于这些文档副本,那么如果文档小于整个聚合的一半,则文档的封面文字可以放在将文档括在聚合中的封面上,或者如果文档采用电子形式,则放在封面的电子等效物上。否则,它们必须出现在将整个聚合括起来的印刷封面上。

  9. 翻译

    翻译被认为是一种修改,因此您可以根据第 4 节的条款分发文档的翻译版本。将不变章节替换为翻译版本需要其版权持有人的特别许可,但您可以包含一些或全部不变章节的翻译版本,以及这些不变章节的原始版本。您可以包含本许可协议的翻译版本,以及文档中的所有许可声明和任何免责声明,前提是您也包含本许可协议的原始英文版本以及这些声明和免责声明的原始版本。如果本许可协议的翻译版本与原始版本或声明或免责声明之间存在歧义,则以原始版本为准。

    如果文档中的某个章节的标题为“致谢”、“献词”或“历史”,则保留其标题的要求(第 4 节)(第 1 节)通常需要更改实际标题。

  10. 终止

    除非本许可明确规定,否则您不得复制、修改、再许可或分发该文档。任何其他尝试复制、修改、再许可或分发的行为均为无效,并将自动终止您在本许可下的权利。

    但是,如果您停止所有违反本许可的行为,那么您从特定版权持有人处获得的许可将被恢复:(a)临时恢复,除非且直到版权持有人明确且最终终止您的许可;并且 (b) 永久恢复,如果版权持有人在停止违规行为后 60 天内未通过某种合理方式通知您违规行为。

    此外,如果版权持有人通过某种合理方式通知您违规行为,并且这是您第一次收到该版权持有人关于违反本许可协议(针对任何作品)的通知,并且您在收到通知后 30 天内纠正了违规行为,则您从特定版权持有人处获得的许可将永久恢复。

    根据本节终止您的权利不会终止根据本许可从您处获得副本或权利的各方的许可。如果您的权利已被终止且未永久恢复,则收到部分或全部相同材料的副本不会使您获得任何使用该材料的权利。

  11. 本许可的未来修订

    自由软件基金会可能会不时发布 GNU 自由文档许可证的新修订版本。这些新版本在精神上将与当前版本相似,但在细节上可能会有所不同,以解决新的问题或疑虑。请参阅 https://gnu.ac.cn/licenses/

    每个版本的许可证都有一个不同的版本号。如果文档指定本许可证的特定编号版本“或任何后续版本”适用于该文档,您可以选择遵循该指定版本的条款和条件,或者遵循自由软件基金会已发布的任何后续版本(不是草案)的条款和条件。如果文档未指定本许可证的版本号,您可以选择自由软件基金会发布的任何版本(不是草案)。如果文档指定代理可以决定可以使用本许可证的哪些未来版本,则该代理公开声明接受某个版本即永久授权您为该文档选择该版本。

  12. 重新许可

    “大型多人协作站点”(或“MMC 站点”)是指任何发布受版权保护作品并为任何人编辑这些作品提供突出设施的万维网服务器。任何人都可以编辑的公共维基就是一个这样的服务器的示例。站点中包含的“大型多人协作”(或“MMC”)是指因此在 MMC 站点上发布的任何一组受版权保护的作品。

    “CC-BY-SA”是指 Creative Commons 公司发布的 Creative Commons 署名-相同方式共享 3.0 许可,该公司是一家总部位于加利福尼亚州旧金山的非营利性公司,以及该组织发布的未来该许可的版权复制版本。

    “纳入”是指将文档的全部或部分作为另一个文档的一部分发布或重新发布。

    如果一个 MMC 根据本许可获得许可,并且所有最初在本 MMC 之外的其他地方根据本许可发布,随后全部或部分纳入 MMC 的作品,(1)没有封面文字或不变章节,并且(2)因此在 2008 年 11 月 1 日之前被纳入,则该 MMC“符合重新许可的条件”。

    如果 MMC 符合重新许可的条件,则 MMC 站点的运营者可以在 2009 年 8 月 1 日之前的任何时间在同一站点上根据 CC-BY-SA 重新发布站点中包含的 MMC。

附录:如何将本许可用于您的文档

要在您编写的文档中使用本许可,请在文档中包含本许可的副本,并在标题页之后放置以下版权和许可声明

  Copyright (C)  year  your name.
  Permission is granted to copy, distribute and/or modify this document
  under the terms of the GNU Free Documentation License, Version 1.3
  or any later version published by the Free Software Foundation;
  with no Invariant Sections, no Front-Cover Texts, and no Back-Cover
  Texts.  A copy of the license is included in the section entitled ``GNU
  Free Documentation License''.

如果您有不变章节、封面文字和封底文字,请将“带有……文字”行替换为以下内容

    with the Invariant Sections being list their titles, with
    the Front-Cover Texts being list, and with the Back-Cover Texts
    being list.

如果您有不带封面文字的不变章节,或者这三者的其他组合,请合并这两个替代方案以适应具体情况。

如果您的文档包含非平凡的程序代码示例,我们建议在您选择的自由软件许可(例如 GNU 通用公共许可证)下并行发布这些示例,以允许在自由软件中使用它们。


上一个:,上一个:顶部   [目录][索引]

索引

跳转到:  #   -  
A   B   C   D   E   F   G   H   I   K   L   M   N   O   P   Q   R   S   T   U   V   W   X  
索引条目 章节

#
#endif,注释: 注释

-
--help’ 输出: --help
--version’ 输出: --version
-Wall’ 编译器选项: 语法约定

A
接受贡献: 贡献
错误报告的地址: --help
ANSI C 标准: 标准 C
数据的任意限制: 语义
ASCII 字符: 字符集
autoconf: 系统可移植性
避免专有代码: 阅读非自由代码

B
一批更改,在更改日志中: 更改日志概念
行为,取决于程序的名称: 用户界面
二进制包: 安装命令类别
bindir: 目录变量
大括号,在 C 源代码中: 格式化
错误报告: --help
[email protected] 电子邮件地址: 序言

C
C 兼容性: 兼容性
C 库函数和可移植性: 系统函数
程序的规范名称: --version
将指针转换为整数: CPU 可移植性
CGI 程序,标准选项: 命令行界面
更改日志: 更改日志
更改日志,条件更改: 条件更改
更改日志,样式: 更改日志的样式
变更集,在更改日志中: 更改日志概念
字符集: 字符集
clang: 语法约定
命令行参数,解码: 语义
命令行界面: 命令行界面
注释: 注释
与 C 和 POSIX 标准的兼容性: 兼容性
编译器警告: 语法约定
条件更改和更改日志: 条件更改
条件语句的注释: 注释
configure: 配置
control-L: 格式化
makefile 的约定: Makefile 约定
CORBA: 图形界面
手册的致谢: 手册致谢

D
D-bus: 图形界面
Gnulib 中的数据结构: 系统函数
数据类型和可移植性: CPU 可移植性
描述,更改日志条目: 更改日志概念
DESTDIR: DESTDIR
创建安装目录: 目录变量
文档: 文档
doschk: 名称
双引号: 引号字符
下载本手册: 序言
动态插件: 动态插件接口

E
编码: 字符集
enum 类型,格式化: 格式化
错误消息: 语义
错误消息,格式化: 错误
Gnulib 中的错误消息: 系统函数
exec_prefix: 目录变量
表达式,拆分: 格式化

F
FDL,GNU 自由文档许可证: GNU 自由文档许可证
文件使用: 文件使用
文件名限制: 名称
格式化错误消息: 错误
格式化源代码: 格式化
换页: 格式化
函数参数,声明: 语法约定
函数定义,格式化: 格式化
函数原型: 标准 C

G
getopt: 命令行界面
gettext: 国际化
GNOME: 图形界面
GNOME 和 Guile: 源语言
Gnulib: 系统函数
gnustandards 项目存储库: 序言
[email protected] 邮件列表: 序言
GNUstep: 图形界面
图形用户界面: 图形界面
重音符: 引号字符
GTK+: 图形界面
Guile: 源语言

H
标题行,更改日志条目: 更改日志概念

I
隐式 int: 语法约定
不可能的条件: 语义
创建安装目录: 目录变量
分阶段安装: DESTDIR
界面样式: 图形界面
国际化: 国际化

K
键盘界面: 图形界面

L
LDAP: OID 分配
左引号: 引号字符
法律方面: 法律问题
法律文件: 贡献
源代码行的长度: 格式化
libexecdir: 目录变量
libiconv: 语义
: 
库函数和可移植性: 系统函数
库界面: 图形界面
手册的许可: 手册的许可
行长度: 格式化
lint: 语法约定
特定于区域设置的引号字符: 引号字符
长选项名称: 选项表
长命名选项: 命令行界面

M
makefile 的约定: Makefile 约定
malloc 返回值: 语义
man 页面: Man 页面
手册结构: 手册结构详细信息
内存分配失败: 语义
内存泄漏: 内存使用
内存使用: 内存使用
消息文本和国际化: 国际化
mmap: Mmap
一行中的多个变量: 语法约定

N
变量、函数和文件的名称: 名称
NEWS 文件: NEWS 文件
非 ASCII 字符: 字符集
非 POSIX 系统和可移植性: 系统可移植性
非标准扩展: 使用扩展
NUL 字符: 语义

O
GNU 的 OID 分配: OID 分配
左大括号: 格式化
起始引号: 引号字符
可选特性,配置时: 配置
兼容性选项: 兼容性
标准命令行选项: 命令行界面
输出设备和程序的行为: 用户界面

P
打包: 发布
PATH_INFO,指定标准选项作为: 命令行界面
插件: 动态插件接口
plugin_is_GPL_compatible: 动态插件接口
可移植性,和数据类型: CPU 可移植性
可移植性,和库函数: 系统函数
可移植性,在系统类型之间: 系统可移植性
POSIX 兼容性: 兼容性
POSIX 函数,和可移植性: 系统函数
POSIXLY_CORRECT,环境变量: 兼容性
安装后命令: 安装命令类别
安装前命令: 安装命令类别
prefix: 目录变量
程序配置: 配置
程序设计: 设计建议
程序名称及其行为: 用户界面
程序的规范名称: --version
编程语言: 源语言
专有程序: 阅读非自由代码

Q
引号字符: 引号字符

R
README 文件: 发布
对非自由材料的引用: 参考资料
发布: 管理发布
右引号: 引号字符

S
gnustandards 的 Savannah 仓库: 序言
sbindir: 目录变量
信号处理: 语义
单引号: 引号字符
SNMP: OID 分配
软件取证,和变更日志: 更改日志
左括号前的空格: 格式化
分阶段安装: DESTDIR
标准命令行选项: 命令行界面
Makefile 的标准: Makefile 约定
struct 类型,格式化: 格式化
语法约定: 语法约定

T
长选项表: 选项表
临时文件: 语义
临时变量: 语法约定
texinfo.tex,在发行版中: 发布
标题,变更日志条目: 更改日志概念
TMPDIR 环境变量: 语义
商标: 商标

U
用户界面风格: 图形界面

V
valgrind: 内存使用
VCS: 更改日志
版本控制系统,用于保存变更日志: 更改日志
版本号,用于发布: 发布

W
从哪里获取 standards.texi: 序言

X
X.509: OID 分配
xmalloc,在 Gnulib 中: 系统函数

跳转到:  #   -  
A   B   C   D   E   F   G   H   I   K   L   M   N   O   P   Q   R   S   T   U   V   W   X