Go语言是怎么管理依赖的

目录

1. Go Modules要点

1.1 GO111MODULE环境变量

1.2 GOPROXY

1.3 GOSUMDB

1.4 GONOPROXY/GONOSUMDB/GOPRIVATE

1.5 全局缓存

2. Go Modules命令

2.1 go.mod文件

2.2 module

2.3 go version

2.4 require

2.4.1 indirect注释

2.4.2 incompatible标记

2.5 版本号

2.6 replace

2.7 exclude

2.8 go.sum文件

3. Go Modules使用


在工程代码中,每种语言基本上都有自己的依赖管理工具,比如pythonpipnode.jsnpmjavamavenrustcargoGo语言也有提供自己的依赖库管理工具。Go语言从v1.5开始开始引入vendor模式,如果项目目录下有vendor目录,那么Go工具链会优先使用vendor内的包进行编译、测试等。在go1.11之后Go语言主要使用go modules对代码依赖进行管理

1. Go Modules要点

1.1 GO111MODULE环境变量

这个环境变量是Go Modules的开关,主要有以下参数:

  • auto:只在项目包含了go.mod文件时启动go modules,在Go1.13版本中是默认值

  • on:无脑启动Go Modules,推荐设置,Go1.14版本以后的默认值

  • off:禁用Go Modules,一般没有使用go modules的工程使用

1.2 GOPROXY

该环境变量用于设置Go模块代理,Go后续在拉取模块版本时能够脱离传统的VCS方式从镜像站点快速拉取,GOPROXY的值要以英文逗号分割,默认值是https://proxy.golang.org,direct,但是该地址在国内无法访问,所以可以使用goproxy.cn来代替(七牛云配置),设置命令:

go env -w GOPROXY=https://goproxy.cn,direct

也可以使用其他配置,例如阿里配置:

go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/

该环境变量也可以关闭,可以设置为"off",禁止Go在后续操作中使用任何Go module proxy
上面的配置中我们用逗号分割后面的值是direct,它是什么意思呢?
direct为特殊指示符,因为我们指定了镜像地址,默认是从镜像站点拉取,但是有些库可能不存在镜像站点中,direct可以指示Go回源到模块版本的源地址去抓取,比如github,当go module proxy返回404、410这类错误时,其会自动尝试列表中的下一个,遇见direct时回源地址抓取

1.3 GOSUMDB

GOSUMDB(go checksum database)是Go官方为了go modules安全考虑,设定的module校验数据库,你在本地对依赖进行变动(更新/添加)操作时,Go 会自动去这个服务器进行数据校验,保证你下的这个代码库和世界上其他人下的代码库是一样的,保证Go在拉取模块版本时拉取到的模块版本数据未经篡改 GOSUMDB的值自定义格式如下:

  • 格式 1:<SUMDB_NAME>+<PUBLIC_KEY>。

  • 格式 2:<SUMDB_NAME>+<PUBLIC_KEY> <SUMDB_URL>

GOSUMDB的默认值是sum.golang.org,默认值与自定义值的格式不一样,默认值在国内是无法访问,这个值我们一般不用动,因为我们一般已经设置好了GOPROXY,goproxy.cn支持代理sum.golang.org;
所以,环境变量GOSUMDB可以用来配置你使用哪个校验服务器和公钥来做依赖包的校验
但是在使用的时候需要注意,如果你的代码仓库或者模块是私有的,那么它的校验值不应该出现在互联网的公有数据库里面,但是我们本地编译的时候默认所有的依赖下载都会去尝试做校验,这样不仅会校验失败,更会泄漏一些私有仓库的路径等信息,我们可以使用GONOSUMDB这个环境变量来设置不做校验的代码仓库, 它可以设置多个匹配路径,用逗号相隔
例如:

go env -w GONOSUMDB=*.example.com,test.xyz/com

这样的话,像 "http://git.example.com", "test.xyz/com"这些公司和自己的私有仓库就都不会做校验了

