原文地址

https://github.com/anqiansong/golang-notes/blob/main/go-module.md

github

https://github.com/anqiansong

go module

在go1.16版本公布后,go module由原来的默认值 auto 变为 on 了,这意味着后续开发中,go更举荐用go module 模式开发,而不是gopath模式开发了。

在之前,我也是大多数以go module模式进行golang开发,但至今对其不相熟,仅仅停留在:他人是这样做的,我跟着做就是了 ,这都算不上会应用go module, 更不用说相熟或者精通了;在此之前,我会存在这些疑难:

  • go mod文件中定义的各项内容代表什么;
  • 除了常见的 require 、偶然见 replace 关键字外,excluderetract (1.16)这些关键字是什么,怎么用;
  • go mod文件语法格局是什么,目前除了跟着他人写,如同也不明确其中的语法
  • github.com/tal-tech/go-zero v1.1.5github.com/antlr/antlr4 v0.0.0-20210105212045-464bcbc32de2
    google.golang.org/protobuf v1.25.0 // indirect 等格局别离代表什么,为什么有的还有
    // indirect 润饰;
  • go.mod上面为什么有一个go.sum,其有什么作用;
  • ...

不晓得有多少人和我一样,对go module的理解微不足道。

最近,带着这些纳闷,去学习了官网的参考手册,这些纳闷就引刃而解了。

project

在正式进入module介绍前,有必要首先理解一下project和module的关系,置信开发过Android或者Java的同学对module有十分好的了解,艰深的讲,一个project能够有多个module组成,module能够作为独立
的project被别的project作为依赖援用,如下golang工程 demo 中就蕴含了 foobar 两个module

demo├── bar│   └── go.mod└── foo    └── go.mod

module介绍

go module(以下称:module、模块、工程模块)
是golang中已公布版本的package的汇合,是Go治理依赖的一种形式,相似Android中的Gradle,Java中的Maven,当然,他们的治理模式必定是天壤之别,然而目标都是统一的,对依赖进行治理。

在go.mod中,其蕴含了main module的module门路、module依赖及其关联信息(版本等),如果一个工程模块须要以
go module mode(module模式)开发,在工程模块的根目录下必须蕴含 go.mod 文件。

module path(module门路)

module门路是一个工程模块中的名称,在go.mod中以 module 命令申明,其也是工程模块中package import的前缀,咱们来看一下 demo/foo 下的module门路:

$ cat demo/foo/go.mod
module github.com/foogo 1.16require github.com/tal-tech/go-zero

github.com/foo 为main module foo 模块的模块门路,github.com/tal-tech/go-zero 也是module path, 他们也是foo中package
import的前缀,我在foo下的base包下增加了一个 Echo 函数,而后在 main.go 中调用,咱们察看一下其package import的前缀

目录树

foo├── base│   └── base.go├── go.mod└── main.go

main.go

package mainimport "github.com/foo/base" // github.com/foo 为 module pathimport "fmt"func main() {    msg := base.Echo("go-zero")    fmt.Println(msg)}

module门路次要作用是形容一个工程模块的作用是什么,在哪里能够找到,因而,module path的组成元素就蕴含了

  • repo 门路
  • repo 文件夹
  • 版本

其表现形式如: {{.repo_url}}/{{.nameOfDir}}/{{.version}}, 示例:github.com/tal-tech/go-zero/v2github.com/tal-tech
明确告知了 repo 的门路,go-zero 即为repo的文件夹,v2 即为版本号 版本个别 v1 个别都默认不写了,只有大于 v1 时则须要用来辨别。

版本号

这里的版本号是指go.mod文件中依赖module的版本,这和上文的 {{.version}} 会有关联,如下示例中的 v1.1.5 即为本次所说的module依赖版本号。

module github.com/foogo 1.16require github.com/tal-tech/go-zero v1.1.5

版本号组成及规定

版本号由 major version(次要版本)、 minor version(主要版本)、 patch version(订正版本)组成;

  • major version:指module中内容作了向后不兼容的更改后,则版本会upgrade,在此版本号upgrade时,minor versionpatch version 要归零;
  • minor version:指在新的性能公布(features)或者作了向后兼容的内容变更后,此版本号会upgrade,在此版本号upgrade时,patch version 要归零;
  • patch version:指有bug修复或者性能优化时,此版本号能够进行upgrade,在有pre-release公布需要时也能够变更此版本号

