教程:创建一个 Go 模块

本文翻译自《Tutorial: Create a Go module》。

目录

先决条件

创建一个其他人可以使用的模块

这是介绍Go语言的一些基本特性的教程的第一部分。如果你刚刚开始使用Go,请务必查看教程:Go入门,其中介绍了go命令、Go模块和非常简单的Go代码。

在本教程中,你将创建两个模块。第一个是旨在由其他库或应用程序导入的库。第二个是将调用第一个模块的应用程序。

本教程的顺序包括七个简短的主题,每个主题都说明了该语言的不同部分。

1 创建一个模块——编写一个小模块,其中包含可以被另一个模块调用的函数。

2 从另一个模块调用你的代码——导入并使用你的新模块。

3 返回并处理一个错误——添加简单的错误处理程序。

4 返回一个随机问候语——处理切片中的数据(Go的动态大小数组)。

5 回复多个人的问候语——在一个映射(map)中存储键/值对。

6 添加一个测试——使用Go内置的单元测试功能来测试你的代码。

7 编译和安装这个应用程序——在本地编译和安装你的代码。

注意:有关其他教程,请参阅教程

先决条件

  • 一些编程经验。此处的代码非常简单,但有助于了解有关函数、循环和数组的一些知识。
  • 一个编辑代码的工具。你拥有的任何文本编辑器都可以正常工作。大多数文本编辑器都对 Go 有很好的支持。最受欢迎的是VSCode(免费)、GoLand(付费)和Vim(免费)。
  • 一个命令行终端。Go在Linux和Mac的任何终端以及Windows中的PowerShell或 cmd上都能很好地工作。

创建一个其他人可以使用的模块

首先创建一个Go模块。在一个模块中,一组离散且有用的功能集合构成一个或多个相关包。例如,你可以创建一个模块,其中的包具有进行财务分析的功能,以便其他编写财务应用程序的人可以使用你的工作。有关开发模块的更多信息,请参阅开发和发布模块

Go代码被分组到包中,包又被分组到模块中。你的模块指定运行代码所需的依赖项,包括Go版本和它需要的其他模块集。

当你在模块中添加或改进功能时,你发布了该模块的新版本。编写调用模块中函数的代码的开发人员可以导入模块的更新后的包并可以在将其投入生产使用之前使用新版本进行测试。

1 打开命令行提示符并cd到你的家目录。

在Linux或Mac上:

cd

在Windows上:

cd %HOMEPATH%

2 为你的Go模块源代码创建一个greetings目录。

例如,在你的家目录使用以下命令:

mkdir greetings
cd greetings

3 使用go mod init命令启动你的模块。

运行go mod init命令,并传给它你的模块路径作为参数——在这里,使用example.com/greetings。如果你发布一个模块,这必须是该模块可以被Go工具下载的路径的一部分。另一部分路径将是你的代码存储库的URL。

有关使用模块路径命名模块的更多信息,请参阅管理依赖项

$ go mod init example.com/greetings
go: creating new go.mod: module example.com/greetings

go mod init命令创建一个go.mod文件来跟踪代码的依赖项。到目前为止,该文件仅包含你的模块名称和你的代码支持的Go版本。但是当你添加依赖项时,go.mod文件将列出你的代码所依赖的版本。这使构建可重现,并让你直接控制要使用的模块及其版本。

4 在你的文本编辑器中,创建一个用于编写代码的文件,并将其命名为greetings.go。

5 将以下代码粘贴到你的greetings.go文件中并保存该文件。

package greetings

import "fmt"

// Hello函数返回对名字由name参数给出的人的一句问候语。
func Hello(name string) string {
    // 返回一句问候语,把name参数的值嵌入其中。
    message := fmt.Sprintf("Hi, %v. Welcome!", name)
    return message
}

这是你的模块的第一个代码块。它向任何请求Hello函数的调用者返回一句问候语。你将在下一步中编写调用此函数的代码。

在此代码中,你:

  • 声明一个greetings包来收集相关的功能。
  • 实现一个Hello函数来返回一句问候语。

该函数接受一个name参数,其类型为字符串(string)类型。该函数还返回一个字符串。在Go中,一个名字以大写字母开头的函数可以被不在同一个包中的函数调用。这在Go中被称为导出名称(Exported names)。有关导出名称的更多信息,请参阅Go教程中的导出名称

  • 声明一个message变量来保存你的问候语。

在Go中,:=运算符是一种在一行中声明和初始化变量的快捷方式(Go使用右侧的值来确定变量的类型)。从长远来看,你可能会这样写:

var message string
message = fmt.Sprintf("Hi, %v. Welcome!", name)
  • 使用fmt包的Sprintf函数创建一句问候语。第一个参数是格式字符串,Sprintfname参数的值替换为%v格式的谓词。插入name参数的值即可完成这个问候语文本。
  • 将格式化后的问候语文本返回给调用者。

在下一步中,你将从另一个模块调用此函数

管理依赖项

本文翻译自《Managing dependencies》。

目录

使用和管理依赖项的工作流程

管理模块依赖项

查找和导入有用的包

在代码中启用依赖项跟踪

给一个模块命名

添加一个依赖项

获取一个指定版本的依赖项

发现可用的更新

升级或降级一个依赖项

同步你代码的依赖项

开发和测试未发布的模块代码

       需求某个本地目录中的模块代码

       需求来自你自己的代码仓库分支的外部模块代码

使用代码仓库标识符获取特定的某个提交(commit)

移除一个依赖项指定一个模块代理服务器

当你的代码使用外部包时,这些包(作为模块分发)成为依赖项。随着时间的推移,你可能需要升级或更换它们。Go提供了依赖项管理工具,可帮助你在合并外部依赖项时确保Go应用程序的安全。

本主题描述如何执行任务来管理你在代码中采用的依赖项。你可以使用Go工具执行其中的大部分操作。本主题还描述了如何执行一些你可能会觉得有用的其他依赖相关的任务。

