前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go的包管理学习笔记

Go的包管理学习笔记

原创
作者头像
Johns
修改2022-06-30 10:28:23
7780
修改2022-06-30 10:28:23
举报
文章被收录于专栏:代码工具

一。Go 包管理历程

大部分语言都有版本管理工具,比如nodejs的npm,python中的pip,java里的maven,但是go语言的版本管理经历了漫长的演进历程:

image.png
image.png
  • Go1.5以前,golang使用GOPATH方式管理代码。
    • 代码开发必须在go path src目录下。
    • 依赖包没有版本可言,都是指master最新代码。这个阶段只能手动管理依赖。

开始Golang这么设计是有原因的,因为Google是一个实践Mono Repo(把所有的相关项目都放在一个仓库中)的公司。但更多的公司和组织更多的是用Multi Repo(按模块分为多个仓库),GOPATH 至少解决了第三方源码依赖的问题,虽然它还不够完美。

  • Go1.5后,有人提出了external packages 的概念,于是就有了Go Vendor
    • 解决了包依赖,用一个配置文件来管理
    • 依赖包全都下载到项目vendor下,每个项目都把有一份,不能夸项目共享公共依赖。
    • 依赖包查找路径变成了 src/vendor ===> GOPATH。
    • 在这种模式下,会将第三方依赖的源码下载到本地,不同项目下可以有自己不同的vendor,依然没有解决版本化问题。
    • 默认是忽略vendor的,如果想在项目目录下有vendor可以执行go vendor命令。当然,如果构建程序的时候,希望使用vendor中的依赖go build -mod vendor
  • Go1.12 版本后, 使用go modules进行包管理
    • 不再以gopath为项目空间,使用Modules启用一个新的文件go.mod记录module的元信息,其中包含了依赖包的版本信息。
    • 项目内会生成一个go.mod文件,列出包依赖。所有来的第三方包会准确的指定版本号对于已经转移的包,可以用replace 申明替换,不需要改代码。
    • 通过GO111MODULE控制,GO111MODULE有三个值off/on/auto(默认值),定义如下: off :go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种通过vendor目录或者GOPATH模式来查找。 on:go命令行会使用modules,而一点也不会去GOPATH目录下查找。 auto: 默认值,当项目在$GOPATH/src外,且项目根目录有go.mod文件时,开启模块支持。 当modules功能启用时,依赖包的存放位置变更为$GOPATH/pkg,允许同一个package多个版本并存,且多个项目可以共享缓存的module。

二。GO Module的简介

在go1.12以前,我们知道golang的依赖包管理仅仅只是可用而已。go1.12之后,go mod才真正解决了依赖包管理的几个核心问题。

go 通过go mod 命令来管理依赖包。其中提供了以下几个命令,一般我用的比较多的是go mod tidy

download

download modules to local cache(下载依赖包)

edit

edit go.mod from tools or scripts(编辑go.mod)

graph

print module requirement graph (打印模块依赖图)

verify

initialize new module in current directory(在当前目录初始化mod)

tidy

add missing and remove unused modules(拉取缺少的模块,移除不用的模块)

vendor

make vendored copy of dependencies(将依赖复制到vendor下)

verify

verify dependencies have expected content (验证依赖是否正确)

why

explain why packages or modules are needed(解释为什么需要依赖)

Go module最重要的是go.mod文件的定义,它用来标记一个module和它的依赖库以及依赖库的版本。作用有点类似于Maven里面的pom.xml文件。

Go module遵循语义化版本规范 2.0.0。语义化版本规范 2.0.0规定了版本号的格式,版本格式定义为:主版本号.次版本号.修订号,版本号递增规则如下:

  1. 主版本号:当你做了不兼容的 API 修改,
  2. 次版本号:当你做了向下兼容的功能性新增,
  3. 修订号:当你做了向下兼容的问题修正。

先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。

下面我们来看一下go.mod的文件到底长啥样:

代码语言:txt
复制
module github.com/panicthis/modfile

go 1.15

require (
github.com/cenk/backoff v2.2.1+incompatible
github.com/coreos/bbolt v1.3.3
github.com/edwingeng/doublejump v0.0.0-20200330080233-e4ea8bd1cbed
github.com/stretchr/objx v0.3.0 // indirect
github.com/stretchr/testify v1.7.0
go.etcd.io/bbolt v1.3.6 // indirect
go.etcd.io/etcd/client/v2 v2.305.0-rc.1
go.etcd.io/etcd/client/v3 v3.5.0-rc.1
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b // indirect
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 // indirect
)

exclude (
go.etcd.io/etcd/client/v2 v2.305.0-rc.0
go.etcd.io/etcd/client/v3 v3.5.0-rc.0
)

go.mod下还会有一个go.sum文件,其中包含特定模块版本内容的预期加密哈希,go命令使用go.sum文件确保这些模块的未来下载检索与第一次下载相同的位,以确保项目所依赖的模块不会出现意外更改,无论是出于恶意、意外还是其他原因。

【1】module path

go.mod的第一行是module path, 一般采用仓库+module name的方式定义。这样我们获取一个module的时候,就可以到它的仓库中去查询,或者让go proxy到仓库中去查询。

如果你的版本已经大于等于2.0.0,按照Go的规范,你应该加上major的后缀,module path改成下面的方式:

