go modules 基础
引言
go 语言支持 go modules 特性已经快两年了,但很多项目由于种种原因还没有实施迁移。这几天想写一篇文章,全面的阐述一下 go modules 特性,使得读者快速掌握其要点,从而低成本的将项目的包管理迁移到 go modules。
考虑到实战部分的内容较多,将计划的一篇文章拆分为多篇来呈现,一层层的慢慢揭开 go modules 的神秘面纱。本文为开篇,主要介绍 go modules 的基础知识。
go modules 背景
在 go1.5 发布后的若干年,gophers 把注意力都集中在如何利用 vendor 机制来解决包依赖问题,从手工添加依赖到 vendor、手工更新依赖,到一众包依赖管理工具的诞生,比如: govendor、glide 以及号称准官方工具的 dep,都在努力地尝试着按照当时的主流思路来解决诸如“钻石型依赖”等难题。
正当 gophers 认为 dep
将“顺理成章”地转正为 go toolchain
一部分的时候,vgo
横空出世,并通过对 “Semantic Import Versioning” 和 ”Minimal Version Selected” 的设定,在原 go tools
上简单快速地实现了 go
原生的包依赖管理方案 这个 vgo
就是 go modules
的前身。
go modules 定义
通常,我们会在一个 go 代码库中创建一组包,而代码库的路径会作为包的导入路径,比如 github.com/agiledragon/gomonkey
。在 go1.11 版本中,给在同一代码库下面的这样一组包定义了一个新的概念叫 module
,module
根目录一般叫 module root
目录。
一个代码库只有一个 module
吗?一般情况是这样,但也可以有多个 module
。
go mod
可以看作是 go modules 的简称。
go mod 配置
go1.11 在引入 go modules 特性时,增加了一个特性开关(环境变量)GO111MODULE
,对应的值有三个:auto,on,off。
下面详细解释一下这三个值的含义:
- 当 GO111MODULE 的值为
off
时,go modules 机制关闭,go 编译器会始终使用GOPATH mode
,即无论要构建的源码目录是否在 GOPATH 路径下,go 编译器都会在传统的 GOPATH 和 vendor 目录下搜索目标程序依赖的包; - 当 GO111MODULE 的值为
on
时,go modules 机制始终开启,与off
相反,go 编译器会始终使用module-aware mode
,即无论要构建的源码目录是否在 GOPATH 路径下,go 编译器都不会在传统的 GOPATH 和 vendor 目录下搜索目标程序依赖的包,而是在 go mod 命令的缓存目录($GOPATH/pkg/mod)下搜索对应版本的依赖包; - 当 GO111MODULE 的值为
auto
时(默认值),使用 GOPATH mode还是 module-aware mode,取决于要构建的源码目录所在位置以及是否包含 go.mod 文件:a. 对于 go1.11 和 go1.12 版本,如果要构建的源码目录不在以 GOPATH/src 为根的目录体系下,且包含go.mod文件(两个条件缺一不可),那么使用 module-aware mode,否则使用传统的 GOPATH mode;b. 对于 go1.13及以上版本,如果要构建的源码目录包含 go.mod 文件,那么使用 module-aware mode,否则使用传统的 GOPATH mode。
GOPATH mode & GOPATH 环境变量
使用 module-aware mode 后,用户可以不用再配置 GOPATH 环境变量了,但细心的读者可能已经发现 go mod 命令的缓存目录为 $GOPATH/pkg/mod
。
那到底还要不要配置GOPATH 环境变量了?
从 go1.8 版本开始,GOPATH 有了默认值,即 $HOME/go
。如果用户配置了 GOPATH 环境变量,则覆盖默认值。所以,在 module-aware mode 下,不管用户是否配置了 GOPATH 环境配置,$GOPATH
都有确定的值,无非是否为默认值的问题,所以 go mod 命令的缓存目录 $GOPATH/pkg/mod
是有效的。
综上,使用 go modules 机制管理依赖包后,我们弃用的是 GOPATH mode,而不是 GOPATH 环境变量,当然你也可以不设置 GOPATH 环境变量,go 编译器直接使用 GOPATH 默认值也 OK。
go mod 命令
go 编译器提供了 go mod 命令来管理依赖包。
go mod 主要包括下命令:
命令 | 说明 |
---|---|
download | download modules to local cache(下载依赖包) |
edit | edit go.mod from tools or scripts(编辑go.mod) |
graph | print module requirement graph (打印模块依赖图) |
init | initialize new module in current directory(在当前目录初始化mod) |
tidy | add missing and remove unused modules(拉取缺少的模块,移除不用的模块) |
vendor | make vendored copy of dependencies(将依赖复制到vendor下) |
verify | verify dependencies have expected content (验证依赖是否正确) |
why | explain why packages or modules are needed(解释为什么需要依赖) |
go mod 文件
go mod 文件主要包括 go.mod 和 go.sum,这两个文件在 module root
目录下,都要提交到代码库。
使用 go mod 管理依赖后,在 module root
目录下:
- 运行 go mod init 命令会自动生成 go.mod 文件,该文件记录当前代码工程的所有依赖库及版本,可以通过 go 的命令来维护
- 运行 go build 或 go run 命令会自动生成 go.sum 文件,该文件记录每个依赖库特定版的哈希值,可以确保下次获取的第三方依赖与本次相同
go.mod 文件提供了 module,require,replace 和 exclude 等命令:
- module 语句指定包的名字(路径)
- require 语句指定依赖的模块列表
- replace 语句可以替换某个 require 指定的模块
- exclude 语句可以忽略某个 require 指定的模块
goconvey 测试框架的 go.mod 文件:
module github.com/smartystreets/goconvey
require (
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
github.com/jtolds/gls v4.20.0+incompatible
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384
)
goconvey 测试框架的 go.sum 文件:
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
go proxy
顾名思义,go proxy 是 go 语言的代理,一般是配合 go modules 使用,可以使用户方便且快速的获取第三方包,尤其是 go 官方的一些包。
作者本机配置的 go proxy 为:
bogon:~ zhangxiaolong$ echo $GOPROXY
https://goproxy.cn,direct
go 团队包管理从传统 GOPATH mode 迁移到 module-aware mode 后,代码库中没有了 vendor 目录,而增加了 go.mod 文件和 go.sum 文件。在 CI 流水线上编译版本时,需要实时获取依赖,但从外网下载所有依赖的第三方包有一些问题:
- 很慢(外网速度比内网速度慢太多)
- 不方便(有的环境无法上外网)
- 下载失败(墙的原因)
此时,在公司内网搭建一个私有的 go proxy 就使得这些问题迎刃而解。
小结
本文主要介绍了 go modules 的基础知识,包括背景、定义、配置、模式、命令、文件和代理等内容,希望对读者感性认识 go modules 特性有一定的帮助。
下一篇文章将开启实战部分,作者将使用 go1.14 版本,通过一些小案例来初步解析 go modules 机制,名字叫啥好呢?
“什么天长地久,只是随便说说,你爱我那一点,你也说不出口......”。突然,听到了张震岳的成名曲《爱的初体验》,要不下一篇文章就叫 《go modules 初体验》?