最近项目需要开始正式的接触 golang 的项目,开源项目中使用了最新的以来管理 go mod,就跟进了解了相关的一些内容,经过几年的尝试和改进, 现在的版本也是作者希望能够持续十年的设计,也期望能够学习到作者们的思想,提升自己对版本依赖的认识。
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
1.创建⼀个⽬录,新建 hello.go⽂档
package hello
func Hello() string{
return"Hello, world."
}
复制代码
1.添加相应的测试⽂档
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
复制代码
添加一个新的主版本
1.添加 quoteV3 版本
package hello
import(
"rsc.io/quote"
quoteV3 "rsc.io/quote/v3"
)
func Hello() string{
return quote.Hello()
}
func Proverb() string{
return quoteV3.Concurrency()
}
1. 添加对应的测试代码
func TestProverb(t *testing.T) {
want := "Concurrency is not parallelism."
if got := Proverb(); got != want {
t.Errorf("Proverb() = %q, want %q", got, want)
}
}
复制代码
1.执⾏测试
➜ 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
复制代码
1.获取最新的依赖关系
➜ 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 云计算(ID:hulktalk)。
原文链接:
https://mp.weixin.qq.com/s/c4I1o_OMxNUwBeQQH_piOA
评论