原文地址
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
关键字外,exclude
、retract
(1.16)这些关键字是什么,怎么用; - go mod文件语法格局是什么,目前除了跟着他人写,如同也不明确其中的语法
github.com/tal-tech/go-zero v1.1.5
、github.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
中就蕴含了 foo
和 bar
两个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/v2
,github.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 version
和patch version
要归零; - minor version:指在新的性能公布(features)或者作了向后兼容的内容变更后,此版本号会upgrade,在此版本号upgrade时,
patch version
要归零; - patch version:指有bug修复或者性能优化时,此版本号能够进行upgrade,在有pre-release公布需要时也能够变更此版本号
示例:v0.0.0
、 v1.2.3
、 v1.2.10-pre
如果一个版本的major version
为0
或者patch-version
有版本后缀(如:pre),则认为这个版本是不稳固的,如v0.2.0
、v1.5.0-pre
、v1.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-0
或vX.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-39540e21d249
的release
版本为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 foofoo
工程目前有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.mod
和 go.sum
。go get
和 go mod tidy
命令会主动执行此操作。
当go命令更新或者获取module依赖时,其会查看 GOPROXY
环境变量,GOPROXY
的值是一个逗号宰割的url列表,或者是关键字 direct
、 off
,
- 逗号宰割的具体url为代理地址,其会告知
go
命令以此值去发动连贯 direct
: 指定module依赖通过版本控制系统去获取off
: 示意不尝试连贯获取module
如果 GOPROXY
设置了具体的url,假如 go
命令要寻找一个github.com/tal-tech/go-zero/zrpc
的 package
,go
命令会并行的去查找一下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 get
、 go mod edit
)可能会自动更新 go.mod
文件。
module 组成元素
在解析 go.mod
文件中的内容时,其会被解析为
空白符
: 蕴含空格(U+0020)、制表符(U+0009)、回车(U+000D)和换行符(U+000A)正文
:正文仅反对单行正文//
标点
:标点符号有(
、)
、,
、=>
关键字
:go
、require
、replace
、exclude
、retract
标识符
:由非空白符
组成的字符序列,如 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 get
、 go mod tidy
命令能够用来修复大多数这类问题。 此外,-mod=mod
标记能够用于大多数模块感知命令(go build
、 go test
等) ,以批示 go
命令主动修复 go.mod
和 go.sum
中的问题。
module 感知
大多数 go 命令能够在 Module-ware
模式或 GOPATH
模式下运行。在 Module-ware
模式下,go 命令应用 go.mod 文件来查找版本相关性,
它通常从模块缓存中加载包,如果短少模块,则下载模块。在 GOPATH
模式下,go 命令疏忽module; 它在 vendor
或 GOPATH
目录中查找依赖项。
在 Go 1.16中,无论是否存在 go.mod
文件,Module-ware
模式默认是启用的。在低版本中,当工作目录文件或任何父目录中存在 go.mod
文件时,启用 Module-ware
模式 。
Module-ware
模式能够通过GO111MODULE 环境变量管制,能够设置为 on
、off
或 auto
off
:go
相干命令会疏忽go.mod
文件,而后以GOPATH
模式运行on
:on
或者空字符串, 相干命令会Module-ware
模式运行auto
: 如果以后文件夹存在go.mod
文件,则会以Module-ware
模式运行,在 Go 1.15及更低版本,此值为默认值,
局部go module相干命令
这里命令必须要在 Module-ware
模式才无效
命令 | 用法 | 备注 | 示例 |
---|---|---|---|
go list -m | go list -m [-u] [-retracted] [-versions] [list flags] [modules] | 查看module信息 | go list -m all |
go mod init | go mod init [module-path] | 在工作目录初始化并创立一个go.mod文件 | go mod init demo |
go mod tidy | go mod tidy [-e] [-v] | 整顿go.mod文件 | go mode tidy |
go clean -modcache | go clean [-modcache] | 革除module缓存 | go clean -modcache |
Proxy
模块代理是一个反对 GET
申请响应 的HTTP
服务器,该申请没有 query
参数,甚至不须要特定的 header
信息,即便 该值是一个固定的文件系统站点(如:file:// URL
)也是能够的。
模块代理的 HTTP
响应状态码必须蕴含 200
(OK),3xx
,4xx
、5xx
,4xx
、5xx
被认为是响应谬误,404
和 410
示意所有的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
命令时会从版本控制系统(git
、svn
等)下载module资源,当然还须要另外两个环境
变量 GOPRIVATE
、 GONOPROXY
配合,更多环境变量信息请参考这里
module文件大小束缚
对模块 zip
文件的内容有许多限度。这些束缚确保能够在宽泛的平台上平安和统一地提取压缩文件。
一个模块最多能够达到500MiB(不论是压缩模式还是未压缩模式文件)
go.mod
文件要求显示在16MiB以内
go.sum
在module的根目录下可能有一个名为 go.sum
的文件,其是间接附丽在go.mod上面的, 当执行 go
相干命令获取module时,
其会查看 zip
文件和 go.sum
中的值是否统一。
go.sum
中的值由一个 module path
、version
和一个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官网文档》