第 4 章。慢速移植

好吧,事情并没有那么简单,该端口需要一些修改才能使其正常工作。在本节中,我们将逐步解释如何修改它以使其适应端口范式。

4.1. 工作原理

首先,以下是用户在端口目录中首次输入 make 时发生的一系列事件。在阅读本文时,在另一个窗口中打开 bsd.port.mk 文件将有助于理解它。

但不用担心,并不是所有人都能完全理解 bsd.port.mk 的工作原理...​ :-)

  1. 会执行 fetch 目标。fetch 目标负责确保压缩包在 DISTDIR 目录中本地存在。如果 fetchDISTDIR 目录中找不到所需的文件,它将查找 MASTER_SITES(在 Makefile 中设置)以及我们用于备份 distfiles 的 FTP 镜像。然后,它将尝试使用 FETCH 获取指定的发布文件,前提是请求站点可以直接访问互联网。如果成功,它将把文件保存到 DISTDIR 目录以便将来使用,然后继续执行。

  2. 会执行 extract 目标。它会在 DISTDIR 中查找端口的发布文件(通常是一个压缩的 tarball),并将其解压缩到由 WRKDIR 指定的临时子目录中(默认值为 work)。

  3. 会执行 patch 目标。首先,应用 PATCHFILES 中定义的任何补丁。其次,如果在 PATCHDIR 中找到任何名为 patch-* 的补丁文件(默认为 files 子目录),则此时会按字母顺序应用这些补丁。

  4. 会执行 configure 目标。它可以执行很多不同的事情。

    1. 如果存在 scripts/configure 文件,则会执行它。

    2. 如果设置了 HAS_CONFIGUREGNU_CONFIGURE,则会执行 WRKSRC/configure 文件。

  5. 会执行 build 目标。它负责进入端口的私有工作目录 (WRKSRC) 并构建它。

  6. 会执行 stage 目标。它将最终的构建文件集放入一个临时目录中 (STAGEDIR,参见 暂存)。此目录的层次结构与将安装软件包的系统的层次结构相同。

  7. 会执行 package 目标。它使用 stage 目标创建的临时目录中的文件以及端口的 pkg-plist 创建一个软件包。

  8. 会执行 install 目标。它将 package 目标创建的软件包安装到主机系统中。

以上是默认操作。此外,还可以定义 pre-somethingpost-something 目标,或者在 scripts 子目录中放置具有这些名称的脚本,它们将在默认操作执行之前或之后执行。

例如,如果在 Makefile 中定义了 post-extract 目标,并在 scripts 子目录中存在一个名为 pre-build 的文件,则在执行常规提取操作后会调用 post-extract 目标,在执行默认构建规则之前会执行 pre-build 文件。建议使用 Makefile 目标(如果操作足够简单),因为这样更容易让人理解端口需要什么样的非默认操作。

默认操作由 bsd.port.mk 中的 do-something 目标执行。例如,用于提取端口的命令位于 do-extract 目标中。如果默认目标不能正确执行,请在 Makefile 中重新定义 do-something 目标。

"主" 目标(例如,extractconfigure 等)只做一件事,就是确保所有阶段都已完成,并调用实际的目标或脚本,不建议修改它们。要修复提取操作,请修复 do-extract,但永远不要改变 extract 的操作方式!此外,post-deinstall 目标是无效的,端口基础设施不会运行它。

现在,我们已经更好地理解了用户输入 make install 时会发生什么,接下来让我们看一下创建完美端口的推荐步骤。

4.2. 获取原始源代码

获取原始源代码(通常)作为一个压缩的 tarball (foo.tar.gzfoo.tar.bz2),并将其复制到 DISTDIR 中。始终使用主流源代码,尽可能地这样做。

设置 MASTER_SITES 变量以反映原始压缩包所在的地址。大多数主流站点在 bsd.sites.mk 文件中都有简写定义。请尽可能使用这些站点以及相关定义,以帮助避免在源代码库中重复相同信息的问题。由于这些站点往往会随着时间的推移而发生变化,因此这会给所有参与者带来维护上的噩梦。有关详细信息,请参见 MASTER_SITES

如果没有与网络良好连接的 FTP/HTTP 站点,或者只能找到格式非常不标准的站点,请在可靠的 FTP 或 HTTP 服务器上放一个副本(例如,主页)。

如果找不到放置 distfiles 的方便可靠的地方,我们可以将其放在 ftp.FreeBSD.org 上,但这是最不理想的解决方案。distfiles 必须放在某人的 freefall 帐户的 ~/public_distfiles/ 目录中。请提交端口的人员这样做。他们还将 MASTER_SITES 设置为 LOCAL/username,其中 username 是他们的 FreeBSD 集群登录名。