另请参阅

  • 如果你不熟悉将依赖项作为模块来使用,请查看入门教程以获取简要介绍。
  • 使用go命令管理依赖项有助于确保你对依赖项的需求保持一致,并且go.mod文件的内容有效。有关命令的参考,请参阅go命令。你还可以通过键入go help 命令名从命令行获得帮助,例如go help mod tidy
  • 用于更改依赖项的Go命令编辑你的go.mod文件。有关该文件内容的更多信息,请参阅go.mod文件参考
  • 让你的编辑器或IDE能够感知Go模块可以使管理它们的工作变得更容易。有关支持Go的编辑器的更多信息,请参阅编辑器插件和IDE
  • 本主题不描述如何开发、发布和模块的版本控制供其他人使用。有关更多信息,请参阅开发和发布模块

使用和管理依赖项的工作流程

你可以通过Go工具获取和使用有用的包。在pkg.go.dev上,你可以搜索你可能觉得有用的包,然后使用go命令将这些包导入到你自己的代码中以调用它们的功。

下面列出了最常见的依赖管理步骤。有关每个步骤的更多信息,请参阅本主题中的对应小节。

1 在pkg.go.dev找到有用的包

2 在代码中导入所需的包

3 将你的代码添加到模块中以进行依赖跟踪(如果它不在模块中的话)。请参阅启用依赖项跟踪

4 添加外部包作为依赖项,以便你可以管理它们。

5 随着时间的推移,根据需要升级或降级依赖项的版本

管理模块依赖项

在Go中,你将依赖项作为模块来管理,在代码中导入这些模块的包。该过程得到以下支持:

  • 用于发布模块和检索其代码的去中心化系统。开发人员使他们的模块可供其他开发人员从代码仓库中使用,并使用版本号发布。开发人员使用版本号在代码仓库中发布他们的模块,供其他开发人员获取并使用。
  • 一个包搜索引擎和文档浏览器(pkg.go.dev),你可以在其中找到模块。请参阅查找和导入有用的包
  • 模块版本编号规约可帮助你了解模块的稳定性和向后兼容性。请参阅模块版本编号
  • 使你更容易管理依赖项的Go工具,包括获取模块的源代码、升级等。有关更多信息,请参阅本主题中的各个小节。

查找和导入有用的包

你可以搜索pkg.go.dev来查找包含可能有用的函数的包。

找到要在代码中使用的包后,在页面顶部找到包路径,然后单击复制路径按钮将路径复制到剪贴板。在你自己的代码中,将路径粘贴到import语句中,如下例所示:

import "rsc.io/quote"

在代码导入包后,启用依赖项跟踪并获取要编译的包代码。有关更多信息,请参阅在代码中启用依赖项跟踪添加依一个赖项

在代码中启用依赖项跟踪

要跟踪和管理你添加的依赖项,你首先要将代码放入其自己的模块中。这会在源代码树的根目录下创建一个go.mod文件。你添加的依赖项将列在该文件中。 要将你的代码添加到它自己的模块中,请使用go mod init命令。例如,从命令行切换到代码的根目录,然后按以下示例运行命令:

$ go mod init example/mymodule

go mod init命令的参数是模块的模块路径。如果可能,模块路径应该是源代码的存储库的位置。

如果一开始你不知道模块的最终存储库位置,请使用一个安全的替代品。这可以是你拥有的域名或你控制的其他名称(如公司名称),以及模块名称或源代码目录的路径。有关详细信息,请参见给一模块个命名

当你使用Go工具管理依赖关系时,这些工具会更新go.mod文件,以便它维护你当前的依赖项列表。

添加依赖项时,Go工具也会创建一个go.sum文件,其中包含你所依赖的模块的校验和。Go使用该文件来验证下载的模块文件的完整性,尤其是对其他项目开发人员而言。

把go.mod文件和go.sum文件和代码一起放在代码存储库中。

有关更多信息,请参阅go.mod参考手册

给一个模块命名

当你运行go mod init以创建用于跟踪依赖项的模块时,你指定一个模块路径作为模块的名称。模块路径是模块中的包的导入路径的前缀。请务必指定一个不会与其他模块的模块路径有冲突的模块路径。

模块路径至少要指出有关其来源的信息,例如公司名称或作者名称或所有者姓名。但是模块路径最好也能描述模块是什么或做什么。 模块路径通常采用以下形式:

<prefix>/<descriptive-text>

前缀prefix通常是部分描述模块的字符串,例如描述模块来源的字符串。这可能是:

  • 可以让Go工具找到模块源代码的存储库的位置(如果你要发布模块,则需要模块源代码的存储库)。例如它可能是github.com/<project-name>/。如果你认为可以发布模块供其他人使用,请使用此最佳实践。有关发布的详细信息,请参阅开发和发布模块
  • 一个你掌控的名字。如果你不使用存储库名称,请确保选择一个你确信不会被其他人使用的前缀。一个很好的选择是你公司的名字。避免使用widgets、utilities或app等常用术语。

对于描述性文本descriptive-text,最好选择项目名称。请记住,包的名称才是主要描述功能信息的。模块路径为这些包的名称创建了一个命名空间。

保留的模块路径前缀

Go保证不会在包名中使用以下字符串。

  • test–你可以使用test作为模块的模块路径前缀,该模块的代码旨在本地测试另一个模块中的功能。对作为测试的一部分创建的模块使用test路径前缀。例如,你的测试本身可能会运行go mod init test,然后以某种特定方式安装该模块,以便使用Go源代码分析工具进行测试。
  • example–在某些Go文档中用作模块路径前缀,例如在教程中创建的模块只是为了跟踪依赖项。

请注意,当示例可能是一个已发布的模块时,Go文档也会使用example.com来演示。

添加一个依赖项

