当前位置: 首页 > 科技观察

GoModules的知识点大家一起学习了吗?

时间:2023-03-12 03:15:50 科技观察

GoModules发展历程goget阶段起初,Go语言在1.5之前是没有依赖管理工具的。如果要导入依赖库,需要执行goget命令将代码拉入GOPATH/src目录下,作为GOPATH下的全局依赖,也就是说没有孤立项目的版本控制和包依赖;在vendor阶段,为了解决孤立项目的包依赖问题,Go1.5版本引入了vendor机制,环境变量中有一个GO15VENDOREXPERIMENT需要设置为1,这是一个环境变量它Go1.6版本默认启用,现已退出历史舞台;vendor实际上是将原本放在GOPATH/src中的依赖包放到项目的vendor目录下进行管理,不同项目独立管理自己的依赖包,互不影响。原来是包共享模式。它通过供应商机制进行隔离。编译项目时,会先去vendor目录下寻找依赖。如果没有找到,会去GOPATH目录下搜索;优点:保证了功能工程的完整性,减少了依赖包的下载,可以使用vendor直接编译。缺点:版本控制问题还是没有解决,goget还是拉取最新版本的代码;很多优秀的社区管理工具的开发者都在这期间工作过他们也实现了很好的包依赖管理工具,比如:godep:https://github.com/tools/godepgovendor:https://github.com/kardianos/govendorglide:https://github.com/Masterminds/glidedep:https://github.com/golang/depdep应该是其中最成功的。Go语言已经正式支持它。官方依赖工具呢?事实上,随着RussCox与Go团队的其他成员继续深入讨论,发现dep的一些细节似乎越来越不适合Go,所以官方采用了新的提议来推进。提案的结果首先是Releasedvgo,最终演变成我们现在看到的Gomodules;gomodulesgomodules由RussCox发起,Go1.11发布,Go1.12成长,Go1.13丰富,Go1.14官方推荐对于生产使用,几乎每个后续版本都有或多或少的一些优化。Go1.16引入了gomodretract,Go1.18引入了goworkworkspace的概念。我们将在本文中介绍这些;GoModules知识点GO111MODULE环境变量该环境变量是GoModules的一个开关,主要有以下参数:auto:只有在项目包含go.mod文件时才启动gomodules,默认值在Go1.13版本上:nobrainStartGoModules,推荐设置,Go1.14off后默认值:GoModulesisdisabled,一般不使用gomodules的项目使用;我现在用的Go版本是1.19.3,默认GO111MODULE=on,感觉这个变量也是会像GO15VENDOREXPERIMENT最后会启动系统环境变量的阶段;GOPROXY,该环境变量用于设置Go模块代理,Go在拉取模块版本时无需传统的VCS方式即可从镜像站点快速拉取。GOPROXY的值应该是逗号Split,默认值是https://proxy.golang.org,直接的,但是这个地址在国内是访问不到的,所以可以改用goproxy.cn(七牛云配置),设置命令:goenv-wGOPROXY=GOPROXY=https://goproxy.cn,直接也可以使用其他配置,比如阿里配置:goenv-wGOPROXY=https://mirrors.aliyun.com/goproxy/这个环境变量也可以关闭,可以设置为“off”,禁止Go在后续操作中使用任何Go模块代理;在上面的配置中,我们在direct之后用逗号分隔值,什么意思呢?direct是一个特殊的指示器,因为我们指定了镜像地址,默认是从镜像站点拉取,但是镜像站点可能不存在一些库,direct可以指示go回到模块版本的源地址去grab,如github,当go模块代理返回404、410等错误时,会自动尝试列表中的下一个,遇到direct时返回源地址进行抓取;GOSUMDB这个环境变量的值是一个Go校验数据库,用于保证Go在拉取模块版本时拉取的模块版本数据没有被篡改。如果发现不一致,它将中止。也可以将该值设置为off,禁止Go进行后续操作。运行时检查模块版本;什么是Go校验和数据库?Go校验和数据库主要用于保护Go免受任何被篡改的非法Go模块版本。详细算法机制参见:https://go.googlesource.com/proposal/+/master/design/25530-sumdb.md#proxying-a-checksum-databaseGOSUMDB默认值为sum.golang.org,默认值的格式和自定义值的格式不一样,国内默认值是Unabletoaccess,我们一般不需要改这个值,因为我们一般都设置了GOPROXY,goproxy.cn支持proxysum.golang.org;GOSUMDB值的自定义格式如下:格式1:+格式2:+。GONOPROXY/GONOSUMDB/GOPRIVATE这三个环境变量放在一起,项目中一般不会用到。这三个环境变量主要用于拉取私有模块。在GOPROXY和GOSUMDB无法访问模块的场景下,比如拉取git上的私有仓库;GONOPROXY和GONOSUMDB的默认值为GOPRIVATE的值,所以我们一般直接使用GOPRIVATE,其值也可以设置多个,用英文逗号分隔;例如:$goenv-wGOPRIVATE="github.com/asong2020/go-localcache,git.xxxx.com"也可以设置使用通配符,为域名设置通配符,这样子域就不会经过Go模块代理Go校验和数据库;globalCachegomoddownload将在本地缓存依赖项。缓存的目录是GOPATH/pkg/mod/cache和GOPATH/pkg/sum。这些缓存依赖可以被多个项目使用,未来可能会迁移到$GOCACHE;您可以使用goclean-modcache清除所有缓存的模块版本数据;GoModules命令我们可以使用gohelpmod查看可用命令:gohelpmodGomod提供对模块操作的访问。请注意,对模块的支持内置于所有go命令中,而不仅仅是'gomod'。例如,依赖项的日常添加、删除、升级和降级应使用“goget”完成。有关模块功能的概述,请参阅“gohelpmodules”。用法:gomod[arguments]命令是:download下载模块到本地缓存editeditgo.modfromtoolsorscriptsgraphprintmodulerequirementgraphinitinitializenewmoduleincurrentdirectorytidyaddmissingandremoveunusedmodulesvendormakevendoredcopyofdependenciesverifyverifydependencieshaveexpectedcontentwhyUmoduleexplainwhypackagesse帮助mod”获取关于命令的更多信息。command函数gomodinit生成go.mod文件gomoddownload下载go.mod文件中指定的所有依赖项并将它们放入全局缓存中gomodtidy整理现有依赖项并添加Missing或移除不用的模块gomodgraph查看已有的依赖结构gomodedit编辑go.mod文件gomodvendor导出项目的所有依赖到vendor目录gomodverify验证模块是否被篡改gomodwhyexplain为什么需要依赖某个模块?go.mod文件go.mod是启用Go模块的项目必备的也是最重要的文件,它描述了其中的meta当前项目的形成。每个go.mod文件的开头符合以下信息:module:用于定义当前项目的模块路径(打通$GOPATH路径)go:当前项目的Go版本,目前仅用于标识目的require:usetosetaspecificmoduleversionexclude:用于排除特定的模块版本usereplace:用于将一个模块版本替换为另一个模块版本,例如chromedp使用的是golang.org/x/image包,一般不会通过直接连接可用,但它有一个github.com/golang/image的图片,所以我们需要使用replace将其替换为图片。Restract:用于声明第三方模块的某些发布版本不能被其他模块使用。Go1.16中引入了一个例子:接下来,我们将分模块详细介绍一下各个部分;modulepathgo.mod文件第一行是模块路径,定义为仓库+模块名,比如上面的项目:modulegithub.com/asong2020/go-localcache因为Go模块遵循语义版本规范2.0.0,所以如果项目版本大于2.0.0,需要按照规范加上major后缀,模块路径改为:modulegithub.com/asong2020/go-localcache/v2modulegithub.com/asong2020/go-localcache/v3...goversiongo.mod文件的第二行是goversion,用来指定你的代码需要的最低版本:go1.19.3其实,此行不是必须的,目前只是一个标志功能,可以留空;requirerequire用于指定项目需要的依赖库及其版本。从上面的例子我们可以看出版本部分有不同的写法,还有注释。接下来解释一下这部分;indirectcommentgithub.com/davecgh/go-spewv1.1.0//间接注释会在以下场景加入:当前项目依赖包A,但是A依赖包B,但是BA的go.mod文件中缺少,所以在当前项目的go.mod中补充B,并添加间接注解当前项目依赖包A,但依赖的包A没有go.mod文件,所以在B中补充当前项目go.mod并添加间接注解当前项目依赖包A,依赖包A同时依赖包B,当依赖包A降级,不再依赖包B时,此时会标记间接注解,可以执行gomodtidy去除依赖;Go1.17版本对此进行了优化,indirectmodule会放在单独的require块中,这样看起来更清晰Incompatiblemark我们会在项目中看到一些库添加了不兼容的标记:github.com/dgrijalva/jwt-gov3.2.0+incompatiblejwt-go这个库是这样的,因为jwt-go的版本大于2,但它们的模块路径仍然没有添加v2、v3等后缀,不符合Go的模块管理规范,所以go模块将它们标记为不兼容,不影响引用;go模块的版本号拉取依赖包。本质也是goget行为,goget主要提供如下命令:命令功能goget拉取依赖,会执行特定的拉取(更新),不会更新依赖它的其他模块。goget-u更新现有的依赖项,这将强制更新它所依赖的所有其他模块,不包括它自己。goget-u-t./...更新所有直接和间接依赖的模块版本,包括单元测试中使用的版本。goget根据依赖包是否有发布标签来拉取依赖包:拉取的依赖包没有发布标签。默认情况下,采用主分支最新提交的提交哈希,并生成一个伪版本号。如果拉取的依赖包有publishedtags如果只有单个模块,则取主版本号最大的tag。如果有多个模块,则计算对应的模块路径,取未发布主版本号最大的tag:github.com/alecthomas/templatev0.0.0-20190718012654-fb15b899a751v0.0.0:根据base生成提交的版本:如果没有基础版本,那么它是vx.0.0的形式如果基础版本是预发布版本,那么它是vx.y.z-pre.0如果基础版本version是正式发布的版本,那么补丁号会加1,形式为vx.y.(z+1)-020190718012654:是本次提交的时间,格式为yyyyMMddhhmmssfb15b899a751:这是版本的commitid,通过它可以判断这个库的具体版本github.com/beego/beev1.12.0replacereplace用于解决一些错误引用依赖库或者debug依赖库;场景示例:示例一:日常异地开发在不开放第三方库的情况下,大部分场景都能满足我们的需求,但有时我们需要对依赖库进行一些自定义修改。依赖库修改后,我们希望引起最小的变化,可以使用replace命令重新引用。调试也可以用replace代替。Go1.18引入了工作空间的概念。可以用工作代替调试,后面会介绍;例2:golang.org/x/crypto库一般不会下载,可以用replace引用去github.com/golang/crypto:gomodedit-replacegolang.org/x/crypto=github.com/golang/crypto@v0.0.0-20160511215533-1f3b11f56072exclude用于跳过某个依赖库的版本,使用场景一般我们知道某个版本有bug或者不兼容,所以我们可以使用exclude来跳过这个版本为了安全;排除(go.etcd.io/etcd/client/v2v2.305.0-rc.0)retractGo1.16版本引入该特性,声明第三方模块的某些release版本不能被其他模块使用;使用场景:严重问题或无意发布某个版本后,模块的维护者可以撤版本,支持撤单个或多个版本;此场景的先前解决方案:维护者删除有问题版本的标签并重新标记新版本;用户发现问题版本标签丢失,涉及手动升级,具体原因不明;引入retract后,维护者可以使用retract在go.mod中添加有问题的版本://Seriousbug...retract(v0.1.0v0.2.0)新版本重新发布后,可以看到版本和引用依赖库的使用,执行golist后提示“严重bug...”;该功能的主要目的是将问题更直观地反馈到开发者手中;go.sum文件go.sun文件也是在gomodinit阶段创建的。go.sum的介绍文档比较少,一般我们很少关注go.sum这个文件。go.sum主要记录所有依赖模块的标定。验证信息,内容如下:image-20230102193717816从上面我们可以看出主要有两种形式:h1:/go.modh1:其中module是依赖的路径,version是依赖hash的版本号是h1:开头的字符串,hash是Gomodules解压目标模块版本的zip文件后,依次对包内的所有文件进行hash,然后用它们的hash结果形成一个总的hash值按照固定的格式和算法。h1hash和go.modhash不能同时存在,或者只有go.modhash存在。当Go认为某个版本不会被使用时,它的h1hash将被省略,只存在go.modhash。;项目中使用的GoModules使用gomodules的前提是Go语言版本大于等于Go1.11;然后我们需要查看环境变量GO111MODULE是否开启,执行goenv查看:$goenvGO111MODULE=off执行如下命令开启gomod:$goenv-wGO111MODULE=on接下来我们随意创建一个项目:$mkdir-pasong/demo$cdasong/demo执行gomodinit初始化项目:$gomodinitgithub.com/asong/demogo:creatingnewgo.mod:modulegithub.com/asong/demo接下来,我们在demo目录下创建main.go文件,写入如下代码:iferr!=nil{return}key:="asong"value:=[]byte("公众号:GolangDreamWorks")err=c.Set(key,value)iferr!=nil{return}entry,err:=c.Get(key)iferr!=nil{return}fmt.Printf("getvalueis%s\n",string(entry))err=c.Delete(key)iferr!=nil{return}}然后执行gomodtidy命令:根据main.go文件自动更新依赖,我们来一个再看go.mod文件:上面是项目对go.mod的简单使用;go1.18新特性:工作空间工作空间用来解决什么问题?场景一:我们有时会在本地对一些三方依赖库进行特殊修改,然后想将项目中的依赖库引用修改到本地调试,这时候我们可以使用replace来替换,这样我们就可以在本地开发调试了,虽然这样可以解决问题,但是会有问题,因为是在项目的go.mod文件中直接修改的,如果误传到远程仓库会影响其他开发同学;场景二:我们在本地开发了一些依赖库。这个时候我们想在本地测试一下,还没有发到远程仓库。然后我们在其他项目中引入依赖库,执行gomodtidy会报远程库没有找到的问题,所以需要先把依赖库push到远程,然后引用调试;因为这些问题,Go语言在Go1.18正式加入了goworkworkspaceGoWork的概念其实就是将N个GoModules组合成一个GoWork。工作区的阅读优先级最高。执行gohelpwork查看gowork提供的功能:$gohelpworkUsage:gowork[arguments]命令为:editeditgo.workfromtoolsorscriptsinitinitializeworkspacefilesyncsyncworkspacebuildlisttomodules使用将模块添加到工作区文件使用“gohelpwork”获取有关命令的更多信息。执行goworkinit命令初始化一个新的工作空间,并在项目中生成一个go.work文件:go1.18//multi-moduleadditionuse(...)replaceXXXXX=>XXXXv1.4.5go.work文件有与go.mod文件语法相同,go.work支持三种指令:go:声明go版本号use:声明应用程序依赖的模块的具体文件路径,路径可以是绝对路径或相对路径,即使路径在当前应用目录之外replace:声明替换某个模块依赖的导入路径,优先级高于go.mod中的replace指令;所以针对上面的场景,我们使用goworkinit命令在项目中导入本地依赖库行关联就可以解决,然后我们只需要在git配置文件中添加go.work文件即可,无需推送到远程;我们也可以在编译时通过-workfile=off命令关闭工作区模式:$gobuild-workfile=off.go.work的引入主要用于本地调试,修改go.mod不会引入问题;参考Go1.18新特性:多模块工作区模式GoModules终极入门Gomod七大罪深入GoModuleGo.mod文件分析总结现在大大小小的公司的项目应该都已经在使用GoModules做依赖包了管理。虽然GoModules与Maven和npm相比并不完美,但它也在不断优化,变得越来越好。如果你当前的项目没有使用gomodules,你可以准备将??你的项目迁移到gomod,我推荐你使用它;好了,本文到此结束,我是asong,我们下期见