Go 中的依赖管理
Go 采用了一种不寻常的依赖管理方式,它是基于源码的,而不是基于制品的。在基于制品的依赖管理系统中,包由从源代码生成的制品组成,并存储在与源代码分开的仓库系统中。例如,许多 NodeJS 包使用 npmjs.org 作为包仓库,使用 github.com 作为源代码仓库。另一方面,Go 中的包就是源代码,发布包不涉及制品生成或单独的仓库。Go 包必须存储在 VCS 服务器上的版本控制仓库中。依赖项直接从其 VCS 服务器获取,或通过一个中介代理获取,该代理本身从其 VCS 服务器获取它们。
版本控制
Go 1.11 为 Go 生态系统引入了模块和一流的包版本控制。在此之前,Go 没有任何明确定义的版本管理机制。虽然存在第三方版本管理工具,但默认的 Go 体验不支持版本控制。
Go 模块使用 语义化版本控制。模块的版本定义为 VCS(版本控制系统)标签,这些标签是有效的语义化版本,前缀为 v。例如,要发布 gitlab.com/my/project 的 1.0.0 版本,开发者必须创建 Git 标签 v1.0.0。
对于 0 和 1 以外的主版本,模块名称必须以 /vX 为后缀,其中 X 是主版本号。例如,gitlab.com/my/project 的 v2.0.0 版本必须命名为并导入为 gitlab.com/my/project/v2。
Go 使用"伪版本",这是引用特定 VCS 提交的特殊语义化版本。语义化版本的预发布组件必须包含或以时间戳和提交标识符的前 12 个字符结尾:
vX.0.0-yyyymmddhhmmss-abcdefabcdef, 当 X 没有更早的标记提交时。vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef, 当最近的先前标签是 vX.Y.Z-pre 时。vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdef, 当最近的先前标签是 vX.Y.Z 时。
如果 VCS 标签匹配这些模式之一,它将被忽略。
要全面了解 Go 模块和版本控制,请参阅官方 Go 网站上的这一系列博客文章。
‘模块’ 与 ‘包’
- 包是包含
*.go文件的文件夹。 - 模块是包含
go.mod文件的文件夹。 - 模块通常也是一个包,即包含
go.mod文件和*.go文件的文件夹。 - 模块可以有子目录,这些子目录可以是包。
- 模块通常以 VCS 仓库的形式出现(Git、SVN、Hg 等)。
- 模块中任何本身就是模块的子目录都是不同的、独立的模块,并被排除在包含模块之外。
- 给定一个模块
repo,如果repo/sub包含go.mod文件,则repo/sub及其中包含的任何文件都是一个独立的模块,而不是repo的一部分。
- 给定一个模块
命名
除标准库外,模块或包的名称必须采用 (sub.)*domain.tld(/path)* 的形式。这类似于 URL,但不是 URL。包名称没有方案(如 https://),也不能有端口号。example.com:8443/my/package 不是有效的名称。
获取包
在 Go 1.12 之前,获取包的过程如下:
- 查询
https://{包名}?go-get=1。 - 扫描响应中的
go-import元标签。 - 使用指定的 VCS 获取元标签指示的仓库。
元标签应采用 <meta name="go-import" content="{prefix} {vcs} {url}"> 的形式。例如,gitlab.com/my/project git https://gitlab.com/my/project.git 表示应以 Git 方式从 https://gitlab.com/my/project.git 获取以 gitlab.com/my/project 开头的包。
获取模块
Go 1.12 引入了校验和数据库和模块代理。
校验和
除了 go.mod,模块还有一个 go.sum 文件。该文件记录了模块或模块依赖项引用的每个依赖项的每个版本的代码和 go.mod 文件的 SHA-256 校验和。Go 在引用新依赖项时持续更新 go.sum。
当 Go 获取模块的依赖项时,如果这些依赖项在 go.sum 中已有条目,Go 会验证这些依赖项的校验和。如果校验和与 go.sum 中的不匹配,构建将失败。这确保了模块的给定版本不能被其开发者或恶意方更改而不会导致构建失败。
Go 1.12+ 可以配置为使用校验和数据库。如果配置为这样做,当 Go 获取依赖项且 go.sum 中没有相应条目时,Go 会查询配置的校验和数据库以获取依赖项的校验和,而不是从下载的依赖项计算。如果在校验和数据库中找不到依赖项,构建将失败。如果下载的依赖项的校验和与校验和数据库的结果不匹配,构建将失败。以下环境变量控制此行为:
GOSUMDB标识要查询的校验和数据库的名称,以及可选的公钥和服务器 URL。- 值为
off时完全禁用校验和数据库查询。 - 如果未定义
GOSUMDB,Go 1.13+ 使用sum.golang.org。
- 值为
GONOSUMDB是一个逗号分隔的模块后缀列表,应禁用对这些模块的校验和数据库查询。支持通配符。GOPRIVATE是一个逗号分隔的模块名称列表,具有与GONOSUMDB相同的功能,此外还禁用其他功能。
代理
Go 1.12+ 可以配置为从 Go 代理获取模块,而不是直接从模块的 VCS 获取。如果配置为这样做,当 Go 获取依赖项时,它会按顺序尝试从配置的代理获取依赖项。以下环境变量控制此行为:
GOPROXY是一个逗号分隔的模块代理查询列表。- 值为
direct时完全禁用模块代理查询。 - 如果列表中的最后一个条目是
direct,且没有代理能提供依赖项,Go 会回退到上述获取包的过程。 - 如果未定义
GOPROXY,Go 1.13+ 使用proxy.golang.org,direct。
- 值为
GONOPROXY是一个逗号分隔的模块后缀列表,这些模块应直接获取,而不是从代理获取。支持通配符。GOPRIVATE是一个逗号分隔的模块名称列表,具有与GONOPROXY相同的功能,此外还禁用其他功能。
获取
从 Go 1.12 开始,获取模块或包的过程如下:
- 如果
GOPROXY是代理列表且模块未被GONOPROXY或GOPRIVATE排除,则按顺序查询它们,并在第一个有效响应时停止。 - 如果
GOPROXY是direct,或模块被排除,或GOPROXY以,direct结尾且没有代理提供该模块,则回退。- 查询
https://{模块或包名}?go-get=1。 - 扫描响应中的
go-import元标签。 - 使用指定的 VCS 获取元标签指示的仓库。
- 如果
{vcs}字段是mod,则应将 URL 视为模块代理而不是 VCS。
- 查询
- 如果模块是直接获取而不是作为依赖项获取,则停止。
- 如果
go.sum包含与模块对应的条目,则验证校验和并停止。 - 如果
GOSUMDB标识了一个校验和数据库且模块未被GONOSUMDB或GOPRIVATE排除,则获取模块的校验和,将其添加到go.sum,并根据它验证下载的源代码。 - 如果
GOSUMDB是off或模块被排除,则从下载的源代码计算校验和并将其添加到go.sum。
下载的源代码必须包含 go.mod 文件。go.mod 文件必须包含一个 module 指令,指定模块的名称。如果 go.mod 指定的模块名称与用于获取模块的名称不匹配,模块编译将失败。
如果模块是直接获取且未指定版本,或者模块是作为依赖项添加且未指定版本,Go 使用模块的最新版本。如果模块是从代理获取的,Go 会查询代理获取版本列表并选择最新的版本。如果模块是直接获取的,Go 会查询仓库获取标签列表并选择最新的有效语义化版本。
身份验证
在 Go 1.13 之前的版本中,Go 对请求进行身份验证的支持有些不一致。Go 1.13 改进了对 .netrc 身份验证的支持。如果通过 HTTPS 发出请求且可以找到匹配的 .netrc 条目,Go 会将 HTTP 基本身份验证凭据添加到请求中。Go 不对通过 HTTP 发出的请求进行身份验证。Go 拒绝 GOPROXY 中包含嵌入凭据的仅 HTTP 条目。
在未来的版本中,Go 可能会添加对任意身份验证标头的支持。详情请关注 golang/go#26232。