Go包管理
简介
总结Go语言历史上的三种包管理机制。
基本知识
Go中的包是通过源码方式进行分发的,所以在引用包的时候,其实就是把它的源码包含进来。
Go语言有两个目录比较重要:GOROOT和GOPATH,使用go env
可以找到这两个目录所在的路径。
GOROOT:Go的安装目录,存放一些内置的开发包和工具。
GOPATH:Go指定的工作空间,保存Go代码和第三方依赖包。GOPATH可能会同时指定很多个目录,每个目录可以看成是一个工作区。在一些需要指定工作区的情况下,会默认选择第一个工作区为当前工作区。
每个工作区中应该有以下三个目录:
bin:存放生成的可执行文件
pkg:存放编译生成的package目标文件
src:存放非内置的源代码,以“仓库名/项目名/包名/子包名”的结构进行组织。
go path方式
Go1.5版本以前的包管理方式是把所有的代码(包括第三方依赖包)都放到GOPATH下。
在这种方式下,当import导入一个包时,搜索的路径依次为$GOPATH/src
、$GOROOT/src
。比如import github.com/spf13/cobra
这条语句,先找$GOPATH/src/github.com/spf13/cobra
,再找$GOROOT/src/github.com/spf13/cobra
。
显而易见,这种方法无法进行版本控制,使用不同版本包需要手动替换。这种方式已经被弃用了。
go vendor方式
Go1.5以后,增加了一种go vendor机制。在每个包下面,会有一个vendor
目录。
在这种方式下,当import导入一个包时,搜索的路径依次为与当前go文件同级的vendor
目录、递归寻找当前go文件上级的vendor
目录、$GOPATH/src
、$GOROOT/src
。这么看来,go vendor方式其实就是个升级版的go path方式。
这种方式将所有的第三方包都放在了vendor
目录下,可以工作在局域网中,解决了包版本控制问题。但是,同一个包在不同的位置被引用,都需要复制一份在各自的vendor
目录中,造成大量冗余。大部分的旧工程都是采用这种方式进行管理的。
注:不要在不同层级中嵌套使用相同的依赖包。
go mod方式
Go1.11以后,官方推出go module作为包管理工具,这也是新工程中推荐的方式。使用go env
可以看到有个GO111MODULE
环境变量,通过修改这个环境变量的值来控制是否启用这个方式。这儿不去细究在不同版本中这个环境变量的默认值,直接用前面的命令查看一下当前值就可以。
注:早期的go环境变量需要手动配置系统环境变量或用户环境变量。从Go1.13开始,可以使用go env -w
命令来修改go环境变量(用户级别)。更多go环境变量的细节,可以使用go help environment
命令查看。
GO111MODULE
有以下三种取值:
off:不使用go mod方式,会使用go vendor方式进行管理。
on:使用go mod方式。
auto:Go工具自己检测是不是使用go mod方式。如果当前包不在$GOPATH并且当前包中有go.mod
文件,使用go mod方式;否则使用go vendor方式。
注:在使用go mod方式管理代码时,可以在go环境变量GOFLAGS
中添加-mod=vendor
标志,这时候就会使用vendor目录中的代码包。
在go mod方式下,包目录中有一个go.mod
文件,它记录了所有依赖包信息,这就是go mod方式进行包管理的核心了。这儿只介绍一下这个文件中的内容,如何使用go mod方式进行源码管理可以参考下面的Go包管理命令。
go.mod
中会存在以下几个内容:
module:记录了当前包的路径
go:Go语言版本
require():依赖包的路径
replace():用指定包路径替换require中的某些包路径
exclude():忽略require中的某些包
Go包管理命令
这儿总结一下在执行一些涉及到包管理的go命令时都做了些什么。更多的go命令及细节可以参考https://github.com/hyper0x/go_command_tutorial。
注:一些go命令在省略必要参数时,会默认使用当前目录的路径为参数。
go build
编译Go包,在这个过程中会根据上面所说的包管理方式去指定路径下找对应的包,找不到会报错。
go run
编译并运行Go包,而且不会在当前路径下生成可执行二进制文件。同go build
,会根据上面所说的包管理方式去指定路径下找对应的包,找不到会报错。
go install
编译并安装Go包。如果包中包括可执行入口(main函数),将会生成可执行二进制文件并复制到$GPATH/bin
目录下;如果包只被用来调用(没有main函数),将会生成二进制库文件并复制到$GOPATH/pkg
下面。
go get
如果本地找不到包,可以用这个命令来下载一个互联网上的包,功能类似git clone命令。下载完成后,它还会执行go build
和go install
命令。
这个命令下载的包将放置在默认工作区目录的src下面,。这个命令不会修改vendor目录下的内容。
注:为了贴合get的含义,将来这个命令可能会只做下载包的操作,更多功能可以通过添加参数来指定。
go list
列出指定的包信息,加-json参数可以显示详细参数。
go clean -modcache
可以用于清理下面说的go mod的缓存文件。
除了上述这些以外,在go mod方式下,还有一系列go mod
子命令经常使用。在go mod方式下,上面提到的这些命令同样有效。
go mod init
初始化一个新包,在包目录下生成go.mod
文件,参数为该模块的导入路径。将会在这个go.mod
文件中写入上面讲过的module和go字段。
go mod download
下载指定版本的依赖包到$GOPATH/pkg/mod
里面。下载过程中会产生缓存文件,这些文件在$GOPATH/pkg/mod/cache
中。
go mod tidy
自动寻找依赖关系,从 go.mod
中删除不需要的依赖、新增需要的依赖,但不会改变依赖版本。将会修改go.mod
文件中的require
字段。同时,会产生一个go.sum
文件,里面保存了当前使用的包。
go mod edit
以命令的方式修改go.mod
文件中的相应字段。
go mod vendor
将当前包依赖的其它包代码写入当前包级别下的vendor目录中。
go mod graph
打印包依赖关系。
go mod verify
校验依赖包。
go mod why
解释为什么需要依赖包。