当前位置: 首页 > 后端技术 > Python

19.一篇文章了解GoModules的前世今生和入门

时间:2023-03-25 20:36:25 Python

大家好。我是明哥在学习Golang的那段时间,我写了详细的学习笔记,放在了我的个人微信上公众号《Go编程时光》。我也是Go语言初学者,所以我写的应该更适合刚接触的同学。如果你也是Go语言新手,不妨关注一下,一起学习成长。在线博客:http://golang.iswbm.comGithub:github.com/iswbm/GolangCodingTime过去Go语言的包依赖管理一直被大家诟病,Go官方一直在努力为开发者提供更方便的简单易用的包管理方案,从最初的GOPATH到GOVENDOR,再到最新的GOModules,虽然走了很多弯路,但最终还是想出了GoModules这样一个像样的解决方案。目前最主流的包依赖管理方式是使用官方推荐的GoModules。Go1.14版本于前段时间发布。官方正式宣布强烈推荐大家使用GoModules,有信心在生产中使用。本文将对GoModules的使用进行长篇大论的讲解,但在此之前,我还是简单介绍一下前两个解决方案,GOPATH和govendor。我觉得这是很有必要的,因为只有了解它的发展历程,才能知道GoModules的到来有多么的艰难和意义。一、最古老的GOPATHGOPATH应该很多人都不陌生。之前配置环境的时候配置过吗?你可以理解为工作目录。在这个工作目录下,通常有如下目录结构。每个目录存放的文件是不同的。bin:存放编译后生成的二进制可执行文件pkg:存放编译后的.afilesrc:存放项目的源代码,可以是你自己写的代码,也可以是你用goget下载的包。将你所有的包或者其他人的包放在$GOPATH/src目录下进行管理。我们称之为GOPATH模式。在这种模式下,使用goinstall时,生成的可执行文件会放在$GOPATH/bin中。如果你安装了一个库,.a文件会在$GOPATH/pkg下相应的平台目录下生成(通过GOOS和GOARCH),生成一个后缀为.a的文件。GOOS表示目标操作系统,包括darwin(Mac)、linux、windows、android、netbsd、openbsd、solaris、plan9等。GOARCH表示目标架构,常见的有arm、amd64等。这两个都是goenv可以在goenv变量名中查看变量。到目前为止,你可能不会认为上述解决方案会造成任何问题,直到你开始真正使用GOPATH开发程序,你将不得不面对各种问题,其中最严重的是版本管理问题,因为GOPATH没有完全没有版本的概念。以下几点是你在使用GOPATH时肯定会遇到的问题:不能在你的项目中使用指定版本的包,因为不同版本包的导入方式是一样的。当其他人运行你开发的程序时,他们无法确定他下载的包的版本就是你期望的版本。当对方使用其他版本时,可能会导致程序无法在本地正常运行。一个包只能保留一个版本,也就是说你在本地开发的所有项目都必须用同一个版本的包,这几乎是不可能的。2、govendor模式的过渡为了解决GOPATH方案下不同项目下无法使用多个版本库的问题,Gov1.5开始支持vendor。过去使用GOPATH时,所有项目共享同一个GOPATH。当他们需要导入依赖时,他们都来这里找。俗话说,GOPATH模式下第三方库只能有一个版本。解决方法是在每个项目下创建一个vendor目录,每个项目需要的依赖只会下载到自己的vendor目录下,项目之间的依赖不会相互影响。编译时,v1.5的Go设置启用GO15VENDOREXPERIMENT=1(注:此变量在v1.6中默认为1,但在v1.7之后,此环境变量已被移除,默认启用vendor特性,不需要手动设置后),vendor目录的依赖包搜索路径的优先级会提高(相对于GOPATH)。搜索包的优先级顺序,从高到低依次为:当前包下的vendor目录,向上一级目录搜索,直到找到src下的vendor目录,GOROOT目录下搜索,搜索依赖包在GOPATH下。这种解决方案虽然解决了一些问题,但并不完美。如果多个项目使用同一个包的同一个版本,包将存在于机器上的不同目录中,不仅浪费磁盘空间,而且无法对第三方包进行集中管理(分散在各个角落).而如果你想分享你的开源项目,你需要上传你所有的依赖包。别人使用的时候,除了你的项目源码外,所有的依赖包都要下载,以保证别人使用的时候,不会因为版本问题导致项目不能正常运行。这些看似没什么问题的问题,会让我们的开发和使用过程非常不爽。虽然我是初学者,没有用过govendor,但我能有一个明显的预感,这个方案还是会让我崩溃的。3、gomod的诞生Gomodulesv1.11版本正式上线。在最新的v1.14版本中,官方发表声明称已经足够成熟,可以用于生产。从v1.11开始,goenv增加了一个环境变量:GO111MODULE,这里的111其实就是v1.11的标志。Go似乎很喜欢这种命名方式。比如vendor出现时,多了一个GO15VENDOREXPERIMENT环境变量,其中15表示vendor诞生于v1.5。GO111MODULE是一个开关,通过它可以打开或关闭gomod模式。它有三个可选值:off、on、auto,默认值为auto。GO111MODULE=off禁用模块支持,编译时将从GOPATH和供应商文件夹中查找包。GO111MODULE=on启用模块支持,GOPATH和vendor文件夹在编译时会被忽略,依赖只会根据go.mod下载。GO111MODULE=auto,当项目在$GOPATH/src之外,且项目根目录下有go.mod文件时,自动开启模块支持。gomod出来后,GOPATH(肯定没人用)和GOVENDOR会被淘汰,但是如果你的项目还在用那些即将过时的包依赖管理方案,请注意设置GO111MODULE为离开。如何设置?您可以使用goenv命令。如果我想启用gomod,我使用这个命令$goenv-wGO111MODULE="on"4.Gomod依赖管理接下来,我们来演示一下gomodules是如何管理包依赖的。gomod不再依赖$GOPATH,这样就可以在没有GOPATH的情况下创建项目,所以我们在home目录下创建一个go_test目录来创建我的项目。具体操作如下:接下来进入项目目录,执行以下命令接下来重点是gomodules的初始化。我们需要通过goinstall查看下载的包安装在哪里?上面我们观察到,使用gomodules模式后,项目目录下会多生成两个文件,分别是go.mod和go.sum。这两个文件是gomodules的核心,这里不得不介绍一下。go.modgo.mod文件的内容比较容易理解。第一行:模块的引用路径。第二行:项目使用的go版本。第三行:项目需要的直接依赖包及其版本。在实际应用中,你会看到比较复杂的go.mod文件,比如下面的模块github.com/BingmingWong/module-testgo1.14require(example.com/applev0.1.2example.com/bananav1.2.3example.com/banana/v2v2.3.4example.com/pear//间接example.com/strawberry//不兼容)excludeexample.com/bananav1.2.4replace(golang.org/x/cryptov0.0.0-20180820150726-614d502a4dac=>github.com/golang/cryptov0.0.0-20180820150726-614d502a4dacgolang.org/x/netv0.0.0-20180821023952-922f4815f713=>github.com/golang/netv0.0.0-20180826012351-8texta4630e=>github4630ecom/golang/textv0.3.0)主要是因为多了两个flags:exclude:忽略指定版本的依赖包replace:因为golang.org/x在国内的每个包都需要绕墙,可以使用replace在go.mod中将其替换为github上的相应库。go.sum文件与go.sum文件相比,它更加复杂和密集。可以看出,虽然内容很多,但不难理解,每一行都是由模块路径、模块版本、hash校验值组成。哈希校验值用于保证当前缓存的模块不会被篡改。hash是以h1:开头的字符串,表示生成校验和的算法是第一版哈希算法(sha256)。值得注意的是,为什么有的包只有一行/go.mod而有的包有两行/go.mod那些有两行的包,不同的是hash值不是一行,一个是h1:hash,一个是go.modh1:hash和bothh1:hashandgo.modh1:hash,或者同时存在,或者只有go.modh1:hash存在。什么情况下h1:hash不存在?即当Go认为某个模块版本不会被使用时,它的h1hash会被省略,不会有h1hash,只有go.modh1:hash的情况。【引自3】go.mod和go.sum是go模块版本管理的指导文件,所以go.mod和go.sum文件都要提交到你的Git仓库,防止别人在写项目的时候使用你,重新生成的go.mod和go.sum与您开发的基础版本不一致。5、gomod命令的使用gomodinit:初始化gomod,生成go.mod文件,然后通过参数指定模块名,上面已经演示过了。gomoddownload:手动触发下载依赖包到本地缓存(默认是$GOPATH/pkg/mod目录)gomodgraph:打印项目的模块依赖结构gomodtidy:添加缺失的包,删除无用的packagesgomodverify:检查模块是否被篡改gomodwhy:检查为什么需要依赖gomodvendor:将项目的所有依赖导出到vendorgomodedit:编辑go.mod文件,格式化go.mod文件加上-fmt参数,然后-require=golang.org/x/text添加依赖,然后-droprequire=golang.org/x/text删除依赖,详见gohelpmodeditgolist-m-jsonall:以json格式打印依赖项如何给项目添加依赖项(写入go.mod)呢?有两种方法:只要你在项目中有import,那么gobuild会自动下载并添加go模块。手动使用goget下载安装后,会自动写入go.mod中。7.总结写在最后。如果让我用一段话来评价GOPATH和govendor,我会说GOPATH作为Golang的第一个包管理模式,只能保证你会用,但不保证好用。解决了GOPATH忽略包版本管理的问题。保证好用,不好用。直到gomod的推出,Golang包的依赖管理才有了可以让Gopher统一和满意的解决方案。有用且易于使用的标准。如果你是刚开始学习Golang,那么GOPATH和govendor不需要深入研究就可以正确理解,除非你要接手的项目因为某些历史原因仍然由govendor工具管理。另外,任何一个Gopher都应该从现在开始跳入gomodules的怀抱。以上是我这几天的学习总结。希望对还没入门的你有所帮助。另外,本文如有不妥之处,还请后台批评指正,以免误导其他小伙伴,万分感谢。8.推荐参考文章Go语言依赖管理Go包依赖管理工具-govendorGoModulesUltimate入门WheretoPlace我们的Go代码系列指南01.搭建开发环境(Goland&VSCode)02.学习五变量创建方法03.详解数据类型:整数和浮点数04。详细数据类型:字节、符文和字符串05。数据类型详解:数组和切片06.数据类型详解:字典和布尔类型07.数据类型详解:指针08.面向对象编程:结构与继承09.一篇理解Go中函数的文章10.Go语言流程控制:if-else条件语句11.Go语言流程控制:switch-case选择语句12.Go语言流程控制:for循环语句13.Go语言流程控制:goto无条件跳转14.Go语言流程控制:defer延迟调用15.Object面向编程:接口与多态16.关键词:make和new的区别?17.一篇理解Go18中语句块和作用域的文章。学习Go协程:goroutine19。学习Go协程:channels/channels20详解。通道死锁的几个经典错误案例详解21。学习Go协程:WaitGroup22。学习Go协程:互斥锁和读写锁23。Go中的异常处理:panic和recover24。超详细解读GoModules的前世今生和入门使用25。Go语言包导入的八个必学知识Point26.如何将自己写的模块开源给别人使用?27.说说Go语言中的类型断言?28.这五点带你看懂Go语言中select的使用