首页 > temp > 简明python教程 >
-
Go Modules 终极入门
Go modules 是 Go 语言中正式官宣的项目依赖解决方案,Go modules(前身为vgo)于 Go1.11 正式发布,在 Go1.14 已经准备好,并且可以用在生产上(ready for production)了,Go 官方也鼓励所有用户从其他依赖项管理工具迁移到 Go modules。
而 Go1.14,在今天终于正式发布,Go 官方亲自 “喊” 你来用:
因此在今天这篇文章中,我将给大家带来 Go modules 的 “终极入门”,欢迎大家一起共同探讨。
什么是 Go Modules
Go modules 是 Go 语言的依赖解决方案,发布于 Go1.11,成长于 Go1.12,丰富于 Go1.13,正式于 Go1.14 推荐在生产上使用。
Go moudles 目前集成在 Go 的工具链中,只要安装了 Go,自然而然也就可以使用 Go moudles 了,而 Go modules 的出现也解决了在 Go1.11 前的几个常见争议问题:
-
Go 语言长久以来的依赖管理问题。 -
“淘汰”现有的 GOPATH 的使用模式。 -
统一社区中的其它的依赖管理工具(提供迁移功能)。
GOPATH 的那些点点滴滴
我们有提到 Go modules 的解决的问题之一就是“淘汰”掉 GOPATH,但是 GOPATH 又是什么呢,为什么在 Go1.11 前就使用 GOPATH,而 Go1.11 后就开始逐步建议使用 Go modules,不再推荐 GOPATH 的模式了呢?
GOPATH 是什么
我们先看看第一个问题,GOPATH 是什么,我们可以输入如下命令查看:
$ go env
GOPATH="/Users/eddycjy/go"
...
我们输入go env
命令行后可以查看到 GOPATH 变量的结果,我们进入到该目录下进行查看,如下:
go
├── bin
├── pkg
└── src
├── github.com
├── golang.org
├── google.golang.org
├── gopkg.in
....
GOPATH目录下一共包含了三个子目录,分别是:
-
bin:存储所编译生成的二进制文件。 -
pkg:存储预编译的目标文件,以加快程序的后续编译速度。 -
src:存储所有 .go
文件或源代码。在编写 Go 应用程序,程序包和库时,一般会以$GOPATH/src/github.com/foo/bar
的路径进行存放。
因此在使用 GOPATH 模式下,我们需要将应用代码存放在固定的$GOPATH/src
目录下,并且如果执行go get
来拉取外部依赖会自动下载并安装到$GOPATH
目录下。
为什么弃用 GOPATH 模式
在 GOPATH 的 $GOPATH/src
下进行 .go
文件或源代码的存储,我们可以称其为 GOPATH 的模式,这个模式,看起来好像没有什么问题,那么为什么我们要弃用呢,参见如下原因:
-
GOPATH 模式下没有版本控制的概念,具有致命的缺陷,至少会造成以下问题: -
在执行 go get
的时候,你无法传达任何的版本信息的期望,也就是说你也无法知道自己当前更新的是哪一个版本,也无法通过指定来拉取自己所期望的具体版本。 -
在运行 Go 应用程序的时候,你无法保证其它人与你所期望依赖的第三方库是相同的版本,也就是说在项目依赖库的管理上,你无法保证所有人的依赖版本都一致。 -
你没办法处理 v1、v2、v3 等等不同版本的引用问题,因为 GOPATH 模式下的导入路径都是一样的,都是 github.com/foo/bar
。
-
-
Go 语言官方从 Go1.11 起开始推进 Go modules(前身vgo),Go1.13 起不再推荐使用 GOPATH 的使用模式,Go modules 也渐趋稳定,因此新项目也没有必要继续使用GOPATH模式。
在 GOPATH 模式下的产物
Go1 在 2012 年 03 月 28 日发布,而 Go1.11 是在 2018 年 08 月 25 日才正式发布(数据来源:GitHub Tag),在这个空档的时间内,并没有 Go modules 这一个东西,最早期可能还好说,因为刚发布,用的人不多,所以没有明显暴露,但是后期 Go 语言使用的人越来越多了,那怎么办?
这时候社区中逐渐的涌现出了大量的依赖解决方案,百花齐放,让人难以挑选,其中包括我们所熟知的 vendor 目录的模式,以及曾经一度被认为是“官宣”的 dep 的这类依赖管理工具。
但为什么 dep 没有正在成为官宣呢,其实是因为随着 Russ Cox 与 Go 团队中的其他成员不断深入地讨论,发现 dep 的一些细节似乎越来越不适合 Go,因此官方采取了另起 proposal 的方式来推进,其方案的结果一开始先是释出 vgo(Go modules 的前身,知道即可,不需要深入了解),最终演变为我们现在所见到的 Go modules,也在 Go1.11 正式进入了 Go 的工具链。
因此与其说是 “在 GOPATH 模式下的产物”,不如说是历史为当前提供了重要的教训,因此出现了 Go modules。
Go Modules基本使用
在初步了解了 Go modules 的前世今生后,我们正式进入到 Go modules 的使用,首先我们将从头开始创建一个 Go modules 的项目(原则上所创建的目录应该不要放在 GOPATH 之中)。
所提供的命令
在 Go modules 中,我们能够使用如下命令进行操作:
命令 | 作用 |
---|---|
go mod init | 生成 go.mod 文件 |
go mod download | 下载 go.mod 文件中指明的所有依赖 |
go mod tidy | 整理现有的依赖 |
go mod graph | 查看现有的依赖结构 |
go mod edit | 编辑 go.mod 文件 |
go mod vendor | 导出项目所有的依赖到vendor目录 |
go mod verify | 校验一个模块是否被篡改过 |
go mod why | 查看为什么需要依赖某模块 |
所提供的环境变量
在 Go modules 中有如下常用环境变量,我们可以通过 go env
命令来进行查看,如下:
$ go env
GO111MODULE="auto"
GOPROXY="https://proxy.golang.org,direct"
GONOPROXY=""
GOSUMDB="sum.golang.org"
GONOSUMDB=""
GOPRIVATE=""
...
GO111MODULE
Go语言提供了 GO111MODULE 这个环境变量来作为 Go modules 的开关,其允许设置以下参数:
-
auto:只要项目包含了 go.mod 文件的话启用 Go modules,目前在 Go1.11 至 Go1.14 中仍然是默认值。 -
on:启用 Go modules,推荐设置,将会是未来版本中的默认值。 -
off:禁用 Go modules,不推荐设置。
GO111MODULE 的小历史
你可能会留意到 GO111MODULE 这个名字比较“奇特”,实际上在 Go 语言中经常会有这类阶段性的变量, GO111MODULE 这个命名代表着Go语言在 1.11 版本添加的,针对 Module 的变量。
像是在 Go1.5 版本的时候,也发布了一个系统环境变量 GO15VENDOREXPERIMENT,作用是用于开启 vendor 目录的支持,当时其默认值也不是开启,仅仅作为 experimental。其随后在 Go1.6 版本时也将默认值改为了开启,并且最后作为了official,GO15VENDOREXPERIMENT 系统变量就退出了历史舞台。
而未来 GO111MODULE 这一个系统环境变量也会面临这个问题,也会先调整为默认值为 on(曾经在Go1.13想想改为 on,并且已经合并了 PR,但最后因为种种原因改回了 auto),然后再把 GO111MODULE 的支持给去掉,我们猜测应该会在 Go2 将 GO111MODULE 给去掉,因为如果直接去掉 GO111MODULE 的支持,会存在兼容性问题。
GOPROXY
这个环境变量主要是用于设置 Go 模块代理(Go module proxy),其作用是用于使 Go 在后续拉取模块版本时能够脱离传统的 VCS 方式,直接通过镜像站点来快速拉取。
GOPROXY 的默认值是:https://proxy.golang.org,direct
,这有一个很严重的问题,就是 proxy.golang.org
在国内是无法访问的,因此这会直接卡住你的第一步,所以你必须在开启 Go modules 的时,同时设置国内的 Go 模块代理,执行如下命令:
$ go env -w GOPROXY=https://goproxy.cn,direct
GOPROXY 的值是一个以英文逗号 “,” 分割的 Go 模块代理列表,允许设置多个模块代理,假设你不想使用,也可以将其设置为 “off” ,这将会禁止 Go 在后续操作中使用任何 Go 模块代理。
direct是什么
而在刚刚设置的值中,我们可以发现值列表中有 “direct” 标识,它又有什么作用呢?
实际上 “direct” 是一个特殊指示符,用于指示 Go 回源到模块版本的源地址去抓取(比如 GitHub 等),场景如下:当值列表中上一个 Go 模块代理返回 404 或 410 错误时,Go 自动尝试列表中的下一个,遇见 “direct” 时回源,也就是回到源地址去抓取,而遇见 EOF 时终止并抛出类似 “invalid version: unknown revision...” 的错误。
GOSUMDB
它的值是一个 Go checksum database,用于在拉取模块版本时(无论是从源站拉取还是通过 Go module proxy 拉取)保证拉取到的模块版本数据未经过篡改,若发现不一致,也就是可能存在篡改,将会立即中止。
GOSUMDB 的默认值为:sum.golang.org
,在国内也是无法访问的,但是 GOSUMDB 可以被 Go 模块代理所代理(详见:Proxying a Checksum Database)。
因此我们可以通过设置 GOPROXY 来解决,而先前我们所设置的模块代理 goproxy.cn
就能支持代理 sum.golang.org
,所以这一个问题在设置 GOPROXY 后,你可以不需要过度关心。
另外若对 GOSUMDB 的值有自定义需求,其支持如下格式:
-
格式 1: <SUMDB_NAME>+<PUBLIC_KEY>
。 -
格式 2: <SUMDB_NAME>+<PUBLIC_KEY> <SUMDB_URL>
。
也可以将其设置为“off”,也就是禁止 Go 在后续操作中校验模块版本。
GONOPROXY/GONOSUMDB/GOPRIVATE
这三个环境变量都是用在当前项目依赖了私有模块,例如像是你公司的私有 git 仓库,又或是 github 中的私有库,都是属于私有模块,都是要进行设置的,否则会拉取失败。
更细致来讲,就是依赖了由 GOPROXY 指定的 Go 模块代理或由 GOSUMDB 指定 Go checksum database 都无法访问到的模块时的场景。
而一般建议直接设置 GOPRIVATE,它的值将作为 GONOPROXY 和 GONOSUMDB 的默认值,所以建议的最佳姿势是直接使用 GOPRIVATE。
并且它们的值都是一个以英文逗号 “,” 分割的模块路径前缀,也就是可以设置多个,例如:
$ go env -w GOPRIVATE="git.example.com,github.com/eddycjy/mquote"
设置后,前缀为 git.xxx.com 和 github.com/eddycjy/mquote 的模块都会被认为是私有模块。
如果不想每次都重新设置,我们也可以利用通配符,例如:
$ go env -w GOPRIVATE="*.example.com"
这样子设置的话,所有模块路径为 example.com 的子域名(例如:git.example.com)都将不经过 Go module proxy 和 Go checksum database,需要注意的是不包括 example.com 本身。
开启 Go Modules
目前 Go modules 并不是默认开启,因此Go语言提供了 GO111MODULE 这个环境变量来作为 Go modules 的开关,其允许设置以下参数:
-
auto:只要项目包含了 go.mod 文件的话启用 Go modules,目前在Go1.11至 Go1.14 中仍然是默认值。 -
on:启用 Go modules,推荐设置,将会是未来版本中的默认值。 -
off:禁用 Go modules,不推荐设置。
如果你不确定你当前的值是什么,可以执行go env
命令,查看结果:
$ go env
GO111MODULE="off"
...
如果需要对 GO111MODULE 的值进行变更,推荐通过go env
命令进行设置:
$ go env -w GO111MODULE=on
但是需要注意的是如果对应的系统环境变量有值了(进行过设置),会出现如下警告信息:warning: go env -w GO111MODULE=... does not override conflicting OS environment variable
。
又或是可以通过直接设置系统环境变量(写入对应的 .bash_profile 文件亦可)来实现这个目的:
$ export GO111MODULE=on
初始化项目
在完成 Go modules 的开启后,我们需要创建一个示例项目来进行演示,执行如下命令:
$ mkdir -p $HOME/eddycjy/module-repo
$ cd $HOME/eddycjy/module-repo
然后进行 Go modules 的初始化,如下:
$ go mod init github.com/eddycjy/module-repo
go: creating new go.mod: module github.com/eddycjy/module-repo
在执行 go mod init
命令时,我们指定了模块导入路径为 github.com/eddycjy/module-repo
。接下来我们在该项目根目录下创建 main.go 文件,如下:
package main
import (
"fmt"
"github.com/eddycjy/mquote"
)