抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >
  • Go的依赖管理曾经是饱受诟病的大问题,随着版本的更迭正逐渐完善起来

  • 现在主要的解决方案有vendor模式和Go Modules 两种管理方式

  • 如果不引入依赖管理单靠GOPATH的话,那么就无法解决项目多版本的问题

1.GOPATH的问题

  • Go提供了GOPATH路径,Go依赖的所有的第三方库都放在GOPATH这个目录下

  • 但是呢,在GOPATH下每个被依赖到的项目只能保存一个版本的代码

  • 如果存在被依赖的项目存在版本V1版本V2,当需要用到某个指定版本时,都需要手动去切换,若切换频率高那将是噩梦般存在,严重影响工作效率

2.vendor机制

2.1 什么是vendor

基于vendor机制下,在执行 go buildgo run 命令时,会按照以下顺序去查找包:

  1. 当前包下的 vendor 目录
  2. 向上级目录查找,直到找到 src 下的 vendor 目录
  3. 在 GOROOT 目录下查找
  4. 在 GOPATH 下面查找依赖包

2.2 govendor 工具

govendor 是一个基于 vendor 目录机制的包管理工具

  • 支持从项目源码中分析出依赖的包,并从 $GOPATH 复制到项目的 vendor 目录下

  • 支持包的指定版本,并用 vendor/vendor.json 进行包和版本管理,在json文件可以清晰看到引入的依赖包目录

  • 支持用 govendor add/update 命令从 $GOPATH 中复制依赖包

  • 如果忽略了 vendor 文件,可用 govendor sync 恢复依赖包

  • 可直接用 govendor fetch 添加或更新依赖包

  • 可用 govendor migrate 从其他 vendor 包管理工具中一键迁移到 govendor

  • 支持 Linux,macOS,Windows所有操作系统

2.3 安装 govendor

如果 Go 版本为 1.5,则必须手动设置环境变量 set GO15VENDOREXPERIMENT=1(应该没人还用这么低版本的Go的吧~)

  1. 拉取代码
1
2
// 拉取govendor代码并安装
go get -u github.com/kardianos/govendor
  1. 设置PATH

$GOPATH/bin 添加到 PATH 中(大部分在搭建Go开发环境就设置了)

Linux/macOS 如下设置:export PATH="$GOPATH/bin:$PATH"

  1. 查看命令

在本地的$GOPATH/bin/下发现 govendor 执行文件说明安装成功了

2.4 初始化并使用

  • 进入Go项目目录下,然后执行govendor init 初始化

  • 项目根目录下即会自动生成 vendor 目录和 vendor.json 文件。

此时 vendor.json 文件内容为:

1
2
3
4
5
6
{
"comment": "",
"ignore": "test",
"package": [],
"rootPath": "govendor-example"
}

常用命令

  • 将已被引用且在 $GOPATH 下的所有包复制到 vendor 目录

    govendor add +external

  • $GOPATH 中复制指定包到 vendor 目录

    govendor add github.com/golang/protobuf

  • 列出代码中所有被引用到的包及其状态

    $ govendor list

  • 列出一个包被哪些包引用

    govendor list -v fmt

  • 从远程仓库添加或更新某个包(不会在 $GOPATH 也存一份)

    govendor fetch golang.org/x/net/context

  • 安装指定版本的包

1
2
3
govendor fetch golang.org/x/net/context@a4bbce9fcae005b22ae5443f6af064d80a6f5a55
govendor fetch golang.org/x/net/context@v1 # Get latest v1.*.* tag or branch.
govendor fetch golang.org/x/net/context@=v1 # Get the tag or branch named "v1".
  • 只格式化项目自身代码(vendor 目录下的不变动)

    govendor fmt +local

  • 只构建编译项目内部的包

    govendor install +local

  • 只测试项目内部的测试案例

    govendor test +local

  • 拉取所有依赖的包到 vendor 目录(包括 $GOPATH 存在或不存在的包)

    govendor fetch +out

  • 包已在 vendor 目录,但想从 $GOPATH 更新

    govendor update +vendor

  • 已修改了 $GOPATH 里的某个包,现在想将已修改且未提交的包更新到 vendor

    govendor update -uncommitted <updated-package-import-path>

  • vendor.json 中记录了依赖包信息,该如何拉取更新

    govendor sync

各子命令详细用法可通过 govendor COMMAND -h 或阅读 github.com/kardianos/govendor/context 查看源码包如何实现的。

2.5 govendor 子命令

