Go module是从Go 1.11版本才引入的新功能。其目标是取代旧的的基于GOPATH方法来指定在工程中使用哪些源文件或导入包。本文首先分析Go引入module之前管理依赖的优缺点,然后针对这些缺点,看module是如何解决的。
在Go1.11之前,如果想要编写Go代码以及引入第三方包,则需要将源代码写在GOPATH/src目录下。即开发者只能将研发的项目放到GOPATH目录下。同时,将引入的第三方包会下载到GOPATH/pkg目录下。我们先来看下在这种包管理模式下,使用go get是如何安装依赖包的,然后再分析这种包管理的不足。
1.1 go get的工作流程
我们以在项目中引入github.com/go-redis/redis包为例。在项目中使用import导入该包:
import "github.com/go-redis/redis"
然后我们需要使用go get命令将该包下载下来:
go get github.com/go-redis/redis
运行go get命令后,Go会访问 https://github.com/go-redis/redis 并下载该包。一旦下载完成,该包就会被保存到 $GOPATH/pkg/github.com/go-redis/redis 目录下。
那么从执行go get命令到包被保存到对应的目录期间,go get都经历了哪些过程呢?
首先,Go会将包拼接成https协议的URL地址。这里是 https://github.com/go-redis/redis 。Go的第三方包是存储在像GIT或SVN这样的在线版本控制管理系统上的。Go目前支持的在线版本管理类型如下:
Bazaar .bzr
Fossil .fossil
Git .git
Mercurial .hg
Subversion .svn
所以,在示例中,Go首先会解析github.com/go-redis/redis.git (模板格式:github.com/go-redis/redis{.type})。
其次,根据支持的协议依次尝试clone该包。若该在线版本管理系统支持多种协议,那么Go会依次尝试。例如,Git支持 https:// 和 git+ssh:// 协议 , 那么Go会依次使用对应的协议进行解析该包。如果Go成功解析了对应的URL地址,那么该包将会被clone并保存到$GOPATH/pkg目录下。
最后,若版本管理系统不是Go所支持的,则尝试查找META信息。在这种场景下,Go也会试图使用https或http协议拼装成的URL地址去解析。并从返回的HTML代码中查找META信息:
<meta name="go-import" content="import-prefix type repo-root">
根据读取到的meta信息,Go就可以从 https://github.com/go-redis/redis.git 中克隆该项目代码,并将其保存到本地的$GOPATH/src目录下的github.com/go-redis/redis中。
到此,我们已经了解了传统的包管理的工作方式了。下面我们来看看这种管理方式有哪些缺点。
1.2 传统包管理方式的不足
首先,所有的项目都必须在GOPATH/src指向的目录下,或者必须更改GOPATH环境变量所指向的目录。
我们以两个项目A、B来举例说明。假设当前的GOPATH=/usr/local/goworkspace/。如果保持GOPATH不变的话,那么A、B两个项目的源代码都必须要放到GOPATH的目录下,即/usr/local/goworkspace/src目录下。同时,A和B项目引入的第三方包都会在GOPATH/pkg目录下。这样两个项目其实就是混合在一起。
如果不想混合在一起怎么办呢?那就只能更改GOPATH的目录。假设我们现在在研发A项目,并将其工作目录放在/usr/local/goworkspace/a目录下,GOPATH=/usr/local/goworkspace/a。但是在开发B项目时,更改GOPATH的指向,例如我们这里使用/usr/local/goworkspace/b目录下。这样两个项目的源代码以及依赖的第三方包就在各自项目下了。但同时如果想继续修改A项目的代码时,就需要再将GOPATH目录更改到指向A项目的目录中,即GOPATH=/usr/local/goworkspace/src目录。
其次,对于依赖的同一个包只能从master分支上导入最新的提交,且不能导入包的指定的版本。
假设我们有一个第三方包redis,项目A首次引入该包时,使用go get命令从代码库的master分支下载当前最新的代码,并将该包保存在本地的GOPATH/pkg目录下。之后redis包有了新的提交,但同时也引入了一个bug。如果项目A升级或重新安装该包时,使用go get命令并没有指定特定版本的参数,还是从该包的代码库的master分支中下载该包,也就造成了向后不兼容。另外,升级或重新安装的包也会被安装到GOPATH/pkg下的相同目录,因为没有版本的管理,所以会覆盖之前。
好了,以上就是在传统的包管理方式中的两大主要不足之处。那么针对这些不足,我们来看看Go的module是如何解决的。
2.1 什么是module
一个module就是一个包含多个package的目录,即一个package的集合。 其要实现的目标如下:
一个module也是可以像package一样共享的。因此,module也必须是一个git仓库或其他Go可支持的代码控制系统。因此,Go的建议是:
2.2 如何创建module
第一,我们在GOPATH之外的任何位置创建一个目录。这里我们使用encodex,该encodex包含一些对字符串的编码功能函数,例如md5,sha1等。如下图:
根据上面所讨论的,一个Go module应该是一个版本控制系统上的代码仓库。所以我们在github上创建一个git的代码仓库,如下图:
第二,在本地的目录下执行go mod init 命令来初始化Go module。
go mod init github.com/goxuetang/encodex
该命令会在encodex的根目录下创建go.mod文件,go.mod文件会包含我们定义的module的导入路径和依赖的包及对应的版本。如下所示:
由上图可知,在生成的go.mod文件中显示了该module可被导入的路径以及Go的版本。因为目前还没有导入任何其他依赖包,所以没有显示导入包的信息。好,现在我们把该目录同时提交到git上。
git init
git remote add origin https://github.com/goxuetang/encodex.git
第三,我们在encodex的hash包中添加如下代码:
好了,到这里我们就可以发布我们的包。但在发布之前我们先来看下语义化的版本。
语义化的版本是一种通用的版本格式。其格式如下:
vMajor.Minor.Patch
该格式以固定的字母 v 开头,Major代表主版本,Minor代表次版本,Patch代表不定版本。只有在版本不兼容之前的版本时,才会改动主版本Major。当做了向下兼容的功能时会改动Minor。当对次版本Minor做了问题修正时会改动Patch。详细的语义化版本可参考语义化版本官方文档进一步阅读。
Go语言指出,当一个module的新老版本不兼容时,新版本应该发布一个新的主版本。同时,Go会认为这是一个独立的module,和之前的老版本没有任何关系。
Git的分支本质上是一个历史提交的记录。对于每一次提交都有一个唯一的标识对应。对于每一个唯一标识,我们还可以给一个语义化的版本别名,也就是我们所说的tag。
最后,我们可以给我们的module打一个tag了。
因为是第一个版本,所以我们使用版本v1.0.0,如下:
git tag v1.0.0
git push --tags
到此,我们的module已经发布了,并由一个v1.0.0的tag版本。接下来,我们看看在项目中如何使用该module
2.4 如何使用第三方module
我们在新建的main module中创建了一个main.go文件,在该module下要想使用encodex模块下的包,则需要引入和安装两个步骤。在文件中使用import语句引入包,如下图: 第一步,使用import引入模块下具体的包。因为在encodex的module中,我们设置的引入路径是github.com/goxuetang/encodex, 即go.mod文件的第一行。hash包是encodex模块下的一个包。所以我们引入的完整路径是:
import "github.com/goxuetang/encodex/hash"
第二步,使用go get命令安装引入的包。使用go get命令时,可以指定包的具体版本,如下:
go get github.com/goxuetang/encodex/hash@v1.0.0
也可以不指定版本,这时go get命令会自动的查找最近的版本,如下:
go get github.com/goxuetang/encodex/hash
go get:added github.com/goxuetang/encodex v1.0.0
如图所示:
同时,go get会将引入的包加在go.mod文件中。require中不仅有包名,还有对应的版本号。如下图所示:
好,我们现在来看另外一个问题,下载下来的包存在哪里了。
2.5 module存储在哪里
当go get将包下载下来后,会将其存储到GOPATH/pkg/mod目录下。通过go env可以查看GOPATH环境变量的具体指向目录,我的环境下的GOPATH=/Users/YuYang/go,如下是上节中引入的encodex模块。如下图所示:
我们发现encodex模块的目录是带版本号的,这也是Go module能够支持多版本的原因。
在上面我们有讲到module使用的是vX.X.X格式的语义化版本。那么在日常的研发中又是如何对这三个版本号进行升级的呢。
3.1 如何升级module的小版本和补丁版本
随着时间的推移,发布的包肯定会有新的提交,比如修复了一个bug,则patch版本号会升级,添加了一个新功能,则小版本号会升级。做了一项大的改动,和前一个版本不兼容了,那么主版本号就会升级。接下来我们看看在已引入的包后,如何升级对应的版本。
如果我们只想升级补丁版本patch,那么可以使用如下命令:
go get -u=patch
如果想更新同一个大版本下的小版本,那么可以使用如下命令:
go get -u
该命令是如果小版本有更新,则升级小版本。如果只有补丁版本有更新,则会升级补丁版本。
如果想升级到指定的版本,则使用指定版本的命令:
go get module@version
例如,要将encodex模块升级到v1.1.3版本,则使用如下命令:
go get github.com/goxuetang/encodex@v1.1.3
3.2 如何升级module的大版本
如果想要升级大版本则需要重新安装大版本,因为在上面我们有提到,在Go中,会将一个大版本视为一个全新的模块。因此,需要使用go get安装该大版本的模块,同时在对应的文件中通过import引入该包。例如encodex模块升级到了v2版本,那么就需要在encodex模块的go.mod中将导入路径更改为v2。如下:
github.com/goxuetang/encodex/v2
然后就可以在工程中引用该v2版本的模块了。如下:
import newHash github.com/goxuetang/encodex/v2/hash
同时使用go get命令下载并安装该模块:
go get github.com/goxuetang/encodex/v2
一个工程所依赖的模块可分为直接依赖和**间接依赖。**直接依赖就是我们的工程文件中使用import语句导入的模块。而间接依赖就是我们直接依赖的模块所依赖的。如下图:
现在我们在main模块中引入github.com/go-redis/redis 模块,然后查看go.mod文件,发现有如下间接的依赖模块,这里的模块正是在github.com/go-redis/redis 中引入的模块,可以查看github.com/go-redis/redis 模块的go.mod文件以确认。
在上图中,我们还发现redis的模块后面的版本是 v6.15.9+incompatible。这个代表什么意思呢?这个代表的是引入的模块的最新版本是v5.15.9,但同时具有不兼容的风险。为什么呢?因为在redis模块中未使用规范的导入名称。例如,规范的模块命名应该是在模块的版本大于1的时候,导入名称就需要增加主版本信息。例如,当该模块是第一个版本时,其对应的go.mod文件如下:
module github.com/go-redis/redis
当主版本升级到2时,则go.mod中的模块导入名称应该为:
module github.com/go-redis/redis/v2
如果不增加v2这个标识,那么当使用go get github.com/go-redis/redis 下载包的时候,go会找到模块名称没有使用主版本标识的最新的版本。我们通过查看该模块在git上的6.15.9的版本源码,发现其源码中并没有go.mod文件。
所以,当模块的go.mod文件中的导入路径没有版本后缀(例如v2)的情况下,默认是v1版本,因此在使用go get获取这样的模块时,默认会获取v1.x.x的最新版本。
我们已经知道了Go可以同时导入主版本不同的module。那么,如果只有小版本或补丁版本不同,那么Go该如何选择呢?假设工程项目直接依赖于两个module:A和B。同时A依赖于MODULE 1 的v1.0.1版本,但B依赖于MODULE 1的v1.0.2版本。如下图所示: 那么,在工程项目模块(PROJECT MODULE)中需要间接依赖MODULE 1的哪个版本呢?如果我们使用v1.0.1,那么MODULE B有可能会产生异常。在语义化版本中,我们知道小版本或补丁版本应该向后兼容,即v1.0.2是兼容v1.0.1的,所以在PROJECZT MODULE中应该选择MODULE 1的v1.0.2版本。
Go module不仅解决了项目代码不再依赖于GOPATH路径,而且还解决了相同module的多版本引入问题。通过本篇文章,相信您对module的创建、发布、版本管理、依赖关系都会有了一个清晰的认识。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/JpE5aIl2Lu0T1mEwksKw_Q
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。