女主宣言
最近项目需要开始正式的接触golang的项目,开源项目中使用了最新的以来管理go mod,就跟进了解了相关的一些内容,经过几年的尝试和改进, 现在的版本也是作者希望能够持续十年的设计,也期望能够学习到作者们的思想,提升自己对版本依赖的认识。
PS:丰富的一线技术、多元化的表现形式,尽在“360云计算”,点关注哦!
1
背景介绍
历史介绍
1.Makefile, goinstall和go get
最早是通过Makefile的形式进行编译,连接程序。后来的goinstall能够在无额外配置的情况拉取到相应的代码,此时只能使用在标准库中。go get解决了开发者之间的代码共享问题,能够通过git等进行代码共享。
2.Versioning和API Stability
通过import的路经中添加相应的版本信息达到了引用不同版的效果。
3.Vendoring和Reproducible Builds
由于go get没有版本相关的信息,所以不能提供一个可以可稳定重复的编译过程。vendor通过拷贝相应的代码到vendor目录下,依赖项不会变更除非手动修改其中的依赖项。
4.An Official Package Management Experiment
dep是官方的依赖管理实验项目,主要是用来探索最佳的实践,是找到最终的解决的重要的一步。
解决的问题
官方的定义
moduleis a collection of related Go packages that are versioned together as a single unit.
Modules record precise dependency requirements and create reproducible builds.
Most often, a version control repository contains exactly one moduledefinedin the repository root.
(Multiple modules are supported in a single repository, but typically that would result in more work
on an on-going basis than a single module per repository).
Summarizing the relationship between repositories, modules, and packages:
A repository contains one or more Go modules.
Eachmodule contains one or more Go packages.
Eachpackage consists of one or more Go source files in a single directory.
Modules must be semantically versioned according to semver, usually in the
form v(major).(minor).(patch), such as v0.1.0, v1.2.3, or v1.5.0-rc.1.
The leading v is required. IfusingGit, tag released commits with their versions.
Publicandprivatemodule repositories and proxies are becoming available (see FAQ below).
module是一组相关的package的集合,作为一个整体进行版本控制。module记录精确的依赖需求并创建可重复的构建。
一个repo可以包含一个或者多个module,一个module可以包含一个或者多个package,一个package是一个路经下包含一个或者多个文件。
module必须是经过语义版本semver的。
版本控制
如何引入一个向下不兼容的版本?
import的兼容规则如下
If an old package and a new package have the same import path,
the new package must be backwards compatible with the old package.
如果使用相同的import路经,则新版本必须是兼容就版本的。go是通过不兼容的版本使用不同的import路经来区分的,
例如 import path和import path/v2的方式
2.同一个主版本采用最低版本选择策略进行版本选择?
最小版本选择,即为选择所有的版本中(不同的主版本是不同的package)选择版本最大的一个版本,也是可用的最小版本。
2
设计目标
鼓励用户打标签,使得更加的可读,并且能够帮助使用者明确哪些是已经发布的,哪些是正在开发中的不依赖具体的版本控制既可以在一个repo中使用一个module,也可以一个repo下使用多个module,并且各自进行版本控制
能够添加自己的代理
删除之前的vendor目录
3
基本操作
创建Module
创建一个目录,新建hello.go文档
package hello
func Hello() string{
return"Hello, world."
}
添加相应的测试文档
package hello
import"testing"
func TestHello(t *testing.T) {
want := "Hello, world."
if got := Hello(); got != want {
t.Errorf("Hello() = %q, want %q", got, want)
}
}
执行测试,确认程序正常运行
注意需要关闭掉相应的module强制设置,确保GO111MODULE不为on,否则会得到 go: cannot find main module; see 'go help modules'
➜ hello go test
go test
PASS
ok _/home/zhangchao11/work/golang/hello 0.003s
➜ hello
初始化为module
go mod init example.com/hello
查看相应的go.mod
module example.com/hello
go 1.13
执行测试
$ go test
go: finding rsc.io/quote v1.5.2
go: downloading rsc.io/quote v1.5.2
go: extracting rsc.io/quote v1.5.2
go: finding rsc.io/sampler v1.3.0
go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
go: downloading rsc.io/sampler v1.3.0
go: extracting rsc.io/sampler v1.3.0
go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
PASS
ok example.com/hello 0.023s
$
说明: go命令能够自动从go.mod中导入指定的依赖项。如果在go.mod中没有找到特定的依赖项的情况
则会从对应的package中自动的查找相应的module,并且把结果写入到go.mod中。导入的module的版本默认选择最新的版本(latest),go.mod中默认自动添加的只有直接依赖的module,并不包含间接依赖的module。
查看新的go.mod
module example.com/hello
go 1.13
require rsc.io/quote v1.5.2
执行test
➜ hello go test
go test
PASS
ok example.com/hello 0.006s
➜ hello
通过上述结果可以看出,第二次执行go test的时候,由于go.mod已经是最新状态,并且需要的module已经缓存在本地了,就不再执行上述的获取,展开的过程了。
查看module的所有依赖
➜ hello go list -m all
go list -m all
example.com/hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
➜ hello
列出了module的所以依赖项,第一行列出的是当前module(也称为主module),依赖的module则按照module的路径进行排序列在下面的内容.
查看生成的go.sum
➜ hello cat go.sum
cat go.sum
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
➜ hello
go.sum中保存的是模块对应的哈希值,该值可以保证后续下载的版本和第一次下载的版本没有被非预期的修改。
升级依赖
升级golang.org/x/text
➜ hello go get golang.org/x/text
go get golang.org/x/text
重新检查依赖列表
➜ hello go list -m all
go list -m all
example.com/hello
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
➜ hello cat go.mod
cat go.mod
module example.com/hello
go 1.13
require (
golang.org/x/text v0.3.2// indirect
rsc.io/quote v1.5.2
)
➜ hello
从上面的结果中可以看出,golang.org/x/text从之前的版本已经升级到了v0.3.2的版本,更新之后的版本信息已经添加到了go.mod,后面添加了indirect表明该依赖项不是主module直接依赖的。
查询可用的所有依赖
➜ hello go list -m -versions rsc.io/sampler
go list -m -versions rsc.io/sampler
rsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99
➜ hello
更新到指定版本
➜ hello go get rsc.io/sampler@v1.3.1
go get rsc.io/sampler@v1.3.1
go: finding rsc.io/sampler v1.3.1
go: downloading rsc.io/sampler v1.3.1
go: extracting rsc.io/sampler v1.3.1
➜ hello
添加一个新的主版本
添加quoteV3版本
package hello
import(
"rsc.io/quote"
quoteV3 "rsc.io/quote/v3"
)
func Hello() string{
return quote.Hello()
}
func Proverb() string{
return quoteV3.Concurrency()
}
添加对应的测试代码
func TestProverb(t *testing.T) {
want := "Concurrency is not parallelism."
if got := Proverb(); got != want {
t.Errorf("Proverb() = %q, want %q", got, want)
}
}
执行测试
➜ hello go test
go test
go: finding rsc.io/quote/v3 v3.1.0
go: downloading rsc.io/quote/v3 v3.1.0
go: extracting rsc.io/quote/v3 v3.1.0
PASS
ok example.com/hello 0.006s
➜ hello
获取最新的依赖关系
➜ hello go list -m all
go list -m all
example.com/hello
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1
➜ hello
不同的主版本采用不同的导入路径,通过这种方式来提供不兼容的版本升级。v0,v1是直接使用module的路径就可以了,v2及以上的版本必须要添加相应的版本信息path/v2等方式。同一个主版本的不同子版本必须要保证向下的兼容性,也就是同一个主版本只会选择一个版本。这种方式保证了我们在使用新版本的特性的时候,又可以暂时不迁移依赖旧版本的代码。
删除不再使用的依赖
查看现有的依赖关系
➜ hello go list -m all
go list -m all
example.com/hello
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1
➜ hello cat go.mod
cat go.mod
module example.com/hello
go 1.13
require (
golang.org/x/text v0.3.2// indirect
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1// indirect
)
➜ hello
从上面的依赖列表中还是依然又v1.5.2,go build或者go test这样的命令,能够添加没有的以来项目但是不能够安全的删除掉相应的依赖。安全的删除一个依赖需要检查module里面的所有package是否依赖该module,而build,test命令都没有加载相应的信息,所以不能够安全的删除相应的module。
go mod tidy
➜ hello go mod tidy
go mod tidy
➜ hello go list -m all
go list -m all
example.com/hello
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1
➜ hello cat go.mod
cat go.mod
module example.com/hello
go 1.13
require (
golang.org/x/text v0.3.2// indirect
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1// indirect
)
➜ hello
通过这个命令能够删除掉不再需要的依赖module
问题
go list -m all?
按照什么排序策略进行排序
提交代码的时候一定要提交go.mod和go.sum不然会出现checksum的时候出错的情况
3
使用中遇到的问题
如何进行依赖库的代码开发?
可以用replace的方式,replace可以替换成本地的路经,来达到使用本地代码的作用。
replace github.com/brocaar/lorawan v0.0.0-20190814113539-8eb2a8d6da09=> path/lorawan
如何使用自由仓库进行依赖库的开发?
设置git的地址转化就可以达到预期的目的,是否可以通过proxy的形式来做到类似的效果,还没有具体研究,后面会去研究以下proxy相关的内容。
git config --global url."${UrlPath}lorawan.git".insteadOf "https://github.com/brocaar/lorawan"
go.mod和go.sum的提交?
提交相应的变更的时候,务必记得提交相应的go.sum,不然会出现后续的sum检查不通过。
360云计算
由360云平台团队打造的技术分享公众号,内容涉及数据库、大数据、微服务、容器、AIOps、IoT等众多技术领域,通过夯实的技术积累和丰富的一线实战经验,为你带来最有料的技术分享