从已发布的模块导入包后,可以使用go get命令将该模块添加为依赖项进行管理。

该命令执行以下操作:

  • 如果有必要,它会在你的go.mod文件中为命令行上给出的包的模块添加require指令。require指令跟踪所依赖的模块的最低版本。请参阅go.mod参考手册以了解更多信息。
  • 如果需要,它会下载模块源代码,以便你可以编译依赖于它们的包。它可以从proxy.golang.org等模块代理或直接从版本控制存储库下载模块。模块源代码缓存在本地。你可以设置Go工具下载模块的位置。有关更多信息,请参阅指定模块代理服务器

下面描述几个例子。

  • 要在模块中添加包的所有依赖项,请运行如下命令(“.”指的是当前目录中的包):$ go get .
  • 要添加特定的依赖项,请将其模块路径指定为该命令的参数:$ go get example.com/theirmodule

该命令还验证它下载的每个模块。这确保它在模块发布后没有发生改变。如果模块在发布后发生了变化——例如,开发人员更改了提交(commit)的内容——Go工具将显示一个安全错误。此身份验证检查可保护你免受可能已被篡改的模块的侵害。

获取一个指定版本的依赖项

你可以通过在go get命令中指定版本号来获取依赖模块的特定版本。该命令更新go.mod文件中的require指令(当然你也可以手动更新)。

你可能想要那样做,如果:

  • 你想要获得模块的指定预发布版本以进行试用。
  • 你发现你当前使用的版本不适合你,因此你想获得一个你知道的可以依赖的版本。
  • 你想要升级或降级你已经依赖的模块。

以下是使用go get命令的示例:

  • 要获得特定编号的版本,请在模块路径后附加一个@符号,后跟你想要的版本编号:$ go get example.com/[email protected]
  • 要获取最新版本,请在模块路径后附加@latest:$ go get example.com/theirmodule@latest

以下go.mod文件的require指令示例(有关更多信息,请参阅go.mod参考手册)说明了如何需求特定版本号:

require example.com/theirmodule v1.3.4

发现可用的更新

你可以检查当前模块中是否已经使用了更新版本的依赖项。使用go list命令显示模块的依赖项列表,以及该模块可用的最新版本。发现可用的升级后,你可以使用你的代码进行试用,以决定是否升级到新版本。

有关go list命令的更多信息,请参阅go list -m

这里有几个例子。

  • 列出当前模块依赖的所有模块,以及每个模块可用的最新版本:$ go list -m -u all
  • 显示特定模块最新可用的版本:$ go list -m -u example.com/theirmodule

升级或降级一个依赖项

你可以通过使用Go工具发现可用版本来升级或降级依赖的模块,然后将不同的版本添加为依赖项。

1 要发现新版本,请使用go list命令,如发现可用更新小节所述。

2 要将特定版本添加为依赖项,请使用获取一个指定版本的依赖项小节所述的go get命令。

同步你代码的依赖项

你可以管理所有导入的包对应的依赖项,同时删除不再使用的依赖项。

当你一直在更改代码和依赖项时,这可能很有用,可能会创建一个你管理的依赖项和下载模块的集合,这些模块不再与代码中导入的包依赖的模块集合相匹配。

要保持依赖项集合整洁,请使用go mod tidy命令。对于代码中导入的包集,此命令编辑你的go.mod文件以添加必需但缺少的模块。它还会删除不提供任何相关包的未使用的模块。

该命令除了一个标志-v外没有其他参数,它打印有关已删除模块的信息。

$ go mod tidy

开发和测试未发布的模块的代码

你可以指定你的代码使用可能还未发布的依赖项模块。这些模块的代码可能在它们各自的存储库中,在这些存储库的分支中,或者在当前依赖它们的模块所在的本地硬盘里。

你可能希望在以下情况下执行此操作:

  • 你想对外部模块的代码进行自己的更改,例如在分叉和/或克隆它之后。例如,你可能想要修复该模块,然后将其作为一个拉取请求(pull request)发送给模块的开发人员。
  • 你正在构建一个新模块并且尚未发布它,因此go get命令访问不到它所在的存储库。

需求本地目录中的某个模块的代码

你可以指定所需模块的代码与依赖它的代码位于同一本地硬盘里。当你处于以下情况时,你可能会发现这很有用:

  • 开发自己的独立模块并希望使用当前模块进行测试。
  • 修复外部模块中的问题或向外部模块添加功能,并希望使用当前模块进行测试。(请注意,你还可以从你自己的存储库分支中获取外部模块。有关更多信息,请参阅下文的需求来自你自己的代码仓库分支的外部模块代码。)

要让Go命令使用模块代码的本地副本,请在go.mod文件中使用replace指令替换require指令中给出的模块路径。有关指令的更多信息,请参阅go.mod参考手册

在下面的go.mod文件示例中,当前模块依赖外部模块example.com/theirmodule,使用不存在的版本号(v0.0.0-unpublished)来确保替换正常工作。replace指令然后用../theirmodule替换原始模块路径,该目录与当前模块目录处于同一级别。

module example.com/mymodule

go 1.16

require example.com/theirmodule v0.0.0-unpublished

replace example.com/theirmodule v0.0.0-unpublished => ../theirmodule

设置require/replace对时,使用go mod editgo get命令确保文件描述的依赖项保持一致:

$ go mod edit -replace=example.com/[email protected]=../theirmodule
$ go get example.com/[email protected]

注意:当你使用replace指令时,Go工具不会验证外部模块,如添加一个依赖项小节所述。

有关版本号的更多信息,请参阅模块版本编号

需求来自你自己的代码仓库分支的外部模块的代码

当你fork一个外部模块的代码仓库时(例如修复模块代码中的问题或添加新功能),你可以让Go工具使用你fork的代码作为模块的源代码。这对于测试你自己的代码的更改很有用。(请注意,你还可以在本地硬盘目录中需求模块代码。有关更多信息,请参阅需求本地目录中的某个模块的代码小节。)

