Golang - Packages 包依赖管理之使用Go模块

内容涵盖Go基础package的概念、依赖包的简要发展史、vgo提案解决的问题、以及最后Go1.11的官方GoModule的介绍!

内容较多和杂,可以基于Toc查看感兴趣的地方,可以直接从第5部分看gomodule相关内容!

1. 包的概念

go run *.go
├── Main package is executed
├── All imported packages are initialized
|  ├── All imported packages are initialized (recursive definition)
|  ├── All global variables are initialized 
|  └── init functions are called in lexical file name order
└── Main package is initialized
   ├── All global variables are initialized
   └── init functions are called in lexical file name order

1.1. 基本概念

  1. 想象在开发一个程序,需要涉及字符串处理(大小写转换),你创建了一个case.go文件,包含了toUpperCase()toLowerCase()函数
  2. 程序继续开发,你发现有更多的string类似操作需要处理,所以你创建了一个string文件夹,里面包含了一系列类似case.go的文件
  3. 你拥有了一个string包,类似的还有很多,比如数学函数、加密、网络、文件处理等等,包的来源有官方、社区等来源
  4. 在go中有两类包,上面说的大都都是可以复用的实用工具包,用于解决重复轮子的;还有一类是main包,作为程序入口,生成可执行文件的;
  5. 没有main包的编译后为package archive,.a文件

1.2. 导入与导出

  • Uppercase开头的变量、函数、常量等被导出
  • import导入包,先从$GOROOT/src找,再从$GOPATH/src
  • 包支持嵌套,类似文件夹
  • 每个包只会初始化一次导入的包(如果包中有许多import语句,则导入的包将在主包执行的生命周期中仅初始化一次。)

1.3. 包初始化

包范围:在一个包中所有文件,可以访问包内内容,无需变量导出(同为main包,version可以被访问)

编译时候需要全部包含进来:go run src/app/version.go src/app/entry.go,但go installgo build只要包名字,故都会包含进来

1.3.1. 编译依赖

函数内:变量a依赖于另一个变量b时,应事先定义b,否则程序将无法编译。在函数内部遵循此规则。 包中:先后顺序可以优化,但不能存在变量相互嵌套赋值

1.3.2. init函数

Go包中可以定义多个init()初始化函数,当包被初始化时候执行,执行顺序基于他们出现的顺序(先按go的文件名排序,在内部定义的顺序排序执行),它们不能被显示调用!

init()函数主要解决在全局上下文中无法初始化的全局变量(例如数组循环for赋值,无法在全局定义),init()执行后,main()函数开始执行。

1.3.3. 包别名

  1. 当出现包重名时候,可以基于包别名处理,通过newname package_name
  2. 当引入一个包,又不调用其中的导出内容时候,通过_ package_name解决(下划线代表null)
  3. 导入(.)到本地文件范围中,通过import . "greet/greet",则包中内容均可以在当前文件内访问(不推荐,容易引起问题)

1.4. 包相关命令

  • go run: 编译+执行程序
  • go install : 编译包,创建二进制文件,以及包的存档文件(避免重复编译)
    • 某些IDE编译器,在第三方包下载的时候,就已经预编译好生成包的存档文件(.a),一旦你修改了包的一些内容,IDE也会做同步预编译
  • go get -u github.com/jinzhu/gorm: 安装最新的第三方包
package main
import "github.com/jinzhu/gorm"

1.5. 发布自己的包

如果你创建了一个包并希望人们使用它,只需在GitHub上发布它就可以了。 如果您的包是可执行的,那么人们可以将它用作命令行工具,否则他们可以将其导入程序并将其用作实用程序模块。

$ go get github.com/your-username/repo-name

2. 包管理和构建发展

Go最初版本是没有包管理的规划,后续就出现了各种包管理的方案(godep、glide、govendor、dep),最后Go团队收集了相关提案(vgo),直至今天的go mod,逐步趋于统一和稳定!

解决了2018 Gopher大会上提到的Go三大问题:范型、错误处理、包管理(逐步完善了),后续Go会成长得更好!Cool!

2.1. 发展史

简要发展史