命令 功能
init 创建 vendor 目录和 vendor.json 文件
list 列出过滤依赖包及其状态
add 从 $GOPATH 复制包到项目 vendor 目录
update 从 $GOPATH 更新依赖包到项目 vendor 目录
remove 从 vendor 目录移除依赖的包
status 列出所有缺失、过期和修改过的包
fetch 从远程仓库添加或更新包到项目 vendor 目录(不会存储到 $GOPATH)
sync 根据 vendor.json 拉取相匹配的包到 vendor 目录
migrate 从其他基于 vendor 实现的包管理工具中一键迁移
get 与 go get 类似,将包下载到 $GOPATH,再将依赖包复制到 vendor 目录
license 列出所有依赖包的 LICENSE
shell 可一次性运行多个 govendor 命令

2.6 govendor 状态参数

状态 缩写 含义
+local l 本地包,即项目内部编写的包
+external e 外部包,即在 GOPATH 中、却不在项目 vendor 目录
+vendor v 已在 vendor 目录下的包
+std s 标准库里的包
+excluded x 明确被排除的外部包
+unused u 未使用的包,即在 vendor 目录下,但项目中并未引用到
+missing m 被引用了但却找不到的包
+program p 主程序包,即可被编译为执行文件的包
+outside 相当于状态为 +external +missing
+all 所有包

3.Go Modules

3.1 什么是Go Modules?

  • Go Modules是Go1.11版本之后官方推出的版本管理工具,并且从Go1.13版本开始,Go Modules成为了Go语言默认的依赖管理工具

  • Go Modules 出现的目的之一就是为了解决 GOPATH 的问题,也可以说准备抛弃 GOPATH

3.2 Go Modules准备工作

  1. 升级go版本至少为 Go 1.11,最好是升级到Go 1.13

  2. 开启GO111MODULE,它有三个可选值:off、on、auto,默认值是auto:

    • GO111MODULE=off:禁用模块支持,编译时会从GOPATH和vendor文件夹中查找包。

    • GO111MODULE=on:启用模块支持,编译时会忽略GOPATH和vendor文件夹,只根据 go.mod下载依赖。

    • GO111MODULE=auto:当项目在$GOPATH/src外且项目根目录有go.mod文件时,开启模块支持。

  1. 设置GOPROXY
  • Go1.11之后设置GOPROXY命令为:export GOPROXY=https://goproxy.cn

  • Go1.13之后GOPROXY默认值为https://proxy.golang.org,在国内是无法访问的,推荐修改为goproxy.cn

1
go env -w GOPROXY=https://goproxy.cn,direct
  1. 创建项目,进入项目根目录下执行:go mod init 生成 go.mod文件

  2. 忘记GOPATH的存在,开始去适应Go Modules的工作模式

注意:Go Modules 从远程拉取的包都会存放在 $GOPATH/pkg/mod/下

3.3 go.mod

go.mod文件记录了项目所有的依赖信息,其结构大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module github.com/littlejoyo/studygo

go 1.13

require (
github.com/DeanThompson/ginpprof v0.0.0-20190408063150-3be636683586
github.com/gin-gonic/gin v1.4.0
github.com/go-sql-driver/mysql v1.4.1
google.golang.org/appengine v1.6.1 // indirect
)

replace(
github.com/go-sql-driver/mysql v1.4.1 => ../myproject
)
  • module:用来定义包名

  • require:用来定义依赖包及版本

  • indirect:表示间接引用

  • replace:表示依赖包指向本地项目,进行取代作用,在国内访问golang.org/x的各个包都需要翻墙,你可以在go.mod中使用replace替换成github上对应的库。

1
2
3
4
5
replace (
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac => github.com/golang/crypto v0.0.0-20180820150726-614d502a4dac
golang.org/x/net v0.0.0-20180821023952-922f4815f713 => github.com/golang/net v0.0.0-20180826012351-8a410e7b638d
golang.org/x/text v0.3.0 => github.com/golang/text v0.3.0
)

依赖包会通过go get进行拉取,默认下载最新依赖的包,并且会在go.mod里面记录下版本号

  • 如果有tag,则会拉取最的tag包,比如上面跟在路径后面的v1.4.0

  • 没有tag则会使用某个固定commit,比如上面的v0.0.0-20190408063150-3be636683586

  • 当然也可以通过直接指定来获取对应版本的包,上面是默认的拉取规则

3.4 常用go mod命令

  • 下载依赖的module到本地cache(默认为$GOPATH/pkg/mod目录)
    go mod download

  • 编辑go.mod文件
    go mod edit