示例:v0.0.0v1.2.3v1.2.10-pre

如果一个版本的 major version0 或者 patch-version 有版本后缀(如:pre),则认为这个版本是不稳固的,如v0.2.0v1.5.0-prev1.1.3-beta
更多对于version语义定义能够参考《Semantic Versioning 2.0.0》

Golang除此之外,还能够用一些标记、分支来代表某一个版本,如:github.com/tal-tech/go-zero 39540e21d249e91f89d96d015a6e3795cfb2be44
github.com/tal-tech/go-zero v1.1.6-0.20210303091609-39540e21d249
github.com/tal-tech/go-zero@master

其中github.com/tal-tech/go-zero v1.1.6-0.20210303091609-39540e21d249 这种版本在golang外面称为 Pseudo-versions
(伪版本),其没有齐全遵循上文中的版本规定,伪版本由三个局部组成:

  • 版本根本前缀( vX.Y.Z-0vX.0.0 ),如 v1.1.6-0
  • 工夫戳:即revision的创立工夫戳,如 20210303091609
  • revision标识符,如 39540e21d249

依据根本前缀的不同,伪版本会有三种模式:

  • vX.0.0-yyyymmddhhmmss-abcdefabcdef :在没有 release 版本时应用
  • vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef :当根本版本是预公布版本时应用
  • vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdef :当 release 版本相似 vX.Y.Z
    时应用,如 github.com/tal-tech/go-zero v1.1.6-0.20210303091609-39540e21d249release 版本为 v1.1.5
伪版本不须要手动输出,其会在执行局部go命令获取某一次提交记录版本(revision)的代码作为依赖时,会主动将其转换为伪版本

上文中的 github.com/tal-tech/go-zero v1.1.6-0.20210303091609-39540e21d249 则是在执行 go get github.com/tal-tech/go-zero 39540e21d249e91f89d96d015a6e3795cfb2be44 后主动转换的后果

版本后缀

为了向前兼容,如果 major version 降级到2时,模块门路必须要指定一个版本后缀 v2(其数值放弃和版本中的 major version 的值统一),在 major version 小于2时,不容许应用版本后缀。

咱们来看一个例子,我在 demo/foo 模块工程中用到了 miniRedis 这个库,该库的 major version 曾经降级到2了,假如我在go.mod中援用如下版本会怎么样?

module github.com/foogo 1.16// 正确引入// require github.com/alicebob/miniredis/v2 v2.14.1 // 谬误引入require github.com/alicebob/miniredis v2.14.1
invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2

下面是go mod应用时的场景,咱们来看一下当main module的release版本升级到 v2.x.x 时(前提先发一个v1.0.0的版本),module path没有增加版本后缀,在另一个module去应用它会有什么成果:
示例module foo
foo 工程目前有release版本

  • v1.0.0
  • v2.0.1
  • ...

应用module的工程 bar

  • 未增加版本后缀前

foo 工程目录树

foo├── echo│   └── echo.go└── go.mod

module path 为 github.com/anqiansong/foo

module github.com/anqiansong/foogo 1.16

在工程 bar 的go.mod应用 v2.0.0 版本

require github.com/anqiansong/foo v2.0.0

你会发现报错内容为

 require github.com/anqiansong/foo: reading https://goproxy.cn/github.com/anqiansong/foo/@v/v2.0.0.info: 404 Not Found    server response: not found: github.com/anqiansong/foo@v2.0.0: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2

如果 require github.com/anqiansong/foo v1.0.0 是能够的。

  • 增加版本后缀后

foo 工程目录树

foo├── echo│   └── echo.go└── go.mod

module path 为 github.com/anqiansong/foo/v2

module github.com/anqiansong/foo/v2go 1.16

在工程 bar 的go.mod应用 v2.0.1 版本

require github.com/anqiansong/foo/v2 v2.0.1

bar 运行失常

