Go的1.11和1.12版本包括对模块--新的Go依赖管理系统的初步支持,使依赖版本信息变得明确且更易于管理。这篇博客文章介绍了开始使用模块所需的基本操作。

模块是存储在根目录有一个 go.mod文件的文件树中的 Go 包(package)的集合。go.mod文件定义了模块的module path(也是模块根目录的导入路径)以及模块依赖的其他模块的要求,满足了依赖要求模块才能被成功构建起来。每个依赖模块的要求被写为一个模块路径和相应的模块版本。

下面展示了一个简单的 go.mod文件

module example.com/hello
go 1.12
require rsc.io/quote v1.5.2

从Go 1.11开始,当当前目录或任何父目录有 go.mod时,只要该目录位于 $GOPATH/src之外,go命令就可以使用模块。(在 $ GOPATH/src内部,出于兼容性考虑,即使找到了 go.mod,go命令仍然在旧的GOPATH模式下运行。)从Go 1.13开始,模块模式将是所有开发的默认模式。

本文介绍了使用模块开发Go代码时出现的一系列常见操作:

  • 创建一个新模块。

  • 添加模块的依赖项。

  • 升级模块的依赖项。

  • 增加依赖项的主版本。

  • 将依赖项升级到新的主版本。

  • 删除未使用的依赖项。

创建一个新模块

$GOPATH/src之外的某个地方创建一个新的空目录,然后在新目录下创建一个新的源文件 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)}
}

假设我们新建的目录为 /home/gopher/hello,此时该目录包含一个包,而不是模块,因为目录中没有 go.mod文件。使用 go 命令运行测试会看到:

$ go test
PASS
ok      _/home/gopher/hello    0.020s
$

输出的最后一行汇总了整个包的测试信息。因为我们工作在 $GOPATH和任意模块之外,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 mod init命令编写了一个 go.mod文件:

$ cat go.mod
module example.com/hello
go 1.12
$

go.mod仅出现在模块的根目录中。位于子目录中的包的导入路径将由模块路径加上子目录路径组成。比如说如果我们创建了一个子目录 world无需(也不希望)在其中运行 go mod init。该包将自动被识别为 example.com/hello模块的一部分,导入路径为 example.com/hello/world

现在再运行 go test其运行结果如下:

$ go test
PASS
ok      example.com/hello    0.020s
$

现在输出中的导入路径变成了 example.com/hello,不知不觉中就编写并测试了我们的第一个go模块。

添加模块依赖

Go模块的主要动机是改善管理使用其他开发者编写的代码(代码依赖)的体验。让我们更新 hello.go以导入 rsc.io/quote并使用它来实现 Hello 函数:

package hello
import "rsc.io/quote"
func Hello() string {return quote.Hello()
}

现在再次运行 go test:

$ 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中的任何模块提供的包的导入时, go命令将自动查找包含该软件包的模块,使用其最新的稳定版本,并将其添加到go.mod中。在我们的示例中, go test将新的导入 rsc.io/quote解析为 rsc.io/quote v1.5.2模块,它还下载了 rsc.io/quote使用的两个依赖项,即 rsc.io/samplergolang.org/x/text。但是只有直接依赖项被记录在 go.mod文件中:

$ cat go.mod
module example.com/hello
go 1.12
require rsc.io/quote v1.5.2
$

再次运行 go test命令不会重复上面的依赖下载工作,因为 go.mod现在是最新的,并且下载的模块已本地缓存在 $ GOPATH/pkg/mod中了。

正如我们在上面看到的,添加一个直接依赖项通常也会带来其他间接依赖项。命令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
$

go list的输出中,当前模块也被称为主模块,总是会出现在第一行,后面跟随的是根据模块路径排序后展示的依赖项:

除了 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.sum并不是类似 package-lock.json的包管理器锁文件,它是一个构建状态跟踪文件。它会记录当前模块所有的直接和间接依赖,以及这些依赖的校验和,从而提供一个可以100%复现的构建过程并对构建对象提供安全性的保证。所以应该将 go.modgo.sum都添加到版本控制中go.sum同时还会保留过去使用的包的版本信息,以便日后可能的版本回退,这一点也与普通的锁文件不同。所以go.sum并不是包管理器的锁文件。

更新依赖

对于Go模块,使用语义版本标记引用模块版本。语义版本包括三个部分:主要,次要和补丁。例如,对于v0.1.2,主要版本为0,次要版本为1,补丁版本为2。让我们逐步进行几个次要版本升级。在下一节中,我们将考虑进行主要版本升级

