# 5.4.源代码包文件

## 5.4.1. `DISTNAME`

`DISTNAME` 是由软件的作者指定的名称。`DISTNAME` 默认为 `${PORTNAME}-${DISTVERSIONPREFIX}${DISTVERSION}${DISTVERSIONSUFFIX}`，如果未设置，则 `DISTVERSION` 默认为 `${PORTVERSION}`，因此只有在必要时才需要覆盖 `DISTNAME`。`DISTNAME` 只在两个地方使用。首先，分发文件列表（`DISTFILES`）默认值为 `${DISTNAME}${EXTRACT_SUFX}`。其次，分发文件预计会解压到一个名为 `WRKSRC` 的子目录中，默认值为 **work/${DISTNAME}**。

一些厂商的分发名称不适合 `${PORTNAME}-${PORTVERSION}` 方案时，可以通过设置 `DISTVERSIONPREFIX`、`DISTVERSION` 和 `DISTVERSIONSUFFIX` 来自动处理。`PORTVERSION` 会从 `DISTVERSION` 自动派生。

> **重要**
>
> `PORTVERSION` 和 `DISTVERSION` 只能设置其中一个。如果 `DISTVERSION` 无法派生出正确的 `PORTVERSION`，则不要使用 `DISTVERSION`。

如果上游版本方案可以派生为与 Port 兼容的版本方案，请设置一个变量来表示上游版本，*不要* 使用 `DISTVERSION` 作为变量名。根据你创建的变量将 `PORTVERSION` 设置为计算出的版本，并相应地设置 `DISTNAME`。

如果上游版本方案无法轻松转换为兼容的 Port 版本值，请将 `PORTVERSION` 设置为一个合理的值，并将 `DISTNAME` 设置为包含上游版本的 `PORTNAME`。

**示例 6. 手动派生 `PORTVERSION`**

BIND9 使用的版本方案与 Port 版本方案不兼容（版本中包含 `-`），且无法使用 `DISTVERSION` 派生，因为在 9.9.9 版本发布后，它将以 `9.9.9-P1` 的形式发布“补丁级别”。`DISTVERSION` 会将其转换为 `9.9.9.p1`，而在 Port 版本方案中，`9.9.9.p1` 表示 `9.9.9` 的预发布 1，意味着它是在 9.9.9 之前，而不是之后。因此，`PORTVERSION` 是从 `ISCVERSION` 变量手动派生的，输出为 `9.9.9p1`。

