引言:
一、GOPATH简介:
二、GOPATH 模式 - go get
三、GO VENDOR 模式
go vender 之 dep
一· 优势:
二· 操作使用
三· 问题
四、Go Modules 模式
一· 相关概念
二· 开始入手
三· 下载过程
四· 常用命令
五· 最佳建议:
六· 核心概念
七· 由 go dep迁移到 go modules :
在项目开发中,任何项目免不了对第三方项目包依赖引用,可以方便直接调用想并获得结果,可以避免的代码逻辑的复杂性和冗余性。
golang语言默认使用 gopath 来管理go的工程。
手动配置到环境变量中的,可以理解成 golang的 workspace;
GOPATH 路径
|
个人理解:这里要区别于Java中的path的作用,golang中对应javapath概念的应该是GOROOT。
GOPATH 文件结构
它需要有三个子目录:
|
当我们在src目录中写的模块是main时, 它会对应到一个可执行文件, 并且编译后的文件会被复制到bin目录; 如果是其他模块, 它会被编译成一个库文件, 并且被复制到pkg目录;
这就是我们必须提供三个目录的原因, 一个放源代码, 一个放编译后的可执行文件, 另外一个放编译后的库文件。
一个项目总是会由多个成员进行协作开发,或者需要使用其他项目做‘工具包’;我们可以运行 gonglang 语言自带的命令 go get *** 去安装一些包,这些包会被下载到 $GOPATH/src 目录下;
将你的包或者别人的包全部放在 $GOPATH/src
目录下进行管理的方式,我们称之为 GOPATH 模式。
go get 命令使用
|
go get 可以借助代码管理工具(Git、SVN、HG)通过远程拉取或更新代码及其依赖包,并自动完成编译和安装;整个过程就像安装一个 App 一样简单。
但是,当另外一个团队成员拉下我们的代码时,还需要逐个去运行 go get *** 。
个人理解:这里有点像不使用版本管理的Java项目一样,从三方包托管平台手动下载对应版本的jar包,然后放到 lib 或 WEB-INF/webapps 文件夹中; 如果其他团队使用了该项目,也需要同样的方式操作jar包,并且包版本需要一致。
你会发现 GOPATH 模式下的问题:
1、你无法在你的项目中,使用指定版本的包,因为不同版本的包的导入方法也都一样;
2、其他人运行你的开发的程序时,无法保证他下载的包版本是你所期望的版本,当对方使用了其他版本,有可能导致程序无法正常运行
3、在本地一个包只能保留一个版本,意味着你在本地开发的所有项目,没法让不同项目对应不同的项目包版本,这几乎是不可能的。
以前使用 GOPATH 的时候,所有的项目都共享一个 GOPATH,需要导入依赖的时候,都来这里找,正所谓一山不容二虎,在 GOPATH 模式下只能有一个版本的第三方库。
为了解决 GOPATH 方案下不同项目下无法使用多个版本库的问题,Go v1.5 开始支持 vendor 。解决的思路就是:
将原来包共享模式转换为每个工程独立维护的模式,及在每个项目下都创建一个 vendor 目录,每个项目所需的依赖都只会下载到自己vendor目录下,项目之间的依赖包互不影响。
vendor 的好处是保证了工程目录下代码的完整性,将工程代码复制到其他Go编译环境,不需要再去下载第三方包,直接就能编译,这种隔离和解耦的设计思路是一大进步。
目前go vendor 模式的依赖工具有很多,如:glide、godep、dep等。拿dep 来介绍:
1、官方发布产品,不用担心更新维护问题;
2、具有Go开箱即用的简单特性,而且兼容性更好,第三方工具都是兼容官方版的;
官方文档:dep · Dependency management for Go
声明一下:百度或者google看到的'godep'不是这里说的‘dep’,那它们是什么关系呢?按照Peter Bourgon博文来说,它们的作者都有相同的人,但是一个是dep是官方版本,godep是第三方工具。
环境要求:golang >= 1.9 :
我的系统 Golang版本
|
二·一:安装文档
参考链接: dep包管理工具安装
二·二:初始化项目 :
dep 初始化操作
|
执行成功之后会生成一个文件夹 vendor 和 两个文件 Gopkg.lock、Gopkg.toml;
Vendor目录:依赖管理目录,保存依赖的项目代码。
个人理解:类似Java项目中,lib 或 WEB-INF/webapps 文件夹的作用,引用的依赖项目都放到该文件夹中;
Gopkg.toml:依赖管理的核心文件,可以手动修改。
Gopkg.toml 式例
## 包引用规则:定义哪些包是必须包含或者哪些包必须排除
# 由于toml文件非树形结构,Gopgk.toml文件要求required和ignored必须在constraint和override之前定义
ignored = [
"github.com/user/project/pkgX",
"bitbucket.org/user/project/pkgA/pkgY"
]
required = ["github.com/user/thing/cmd/thing"]
## 依赖规则:定义依赖的包的版本以及搜索来源
[[constraint]]
# 依赖的项目对应的导入路径
name = "golang.****-corp.com/tools/alice"
# 指定依赖项的版本约束,可使用运算符或通配符表示
version = ">=0.9.0"
# 使用该约束会导致默认的‘master’变更为分支,修改也会被记录到Gopkg.lock中。
branch = "FIN-188888"
## 修剪规则
[prune]
# 去除Go不使用的文件
non-go = true
# 去除Go测试文件
go-tests = true
# 去除未出现在导入图中的目录文件
unused-packages = true
Gopkg.lock:自动生成的文件,不需要手动修改;
Gopkg.lock 式例
[[projects]]
# 依赖项目对应标识
name = "golang.****-corp.com/kit/cryptx"
# 分支
branch = "master"
# vendor目录下当前项目内容的哈希签名,使用标准的crypto/sha256算法
digest = "1:abcf1c61e6914aca18cc057903bb4c17da838e651554f872a47b1f6bac7df1b2"
packages = [
".",
"env",
"exec",
"http",
"option",
"request",
"sign",
]
# 修剪规则-字母缩写
pruneopts = "NUT"
# 版本信息
revision = "01b74674cc5a2f6403ab9e6de3a0d417c4ab73ef"
一般情况下,Gopkg.toml只定义项目中使用的直接依赖项,而Gopkg.lock里面除了包含Gopkg.toml中所有依赖项之外,还包含传递依赖项。
比如当前工程依赖项目A,而项目A又依赖B、C,那么只有A会包含在Gopkg.toml中,而A、B、C都会在Gopkg.lock中定义,所以Gopkg.lock定义了所有依赖项目的详细信息,使得每次build我们自己的项目时,始终基于确定不变的依赖项。
个人理解:对比Java-Maven项目:Gopkg.toml 好比 pom.xml 文件,对项目直接的依赖项进行管理;而Gopkg.lock 文件好比操作 ‘Show Dependencies‘ 生成的依赖图谱
vendor、Gopkg.toml 和 Gopkg.lock 三者之间的关系:
二·三:使用方法
dep 命令 展开源码
# 添加一条依赖
dep ensure -add github.com/bitly/go-simplejson
# 这里 @= 参数指定的是 某个 tag
dep ensure -add github.com/bitly/go-simplejson@=0.4.3
# 添加后,先调用一下新加入的库,然后执行 确保 同步
dep ensure
# 更新依赖
dep ensure -update golang.****-corp.com/tools/alice
二·四: 引入过程
其搜索包的优先级顺序,由高到低是这样的:
1、当前包下的 vendor 目录
2、向上级目录查找,直到找到 src 下的 vendor 目录
3、在 GOROOT 目录下查找
4、在 GOPATH 下面查找依赖包
虽然这个方案解决了 GOPATH模式 下的 一些问题,但是方案并不完美:
1、如果多个项目用到了同一个包的同一个版本,这个包会存在于该机器上的不同目录下,不仅对磁盘空间是一种浪费,而且没法对第三方包进行集中式的管理(分散在各个角落)。
2、并且如果要分享你的项目,需要将所有的依赖包悉数上传;在别人使用的时候,除了项目的源码外,还要把所有的依赖包全部下载下来,才能保证别人使用的时候保证版本一致而正常运行。
Go modules 是 Go 语言中正式官宣的项目依赖解决方案。官方WIKI:https://github.com/golang/go/wiki/Modules
发布于 Go1.11(2018-08-24),成长于 Go1.12,丰富于 Go1.13,正式于 Go1.14,推荐在生产上使用,Go 官方也鼓励所有用户从其他依赖项管理工具迁移到 Go modules。
Go moudles 目前集成在 Go 的工具链中,只要安装了 Go,自然而然也就可以使用 Go moudles。
利用Go 的 module 特性,你再也不需要关心传统的GOPATH了(当然GOPATH变量还是要存在的,但只需要指定一个目录),你可以任性的在硬盘任何位置新建一个Golang项目。
个人理解:传统的GOPATH模式,我们必须按照固定文件目录创建项目,类似Maven生成的目录结构,src 下main只能放源码,test放测试代码,target是编译后的代码……
利用go mod后,Golang项目可以像Java项目一样在任意位置创建工程;而原来变量中指定的GOPATH不用过多关注,可以理解成Maven的仓库,对应的文件夹会用来存放不同版本的依赖包。
二·一:设置环境变量
从 v1.11 开始,go env多了个环境变量:GO111MODULE,这里的 111其实就是 v1.11 的象征标志,go 里好像很喜欢这样的命名方式;
GO111MODULE 是一个开关,通过它可以开启或关闭 go mod 模式。
它有三个可选值:off、on、auto,默认值是auto。go env -w GO111MODULE="on"
1、GO111MODULE=off 禁用模块支持,编译时会从GOPATH和vendor文件夹中查找包。
2、GO111MODULE=on 启用模块支持,编译时会忽略GOPATH和vendor文件夹,只根据 go.mod下载依赖。
3、GO111MODULE=auto,当项目在$GOPATH/src 外且项目根目录有go.mod文件时,自动开启模块支持。
二·一:初始化工程
在对应的工程目录中打开终端执行命令: go mod init ***demoProject
看到提示 “go: creating new go.mod: module ***demoProject”, 说明 go mod 初始化成功了,会在项目根目录下生个文件 go.mod
包含go.mod文件的目录也被称为模块根,也就是说: go.mod 文件的出现定义了它所在的目录为一个模块。
go.mod 式例
//置顶-代表go模块名,也即被其它模块引用的名称,位于文件第一行
module golang.****-corp.com/demo
//go版本
go 1.16
//最小需求列表(依赖模块及其版本信息)
require (
// 对应依赖项目 项目版本号
github.com/spf13/cobra v1.3.0
golang.***-corp.com/finance/abcdef-common v0.0.0-20220223084623-8638e88c999e
)
// 用于从使用中排除一个特定的模块版本
exclude example.com/banana v1.2.4
//用于将一个模块版本替换为另外一个模块版本
replace example.com/apple v0.1.2 => example.com/fried v0.1.0
replace example.com/banana => example.com/fish
二·三: 引入依赖:
项目引用了其他依赖后,执行 go mod download 手动下载所有的依赖到本地,或 go mod tidy 整理依赖完善 go.mod 文件并下载至本地,会生成 go.sum文件
go.sum 式例
//记录每个依赖库的版本和哈希值
github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0=
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
golang.****-corp.com/finance/abcd-common v0.0.0-20220223084623-8638e88c999e h1:OvrVPaCLGUeMgY+Uzo6uLbOF5NIdKBsTDisnkyuG0rY=
golang.****-corp.com/finance/abcd-common v0.0.0-20220223084623-8638e88c999e/go.mod h1:APKYogyUvNvSCVZol17RGhkN3ktrUANIWInqJPQs10U=
h1:hash 和 go.mod h1:hash两者,要不就是同时存在,要不就是只存在 go.mod h1:hash。那什么情况下会不存在 h1:hash 呢?
就是当 Go 认为肯定用不到某个模块版本的时候就会省略它的h1 hash,就会出现不存在 h1 hash,只存在 go.mod h1:hash 的情况。
go.mod 和 go.sum 是 go modules 版本管理的指导性文件,因此 go.mod 和 go.sum 文件都应该提交到你的 Git 仓库中去,避免其他人使用你写项目时,重新生成的go.mod 和 go.sum 与你开发的基准版本的不一致。
go module 默认不在 GOPATH 目录下查找依赖文件,其首先会在 $GOPATH/pkg/mod 中查找有没有所需要的依赖,没有的直接会进行下载。
可以使用 go mod download下载好所需要的依赖,依赖默认会下载到$GOPATH/pkg/mod中,其他项目也会使用缓存的 module。
使用 Go 的其他包管理工具 godep、govendor、glide、dep 等都避免不了的问题,Go Modules 也是一样,但在go.mod中可以使用replace将特定的库替换成其他库。
go mod init # 初始化生成go.mod
go mod tidy # 更新依赖文件 (比较好用 初始化、整理、下载一步到位)
go mod download # 下载依赖文件
go mod vendor # 将依赖转移至本地的vendor文件
go mod edit # 手动修改依赖文件
go mod graph # 打印依赖图
go mod verify # 校验依赖
go mod why # 解释为什么需要依赖
1、尽量不要手动修改go.mod文件,通过go命令来操作go.mod文件
2、尽量遵守semantic version(语义化版本)发布和管理模块
3、利用go mod tidy进行自动整理操作。该模块会清理需求列表:删除不需要的需求项,添加需要的需求项
Go Modules 以semantic version(语义版本化)和Minimal Version Selection, MVS(最小版本选择)为核心,相比dep更具稳定性;同时也解决了vendor代码库依赖过于庞大,造成存储浪费的问题
四·一:语义版本化:
使用semantic version来标识package的版本。具体来说:
主版本号(MAJOR version):当你做了不兼容的 API 修改。
次版本号(MINOR version):当你做了向下兼容的功能性新增。
修订号(PATCH version):当你做了向下兼容的问题修
这里,只要模块的主版本号(MAJOR)不变,次版本号(MINOR)以及修订号(PATCH)的变更都不会引起破坏性的变更(breaking change)。
这就要求开发人员尽可能按照semantic version发布和管理模块(实际是否遵守以及遵守的程度不能保证,参考Hyrum's Law)
四·二:最小版本选择(MVS)
在Minimal version selection之前,Go的选择算法很简单,且提供了 2 种不同的版本选择算法,但都不正确:
第 1 种算法是 go get 的默认行为:若本地有一个版本,则使用此版本;否则下载使用最新的版本。
这种模式将导致使用的版本太老:假设已经安装了B 1.1,并执行 go get 下载,那么go get 不会更新到B 1.2,这样就会导致因为B 1.1太老构建失败或有bug
第 2 种算法是 go get -u 的行为:下载并使用所有模块的最新版本。
这种模式可能会因为版本太新而失败:若你运行 go get -u 来下载A依赖模块,会正确地更新到B 1.2。同时也会更新到C 1.3 和E 1.3,但这可能不是 A 想要的,因为这些版本可能未经测试,无法正常工作。
上图显示了module A,B和C如何分别独立地需要module D和各自需要D的不同版本。
如果我启动一个需要module A的项目,那么为了构建代码,我还需要module D。module D可能有很多版本(最大版本为v1.5.0)可供选择 go list -m -versions *** 。
现在只有module A 需要module D,而module A已指定它要求的版本为v1.0.6,所需版本集合中有v1.0.6,因此Go选择的module D的版本即是它;
再将module B导入项目后,Go会将项目的module D版本从集合(v1.0.6和v1.2.0)中把版本从v1.0.6升级到v1.2.0;
再引入module C的工程时会怎样?Go将从当前所需版本集合(v1.0.6,v1.2.0,v1.3.2)中选择最新版本(v1.3.2)
删除刚刚添加的依赖module C的代码会怎样?Go会将项目锁定到module D的版本v1.3.2上。
降级到版本v1.2.0将是一个更大的更改,而Go知道版本v1.3.2可以正常并稳定运行,因此版本v1.3.2仍然是module D的“最新但非最大(latest non-greatest)“版本。
另外,module文件(go.mod)仅维护快照,而不是日志。没有有关历史撤消或降级的信息。
PS: maven使用最短路径原理:
1、路径不同间接依赖中maven采用的是 - 路径最短者优先;
2、路径相同间接依赖中maven 采用的是 - 依赖定义顺序从上到下。
操作步骤:
1、执行 go version 确保你的 go 版本在 11 或更高
2、将你的代码移动到 GOPATH 之外 并设置 export GO111MODULE=on
3、go mod init [module path] 这个会从你的 Gopkg.lock 文件中读取依赖
4、go mod tidy 这个会移除一些你不需要的依赖
5、rm -rf vendor/ 你可以选择性的删除掉 vendor 目录
6、go build 测试有一下是否成功
7、rm -f Gopkg.lock Gopkg.toml 最后你可以删除掉你的 dep 依赖文件了
### 说的比较简单,实际操作中的坑太多了( 常见的问题,文件要对应国内版本;环境配置问题;项目中依赖环境问题 等等)