go依赖管理

原始依赖管理方式

1.Go语言可以利用本身的能力做基础依赖管理,几个重要的组件包括GOPATH工作目录,Go命令工具(get install build)等,通过go get下载依赖包的最新版本到GOPATH指定目录。

Go语言有个重要的环境变量 GOPATH,保存工作目录路径(working space),此目录下存放正在开发的代码,编译后的依赖包,依赖包的源码以及可执行文件。GOPATH默认路径是$HOME/go,可以修改指向路径,但是,设置时不能修改成Go的安装目录(GOROOT)。 代码里面import非语言自带的包时,从GOPATH目录查找。

bin/ # 可执行文件
    hello                          # command executable
    outyet                         # command executable
pkg/ # go mod依赖,编译过程文件等
src/ # 存放正在开发的源码
    golang.org/x/example/ # 此目录也称为 `import path`
        .git/                      # Git repository metadata
    hello/
        hello.go               # command source
    outyet/
        main.go                # command source
        main_test.go           # test source
    stringutil/
        reverse.go             # package source
        reverse_test.go        # test source
    golang.org/x/image/
        .git/                      # Git repository metadata
    bmp/
        reader.go              # package source
        writer.go              # package source

$GOPATH/pkgGo mod启用前,预编译好的依赖包,加快编译速度,如果遇到编译问题,可以删掉此目录,Go会重建。启用Go mod后,依赖包存放在此目录,参考下面目录结构。

├── pkg
├── darwin_amd64 # 处理器架构
│   └── github.com
│       ├── cpuguy83
│       │   └── go-md2man.a  # 编译的中间文件
│       └── urfave
│           └── cli.a
│   ├── mod # 依赖模块
│   │   ├── cache
│   │   │   ├── download
│   │   │   ├── lock
│   │   │   └── vcs
│   │   ├── cloud.google.com
│   │   │   ├── go@v0.37.4
│   │   │   └── go@v0.81.0
│   │   ├── git.apache.org
│   │   │   └── thrift.git@v0.13.0

  

go get

go install

go build

存在的问题

虽然,Go的基础组件能够一定程度地支持依赖管理,不过也存在明显的缺陷:

  • 无法有效管理依赖包的不同版本,go get只能获取到最新版本的包,由于无法指定依赖版本,依赖包的源码仓库有变动,必然会造成意外影响(更新依赖发现不能编译了,多人开发依赖版本不一致导致的bug等)
  • 无法达成同一个项目,依赖包的不同版本,比如,你要做ElasticSearch迁移,从ElasticSearch6迁移到ElasticSearch7,这两个版本的client sdk有不兼容。想象下,如果不支持同时依赖多个版本,对于开发者来说,要么在依赖包的代码仓库层做文章(比如,不同仓库,不同目录等),要么下载依赖包后,在包的存储目录上处理,效率太低

 

 

godep方案

为了解决依赖管理的问题,从1.5版本开始,Go引入了vendoring机制,简单来说,就是把项目中依赖到的模块,直接拷贝到项目目录中,跟随项目源码一起做版本管理,不过有个问题是,go get并不会把依赖直接下载到项目的vendor目录里去,需要人肉处理,或者使用工具godep

使用vendoring的话,项目目录参考,每个包最好都维持自身的vendor目录,确保依赖可控。

项目中依赖的解析规则:

  1. 优先检测当前目录是否有vendor目录,有的话从此目录查找依赖
  2. 如果同目录没有,则往上层目录查找,
    1. 要么找到依赖
    2. 要么找到一直到$GOPATH/src还没有找到
  3. 如果第二步没有找到,那么,从GOROOT目录找
  4. 如果还没找到,那么,从$GOPATH/src继续查找
  5. 失败 or 找到依赖
$GOPATH
  src/
    github.com/user/package-one/
      one.go
    myproject
      main.go  # import user/package-one,依赖的是同目录vendor下的包
      vendor/
        github.com/user/package-one/
          one.go
      client/
        client.go  # import user/package-one,依赖的是同目录vendor下的包
        vendor/
          github.com/user/package-one/
      server/
        server.go  # import user/package-one,依赖的是同目录vendor下的包
        vendor/
          github.com/user/package-one/
            one.go

  项目开发过程中,可以使用godep save命令,将依赖从GOPATH/src/path/to/package拷贝到项目的vendor目录。同时,生成一份依赖列表文件存放在Godeps/Godeps.json中,如下:

{
	"ImportPath": "github.com/tools/godep",
	"GoVersion": "go1.7",
	"GodepVersion": "v74",
	"Deps": [
		{
			"ImportPath": "github.com/kr/fs",
			"Rev": "2788f0dbd16903de03cb8186e5c7d97b69ad387b"
		},
		{
			"ImportPath": "github.com/kr/pretty",
			"Comment": "go.weekly.2011-12-22-24-gf31442d",
			"Rev": "f31442d60e51465c69811e2107ae978868dbea5c"
		},
		{
			"ImportPath": "github.com/kr/text",
			"Rev": "6807e777504f54ad073ecef66747de158294b639"
		},
		{
			"ImportPath": "github.com/pmezard/go-difflib/difflib",
			"Rev": "f78a839676152fd9f4863704f5d516195c18fc14"
		},
		{
			"ImportPath": "golang.org/x/tools/go/vcs",
			"Rev": "1f1b3322f67af76803c942fd237291538ec68262"
		}
	]
} 

  

godep save后,依赖代码可以一起纳入版本管理,递交到git仓库。godep restore 命令是反向操作,将Godeps/Godeps.json中指定的依赖包/版本,恢复到$GOPATH。 如果依赖的包有版本更新,可以用go get -u更新包,再通过godep save将依赖拷贝到项目中去。

