Introduction to Go Modules
July 2, 2020
Creating a Module
先创建一个package,用于测试go mod的版本管理。
package的代码也很简单:
将package放入仓库:
现在就可以通过go get将package获取到本地使用了:
注意这里是从master分支获取最新的code,master分支通常处于开发中,特性不稳定,所以这种方式不推荐。
语义化版本
了解go module的版本机制之前,需要先对语义化版本有个大致了解。
版本格式
常规版本格式:主版本号.次版本号.修订号,版本号递增规则如下:
- 主版本号(major):当你做了不兼容的 API 修改。
- 次版本号(minor):当你做了向下兼容的功能性新增,可以理解为Feature版本。
- 修订号(patch):当你做了向下兼容的问题修正,可以理解为Bug fix版本。
先行版本号
在常规的版本号命名之上还有一个特殊类别,叫做预发版本号(prerelease version)。它表示当前版本是一个不稳定的版本,使用它时需要注意风险。
先行版本号的格式是 <major>.<minor>.<patch>-<stage>.<num>,即前半部分和常规版本号相同,然后跟上连接符 -,后面再跟上字母数字点号连接符。
常用的stage有:
- alpha:内测版,内部交流或者专业测试人员测试用;
- beta:公测版,专业爱好者大规模测试用,存在一些缺陷,该版本也不适合一般用户安装;
- Gamma:比较成熟的测试版,与即将发行的正式版相差无几;
- rc:是 Release Candidate的缩写,意思是发布倒计时,候选版本,处于Gamma阶段,该版本已经完成全部功能并清除大部分的BUG。到了这个阶段只会除BUG,不会对软件做任何大的更改。从Alpha到Beta再到Gamma是改进的先后关系,但RC1、RC2往往是取舍关系。
- lts:长期维护,long term support 的缩写。
- Stable:稳定版。在开源软件中,都有stable版,这个就是开源软件的稳定发行版。
如:1.0.0-alpha.1
版本发布准则
列举出比较实用的一些规则:
- 标准的版本号必须采用XYZ的格式,并且X、Y 和 Z 为非负的整数,禁止在数字前方补零,版本发布需要严格递增。例如:1.9.1 -> 1.10.0 -> 1.11.0。
- 某个软件版本发行后,任何修改都必须以新版本发行。
- 1.0.0 的版本号用于界定公共 API。当你的软件发布到了正式环境,或者有稳定的API时,就可以发布1.0.0版本了。
- 版本的优先层级指的是不同版本在排序时如何比较。判断优先层级时,必须把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较。
Quick Intro to Module Versioning
Making our first release
发布我们testmod的第一个版本:
同时也拉取一个分支,用作后续的bug fix:
Using our module
在另一个项目中使用前面创建的testmod package:
go module初始化和build之后,默认会生成两个本地文件:go.mod、go.sum
go.mod:
用于记录当前module的相关依赖,在build过程中会自动解析相关依赖,当发现import 的package不在go.mod文件中时,go会查找“最新的版本”,并写入到go.mod文件中。
那么该如何定义“最新”呢?
- 可以是最新的tagged稳定版(非prerelease版本)
- 或者是最新的tagged预发布版本
- 或者是最新的untagged版本
关于伪版本(pseudo-version):The go.mod file and the go command more generally use semantic versions as the standard form for describing module versions, so that versions can be compared to determine which should be considered earlier or later than another. A module version like v1.2.3 is introduced by tagging a revision in the underlying source repository. Untagged revisions can be referred to using a "pseudo-version" like v0.0.0-yyyymmddhhmmss-abcdefabcdef, where the time is the commit time in UTC and the final suffix is the prefix of the commit hash. The time portion ensures that two pseudo-versions can be compared to determine which happened later, the commit hash identifes the underlying commit, and the prefix (v0.0.0- in this example) is derived from the most recent tagged version in the commit graph before this commit.
在go.mod文件中可能看到部分间接依赖的module(尾部标识有// inderect),那是因为部分直接依赖的package自身没有包含go.mod文件,所以也将其引入到了当前项目的go.mod文件。
可以使用go list -m all列出所有依赖。也可以通过go mod why -m <module> 确认某个模块的依赖路径。
go.sum:
用于记录各个module的Hash值,避免后续下载的module被意外更新的情况。
Making a bugfix release
现在testmod v1.0.0的版本需要bug fix,还是在之前的v1分支上进行简单的bug修复,不涉及API的调整:
Updating modules
为了保证build的稳定,go默认不会更新相关依赖module。可以通过go get来更新:
- go get -u将更新到最新的次版本或者修订版本,比如从1.0.0更新到1.0.1,或者1.1.0 (次版本优先)。
- go get -u=patch更新到最新的修订版本,比如从1.0.0更新到1.0.1,而非1.1.0
- go get package@version更新到指定版本。
上面的例子中,我们通过以下几种方式都可以将testmod更新到v1.0.1。
更新完毕后可以看到go.mod中的内容也产生了变化,testmod的依赖版本改为了v1.0.1。
Major versions
语义化版本有提到,当你做了不兼容的 API 修改时,主版本号就需要升级了。从go module的角度看,同一个package的两个不同的主版本,可以认为是完全独立的两个package。使用时对应的import path也不一样。
现在修改testmod,且API与之前的版本不兼容:
由于API与之前的v1.X版本完全不兼容,我们需要需要一个新的import path。通常的做法是在现有path的尾部增加版本号:
发布一个v2的版本:
注意在go.mod中module的名字也做了变更。
Updating to a major version
尽管testmod发布了一个不兼容的v2版本,这不影响之前的版本使用,go get -u也不会将testmod升级到v.2版本。
如果希望使用testmod的新特性,需要主动指定import的版本:
也可以在同一份代码中引入两个不同的版本,这两个版本会被当做不同的package