golang语言的包依赖管理方式 综述

golang语言的包依赖管理方式 综述_第1张图片

引言:

一、GOPATH简介:

二、GOPATH 模式 - go get

三、GO VENDOR 模式

go vender 之 dep 

一· 优势:

二· 操作使用 

三· 问题

四、Go Modules 模式

一· 相关概念

二· 开始入手

三· 下载过程

四· 常用命令

五· 最佳建议:

六· 核心概念

七· 由 go dep迁移到 go modules :


在项目开发中,任何项目免不了对第三方项目包依赖引用,可以方便直接调用想并获得结果,可以避免的代码逻辑的复杂性和冗余性。

golang语言默认使用 gopath 来管理go的工程。

一、GOPATH简介:

手动配置到环境变量中的,可以理解成 golang的 workspace;

GOPATH 路径

~ % go env GOPATH

/Users/superlfx/go

个人理解:这里要区别于Java中的path的作用,golang中对应javapath概念的应该是GOROOT。

GOPATH 文件结构

        它需要有三个子目录:

./go

    |—— bin

        |—— 存储所编译生成的二进制文件。

    |—— pkg

        |—— 存储预编译的目标文件,以加快程序的后续编译速度。

    |—— src

        |—— 存储所有.go文件或源代码。

        |—— ......

当我们在src目录中写的模块是main时, 它会对应到一个可执行文件, 并且编译后的文件会被复制到bin目录; 如果是其他模块, 它会被编译成一个库文件, 并且被复制到pkg目录;

这就是我们必须提供三个目录的原因, 一个放源代码, 一个放编译后的可执行文件, 另外一个放编译后的库文件。


二、GOPATH 模式 - go get

一个项目总是会由多个成员进行协作开发,或者需要使用其他项目做‘工具包’;我们可以运行 gonglang 语言自带的命令 go get *** 去安装一些包,这些包会被下载到 $GOPATH/src 目录下;

将你的包或者别人的包全部放在 $GOPATH/src 目录下进行管理的方式,我们称之为 GOPATH 模式

go get 命令使用

~ % go get github.com/davyxu/cellnet

go get 可以借助代码管理工具(Git、SVN、HG)通过远程拉取或更新代码及其依赖包,并自动完成编译和安装;整个过程就像安装一个 App 一样简单。
但是,当另外一个团队成员拉下我们的代码时,还需要逐个去运行  go get *** 。

个人理解:这里有点像不使用版本管理的Java项目一样,从三方包托管平台手动下载对应版本的jar包,然后放到 lib 或 WEB-INF/webapps 文件夹中;
如果其他团队使用了该项目,也需要同样的方式操作jar包,并且包版本需要一致。

你会发现 GOPATH 模式下的问题:

1、你无法在你的项目中,使用指定版本的包,因为不同版本的包的导入方法也都一样;

2、其他人运行你的开发的程序时,无法保证他下载的包版本是你所期望的版本,当对方使用了其他版本,有可能导致程序无法正常运行

3、在本地一个包只能保留一个版本,意味着你在本地开发的所有项目,没法让不同项目对应不同的项目包版本,这几乎是不可能的。


三、GO VENDOR 模式

以前使用 GOPATH 的时候,所有的项目都共享一个 GOPATH,需要导入依赖的时候,都来这里找,正所谓一山不容二虎,在 GOPATH 模式下只能有一个版本的第三方库。

为了解决 GOPATH 方案下不同项目下无法使用多个版本库的问题,Go v1.5 开始支持 vendor 。解决的思路就是:

        将原来包共享模式转换为每个工程独立维护的模式,及在每个项目下都创建一个 vendor 目录,每个项目所需的依赖都只会下载到自己vendor目录下,项目之间的依赖包互不影响。

vendor 的好处是保证了工程目录下代码的完整性,将工程代码复制到其他Go编译环境,不需要再去下载第三方包,直接就能编译,这种隔离和解耦的设计思路是一大进步。

go vender 之 dep 

        目前go vendor 模式的依赖工具有很多,如:glide、godep、dep等。拿dep 来介绍:

一· 优势:

        1、官方发布产品,不用担心更新维护问题;

        2、具有Go开箱即用的简单特性,而且兼容性更好,第三方工具都是兼容官方版的;

golang语言的包依赖管理方式 综述_第2张图片     官方文档:dep · Dependency management for Go

声明一下:百度或者google看到的'godep'不是这里说的‘dep’,那它们是什么关系呢?按照Peter Bourgon博文来说,它们的作者都有相同的人,但是一个是dep是官方版本,godep是第三方工具。

二· 操作使用 

        环境要求:golang >= 1.9 :

        我的系统 Golang版本

~ % go version

go version go1.16.8 darwin/amd64

二·一:安装文档

        参考链接: dep包管理工具安装

二·二:初始化项目 :

        dep 初始化操作

~ % dep init -v

执行成功之后会生成一个文件夹 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 三者之间的关系:

golang语言的包依赖管理方式 综述_第3张图片

二·三:使用方法

        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 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):当你做了向下兼容的问题修

  • golang语言的包依赖管理方式 综述_第4张图片

这里,只要模块的主版本号(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 想要的,因为这些版本可能未经测试,无法正常工作。

golang语言的包依赖管理方式 综述_第5张图片

 上图显示了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 采用的是 - 依赖定义顺序从上到下。

七· 由 go dep迁移到 go modules :

操作步骤:

        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 依赖文件了

###  说的比较简单,实际操作中的坑太多了( 常见的问题,文件要对应国内版本;环境配置问题;项目中依赖环境问题 等等)

你可能感兴趣的:(golang,依赖管理,golang,开发语言,后端)