1.4 GONOPROXY/GONOSUMDB/GOPRIVATE

这三个环境变量放在一起说,一般在项目中不经常使用,这三个环境变量主要用于私有模块的拉取,在GOPROXY、GOSUMDB中无法访问到模块的场景中,例如拉取git上的私有仓库; GONOPROXY、GONOSUMDB的默认值是GOPRIVATE的值,所以我们一般直接使用GOPRIVATE即可,其值也是可以设置多个,以英文逗号进行分割;例如:

go env -w GOPRIVATE="github.com/asong2020/go-localcache,git.xxxx.com"

也可以使用通配符的方式进行设置,对域名设置通配符号,这样子域名就都不经过Go module proxyGo checksum database

1.5 全局缓存

go mod download会将依赖缓存到本地,缓存的目录是GOPATH/pkg/mod/cache、GOPATH/pkg/sum,这些缓存依赖可以被多个项目使用,未来可能会迁移到$GOCACHE下面; 可以使用go clean -modcache清理所有已缓存的模块版本数据

2. Go Modules命令

我们可以使用go help mod查看可以使用的命令:

go help mod
Go mod provides access to operations on modules.

Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.

Usage:

        go mod <command> [arguments]

The commands are:

        download    download modules to local cache
        edit        edit go.mod from tools or scripts
        graph       print module requirement graph
        init        initialize new module in current directory
        tidy        add missing and remove unused modules
        vendor      make vendored copy of dependencies
        verify      verify dependencies have expected content
        why         explain why packages or modules are needed

Use "go help mod <command>" for more information about a command.
命令作用
go mod init生成go.mod文件
go mod download下载go.mod文件中指明的所有依赖放到全局缓存
go mod tidy整理现有的依赖,添加缺失或移除不使用的modules
go mod graph查看现有的依赖结构
go mod edit编辑go.mod文件
go mod vendor导出项目所有的依赖到vendor目录
go mod verify校验一个模块是否被篡改过
go mod why解释为什么需要依赖某个模块

2.1 go.mod文件

go.mod是启用Go modules的项目所必须且最重要的文件,其描述了当前项目的元信息,每个go.mod文件开头符合包含如下信息:

  • module:用于定义当前项目的模块路径(突破$GOPATH路径)

  • go:当前项目Go版本,目前只是标识作用

  • require:用于设置一个特定的模块版本

  • exclude:用于从使用中排除一个特定的模块版本

  • replace:用于将一个模块版本替换为另外一个模块版本,例如chromedp使用golang.org/x/image这个package一般直连是获取不了的,但是它有一个github.com/golang/image的镜像,所以我们要用replace来用镜像替换它

  • restract:用来声明该第三方模块的某些发行版本不能被其他模块使用,在Go1.16引入
    示例如下:

module rotatebot

go 1.17

require (
   github.com/gin-gonic/gin v1.8.1
   github.com/sirupsen/logrus v1.9.0
   gorm.io/driver/sqlite v1.4.3
   gorm.io/gorm v1.24.2
)

require (
   github.com/gin-contrib/sse v0.1.0 // indirect
   github.com/go-playground/locales v0.14.0 // indirect
)

exclude (
   github.com/json-iterator/go v1.1.12 
)

replace (
   github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd ==> github.com/modern-go/concurrent v0.0.0-20190606011245-iyfhlkiuhydg
)

retract v0.2.0

假设我们有上述go.mod文件,接下来我们分模块详细介绍一下各个部分

2.2 module

go.mod文件的第一行是module, 表示工程里的依赖的基路径,例如上面的项目:

module rotatebot

工程里的import的路径都是以rotatebot开头的字符串

2.3 go version

go.mod文件的第二行是go version,其是用来指定你的代码所需要的最低版本:

go 1.17

2.4 require

require用来指定该项目所需要的各个依赖库以及他们的版本,从上面的例子中我们看到版本部分有不同的写法,还有注释,接下来我们来解释一下这部分