为此,你可以在go.mod文件中使用replace指令,将外部模块的原始模块路径替换为你fork的代码仓库的路径。这会指示Go工具在编译时使用替换路径(fork的代码仓库的路径),同时允许你保持你代码里的import语句里的原始模块路径保持不变。

有关replace指令的更多信息,请参阅go.mod文件参考手册

在下面的go.mod文件示例中,当前模块需求外部模块example.com/theirmodule。然后replace指令使用example.com/myfork/theirmodule替换原始模块路径,这是模块代码仓库的你的一个分支。

module example.com/mymodule

go 1.16

require example.com/theirmodule v1.2.3

replace example.com/theirmodule v1.2.3 => example.com/myfork/theirmodule v1.2.3-fixed

设置require/replace对时,最好使用Go工具命令,以确保与go.mod文件中描述的需求保持一致。使用go list命令查看当前使用的模块的版本。然后使用go mod edit命令用fork的分支替换所需的模块:

$ go list -m example.com/theirmodule
example.com/theirmodule v1.2.3
$ go mod edit -replace=example.com/[email protected]=example.com/myfork/[email protected]

注意:当你使用replace指令时,Go工具不会对外部模块进行身份验证,如添加一个依赖项小节中所述。

有关版本号的更多信息,请参阅模块版本编号

使用代码仓库标识符获取特定的某个提交(commit)

你可以使用go get命令从某个模块的代码仓库中,提取指定某个commit里的未发布(译者注:未加版本号标签)的代码。

为此,你可以使用go get命令,并使用@符号指定你想要的代码。当你使用go get时,该命令将向你的go.mod文件添加一个需求外部模块的require指令,使用基于commit的详细信息的伪版本号。

以下示例提供了一些说明。这些基于一个模块,其源代码位于git存储库中。

  • 要提取指定某个commit里的模块代码,请附加@commithash(译者注:@commit的哈希值)形式:$ go get example.com/theirmodule@4cf76c2
  • 要提取指定某个分支里的模块代码,请附加@branchname(译者注:@分支名称)形式:$ go get example.com/theirmodule@bugfixes

移除一个依赖项

当你的代码不再使用某个模块中的任何包时,你可以停止将该模块作为依赖项进行追踪。

要停止追踪所有未使用的模块,请运行go mod tidy命令。此命令还可以在构建模块的包时添加所需的缺失依赖项。

$ go mod tidy

要删除特定依赖项,请使用go get命令,指定该模块的模块路径并附加@none,如下例所示:

$ go get example.com/theirmodule@none

go get命令还将降级或删除依赖于已删除模块的其他依赖项。

指定一个模块代理服务器

当你使用Go工具处理模块时,这些工具默认从proxy.golang.org(Google运营的公共模块镜像)或直接从模块的代码仓库下载模块。你可以指定Go工具使用另一个代理服务器来下载和验证模块。例如,一些人设置了一个自己的模块代理服务器,以便更好地控制依赖项的使用方式。

要指定另外的模块代理服务器供Go工具使用,请将GOPROXY环境变量设置为一个或多个另外的模块代理服务器的URL。Go工具将按照你指定的顺序尝试每个URL。默认情况下,GOPROXY首先指定一个Google运营的公共的模块代理,然后直接从模块的存储库中下载模块代码(由其模块路径指出):

GOPROXY="https://proxy.golang.org,direct"

有关GOPROXY环境变量(包括其值支持的他行为)的详细信息,请参阅go命令参考手册

你可以将变量设置为其他模块代理服务器的URL,用逗号或管道分隔多个URL。

  • 当你使用逗号时,只有当前URL返回HTTP 404或410时,Go工具才会尝试列表中的下一个URL。GOPROXY="https://proxy.example.com,https://proxy2.example.com"
  • 当你使用管道时,Go工具将尝试列表中的下一个URL,而不考虑HTTP错误代码。GOPROXY="https://proxy.example.com|https://proxy2.example.com"

Go模块经常在版本控制服务器和模块代理上开发和分发,有些服务器和代理在公共互联网上不可用。此时你可以设置GOPRIVATE环境变量来配置go命令,以便从私有源下载和构建模块。

GOPRIVATEGONOPROXY环境变量可以设置为与模块前缀匹配的全局模式列表,与这些模式匹配的模块是私有的,不应该从中代理任何请求。例如:

GOPRIVATE=*.corp.example.com,*.research.example.com

管理模块的源代码

本文翻译自《Managing module source》。

目录

Go工具如何查找你发布的模块

在存储库中组织代码

选择存储库的范围

一个存储库管理一个模块的源代码

一个存储库管理多个模块的源代码

当你开发要发布以供其他人使用的模块时,你可以通过遵循本篇文章中描述的存储库规约来确保其他开发人员更容易地使用你的模块。

本文描述了你在管理模块存储库时可能采取的操作。有关在版本之间进行修订时你将采取的工作流程步骤顺序的信息,请参阅模块发布和版本控制工作流程

此处描述的一些规约在模块中是必需的,而另一些则是最佳实践。本内容假设你熟悉管理依赖项中描述的基本模块使用实践。

Go支持以下用于发布模块的存储库:Git、Subversion、Mercurial、Bazaar和Fossil。

有关模块开发的概述,请参阅开发和发布模块

Go工具如何找到你发布的模块

在Go发布模块和获取代码的分布式系统中,你可以将代码保留在存储库中的同时发布模块。Go工具依赖于存储库路径和存储库标签的命名规则,它们指示模块的名称和版本号。当你的存储库符合这些要求时,你的模块代码可以通过Go工具(例如go get命令)从存储库下载。

当开发人员使用go get命令为其代码导入的包获取源代码时,该命令执行以下操作:

1 go get在Go源代码中的import语句的包路径中识别模块路径。