Go1.9推出了官方试验方案dep,功能类似,不再细述。

官方解决方案 go mod

Go mod目前是Go的官方解决方案,Go 1.11(2018年8月) 引入GO111MODULE环境变量,默认值为auto。如果设置该变量 GO111MODULE=off,那么Go命令将始终使用GOPATH mode开发模式。如果设置该变量GO111MODULE=on,Go命令将始终使用Go Modules开发模式。Go 1.13开始成本默认的包管理工具。

模块是指一组相关的包,这些包一起发布,版本号一致,可以直接从版本管理仓库(比如git)下载,也可以通过proxy代理服务器下载到本地。类似maven的包管理,每个包/模块都有自己的坐标,唯一标识。Go mod中使用module path来唯一标识模块,其定义在go.mod文件中,此文件也包括了模块的依赖信息。模块中的包是一些在相同目录中的go源码文件,包也有自己的标识/路径,由module path和包所在的目录组成,比如,模块golang.org/x/net,其中html包的路径就是golang.org/x/net/html

go module golang.org/x/net的定义:

 

➜  net@v0.0.0-20191209160850-c0dbc17a3553 cat go.mod
module golang.org/x/net

go 1.11

require (
	golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
	golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a
	golang.org/x/text v0.3.0
)


x/net目录结构: 
    ├── net@v0.0.0-20191209160850-c0dbc17a3553
    │   ├── AUTHORS
    │   ├── CONTRIBUTING.md
    │   ├── CONTRIBUTORS
    │   ├── LICENSE
    │   ├── PATENTS
    │   ├── README.md
    │   ├── bpf
    │   ├── codereview.cfg
    │   ├── context
    │   ├── dict
    │   ├── dns
    │   ├── go.mod
    │   ├── go.sum
    │   ├── html

 

module path

module path描述了模块的作用,也说明了模块的位置(从哪里可以找到这个模块),module path定义在go.mod文件中,下面的示例,module path是github.com/hashicorp/raft/v2

 
 
module github.com/hashicorp/raft/v2

go 1.12

require (
	github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878
	github.com/hashicorp/go-hclog v0.9.1
	github.com/hashicorp/go-msgpack v0.5.5
	github.com/stretchr/testify v1.3.0
)

  

一般来说,module path包含三部分:

  • 代码仓库根目录,大部分模块的目录就是仓库的根目录(/),比如,golang.org/x/net 模块,其代码仓库根目录就是模块目录,模块就定义在仓库的根目录下
  • 模块目录,模块没有定义在仓库的根目录下,有独立的目录,此目录名作为module path的一部分,比如模块golang.org/x/tools/gopls,模块内容在仓库golang.org/x/toolsgopls目录下
  • 主版本信息,如果模块的主版本>=2,那么,模块名需要加上主版本信息,比如/v2。这里特别要注意的一点,/v2可以是仓库下的独立目录,也可以是仓库的根目录,比如,module pathgolang.org/x/repo/sub/v2 ,可以是定义在golang.org/x/repo/sub目录,也可以是定义在/sub/v2目录下。如果是定义在/sub/v2目录下,那么,此模块的代码仓库用同一份代码,维护了多个版本,/sub/v2维护了/v2版本,/sub维护了另外一个版本

module version

模块版本名以字母v开头,命名规则:

v{major}.{minor}.{patch}-prerelease 
v{major}.{minor}.{patch}+meta

major = 0 非稳定版本

prerelease预发布版本,版本排序时,排在v{major}.{minor}.{patch}之前,prerelease版本一般认为是不稳定的版本,使用时需要注意是否有兼容性、逻辑问题。

meta 在比较版本时,meta不参与比较。由于历史原因,很多go模块在开始开发的时候,并没有引入module机制,这些go模块后续使用包机制时,直接从版本2开始引入,这些模块的v2版本一般会加上+incompatible标记。

Pseudo-versions

一般用在预发布版本上,直接利用代码仓库的版本信息转化为模块版本,比如v0.0.0-20191109021931-daa7c04131f5。 在实际使用时,如果依赖特定commit版本的包,可以直接指定:

go get -d example.com/mod@master
go list -m -json example.com/mod@abcd1234

Major version suffixes

如果新版本和老版本的import path一致,那必须确保这两个版本是兼容的。为了做到这点,从v2版本开始,需要重新定义import path才可以。

有个特例,如果module path是gopkg.in/开头,那么,从版本0开始,import path就需要加上主版本信息,但不是/v2这样的形式,而是.v2,比如,gopkg.in/yaml.v2

常用命令

初始化 go mod init ,执行后,在当前目录内创建go.mod文件:

module github.com/you/hello
 
go 1.12 

  增加依赖 go get github.com/go-chi/chi@v4.0.1

module github.com/you/hello
 
go 1.12
 
require github.com/go-chi/chi v0.9.1

  

替换依赖模块

有的时候,我们可能会需要依赖修改过的模块,比如,我们在开发web框架,依赖了gin框架,但是,开发过程中,需要修改一些gin框架的代码,我们就可以用我们自己维护的gin版本来做。使用replace命令,替换相关依赖,go mod edit -replace github.com/my/library ../path

 

# go.mod

module github.com/myorg/app

require (
	github.com/thirdpart/library v1.1.0
)

replace github.com/myorg/library => ../path

  

修改过后,后续每次编译都会使用replace后的代码来编译,如上../path下的代码,后续,开发结束后,可以把对应replace依赖去掉,go mod edit -dropreplace github.com/my/library

 

清理依赖 go mod tidy

posted @ 2023-02-25 16:40  易先讯  阅读(104)  评论(0编辑  收藏  举报