1
2
3
4
5
go mod edit -fmt # 格式化go.mod文件
go mod edit -require=golang.org/x/text # 添加依赖项
go mod edit -droprequire=golang.org/x/text # 移除依赖项
go mod graph #打印模块依赖图
go help mod edit 更多用法
  • 初始化当前文件夹, 创建go.mod文件
    go mod init

  • 增加缺少的module,删除无用的module(常用)
    go mod tidy

  • 将依赖复制到vendor下
    go mod vendor

  • 校验依赖
    go mod verify

  • 解释为什么需要依赖
    go mod why

3.5 其他关键词解释

3.5.1 go.sum

  • go.sum 详细罗列了当前项目直接或间接依赖的所有模块版本,并写明了那些模块版本的 SHA-256 哈希值

  • 主要作用是以备 Go 在今后的操作中保证项目所依赖的那些模块版本不会被篡改。

3.5.2 GOSUMDB

  • GOSUMDB是一个 Go checksum database的缩写,用于使 Go 在拉取模块版本时(无论是从源站拉取还是通过 Go module proxy 拉取)保证拉取到的模块版本数据未经篡改

  • 主要用于保护 Go 不会从任何源头拉到被篡改过的非法 Go 模块版本

  • GOSUMDB也可以是“off”即禁止 Go 在后续操作中校验模块版本

1
2
格式 1:<SUMDB_NAME>+<PUBLIC_KEY>
格式 2:<SUMDB_NAME>+<PUBLIC_KEY> <SUMDB_URL>
  • 拥有默认值:sum.golang.org (之所以没有按照上面的格式是因为 Go 对默认值做了特殊处理)。

  • sum.golang.org 在中国无法访问,故而更加建议将 GOPROXY 设置为 goproxy.cn,因为 goproxy.cn 支持代理 sum.golang.org

3.5.3 GOPRIVATE

一起看下这三个环境变量:GONOPROXY/GONOSUMDB/GOPRIVATE

  • 当GOPROXY 指定的 Go module proxy 或由 GOSUMDB 指定 Go checksum database 都无法访问到的模块时的时候,这三个环境变量就派上用场了

  • 它们三个的值都是一个以英文逗号 “,” 分割的模块路径前缀,匹配规则同 path.Match。

  • 其中 GOPRIVATE 较为特殊,它的值将作为 GONOPROXYGONOSUMDB 的默认值,所以再修改的时候重点关注 GOPRIVATE 即可。

  • 在使用上来讲,比如 GOPRIVATE=*.corp.example.com 表示所有模块路径以 corp.example.com 的下一级域名 (如 team1.corp.example.com) 为前缀的模块版本都将不经过 Go module proxyGo checksum database

需要注意匹配的时候,是不包括 corp.example.com 本身。

3.5.4 GOPROXY

  • 用于设置 Go 模块代理,其作用是使 Go 在拉取模块版本时,直接通过镜像站点来快速拉取

  • GOPROXY默认值是proxy.golang.org,国内无法访问,所以开启 Go Modules 必需设置镜像代理地址

  • 国内常用的镜像代理地址如下:

命令 功能
goproxy.io 一个全球代理为 Go 模块而生
mirrors.aliyun.com/goproxy 阿里镜像代理
goproxy.cn 七牛云赞助支持

3.6 Go module 导入本地包

3.6.1 处于同一项目下

  • 在一个项目(project)下我们是可以定义多个包(package)的。

  • 现在animal/main.go中调用了dog这个包。

1
2
3
4
5
animal
├── go.mod
├── main.go
└── dog
└── dog.go
  • animal/go.mod 的定义
1
2
3
module animal

go 1.13
  • 如何在 animal/main.go 中导入 dog 包?
1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"animal/dog" // 导入同一项目下的dog包
)
func main() {
dog.Walk()
fmt.Println("dog is walking!")
}

3.6.2 不在同一项目

  • 目录结构如下:
1
2
3
4
5
6
├── animal
│ ├── go.mod
│ └── main.go
└── dog
├── go.mod
└── dog.go
  • 因为这两个包不在同一个项目路径下,如果你想要导入本地包,并且这些包也没有发布到远程的github或其他代码仓库地址。

  • 这个时候我们就需要在go.mod文件中使用replace指令,进行本地包的指向

  • 在调用方,也就是animal/go.mod中按如下方式指定使用相对路径来寻找mypackage这个包

1
2
3
4
5
6
module moduledemo

go 1.13

require "animal" v0.0.0
replace "dog" => "../dog"
  • 此时,animal包就能找到本地的dog包的代码

微信公众号

扫一扫关注Joyo说公众号,共同学习和研究开发技术。

weixin-a

评论