年份 事项
2009年11月 Go的初始版本是编译器,链接器和一些库,编写一个合适的makefile
2010年2月 goinstall命令,支持基于从Github源下载软件包
2011年12月 引入go命令,加入了go get,缺少任何版本控制的概念,将版本问题交给代码仓库管理,同时带来的问题(a. 版本控制API的稳定性1、b. 采用vendor重复构建)
2013年9月 godep,用于冻结软件包依赖性的新工具,在不修改源文件的情况下将依赖项复制到项目中
2014年10月 glide 出现
2015年4月 gb, 允许通过源代码进行可重复构建,代码不一定需要存储在GOPATH中
2015年6月 Go团队暂定接收Go1.5的Vendor提议,在Go1.6中使用;引入vendor,允许程序分析工具go vet更好地使用vendoring来理解项目,问题(虽然解决了可重建,但无助于理解包版本并决定使用哪个版本的包)
2016年 GopherCon 对Go包管理进行了广泛的讨论,其中一个成果是组建了一个包管理工作委员会和咨询小组,目标是为Go包管理创建一个新工具;该工具的愿景是统一和替换现有工具,但它仍然可以使用供应商目录在直接工具链之外实现;实施为dep
2017年7月的GopherCon演讲 dep 探索 : Go Package管理新时代,原型是我们称之为的独立命令vgo

相关解决方案

  • godep
  • gopkg.in
  • govendor
  • gb

被许诺的为vendor/目录,作为项目包版本/模块的的copy。

2.2. 2018年

  1. 尽可能顺利地以任何形式完成从命令集成dep到 go命令集成的最终过渡。
  2. godep和glide已经结束了积极的发展,鼓励迁移到dep
  3. 开始试验vgo并不意味着结束支持dep
  4. 2018年8月发布的1.11版,对模块进行了初步支持。
  5. go在GOPATH/src之外的目录树中运行时,该命令默认为模块模式,并go.mod在其根目录中标记文件。

2.2.1. go命令添加版本控制的提议

  1. 首先,采用Go FAQgopkg.in提出的导入兼容性规则;
  2. 其次,使用一种简单的新算法(称为最小版本选择)来选择在给定构建中使用哪些包版本。
  3. 再次,介绍Go 模块的概念,这是一组版本化为单个单元的软件包,它们声明了它们的依赖项必须满足的最低要求。
  4. 最后,定义如何将所有这些改造成现有的go命令,以便基本工作流程从今天起不会发生显着变化。

2.2.2. 提案部分解释

  1. 期望作者遵循导入兼容性规则,这样我们就可以避免尝试驯服不兼容性,从而使整个系统呈指数级更简单,并且包装生态系统更少碎片化。
  2. 默认情况下,最小版本选择可提供可重现的构建,而无需锁定文件。

2.2.3. 提案中针对Go Module相关约定

  • Go 模块是共享公共导入路径前缀的包的集合,称为模块路径;
  • Go 模块是版本控制的单元,模块版本是作为语义版本字符串编写的(可以理解成git中,特定格式的tag标签)
  • Go 模块在一个名为的新文件中定义了go.mod它所依赖的其他模块的最低版本要求
  • Go 模块始终是通过HTTP提供的zip存档,作为zip存档的模块的统一表示使得模块下载代理的简单协议和实现成为可能。
  • 采用了vgo后,vendor 目录可以移除掉了
  • 强调GOPATH作为Go代码处理的必要场所
// 简单的go.mod文件:

module "rsc.io/hello"

require (
    "golang.org/x/text" v0.0.0-20180208041248-4e4a3210bb54 // 不规范,考虑语义版本控制的标签
    "rsc.io/quote" v1.5.2
)

2.3. vgo介绍

2018年初,Go团队提出了他们自己的工具称为vgo,现在称为go module

Go模块集成到Go工具中,模块的的概念被授予一等公民资格。这使得Go开发人员可以在任何地方构建他们的代码 他们想要的东西。