代码语言:txt
复制
module github.com/panicthis/modfile/v2
module github.com/panicthis/modfile/v3

而且引用代码的时候,也要加上v2v3vx后缀,以便和其它major版本进行区分。这是一个很奇怪的约定,带来的好处是你一个项目中可以使用依赖库的不同的major版本,它们可以共存。

【2】go directive

第二行是go directive。格式是 go 1.xx,它并不是指你当前使用的Go版本,而是指名你的代码所需要的Go的最低版本。

require段中列出了项目所需要的各个依赖库以及它们的版本,除了正规的v1.3.0这样的版本外,还有一些奇奇怪怪的版本和注释,那么它们又是什么意思呢?

正式的版本号我们就不需要介绍了,大家都懂:

代码语言:txt
复制
github.com/coreos/bbolt v1.3.3
【3】伪版本号
代码语言:txt
复制
github.com/edwingeng/doublejump v0.0.0-20200330080233-e4ea8bd1cbed

上面这个库中的版本号就是一个伪版本号v0.0.0-20200330080233-e4ea8bd1cbed,这是go module为它生成的一个类似符合语义化版本2.0.0版本,实际这个库并没有发布这个版本。正式因为这个依赖库没有发布版本,而go module需要指定这个库的一个确定的版本,所以才创建的这样一个伪版本号。

go module的目的就是在go.mod中标记出这个项目所有的依赖以及它们确定的某个版本。

这里的20200330080233是这次提交的时间,格式是yyyyMMddhhmmss, 而e4ea8bd1cbed就是这个版本的commit id,通过这个字段,就可以确定这个库的特定的版本。

而前面的v0.0.0可能有多种生成方式,主要看你这个commit的base version:

  • vX.0.0-yyyymmddhhmmss-abcdefabcdef: 如果没有base version,那么就是vX.0.0的形式
  • vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef: 如果base version是一个预发布的版本,比如vX.Y.Z-pre,那么它就用vX.Y.Z-pre.0的形式
  • vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdef: 如果base version是一个正式发布的版本,那么它就patch号加1,如vX.Y.(Z+1)-0

伪版本号之所以存在主要是由于提交的记录没有打tag, 打了tag就会直接使用tag的版本号。

【4】indirect 间接引用
代码语言:txt
复制
go.etcd.io/bbolt v1.3.6 // indirect
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b // indirect
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 // indirect

有些库后面加了indirect后缀,这又是什么意思的。

如果用一句话总结,间接的使用了这个库,但是又没有被列到某个go.mod中,当然这句话也不算太准确,更精确的说法是下面的情况之一就会对这个库加indirect后缀:

  • 当前项目依赖A,但是A的go.mod遗漏了B, 那么就会在当前项目的go.mod中补充B, 加indirect注释
  • 当前项目依赖A,但是A没有go.mod,同样就会在当前项目的go.mod中补充B, 加indirect注释
  • 当前项目依赖A,A又依赖B,当对A降级的时候,降级的A不再依赖B,这个时候B就标记indirect注释
【5】incompatible 不规范的依赖

有些库后面加了incompatible后缀,但是你如果看这些项目,它们只是发布了v2.2.1的tag,并没有+incompatible后缀。

代码语言:txt
复制
github.com/cenk/backoff v2.2.1+incompatible

这些库采用了go.mod的管理,但是不幸的是,虽然这些库的版major版本已经大于等于2了,但是他们的module path中依然没有添加v2, v3这样的后缀。

所以go module把它们标记为incompatible的,虽然可以引用,但是实际它们是不符合规范的。

【6】exclude 排除依赖

如果你想在你的项目中跳过某个依赖库的某个版本,你就可以使用这个段。

代码语言:txt
复制
exclude (
go.etcd.io/etcd/client/v2 v2.305.0-rc.0
go.etcd.io/etcd/client/v3 v3.5.0-rc.0
)

这样,Go在版本选择的时候,就会主动跳过这些版本,比如你使用go get -u ......或者go get github.com/xxx/xxx@latest等命令时,会执行version query的动作,这些版本不在考虑的范围之内。

【7】replace 替换依赖

replace也是常用的一个手段,用来解决一些错误的依赖库的引用或者调试依赖库。

代码语言:txt
复制
replace github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.3
replace github.com/panicthis/A v1.1.0 => github.com/panicthis/R v1.8.0
replace github.com/coreos/bbolt => ../R

比如etcd v3.3.x的版本中错误的使用了github.com/coreos/bbolt作为bbolt的module path,其实这个库在它自己的go.mod中声明的module path是go.etcd.io/bbolt,又比如etcd使用的grpc版本有问题,你也可以通过replace替换成所需的grpc版本。甚至你觉得某个依赖库有问题,自己fork到本地做修改,想调试一下,你也可以替换成本地的文件夹。

replace可以替换某个库的所有版本到另一个库的特定版本,也可以替换某个库的特定版本到另一个库的特定版本。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一。Go 包管理历程
  • 二。GO Module的简介
    • 【1】module path
      • 【2】go directive
        • 【3】伪版本号
          • 【4】indirect 间接引用
            • 【5】incompatible 不规范的依赖
              • 【6】exclude 排除依赖
                • 【7】replace 替换依赖
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档