go modules详解
[TOC]
go1.11开始,支持使用go modules管理依赖包. 刚加入的适合尝试过, 并不是特别好用, 特别是需要从老的vendor方式切换过来.但是golang包管理一直为人诟病, 官方尝试解决是值得肯定和期待的.
1.13即将正式加入此特性, 有必要对使用方法做一些整理, 避坑备用吧.
原理简介
并没有仔细研究过实现原理,2018年Go team 技术leader Russ Cox在博客发布数篇文章详细阐述了包管理技术方案: vgo(versioned Go), 具体参考vgo wiki, 其主要思想是: 、语义化包导入版本 Semantic Import Versioning, 最小版本选择(MVS)Minimal Version Selection, 可复现、可验证且验证过的构建(方式或过程)Reproducible, Verifiable, Verified Builds, Go模块定义Defining Go Modules等.
vgo和包管理
其中vgo是原型描述, 因其最小版本选择与之前的dep等包管理方法冲突, 随着vgo代码并入主分支,go modules
包管理机制成为官方工具, dep等包管理工具可能要退出江湖了.
语义化版本
细节可参考官网
- 版本格式:主版本号(major).次版本号(minor).修订号(patch)
- 主版本号:做了不兼容的 API修改
- 次版本号:向下兼容的功能性新增或改动
- 修订号:向下兼容的问题修正等
根据这种定义, 主版本号改动即视为不兼容之前大版本, 包管理机制也要使用不同主版本的包得到正确管理, 而次版本号以下的改动视为兼容, 会做合并
module定义
A module is a collection of Go packages stored in a file tree with a go.mod file at its root
模块是Go引用包的集合, 这些包保存在项目根目录的go.mod
文件中描述的文件树中.
原理上简单看, 从主模块构建(build list)表开始,递归遍历其下依赖, 升级到指定版本兼容的最新版本, 合并兼容的版本,最后得到final list
对build lists到操作, 具体定义和算法描述(比如upgrade一个module,upgrade所有modules)请参考原文:
- Construct the current build list.
- Upgrade all modules to their latest versions.
- Upgrade one module to a specific newer version.
- Downgrade one module to a specific older version.
- 实际项目使用会生成2个文件:
go.mod
,go.sum
(checksum)
使用实践
- 使用Go1.11以上版本, 请关注迭代过程中的改变
- 是否启用
go modules
可由环境变量GO111MODULE
控制(auto
,on
,off
), 默认为auto
: 项目在$GOPAH/src外会自动启动,否则兼容老的vendor方式 - 虽说项目不再依赖GOPATH环境变量, 但
go mod
下载的依赖包会cache到$GOPATH/pkg/mod下面, 按版本组织
$GOPATH/pkg/mod/github.com/sirupsen/logrus@v1.4.1
help 文档
目前go mod
使用多的是init
, tidy
等子命令
go help modules
go help mod
go help mod init|tidy|vendor
# go.mod文件帮助
go help go.mod
步骤
基本用法还是比较简单的, 以下为示例:
# 创建project
make hello-mod
touch main.go
# coding... and 导入包,
# go mod 初始化,会生成go.mod, 项目module名为hello-mod
go mod init hello-mod
go mod tidy
cat go.mod
# 或在代码中import,然后直接build或test
go build
# 查看当前包版本信息(包括间接引用),执行对应更新
go list -m -json all
# 带目录路径信息
go list -m -f={{.Dir}}
go get xxxx
go build ./...
# 或者直接run
go run main.go
也可使用vendor方式下载包到项目下.
go init hello-vendor
go mod vendor
go run main.go
最初Russ Cox打算彻底废弃vendor方式, 经社区讨论最终得以保留, 以兼容Go1.11之前版本.
避坑指南
看示例挺简单,实际上还是遇到不少坑的
代理
因为某些原因有些包无法直接下载, 需要代理,目前官方提供:
a module mirror for accelerating Go module downloads, an index for discovering new modules, and a global go.sum database for authenticating module content.
proxy.golang.org - a module mirror
sum.golang.org - an auditable checksum database to authenticate modules.
index.golang.org - an index which serves a feed of new module versions
status
应该还是beta
, 目前测试在国内可用
# 查看帮助
go help goproxy
# 环境变量设置
export GOPROXY=https://proxy.golang.org
# 类似的代理还有不少,下面这个在香港有服务器, 速度挺快:
export GOPROXY=https://goproxy.io
go.mod文件
go.mod
文件可保证构建过程可重现.
-
module
定义模块路径, 比如go init hello-mod
指定的hello-mod
,子目录下到包可以从此相对路径指定
# 例如之前示例的hello-mod,假设之下有子目录sub包,在代码中包含:
import "hello-mod/sub"
-
require
表示依赖包
replace example.com/some/dependency => example.com/some/dependency v1.2.3
-
exclude
排除某个特定包 -
replace
使用另外版本替代模块版本
这是个文本文件, 可手动修改:
- 如果依赖包源不可访问, 如若
golang.org/x/tools
无法访问, 可使用replace
- 获取特定版本, 或满足某些条件(大于、小于等关系), 在require下对应项修改版本号
-
module query:
go mod edit -require='github.com/xxx/xxxx@>=v1.2.3'
(老版是go mod -require
)
# 修改go.mod
require github.com/sirupsen/logrus <v1.4.2
# 重新运行会重现解析下载满足条件的依赖
go run main.go
# 查看发现版本降级为v1.4.1
cat go.mod
require github.com/sirupsen/logrus v1.4.2
go.sum文件
go.sum
为依赖包版本信息加密校验和(the expected cryptographic checksums)
- 运行go命令会进行校验
-
go mod verify
校验本地cache的包与go.sum是否匹配 - 最好与
go.mod
一起上传到repo中, 用于下载后进行依赖包校验
build 选项
- --mod=readonly, 不更新依赖, 如在ci过程中build
- --mod=vendor, 使用项目顶层目录下的vendor,需要确保vendor包完整准确, 因为依然会忽略其他目录下的vendor包
go run -mod=readonly main.go
升级或降级包
go list -m -json all
go list -m -f={{.Dir}} all
run go get -u # to use the latest minor or patch releases
run go get -u=patch # to use the latest patch releases
社区相关工具
-
github.com/goware/modvendor
, 帮助copy额外依赖文件
$ GO111MODULE=on go mod vendor
$ modvendor -copy="**/*.c **/*.h **/*.proto" -v
-
github.com/marwan-at-work/mod
, 帮助自动升级/降级模块版本, 修改import路径(如增加版本号)
Command line tool to automatically upgrade/downgrade major versions for modules
GO111MODULE=on go get github.com/marwan-at-work/mod/cmd/mod
# 更新项目自身版本
mod upgrade
cat go.mod
# module hello-vendor/v2
# import "hello-vendor/v2/sub"
# 升级依赖包版本
mod upgrade --mod-name=github.com/x/y
需要注意的是 --mod-name
如果是自身, 则go.mod中module不会升版本, 但是代码中引用自身子包的版本增加, 这是否算一个bug呢?
references
- 见链接