Golang 的包管理一直被大众所诟病的一个点,但是我们可以看到现在确实是在往好的方向进行发展。下面是官方的包管理工具的发展历史:
go build
的时候会先去 vendor 目录查找依赖,如果没有找到会再去 GOPATH 目录下查找。除此之外,社区也一直在有几个活跃的包管理工具,使用广泛且具有代表性的主要有下面几个:
Golang 版本:1.12.3。在 1.12 版本之前,使用 Go modules 之前需要环境变量 GO111MODULE:
set GO111MODULE=on //windows export GO111MODULE=on //linux
在 GOPATH 之外创建一个项目 mod-demo,包含一个 main.go 文件,内容如下:
package main import "fmt" import "github.com/astaxie/beego" func main() { fmt.Println("hello") beego.Run() }
在项目根目录执行命令 go mod init test-demo
,然后会生成一个 go.mod
这里比较关键的就是这个 go.mod 文件,这个文件中标识了我们的项目的依赖的 package 的版本。执行 init 暂时还没有将所有的依赖管理起来。我们需要将程序 run 起来(比如执行 go run/test),或者 build(执行命令 go build)的时候,才会触发依赖的解析。
比如使用 go run 即可触发 modules 工作。
go run test.go
这个时候我们再查看 go.mod 文件:
module test-demo go 1.13 require ( github.com/astaxie/beego v1.12.0 // indirect github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect )
注意:有indirect注释的代表间接依赖,没有的代表直接依赖,这里是版本号+时间戳+hash
注意:在goland中可以用alt+enter/option+enter来下载依赖包
同时我们发现项目目录下多了一个 go.sum 用来记录每个 package 的版本和哈希值。go.mod 文件正常情况会包含 module 和 require 模块,除此之外还可以包含 replace 和 exclude 模块。
这些 package 并不是直接存储到 GOPATH/src,而是存储到 GOPATH/pkg/mod 下面,不同版本并存的方式。
可以使用如下命令来查看当前项目依赖的所有的包
go list -m -u all //如果我想要升级(降级)某个 package 则只需要 go get 即可,比如: go get package@version
需要注意的是,在 modules 模式开启和关闭的情况下,go get
的使用方式不是完全相同的。在 modules 模式开启的情况下,可以通过在 package 后面添加 @version 来表明要升级(降级)到某个版本。如果没有指明 version 的情况下,则默认先下载打了 tag 的 release 版本,比如 v0.4.5 或者 v1.2.3;如果没有 release 版本,则下载最新的 pre release 版本,比如 v0.0.1-pre1。如果还没有则下载最新的 commit。这个地方给我们的一个启示是如果我们不按规范的方式来命名我们的 package 的 tag,则 modules 是无法管理的。version 的格式为 v(major).(minor).(patch)
,更多信息可以参考:https://semver.org/ 。
比如我们现在想将我们依赖中的 beego 项目的版本改为 v1.11.1,则可以像如下操作。我们发现执行完 go get
之后, go.mod 中的项目的版本也相应改变了。
go get github.com/astaxie/beego@v1.11.1
在 modules 开启的模式下,go get 还支持 version 模糊查询,比如 > v1.0.0 表示大于 v1.0.0
的可使用版本;< v1.12.0 表示小于 v1.12.0 版本下最近可用的版本。version 的比较规则按照 version
的各个字段来展开。
除了指定版本,我们还可以使用如下命名使用最近的可行的版本:
我们知道 Go 1.5 推出了 vendor 机制,go mod 也可以支持 vendor 机制,将依赖包拷贝到 vendor 目录。但是像一些 test case 里面的依赖包并不会拷贝的 vendor 目录中。
go mod vendor
proxy 顾名思义,代理服务器。众所周知,有些 Golang 的 package 在国内是无法直接 go get 的。在之前,我们解决这个问题,一般都是通过设置 http_proxy/https_proxy 来解决。GoProxy 相当于官方提供了一种 proxy 的方式让用户来进行包下载。要使用 GoProxy 只需要设置环境变量 GOPROXY
即可。目前公开的 GOPROXY 有:
当然你也可以实现自己的 GoProxy 服务,比如项目中的依赖包含外部依赖和内部依赖的时候,那么只需要实现 module proxy protocal 协议即可。
值得注意的是,在最新 release 的 Go 1.13 版本中默认将 GOPROXY 设置为 https://proxy.golang.org,这个对于国内的开发者是无法直接使用的。所以如果升级了 Go 1.13 版本一定要把 GOPROXY 手动改掉。
replace 主要为了解决某些包发生改名的问题。
对于另外一种场景有的时候也是有用的,比如对于有些 golang.org/x/ 下面的包由于某些原因在国内是下载不了的,但是对应的包在 github 上面是有一份拷贝的,这个时候我们就可以将 go.mod 中的包进行 replace 操作。
下面是一个 Beego 项目的 go.mod 的 replace 的示例。
replace golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 => github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85 replace gopkg.in/yaml.v2 v2.2.1 => github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d
modules 支持的 subcommand 如下
即输入go mod
后面可以跟以下命令
上面在讨论 GoProxy 的时候提到了 Go 1.13 默认设置环境变量 GOPROXY 的值,除此之外 Go 1.13 对 modules 还有哪些值得注意的改动呢?
modules 在 Go 1.13 的版本下是默认开启的。
前面也说到对于一些内部的 package,GoProxy 并不能很好的处理,Go 1.13 推出了 GOPRIVATE 机制。只需要设置这个环境变量,然后标识出哪些 package 是 private 的,那么对于这个 package 的处理将不会从 proxy 下载。GOPRIVATE 的值是一个以逗号分隔的列表,支持正则(正则语法遵守 Golang 的 包 path.Match)。下面是一个 GOPRIVATE 的示例:
GOPRIVATE=*.corp.example.com,rsc.io/private
上面的 GOPRIVATE 表示以 *.corp.example.com 或者 rsc.io/private 开头的 package 都是私有的。
GOSUMDB 的全称为 Go CheckSum Database,用来下载的包的安全性校验问题。包的安全性在使用 GoProxy 之后更容易出现,比如我们引用了一个不安全的 GoProxy 之后然后下载了一个不安全的包,这个时候就出现了安全性问题。对于这种情况,可以通过 GOSUMDB 来对包的哈希值进行校验。当然如果想要关闭哈希校验,可以将 GOSUMDB 设置为 off;如果要对部分包关闭哈希校验,则可以将包的前缀设置到环境变量中 GONOSUMDB 中,设置规则类似 GOPRIVATE。
关于 GOSUMDB 的配置格式为:++。
GOSUMDB="sum.golang.org"GOSUMDB="sum.golang.org+<publickey>"GOSUMDB="sum.golang.org+<publickey> https://sum.golang.org"
上面三种配置都是合理的,因为对于 sum.golang.org,Go 自己知道其对应的 publickey 和 url,所以我们只要配置一个名字即可,对于另外一个 sum.golang.google.cn 也是一样。除此之外的,都需要指明 publickey,url 默认是 https://。
关于 GOSUMDB 更多的详细信息可以参考:https://golang.org/cmd/go/#hdr-Module_authentication_failures
直接在go.mod中写包名即可
module test-demo go 1.13 require ( github.com/astaxie/beego v1.12.0 github.com/gin-gonic/gin v1.4.0 // indirect github.com/go-redis/redis v6.15.6+incompatible github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect redisPackage v0.0.1 //自己加的v0.1.1 必须为三个数(也可以 不加) )
Golang 包管理历经多个版本,目前来看并没有一个完全让开发者满意的方案,比如类似 Java 的 maven 的包管理方式。很多开发者也表示 modules 的管理方式也不是很直观。其实 Golang 的 package 使用 git 的管理方式来管理其实是一个很好的管理方式,我们最需要的其实如何能让普通开发者获取到任何 package 的心智负担降到最低。值得欣慰的是我们确实有看到大家在这个方向上的努力,比如 modules 的 GoProxy。
放眼未来,希望会有一种令大部分普通开发者满意的包管理方式吧。
go mod init:初始化modules go mod download:下载modules到本地cache go mod edit:编辑go.mod文件,选项有-json、-require和-exclude,可以使用帮助go help mod edit go mod graph:以文本模式打印模块需求图 go mod tidy:检查,删除错误或者不使用的modules,下载没download的package go mod vendor:生成vendor目录 go mod verify:验证依赖是否正确 go mod why:查找依赖 go test 执行一下,自动导包 go list -m 主模块的打印路径 go list -m -f={{.Dir}} print主模块的根目录 go list -m all 查看当前的依赖和版本信息 go clean -modcache 用于清理本地缓存