我们一起 Go Modules知识点,你学会了吗?

起初Go语言在1.5之前没有依赖管理工具,若想引入依赖库,需要执行go get命令将代码拉取放入GOPATH/src目录下,作为GOPATH下的全局依赖,这也就意味着没有版本控制及隔离项目的包依赖;,为了解决隔离项目的包依赖问题,Go1.5版本推出了vendor机制,环境变量中有一个GO15VENDOREXPERIMENT需要设置为1,该环境变量在Go1.6版本时变成默认开启,目前已经退出了历史舞台;,vendor其实就是将原来放在GOPATH/src的依赖包放到工程的vendor目录中进行管理,不同工程独立地管理自己的依赖包,相互之间互不影响,原来是包共享的模式,通过vendor这种机制进行隔离,在项目编译的时候会先去vendor目录查找依赖,如果没有找到才会再去GOPATH目录下查找;,优点:保证了功能项目的完整性,减少了下载依赖包,直接使用vendor就可以编译,缺点:仍然没有解决版本控制问题,go get仍然是拉取最新版本代码;,很多优秀的开发者在这期间也都实现了不错的包依赖管理工具,例如:,godep:https://github.com/tools/godep,govendor:https://github.com/kardianos/govendor,glide:https://github.com/Masterminds/glide,dep:https://github.com/golang/dep,dep应该是其中最成功的,得到了Go语言官方的支持,该项目也被放到了https://github.com/golang/dep,但是为什么dep没有称为官宣的依赖工具呢?,其实因为随着Russ Cox 与 Go 团队中的其他成员不断深入地讨论,发现 dep 的一些细节似乎越来越不适合 Go,因此官方采取了另起 proposal 的方式来推进,其方案的结果一开始先是释出 vgo,最终演变为我们现在所见到的 Go modules;,go modules是Russ Cox推出来的,发布于Go1.11,成长于Go1.12,丰富于Go1.13,正式于Go1.14推荐在生产上使用,几乎后续的每个版本都或多或少的有一些优化,在Go1.16引入go mod retract、在Go1.18引入go work工作区的概念,这些我们在本文都会介绍到;,这个环境变量是Go Modules的开关,主要有以下参数:,我现在使用的Go版本是1.19.3,默认GO111MODULE=on,感觉该变量也会像GO15VENDOREXPERIMENT最终推出系统环境变量的舞台;,该环境变量用于设置Go模块代理,Go后续在拉取模块版本时能够脱离传统的VCS方式从镜像站点快速拉取,GOPROXY的值要以英文逗号分割,默认值是https://proxy.golang.org,direct,但是该地址在国内无法访问,所以可以使用goproxy.cn来代替(七牛云配置),设置命令:,也可以使用其他配置,例如阿里配置:,该环境变量也可以关闭,可以设置为”off”,禁止Go在后续操作中使用任何Go module proxy;,上面的配置中我们用逗号分割后面的值是direct,它是什么意思呢?,direct为特殊指示符,因为我们指定了镜像地址,默认是从镜像站点拉取,但是有些库可能不存在镜像站点中,direct可以指示Go回源到模块版本的源地址去抓取,比如github,当go module proxy返回404、410这类错误时,其会自动尝试列表中的下一个,遇见direct时回源地址抓取;,该环境变量的值是一个Go checksum database,用于保证Go在拉取模块版本时拉取到的模块版本数据未经篡改,若发现不一致会中止,也可以将值设置为​​off​​即可以禁止Go在后续操作中校验模块版本;,什么是Go checksum database?,Go checksum database主要用于保护Go不会从任何拉到被篡改过的非法Go模块版本,详细算法机制可以看一下:https://go.googlesource.com/proposal/+/master/design/25530-sumdb.md#proxying-a-checksum-database,GOSUMDB的默认值是sum.golang.org,默认值与自定义值的格式不一样,默认值在国内是无法访问,这个值我们一般不用动,因为我们一般已经设置好了GOPROXY,goproxy.cn支持代理sum.golang.org;,GOSUMDB的值自定义格式如下:,这三个环境变量放在一起说,一般在项目中不经常使用,这三个环境变量主要用于私有模块的拉取,在GOPROXY、GOSUMDB中无法访问到模块的场景中,例如拉取git上的私有仓库;,GONOPROXY、GONOSUMDB的默认值是GOPRIVATE的值,所以我们一般直接使用GOPRIVATE即可,其值也是可以设置多个,以英文逗号进行分割;例如:,也可以使用通配符的方式进行设置,对域名设置通配符号,这样子域名就都不经过Go module proxy和Go checksum database;,go mod download会将依赖缓存到本地,缓存的目录是GOPATH/pkg/mod/cache、GOPATH/pkg/sum,这些缓存依赖可以被多个项目使用,未来可能会迁移到$GOCACHE下面;,可以使用go clean -modcache清理所有已缓存的模块版本数据;,我们可以使用go help 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引入,例子:,图片,接下来我们分模块详细介绍一下各部分;,go.mod文件的第一行是module path,采用仓库+module name的方式定义,例如上面的项目:,因为Go module遵循语义化版本规范2.0.0,所以如果工程的版本已经大于2.0.0,按照规范需要加上major的后缀,module path改成如下:,go.mod文件的第二行是go version,其是用来指定你的代码所需要的最低版本:,其实这一行不是必须的,目前也只是标识作用,可以不写;,require用来指定该项目所需要的各个依赖库以及他们的版本,从上面的例子中我们看到版本部分有不同的写法,还有注释,接下来我们来解释一下这部分;,以下场景才会添加indirect注释:,Go1.17版本对此做了优化,indirect 的 module 将被放在单独 require 块的,这样看起来更加清晰明了。,我们在项目中会看到有一些库后面添加了incompatible标记:,jwt-go这个库就是这样的,这是因为jwt-go的版本已经大于2了,但是他们的module path仍然没有添加v2、v3这样的后缀,不符合Go的module管理规范,所以go module把他们标记为incompatible,不影响引用;,go module拉取依赖包本质也是go get行为,go get主要提供了以下命令:,go get拉取依赖包取决于依赖包是否有发布的tags:,没有发布的tags:,v0.0.0:根据commit的base version生成的:,​20190718012654:是这次提交的时间,格式是​​yyyyMMddhhmmss​​,fb15b899a751:是这个版本的commit id,通过这个可以确定这个库的特定的版本,replace用于解决一些错误的依赖库的引用或者调试依赖库;,场景举例:,举例1:,日常开发离不开第三方库,大部分场景都可以满足我们的需要,但是有些时候我们需要对依赖库做一些定制修改,依赖库修改后,我们想引起最小的改动,就可以使用replace命令进行重新引用,调试也可以使用replace进行替换,Go1.18引入了工作区的概念,调试可以使用work进行代替,后面会介绍;,举例2:,golang.org/x/crypto库一般我们下载不下来,可以使用replace引用到github.com/golang/crypto:,用于跳过某个依赖库的版本,使用场景一般是我们知道某个版本有bug或者不兼容,为了安全起可以使用exclude跳过此版本;,这个特性是在Go1.16版本中引入,用来声明该第三方模块的某些发行版本不能被其他模块使用;,使用场景:发生严重问题或者无意发布某些版本后,模块的维护者可以撤回该版本,支持撤回单个或多个版本;,这种场景以前的解决办法:,维护者删除有问题版本的tag,重新打一个新版本的tag;,使用者发现有问题的版本tag丢失,手动介入升级,并且不明真因;,引入retract后,维护者可以使用retract在go.mod中添加有问题的版本:,重新发布新版本后,在引用该依赖库的使用执行go list可以看到 版本和”严重bug…”的提醒;,该特性的主要目的是将问题更直观的反馈到开发者的手中;,go.sun文件也是在go mod init阶段创建,go.sum的介绍文档偏少,我们一般也很少关注go.sum文件,go.sum主要是记录了所有依赖的module的校验信息,内容如下:,图片,image-20230102193717816,从上面我们可以看到主要是有两种形式:,其中module是依赖的路径,version是依赖的版本号。hash是以​​h1:​​开头的字符串,hash 是 Go modules 将目标模块版本的 zip 文件开包后,针对所有包内文件依次进行 hash,然后再把它们的 hash 结果按照固定格式和算法组成总的 hash 值。,h1 hash 和 go.mod hash两者要不同时存在,要不就是只存在go.mod hash,当Go认为肯定用不到某个版本的时候就会省略它的h1 hash,就只有go.mod hash;,使用go modules的一个前置条件是Go语言版本大于等于Go1.11;,然后我们要检查环境变量GO111MODULE是否开启,执行go env查看:,执行如下命令打开go mod:,接下来我们随意创建一个项目:,执行go mod init初始化该项目:,接下来我们在demo目录下创建main.go文件,写下如下代码:,然后执行go mod tidy命令:,图片,自动根据main.go文件更新依赖,我们再看一下go.mod文件:,图片,以上就是在项目对go.mod的简单使用;,工作区用来解决什么问题?,场景1:我们有时在本地会对一些三方依赖库进行特制修改,然后想在项目修改依赖库引用到本地进行调试,这时我们可以使用replace做替换,这样就可以在本地进行开发联调,这样虽然可以解决问题,但是会存在问题,因为是在项目的go.mod文件直接修改的,如果误传到远程仓库,会影响到其他开发同学;,场景2:我们在本地开发了一些依赖库,这时想在本地测试一下,还未发到远程仓库,那么我们在其他项目中引入该依赖库后,执行​​go mod tidy​​就会报远程库没有找到的问题,所以就必须要把依赖库先推送到远程,在引用调试;,正是这些问题,Go语言在Go1.18正式增加了go work工作区的概念,其实就是将N个Go Module组成一个Go Work,工作区的读取优先级是最高的,执行go help work可以查看go work提供的功能:,执行go work init命令初始化一个新的工作区,在项目中生成一个go.work文件:,go.work文件与go.mod文件语法一致,go.work支持三个指令:,所以针对上述场景,我们使用go work init命令在项目中对本地依赖库进行关联即可解决,后续我们只需要在git配置文件中添加go.work文件不推送到远程即可;,我们也可以在编译时通过-workfile=off指令禁用工作区模式:,go.work的推出主要是用于在本地调试,不会因为修改go.mod引入问题;,现在大小公司的项目应该都已经在使用Go Modules​进行依赖包管理了,虽然Go Modules​相比于Maven、npm还不是很完善,但也在不断地进行优化,变得越来越好,如果你现在项目还没有使用go modules,可以准备将项目迁移到go mod了,推荐你使用;,好啦,本文到这里就结束了,我是asong,我们下期见。

文章版权声明

 1 原创文章作者:cmcc,如若转载,请注明出处: https://www.52hwl.com/16606.html

 2 温馨提示:软件侵权请联系469472785#qq.com(三天内删除相关链接)资源失效请留言反馈

 3 下载提示:如遇蓝奏云无法访问,请修改lanzous(把s修改成x)

 免责声明:本站为个人博客,所有软件信息均来自网络 修改版软件,加群广告提示为修改者自留,非本站信息,注意鉴别

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023年3月5日 上午12:00
下一篇 2023年3月7日 下午10:34