本节我们根据实际的例子来演示go module的功能。在开始之前,我希望读者思考如下问题,并带着这些问题阅读下面的内容或者亲自动手实践以便加深认识。
Go module到底是做什么的?
我们在前面的章节已介绍过,但还是想强调一下,Go module实际上只是精准的记录项目的依赖情况,包括每个依赖的精确版本号,仅此而矣。
那么,为什么需要记录这些依赖情况,或者记录这些依赖有什么好处呢?
试想一下,在编译某个项目时,第三方包的版本往往是可以替换的,如果不能精确的控制所使用的第三方包的版本,最终构建出的可执行文件从本质上是不同的,这会给问题诊断带来极大的困扰。
接下来,我们从一个Hello World项目开始,逐步介绍如何初始化module、如何记录依赖的版本信息。
项目托管在GitHub https://github.com/renhongcai/gomodule
中,并使用版本号区别使用go module的阶段。
- v1.0.0 未引用任何第三方包,也未使用go module
- v1.1.0 未引用任何第三方包,已开始使用go module,但没有任何外部依赖
- v1.2.0 引用了第三方包,并更新了项目依赖
需要注意的是,下面的例子统一使用go 1.13版本,如果你使用go 1.11 或者go 1.12,运行效果可能略有不同。
本文最后部分我们尽量尝试记录一些版本间的差异,以供参考。
Hello World
在v1.0.0版本时,项目只包含一个main.go文件,只是一个简单的字符串打印:
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
此时,项目还没有引用任何第三方包,也未使用go module。
初始化module
一个项目若要使用Go module,那么其本身需要先成为一个module,也即需要一个module名字。
在Go module机制下,项目的module名字以及其依赖信息记录在一个名为go.mod
的文件中,该文件可以手动创建,也可以使用go mod init
命令自动生成。推荐自动生成的方法,如下所示:
[root@ecs-d8b6 gomodule]# go mod init github.com/renhongcai/gomodule
go: creating new go.mod: module github.com/renhongcai/gomodule
完整的go mod init
命令格式为go mod init [module]
:其中[module]
为module名字,如果不填,go mod init
会尝试从版本控制系统或import的注释中猜测一个。这里推荐指定明确的module名字,因为猜测有时需要一些额外的条件,比如 Go 1.13版本,只有项目位于GOPATH中才可以正确运行,而 Go 1.11版本则没有此要求。
上面的命令会自动创建一个go.mod
文件,其中包括module名字,以及我们所使用的Go 版本:
[root@ecs-d8b6 gomodule]# cat go.mod
module github.com/renhongcai/gomodule
go 1.13
go.mod
文件中的版本号go 1.13
是在Go 1.12引入的,意思是开发此项目的Go语言版本,并不是编译该项目所限制的Go语言版本,但是如果项目中使用了Go 1.13的新特性,而你使用Go 1.11编译的话,编译失败时,编译器会提示你版本不匹配。
由于我们的项目还没有使用任何第三方包,所以go.mod
中并没有记录依赖包的任何信息。我们把自动生成的go.mod
提交,然后我们尝试引用一个第三方包。
管理依赖
现在我们准备引用一个第三方包github.com/google/uuid
来生成一个UUID,这样就会产生一个依赖,代码如下:
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
id := uuid.New().String()
fmt.Println("UUID: ", id)
}
在开始编译以前,我们先使用go get
来分析依赖情况,并会自动下载依赖:
[root@ecs-d8b6 gomodule]# go get
go: finding github.com/google/uuid v1.1.1
go: downloading github.com/google/uuid v1.1.1
go: extracting github.com/google/uuid v1.1.1
从输出内容来看,go get
帮我们定位到可以使用github.com/google/uuid
的v1.1.1版本,并下载再解压它们。
注意:go get
总是获取依赖的最新版本,如果github.com/google/uuid
发布了新的版本,输出的版本信息会相应的变化。关于Go Module机制中版本选择我们将在后续的章节详细介绍。
go get
命令会自动修改go.mod
文件:
[root@ecs-d8b6 gomodule]# cat go.mod
module github.com/renhongcai/gomodule
go 1.13
require github.com/google/uuid v1.1.1
可以看到,现在go.mod
中增加了require github.com/google/uuid v1.1.1
内容,表示当前项目依赖github.com/google/uuid
的v1.1.1
版本,这就是我们所说的go.mod
记录的依赖信息。
由于这是当前项目第一次引用外部依赖,go get
命令还会生成一个go.sum
文件,记录依赖包的hash值:
[root@ecs-d8b6 gomodule]# cat go.sum
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
该文件通过记录每个依赖包的hash值,来确保依赖包没有被篡改。关于此部分内容我们在此暂不展开介绍,留待后面的章节详细介绍。
经go get
修改的go.mod
和创建的go.sum
都需要提交到代码库,这样别人获取到项目代码,编译时就会使用项目所要求的依赖版本。
至此,项目已经有一个依赖包,并且可以编译执行了,每次运行都会生成一个独一无二的UUID:
[root@ecs-d8b6 gomodule]# go run main.go
UUID: 20047f5a-1a2a-4c00-bfcd-66af6c67bdfb
注:如果你没有使用go get
在执行之前下载依赖,而是直接使用go build main.go
运行项目的话,依赖包也会被自动下载。但是在v1.13.4
中有个bug,即此时生成的go.mod
中显示的依赖信息则会是require github.com/google/uuid v1.1.1 // indirect
,注意行末的indirect
表示间接依赖,这明显是错误的,因为我们直接import
的。
版本间差异
由于Go module在Go v1.11初次引入,历经Go v1.12、v1.13的发展,其实现细节上已有了一些变化,按照之前的规划Go module将会在v1.14定型,推荐尽可能使用最新版本,否则可能会产生一些困扰。
比如,在v1.11中使用go mod init
初始化项目时,不填写module
名称是没有问题,但在v1.13中,如果项目不在GOPATH目录中,则必须填写module
名称。
后记
本节,我们通过简单的示例介绍了如何初始化module以及如何添加新的依赖,还有更多的内容没有展开。比如go.mod
文件中除了 module
和require
指令外还有replace
和exclude
指令,再比如go get
下载的依赖包如何存储的,以及go.sum
如何保证依赖包未被篡改的,这些内容我们留待后面的章节一一介绍。
赠人玫瑰手留余香,如果觉得不错请给个赞~
本篇文章已归档到GitHub项目,求星~ 点我即达