如果端口的 distfiles 经常发生变化,而作者没有进行任何版本的更新,请考虑将 distfiles 放置到主页上,并将其列为第一个 MASTER_SITES。尽量说服端口作者不要这样做,因为建立某种源代码控制确实很有帮助。托管特定版本可以防止用户收到 checksum mismatch 错误,并减少我们 FTP 站点维护人员的工作量。此外,如果端口只有一个主站点,建议在主页上放置一个备份,并将其列为第二个 MASTER_SITES

如果端口需要在互联网上可用的其他补丁,请也获取它们并将它们放到 DISTDIR 中。不用担心它们是否来自与主源压缩包不同的站点,我们有一种方法来处理这些情况(请参见下面对 PATCHFILES 的描述)。

4.3. 修改端口

在私有目录中解压压缩包的副本,并进行必要的修改,以便端口在当前版本的 FreeBSD 下正确编译。仔细跟踪这些步骤,因为这些步骤将很快用于自动化流程。所有内容,包括文件的删除、添加或修改,都必须在端口完成时使用自动化脚本或补丁文件来完成。

如果端口需要大量用户交互/自定义才能编译或安装,请查看 Larry Wall 的经典 Configure 脚本之一,并尝试做类似的事情。新端口集合的目标是尽可能地让最终用户可以"即插即用"地使用每个端口,同时使用最少的磁盘空间。

除非另有说明,否则贡献给 FreeBSD 端口集合的补丁文件、脚本和其他文件都假设受标准 BSD 版权条件的约束。

4.4. 补丁

在准备端口时,可以使用 diff(1) 记录添加或更改的文件,以便稍后将其提供给 patch(1)。使用典型文件执行此操作涉及在进行任何更改之前使用 .orig 后缀保存原始文件的副本。

% cp file file.orig

完成所有更改后,cd 回到端口目录。使用 make makepatchfiles 目录中生成更新的补丁文件。

使用 BINARY_ALIAS 在构建期间替换硬编码的命令,并避免修补构建文件。有关更多信息,请参见 使用 BINARY_ALIAS 重命名命令而不是修补构建

4.4.1. 补丁的一般规则

补丁文件存储在 PATCHDIR 中,通常为 files/,它们将从这里自动应用。所有补丁必须相对于 WRKSRC。通常,WRKSRCWRKDIR 的子目录,WRKDIR 是解压 distfiles 的目录。使用 make -V WRKSRC 查看实际路径。补丁文件名应遵循以下规则

  • 避免有多个补丁修改同一个文件。例如,如果 patch-foobar.cpatch-foobar.c2 都对 ${WRKSRC}/foobar.c 文件进行修改,就会使它们变得脆弱且难以调试。

  • 在创建补丁文件名时,将每个下划线 (_) 替换为两个下划线 (__),并将每个斜杠 (/) 替换为一个下划线 (_)。例如,要修补名为 src/freeglut_joystick.c 的文件,请将相应的补丁命名为 patch-src_freeglut__joystick.c。不要将补丁命名为 patch-aapatch-ab。始终使用路径和文件名作为补丁名称。使用 make makepatch 会自动生成正确的名称。

  • 如果更改是相关的,并且补丁名称合适,则一个补丁可以修改多个文件。例如,patch-add-missing-stdlib.h

  • 仅使用字符 [-+._a-zA-Z0-9] 来命名补丁。特别是,不要使用 :: 作为路径分隔符,请改用 _

尽量减少补丁中非功能性空白变化的数量。在开源世界中,项目通常会共享大量代码库,但遵守不同的风格和缩进规则。当从一个项目中提取一个工作功能来修复另一个项目中类似的区域时,请务必小心:生成的补丁可能充满了非功能性变化。这不仅会增加端口存储库的大小,还会使人们难以找到问题的原因以及到底发生了什么变化。

如果必须删除文件,请在 post-extract 目标中执行,而不是作为补丁的一部分。

4.4.2. 手动生成补丁

手动创建补丁通常没有必要。如本节前面所述的自动补丁生成是首选方法。但是,偶尔可能需要手动打补丁。

补丁保存到名为 patch-* 的文件中,其中 * 表示被修补的文件的路径名,例如 patch-Imakefilepatch-src-config.h。文件名不以 patch- 开头的补丁不会自动应用。

修改文件后,使用 diff(1) 记录原始版本和修改版本之间的差异。-u 使 diff(1) 生成“统一”的差异,这是首选形式。

% diff -u file.orig file > patch-pathname-file

为新添加的文件生成补丁时,使用 -N 告诉 diff(1) 将不存在的原始文件视为存在但为空。

% diff -u -N newfile.orig newfile > patch-pathname-newfile

使用 diff(1) 的递归 (-r) 选项来生成补丁是可以的,但请查看生成的补丁以确保其中没有不必要的垃圾。特别是,两个备份文件之间的差异,当端口使用 Imake 或 GNU configureMakefile,等等,都是不必要的,必须删除。如果需要编辑 configure.in 并运行 autoconf 来重新生成 configure,请不要进行 configure 的差异(它通常会增长到几千行!)。相反,定义 USES=autoreconf 并进行 configure.in 的差异。