2.4.1 indirect注释

github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect

以下场景才会添加indirect注释:

  • 当前项目依赖包A,A又依赖包B,但是A的go.mod文件中缺失B,所以在当前项目go.mod中补充B并添加indirect注释

  • 当前项目依赖包A,A又依赖包B,但是依赖包A没有go.mod文件,所以在当前项目go.mod中补充B并添加indirect注释

  • 当前项目依赖包A,A又依赖包B,当依赖包A降级不再依赖B时,这个时候就会标记indirect注释,可以执行go mod tidy移除该依赖
    Go1.17版本对此做了优化,indirectmodule 将被放在单独 require 块的,这样看起来更加清晰明了

2.4.2 incompatible标记

incompatible标记其实是一个module标签归规范约束,Go module 的版本选择机制规定,Module 的版本号需要遵循 v<major>.<minor>.<patch> 的格式,此外,如果 major 版本号大于 1 时,其版本号还需要体现在 Module 名字中。
比如 Module github.com/RainbowMango/m,如果其版本号增长到 v2.x.x 时,其 Module 名字也需要相应的改变为: github.com/RainbowMango/m/v2。即,如果 major 版本号大于 1 时,需要在 Module 名字中体现版本。
那么如果 Modulemajor 版本号虽然变成了 v2.x.x,但 Module 名字仍保持原样会怎么样呢? 其他项目是否还可以引用呢?
假设github.com/gin-contrib/sse的当前版本为v3.5.0,按照Go module 的版本选择机制,其 Module 名字需要相应的改变为: github.com/gin-contrib/sse/v3,但是如果module名没改,还是github.com/gin-contrib/sse,则在被形目引用的时候,就会在后面加上incompatible标记,变成

require (
        github.com/gin-contrib/sse v3.6.0+incompatible
)

除了增加 +incompatible(不兼容)标识外,在其使用上没有区别

2.5 版本号

go module拉取依赖包本质也是go get行为,go get主要提供了以下命令:

命令作用
go get拉取依赖,会进行指定性拉取(更新),并不会更新所依赖的其它模块
go get -u更新现有的依赖,会强制更新它所依赖的其它全部模块,不包括自身
go get -u -t ./...更新所有直接依赖和间接依赖的模块版本,包括单元测试中用到的

go get拉取依赖包取决于依赖包是否有发布的tags:

  1. 拉取的依赖包没有发布tags
    • 默认取主分支最近一次的commit的commit hash,生成一个伪版本号

  2. 拉取的依赖包有发布tags
    • 如果只有单个模块,那么就取主版本号最大的那个tag

    • 如果有多个模块,则推算相应的模块路径,取主版本号最大的那个tag

没有发布的tags:

 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd

v0.0.0:根据commit的base version生成的:

  • 如果没有base version,那么就是vx.0.0的形式

  • 如果base version是一个预发版本,那么就是vx.y.z-pre.0的形式

  • 如果base version是一个正式发布的版本,那么它就patch号加1,就是vx.y.(z+1)-0的形式

20190718012654:是这次提交的时间,格式是yyyyMMddhhmmss
fb15b899a751:是这个版本的commit id,通过这个可以确定这个库的特定的版本

有发布的tags:

github.com/gin-contrib/sse v0.1.0

2.6 replace

因为某些未知原因,并不是所有的包都能直接用go get获取到,或者说是我们想要在官方的依赖库中集成一些我们自己的功能,这时我们就需要使用go modulesreplace功能了
replace顾名思义,就是用新的package去替换另一个package,他们可以是不同的package,也可以是同一个package的不同版本。看一下基本的语法:

go mod edit -replace=old[@v]=new[@v]