Go模块不需要**vendor/**目录,如果使用了模块,则不再需要使用GOPATH来保存所有Go源代码。

2.3.1. vgo规划2

go命令需要能够准确地告诉开发人员特定构建中哪些软件包的版本,反之亦然。

  1. 版本控制,将允许我们启用可重现的构建,包括不同开发可以构建完全相同的二进制文件;
  2. 版本锁定,确保即使有我的依赖项的较新版本,go命令也不应该自动更新,除非开发指定;
  3. 满足简单、兼容要求,保证Go语言原有的的简单性,速度和可理解性;

简而言之,

  1. 添加包版本控制,但需要在不破坏的情况下完成它go get,提案选择加入功能集成到Go 1.11中。
  2. 提议保留了最佳部分go get(自动感知get),添加了可重现的构建。
  3. 采用语义版本控制消除了vendor弃用了GOPATH以支持基于项目的工作流。
  4. 提供了从其dep及其前身的平滑迁移。

2.3.2. 安装vgo

// 安装
$ go get -u golang.org/x/vgo

2.3.3. vgo构建和查看

// 新建hello.go,加入依赖模块
// 新建go.mod文件,执行build下载依赖包
$ vgo build 
// 查看完整的模块集合list
$ vgo list -m
// 执行hello命令
$ LANG = fr ./hello 
Bonjour le monde。

2.3.4. 更新软件包/模块版本

// 先检查更新的软件包
$ vgo list -m -u
// 查找并更新给定模块的最新版本
$ vgo get golang.org/x/text 
// 更新所有模块
$ vgo get -u
// 升级后,需要再次检查应用程序测试是否有效
$ vgo test all
// 测试局部包
$ vgo test rsc.io/quote/...
// 最后,在执行构建
$ vgo build
// 与go get -d 有区别
$ go get -d rsc.io/hello
$ go build -o badhello rsc.io/hello

2.3.5. 降级软件包/模块版本

// 查看tags
$ vgo list -t rsc.io/sampler
// 查看当前程序中模块的版本
$ vgo list -m 或者
$ cat go.mod
// 降级到指定版本
$ vgo get rsc.io/sampler@v1.3.1
// 查看更改后的版本信息
$ vgo list -m
// 不要忘记测试一把
$ vgo test all

2.3.6. 移除软件包/模块依赖

将版本指定为none

$ vgo get rsc.io/sampler@none

2.3.7. 排除指定模块

当某个包模块的指定版本存在问题时候,可以通过排查该版本的包。

同时注意,排除仅适用于当前模块的构建。如果更大的构建需要当前模块,则不适用排除。

// 排除的包版本,追加进go.mod文件中
$ echo 'exclude rsc.io/sampler v1.99.99' >>go.mod
// 再次查看,发现已被排除
$ vgo get -u
$ vgo list -m
$ vgo test all
$ vgo build

2.3.8. 模块替换

试想get了一个模块,发现了模块有问题或者想尝试在该模块上面做一些变更:

  1. 方式1:联系原来的模块负责人,更新想法,然后tag,然后你重新vgo get -u模块新的版本
  2. 方式2:基于模块替换

模块替换,尝试更改github.com/rsc/quote模块内容:

2.3.9. 本地修改

// clone下来,并编辑加入自己的代码
$ git clone https://github.com/rsc/quote ../quote
$ cd ../quote
$ <edit quote.go>
// 回到应用,在go.mod中加入模块替换
$ cd ../hello
$ echo 'replace rsc.io/quote v1.5.2 => ../quote' >>go.mod
$ vgo list -m
MODULE                VERSION
 => ../quote
rsc.io/sampler        v1.3.1

2.3.10. 尝试fork+tag再次push到自己的仓库做替换

// fork+edit+commit+push
$ cd ../quote
$ git commit -a -m 'my fork'
$ git tag v0.0.0-myfork
$ git push https://github.com/you/quote v0.0.0-myfork

// 替换模块版本到go.mod中
$ cd ../hello
$ echo 'replace rsc.io/quote v1.5.2 => github.com/you/quote v0.0.0-myfork' >>go.mod
$ vgo list -m

// 再次以自己的fork版本构建
$ vgo build

2.3.11. vendor后项兼容

即使您想为项目使用vgo,也可能不希望要求所有用户都拥有vgo。相反,您可以创建一个供应商目录,允许go命令用户生成几乎相同的构建(当然,在GOPATH内构建):

$ vgo vendor
$ mkdir -p $GOPATH/src/github.com/you
$ cp -a . $GOPATH/src/github.com/you/hello
$ go build -o vhello github.com/you/hello
$ LANG=es ./vhello

3. 欢呼,GO模块到来

2018年8月发布的1.11版,对模块进行了初步支持,Go团队计划从Go1.13开始,模块模式将是所有开发的默认模式。

3.1. 2019模块规划

参见:https://blog.golang.org/modules2019

3.1.1. 模块版本支持情况

2018年8月发布的1.11版,对模块进行了初步支持,目前,模块支持与传统的基于GOPATH的机制一起维护。

go在GOPATH/src之外的目录树中运行时,该命令默认为模块模式,并go.mod在其根目录中标记文件。可以通过将过渡环境变量设置$GO111MODULE为on或来覆盖此设置off; 默认行为是auto模式。

目标是定于2019年8月的Go 1.13默认启用模块模式(即,将默认值更改auto为on)并弃用GOPATH模式。为此,我们一直致力于提供更好的工具支持以及对开源模块生态系统的更好支持。

3.1.2. 模块查询

正在开发一项新服务Go Module Index,它将提供进入Go生态系统的软件包的公共日志。像godoc.org和goreportcard.com这样的网站将能够查看此日志以获取新条目,而不是每个独立实现代码以查找新软件包。

3.1.3. 模块认证

  1. go get依靠连接级别身份验证(HTTPS或SSH)来检查它是否正在与正确的服务器通信以下载代码。(存在中间人攻击可能)
  2. Go模块设计通过go.sum在每个模块中存储文件来改进代码验证; 该文件列出了每个模块依赖项的预期文件树的加密哈希值。(Go团队打算运行一个服务,做模块索引的公证人)

3.1.4. 模块镜像

Go团队目标是go在Go 1.13开始的命令中默认使用Google运行的模块镜像。

3.1.5. 模块发现

2019年的部分工作将是godoc.org的重大改进,使其对需要发现可用模块然后决定是否依赖给定模块的开发人员更有用。

3.1.6. 附带一张模块规划图

4. GO1.11之后模块管控

Go模块是Go的内置依赖版本控制和依赖管理功能。模块是相关Go包的集合。go命令直接支持使用模块,包括记录和解析对其他模块的依赖性。模块替换旧的基于GOPATH的方法来指定在给定构建中使用哪些源文件。

4.1. GOPATH和Module

目前的GO1.11~1.13之间,在GOPATH/src下是禁用了模块使用的,因此需要在该路径外使用模块。

使用模块时,GOPATH不再用于解析导入。但是,它仍然用于存储下载的源代码(在GOPATH/pkg/ mod中)和编译的命令(在GOPATH/bin中)。

4.2. Vendor路径

Go 1.6包括支持使用外部依赖项的本地副本来满足这些依赖项的导入,通常称为vendoring。

4.3. Module Proxy协议

默认情况下,go命令直接从版本控制系统下载模块,就像go get一样。

GOPROXY环境变量允许进一步控制下载源,如果未设置GOPROXY,是空字符串,或者是字符串“direct”,则下载使用默认的直接连接到版本控制系统;若将GOPROXY设置为“off”不允许从任何来源下载模块。

4.4. go.mod和go.sum

  • go.mod: 在根目录定义的模块版本信息
    • module : 定义模块路径
    • go: 设置期待go的版本
    • require: 需要>=给定的版本
    • exclude: 排查指定版本
    • replace: 替代指定版本的模块
  • go.sum: 针对下载的模块内容签名
// 示例1
module github.com/tkstorm/gtmod
go 1.12
// 示例2
module my/thing
go 1.12
require other/thing v1.0.2
require new/thing/v2 v2.3.4
exclude old/thing v1.2.3
replace bad/thing v1.4.5 => good/thing v1.4.5

go.mod内容可以通过go mod edit或者直接编辑;更多内容可以自行参考:go help go.mod

4.5. 环境变量 - GO111MODULE

GO111MODULE支持off, on, or auto(默认)

  • off: go命令不会使用新模块支持,继续查询vendor目录和GOPATH寻找依赖
  • on: go命令使用模块,不会考虑GOPATH,该模式称之为module-aware mode(该模式下GOPATH没有import导入的特殊含义,但仍然保留下载依赖到GOPATH/pkg/mod,并安装命令到GOPATH/bin
  • auto或者变量unset: 基于当前目录解析(仅当在GOPATH/src目录外,且包含有go.mod文件,开启模块支持

4.6. 模块操作内容

  • 创建一个新模块。
  • 添加依赖项。
  • 升级依赖项。
  • 添加新主要版本的依赖项。
  • 将依赖项升级到新的主要版本。
  • 删除未使用的依赖项。

4.7. 模块操作命令

Go模块是Go中依赖管理的未来。现在,所有支持的Go版本(即Go 1.11以后)都提供了模块功能。

  • go mod init :创建一个新模块,初始化描述它的go.mod文件。
  • go build,go test和其他包构建​​命令 :根据需要向go.mod添加新的依赖项。
  • go list -m all :打印当前模块的依赖项。
  • go get :更改所需的依赖项版本(或添加新的依赖项)。
  • go mod tidy :删除未使用的依赖项。
Usage:
go mod <command> [arguments]
The commands are:
download    // 將依賴全部下載到本機中,位置為 $GOPATH/pkg/mod/cache
edit        // 編輯 go.mod 例如鎖定某個依賴的版本
graph       // 列出專案中哪一個部分使用了某個依賴
init        // 建立 go.mod
tidy        // 增加遺失的依賴,移除未使用的依賴
vendor      // 將既有的 go.mod 依賴全部存在 /vendor 底下
verify      // 驗證本地依賴依然符合 go.sum 
why         // 解釋某個依賴為何存在在 go.mod 中,誰使用了它

5. 具体操作实践

  1. 先确保go version版本在1.11以上,且在GOPATH/src以外的地方创建模块文件夹操作!!(在GOPATH之外,GO111MODULE为激活模块模式)

5.1. 初始化一个新的gtmod模块

$ mkdir -p /data/go/gtmod
$ cd /data/go/gtmodls
// 初始化新的模块
$ go mod init github.com/tkstorm/gtmod
go: creating new go.mod: module github.com/tkstorm/gtmod
$ ls
go.mod

5.2. 为gtmod新模块新增功能

Golang中的配置,开启模块支持:

5.3. 模块功能编写

这里创建了两个子包,一个电话号码生成,一个单词翻转(参考:https://github.com/tkstorm/gtmod/)

$ tree
.
├── example.go
├── go.mod
├── phone
│   └── generate.go
└── word
    ├── word.go
    └── word_test.go

模块简单示example试了下,运行测试OK:

!gnaloG欢喜我 ,woW
18298498081
18427131847
18939984059
18911902081
18474941318

5.4. 模块提交

// 完成后,打上tag提交到版本仓库
$ git tag -l
v0.0.1
v0.1.0
v1.0.0

5.5. 在新的项目中引入我们提交的gtmod模块

我们在/data/go/awsomepj目录新建一个项目!

新建一个main.go文件,把之前的example.go中内容,加入进来!(同时也注意Golang的模块配置开启)

// /data/go/awsomepj/main.go
package main

import (
    "fmt"
    "github.com/tkstorm/gtmod/phone"
    "github.com/tkstorm/gtmod/word"
)

func main() {
    fmt.Println(word.Say("Wow, 我喜欢Golang!"))

    for i := 0; i < 5; i++ {
        fmt.Println(phone.Generate())
    }
}

5.6. 初始化awsomepj模块

$ go mod init github.com/tkstorm/awsomepj
go: creating new go.mod: module github.com/tkstorm/awsomepj
// 看一眼go.mod
$ cat go.mod
module github.com/tkstorm/awsomepj
go 1.12

5.7. 执行go run命令

当执行go run命令,我们发现我们的程序将依赖的模块找到,并下载对应的模块,然后通过编译,最后执行!

$ go run main.go
go: finding github.com/tkstorm/gtmod/word latest
go: finding github.com/tkstorm/gtmod/phone latest
go: finding github.com/tkstorm/gtmod v1.0.0
go: downloading github.com/tkstorm/gtmod v1.0.0
go: extracting github.com/tkstorm/gtmod v1.0.0
!gnaloG欢喜我 ,woW
18298498081
18427131847
18939984059
18911902081
18474941318
// 再看一眼go.mod,多了一行requrire
$ cat go.mod
module github.com/tkstorm/awsomepj
go 1.12
require github.com/tkstorm/gtmod v1.0.0 // indirect

5.7.1. 几点注意

  1. 当go工具链中,任何go相关命令(go build、go test、go get、go list等),找到任何不熟悉的import模块时候, 都会查找包含该导入的模块,并自动将该模块的最新版本添加到go.mod!
  2. 不再需要并且可以删除模块,可以通过相关命令go mod tidy清除!

5.8. 查看主模块以和构建清单

go list -m              # print path of main module
go list -m -f={{.Dir}}  # print root directory of main module
go list -m all          # print build list

5.9. 假设gtmod中有一个BUG

  1. 我们修复gtmod模块的BUG(一个rand没有添加随机种子的BUG),更新模块版本v1.0.1 -> v1.0.1
  2. 我们在awsomepj项目中操作,如果此时我们执行go install、build构建,因为版本被锁定,故不会安装最新的v1.0.1的修订

5.9.1. 查看当前的版本

$ cd /data/go/awsomepj
$ go list -m all
github.com/tkstorm/awsomepj
github.com/tkstorm/gtmod v1.0.0

5.9.2. go get查看包版本,发现有新的版本

$ go get github.com/tkstorm/gtmod
go: finding github.com/tkstorm/gtmod v1.0.1
go: downloading github.com/tkstorm/gtmod v1.0.1
go: extracting github.com/tkstorm/gtmod v1.0.1

5.9.3. 模块感知go get

‘go get'命令根据go命令是在模块感知模式还是传统GOPATH模式下运行来改变行为。

  1. get解决要添加的依赖项
    • 默认get最新版本
    • @version后缀来覆盖此默认版本选择,其他类似的@latest@maste@none
    • -u,可用时使用较新的次要或补丁版本(主版本.次要版本.修订版本
    • -u=patch,更新到最新的修订版本
  2. 下载(如果需要),构建和安装命名包
    • -d,下载包源码,以及其相关依赖,但不构建安装他们
  3. 没有包参数,go get仅应用于主模块,将更新主模块的所有依赖项
    • go get不仅仅是go install,前者多了下载行为
    • go get -d不仅仅是go list,前者多了下载行为

5.9.4. go get -u 更新gtmod,版本升级成功

$ go get -u github.com/tkstorm/gtmod
$ go list -m all
github.com/tkstorm/awsomepj
github.com/tkstorm/gtmod v1.0.1
// 升级整个应用环境的包
$ go get -u 

5.9.5. 移除一个模块或者清理不相关的模块

// 移除一个模块
$ go get github.com/tkstorm/gtmod@none
// 清理不相关的(没有依赖的)模块
$ go mod tidy

5.9.6. 强推覆盖标签,导致拉取不了最新代码问题

如果我们提交tag之前,tag被使用用过,然后通过git push --force强制覆盖的话会出现go get -u无法更新到最新版本(因为go tool会基于之前下载的模块文件缓存安装,即使移除模块也无用,需要删除cache中的内容,从新从版本仓库中拉取最新代码!)。

移除缓存文件,然后在安装,发现和之前版本的checksum不匹配!

$ go clean -modcache
$ go install                     
go: downloading github.com/tkstorm/gtmod v1.0.1
verifying github.com/tkstorm/gtmod@v1.0.1: checksum mismatch
        downloaded: h1:omGc/zbIe35JFMwE1Hyy6AFm74ggjT4Virq5OtqXLy0=
        go.sum:     h1:5YK+2AgzP4UDsU/DOIY0CMxYYG+a0b9xSbdmaeQS7LM=
// 删除go.sum,再更新就OK了!
$ go install   
go: downloading github.com/tkstorm/gtmod v1.0.1
go: extracting github.com/tkstorm/gtmod v1.0.1
// 通过执行发现代码已fix了!

6. 总结

GO的模块从一开始没有相关规划(实际上是当时没有明确的目标),到逐步各类依赖包管理工具出现,万象重生,没有统一标准,最终逐步走向标准化的过程(这块也足见一门技术标准的重要性)。

GO的模块主要解决包依赖以及模块版本安全升级的问题,在Go1.11之前,经历了dep、vendor、vgo等多套包依赖解决方案方案,从18年Go1.11以后推出了官方的Module版本,目前支持与vendor目录方式兼容,在Go1.13版本将默认启用,所以开始拥抱变化吧!

7. 参考

  • Go Blog using-go-modules: https://blog.golang.org/using-go-modules
  • Go & 版本信息:https://research.swtch.com/vgo
  • Go包相关:https://medium.com/rungo/everything-you-need-to-know-about-packages-in-go-b8bac62b74cc
  • Go Module计划:https://github.com/golang/proposal/blob/master/design/24301-versioned-go.md
  • 语义版本管控: https://semver.org/
  • GO CMD说明:https://golang.org/cmd/go

  1. 版本感知的GitHub重定向器:https://gopkg.in ↩︎

  2. vgo介绍:https://research.swtch.com/vgo-intro ↩︎