go list-m all的输出中,我们可以看到我们正在使用未标记版本的 golang.org/x/text。让我们升级到最新的标记版本,并测试一切是否正常:

$ 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 // indirectrsc.io/quote v1.5.2
)
$

golang.org/x/text软件包已升级到最新的标记版本(v0.3.0)。go.mod文件中 golang.org/x/text也已更新为指定的 v0.3.0indirect注释指明依赖项不被当前模块直接使用,而是由其依赖的模块所使用的。

现在,让我们尝试升级 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
$

我们将 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
$ go test
PASS
ok      example.com/hello    0.022s
$

注意go get参数中的显式@ v1.3.1。通常,传递给get的每个参数都可以采用显式形式。默认值为@latest,它将解析为先前定义的最新版本。

增加依赖的主版本

让我们在包中添加一个新函数:函数 Proverb通过调用 quote.Concurrency返回Go并发谚语(就是Pike说在某年 Go 开发大会上说的金句:"Concurrency is not parallelism"),这是由 rsc.io/quote/v3模块提供的。首先,我们更新 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 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
$

可以看到 go 命令下载安装了 rsc.io/quote/v3模块,现在我们的模块同时依赖了 rsc.io/quotersc.io/quote/v3:

$ go list -m rsc.io/q...
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
$

Go模块的每个不同的主要版本(v1,v2等)都使用不同的模块路径:从v2开始,该路径必须以主要版本结尾。在示例中, rsc.io/quote的v3版本的模块路径不再是 rsc.io/quote,而是 rsc.io/quote/v3。此约定称为语义导入版本控制,它为不兼容的程序包(具有不同主要版本的程序包)提供了不同的名称。相反, rsc.io/quote的v1.6.0应该与 v1.5.2向后兼容,因此它重用了名称 rsc.io/quote

go命令要求每个主版本模块路径不可重复,每个主要版本的至多:一个 rsc.io/quote,一个 rsc.io/quote/v2,一个 rsc.io/quote/v3,依此类推。这为模块作者提供了关于可能重复单个模块路径的明确规则:程序无法同时使用 rsc.io/quote v1.5.2和 rsc.io/quote v1.6.0来构建。同时,允许模块的不同主要版本(因为它们具有不同的路径)使模块使用者可以逐步升级到新的主要版本。在此示例中,我们想使用 rsc/quote/v3 v3.1.0中的 quote.Concurrency,但尚未准备好迁移 rsc.io/quote v1.5.2的使用。在大型程序或代码库中,增量迁移的能力尤其重要。

将依赖项升级到新的主版本

让我们完成从使用 rsc.io/quote两个版本的包到仅使用 rsc.io/quote/v3的转换。由于版本的重大更改,我们应该期望某些API可能已以不兼容的方式被删除,重命名或以其他方式更改。阅读文档,我们可以看到Hello已经变成 HelloV3

$ go doc rsc.io/quote/v3
package quote // import "rsc.io/quote"
Package quote collects pithy sayings.
func Concurrency() string
func GlassV3() string
func GoV3() string
func HelloV3() string
func OptV3() string
$

我们可以把 hello.go中对 qoute.Hello()的调用更新为使用 quoteV3.HelloV3(),现在已经不需要对 v3 版本的导入路径重命名了所以我们撤销包的重命名(注意默认包名不会包含版本后缀)。

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 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/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 // indirectrsc.io/quote v1.5.2rsc.io/quote/v3 v3.0.0rsc.io/sampler v1.3.1 // indirect
)
$

为什么?因为构建单个软件包(例如使用go build或go test)可以轻松判断出来缺少某些内容并需要添加,但无法确定某些内容是否可以安全删除。只有在检查模块中的所有软件包以及这些软件包的所有可能的构建标记组合之后,才能删除依赖项。普通的 build命令不会加载此信息,因此它不能安全地删除依赖项。

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 // indirectrsc.io/quote/v3 v3.1.0rsc.io/sampler v1.3.1 // indirect
)
$ go test
PASS
ok      example.com/hello    0.020s
$

总结

Go模块是Go依赖管理的未来。从 Go1.11开始都提供模块功能。这篇文章介绍了使用Go模块的这些工作流程:

  • go mod init 创建一个新模块,初始化描述它的go.mod文件。

  • go buil,go test和其他程序包构建命令根据需要向 go.mod添加新的依赖项。

  • go list -m all打印当前模块的依赖关系。

  • go get更改所需依赖的版本(或添加新的依赖)。

  • go mod tidy删除未使用的依赖项。