如果 major version 降级至 v2 时,如果该版本没有打算向前兼容,且不想把module path增加版本后缀,则能够在build tag时以 +incompatible 结尾即可,
则别的工程援用示例为 require github.com/anqiansong/foo v2.0.0+incompatible

如何解析package中的module

Go 命令首先在构建列表中搜寻具备包门路前缀的模块。例如,如果导入了包 example.com/a/b ,而模块 example.com/a 位于构建列表中, 则 go 命令将查看 example.com/a
是否蕴含目录 b 中的包。且该目录中至多蕴含一个go文件,这样能力被视为 package 。生成束缚不利用于此目标。 如果生成列表中只有一个模块提供包,则应用该模块。如果没有模块提供包,或者有两个或多个模块提供包,则 go
命令报告谬误。mod=mod 标记批示 go 命令尝试查找提供失落包的新模块, 并更新 go.modgo.sumgo getgo mod tidy 命令会主动执行此操作。

当go命令更新或者获取module依赖时,其会查看 GOPROXY 环境变量,GOPROXY 的值是一个逗号宰割的url列表,或者是关键字 directoff

  • 逗号宰割的具体url为代理地址,其会告知 go 命令以此值去发动连贯
  • direct: 指定module依赖通过版本控制系统去获取
  • off: 示意不尝试连贯获取module

如果 GOPROXY 设置了具体的url,假如 go 命令要寻找一个github.com/tal-tech/go-zero/zrpcpackagego 命令会并行的去查找一下module

  • github.com/tal-tech/go-zero/zrpc
  • github.com/tal-tech/go-zero
  • github.com/tal-tech
  • github.com

如果其中有一个或者多个匹配到蕴含满足 github.com/tal-tech/go-zero/zrpc 的内容,则取最长的 module作为依赖,在找到适合的module和版本后,go 命令会向 go.mod
go.sum 文件中填写require, ,如果解析到的 module 不是 main module 被动引入的,则会在 require 的值前面增加 // direct
正文,如果一个都没有匹配到,则报错;如果 GOPROXY 有多个url代理,在后面失败的状况下,会顺次 向前面代理执行下面的步骤。

go.mod 文件

一个 module(模块工程)的标识是在其根目录下蕴含一个编码为 UTF-8、名称为 go.mod 的文本文件,go.mod 文件中的内容是面向的,每一行蕴含一个指令,且每行均由一个 关键字参数
组成,就像:

module github.com/anqiansong/foogo 1.16require github.com/tal-tech/go-zerorequire github.com/tal-tech/go-queuereplace go.etcd.io/etcd => go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698retract [v1.0.0, v1.0.1]

当然,领有雷同关键字的内容能够分离出来,用 关键字 + block组成,就像:

module github.com/anqiansong/foogo 1.16require (    github.com/tal-tech/go-zero    github.com/tal-tech/go-queue)replace go.etcd.io/etcd => go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698retract [v1.0.0, v1.0.1]

go.mod是机器可写的,像执行一些命令(如: go getgo mod edit)可能会自动更新 go.mod文件。

module 组成元素

在解析 go.mod文件中的内容时,其会被解析为

  • 空白符 : 蕴含空格(U+0020)、制表符(U+0009)、回车(U+000D)和换行符(U+000A)
  • 正文 :正文仅反对单行正文 //
  • 标点 :标点符号有 (),=>
  • 关键字gorequirereplaceexcluderetract
  • 标识符 :由非 空白符 组成的字符序列,如 module path、语义版本
  • 字符串 :由英文双引号 " (U+0022)包裹的解释字符串或者有 < (U+0060)包裹的原始字符串。如"github/com/tal-tech/go-zero"、``
标识符字符串go.mod语法中能够替换

module 语法词法

go.mod 语法是通过Extended Backus-Naur Form (EBNF范式) 定义的,就像

GoMod = { Directive } .Directive = ModuleDirective |            GoDirective |            RequireDirective |            ExcludeDirective |            ReplaceDirective |            RetractDirective .

module 指令

module 关键字定义了main module的module path,在 go.mod 文件中有且只有一个 module 指定。

语法规定:

