Go module详细介绍

914 阅读6分钟

我正在参加「掘金·启航计划」

一、概述

大多数语言都有“依赖”、“包”等概念,Go语言的依赖处理经历了几次变革。

最早的时候,Go所依赖的所有的第三方库都放在GOPATH这个目录下面从v1.5开始引入vendor模式,如果项目目录下有vendor目录,那么go工具链会优先使用vendor内的包进行编译、测试等

v1.11开始,引入了Go Module 作为依赖解决方案,到v1.14宣布Go Module已经可以用于生产环境,到v1.16版本开始Go Module默认开启。

二、GO PATH介绍

首先从 Golang 的环境配置说起,安装好之后,通过命令行来看下当前 Golang 的环境配置。

go env

有两个比较重要的环境变量:

  • GOROOT:Golang 安装目录的路径,默认编译器程序和系统包,也可以放置三方包。
  • GOPATH:当前工作目录,放置编译后二进制和 import 包时的搜索路径,一般有三个目录: bin、pkg、src。
    • bin:用来存放编译后的可执行文件
    • pkg:存储预编译的目标文件,以加快程序的后续编译速度。
    • src:存储所有.go文件或源代码。在编写 Go 应用程序,程序包和库时,一般会以$GOPATH/src/github.com/foo/bar的路径进行存放。

因此在使用 GOPATH 模式下,我们需要将应用代码存放在固定的$GOPATH/src目录下,并且如果执行go get来拉取外部依赖会自动下载并安装到$GOPATH目录下。

GOPATH模式的弊端

在 GOPATH 的 $GOPATH/src 下进行 .go 文件或源代码的存储,我们可以称其为 GOPATH 的模式,这个模式拥有一些弊端。

  • A. 无版本控制概念. 在执行go get的时候,你无法传达任何的版本信息的期望,也就是说你也无法知道自己当前更新的是哪一个版本,也无法通过指定来拉取自己所期望的具体版本。
  • B.无法同步一致第三方版本号. 在运行 Go 应用程序的时候,你无法保证其它人与你所期望依赖的第三方库是相同的版本,也就是说在项目依赖库的管理上,你无法保证所有人的依赖版本都一致。
  • C.无法指定当前项目引用的第三方版本号. 你没办法处理 v1、v2、v3 等等不同版本的引用问题,因为 GOPATH 模式下的导入路径都是一样的,都是github.com/foo/bar。

Go 语言官方从 Go1.11 起开始推进 Go modules(前身vgo),Go1.13 起不再推荐使用 GOPATH 的使用模式,Go modules 也渐趋稳定,因此新项目也没有必要继续使用GOPATH模式。

三、go module

3.1 概念

module是一个相关Go包的集合,它是源代码更替和版本控制的单元。模块由源文件形成的go.mod文件的根目录定义,包含go.mod文件的目录也被称为模块根。moudles取代旧的的基于GOPATH方法来指定在工程中使用哪些源文件或导入包。模块路径是导入包的路径前缀,go.mod文件定义模块路径,并且列出了在项目构建过程中使用的特定版本。

优势
  • 首先,研发者应该能够在任何目录下工作,而不仅仅是在GOPATH指定的目录。
  • 可以安装依赖包的指定版本,而不是只能从master分支安装最新的版本。
  • 可以导入同一个依赖包的多个版本。当我们老项目使用老版本,新项目使用新版本时会非常有用。
  • 要有一个能够罗列当前项目所依赖包的列表。这个的好处是当我们发布项目时不用同时发布所依赖的包。Go能够根据该文件自动下载对应的包。

3.2 go module相关命令

命令介绍
go mod init初始化项目依赖,生成go.mod文件
go mod download根据go.mod文件下载依赖
go mod tidy比对项目文件中引入的依赖与go.mod进行比对
go mod graph输出依赖关系图
go mod edit编辑go.mod文件
go mod vendor将项目的所有依赖导出至vendor目录
go mod verify检验一个依赖包是否被篡改过
go mod why解释为什么需要某个依赖

3.3 参数配置

  • GO111MODULE

Go语言提供了 GO111MODULE 这个环境变量来作为 Go modules 的开关,其允许设置以下参数:

参数说明
auto只要项目包含了 go.mod 文件的话启用 Go modules,目前在 Go1.11 至 Go1.14 中仍然是默认值。
on启用 Go modules,推荐设置,将会是Go1.16版本之后的默认值。
off禁用 Go modules,不推荐设置。
  • GOPROXY

这个环境变量主要是用于设置 Go 模块代理(Go module proxy),其作用是用于使 Go 在后续拉取模块版本时能够脱离传统的 VCS 方式,直接通过镜像站点来快速拉取。

GOPROXY 的默认值是:https://proxy.golang.org,direct,由于某些原因国内无法正常访问该地址,所以我们通常需要配置一个可访问的地址。目前社区使用比较多的有两个https://goproxy.cnhttps://goproxy.io,当然如果你的公司有提供GOPROXY地址那么就直接使用。

设置GOPAROXY的命令如下:

go env -w GOPROXY=https://goproxy.cn,direct

GOPROXY 允许设置多个代理地址,多个地址之间需使用英文逗号 “,” 分隔。最后的 “direct” 是一个特殊指示符,用于指示 Go 回源到源地址去抓取(比如 GitHub 等)。当配置有多个代理地址时,如果第一个代理地址返回 404 或 410 错误时,Go 会自动尝试下一个代理地址,当遇见 “direct” 时触发回源,也就是回到源地址去抓取。

  • GOPRIVATE