通过使用 [pkg-version(8)](https://man.freebsd.org/cgi/man.cgi?query=pkg-version\&sektion=8\&format=html) 的 `-t` 参数检查 Port 框架和 pkg 对版本的排序：

```sh
% pkg version -t 9.9.9 9.9.9.p1
> ①
% pkg version -t 9.9.9 9.9.9p1
< ②
```

* ① `>` 符号表示传递给 `-t` 的第一个参数大于第二个参数。`9.9.9` 排在 `9.9.9.p1` 后面。
* ② `<` 符号表示传递给 `-t` 的第一个参数小于第二个参数。`9.9.9` 排在 `9.9.9p1` 前面。

例如，在 Port **Makefile** 中，如 [dns/bind99](https://cgit.freebsd.org/ports/tree/dns/bind99/)，它通过以下方式实现：

```makefile
PORTNAME=	bind
PORTVERSION=	${ISCVERSION:S/-P/P/:S/b/.b/:S/a/.a/:S/rc/.rc/}
CATEGORIES=	dns net
MASTER_SITES=	ISC/bind9/${ISCVERSION}
PKGNAMESUFFIX=	99
DISTNAME=	${PORTNAME}-${ISCVERSION}

MAINTAINER=	mat@FreeBSD.org
COMMENT=	BIND DNS suite with updated DNSSEC and DNS64
WWW=		https://www.isc.org/bind/

LICENSE=	ISCL

# ISC 发布的版本如 9.8.0-P1 或 9.8.1rc1，与我们的版本管理不兼容。
ISCVERSION=	9.9.9-P6
```

在此示例中，定义了上游版本为 `ISCVERSION`，并带有说明 *为什么* 需要它。使用 `ISCVERSION` 来获得兼容 Port 的 `PORTVERSION`，并使用 `ISCVERSION` 直接获得正确的分发文件 URL，并以 `ISCVERSION` 来命名分发文件。

**示例 7. 从 `PORTVERSION` 派生 `DISTNAME`**

有时，分发文件名称与软件的版本几乎没有关系。

在 [comms/kermit](https://cgit.freebsd.org/ports/tree/comms/kermit/) 中，分发文件中仅包含版本的最后一部分：

```makefile
PORTNAME=	kermit
PORTVERSION=	9.0.304
CATEGORIES=	comms ftp net
MASTER_SITES=	ftp://ftp.kermitproject.org/kermit/test/tar/
DISTNAME=	cku${PORTVERSION:E}-dev20
```

其中，`:E` 是 [make(1)](https://man.freebsd.org/cgi/man.cgi?query=make\&sektion=1\&format=html) 修饰符，用于返回变量的后缀，在此示例中是 `304`。因此，分发文件会正确生成为 `cku304-dev20.tar.gz`。

**示例 8. 特殊情况 1**

有时，软件名称、版本及其分发文件之间没有任何关联。

例如在 [audio/libworkman](https://cgit.freebsd.org/ports/tree/audio/libworkman/) 中：

```makefile
PORTNAME=       libworkman
PORTVERSION=    1.4
CATEGORIES=     audio
MASTER_SITES=   LOCAL/jim
DISTNAME=       ${PORTNAME}-1999-06-20
```

**示例 9. 特殊情况 2**

在 [comms/librs232](https://cgit.freebsd.org/ports/tree/comms/librs232/) 中，分发文件没有版本号，因此需要使用 [`DIST_SUBDIR`](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#makefile-dist_subdir)：

```makefile
PORTNAME=       librs232
PORTVERSION=    20160710
CATEGORIES=     comms
MASTER_SITES=   http://www.teuniz.net/RS-232/
DISTNAME=       RS-232
DIST_SUBDIR=	${PORTNAME}-${PORTVERSION}
```

> **注意**
>
> `PKGNAMEPREFIX` 和 `PKGNAMESUFFIX` 不会影响 `DISTNAME`。还需要注意的是，如果 `WRKSRC` 等于 **${WRKDIR}/${DISTNAME}**，而原始源归档的名称与 `${PORTNAME}-${PORTVERSION}${EXTRACT_SUFX}` 不同，则只需定义 `DISTFILES`，而不需要同时定义 `DISTNAME` 和 `WRKSRC`（以及可能的 `EXTRACT_SUFX`）。

## 5.4.2. `MASTER_SITES`

在 `MASTER_SITES` 中记录指向原始 tarball 的 FTP/HTTP URL 的目录部分。不要忘记尾部的斜杠（/\*\*/）！

如果系统上找不到分发文件，`make` 宏将尝试使用此规范通过 `FETCH` 获取分发文件。

建议在此列表中包含多个站点，最好来自不同的大陆。这将有助于避免广域网问题。

> **重要**
>
> `MASTER_SITES` 不能为空。它必须指向实际托管分发文件的站点。不能指向网页档案或 FreeBSD 分发文件缓存站点。唯一的例外是没有分发文件的 Ports。例如，meta-Ports 没有分发文件，因此不需要设置 `MASTER_SITES`。

### 5.4.2.1. 使用 `MASTER_SITE_*` 变量

对于流行的档案站点，如 SourceForge（`SOURCEFORGE`）、GNU（`GNU`）或 Perl CPAN（`PERL_CPAN`），提供了简写的变量。`MASTER_SITES` 可以直接使用这些变量：

```makefile
MASTER_SITES=	GNU/make
```

旧的扩展格式仍然有效，但所有 Ports 已经转换为简洁格式。扩展格式如下所示：

```makefile
MASTER_SITES=		${MASTER_SITE_GNU}
MASTER_SITE_SUBDIR=	make
```

这些值和变量定义在 [Mk/bsd.sites.mk](https://cgit.freebsd.org/ports/tree/Mk/bsd.sites.mk) 中。此文件经常添加新的条目，因此在提交 Port 之前，确保检查该文件的最新版本。

> **技巧**
>
> 对于任何 `MASTER_SITE_FOO` 变量，可以使用简写形式 `FOO`。例如，使用：
>
> ```makefile
> MASTER_SITES=	FOO
> ```
>
> 如果需要 `MASTER_SITE_SUBDIR`，则使用：
>
> ```makefile
> MASTER_SITES=	FOO/bar
> ```

> **重要**
>
> 一些 `MASTER_SITE_*` 名称较长，为了便于使用，已定义了简写形式：`MASTER_SITE_*` 宏的简写

| 宏                 | 简写      |
| ----------------- | ------- |
| `PERL_CPAN`       | `CPAN`  |
| `GITHUB`          | `GH`    |
| `GITHUB_CLOUD`    | `GHC`   |
| `LIBREOFFICE_DEV` | `LODEV` |
| `NETLIB`          | `NL`    |
| `RUBYGEMS`        | `RG`    |
| `SOURCEFORGE`     | `SF`    |

### 5.4.2.2. Magic MASTER\_SITES 宏

一些流行站点有可预测的目录结构，存在一些“魔法”宏。对于这些站点，只需使用简写，系统会自动选择子目录。例如，对于名为 `Stardict`，版本为 `1.2.3`，并托管在 SourceForge 上的 Port，添加以下行：

```makefile
MASTER_SITES=	SF
```

这将推测出名为 `/project/stardict/stardict/1.2.3` 的子目录。如果推测的目录不正确，可以覆盖它：

```makefile
MASTER_SITES=	SF/stardict/WyabdcRealPeopleTTS/${PORTVERSION}
```

也可以写成：

```makefile
MASTER_SITES=	SF
MASTER_SITE_SUBDIR=	stardict/WyabdcRealPeopleTTS/${PORTVERSION}
```

**表 4. 魔法 `MASTER_SITES` 宏**

| 宏                         | 假定子目录                                                                            |
| ------------------------- | -------------------------------------------------------------------------------- |
| `APACHE_COMMONS_BINARIES` | `${PORTNAME:S,commons-,,}`                                                       |
| `APACHE_COMMONS_SOURCE`   | `${PORTNAME:S,commons-,,}`                                                       |
| `APACHE_JAKARTA`          | `${PORTNAME:S,-,/,}/source`                                                      |
| `BERLIOS`                 | `${PORTNAME:tl}.berlios`                                                         |
| `CHEESESHOP`              | `source/${DISTNAME:C/(.).*/\1/}/${DISTNAME:C/(.*)-[0-9].*/\1/}`                  |
| `CPAN`                    | `${PORTNAME:C/-.*//}`                                                            |
| `DEBIAN`                  | `pool/main/${PORTNAME:C/^((lib)?.).*$/\1/}/${PORTNAME}`                          |
| `FARSIGHT`                | `${PORTNAME}`                                                                    |
| `FESTIVAL`                | `${PORTREVISION}`                                                                |
| `GCC`                     | `releases/${DISTNAME}`                                                           |
| `GENTOO`                  | `distfiles`                                                                      |
| `GIMP`                    | `${PORTNAME}/${PORTVERSION:R}/`                                                  |
| `GH`                      | `${GH_ACCOUNT}/${GH_PROJECT}/tar.gz/${GH_TAGNAME}?dummy=/`                       |
| `GHC`                     | `${GH_ACCOUNT}/${GH_PROJECT}/`                                                   |
| `GNOME`                   | `sources/${PORTNAME}/${PORTVERSION:C/^(<span class="0-9">\.[0-9]</span>).*/\1/}` |
| `GNU`                     | `${PORTNAME}`                                                                    |
| `GNUPG`                   | `${PORTNAME}`                                                                    |
| `GNU_ALPHA`               | `${PORTNAME}`                                                                    |
| `HORDE`                   | `${PORTNAME}`                                                                    |
| `LODEV`                   | `${PORTNAME}`                                                                    |
| `MATE`                    | `${PORTVERSION:C/^(<span class="0-9">\.[0-9]</span>).*/\1/}`                     |
| `MOZDEV`                  | `${PORTNAME:tl}`                                                                 |
| `NL`                      | `${PORTNAME}`                                                                    |
| `QT`                      | `archive/qt/${PORTVERSION:R}`                                                    |
| `SAMBA`                   | `${PORTNAME}`                                                                    |
| `SAVANNAH`                | `${PORTNAME:tl}`                                                                 |
| `SF`                      | `${PORTNAME:tl}/${PORTNAME:tl}/${PORTVERSION}`                                   |

## 5.4.3. `USE_GITHUB`

如果分发文件来自 [GitHub](https://github.com/) 上的特定提交或标签，而没有官方发布的文件，可以通过一种简单的方法自动设置正确的 `DISTNAME` 和 `MASTER_SITES`。

> **重要**
>
> 从 2023 年 2 月 21 日起，[GitHub](https://github.blog/2023-02-21-update-on-the-future-stability-of-source-code-archives-and-hashes/) 宣布源代码下载将稳定一年。请切换到发布的资产，如果没有发布，要求上游生成相应的资产。

以下是可用的变量：

**表 5. `USE_GITHUB` 说明**

| 变量           | 说明                                                                                                                                                                                     | 默认值                                                      |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `GH_ACCOUNT` | 托管项目的 GitHub 用户名                                                                                                                                                                       | `${PORTNAME}`                                            |
| `GH_PROJECT` | GitHub 上的项目名称                                                                                                                                                                          | `${PORTNAME}`                                            |
| `GH_TAGNAME` | 要下载的标签名称（例如 2.0.1、哈希值等）。这里使用分支名称是不正确的。也可以使用提交 ID 的哈希值来进行快照。                                                                                                                            | `${DISTVERSIONPREFIX}${DISTVERSION}${DISTVERSIONSUFFIX}` |
| `GH_SUBDIR`  | 当软件需要额外的分发文件并且这些文件需要在 `${WRKSRC}` 内提取时，可以使用此变量。有关更多信息，请参见 [从 GitHub 获取多个文件](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#makefile-master_sites-github-multiple) 的示例。  | （无）                                                      |
| `GH_TUPLE`   | `GH_TUPLE` 允许将 `GH_ACCOUNT`、`GH_PROJECT`、`GH_TAGNAME` 和 `GH_SUBDIR` 放入一个变量中。格式为 *account*`:`*project*`:`*tagname*`:`*group*`/*subdir*。其中`/\`*subdir* 部分是可选的。当有多个 GitHub 项目需要获取时，这非常有用。 |                                                          |

> **重要**
>
> 不要使用 `GH_TUPLE` 来处理默认的分发文件，因为它没有默认值。

**示例 10. 简单使用 `USE_GITHUB`**

在为来自 GitHub 上 FreeBSD 用户的 `pkg` 版本 `1.2.7` 制作 Port 时，位于 <https://github.com/freebsd/pkg/> 上，**Makefile** 会像下面这样（为了示例做了略微简化）：

```makefile
PORTNAME=	pkg
DISTVERSION=	1.2.7

USE_GITHUB=	yes
GH_ACCOUNT=	freebsd
```

它会自动将 `MASTER_SITES` 设置为 `GH`，并将 `WRKSRC` 设置为 `${WRKDIR}/pkg-1.2.7`。

**示例 11. 更完整地使用 `USE_GITHUB`**

在为来自 GitHub 上 FreeBSD 用户的 `pkg` 的最新版本制作 Port 时，位于 <https://github.com/freebsd/pkg/> 上，**Makefile** 会像下面这样（为了示例做了略微简化）：

```makefile
PORTNAME=	pkg-devel
DISTVERSION=	1.3.0.a.20140411

USE_GITHUB=	yes
GH_ACCOUNT=	freebsd
GH_PROJECT=	pkg
GH_TAGNAME=	6dbb17b
```

它会自动将 `MASTER_SITES` 设置为 `GH`，并将 `WRKSRC` 设置为 `${WRKDIR}/pkg-6dbb17b`。

`20140411` 是 `GH_TAGNAME` 中引用的提交的日期，而不是 **Makefile** 编辑的日期，也不是提交的日期。

**示例 12. 使用 `USE_GITHUB` 和 `DISTVERSIONPREFIX`**

有时，`GH_TAGNAME` 会与 `DISTVERSION` 略有不同。例如，如果版本是 `1.0.2`，标签是 `v1.0.2`。在这种情况下，可以使用 `DISTVERSIONPREFIX` 或 `DISTVERSIONSUFFIX`：

```makefile
PORTNAME=	foo
DISTVERSIONPREFIX=	v
DISTVERSION=	1.0.2

USE_GITHUB=	yes
```

它会自动将 `GH_TAGNAME` 设置为 `v1.0.2`，同时 `WRKSRC` 会保持为 `${WRKDIR}/foo-1.0.2`。

**示例 13. 当上游没有使用版本时使用 `USE_GITHUB`**

如果上游从未使用过版本号，则不要像 `0.1` 或 `1.0` 那样虚构一个版本号。创建 Port 时，将 `DISTVERSION` 设置为 `gYYYYMMDD`，其中 `g` 代表 Git，`YYYYMMDD` 代表 `GH_TAGNAME` 中引用的提交的日期。

```makefile
PORTNAME=	bar
DISTVERSION=	g20140411

USE_GITHUB=	yes
GH_TAGNAME=	c472d66b
```

这创建了一个随时间增长的版本控制方案，并且仍然处于版本 `0` 之前（参见 [使用](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#makefile-versions-ex-pkg-version)[pkg-version\\(8)](https://man.freebsd.org/cgi/man.cgi?query=pkg-version\&sektion=8\&format=html) 比较版本）：

```makefile
% pkg version -t g20140411 0
<
```

这意味着，如果上游将来决定创建版本，则不需要使用 `PORTEPOCH`。

**示例 14. 使用 `USE_GITHUB` 来访问两个版本之间的提交**

如果当前版本的软件使用 Git 标签，而 Port 需要更新到一个没有标签的较新中间版本，可以使用 [git-describe(1)](https://man.freebsd.org/cgi/man.cgi?query=git-describe\&sektion=1\&format=html) 来找出要使用的版本：

```sh
% git describe --tags f0038b1
v0.7.3-14-gf0038b1
```

`v0.7.3-14-gf0038b1` 可以分为三个部分：

`v0.7.3` 这是在请求的提交之前，Git 提交历史中出现的最后一个 Git 标签。

`-14` 这意味着请求的提交 `f0038b1` 是在 `v0.7.3` 标签之后的第 14 次提交。

`-gf0038b1` `-g` 代表 "Git"，`f0038b1` 是此引用指向的提交哈希值。

```makefile
PORTNAME=	bar
DISTVERSIONPREFIX=  v
DISTVERSION=	0.7.3-14
DISTVERSIONSUFFIX=  -gf0038b1

USE_GITHUB=	yes
```

这创建了一个随着时间推移（实际上是随着提交增加）的版本控制方案，并且与创建 `0.7.4` 版本不会冲突。（参见 [使用](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#makefile-versions-ex-pkg-version)[pkg-version\\(8)](https://man.freebsd.org/cgi/man.cgi?query=pkg-version\&sektion=8\&format=html) 比较版本）：

```sh
% pkg version -t 0.7.3 0.7.3.14
<
% pkg version -t 0.7.3.14 0.7.4
<
```

如果请求的提交与某个标签相同，默认会显示一个更短的描述。更长的版本是等效的：

```sh
% git describe --tags c66c71d
v0.7.3

% git describe --tags --long c66c71d
v0.7.3-0-gc66c71d
```

### 5.4.3.1. 从 GitHub 获取多个文件

`USE_GITHUB` 框架还支持从 GitHub 的不同位置获取多个分发文件。它的工作方式与 [从多个位置获取多个分发文件或补丁文件](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#porting-master-sites-n) 非常相似。

多个值可以添加到 `GH_ACCOUNT`、`GH_PROJECT` 和 `GH_TAGNAME` 中。每个不同的值会分配到一个组。主值可以没有组，或者使用 `:DEFAULT` 组。如果值与默认值相同，可以省略（详细信息请参见 [`USE_GITHUB` 描述](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#makefile-master_sites-github-description)）。

当需要处理很多分发文件时，也可以使用 `GH_TUPLE`。它有助于将账户、项目、标签名和组信息集中在同一地方。

对于每个组，会创建一个 `${WRKSRC_group}` 辅助变量，包含文件提取到的目录。`${WRKSRC_group}` 变量可以在 `post-extract` 中用于移动目录，或添加到 `CONFIGURE_ARGS`，或者执行其他需要的操作，以便软件正确构建。

> **小心**
>
> `:group` 部分 *必须* 只用于一个分发文件。它作为一个唯一的键使用，如果多次使用，它将覆盖之前的值。

> **注意**
>
> 由于这是 `DISTFILES` 和 `MASTER_SITES` 之上的语法糖，组名必须遵循 [从多个位置获取多个分发文件或补丁文件](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#porting-master-sites-n) 中列出的组名限制。

在从 GitHub 获取多个文件时，有时默认的分发文件不会从 GitHub 获取。要禁用默认分发文件的获取，请设置：

```makefile
USE_GITHUB=	nodefault
```

> **重要**
>
> 使用 `USE_GITHUB=nodefault` 时，**Makefile** 必须在其 [顶部块](https://docs.freebsd.org/en/books/porters-handbook/porting-order/#porting-order-portname) 中设置 `DISTFILES`。定义应为：
>
> ```makefile
> DISTFILES=    ${DISTNAME}${EXTRACT_SUFX}
> ```

**示例 15. 使用 `USE_GITHUB` 获取多个分发文件**

有时需要获取多个分发文件。例如，当上游 Git 仓库使用子模块时，可以通过在 `GH_*` 变量中使用组来轻松实现：

```makefile
PORTNAME=	foo
DISTVERSION=	1.0.2

USE_GITHUB=	yes
GH_ACCOUNT=	bar:icons,contrib
GH_PROJECT=	foo-icons:icons foo-contrib:contrib
GH_TAGNAME=	1.0:icons fa579bc:contrib
GH_SUBDIR=	ext/icons:icons

CONFIGURE_ARGS=	--with-contrib=${WRKSRC_contrib}
```

这将从 GitHub 获取三个分发文件。默认文件来自 **foo/foo**，版本为 `1.0.2`。第二个文件（带有 `icons` 组）来自 **bar/foo-icons**，版本为 `1.0`。第三个文件来自 **bar/foo-contrib**，使用 Git 提交 `fa579bc`。分发文件命名为 **foo-foo-1.0.2\_GH0.tar.gz**、**bar-foo-icons-1.0\_GH0.tar.gz** 和 **bar-foo-contrib-fa579bc\_GH0.tar.gz**。

所有分发文件都将被解压到 `${WRKDIR}` 中的各自子目录中。默认文件仍然被解压到 `${WRKSRC}`，在此示例中为 **${WRKDIR}/foo-1.0.2**。每个附加的分发文件会解压到 `${WRKSRC_group}` 中。这里，`icons` 组的文件解压到 `${WRKSRC_icons}`，其中包含 **${WRKDIR}/foo-icons-1.0**。带有 `contrib` 组的文件解压到 `${WRKSRC_contrib}`，其中包含 `${WRKDIR}/foo-contrib-fa579bc`。

该软件的构建系统期望在其源代码中找到 **ext/icons** 子目录，因此使用了 `GH_SUBDIR`。`GH_SUBDIR` 确保 **ext** 存在，但 **ext/icons** 不会已经存在。然后，它会执行如下操作：

```makefile
post-extract:
      @${MV} ${WRKSRC_icons} ${WRKSRC}/ext/icons
```

**示例 16. 使用 `USE_GITHUB` 获取多个分发文件并使用 `GH_TUPLE`**

这与 [使用 `USE_GITHUB` 获取多个分发文件](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#makefile-master_sites-github-multi) 功能相同，但使用了 `GH_TUPLE`：

```makefile
PORTNAME=	foo
DISTVERSION=	1.0.2

USE_GITHUB=	yes
GH_TUPLE=	bar:foo-icons:1.0:icons/ext/icons \
		bar:foo-contrib:fa579bc:contrib

CONFIGURE_ARGS=	--with-contrib=${WRKSRC_contrib}
```

在前一个示例中使用了 `bar:icons,contrib` 来进行分组。由于 `GH_TUPLE` 无法进行分组，因此会存在一些冗余信息。

**示例 17. 如何使用 `USE_GITHUB` 与 Git 子模块一起使用？**

使用 GitHub 作为上游仓库的 Ports 有时会使用子模块。有关更多信息，请参见 [git-submodule(1)](https://man.freebsd.org/cgi/man.cgi?query=git-submodule\&sektion=1\&format=html)。

子模块的问题在于每个子模块都是一个独立的仓库。因此，必须分别获取每个子模块。

以 [finance/moneymanagerex](https://cgit.freebsd.org/ports/tree/finance/moneymanagerex/) 为例，其 GitHub 仓库为 <https://github.com/moneymanagerex/moneymanagerex/>。在根目录下有一个 [.gitmodules](https://github.com/moneymanagerex/moneymanagerex/blob/master/.gitmodules) 文件。该文件描述了该仓库中使用的所有子模块，并列出了所需的附加仓库。该文件会告诉需要哪些附加仓库：

```ini
[submodule "lib/wxsqlite3"]
	path = lib/wxsqlite3
	url = https://github.com/utelle/wxsqlite3.git
[submodule "3rd/mongoose"]
	path = 3rd/mongoose
	url = https://github.com/cesanta/mongoose.git
[submodule "3rd/LuaGlue"]
	path = 3rd/LuaGlue
	url = https://github.com/moneymanagerex/LuaGlue.git
[submodule "3rd/cgitemplate"]
	path = 3rd/cgitemplate
	url = https://github.com/moneymanagerex/html-template.git
[...]
```

该文件唯一缺少的信息是要使用的提交哈希或标签。可以在克隆仓库后找到该信息：

```sh
% git clone --recurse-submodules https://github.com/moneymanagerex/moneymanagerex.git
Cloning into 'moneymanagerex'...
remote: Counting objects: 32387, done.
[...]
Submodule '3rd/LuaGlue' (https://github.com/moneymanagerex/LuaGlue.git) registered for path '3rd/LuaGlue'
Submodule '3rd/cgitemplate' (https://github.com/moneymanagerex/html-template.git) registered for path '3rd/cgitemplate'
Submodule '3rd/mongoose' (https://github.com/cesanta/mongoose.git) registered for path '3rd/mongoose'
Submodule 'lib/wxsqlite3' (https://github.com/utelle/wxsqlite3.git) registered for path 'lib/wxsqlite3'
[...]
Cloning into '/home/mat/work/freebsd/ports/finance/moneymanagerex/moneymanagerex/3rd/LuaGlue'...
Cloning into '/home/mat/work/freebsd/ports/finance/moneymanagerex/moneymanagerex/3rd/cgitemplate'...
Cloning into '/home/mat/work/freebsd/ports/finance/moneymanagerex/moneymanagerex/3rd/mongoose'...
Cloning into '/home/mat/work/freebsd/ports/finance/moneymanagerex/moneymanagerex/lib/wxsqlite3'...
[...]
Submodule path '3rd/LuaGlue': checked out 'c51d11a247ee4d1e9817dfa2a8da8d9e2f97ae3b'
Submodule path '3rd/cgitemplate': checked out 'cd434eeeb35904ebcd3d718ba29c281a649b192c'
Submodule path '3rd/mongoose': checked out '2140e5992ab9a3a9a34ce9a281abf57f00f95cda'
Submodule path 'lib/wxsqlite3': checked out 'fb66eb230d8aed21dec273b38c7c054dcb7d6b51'
[...]
% cd moneymanagerex
% git submodule status
 c51d11a247ee4d1e9817dfa2a8da8d9e2f97ae3b 3rd/LuaGlue (heads/master)
 cd434eeeb35904ebcd3d718ba29c281a649b192c 3rd/cgitemplate (cd434ee)
 2140e5992ab9a3a9a34ce9a281abf57f00f95cda 3rd/mongoose (6.2-138-g2140e59)
 fb66eb230d8aed21dec273b38c7c054dcb7d6b51 lib/wxsqlite3 (v3.4.0)
[...]
```

这些信息也可以在 GitHub 上找到。每个子模块的子目录显示为 `目录 @ 哈希`，例如，`mongoose @ 2140e59`。

虽然从 GitHub 获取信息似乎更直接，但使用 `git submodule status` 获取的信息将提供更有意义的信息。例如，在这里，`lib/wxsqlite3` 的提交哈希 `fb66eb2` 对应于 `v3.4.0`。这两者可以互换使用，但当标签可用时，应使用标签。

现在已经收集到所有所需的信息，可以编写 **Makefile**（仅显示与 GitHub 相关的行）：

```ini
PORTNAME=	moneymanagerex
DISTVERSIONPREFIX=	v
DISTVERSION=	1.3.0

USE_GITHUB=	yes
GH_TUPLE=	utelle:wxsqlite3:v3.4.0:wxsqlite3/lib/wxsqlite3 \
		moneymanagerex:LuaGlue:c51d11a:lua_glue/3rd/LuaGlue \
		moneymanagerex:html-template:cd434ee:html_template/3rd/cgitemplate \
		cesanta:mongoose:2140e59:mongoose/3rd/mongoose \
		[...]
```

## 5.4.4. `USE_GITLAB`

与 GitHub 类似，如果发行文件来自 [gitlab.com](https://gitlab.com/) 或托管 GitLab 软件时，可以使用以下变量，并且可能需要设置它们。

| 变量           | 描述                                                                                                                                                                                                          | 默认值                   |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| `GL_SITE`    | 托管 GitLab 项目的站点名称                                                                                                                                                                                           | <https://gitlab.com/> |
| `GL_ACCOUNT` | 托管项目的 GitLab 用户的账户名                                                                                                                                                                                         | `${PORTNAME}`         |
| `GL_PROJECT` | GitLab 上的项目名称                                                                                                                                                                                               | `${PORTNAME}`         |
| `GL_COMMIT`  | 要下载的提交哈希值，必须是完整的 160 位、40 字符的十六进制 sha1 哈希值。对于 GitLab，这是一个必需的变量。                                                                                                                                             | `(none)`              |
| `GL_SUBDIR`  | 当软件需要从 `${WRKSRC}` 中提取额外的分发文件时，可以使用此变量。有关更多信息，请参阅 [从 GitLab 获取多个文件](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#makefile-master_sites-gitlab-multiple)。                                   | (none)                |
| `GL_TUPLE`   | `GL_TUPLE` 允许将 `GL_SITE`、`GL_ACCOUNT`、`GL_PROJECT`、`GL_COMMIT` 和 `GL_SUBDIR` 放入一个变量中，格式为 *site*`:`*account*`:`*project*`:`*commit*`:`*group*`/`subdir`。其中`site`和`subdir\` 部分是可选的。当有多个 GitLab 项目需要获取时，它非常有用。 |                       |

**示例 18. 使用 `USE_GITLAB` 的简单示例**

在尝试为来自 gitlab.com 上的 accounts-sso 用户的 libsignon-glib 版本 `1.14` 创建 Port 时，位于 <https://gitlab.com/accounts-sso/libsignon-glib/>，**Makefile** 的内容如下，用于获取发行文件：

```makefile
PORTNAME=	libsignon-glib
DISTVERSION=	1.14

USE_GITLAB=	yes
GL_ACCOUNT=	accounts-sso
GL_COMMIT=	e90302e342bfd27bc8c9132ab9d0ea3d8723fd03
```

它将自动将 `MASTER_SITES` 设置为 [gitlab.com](https://gitlab.com/) 并将 `WRKSRC` 设置为 `${WRKDIR}/libsignon-glib-e90302e342bfd27bc8c9132ab9d0ea3d8723fd03-e90302e342bfd27bc8c9132ab9d0ea3d8723fd03`。

### 示例 19. 更完整的使用 `USE_GITLAB` 的示例

如果该 Port 没有版本，并且来自 foo 用户的项目 bar 位于自托管的 GitLab 站点 `https://gitlab.example.com/`，则 **Makefile** 如下：

```makefile
PORTNAME=	foobar
DISTVERSION=	g20170906

USE_GITLAB=	yes
GL_SITE=	https://gitlab.example.com
GL_ACCOUNT=	foo
GL_PROJECT=	bar
GL_COMMIT=	9c1669ce60c3f4f5eb43df874d7314483fb3f8a6
```

它将把 `MASTER_SITES` 设置为 `"https://gitlab.example.com"`，并将 `WRKSRC` 设置为 `${WRKDIR}/bar-9c1669ce60c3f4f5eb43df874d7314483fb3f8a6-9c1669ce60c3f4f5eb43df874d7314483fb3f8a6`。

> **技巧**
>
> `20170906` 是所引用的 `GL_COMMIT` 中提交的日期，而不是编辑 **Makefile** 的日期或提交到 FreeBSD Ports 树的日期。

> **注意**
>
> `GL_SITE` 的协议、Port 和网页根目录可以在同一个变量中修改。

### 5.4.4.1. 从 GitLab 获取多个文件

`USE_GITLAB` 框架还支持从 GitLab 和 GitLab 托管站点的不同位置获取多个分发文件。它的工作方式与 [从多个位置获取多个分发文件或补丁文件](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#porting-master-sites-n) 和 [从 GitLab 获取多个文件](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#makefile-master_sites-gitlab-multiple) 类似。

可以将多个值添加到 `GL_SITE`、`GL_ACCOUNT`、`GL_PROJECT` 和 `GL_COMMIT` 中。每个不同的值分配一个组。 [`USE_GITLAB` 描述](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#makefile-master_sites-gitlab-description)。

还可以使用 `GL_TUPLE`，当有许多分发文件时，它有助于将站点、账户、项目、提交和组信息集中在一起。

对于每个组，创建一个 `${WRKSRC_group}` 帮助变量，包含文件被提取到的目录。可以在 `post-extract` 期间使用 `${WRKSRC_group}` 变量来移动目录，或者将其添加到 `CONFIGURE_ARGS`，或者做其他必要的操作，以确保软件正确构建。

> **小心**
>
> `:group` 部分 *必须* 仅用于 *一个* 分发文件。它作为唯一键使用，使用多次将覆盖之前的值。

> **注意**
>
> 由于这只是 `DISTFILES` 和 `MASTER_SITES` 的语法糖，因此组名称必须遵守 [从多个位置获取多个分发文件或补丁文件](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#porting-master-sites-n) 中规定的组名限制。

**示例 20. 使用 `USE_GITLAB` 获取多个分发文件**

有时需要获取多个分发文件。例如，当上游的 git 仓库使用子模块时，可以通过在 `GL_*` 变量中使用组轻松完成此操作：

```makefile
PORTNAME=	foo
DISTVERSION=	1.0.2

USE_GITLAB=	yes
GL_SITE=	https://gitlab.example.com:9434/gitlab:icons
GL_ACCOUNT=	bar:icons,contrib
GL_PROJECT=	foo-icons:icons foo-contrib:contrib
GL_COMMIT=	c189207a55da45305c884fe2b50e086fcad4724b ae7368cab1ca7ca754b38d49da064df87968ffe4:icons 9e4dd76ad9b38f33fdb417a4c01935958d5acd2a:contrib
GL_SUBDIR=	ext/icons:icons

CONFIGURE_ARGS= --with-contrib=${WRKSRC_contrib}
```

这将从 gitlab.com 获取两个分发文件，从 `gitlab.example.com` 托管的 GitLab 获取一个。默认的一个来自 <https://gitlab.com/foo/foo>，提交哈希为 `c189207a55da45305c884fe2b50e086fcad4724b`。第二个，属于 `icons` 组，来自 <https://gitlab.example.com:9434/gitlab/bar/foo-icons>，提交哈希为 `ae7368cab1ca7ca754b38d49da064df87968ffe4`。第三个来自 <https://gitlab.com/bar/foo-contrib>，提交哈希为 `9e4dd76ad9b38f33fdb417a4c01935958d5acd2a`。分发文件的名称分别是 **foo-foo-c189207a55da45305c884fe2b50e086fcad4724b\_GL0.tar.gz**、**bar-foo-icons-ae7368cab1ca7ca754b38d49da064df87968ffe4\_GL0.tar.gz** 和 **bar-foo-contrib-9e4dd76ad9b38f33fdb417a4c01935958d5acd2a\_GL0.tar.gz**。

所有分发文件都将被提取到 `${WRKDIR}` 下的各自子目录中。默认的文件仍然会提取到 `${WRKSRC}` 中，在此案例中为 **${WRKDIR}/foo-c189207a55da45305c884fe2b50e086fcad4724b-c189207a55da45305c884fe2b50e086fcad4724b**。每个额外的分发文件会提取到 `${WRKSRC_group}`。这里，`icons` 组的文件提取到 `${WRKSRC_icons}`，包含 **${WRKDIR}/foo-icons-ae7368cab1ca7ca754b38d49da064df87968ffe4-ae7368cab1ca7ca754b38d49da064df87968ffe4**。属于 `contrib` 组的文件提取到 `${WRKSRC_contrib}`，并包含 `${WRKDIR}/foo-contrib-9e4dd76ad9b38f33fdb417a4c01935958d5acd2a-9e4dd76ad9b38f33fdb417a4c01935958d5acd2a`。

软件的构建系统预计会在其源代码的 **ext/icons** 子目录中找到图标，因此使用了 `GL_SUBDIR`。`GL_SUBDIR` 确保 **ext** 存在，但 **ext/icons** 不会提前存在。然后它会执行以下操作：

```makefile
post-extract:
        @${MV} ${WRKSRC_icons} ${WRKSRC}/ext/icons
```

**示例 21. 使用 `USE_GITLAB` 获取多个分发文件并使用 `GL_TUPLE`**

这个示例与[使用 `USE_GITLAB` 获取多个分发文件](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#makefile-master_sites-gitlab-multi) 功能上是等效的，但使用了 `GL_TUPLE`：

```ini
PORTNAME=	foo
DISTVERSION=	1.0.2

USE_GITLAB=	yes
GL_COMMIT=	c189207a55da45305c884fe2b50e086fcad4724b
GL_TUPLE=	https://gitlab.example.com:9434/gitlab:bar:foo-icons:ae7368cab1ca7ca754b38d49da064df87968ffe4:icons/ext/icons \
		bar:foo-contrib:9e4dd76ad9b38f33fdb417a4c01935958d5acd2a:contrib

CONFIGURE_ARGS= --with-contrib=${WRKSRC_contrib}
```

在前面的示例中使用了分组 `bar:icons,contrib`。由于不可能进行分组操作，所以在 `GL_TUPLE` 中出现了一些冗余信息。

## 5.4.5. `EXTRACT_SUFX`

如果只有一个分发文件，并且它使用奇怪的后缀来表示压缩机制，可以设置 `EXTRACT_SUFX`。

例如，如果分发文件的名称是 **foo.tar.gzip** 而不是更常见的 **foo.tar.gz**，可以写：

```makefile
DISTNAME=	foo
EXTRACT_SUFX=	.tar.gzip
```

`USES=tar[:xxx]`、`USES=lha` 或 `USES=zip` 会自动根据需要设置 `EXTRACT_SUFX` 为最常见的归档扩展名，更多信息请参阅[使用 `USES` 宏](https://docs.freebsd.org/en/books/porters-handbook/uses/#uses)。如果没有设置这些，`EXTRACT_SUFX` 默认为 `.tar.gz`。

> **注意**
>
> 由于 `EXTRACT_SUFX` 仅在 `DISTFILES` 中使用，因此只需设置其中之一。

## 5.4.6. `DISTFILES`

有时下载的文件名称与 Port 的名称没有任何关系。例如，它可能被称为 **source.tar.gz** 或类似名称。在其他情况下，应用程序的源代码可能分散在几个不同的归档文件中，所有这些文件都必须下载。

如果是这种情况，可以将 `DISTFILES` 设置为一个由空格分隔的文件列表，列出所有需要下载的文件。

```makefile
DISTFILES=	source1.tar.gz source2.tar.gz
```

如果未明确设置，`DISTFILES` 默认为 `${DISTNAME}${EXTRACT_SUFX}`。

## 5.4.7. `EXTRACT_ONLY`

如果只需要解压部分 `DISTFILES`，例如其中一个是源代码，另一个是未压缩的文档，可以在 `EXTRACT_ONLY` 中列出必须解压的文件名。

```makefile
DISTFILES=	source.tar.gz manual.html
EXTRACT_ONLY=	source.tar.gz
```

当没有 `DISTFILES` 需要解压时，可以将 `EXTRACT_ONLY` 设置为空字符串。

```makefile
EXTRACT_ONLY=
```

## 5.4.8. `PATCHFILES`

如果 Port 需要一些额外的补丁，并且这些补丁可以通过 FTP 或 HTTP 获取，可以将 `PATCHFILES` 设置为补丁文件的名称，`PATCH_SITES` 设置为包含补丁的目录的 URL（格式与 `MASTER_SITES` 相同）。

如果补丁与源代码树的顶部（即 `WRKSRC`）不相关，因为它包含一些额外的路径名，应该相应地设置 `PATCH_DIST_STRIP`。例如，如果补丁中的所有路径名前面都有 `foozolix-1.0/`，则应设置 `PATCH_DIST_STRIP=-p1`。

不必担心补丁是否压缩；如果补丁文件名以 **.Z**、**.gz**、**.bz2** 或 **.xz** 结尾，它们会自动解压。

如果补丁与其他文件（如文档）一起分发，并且这些文件被压缩成一个 tarball，则不能使用 `PATCHFILES`。在这种情况下，将补丁 tarball 的名称和位置添加到 `DISTFILES` 和 `MASTER_SITES` 中。然后，使用 `EXTRA_PATCHES` 指向这些文件，**bsd.port.mk** 会自动应用它们。特别是，不要将补丁文件复制到 **${PATCHDIR}**，该目录可能不可写。

> **技巧**
>
> 如果有多个补丁，并且它们的 `strip` 参数需要不同的值，可以在 `PATCHFILES` 中的补丁名称旁边添加这些值，例如：
>
> ```makefile
> PATCHFILES=	patch1 patch2:-p1
> ```
>
> 这不会与[主站分组功能](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#porting-master-sites-n)冲突，添加分组也同样有效：
>
> ```makefile
> PATCHFILES=	patch2:-p1:source2
> ```

> **注意**
>
> tarball 会与常规源代码一起解压，因此如果它是常规压缩 tarball，就不需要显式地解压它。提取时请特别小心，不要覆盖该目录中已经存在的文件。如果手动提取补丁，记得在 `pre-clean` 目标中添加命令以删除已复制的补丁。

## 5.4.9. 从多个位置下载多个分发文件或补丁

（可以将本节视为一个“高级话题”；对于初学者，建议初次阅读时跳过此部分）。

本节介绍了一种名为 `MASTER_SITES:n` 和 `MASTER_SITES_NN` 的获取机制。我们将这种机制称为 `MASTER_SITES:n`。

首先一些背景知识。OpenBSD 在 `DISTFILES` 和 `PATCHFILES` 中具有一个非常方便的功能，允许在文件和补丁的名称后附加 `:n` 标识符。这里，`n` 可以是任何包含 `[0-9a-zA-Z_]` 的单词，并表示一个组的标识符。例如：

```makefile
DISTFILES=	alpha:0 beta:1
```

在 OpenBSD 中，分发文件 **alpha** 将与变量 `MASTER_SITES0` 关联，而不是我们常见的 `MASTER_SITES`，而 **beta** 将与 `MASTER_SITES1` 关联。

这是一个非常有趣的功能，可以减少不断寻找正确下载站点的麻烦。

试想一下，`DISTFILES` 中有两个文件，`MASTER_SITES` 中有 20 个站点，其中 **beta** 文件在所有站点上都有，而 **alpha** 文件只能在第 20 个站点找到。如果维护者事先知道这个情况，完全可以避免在所有站点中浪费时间，不是吗？这样就避免了在愉快的周末里浪费大量时间！

现在你明白了这个概念，试想更多的 `DISTFILES` 和更多的 `MASTER_SITES`，肯定会让我们的“distfiles 调查专家”对减少网络压力感到轻松。

在接下来的章节中，将介绍 FreeBSD 对这个想法的实现。我们对 OpenBSD 的概念做了一些改进。

> **重要**
>
> 组名称不能包含连字符（`-`），事实上，它们不能包含任何超出 `[a-zA-Z0-9_]` 范围的字符。这是因为，虽然 [make(1)](https://man.freebsd.org/cgi/man.cgi?query=make\&sektion=1\&format=html) 可以接受包含连字符的变量名，但 [sh(1)](https://man.freebsd.org/cgi/man.cgi?query=sh\&sektion=1\&format=html) 不允许。

### 5.4.9.1. 简化信息

本节解释了如何快速准备从不同站点和子目录下载多个分发文件和补丁的精细化获取。我们在这里描述了简化版 `MASTER_SITES:n` 的使用。这对于大多数场景来说已经足够了。更多详细信息请参见 [详细信息](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#ports-master-sites-n-detailed)。

有些应用程序由多个分发文件组成，这些文件必须从多个不同的站点下载。例如，Ghostscript 包含程序的核心部分，还有大量驱动文件，这些驱动文件根据用户的打印机不同而有所不同。部分驱动文件随核心文件一起提供，但许多其他驱动文件必须从不同的站点下载。

为了解决这个问题，`DISTFILES` 中的每个条目可以跟随一个冒号和一个“组名”。然后，`MASTER_SITES` 中列出的每个站点都将跟随一个冒号和该组，表示从该站点下载的分发文件。

例如，考虑一个由两个部分组成的应用程序，**source1.tar.gz** 和 **source2.tar.gz**，这两个文件必须从不同的站点下载。Port 的 **Makefile** 应包括如下内容：

**示例 22. 简化版 `MASTER_SITES:n` 使用（每个站点一个文件）**

```makefile
MASTER_SITES=	ftp://ftp1.example.com/:source1 \
		http://www.example.com/:source2
DISTFILES=	source1.tar.gz:source1 \
		source2.tar.gz:source2
```

多个分发文件可以具有相同的组。继续之前的示例，假设有一个第三个分发文件 **source3.tar.gz**，它从 `ftp.example2.com` 下载。此时，**Makefile** 应写成如下：

**示例 23. 简化版 `MASTER_SITES:n` 使用（每个站点多个文件）**

```makefile
MASTER_SITES=	ftp://ftp.example.com/:source1 \
		http://www.example.com/:source2
DISTFILES=	source1.tar.gz:source1 \
		source2.tar.gz:source2 \
		source3.tar.gz:source2
```

### 5.4.9.2. 详细信息

好的，那么之前的例子没有反映出新的 Port 的需求吗？在这一节中，我们将详细解释细粒度获取机制 `MASTER_SITES:n` 的工作原理以及如何使用它。

1. 元素可以通过 `:n` 后缀，其中 *n* 是 `<span class="^:">`，也就是说，*n* 可以是任何字母数字字符串，但我们将其限制为 `[a-zA-Z_][0-9a-zA-Z_]</span>` 目前为止。\
   此外，字符串匹配是区分大小写的；也就是说，`n` 和 `N` 是不同的。

   然而，这些单词不能用于后缀化，因为它们具有特殊含义：`default`、`all` 和 `ALL`（它们在 [ii](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#porting-master-sites-n-what-changes-in-port-targets) 项中被内部使用）。此外，`DEFAULT` 是一个特殊用途的单词（请查看 [3](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#porting-master-sites-n-DEFAULT-group) 项）。
2. 带有 `:n` 后缀的元素属于组 `n`，带有 `:m` 后缀的元素属于组 `m`，依此类推。
3. 没有后缀的元素没有分组，它们都属于特殊组 `DEFAULT`。任何带有 `DEFAULT` 后缀的元素都是冗余的，除非该元素同时属于 `DEFAULT` 和其他组（请参阅 [5](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#porting-master-sites-n-comma-operator) 项）。\
   这些示例是等效的，但第一个是首选：

   ```makefile
   MASTER_SITES=	alpha
   ```

   ```makefile
   MASTER_SITES=	alpha:DEFAULT
   ```
4. 组不是互斥的，一个元素可以同时属于多个不同的组，一个组可以包含多个不同的元素，也可以没有元素。
5. 当一个元素同时属于多个组时，使用逗号操作符（`,`）。\
   我们可以一次列出多个组，而不是多次重复每次不同的后缀。例如，`:m,n,o` 表示该元素属于组 `m`、`n` 和 `o`。

   所有这些示例都是等效的，但最后一个是首选：

   ```makefile
   MASTER_SITES=	alpha alpha:SOME_SITE
   ```

   ```makefile
   MASTER_SITES=	alpha:DEFAULT alpha:SOME_SITE
   ```

   ```makefile
   MASTER_SITES=	alpha:SOME_SITE,DEFAULT
   ```

   ```makefile
   MASTER_SITES=	alpha:DEFAULT,SOME_SITE
   ```
6. 给定组内的所有站点按照 `MASTER_SORT_AWK` 排序。所有 `MASTER_SITES` 和 `PATCH_SITES` 中的组也会被排序。
7. 组语义可以在以下变量中使用：`MASTER_SITES`、`PATCH_SITES`、`MASTER_SITE_SUBDIR`、`PATCH_SITE_SUBDIR`、`DISTFILES` 和 `PATCHFILES`，并遵循此语法：
   1. 所有 `MASTER_SITES`、`PATCH_SITES`、`MASTER_SITE_SUBDIR` 和 `PATCH_SITE_SUBDIR` 元素必须以斜杠 `/` 字符结束。如果任何元素属于任何组，则组后缀 `:n` 必须紧随其后。`MASTER_SITES:n` 机制依赖于斜杠 `/` 结束符，以避免混淆那些 `:n` 是元素的有效部分的情况与那些 `:n` 表示组 `n` 的情况。为了兼容性，由于之前在 `MASTER_SITE_SUBDIR` 和 `PATCH_SITE_SUBDIR` 元素中不要求 `/` 结束符，如果紧接在后缀之前的字符不是 `/`，那么 `:n` 将被视为元素的有效部分，而不是组后缀，即使元素带有 `:n` 后缀。请参见 [详细使用 `MASTER_SITES:n` 在 `MASTER_SITE_SUBDIR` 中](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#ports-master-sites-n-example-detailed-use-master-site-subdir) 和 [详细使用 `MASTER_SITES:n` 与逗号操作符](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#ports-master-sites-n-example-detailed-use-complete-example-master-sites)。**示例 24. 在 `MASTER_SITE_SUBDIR` 中详细使用 `MASTER_SITES:n`**

      ```makefile
      MASTER_SITE_SUBDIR=	old:n new/:NEW
      ```

      * 属于 `DEFAULT` 组的目录 → old:n
      * 属于 `NEW` 组的目录 → new

      **示例 25. 使用逗号操作符的详细示例，多个文件、多个站点和多个子目录**

      ```makefile
      MASTER_SITES=	http://site1/%SUBDIR%/ http://site2/:DEFAULT \
      		http://site3/:group3 http://site4/:group4 \
      		http://site5/:group5 http://site6/:group6 \
      		http://site7/:DEFAULT,group6 \
      		http://site8/%SUBDIR%/:group6,group7 \
      		http://site9/:group8
      DISTFILES=	file1 file2:DEFAULT file3:group3 \
      		file4:group4,group5,group6 file5:grouping \
      		file6:group7
      MASTER_SITE_SUBDIR=	directory-trial:1 directory-n/:groupn \
      		directory-one/:group6,DEFAULT \
      		directory
      ```

      上述示例的细粒度获取结果如下。站点将按使用顺序列出。

      * **file1** 将从以下站点获取：
        * `MASTER_SITE_OVERRIDE`
        * <http://site1/directory-trial:1/>
        * <http://site1/directory-one/>
        * <http://site1/directory/>
        * <http://site2/>
        * <http://site7/>
        * `MASTER_SITE_BACKUP`
      * **file2** 将与 **file1** 完全相同地获取，因为它们属于相同的组
        * `MASTER_SITE_OVERRIDE`
        * <http://site1/directory-trial:1/>
        * <http://site1/directory-one/>
        * <http://site1/directory/>
        * <http://site2/>
        * <http://site7/>
        * `MASTER_SITE_BACKUP`
      * **file3** 将从以下站点获取：
        * `MASTER_SITE_OVERRIDE`
        * <http://site3/>
        * `MASTER_SITE_BACKUP`
      * **file4** 将从以下站点获取：
        * `MASTER_SITE_OVERRIDE`
        * <http://site4/>
        * <http://site5/>
        * <http://site6/>
        * <http://site7/>
        * <http://site8/directory-one/>
        * `MASTER_SITE_BACKUP`
      * **file5** 将从以下站点获取：
        * `MASTER_SITE_OVERRIDE`
        * `MASTER_SITE_BACKUP`
      * **file6** 将从以下站点获取：
        * `MASTER_SITE_OVERRIDE`
        * <http://site8/>
        * `MASTER_SITE_BACKUP`
8. 如何将 **bsd.sites.mk** 中的某个特殊宏分组，例如 SourceForge (`SF`)？\
   这已经尽可能简化。请参见 [详细使用 `MASTER_SITES:n` 与 SourceForge (`SF`)](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#ports-master-sites-n-example-detailed-use-master-site-sourceforge)。

   **示例 26. 详细使用 `MASTER_SITES:n` 与 SourceForge (`SF`)**

   ```makefile
   MASTER_SITES=	http://site1/ SF/something/1.0:sourceforge,TEST
   DISTFILES=	something.tar.gz:sourceforge
   ```

   **something.tar.gz** 将从 SourceForge 中的所有站点获取。
9. 如何与 `PATCH*` 一起使用？\
   所有示例都使用了 `MASTER*`，但它们与 `PATCH*` 一样适用，正如在 [简化使用 `MASTER_SITES:n` 与 `PATCH_SITES`](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#ports-master-sites-n-example-detailed-use-patch-sites) 中所见。

   **示例 27. 简化使用 `MASTER_SITES:n` 与 `PATCH_SITES`**

   ```makefile
   PATCH_SITES=	[http://site1/](http://site1/) [http://site2/\:test](http://site2/:test)
   PATCHFILES=	patch1\:test
   ```

### 5.4.9.3. 对 Ports 的影响：变化与不变之处

1. 所有当前的 Ports 保持不变。只有当存在按上述语法规则附加 `:n` 后缀的元素时，`MASTER_SITES:n` 特性才会被激活，特别是在 [第 7 项](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#porting-master-sites-n-group-semantics) 中显示的那样。
2. Port 目标保持不变：`checksum`、`makesum`、`patch`、`configure`、`build` 等。唯一的例外是 `do-fetch`、`fetch-list`、`master-sites` 和 `patch-sites`。
   * `do-fetch`：部署新分组，并根据其匹配的组元素，在 `MASTER_SITES` 和 `PATCH_SITES` 中分别使用对应的分组元素，同时在 `MASTER_SITE_SUBDIR` 和 `PATCH_SITE_SUBDIR` 中使用匹配的组元素。请查看 [使用逗号操作符的 `MASTER_SITES:n` 的详细用法](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#ports-master-sites-n-example-detailed-use-complete-example-master-sites)。
   * `fetch-list`：与旧的 `fetch-list` 工作方式相同，唯一的区别是它像 `do-fetch` 一样进行分组。
   * `master-sites` 和 `patch-sites`：（与旧版本不兼容）仅返回 `DEFAULT` 组的元素；实际上，它们分别执行 `master-sites-default` 和 `patch-sites-default` 目标。此外，使用 `master-sites-all` 或 `patch-sites-all` 目标来代替直接检查 `MASTER_SITES` 或 `PATCH_SITES` 是更为推荐的方式。直接检查在未来版本中可能无法正常工作。有关这些新 Port 目标的更多信息，请参阅 [第 B 项](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#porting-master-sites-n-new-port-targets-master-sites-all)。
3. 新的 Port 目标：
   1. `master-sites-n` 和 `patch-sites-n` 目标将列出各自组 `n` 中的元素。例如，`master-sites-DEFAULT` 和 `patch-sites-DEFAULT` 将返回 `DEFAULT` 组的元素，`master-sites-test` 和 `patch-sites-test` 将返回 `test` 组的元素，依此类推。
   2. 新的目标 `master-sites-all` 和 `patch-sites-all` 将执行旧的 `master-sites` 和 `patch-sites` 目标的功能。它们返回所有组的元素，就像它们都属于同一个组一样，唯一的不同是，它们列出了与 `DISTFILES` 或 `PATCHFILES` 中定义的组数量相同的 `MASTER_SITE_BACKUP` 和 `MASTER_SITE_OVERRIDE`，分别对应 `master-sites-all` 和 `patch-sites-all`。

## 5.4.10. `DIST_SUBDIR`

不要让 Port 混乱 **/usr/ports/distfiles**。如果 Port 需要获取大量文件，或者包含一个可能与其他 Ports 冲突的文件（例如，**Makefile**），请将 `DIST_SUBDIR` 设置为 Port 的名称（`${PORTNAME}` 或 `${PKGNAMEPREFIX}${PORTNAME}` 都可以）。这将把 `DISTDIR` 从默认的 **/usr/ports/distfiles** 改为 **/usr/ports/distfiles/${DIST\_SUBDIR}**，从而将该 Port 所需的所有文件放入该子目录中。

它还会查看备份主站点上具有相同名称的子目录，网址为 [http://distcache.FreeBSD.org](http://distcache.freebsd.org/)（仅在 **Makefile** 中显式设置 `DISTDIR` 是无法实现这一点的，请使用 `DIST_SUBDIR`）。

> **注意**
>
> 这不会影响 **Makefile** 中定义的 `MASTER_SITES`。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://porters-handbook.bsdcn.org/di-5-zhang-pei-zhi-makefile/5.4.-yuan-dai-ma-bao-wen-jian.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