ModuleDirective = "module" ( ModulePath | "(" newline ModulePath newline ")" newline .

示例:

module github.com/tal-tech/go-zero

go 指令

go 关键字定义了 module 设置预期应用的go语言版本,版本必须是一个无效的go版本(能够了解为合乎 version 规定,也能够了解为为Go曾经release的版本)

通过 go 关键字定义版本后,编译器在编译包时就晓得应该应用哪个go版本去编译,除此外,go 关键字定义版本还能够用于 是否启用 go命令的一些个性,如是否主动开始vendoring在版本 1.14 及当前。

语法规定:

GoDirective = "go" GoVersion newline .GoVersion = string | ident .  /* valid release version; see above */

示例:

go 1.16

require 指令

require 申明了module依赖的最小版本,在 require 指定版本后,go 相干命令会依据 MVS
规定依据此值来加载依赖。

go 寻找依赖时,如果该依赖不是main module间接依赖的,则会在该module path 前面增加 // direct 正文内容。

语法规定:

RequireDirective = "require" ( RequireSpec | "(" newline { RequireSpec } ")" newline ) .RequireSpec = ModulePath Version newline .

示例:

module github.com/tal-tech/go-zerogo 1.16require (    golang.org/x/crypto v1.4.5 // indirect    golang.org/x/text v1.6.7)

excule 指令

excule 会疏忽内容中的指定版本,从 go 1.16 后,exclude 指定的module会被疏忽, 在 go 1.16 前, 如果 require 的module被 exclude
指定后,会列出并获取更改的为被 exclude 的版本。

语法规定:

ExcludeDirective = "exclude" ( ExcludeSpec | "(" newline { ExcludeSpec } ")" ) .ExcludeSpec = ModulePath Version newline .

示例:

module github.com/tal-tech/go-zerogo 1.16exclude golang.org/x/net v1.2.3excule (    golang.org/x/crypto v1.4.5    golang.org/x/text v1.6.7)

replace 指令

replace 指令用于将module的指定版本或者module应用其余的module或者版本来替换,如果 => 右边质指定了版本,则替换 这个版本至指标内容,否则替换替换module的所有版本至指标内容

语法规定:

ReplaceDirective = "replace" ( ReplaceSpec | "(" newline { ReplaceSpec } ")" newline ")" ) .ReplaceSpec = ModulePath [ Version ] "=>" FilePath newline            | ModulePath [ Version ] "=>" ModulePath Version newline .FilePath = /* platform-specific relative or absolute file path */

示例:

replace golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5

=> 左边的内容能够是无效的module path,也能够是绝对或者绝对路径,如果是绝对或者绝对路径,这该门路的根目录必须蕴含 go.mod 文件。

示例:

require github.com/foo v1.0.0replace github.com/foo v1.0.0 => ../bar

retract 指令(1.16新增)

retract 申明的内容,用于标记某些版本或者某个版本范畴(闭区间)标记为撤回,个别 retract 申明前须要写一条正文用于阐明,当执行 go get 命令时, 如果援用了被标记为 retract 的版本,或者
retract 标记的版本范畴内,则会提醒一条正告(其内容为 retract 的正文内容),通过go list -m -versions 获取版本时也会暗藏该版本。

语法规定:

RetractDirective = "retract" ( RetractSpec | "(" newline { RetractSpec } ")" ) .RetractSpec = ( Version | "[" Version "," Version "]" ) newline .

示例:

// someting wrongretract v1.0.0// someting wrong in range of versions => v1.0.0~v1.2.0 retrace [v1.0.0,v1.2.0]

咱们来看一个例子,目前 github.com/anqiansong/retract 曾经有 v1.0.0 等版本了,咱们 增加一行 retract 指令标记
v1.0.0 撤回:

// someting wrongretract v1.0.0

而后release一个版本为 v1.0.1 ,接下来在 github.com/anqiansong/bar 中援用 v1.0.0版本

require github.com/anqiansong/retract v1.0.0

而后执行 go get github.com/anqiansong/retract@v1.0.0 , 不出意外,会失去一个提醒蕴含 something wrong 和 提醒更新到 v1.0.1 的信息

$ go get github.com/anqiansong/retract@v1.0.0
go: warning: github.com/anqiansong/retract@v1.0.0: retracted by module author: someting wronggo: to switch to the latest unretracted version, run:        go get github.com/anqiansong/retract@latestgo get: downgraded github.com/anqiansong/retract v1.0.1 => v1.0.0

获取 github.com/anqiansong/retract 所有module release版本

$ go list -m -versions github.com/anqiansong/retract
github.com/anqiansong/retract v1.0.1

局部命令查看 github.com/anqiansong/retract@v1.0.0的后果:

  • go get

    $ go get github.com/anqiansong/retract@v1.0.0
    go: warning: github.com/anqiansong/retract@v1.0.0: retracted by module author: someting wronggo: to switch to the latest unretracted version, run:go get github.com/anqiansong/retract@latest
  • go list -m -u

    $ go get github.com/anqiansong/retract@v1.0.0
    github.com/anqiansong/retract v1.0.0 (retracted) [v1.0.1]
  • go list -m -versions

      $ go list -m -versions github.com/anqiansong/retract
    github.com/anqiansong/retract v1.0.1
阐明:

retract 管制的是main module的版本,而非依赖的module 版本。

retract 标记的版本其余module还是能够援用的,只是局部 go 命令执行时会有 retract 的不同后果,如上。

自动更新

如果 go.mod 短少信息或者不能精确反映理论状况,大多数 go 命令都会报告谬误。go getgo mod tidy 命令能够用来修复大多数这类问题。 此外,-mod=mod
标记能够用于大多数模块感知命令(go buildgo test 等) ,以批示 go 命令主动修复 go.modgo.sum 中的问题。

module 感知

大多数 go 命令能够在 Module-ware 模式或 GOPATH 模式下运行。在 Module-ware 模式下,go 命令应用 go.mod 文件来查找版本相关性,
它通常从模块缓存中加载包,如果短少模块,则下载模块。在 GOPATH 模式下,go 命令疏忽module; 它在 vendorGOPATH 目录中查找依赖项。

在 Go 1.16中,无论是否存在 go.mod 文件,Module-ware 模式默认是启用的。在低版本中,当工作目录文件或任何父目录中存在 go.mod 文件时,启用 Module-ware 模式 。

Module-ware 模式能够通过GO111MODULE 环境变量管制,能够设置为 onoffauto

  • offgo 相干命令会疏忽 go.mod文件,而后以 GOPATH 模式运行
  • onon 或者空字符串, 相干命令会 Module-ware 模式运行
  • auto: 如果以后文件夹存在 go.mod 文件,则会以 Module-ware 模式运行,在 Go 1.15及更低版本,此值为默认值,

局部go module相干命令

这里命令必须要在 Module-ware 模式才无效

命令用法备注示例
go list -mgo list -m [-u] [-retracted] [-versions] [list flags] [modules]查看module信息go list -m all
go mod initgo mod init [module-path]在工作目录初始化并创立一个go.mod文件go mod init demo
go mod tidygo mod tidy [-e] [-v]整顿go.mod文件go mode tidy
go clean -modcachego clean [-modcache]革除module缓存go clean -modcache

Proxy

模块代理是一个反对 GET 申请响应 的HTTP服务器,该申请没有 query 参数,甚至不须要特定的 header 信息,即便 该值是一个固定的文件系统站点(如:file:// URL )也是能够的。

模块代理的 HTTP 响应状态码必须蕴含 200(OK),3xx4xx5xx4xx5xx被认为是响应谬误,404410
示意所有的module申请是不可用的,留神,谬误的响应的contentType 应该设置为 text/plain,字符集为 utf-8 或者 us-ascii

URLs

go 命令能够通过读取 GOPROXY 环境变量配置来连贯连贯代理服务器或者版本控制系统,GOPROXY 承受一个逗号(,)或者竖线(|)宰割的多个url值, 当以英文逗号(,)
宰割时,只有响应状态码为404或者410时就会尝试前面的代理地址,如果是以竖线(|)宰割,则在http呈现任何谬误(蕴含超时)都会跳过去尝试前面的代理地址。 也能够是 direct 或者 off 关键字。

上面的表格为一个代理地址必须要实现且有申请响应的path(即一个代理服务器必须要要反对一下路由的实现)

  • $base为代理服务器地址,如:https://goproxy.cn
  • $module为module path,如:github.com/tal-tech/go-zero
  • $version为module 版本
path形容示例示例后果
$base/$module/@v/list以纯文本模式返回给定模块的已知版本的列表,每行一个。此列表不应包含伪版本curl -X GET https://goproxy.cn/github.com...v1.0.0
v1.0.1
v1.0.2
v1.0.3
v1.0.4
...
$base/$module/c/$version.info返回对于某个模块的特定版本的 json 格局的元数据。响应必须是一个 JSON 对象,对应于上面的 Go 数据结构: ` type Info struct { Version string // version string Time time.Time // commit time } `curl -X GET https://goproxy.cn/github.com...`{ "Version": "v1.1.5", "Time": "2021-03-02T03:02:57Z"}`
$base/$module/@v/$version.mod返回指定版本的go.mod中的信息/td>curl -X GET https://goproxy.cn/github.com...` module github.com/tal-tech/go-zerogo 1.14require (github.com/ClickHouse/clickhouse-go v1.4.3github.com/DATA-DOG/go-sqlmock v1.4.1github.com/alicebob/miniredis/v2 v2.14.1github.com/antlr/antlr4 v0.0.0-20210105212045-464bcbc32de2....)`
$base/$module/@v/$version.zip返回指定版本的go module的zip文件wget https://goproxy.cn/github.com...v1.1.5.zip
$base/$module/@latest返回无关模块的最新已知版本的 json 格局的元数据curl -X GET https://goproxy.cn/github.com...{ "Version": "v1.1.5", "Time": "2021-03-02T03:02:57Z"}

在获取module最新版本时, go 相干命令会优先申请 $base/$module/@v/list 地址,如果没有找到适宜的版本,
则申请 $base/$module/@latest 获取并匹配是否满足,go 相干命令会依照 release 版本、 pre-release 版本、 pseudo 版本排序。

$base/$module/$version.mod$base/$module/$version.zip 地址必须要提供,因为这些信息能够用于和 go.sum 进行数据校验。

go module下载后的内容个别会存储在 $GOPATH/pkg/mod/cache/download 门路下,包含版本控制系统下载的也是如此。

direct

如果 GOPROXY 设置了 direct 值,则在执行相干 go 命令时会从版本控制系统(gitsvn等)下载module资源,当然还须要另外两个环境
变量 GOPRIVATEGONOPROXY 配合,更多环境变量信息请参考这里

module文件大小束缚

对模块 zip 文件的内容有许多限度。这些束缚确保能够在宽泛的平台上平安和统一地提取压缩文件。

一个模块最多能够达到500MiB(不论是压缩模式还是未压缩模式文件)

go.mod文件要求显示在16MiB以内

go.sum

在module的根目录下可能有一个名为 go.sum的文件,其是间接附丽在go.mod上面的, 当执行 go 相干命令获取module时,
其会查看 zip 文件和 go.sum 中的值是否统一。

go.sum 中的值由一个 module pathversion 和一个hash值组成,如:

github.com/anqiansong/retract v1.0.1 h1:jxcsUM/6tvxM7p14/XMeZPFbql5KAAZJfFqiHG+YKxA=

总结

花了3天工夫,第一天看英文原文档,第二、三天翻译成中文来验证本人的了解,并写下这篇日志,通过学习,对于go module手册的学习解开了不少纳闷,
至多开篇的几个问题算是解开了,本文也是将手册中的局部内容作了 搬运 联合了本人的一些了解,
以及用实在例子去验证, 其中还有很多内容我并未齐全照搬,如果有趣味的同学,能够参考官网手册,本文局部内容蕴含集体了解,也是联合google翻译加上
对一些翻译不合理的中央进行人工翻译,必定存在不合理的中央,如果有了解不统一的,欢送提出和探讨。

最初

有播种能够到 github 点个小❤️❤️ 哈。

参考文档

  • 《Semantic Versioning 2.0.0》
  • 《Go Module官网文档》