old是要被替换的packagenew就是用于替换的package
replace的使用步骤:

  1. 首先go get new-package(如果你知道package的版本tag,那么这一步其实可以省略,如果想使用最新的版本而不想确认版本号,则需要这一步)

  2. 然后查看go.mod,手动复制new-package的版本号(如果你知道版本号,则跳过)

  3. go mod edit -replace=old[@v]=new[@v]

  4. 接着go mod tidy或者go build或者使用其他的go tools,他们会去获取new-package然后替换掉old-package

  5. 最后,在你的代码里直接使用old-package的名字,golang会自动识别出replace,然后实际你的程序将会使用new-package,替换成功

2.7 exclude

这个特性是在Go1.16版本中引入,用来声明该第三方模块的某些发行版本不能被其他模块使用;使用场景:
发生严重问题或者无意发布某些版本后,模块的维护者可以撤回该版本,支持撤回单个或多个版本;
这种场景以前的解决办法: 维护者删除有问题版本的tag,重新打一个新版本的tag;使用者发现有问题的版本tag丢失,手动介入升级,并且不明真因
引入retract后,维护者可以使用retract在go.mod中添加有问题的版本:

// 严重bug...
retract (
  v0.1.0
  v0.2.0
)

重新发布新版本后,在引用该依赖库的使用执行go list可以看到 版本和"严重bug..."的提醒;该特性的主要目的是将问题更直观的反馈到开发者的手中;

2.8 go.sum文件

Go 在做依赖管理时会创建两个文件,go.mod go.sumgo.mod 的重要性不言而喻,这个文件几乎提供了依赖版本的全部信息。而 go.sum 则是记录了所有依赖的 module 的校验信息,以防下载的依赖被恶意篡改,主要用于安全校验。这个文件我们一般不需要编辑,更新以来的时候会自动更新。
每行的格式如下:

<module> <version> <hash>
<module> <version>/go.mod <hash>

比如

github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=

其中 module 是依赖的路径,version 是依赖的版本号。如果 version 后面跟/go.mod表示对哈希值是 modulego.mod 文件;否则,哈希值是 module.zip文件。 hash 是以h1:开头的字符串,表示生成 checksum 的算法是第一版的 HASH 算法(SHA256)。如果将来在 SHA-256 中发现漏洞,将添加对另一种算法的支持,可能会命名为 h2

3. Go Modules使用

使用go modules的一个前置条件是Go语言版本大于等于Go1.11;然后我们要检查环境变量GO111MODULE是否开启,执行go env查看:

go env | grep GO111MODULE
GO111MODULE="on"

如果GO111MODULE=off,可以执行一下命令打开

 go env -w GO111MODULE=on

接下来就可以使用GO MODULE管理项目工程了,先创建一个项目目录

mkdir -p go_tour/main
cd go_tour

执行:

go mod init go_tour 

运行结果 :

go: creating new go.mod: module go_tour

会在go_tour目录下生成一个go.mod文件,在main包下建立main.go文件

package main

import "github.com/tidwall/gjson"

const json = `{"name":{"hello":"golang","key1":"value1"},"id":12345}`

func main() {
    value := gjson.Get(json, "name.key1")
    println(value.String())
}

然后在go_tour目录下执行go mod tidy命令,可以看到:

go: finding module for package github.com/tidwall/gjson
go: downloading github.com/tidwall/gjson v1.14.4
go: found github.com/tidwall/gjson in github.com/tidwall/gjson v1.14.4

可以看到此时的go.mod文件内容:

module go_tour

go 1.17

require github.com/tidwall/gjson v1.14.4

require (
   github.com/tidwall/match v1.1.1 // indirect
   github.com/tidwall/pretty v1.2.0 // indirect
)

依赖已经安装好,并且可以使用了,在go.mod下还可以看到一个go.sum文件对依赖包的校验,go.sum我们一般不用管

github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=

此时执行go run main.go就可以看到执行结果了

value1

以上就是用go module管理依赖库的简单用法


关注我,观看体系化的技术系列文章。持续给大家分享,不仅仅有后端技术,还有一些行业

新鲜事儿,更有一些职场心得以及求职心得。一起聊聊互联网那些事儿

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值