2 使用从模块路径派生的URL,该命令在模块代理服务器上或直接在其存储库中定位模块源代码。

3 通过将模块的版本号与存储库标签匹配来查找要下载的模块版本的源代码,以发现存储库中的代码。当尚不知道要使用的版本号时,go get将查找最新版本。

4 检索模块源代码并将其下载到开发人员的本地模块缓存。

在存储库中组织代码

通过遵循此处描述的规约,你可以保持简单的维护并改善开发人员对你的模块的使用体验。将模块代码放入存储库通常与使用其他代码一样简单。

下图说明了具有两个包的简单模块的源代码层次结构。

你的首次提交应包括下表中列出的文件:

文件描述
LICENSE模块的许可证。
go.mod描述模块,包括它的模块路径(实际上就是它的名称)和它的依赖关系。有关更多信息,请参阅go.mod参考
模块路径将在module指令中给出,例如:
module example.com/mymodule
有关选择模块路径的更多信息,请参阅管理依赖项
虽然你可以编辑go.mod文件,但你会发现通过go命令进行更改更可靠。
go.sum包含表示模块依赖项的加密哈希值。Go工具使用这些哈希值来验证下载的模块,试图确认下载的模块是可信的。如果此确认失败,Go将显示一个安全性错误。
当没有依赖项时,该文件将为空或不存在。你不应该编辑此文件,除非使用go mod tidy命令删除不需要的条目。
包目录和.go源代码文件目录构成模块中的包,.go文件就是源代码文件。

你可以在命令行创建一个空的存储库,添加将成为首次提交的一部分的文件,并在提交时添加注释。下面是一个使用git的例子:

$ git init
$ git add --all
$ git commit -m "mycode: initial commit"
$ git push

选择存储库的范围

当代码应该独立于其他模块中的代码进行版本控制时,你在模块中发布该代码。

将存储库设计为在其根目录中托管单个模块将有助于简化维护,尤其是随着时间的推移,当你发布新的次版本和补丁版本、创建新的主版本分支等时候。但是,如果你的其他模块的代码依赖它,你可以改为在单个存储库中维护一组模块。

一个存储库管理一个模块的源代码

你可以维护一个包含单个模块源代码的存储库。在此模型中,你将go.mod文件放在存储库的根目录里,存储库的根目录里还有包子目录,包子目录里有Go源代码文件。

这是最简单的方法,随着时间的推移,你的模块可能更易于管理。它可以帮助你避免在模块目录名上附加版本号。

一个存储库管理多个模块的源代码

你可以用单个存储库发布多个模块。例如,你可能在一个存储库中包含多个模块的源代码,但希望分别对这些模块进行版本控制。

作为模块根目录的每个子目录都必须有自己的go.mod文件。

当发布一个模块时,在子目录中管理模块源代码需要更改版本标签的形式。你必须在标签版本号前面加上对应的模块根目录下的子目录的名称。有关版本号的更多信息,请参阅模块版本编号

例如,对于下面的模块example.com/mymodules/module1的v1.2.3版本,你将拥有以下内容:

  • 模块路径:example.com/mymodules/module1
  • 版本标签:module1/v1.2.3
  • 用户导入的包路径:example.com/mymodules/module1/package1
  • 用户require指令中的模块路径:example.com/mymodules/module1 module1/v1.2.3

发布一个模块

本文翻译自《Publishing a module》。

当你想让一个模块可供其他开发人员使用时,你可以发布它,以便Go工具可以看到它。发布模块后,导入其包的开发人员将能够通过运行go get等命令来解析对模块的依赖关系。

注意:发布后不要更改模块的已加标签的版本。对于使用该模块的开发人员,Go工具会根据第一个下载的副本对下载的模块进行身份验证。如果两者不同,Go工具将返回安全错误。不要更改以前发布版本的代码,而是发布一个新版本。

另请参阅

发布步骤

使用以下步骤发布模块。

1 打开命令提示符并切换到本地存储库中的模块的根目录。

2 运行go mod tidy,它会删除模块可能积累的任何不再需要的依赖项。

$ go mod tidy

3 最后一次运行go test ./...以确保一切正常。

这会运行你使用Go测试框架编写的单元测试。

$ go test ./...
ok      example.com/mymodule       0.015s

4 使用git tag命令给项目打新版本号标签。

对于版本号,请使用一个向用户表明此版本中更改性质的数字。有关更多信息,请参阅模块版本编号

$ git commit -m "mymodule: changes for v0.1.0"
$ git tag v0.1.0

5 将新标签推送到源存储库。

$ git push origin v0.1.0

6 通过运行go list命令使模块可用,以提示Go使用有关你正在发布的模块的信息更新其模块索引。

在命令前面加上将GOPROXY环境变量设置为Go代理的语句。这将确保你的请求到达代理。

$ GOPROXY=proxy.golang.org go list -m example.com/[email protected]

对你的模块感兴趣的开发人员从中导入一个包并运行go get命令,就像他们对任何其他模块一样。他们可以针对最新版本运行go get命令,也可以指定特定版本,如下例所示:

$ go get example.com/[email protected]

开发一个主版本更新

本文翻译自《Developing a major version update》。

目录

主版本更新的注意事项

为主版本的发布创建分支

当你在潜在的新版本中所做的更改不能保证模块用户的向后兼容性时,你必须把这些更改更新到主版本。例如,如果你更改模块的公开API会破坏使用该模块以前版本的客户端代码,你就应该通过开发一个新的主版本来实现这种更改。

注意:每个版本类型——主版本、次版本、补丁版本或预发布版本——对模块的用户来说都有不同的含义。这些用户依靠这些差异来了解版本对他们自己的代码所代表的风险级别。换句话说,在准备发布时,请确保其版本号准确地反映了自上一版本以来的更改的性质。有关版本号的更多信息,请参阅模块版本编号

