内容简介:Go 1.11 和 1.12 版本初步支持了模块(modules)。它是 Go 中新的依赖管理系统,能更简单的显示、管理依赖的版本。本文介绍将一个Go 1.11 中,go 会在下面情况下使用
- 原文标题:Using Go Modules
- 原文作者:Tyler Bui-Palsulich and Eno Compton
- 原文时间:2019-03-19
Go 1.11 和 1.12 版本初步支持了模块(modules)。它是 Go 中新的依赖管理系统,能更简单的显示、管理依赖的版本。本文介绍将 模块
使用入门所需要的一些基本操作。后续将有文章来涵盖跟多 模块
的使用。
一个 模块
就是一系列 Go packages
的集合,其存储在一个根目录中包含 go.mod
文件的文件夹中。 go.mod
文件定义了模块的 模块路径
,这也是导入路径中的根路径,还定义了能成功构建本模块所需的依赖模块。每一项依赖表示为一个模块路径和一个特殊的语义化的版本号。
Go 1.11 中,go 会在下面情况下使用 模块
:当前目录或任何上级目录中有 go.mod
文件,且在 $GOPATH/src
目录之外(为了兼容,在 $GOPATH/src
目录中依然使用老的 GOPATH 模式,即使存在 go.mod
,详情见go 命令文档)。从 Go 1.13 开始, 模块模式
将成为默认的依赖管理系统。
本文介绍使用 模块
来开发 Go 项目时一些常用操作:
- 新建模块
- 添加依赖
- 更新依赖
- 添加新的 major 版本的依赖
- 更新依赖到新的 major 版本
- 移除未使用的依赖
新建模块
让我们来新建一个模块。
在 $GOPATH/src
目录外的任意目录中,新建一个空的文件夹。cd 进入该文件夹,新建一个源文件, 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) } } 复制代码
现在,这个目录下包含一个 包(package)
,而不是一个 模块(module)
,因为没有 go.mod
文件。如果该目录为 /home/gopher/hello,运行 go test
,我们将看到:
$ go test PASS ok _/home/gopher/hello 0.020s $ 复制代码
最后一行列出了所有被测试的包。因为我们的目录在 $GOPATH
之外,也不属于任何模块,go 知道对于当前目录没有导入路径,所以 go 创建了一个基于当前文件路径的假模块:_/home/gopher/hello。
使用 go mod init
来把当前目录变成一个模块的根路径,然后再试一次 go test
:
$ go mod init example.com/hello go: creating new go.mod: module example.com/hello $ go test PASS ok example.com/hello 0.020s $ 复制代码
恭喜!你已经完成并测试了你的第一个 模块
。
go mod init
命令将以下内容写入 go.mod
文件中:
$ cat go.mod module example.com/hello go 1.12 $ 复制代码
go.mod
文件仅出现在模块的根路径中。子目录下的包的导入路径为模块路径加上子目录路径。例如,我们新建一个 world
子目录,我们不需要在 world
中运行(也不想) go mod init
。这个包将自动成为模块 example.com/hello
的一部分,导入路径为 example.com/hello/world
。
添加依赖
Go 模块
的首要目标是提升使用别人写的代码(也是添加依赖)的体验。
让我们更新 hello.go
,导入 rsc.io/quote
依赖并使用它来实现 Hello:
package hello import "rsc.io/quote" func Hello() string { return quote.Hello() } 复制代码
现在,让我们再次运行测试:
$ 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
中任何一个模块中的导入包时,它会自动寻找包含该缺失包的模块,并把这个模块的最新版本( 最新版本
的定义为最新的一个被标记为 稳定(stable)
的版本,如果没有则为最新的 预览(prerelease)
版本,如果还是没有,则为最新的没有标记的版本)加入 go.mod
中。在我们的例子中,go test 解析新的 rsc.io/quote
导入为模块 rsc.io/quote v1.5.2
。它同时也下载了 rsc.io/quote
所需要的两个依赖: rsc.io/sampler
和 golang.org/x/test
。只有直接依赖会出现在 go.mod
文件中:
$ cat go.mod module example.com/hello go 1.12 require rsc.io/quote v1.5.2 $ 复制代码
再次运行 go test 不会重复以上的工作,因为 go.mod
已经被更新,而且能下载的模块也缓存到了本地(在 $GOPATH/pgk/mod
中):
$ go test PASS ok example.com/hello 0.020s $ 复制代码
注意,虽然go 命令能轻松快速的添加一个新依赖,但这并非没有代价。你的模块现在依赖新的模块,需要注意很多特殊的点,例如:正确性、安全性以及合适的许可证,以上仅仅是几个例子。更多思考,见 Russ Cox 的这篇文章, Our Software Dependency Problem 。
就如我们上面看到的,添加一个直接依赖往往会带来其他的非直接依赖。命令 go list -m
会列出当前模块和所有的依赖模块:
$ 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 $ 复制代码
go list
输出中第一行永远是当前模块,也叫 主模块
。接下来是按模块路径 排序 的依赖模块。
上面 golang.org/x/text version v0.0.0-20170915032832-14c0d48ead0c
是一个 pseudo-version
的例子,这是 go 对于没有标签的版本语法。
还有一点关于 go.mod
,就是 go 会维护一个名为 go.sum
文件,该文件包含目标依赖特定版本的哈希值:
$ cat go.sum golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZO... golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:Nq... rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3... rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPX... rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/Q... rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9... $ 复制代码
go 使用这个 go.sum
来确保将来下载的依赖版本和第一次下载的是一模一样的,以确保你项目中依赖的模块没有改变,无论是恶意的或意外的还是其他原因导致的改变。 go.mod
和 go.sum
都应该加入到版本控制中。
更新依赖
Go 模块中,版本号是语义化的。语义化版本号有三个部分:major、minor、patch。例如,版本号 v0.1.2
中,major 版本是 0,minor 版本是 1,patch 版本是 2。让我们试一试 minor 版本更新。下一节,进行 major 版本更新。
从 go list -m all
的输出中,我们可以看到,我们使用的是没有版本号的 golang.org/x/test
。让我们来升级到最新的版本,看看是否还能正常工作:
$ go get golang.org/x/text go: finding golang.org/x/text v0.3.0 go: downloading golang.org/x/text v0.3.0 go: extracting golang.org/x/text v0.3.0 $ go test PASS ok example.com/hello 0.013s $ 复制代码
哇,所有都通过了。让我们看看 go list -m all
和 go.mod
文件:
$ go list -m all example.com/hello golang.org/x/text v0.3.0 rsc.io/quote v1.5.2 rsc.io/sampler v1.3.0 $ cat go.mod module example.com/hello go 1.12 require ( golang.org/x/text v0.3.0 // indirect rsc.io/quote v1.5.2 ) $ 复制代码
golang.org/x/test
包已经更新到最新的带版本号的版本(v0.3.0)。 go.mod
中也更新到了 v0.3.0。 indirect
注释表示它不是被本模块直接使用,而是被本模块依赖的模块使用。详情见 go modules
。
现在,让我们来试下更新 rsc.io/sampler
的 minor 版本。和上面一样,运行 go get
然后运行测试:
$ go get rsc.io/sampler go: finding rsc.io/sampler v1.99.99 go: downloading rsc.io/sampler v1.99.99 go: extracting 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 example.com/hello 0.014s $ 复制代码
啊,测试失败。输出表明最新版的 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.0
,而 v1.99.99
与我们不兼容。那试一试 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 $ go test PASS ok example.com/hello 0.022s $ 复制代码
注意, go get
中使用 @v1.3.1
来显示的指明版本,默认是 @latest
,也就是最新的版本。
添加一个新 major 版本依赖
我们来添加一个新函数: func Proverb
返回一个 Go 并发 proverb。这是通过调用 rsc.io/quote/v3
中的 quote
实现的。首先,我们在 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 := Prover(); got != want { t.Error("proverb() = %q, want %q", got, want) } } 复制代码
然后,我们运行测试:
$ 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.024s $ 复制代码
注意,我们的模块现在同时依赖 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 $ 复制代码
Go 模块的每一个不同的 major 的版本(v1, v2等等)都使用不同的模块路径:从 v2
开始,路径必须以 major 版本号结尾。例如, rsc.io/quote
的 v3 版本不同于 rsc.io/quote
,它们通过模块路径来区别。这种约定惯例称为 语义化导入版本(semantic import versioning)
,它给不兼容的包(不同的 major 版本号)不同的名字。与之对比, rsc.io/quote
的 v1.6.0 版应该向后兼容 v1.5.2 版,所以它能重用 rsc.io/quote
这个名字。(上节中, rsc.io/sampler
的 v1.99.99 版应该向后兼容 v1.3.0 版,但由于 bug 或者客户端错误的使用导致不兼容。)
go 构建的时候某个模块路径最多只能有一个版本,也就是每个 major 版本最多只能有一个:一个 rsc.io/quote
,一个 rsc.io/quote/v2
,一个 rsc.io/quote/v3
等等。这给模块作者一个模块可能重复使用的清晰规则:同时使用 rsc.io/quote
的 v1.5.2 和 1.6.0 是不允许的。同时,允许使用同一模块的不同 major 版本(因为它们有不同的模块路径),这给模块使用者升级到一个新的 major 版本的能力。例如,我们想使用 rsc.io/quote/v3
中的 quote.Concurrency
,还我们还不准备对 rsc.io/quote
v1.5.2 的使用进行升级合并。这种平滑升级合并是非常重要的,尤其是在大型软件或代码库中。
更新一个依赖到最新的 major 版本
现在让我们来完成从 src.io/quote
到 src.io/quote/v3
的升级转化。因为 major 版本号变了,那可能存在一些 API 被移除,或重命名,或有不兼容的变化。通过文档,我们发现 Hello
变成了 HelloV3
:
$ go doc rsc.io/quote/v3 package quote // import "rsc.io/quote/v3" Package quote collects pithy sayings. func Concurrency() string func GlassV3() string func GoV3() string func HelloV3() string func OptV3() string $ 复制代码
我们将 hello.go
中的 quote.Hello()
升级为 quoteV3.HelloV3()
:
package hello import quoteV3 "rsc.io/quote/v3" func Hello() string { return quoteV3.HelloV3() } func Proverb() string { return quoteV3.Concurrency() } 复制代码
现在,我们也不用重命名导入了,所以去掉重命名:
package hello import "rsc.io/quote/v3" func Hello() string { return quote.HelloV3() } func Proverb() string { return quote.Concurrency() } 复制代码
最后,运行测试看看一切都是否正确:
$ go test PASS ok example.com/hello 0.014s 复制代码
移除未使用的依赖
我们移除了所有关于 rsc.io/quote
的使用,但这个模块依然在 go list -m
和 go.mod
文件中:
$ go list -m all example.com/hello golang.org/x/text v0.3.0 rsc.io/quote v1.5.2 rsc.io/quote/v3 v3.1.0 rsc.io/sampler v1.3.1 $ cat go.mod module example.com/hello go 1.12 require ( golang.org/x/text v0.3.0 // indirect rsc.io/quote v1.5.2 rsc.io/quote/v3 v3.0.0 rsc.io/sampler v1.3.1 // indirect ) $ 复制代码
这是为什么?因为构建单个包,比如 go build
或 go test
,能轻松的发现缺什么包并需要加入到依赖中,却不能发现能安全移除的包。只有在检查一个模块中所有的包之后,才能确定哪些依赖能安全移除。普通的构建命令不会加载所有的包,因而也不能安全的移除没有使用的依赖。
go mod tidy
命令能清理这些没有使用的依赖:
$ go mod tidy $ go list -m all example.com/hello golang.org/x/text v0.3.0 rsc.io/quote/v3 v3.1.0 rsc.io/sampler v1.3.1 $ cat go.mod module example.com/hello go 1.12 require ( golang.org/x/text v0.3.0 // indirect rsc.io/quote/v3 v3.1.0 rsc.io/sampler v1.3.1 // indirect ) $ go test PASS ok example.com/hello 0.020s $ 复制代码
总结
Go 模块
是 Go 语言未来的依赖管理系统。模块功能在所有支持的 Go 版本中都是可用的(现在为,Go 1.11,Go 1.12)。
本文介绍了使用 Go 模块的工作流:
-
go mod init
创建一个新模块,并初始化go.mod
文件来描述该模块。 -
go bulid
,go test
还有其他的包构建命令来添加一个新的依赖到go.mod
中。 -
go list -m all
打印当前模块的依赖。 -
go get
改变依赖的版本(或者添加新依赖)。 -
go mod tidy
移除未使用的依赖。
我们鼓励你在个人本机开发中使用 Go 模块,并且把 go.mod
和 go.sum
加入到你的项目中。提供反馈和帮助 Go 中依赖管理完善,请发送bug 报告 或使用报告 给我们。
感谢你的反馈以及对模块功能提升的帮助。
译者总结
本文主要讲 Go 模块的基本用法,关于模块的更多概念和用法见 Github Page
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Python Requests模块快速入门
- TypeScript基础入门之模块(一)
- TypeScript基础入门之模块解析(一)
- TypeScript基础入门之模块解析(二)
- Node.js模块系统 (创建模块与加载模块)
- 黑客基础,Metasploit模块简介,渗透攻击模块、攻击载荷模块
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JS 压缩/解压工具
在线压缩/解压 JS 代码
随机密码生成器
多种字符组合密码