参考文章:https://blog.golang.org/using-go-modules

现在越来越多的项目都开始用Go Modules来管理依赖包,我也是刚开始尝试将现有项目迁移到用 Go Modules管理的模式,在实践中发现还有很多要学习的地方,后期会分享更多这方面的学习文章和总结。

Go 模块--开始使用Go Modules相关推荐

  1. Nginx编译配置脚本篇(8)- 模块配置脚本auto/modules

    Nginx编译配置脚本篇(8)- 模块配置脚本auto/modules 1.相关文章 2.前言 3.auto/module脚本文件详解 3.1.设置ngx_var的值 3.2.当ngx_module_ ...

  2. 模块开发者使用 ES Modules 的正确姿势

    这里指的 ES Modules (esm)是指源代码中使用 'import','export' 方式导入或导出模块. 这种方式成为标准已经好几年了, 各大浏览器厂商全部支持这种写法: <scri ...

  3. Go 模块--开始使用 Go Modules

    Go的1.11和1.12版本包括对模块--新的Go依赖管理系统的初步支持,使依赖版本信息变得明确且更易于管理.这篇博客文章介绍了开始使用模块所需的基本操作. 模块是存储在根目录有一个 go.mod文件 ...

  4. python之路——模块和包

    一.模块 1.什么是模块? 常见的场景:一个模块就是一个包含了Python定义和声明的文件,文件名就是模块名字加上.py的后缀. 但其实import加载的模块分为四个通用类别: 1.使用Python编 ...

  5. javascript模块_JavaScript模块第2部分:模块捆绑

    javascript模块 by Preethi Kasireddy 通过Preethi Kasireddy JavaScript模块第2部分:模块捆绑 (JavaScript Modules Part ...

  6. 深入探讨Python的import机制:实现远程导入模块 | CSDN博文精选

    来源 | Python编程时光(ID:Python-Time) 所谓的模块导入,是指在一个模块中使用另一个模块的代码的操作,它有利于代码的复用. 也许你看到这个标题,会说我怎么会发这么基础的文章? 与 ...

  7. Python基础之模块与包

    一.模块 1.什么是模块? 一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀. 2.为何要使用模块? 如果你退出python解释器然后重新进入,那么你之前定义的函 ...

  8. apache2.4中layout模块和ssi模块的冲突

    apache2.4中启用ssi和以前的版本区别不大,但是2.4中有的小版本需要使用AddOutputFilterByType才能正确启用ssi模块. 加载模块: LoadModule include_ ...

  9. javascript 异步模块加载 简易实现

    在javascript是没有类似java或其他语言的模块概念的,因此也不可能通过import或using等关键字来引用模块,这样造成了复杂项目中前端代码混乱,变量互相影响等. 因此在复杂项目中引入AM ...

最新文章

  1. 合肥工业大学—SQL Server数据库实验九:视图的定义与使用
  2. Linux的nm查看动态和静态库中的符号
  3. 交换机、集线器、路由器
  4. C++primer第十一章 关联容器 11.1使用关联容器 11.2 关联容器概述
  5. 最近弄了个新的技术公众号,欢迎大家关注
  6. 关于线程插入函数如何用的问题
  7. DOM对象转化成jQuery对象 $(参数) (能不能查到jQuery对象的所有方法)
  8. Java学习笔记之---集合
  9. 【DM7】JAVA操作达梦数据库7.0
  10. 学生免费获取PyCharm专业版
  11. 有关人工智能发展历史及未来前景的论文
  12. python中集合的符号_python集合中的操作符有哪些?怎么用?
  13. 蓝桥杯第七届省赛 模拟风扇控制系统 by YYC
  14. 参考牛顿冷却定律优化最热问题的排序
  15. matlab提取多边形区域_如何利用Matlab求任意多边形的面积
  16. Snipaste 免费截图贴图工具(良心推荐)
  17. utorrent无传入连接_utorrent设置教程~
  18. RTU-518G 研究
  19. Mac平台工具介绍-视频播放器篇
  20. NICE-SLAM学习

热门文章

  1. tinypng upload一键压缩上传工具
  2. 如何让普通用户可以对DBA_SOURCE视图进行闪回查询?
  3. SQL 2000 中如何 纵表变横表
  4. Codeforces 216D Spider#39;s Web 树状数组+模拟
  5. 关于Mysql的错误:No query specified
  6. UVA 10534 Wavio Sequence DP LIS
  7. 异常处理 Exception
  8. python import 路径设置
  9. C#代码中背后进行的值拷贝
  10. 看看老外的智慧城市都是咋样的