另请参阅

主版本更新的注意事项

仅在绝对必要时才更新到新的主版本。因为主版本更新对你和你模块的用户来说都意味着重大的变化。当你考虑进行主版本更新时,请考虑以下事项:

  • 发布新的主版本对你以前的主版本的维护意味着什么,你需要向你的用户明确这一点。以前的版本是否已弃用?还是像以前一样支持?你会维护以前的版本,包括Bug修复吗?
  • 准备好维护两个版本:旧版本和新版本。例如,如果你修复了一个Bug,你通常会将这些修复移植到另一个中。
  • 请记住,从依赖管理的角度来看,新的主版本是一个新模块。在你发布新的主版本后,你的用户将需要更新以使用新模块,而不是简单地升级。这是因为新的主版本与之前的主版本具有不同的模块路径。例如,对于模块路径为example.com/mymodule的模块,v2版本将具有模块路径example.com/mymodule/v2。
  • 当你开发新的主版本时,你还必须在导入新模块里的包的那些代码里,修改导入路径。如果你的模块的用户想要升级到新的主版本,他们也必须更改他们的导入路径。

为主版本的发布创建分支

在准备开发新的主版本时,最直接的处理源代码的方式是从存储库创建先前主版本的最新版本的分支。

例如,在命令提示符下,你可以cd到模块的根目录,然后在那里创建一个新的v2分支。

$ cd mymodule
$ git checkout -b v2
Switched to a new branch "v2"

创建源代码的分支后,需要对新版本的源代码进行以下更改:

  • 在新版本的go.mod文件中,将新的主版本号附加到模块路径,如下例所示:

现有版本:example.com/mymodule

新版本:example.com/mymodule/v2

  • 在Go代码中,更新从该模块导入包的每个导入路径,将主版本号附加到模块路径部分。

旧的导入语句:import“example.com/mymodule/package1”

新的导入语句:import“example.com/mymodule/v2/package1”

有关发布步骤,请参见发布一个模块

模块发布和版本控制工作流程

本文翻译自《Module release and versioning workflow》。

目录

常见的工作流程步骤

针对未发布的模块进行编程

发布预发布(pre-release)版本

发布第一个(不稳定的)版本

发布第一个稳定版本

发布Bug修复

发布不破坏API的更改

发布破坏API的更改

当你开发供其他开发人员使用的模块时,你可以遵循有助于使用该模块的开发人员获得可靠、一致的体验的工作流程。本主题描述了该工作流程中的高级步骤。

有关模块开发的概述,请参阅开发和发布模块

也请参阅

  • 如果你只想在代码中使用外部包,请务必查看管理依赖项
  • 对于每个新版本,你都用其版本号表示对模块的更改。有关更多信息,请参阅模块版本编号

常见的工作流程步骤

以下序列说明了一个新模块的发布和版本控制的工作流程步骤。有关每个步骤的更多信息,请参阅本主题中的各个章节。

1 开始一个模块并组织它的源代码,使开发人员更容易使用,使你更容易维护。

如果你是开发模块的新手,请查看教程:创建Go模块

在Go的去中心化模块发布系统中,如何组织代码很重要。有关更多信息,请参阅管理模块源代码

2 开始编写调用未发布模块中的函数的本地客户端代码

在发布模块之前,它不能用于使用go get等命令的典型的依赖管理工作流程。在此阶段测试模块代码的一个好方法是,在本地目录中调用并测试它的代码。

有关本地开发的更多信息,请参阅下文的针对未发布的模块进行编程小节。

3 当模块的代码准备好供其他开发人员试用时,你可以开始发布alphas和beta等v0预发布版。有关详细信息,请参见下文的发布预发布(pre-release)版本小节。

4 发布一个v0版本,不保证稳定,但用户可以试用。有关详细信息,请参见下文的发布第一个(不稳定的)版本小节。

5 发布v0版本后,你可以(而且应该!)继续发布它的新版本

这些新版本可能包括bug修复(补丁版本)、对模块公开API的添加(次要版本),甚至是破坏性的更改。因为v0版本不保证稳定性或向后兼容性,所以你可以对其版本进行重大更改。

有关详细信息,请参见下文的发布Bug修复小节和发布不破坏API的更改小节。

6 当你准备好发布稳定版本时,你可以将预发布版本发布为alphas版本或beta版本。有关详细信息,请参见下文的发布预发布(pre-release)版本小节。

7 发布v1作为第一个稳定版本

这是第一个对模块稳定性做出承诺的版本。有关详细信息,请参见下文的发布第一个稳定版本小节。

8 在v1版本中,继续修复bug,并在必要时添加模块的公共API。

有关详细信息,请参见下文的发布Bug修复小节和发布不破坏API的更改小节。

9 当无法避免时,在一个新的主版本中发布破坏性的更改。

主版本更新——例如从v1.x.x到v2.x.x——对于模块的用户来说可能是一个非常具有破坏性的升级。这应该是最后的手段。有关更多信息,请参阅下文的发布破坏API的更改小节。

针对未发布的模块进行编程

当你开始开发模块或模块的新版本时,你还没有发布它。在发布模块之前,你将无法使用Go命令将该模块添加为依赖项。相反,当你在另一个模块中编写调用这个未发布模块的函数的客户端代码时,你首先需要在本地文件系统里引用该模块的副本。

你可以使用客户端模块的go.mod文件中的replace指令从本地引用该模块。有关更多信息,请参阅在本地目录中需求模块代码

发布预发布(pre-release)版本

你可以发布预发布版本,使模块可供其他人试用并向你提供反馈。预发布版本不保证稳定性。

预发布版本号后附有预发布标识符。有关版本号的更多信息,请参见模块版本号

以下是两个例子:

v0.2.1-beta.1
v1.2.3-alpha

