如何分发 Go 模块

作者选择了 Diversity in Tech Fund以作为 Write for Donations计划的一部分接受捐款。

介绍

许多现代编程语言允许开发者将现成的库分发给他人用于他们的程序,而Go也不例外. 虽然有些语言使用一个中央寄存器来安装这些库,Go从用于创建库的同一种版本控制寄存器中分发. Go还使用一个叫 [semantic serving] (https://semver.org/)的版本系统来显示用户何时和做了什么样的修改. 这有助于用户了解更新的模块是否安全地迅速升级到模块中,并有助于确保他们的软件继续配合模块向前发展.

在本教程中,您将创建和发布一个新的模块,学习使用语义版本,并发布模块的语义版本。

前提条件

创建一个模块来发布

与许多其他编程语言不同,Go模块是直接从其所居住的源代码存储库中分发的,而不是独立的包存储库。这使得用户更容易在他们的代码中找到参考的模块,而模块维护者则可以发布其模块的新版本。

要开始创建您的模块,您将使用git 克隆在您创建的空存储库作为下载初始存储库的前提的一部分。这个存储库可以被克隆到您想要的任何地方在您的计算机上,但许多开发人员倾向于为他们的项目有一个目录。

创建项目目录并导航到它:

1mkdir projects
2cd projects

项目目录中运行git 克隆,将您的存储库克隆到您的计算机:

1git clone [email protected]:your_github_username/pubmodule.git

克隆模块会将你的空模块下载到你的项目目录中的pubmodule目录中,你可能会收到一个警告,即你已经克隆了一个空存储库,但这不是什么可担心的:

1[secondary_label Output]
2Cloning into 'pubmodule'...
3warning: You appear to have cloned an empty repository.

接下来,更改您下载的目录:

1cd pubmodule

一旦您在模块目录中,您将使用go mod init创建新模块,并将其位置作为模块名称。

1go mod init github.com/your_github_username/pubmodule

Go 将通过告诉您它创建了 go.mod 文件来确认您的模块是创建的:

1[secondary_label Output]
2go: creating new go.mod: module github.com/your_github_username/pubmodule

最后,使用您最喜欢的文本编辑器,如nano,创建和打开与您的存储库相同名称的文件:pubmodule.go

1nano pubmodule.go

这个文件的名称可以是任何东西,但使用与包相同的名称使您更容易知道在与未知的包一起工作时应该从哪里开始。 但是,包名称本身应该与您的存储名称相同。 这样,当有人从您的包中引用一种方法或类型时,它就匹配了存储库,例如‘pubmodule.MyFunction’。 这将使他们更容易知道包来自哪里,如果他们需要稍后引用它。

接下来,将一个Hello方法添加到您的包中,该方法将返回字符串Hello, You!

1[label projects/pubmodule/pubmodule.go]
2package pubmodule
3
4func Hello() string {
5  return "Hello, You!"
6}

您现在使用go mod init创建了一个新的模块,该模块名称匹配您的远程存储库(‘github.com/your_github_username/pubmodule’)。

发布模块

一旦你创建了一个本地模块,你已经准备好为其他用户提供它,是时候发布你的模块了. 因为Go模块是从相同的代码存储库中分发的,你将你的代码委托到你的本地Git存储库,并将其推到你的存储库在github.com/your_github_username/pubmodule

在您将代码发送到您的本地Git存储库之前,最好确保您不会发送任何您不希望发送的文件,然后在您将代码推到GitHub时将其公开发布。

1git status

结果将看起来像这样:

1[secondary_label Output]
2On branch main
3
4No commits yet
5
6Untracked files:
7  (use "git add <file>..." to include in what will be committed)
8    go.mod
9    pubmodule.go

您应该看到由go mod init命令创建的go.mod文件,以及您创建的Hello函数的pubmodule.go文件。

当你确定你只有你正在寻找的文件时,你可以用git add阶段文件,并用git commit将它们连接到存储库:

1git add .
2git commit -m "Initial Commit"

结果将看起来像这样:

1[secondary_label Output]
2[main (root-commit) 931071d] Initial Commit
3 2 files changed, 8 insertions(+)
4 create mode 100644 go.mod
5 create mode 100644 pubmodule.go

最后,使用git push命令将模块推到GitHub存储库:

1git push

结果将看起来像这样:

1[secondary_label Output]
2Enumerating objects: 4, done.
3Counting objects: 100% (4/4), done.
4Delta compression using up to 8 threads
5Compressing objects: 100% (3/3), done.
6Writing objects: 100% (4/4), 367 bytes | 367.00 KiB/s, done.
7Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
8To github.com:your_github_username/pubmodule.git
9 * [new branch]      main -> main

运行git push命令后,您的模块将被推到您的存储库,现在可以被其他人使用。如果您没有发布的版本,Go将使用存储库的默认分支中的代码作为您的模块的代码。

在这个区域,你拿了当地 您创建并发布到您的 GitHub 寄存器的模块, 供其他人使用 。 虽然您现在有一个已发布的模块,但维护一个公共模块的另一部分是确保您的模块的用户能够使用一个稳定的版本. 你可能会想做一些改变,并在你的模块中添加一些特性,然后继续前进,但是如果你在模块中不使用版本而做出这些改变,你可能会意外地打破使用你的模块的人的代码. 为了解决这个问题,当您在开发中达到一个新的里程碑时,您可以在您的模块中添加版本. 不过,在添加新版本时,一定要选择一个有意义的版本编号,这样你的用户就能知道他们是否安全地立即升级.

语义版本

一个有意义的版本号码会让您的用户了解公共界面,或者API,他们互动的多少已经改变. Go通过一个被称作 [语义版本] (https://semver.org/) 的版本方案传达这些修改,或简称为"SemVer". (Semantic版本使用版本字符串来表达关于代码更改的含义,这就是Semantic版本获得其名称的地方. Go的模块系统遵循SemVer来确定哪些版本比您目前使用的版本更新,以及更新的模块是否安全可以自动升级.

语义化版本给一个版本字符串中的每个数字一个意义. SemVer中一个典型的版本包含三个主要数字:主要版本,小版本和补丁版本. 每个数字都与. ' 合并,形成版本,如1.2.3'。 编号顺序为主要版本第一,次要版本第二,补丁版本最后. 这样一来,在查看一个版本时,可以看到哪个版本是更新的,因为特定位置的数字比以前的版本要高. 例如,2.2.3'版本比1.2.3'更新,因为主要版本较高。 同样,1.4.3'的版本比1.2.10'的版本更新,因为其次要版本更高。 尽管10'在补丁版本中高于3',但4'小版本高于2',因此该版本优先。 当版本字符串中的数字增加时,版本之后的所有其他部分重置为"0". 例如,增加 " 1.3.10 " 这一小版本将导致 " 1.4.0 " ,而增加 " 2.4.1 " 的主要版本将导致 " 3.0.0 " .

使用这些规则允许 确定运行时要使用的模块的哪个版本。 例如,假设您有一个使用模块1.4.3'版本github.com/ your_github_username/pubmodule'的项目。 如果依赖于‘pubmodule'稳定,你可能只想自动升级补丁版本('. 如果运行去得到-u=patch github.com/你的_github_用户名/pubmodule命令, Go将会看到您想要升级模块的补丁版本,并且只寻找以"1.4"作为版本主要和次要部分的新版本.

在创建您模块的新发布时,必须考虑您模块的公开API是如何变化的. 语义版本字符串的每个部分向你和你的用户传达API更改的范围. 这些类型的变化通常分为三个不同的类别,与版本的每个组成部分一起排出. 最小的修改会增加补丁版本,中等的修改会增加小版本,而最大的修改会增加主要版本. 使用这些分类来决定要增加的版本号,将帮助您避免破坏自己的代码和任何依赖您模块的其他人的代码.

主要版本号码

SemVer版本中的第一个号码是主要版本号码(1.4.3')。 主要版本编号是发布您模块新版本时需要考虑的最重要数字. 一个主要版本的更改用于向您的公共 API 显示后向相容的更改 。 一个逆向变化 将是您模块中的任何变化 如果在不做任何其它变化的情况下升级, 会导致程序崩溃。 断裂可能意味着由于函数名已经改变而未能建立,或者改变图书馆的工作方式,从而导致返回v1'而不是`"1'。 不过,这只针对您的公共 API , 意思是任何输出类型或方法 其他人可以使用。 如果该版本只包含改进,则您的库的用户不会注意到,它不需要重大版本修改. 要记住哪些变化可能适合这个类别,一个方法就是任何被认为是"更新"或"删除"的东西都会是主要的版本增加.

<美元 > [注] ** 注:** 与SemVer中的其他数字类型不同, " 0 " 的主要版本具有额外的特殊意义。 主要版本"0"被认为是"正在开发"版本. 任何主要版本为 " 0 " 的SemVer都被认为不稳定,而且API中的任何内容可随时改变。 当您创建了一个新的模块时,最好从"0"的主要版本开始,并且只更新小版本和补丁版本,直到您完成您的模块的初始开发. 一旦您的模块的公用API完成更改,并被认为您的用户稳定,是时候从"1.0.0"版本开始了. < $ > (美元)

您有一个名为UserAddress的函数,该函数目前接受一个字符串作为参数,并返回一个字符串:

1func UserAddress(username string) string {
2    // return user address as a string
3}

虽然该函数目前返回一个字符串,但您可以确定如果该函数返回一个字符串,如地址

1type Address struct {
2    Address string
3    PostalCode string
4}
5
6func UserAddress(username string) *Address {
7    // return user address and postal code struct
8}

这是一个主要版本变更的例子,因为它会要求用户对自己的代码进行更改,以便使用它。

另一个主要版本更改的例子是将一个新的参数添加到用户地址函数中,即使它仍然返回一个字符串:

1func UserAddress(username string, uppercase bool) string {
2    // return user address as a string, uppercase if bool is true
3}

由于此更改还要求用户更新他们的代码,如果他们使用用户地址函数,这也将需要一个主要的版本增加。

然而,您对代码所做的所有更改都不会那么剧烈,有时您会对公共 API 进行更改,这些更改会添加新的函数或值,但不会改变现有的函数或值。

小版本号码

SemVer版本中的第二个数字是较小的版本号(1.4.3)。一个较小的版本更改被用来信号你的公共API的后面兼容的更改。一个后面兼容的更改将是任何不影响当前使用你的模块的代码或项目的更改。类似于主要的版本号,这只会影响你的公共API。

使用主要版本号中的相同示例,想象你有一个名为UserAddress的方法,该方法返回一个字符串:

1func UserAddress(username string) string {
2    // return user address as a string
3}

然而,这一次,而不是更新UserAddress返回*Address,您决定添加一个名为UserAddressDetail的新方法:

 1type Address struct {
 2    Address string
 3    PostalCode string
 4}
 5
 6func UserAddress(username string) string {
 7    // return user address as a string
 8}
 9
10func UserAddressDetail(username string) *Address {
11    // return user address and postal code struct
12}

添加这个新的UserAddressDetail功能不需要用户的更改,如果他们更新到这个版本的模块,所以它将被视为一个小版本的增加。

然而,公共API的更改可能不是你发布模块的新版本的唯一时间,错误是软件开发的不可避免的一部分,补丁版本号是为了掩盖这些漏洞。

Patch 版本号码

补丁版本号是SemVer版本("1.4.3")中最后一个编号. 补丁版本更改是指不影响 模块的** 公用API** 的任何更改. 不影响模块的公用API的更改,一般是诸如bug修正或安全修正等. 使用前例中的用户地址函数,假设函数返回的字符串中缺少您模块的一部分地址。 如果您发布了您模块的新版本来修复该错误,它只会增加补丁版本. 发布时不会包括用户如何使用"用户Address"公共API的任何修改,只有返回的数据的正确性.

正如您在本节中所看到的,仔细选择新版本号是赢得用户信任的重要方法。使用语义版本显示用户更新到新版本所需的工作量,您不会意外地惊喜他们更新破坏他们的程序。

发布新模块版本

在发布您的模块的新版本之前,您需要更新您的模块以对您计划进行的更改。如果没有任何更改,您将无法确定该语义版本的哪个部分要增加。

首先,打开pubmodule.go文件,并将新的再见方法添加到您的公共API:

 1[label pubmodule/pubmodule.go]
 2package pubmodule
 3
 4func Hello() string {
 5  return "Hello, You!"
 6}
 7
 8func Goodbye() string {
 9  return "Goodbye for now!"
10}

一旦您保存了更改,您将想要通过运行git status来检查哪些更改预计会发生:

1git status

输出将类似于此,显示您模块中唯一的变化是您添加到pubmodule.go 的方法:

 1[secondary_label Output]
 2On branch main
 3Your branch is up to date with 'origin/main'.
 4
 5Changes not staged for commit:
 6  (use "git add <file>..." to update what will be committed)
 7  (use "git restore <file>..." to discard changes in working directory)
 8    modified:   pubmodule.go
 9
10no changes added to commit (use "git add" and/or "git commit -a")

接下来,将更改添加到分阶段文件中,并将更改与git addgit commit联系到您的本地存储库:

1git add .
2git commit -m "Add Goodbye method"

结果将看起来像这样:

1[secondary_label Output]
2[main 3235010] Add Goodbye method
3 1 file changed, 4 insertions(+)

在完成修改后,你需要把它们推到你的GitHub寄存器. 在更大的软件项目中,或当与其他开发者合作进行一个项目时,这一步骤通常会略有不同. 当在新功能上进行开发时,开发者会创建一个Git分支来将更改放入,直到新功能稳定并准备发布. 一旦发生这种情况,另一开发商将审查分支的变化,以添加第二双眼睛,可能抓住第一个开发商可能错过的问题。 审评完成后,该分支机构将并入默认分支机构(如 " 主 " 或 " 主要 " )。 在发布之间,默认分支会累积这些类型的变化,直到发布新发布的时候.

由于您的模块不会通过此过程,所以将您所做的更改推向存储库将模拟更改的积累:

1git push

结果将看起来像这样:

1[secondary_label Output]
2numerating objects: 5, done.
3Counting objects: 100% (5/5), done.
4Delta compression using up to 8 threads
5Compressing objects: 100% (3/3), done.
6Writing objects: 100% (3/3), 369 bytes | 369.00 KiB/s, done.
7Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
8To github.com:your_github_username/pubmodule.git
9   931071d..3235010 main -> main

输出显示新代码为用户准备在默认分支中使用。

到目前为止,您所做的一切都与最初发布模块相同,但现在发布新版本的一个重要部分出现了:选择新版本号。

如果你看看您对模块所做的修改,对公共API(或实际上的任何修改)的唯一修改是在您的模块中添加"再见"方法. 由于用户可以从只有 " 你好 " 功能的上个版本进行更新,而无需作出修改,因此这一修改将是一种落后的、相容的改变。 在语义版本中,对公用API进行后相兼容的更改将意味着小版本号的增加. 不过,这是您模块的第一个版本正在发布,所以没有之前的版本可以增加. 如果认为"0.0.0"是"没有版本",那么将小版本加成"0.1.0",即你的模块的下一个版本.

现在,你有一个版本号可以给您的模块的发布,你可以使用它,与Git标签对齐,以发布一个新的版本. 当开发者使用Git来跟踪他们的源代码时,即使是在Go以外的语言中,一个常见的公约是使用Git的标记来跟踪为特定版本发布的代码. 这样,如果他们需要修改旧版本,就可以使用标记. 自兹 Go已经从源寄存器下载模块,它通过使用这些同版本标记来利用这个做法.

若要使用这些标签发布您自己的模块的新版本,您将用git tag命令标记您发布的代码。作为对git tag命令的论点,您还需要提供版本标签。 若要创建版本标签,请从v前缀开始,并在该命令之后立即添加您的 SemVer。 在您的模块的情况下,您的最终版本标签将是v0.1.0。 现在,运行git tag以标记您的模块。

1git tag v0.1.0

一旦本地添加了版本标签,您仍然需要将标签推到您的GitHub存储库,您可以使用git pushorigin来做到这一点:

1git push origin v0.1.0

git push命令成功后,你会看到一个新的标签,v0.1.0已经创建:

1[secondary_label Output]
2Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
3To github.com:your_github_username/pubmodule.git
4 * [new tag]         v0.1.0 -> v0.1.0

上面的输出显示你的标签已经被推移,你的GitHub存储库有一个新的v0.1.0标签可用于参照你的模块的用户。

现在您已经用"git tag"发布了您模块的新版本,每当用户运行"去得到"以获取您模块的最新版本时,它将不再下载基于默认分支的最新承诺散列的版本. 一旦一个模块有发布版本, " go " 工具将开始使用这些版本来确定更新模块的最佳方式。 Paired 有语义化的版本,这使得您可以在同时为您的用户提供一致而稳定的经验的同时,去去除并改进您的模块.

结论

在本教程中,您创建了一个公共的Go模块,并将其发布到GitHub存储库,以便其他人可以使用它。您还使用语义版本来确定模块的最佳版本号。

如果您想了解更多关于语义版本的信息,包括如何将数字以外的信息添加到您的版本中,则 Semantic Versioning 网站将深入了解。

有关 Go 模块的更多信息,Go 项目有 一系列博客帖子详细介绍 Go 工具如何与模块互动和理解。

本教程也是 DigitalOcean How to Code in Go系列的一部分,该系列涵盖了许多 Go 主题,从首次安装 Go 到如何使用语言本身。

Published At
Categories with 技术
Tagged with
comments powered by Disqus