learn-tech/专栏/Go语言项目开发实战/特别放送GoModules实战.md
2024-10-16 00:01:16 +08:00

403 lines
13 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

因收到Google相关通知网站将会择期关闭。相关通知内容
特别放送 Go Modules实战
你好,我是孔令飞。
今天我们更新一期特别放送作为加餐。在 特别放送 | Go Modules依赖包管理全讲中我介绍了Go Modules的知识里面内容比较多你可能还不知道具体怎么使用Go Modules来为你的项目管理Go依赖包。
这一讲我就通过一个具体的案例带你一步步学习Go Modules的常见用法以及操作方法具体包含以下内容
准备一个演示项目。
配置Go Modules。
初始化Go包为Go模块。
Go包依赖管理。
准备一个演示项目
为了演示Go Modules的用法我们首先需要一个Demo项目。假设我们有一个hello的项目里面有两个文件分别是hello.go和hello_test.go所在目录为/home/lk/workspace/golang/src/github.com/marmotedu/gopractise-demo/modules/hello。
hello.go文件内容为
package hello
func Hello() string {
return "Hello, world."
}
hello_test.go文件内容为
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)
}
}
这时候该目录包含了一个Go包但还不是Go模块因为没有go.mod件。接下来我就给你演示下如何将这个包变成一个Go模块并执行Go依赖包的管理操作。这些操作共有10个步骤下面我们来一步步看下。
配置Go Modules
打开Go Modules
确保Go版本>=go1.11并开启Go Modules可以通过设置环境变量export GO111MODULE=on开启。如果你觉得每次都设置比较繁琐可以将export GO111MODULE=on追加到文件$HOME/.bashrc中并执行 bash 命令加载到当前shell环境中。
设置环境变量
对于国内的开发者来说需要设置export GOPROXY=https://goproxy.cn,direct这样一些被墙的包可以通过国内的镜像源安装。如果我们有一些模块存放在私有仓库中也需要设置GOPRIVATE环境变量。
因为Go Modules会请求Go Checksum DatabaseChecksum Database国内也可能会访问失败可以设置export GOSUMDB=off来关闭Checksum校验。对于一些模块如果你希望不通过代理服务器或者不校验checksum也可以根据需要设置GONOPROXY和GONOSUMDB。
初始化Go包为Go模块
创建一个新模块
你可以通过go mod init命令初始化项目为Go Modules。 init 命令会在当前目录初始化并创建一个新的go.mod文件也代表着创建了一个以项目根目录为根的Go Modules。如果当前目录已经存在go.mod文件则会初始化失败。
在初始化Go Modules时需要告知go mod init要初始化的模块名可以指定模块名例如go mod init github.com/marmotedu/gopractise-demo/modules/hello。也可以不指定模块名让init自己推导。下面我来介绍下推导规则。
如果有导入路径注释,则使用注释作为模块名,比如:
package hello // import "github.com/marmotedu/gopractise-demo/modules/hello"
则模块名为github.com/marmotedu/gopractise-demo/modules/hello。
如果没有导入路径注释并且项目位于GOPATH路径下则模块名为绝对路径去掉$GOPATH/src后的路径名例如GOPATH=/home/lk/workspace/golang项目绝对路径为/home/colin/workspace/golang/src/github.com/marmotedu/gopractise-demo/modules/hello则模块名为github.com/marmotedu/gopractise-demo/modules/hello。
初始化完成之后会在当前目录生成一个go.mod文件
$ cat go.mod
module github.com/marmotedu/gopractise-demo/modules/hello
go 1.14
文件内容表明当前模块的导入路径为github.com/marmotedu/gopractise-demo/modules/hello使用的Go版本是go 1.14。
如果要新增子目录创建新的package则package的导入路径自动为 模块名/子目录名 github.com/marmotedu/gopractise-demo/modules/hello/<sub-package-name>不需要在子目录中再次执行go mod init。
比如我们在hello目录下又创建了一个world包world/world.go则world包的导入路径为github.com/marmotedu/gopractise-demo/modules/hello/world。
Go包依赖管理
增加一个依赖
Go Modules主要是用来对包依赖进行管理的所以这里我们来给hello包增加一个依赖rsc.io/quote
package hello
import "rsc.io/quote"
func Hello() string {
return quote.Hello()
}
运行go test
$ go test
go: finding module for package rsc.io/quote
go: downloading rsc.io/quote v1.5.2
go: found rsc.io/quote in rsc.io/quote v1.5.2
go: downloading rsc.io/sampler v1.3.0
PASS
ok github.com/google/addlicense/golang/src/github.com/marmotedu/gopractise-demo/modules/hello 0.003s
当go命令在解析源码时遇到需要导入一个模块的情况就会去go.mod文件中查询该模块的版本如果有指定版本就导入指定的版本。
如果没有查询到该模块go命令会自动根据模块的导入路径安装模块并将模块和其最新的版本写入go.mod文件中。在我们的示例中go test将模块rsc.io/quote解析为rsc.io/quote v1.5.2并且同时还下载了rsc.io/quote模块的两个依赖模块rsc.io/quote和rsc.io/sampler。只有直接依赖才会被记录到go.mod文件中。
查看go.mod文件
module github.com/marmotedu/gopractise-demo/modules/hello
go 1.14
require rsc.io/quote v1.5.2
再次执行go test
$ go test
PASS
ok github.com/marmotedu/gopractise-demo/modules/hello 0.003s
当我们再次执行go test时不会再下载并记录需要的模块因为go.mod目前是最新的并且需要的模块已经缓存到了本地的$GOPATH/pkg/mod目录下。可以看到在当前目录还新生成了一个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=
go test在执行时还可以添加-mod选项比如go test -mod=vendor。-mod有3个值我来分别介绍下。
readonly不更新go.mod任何可能会导致go.mod变更的操作都会失败。通常用来检查go.mod文件是否需要更新例如用在CI或者测试场景。
vendor从项目顶层目录下的vendor中导入包而不是从模块缓存中导入包需要确保vendor包完整准确。
mod从模块缓存中导入包即使项目根目录下有vendor目录。
如果go test执行时没有-mod选项并且项目根目录存在vendor目录go.mod中记录的go版本大于等于1.14此时go test执行效果等效于go test -mod=vendor。-mod标志同样适用于go build、go install、go run、go test、go list、go vet命令。
查看所有依赖模块
我们可以通过go list -m all命令查看所有依赖模块
$ go list -m all
github.com/marmotedu/gopractise-demo/modules/hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
可以看出除了rsc.io/quote v1.5.2外,还间接依赖了其他模块。
更新依赖
通过go list -m all我们可以看到模块依赖的golang.org/x/text模块版本是v0.0.0我们可以通过go get命令将其更新到最新版本并观察测试是否通过
$ go get golang.org/x/text
go: golang.org/x/text upgrade => v0.3.3
$ go test
PASS
ok github.com/marmotedu/gopractise-demo/modules/hello 0.003s
go test命令执行后输出PASS说明升级成功再次看下go list -m all和go.mod文件
$ go list -m all
github.com/marmotedu/gopractise-demo/modules/hello
golang.org/x/text v0.3.3
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
$ cat go.mod
module github.com/marmotedu/gopractise-demo/modules/hello
go 1.14
require (
golang.org/x/text v0.3.3 // indirect
rsc.io/quote v1.5.2
)
可以看到golang.org/x/text包被更新到最新的tag版本(v0.3.3)并且同时更新了go.mod文件。// indirect说明golang.org/x/text是间接依赖。现在我们再尝试更新rsc.io/sampler并测试
$ go get rsc.io/sampler
go: rsc.io/sampler upgrade => v1.99.99
go: downloading rsc.io/sampler v1.99.99
$ go test
--- FAIL: TestHello (0.00s)
hello_test.go:8: Hello() = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."
FAIL
exit status 1
FAIL github.com/marmotedu/gopractise-demo/modules/hello 0.004s
测试失败说明最新的版本v1.99.99与我们当前的模块不兼容我们可以列出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
# 我们尝试选择一个次新的版本v1.3.1
$ go get rsc.io/[email protected]
go: downloading rsc.io/sampler v1.3.1
$ go test
PASS
ok github.com/marmotedu/gopractise-demo/modules/hello 0.004s
可以看到更新到v1.3.1版本测试是通过的。go get还支持多种参数如下表所示
添加一个新的major版本依赖
我们尝试添加一个新的函数func Proverb该函数通过调用rsc.io/quote/v3的quote.Concurrency函数实现。
首先我们在hello.go文件中添加新函数
package hello
import (
"rsc.io/quote"
quoteV3 "rsc.io/quote/v3"
)
func Hello() string {
return quote.Hello()
}
func Proverb() string {
return quoteV3.Concurrency()
}
在hello_test.go中添加该函数的测试用例
func TestProverb(t *testing.T) {
want := "Concurrency is not parallelism."
if got := Proverb(); got != want {
t.Errorf("Proverb() = %q, want %q", got, want)
}
}
然后执行测试:
$ go test
go: finding module for package rsc.io/quote/v3
go: found rsc.io/quote/v3 in rsc.io/quote/v3 v3.1.0
PASS
ok github.com/marmotedu/gopractise-demo/modules/hello 0.003s
测试通过可以看到当前模块同时依赖了同一个模块的不同版本rsc.io/quote和rsc.io/quote/v3
$ go list -m rsc.io/q...
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
升级到不兼容的版本
在上一步中我们使用rsc.io/quote v1版本的Hello()函数。按照语义化版本规则如果我们想升级major版本可能面临接口不兼容的问题需要我们变更代码。我们来看下rsc.io/quote/v3的函数
$ go doc rsc.io/quote/v3
package quote // import "github.com/google/addlicense/golang/pkg/mod/rsc.io/quote/[email protected]"
Package quote collects pithy sayings.
func Concurrency() string
func GlassV3() string
func GoV3() string
func HelloV3() string
func OptV3() string
可以看到Hello()函数变成了HelloV3()这就需要我们变更代码做适配。因为我们都统一模块到一个版本了这时候就不需要再为了避免重名而重命名模块所以此时hello.go内容为
package hello
import (
"rsc.io/quote/v3"
)
func Hello() string {
return quote.HelloV3()
}
func Proverb() string {
return quote.Concurrency()
}
执行go test
$ go test
PASS
ok github.com/marmotedu/gopractise-demo/modules/hello 0.003s
可以看到测试成功。
删除不使用的依赖
在上一步中我们移除了rsc.io/quote包但是它仍然存在于go list -m all和go.mod中这时候我们要执行go mod tidy清理不再使用的依赖
$ go mod tidy
[colin@dev hello]$ cat go.mod
module github.com/marmotedu/gopractise-demo/modules/hello
go 1.14
require (
golang.org/x/text v0.3.3 // indirect
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1 // indirect
)
使用vendor
如果我们想把所有依赖都保存起来在Go命令执行时不再下载可以执行go mod vendor该命令会把当前项目的所有依赖都保存在项目根目录的vendor目录下也会创建vendor/modules.txt文件来记录包和模块的版本信息
$ go mod vendor
$ ls
go.mod go.sum hello.go hello_test.go vendor world
到这里我就讲完了Go依赖包管理常用的10个操作。
总结
这一讲中我详细介绍了如何使用Go Modules来管理依赖它包括以下Go Modules操作
打开Go Modules
设置环境变量;
创建一个新模块;
增加一个依赖;
查看所有依赖模块;
更新依赖;
添加一个新的major版本依赖
升级到不兼容的版本;
删除不使用的依赖。
使用vendor。
课后练习
思考下,如何更新项目的所有依赖到最新的版本?
思考下如果我们的编译机器访问不了外网如何通过Go Modules下载Go依赖包
欢迎你在留言区与我交流讨论,我们下一讲见。