在提供预发布版本时,请记住,使用预发布版本的开发人员需要使用go get命令按版本号明确指出它。这是因为,在默认情况下,go命令在定位你需求的模块时更喜欢正式的发布版本而不是预发布版本。因此,开发人员必须通过显式地指出来获得预发布版本,如下例所示:

go get example.com/[email protected]

你可以通过给存储库中的模块代码加标签来发布预发布版本,并在标签中指定预发布标识符。有关更多信息,请参阅发布模块

发布第一个(不稳定的)版本

当你发布预发布版本时,你可以发布不保证稳定性或向后兼容性的发布版本,但给你的用户一个试用该模块并向你提供反馈的机会。

不稳定版本是指版本号在v0.x.x范围内的版本。v0版本不保证稳定性或向后兼容性。但它为你提供了一种方法,可以在使用v1和更高版本做出稳定性承诺之前获得反馈并改进你的API。有关更多信息,请参见模块版本编号

与其他已发布版本一样,你可以在发布稳定的v1版本之前进行更改代码时增加v0版本号的次版本号和补丁版本号部分。例如,在发布v.0.0.0版本之后,你可能会发布带有第一组Bug修复的v0.0.1版本。 这是一个示例版本号:

v0.1.3

你可以通过给存储库中的模块代码加标签来发布不稳定版本,并在标签中指定v0版本号。有关更多信息,请参阅发布模块

发布第一个稳定版本

你的第一个稳定版本将具有v1.x.x版本号。第一个稳定版本是在预发布版本和v0版本之后发布的,你可以通过它们获得反馈、修复Bug并为用户稳定模块。

在v1版本中,你向使用你的模块的开发人员做出以下承诺:

  • 他们可以升级到主版本号后续的次版本和补丁版本,而不会破坏自己的代码。
  • 你不会对模块的公共API进行进一步的更改——包括它的函数和方法签名——这会破坏向后兼容性。
  • 你不会删除任何导出的类型,因为这会破坏向后兼容性。
  • 未来对API的更改(例如向结构体添加新字段)将向后兼容,并将包含在新的次版本中。
  • Bug修复(例如安全修复)将包含在补丁版本中或作为次版本的一部分。

注意:虽然你的第一个主版本可能是v0版本,但v0版本并没有稳定性或向后兼容性保证。因此,当你从v0升级到v1时,你无需注意破坏向后兼容性,因为v0版本被认为是不稳定的。

有关版本号的更多信息,请参阅模块版本编号。 下面是一个稳定版本号的例子:

v1.0.0

你通过给存储库中的模块代码加标签来发布第一个稳定版本,并在标签中指定v1版本号。有关更多信息,请参阅发布模块

发布Bug修复

你可以发布一个版本,其中的更改仅限于Bug修复。这称为补丁版本。

补丁版本仅包含较小的更改。特别是,它没有对模块的公开API进行任何更改。使用代码的开发人员可以安全地升级到这个版本,而无需更改他们的代码。

注意:你的补丁版本应该尽量不要将任何该模块自己的依赖项升级超过补丁版本。否则,升级到该模块补丁版本的人可能会意外地对他们使用的间接依赖项进行更具侵入性的更改。

补丁版本会增加模块版本号的补丁部分。有关更多信息,请参阅模块版本编号

在以下示例中,v1.0.1是一个补丁版本。

旧版本:v1.0.0

新版本:v1.1.0

你可以通过给存储库中的模块代码加标签来发布次要版本,增加标签中的次要版本号。有关更多信息,请参阅发布模块

发布破坏API的更改

你可以通过发布(major)版本来发布破坏向后兼容的版本。

主版本不保证向后兼容性,通常是因为它包含对模块公开API的更改,这些更改会破坏调用模块先前版本的代码。

考虑到主版本升级可能对依赖于模块的代码产生的破坏性影响,如果可以的话,你应该避免主版本更新。有关主版本更新的更多信息,请参阅开发主版本更新。有关避免进行破坏性更改的策略,请参阅博客文章保持模块兼容

如果发布其他类型的版本需要使用版本号来给模块代码加标签,那么发布主版本更新需要更多的步骤。

1 在开始开发新的主版本之前,在你的存储库中为新版本的源代码创建一个位置。

一种方法是在存储库中创建一个新的分支,专门用于新的主版本及其后续的次版本和补丁版本。有关更多信息,请参见管理模块源代码。 2 在模块的go.mod文件中,修改模块路径以附加新的主版本号,如下例所示:

example.com/mymodule/v2

既然模块路径是模块的标识,此更改有效地创建了一个新模块。它还更改了包路径,确保开发人员不会无意中导入破坏其代码的版本。相反,那些想要升级的用户将显式地用新路径替换旧路径。

3 在你的代码中,更改你正在更新的模块里的任何包的导入路径,包括你正在更新的模块里的包。你需要这样做,因为你更改了模块路径。

4 与任何新版本一样,在发布正式版本之前,你应该发布预发布版本以获得反馈和Bug报告。

5 通过在存储库中给模块代码加标签,增加标签中的主版本号(例如从v1.5.2到v2.0.0),发布新的主版本。

有关详细信息,请参见发布一个模块

教程:多模块工作空间入门

本文翻译自《Tutorial: Getting started with multi-module workspaces》。

目录

先决条件

为代码创建模块

创建工作空间

下载并修改golang.org/x/example模块

了解有关工作空间的详细信息

本教程介绍Go中多模块工作空间的基础知识。使用多模块工作空间,你可以告诉Go命令,你正在同时在多个模块中编写代码,并且可以轻松地在这些模块中构建和运行代码。

在本教程中,你将在共享的多模块工作空间中创建两个模块,对这些模块进行更改,并在一个构建中查看这些更改的结果。

注意:有关其他教程,请参见教程

先决条件

  • 安装Go 1.18或更高版本。
  • 编辑代码的工具。任何文本编辑器都可以正常工作。
  • 命令行终端。Go在Linux和Mac上的任何终端以及Windows中的PowerShell或cmd上都能很好地工作。