4.4.3. 简单自动替换

可以使用 sed(1) 的就地模式直接从端口 Makefile 中执行简单的替换。当更改使用变量的值时,这很有用。

post-patch:
	@${REINPLACE_CMD} -e 's|/usr/local|${PREFIX}|g' ${WRKSRC}/Makefile

只使用 sed(1) 来替换变量内容。您必须使用补丁文件而不是 sed(1) 来替换静态内容。

通常,被移植的软件在源文件中使用 CR/LF 约定。这可能会导致进一步的修补、编译器警告或脚本执行问题(例如 /bin/sh^M not found)。要快速将所有文件从 CR/LF 转换为 LF,请将此条目添加到端口 Makefile

USES=	dos2unix

可以给出要转换的特定文件的列表。

USES=	dos2unix
DOS2UNIX_FILES=	util.c util.h

使用 DOS2UNIX_REGEX 跨子目录转换一组文件。它的参数是 find(1) 兼容的正则表达式。有关格式的更多信息,请参见 re_format(7)。此选项对于转换给定扩展名的所有文件很有用。例如,转换所有源代码文件,保持二进制文件完整

USES=	dos2unix
DOS2UNIX_REGEX=	.*\.([ch]|cpp)

类似的选项是 DOS2UNIX_GLOB,它为其中列出的每个元素运行 find

USES=	dos2unix
DOS2UNIX_GLOB=	*.c *.cpp *.h

可以设置转换的基目录。当有多个 distfile 且多个包含需要换行符转换的文件时,这很有用。

USES=	dos2unix
DOS2UNIX_WRKSRC=	${WRKDIR}

4.4.4. 条件打补丁

一些端口需要仅针对特定 FreeBSD 版本或启用或禁用特定选项时才应用的补丁。条件补丁通过将补丁文件的完整路径放在 EXTRA_PATCHES 中来指定。条件补丁文件名通常以 extra- 开头,尽管这不是必需的。但是,它们的文件名不能patch- 开头。如果它们这样做了,它们将被框架无条件地应用,这对条件补丁来说是不希望的。

示例 1. 为特定 FreeBSD 版本应用补丁
.include <bsd.port.options.mk>

# Patch in the iconv const qualifier before this
.if ${OPSYS} == FreeBSD && ${OSVERSION} < 1100069
EXTRA_PATCHES=	${PATCHDIR}/extra-patch-fbsd10
.endif

.include <bsd.port.mk>
示例 2. 可选应用补丁

选项 需要补丁时,使用 opt_EXTRA_PATCHESopt_EXTRA_PATCHES_OFF 使补丁有条件地取决于 opt 选项。有关更多信息,请参见 通用变量替换

OPTIONS_DEFINE=	  FOO BAR
FOO_EXTRA_PATCHES=  ${PATCHDIR}/extra-patch-foo
BAR_EXTRA_PATCHES_OFF=	${PATCHDIR}/extra-patch-bar.c \
		${PATCHDIR}/extra-patch-bar.h
示例 3. 使用 EXTRA_PATCHES 与目录

有时,一个功能需要很多补丁,在这种情况下,可以将 EXTRA_PATCHES 指向一个目录,它会自动应用其中所有名为 patch-* 的文件。

${PATCHDIR} 中创建一个子目录,并将补丁移动到其中。例如

% ls -l files/foo-patches
-rw-r--r--  1 root  wheel    350 Jan 16 01:27 patch-Makefile.in
-rw-r--r--  1 root  wheel   3084 Jan 18 15:37 patch-configure.ac

然后将此添加到 Makefile

OPTIONS_DEFINE=	FOO
FOO_EXTRA_PATCHES=	${PATCHDIR}/foo-patches

然后框架将使用该目录中所有名为 patch-* 的文件。

4.5. 配置

configure 脚本中包含任何额外的自定义命令,并将它保存在 scripts 子目录中。如上所述,也可以使用 Makefile 目标和/或名为 pre-configurepost-configure 的脚本来实现。

4.6. 处理用户输入

如果端口需要用户输入才能构建、配置或安装,请在 Makefile 中设置 IS_INTERACTIVE。这将允许“隔夜构建”跳过它。如果用户在他们的环境中设置了变量 BATCH(并且如果用户设置了变量 INTERACTIVE,那么只有那些需要交互的端口才会被构建)。这将为不断构建端口的机器组节省大量浪费的时间(见下文)。

还建议,如果对问题有合理的默认答案,则使用 PACKAGE_BUILDING 来关闭交互式脚本。这将使我们能够为 CDROM 和 FTP 构建软件包。


最后修改时间:2024 年 3 月 9 日,作者 Danilo G. Baio