设置了GOPROXY 之后,go 命令就会从配置的代理地址拉取和校验依赖包。当我们在项目中引入了非公开的包(公司内部git仓库或 github 私有仓库等),此时便无法正常从代理拉取到这些非公开的依赖包,这个时候就需要配置 GOPRIVATE 环境变量。GOPRIVATE用来告诉 go 命令哪些仓库属于私有仓库,不必通过代理服务器拉取和校验。

GOPRIVATE 的值也可以设置多个,多个地址之间使用英文逗号 “,” 分隔。我们通常会把自己公司内部的代码仓库设置到 GOPRIVATE 中,例如:

$ go env -w GOPRIVATE="git.mycompany.com"

这样在拉取以git.mycompany.com为路径前缀的依赖包时就能正常拉取了。

此外,如果公司内部自建了 GOPROXY 服务,那么我们可以通过设置 GONOPROXY=none,允许通内部代理拉取私有仓库的包。

3.4 配置文件

  • go.mod 文件

在初始化项目时,会生成一个 go.mod 文件,是启用了 Go modules 项目所必须的最重要的标识,同时也是 GO111MODULE 值为 auto 时的识别标识,它描述了当前项目(也就是当前模块)的元信息,每一行都以一个动词开头。

示例文件

module github.com/eddycjy/module-repo
 
 
go 1.13
 
 
require (
    example.com/apple v0.1.2
    example.com/banana v1.2.3
    example.com/banana/v2 v2.3.4
    example.com/pear // indirect
    example.com/strawberry // incompatible
)
 
 
exclude example.com/banana v1.2.4
replace example.com/apple v0.1.2 => example.com/fried v0.1.0 
replace example.com/banana => example.com/fish

说明

  • module:用于定义当前项目的模块路径。
  • go:用于标识当前模块的 Go 语言版本,值为初始化模块时的版本,目前来看还只是个标识作用。
  • require:用于设置一个特定的模块版本。
  • exclude:用于从使用中排除一个特定的模块版本。
  • replace:用于将一个模块版本替换为另外一个模块版本。

另外你会发现 example.com/pear 的后面会有一个 indirect 标识,indirect 标识表示该模块为间接依赖,也就是在当前应用程序中的 import 语句中,并没有发现这个模块的明确引用,有可能是你先手动 go get 拉取下来的,也有可能是你所依赖的模块所依赖的,情况有好几种。

  • go.sum 文件

在第一次拉取模块依赖后,会发现多出了一个 go.sum 文件,其详细罗列了当前项目直接或间接依赖的所有模块版本,并写明了那些模块版本的 SHA-256 哈希值以备 Go 在今后的操作中保证项目所依赖的那些模块版本不会被篡改。

github.com/eddycjy/mquote v0.0.1 h1:4QHXKo7J8a6J/k8UA6CiHhswJQs0sm2foAQQUq8GFHM=
github.com/eddycjy/mquote v0.0.1/go.mod h1:ZtlkDs7Mriynl7wsDQ4cU23okEtVYqHwl7F1eDh4qPg=
github.com/eddycjy/mquote/module/tour v0.0.1 h1:cc+pgV0LnR8Fhou0zNHughT7IbSnLvfUZ+X3fvshrv8=
github.com/eddycjy/mquote/module/tour v0.0.1/go.mod h1:8uL1FOiQJZ4/1hzqQ5mv4Sm7nJcwYu41F3nZmkiWx5I=
...

可以看到一个模块路径可能有如下两种:

github.com/eddycjy/mquote v0.0.1 h1:4QHXKo7J8a6J/k8UA6CiHhswJQs0sm2foAQQUq8GFHM=
github.com/eddycjy/mquote v0.0.1/go.mod h1:ZtlkDs7Mriynl7wsDQ4cU23okEtVYqHwl7F1eDh4qPg=

h1 hash 是 Go modules 将目标模块版本的 zip 文件开包后,针对所有包内文件依次进行 hash,然后再把它们的 hash 结果按照固定格式和算法组成总的 hash 值。

而 h1 hash 和 go.mod hash 两者,要不就是同时存在,要不就是只存在 go.mod hash。那什么情况下会不存在 h1 hash 呢,就是当 Go 认为肯定用不到某个模块版本的时候就会省略它的 h1 hash,就会出现不存在 h1 hash,只存在 go.mod hash 的情况。

3.5 全局缓存

Go module 会把下载到本地的依赖包会以类似下面的形式保存在 $GOPATH/pkg/mod目录下,每个依赖包都会带有版本号进行区分,这样就允许在本地存在同一个包的多个不同版本。

mod
├── cache
├── cloud.google.com
├── github.com
    	└──q1mi
          ├── hello@v0.0.0-20210218074646-139b0bcd549d
          ├── hello@v0.1.1
          └── hello@v0.1.0
...

如果想清除所有本地已缓存的依赖包数据,可以执行 go clean -modcache 命令。

3.6 go get 升级

Golang 包的版本号管理一般都是:主版本、次要版本、修订版本(x.y.z)这种方式。

使用 go get 进行升级有如下规则:

  • go get -u 将会升级到最新的次要版本或者修订版本。
  • go get -u=patch 将会升级到最新的修订版本。
  • go get package@version 将会升级到指定的版本号。
  • go get 如果有版本的更改,那么 go.mod 文件也会更改。

附参考文章链接

www.liwenzhou.com/posts/Go/11…

blog.csdn.net/u011069013/…