本教程需要go1.18或更高版本。确保你已经使用Go.dev/dl上的链接安装了Go 1.18或更高版本。

为你的代码创建一个模块

首先,为你要编写的代码创建一个模块。

1 打开命令提示符并切换到你的家目录。 在Linux或Mac上:

$ cd ~

在Windows上:

C:\> cd %HOMEPATH%

(译者注:或者执行C:\> cd %USERPROFILE%

本教程的其余部分将显示$作为提示符。你使用的命令也可以在Windows上使用。

2 在命令提示符下,为代码创建一个名为workspace的目录。

$ mkdir workspace
$ cd workspace

3 初始化模块

我们的示例将创建一个依赖于golang.org/x/example的新模块hello

创建hello模块:

$ mkdir hello
$ cd hello
$ go mod init example.com/hello
go: creating new go.mod: module example.com/hello

使用go get添加golang.org/x/example依赖项。

$ go get golang.org/x/example

在hello目录中创建hello.go,并输入以下内容:

package main

import (
    "fmt"

    "golang.org/x/example/stringutil"
)

func main() {
    fmt.Println(stringutil.Reverse("Hello"))
}

现在,运行hello程序:

$ go run example.com/hello
olleH

创建工作空间

在这一步中,我们将创建一个go.work文件来指定带有模块的工作空间。

初始化工作空间

workspace目录中,运行:

$ go work init ./hello

go work init命令告诉go为包含./hello目录中的模块的工作空间创建一个go.work文件。

go命令生成一个如下所示的go.work文件:

go 1.18

use ./hello

go.work文件的语法与go.mod相似。

go指令告诉Go应该使用哪个版本的Go来解释Go文件。它类似于go.mod文件中的go指令。

use指令告诉Go在构建时hello目录中的模块应该是主(main)模块。

因此,在workspace的任何子目录中,该模块都将处于活动状态。

运行workspace目录下的程序

在workspace目录中,运行:

$ go run example.com/hello
olleH

Go命令把包含在工作空间中的所有模块作为主模块。这允许我们引用一个模块中的包,甚至模块外的包。在模块或工作空间之外运行go run命令会导致错误,因为go命令不知道要使用哪些模块。

接下来,我们将golang.org/x/example模块的本地副本添加到工作空间。然后,我们将向stringutil包添加一个新函数,我们可以使用它来代替Reverse

下载并修改golang.org/x/example模块

在这一步中,我们将下载包含golang.org/x/example模块的Git存储库的副本,将其添加到工作空间,然后向其中添加一个我们将在hello程序中使用的新函数。

1 克隆存储库 在workspace目录中,运行git命令来克隆存储库:

$ git clone https://go.googlesource.com/example
Cloning into 'example'...
remote: Total 165 (delta 27), reused 165 (delta 27)
Receiving objects: 100% (165/165), 434.18 KiB | 1022.00 KiB/s, done.
Resolving deltas: 100% (27/27), done.

2 将模块添加到工作空间

$ go work use ./example

go work use命令将一个新模块添加到go.work文件中。它现在看起来像这样:

go 1.18

use (
    ./hello
    ./example
)

该模块现在包括example.com/hello模块和golang.org/x/example模块。

这将允许我们使用我们将在stringutil模块的副本中编写的新代码,而不是使用go get命令下载的模块缓存中的该模块版本。(译者注:这说明go.work文件中use的模块被使用的优先级高于本地模块缓存中和网络上的同名模块。)

3 添加新函数

我们将在golang.org/x/example/stringutil包中添加一个将字符串大写的新函数。

workspace/example/stringutil目录中创建一个名为toupper.go的新文件,其中包含以下内容:

package stringutil

import "unicode"

// ToUpper把参数字符串s里的所有rune字符变为大写
func ToUpper(s string) string {
    r := []rune(s)
    for i := range r {
        r[i] = unicode.ToUpper(r[i])
    }
    return string(r)
}

4 修改hello程序以使用该函数

修改workspace/hello/hello.go的内容,包含以下内容:

package main

import (
    "fmt"

    "golang.org/x/example/stringutil"
)

func main() {
    fmt.Println(stringutil.ToUpper("Hello"))
}

运行工作空间里的代码

在workspace目录里,运行

$ go run example.com/hello
HELLO

Go命令找到在命令行上给出的,由go.work文件指定的,在hello目录中的example.com/hello模块。并类似地解析由go.work文件导入的golang.org/x/example模块。

可以使用go.work来代替添加replace指令跨多个模块工作。

由于这两个模块位于同一个工作空间中,因此很容易在一个模块中进行更改并在另一个模块中使用它。

更进一步

现在,要正确发布这些模块,我们需要发布golang.org/x/example模块,例如v0.1.0。 这通常是通过在模块的版本控制存储库里给提交(commit)加标签来完成的。

有关更多详细信息,请参阅模块发布工作流程文档。发布完成后,我们可以在hello/go.mod中增加对golang.org/x/example模块的需求:

cd hello
go get golang.org/x/[email protected]

这样,go命令就可以正确地解析工作空间之外的模块。

学习更多工作空间相关的知识

go命令除了我们之前在教程中见过的go work init之外,还有多个子命令用于处理工作空间:

  • go work use [-r] [dir]dir添加一个use指令到go.work文件,如果dir参数指定的目录存在的话,如果dir参数指定的目录不存在,该命令就会删除对应的use指令。dir的工作文件(如果存在),如果参数目录不存在,则删除use目录。-r标志递归地检查dir参数指定的目录的子目录。
  • go work edit编辑go.work文件,类似于go mod edit
  • go work sync将工作空间构建列表中的依赖项同步到每个工作空间模块中。

有关工作空间和go.work文件的详细信息,请参见《Go模块文档》中的工作空间(workspace)小节。