概述
大多数语言都有“依赖”、“包”等概念,Go
语言的依赖处理经历了几次变革。
最早的时候,Go
所依赖的所有的第三方库都放在GOPATH
这个目录下面。从v1.5
开始引入vendor
模式,如果项目目录下有vendor
目录,那么go
工具链会优先使用vendor
内的包进行编译、测试等。
从v1.11
开始,引入了Go Modules
作为依赖解决方案,到v1.14
宣布Go Modules
已经可以用于生产环境,到v1.16
版本开始Go Module
默认开启。
什么是 Go Modules
Go modules 是 Go 语言的依赖解决方案,发布于 Go1.11,成长于 Go1.12,丰富于 Go1.13,正式于 Go1.14 推荐在生产上使用。
Go Modules使得Go语言开发者能够更方便地管理代码包及其版本,并能够与现有的版本控制工具(如Git、SVN等)集成使用。
在传统的GOPATH模式中,所有Go代码都必须位于一个全局的GOPATH路径之下,这使得在不同项目中使用不同版本的依赖包变得非常困难。然而,在Go Modules模式下,每个项目都可以独立管理自己的依赖关系,具有更好的兼容性。当使用Go Modules模式后,项目中会自动创建go.mod文件,其中记录了项目所依赖的模块及其版本信息。go.mod是Go语言项目中的模块文件,用于管理项目的依赖关系和版本信息。
Go Modules也支持语义化版本控制,这意味着开发者可以指定依赖包的版本范围,而不是仅仅依赖最新的版本。这种灵活性有助于确保项目的稳定性和可维护性。
Go moudles 目前集成在 Go 的工具链中,只要安装了 Go,自然而然也就可以使用 Go moudles 了,而 Go modules 的出现也解决了在 Go1.11 前的几个常见争议问题:
- Go 语言长久以来的依赖管理问题。
- “淘汰”现有的
GOPATH
的使用模式。 - 统一社区中的其它的依赖管理工具(提供迁移功能)。
优势
- 首先,研发者能够在任何目录下工作,而不仅仅是在GOPATH指定的目录。
- 可以安装依赖包的指定版本,而不是只能从master分支安装最新的版本。
- 可以导入同一个依赖包的多个版本。当我们老项目使用老版本,新项目使用新版本时会非常有用。
- 要有一个能够罗列当前项目所依赖包的列表。这个的好处是当我们发布项目时不用同时发布所依赖的包。Go能够根据该文件自动下载对应的包。
GO PATH介绍
安装好go开发环境之后,可以运行go env
查看go运行时的环境变量。要修改这些环境变量,可以通过配置环境变量来覆盖默认值(覆盖了就不能通过命令设置),如临时设置export GO111MODULE=on
。或者通过命令go env -w key=value
,如go env -w GO111MODULE=on
。通过命令修改的环境变量保存在GOENV
这个环境变量指向的文件。
有两个比较重要的环境变量:
- GOROOT:Golang 安装目录的路径,包含编译器程序和系统包,也可以放置三方包(不推荐)。新版本已经不需要配置这个环境变量了,安装了go会自动推断出该变量的值。如果安装之后环境变量中没有
$GORROOT/bin
,需要手动添加,这样才能直接在命令行中运行go编译程序。 - GOPATH:该工作目录,放置编译后二进制和 import 包时的搜索路径,一般有三个目录: bin、pkg、src。并且该环境变量必须手动设置。
- bin:用来存放编译后的可执行文件。引入Go modules之后用于存放
get install
安装的可执行文件。 - pkg:存储预编译的目标文件,以加快程序的后续编译速度。引入Go modules之后用于存放第三方包。
- src:存储所有.go文件或源代码。在编写 Go 应用程序,程序包和库时,一般会以
$GOPATH/src/github.com/foo/bar
的路径进行存放。引入Go modules之后用一般不用,go项目可以放在任意目录中,不在是$GOPATH/src
- bin:用来存放编译后的可执行文件。引入Go modules之后用于存放
因此在使用 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 语义化版本规范
Go Module 的设计采用了语义化版本规范,语义化版本规范非常流行且具有指导意义,本文就来聊聊语义化版本规范的设计和在 Go 中的应用。
语义化版本规范
语义化版本规范(SemVer)是由 Gravatars 创办者兼 GitHub 共同创办者 Tom Preston-Werner 所建立,旨在解决 依赖地狱
问题。
它清楚明了的规定了版本格式、版本号递增规:
版本格式:采用 X.Y.Z 的格式,X 是主版本号、Y 是次版本号、而 Z 为修订号(即:主版本号.次版本号.修订号),其中 X、Y 和 Z 为非负的整数,且禁止在数字前方补零。
版本号递增规则:
主版本号:当做了不兼容的 API 修改。
次版本号:当做了向下兼容的功能性新增及修改。
修订号:当做了向下兼容的问题修正。
另外,先行版本号
及 版本编译信息
可以加到 主版本号.次版本号.修订号
的后面,作为延伸。
完整版本格式如下:
先行版本号可以有多个,如第一个为UTC时间,第二个为提交的哈希值:
|
|
其中版本号核心部分 X.Y.Z 是必须的,使用 .
连接,先行版本号和版本编译信息是可选的,先行版本号通过 -
与核心部分连接,版本编译信息通过 +
与核心部分或先行版本号连接。
合法的几种版本号格式如下:
- 主版本号.次版本号.修订号
- 主版本号.次版本号.修订号-先行版本号
- 主版本号.次版本号.修订号+版本编译信息
- 主版本号.次版本号.修订号-先行版本号+版本编译信息
主版本号必须在有任何不兼容的修改被加入公共 API 时递增。每当主版本号递增时,次版本号和修订号必须归零。
次版本号必须在有向下兼容的新功能出现或有改进时递增,或在任何公共 API 的功能被标记为弃用时也必须递增。每当次版本号递增时,修订号必须归零。
修订号必须在只做了向下兼容的修正时才递增。这里的修正指的是针对不正确结果而进行的内部修改。
存在先行版本号,意味着当前版本不够稳定,且可能存在兼容性问题。先行版本号是一连串以 .
分隔的标识符,由 ASCII 字母数字和连接号 [0-9A-Za-z-]
组成,禁止出现空白符,数字类型则禁止在前方补零。合法示例:1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。
版本编译信息标志符规格与先行版本号基本相同,略有差异的是数字类型前方允许补零。合法示例:1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。
除了上面几点说明,还需要额外关注以下几点:
- 标记版本号的软件发行后,禁止改变该版本软件的内容。任何修改都必须以新版本发行。
- 主版本号为零(0.y.z)的软件处于开发初始阶段,一切都可能随时被改变。这样的公共 API 不应该被视为稳定版。
- 1.0.0 的版本号用于界定公共 API 的形成。这一版本之后所有的版本号更新都基于公共 API 及其修改内容。
- 社区中还存在一个不成文的规定,对于次版本号,偶数为稳定版本,奇数为开发版本。当然不是所有项目都这样设计。
使用语义化版本规范可能遇到的问题
在使用语义化版本规范过程中,可能人为或程序编写错误导致出现如下几种可预见的问题:
万一不小心把一个不兼容的改版当成了次版本号发行了该怎么办?
一旦发现自己破坏了语义化版本控制的规范,就要修正这个问题,并发行一个新的次版本号来更正这个问题并且恢复向下兼容。即使是这种情况,也不能去修改已发行的版本。可以的话,将有问题的版本号记录到文档中,告诉使用者问题所在,让他们能够意识到这是有问题的版本。
注意:不到万不得已,不要也不能去修改已发行的版本。
如果我变更了公共 API 但无意中未遵循版本号的改动怎么办呢?(意即在修订等级的发布中,误将重大且不兼容的改变加到代码之中)
自行做最佳的判断。如果你有庞大的使用者群在依照公共 API 的意图而变更行为后会大受影响,那么最好做一次主版本的发布,即使严格来说这个修复仅是修订等级的发布。记住,语义化的版本控制就是透过版本号的改变来传达意义。若这些改变对你的使用者是重要的,那就透过版本号来向他们说明。
v1.2.3
是一个语义化版本号吗?v1.2.3
并不是的一个语义化的版本号。但是,在语义化版本号之前增加前缀v
是用来表示版本号的常用做法。在版本控制系统中,将version
缩写为v
是很常见的。比如:git tag v1.2.3 -m "Release version 1.2.3"
中,v1.2.3
表示标签名称,而1.2.3
是语义化版本号。go modules的模块版本也是在前面加v
如何验证语义化版本规范正确性
官方提供了两个正则可以检查语义化版本号的正确性。
支持按组名称提取匹配结果
1
^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
Go 语言示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
package main import ( "encoding/json" "fmt" "regexp" ) func main() { version := "0.1.2-alpha+001" pattern := regexp.MustCompile(`^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) r := pattern.FindStringSubmatch(version) m := make(map[string]string) for i, name := range pattern.SubexpNames() { if i == 0 { m["version"] = r[i] } else { m[name] = r[i] } } result, _ := json.MarshalIndent(m, "", " ") fmt.Printf("%s\n", result) } /* { "buildmetadata": "001", "major": "0", "minor": "1", "patch": "2", "prerelease": "alpha", "version": "0.1.2-alpha+001" } */
支持按编号提取匹配结果
1
^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
Go 语言示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
package main import ( "fmt" "regexp" ) func main() { version := "0.1.2-alpha+001" pattern := regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) r := pattern.FindStringSubmatch(version) for i, s := range r { fmt.Printf("%d -> %s\n", i, s) } } /* 0 -> 0.1.2-alpha+001 1 -> 0 2 -> 1 3 -> 2 4 -> alpha 5 -> 001 */
Go Modules版本设计
依赖地狱
我们先来看下早期 Go 依赖包存在的依赖地狱问题:
首先存在两个包 pkg1
和 pkg2
,分别依赖 pkg3
的 v1.0.0
版本和 v2.0.0
版本,现在我们开发一个 app
包,它依赖 pkg1
和 pkg2
,那么此时由于 app
包只允许包含一个 pkg3
依赖,所以 Go 构建工具无法抉择应该使用哪个版本的 pkg3
。这就是所谓的依赖地狱问题。
语义导入版本
为了解决依赖地狱问题,Go 在 1.11 版本时引入和 Go Modules:
Go Module 解决问题的方式是,把 pkg3
的 v1.0.0
版本和 v2.0.0
版本当作两个不同的包,这样也就允许了 app
包能够同时包含多个不同版本的 pkg3
。
在使用时,需要在包的导入路径上加上包的主版本号。这里以 go-micro
包使用为例,展示下 Go Module 语义导入版本的用法:
|
|
可以看到导入路径为 "go-micro.dev/v4"
,其中 v4
就代表了需要引入 go-micro
的 v4.y.z
版本。
Go Modules基本使用
go modules相关命令
在 Go modules 中,我们能够使用如下命令进行操作:
命令 | 介绍 |
---|---|
go mod init <project> | 初始化项目依赖,生成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 | 解释为什么需要某个依赖 |
go modules参数配置
GO111MODULE
Go语言提供了 GO111MODULE 这个环境变量来作为 Go modules 的开关,其允许设置以下参数:
参数 说明 auto 只要项目包含了 go.mod 文件的话启用 Go modules,目前在 Go1.11 至 Go1.14 中仍然是默认值。 on 启用 Go modules,推荐设置,将会是Go1.16版本之后的默认值。 off 禁用 Go modules,不推荐设置。 你可能会留意到 GO111MODULE 这个名字比较“奇特”,实际上在 Go 语言中经常会有这类阶段性的变量, GO111MODULE 这个命名代表着Go语言在 1.11 版本添加的。后续版本中可能会去掉。
GOPROXY
这个环境变量主要是用于设置 Go 模块代理(Go module proxy),其作用是用于使 Go 在后续拉取模块版本时能够脱离传统的 VCS(版本控制系统,如github,就是源地址下载) 方式,直接通过镜像站点来快速拉取。值为
off
表示禁止模块代理。设置
GOPROXY
可以加速模块下载,确保构建确定性(提供稳定的构建版本),提高安全性,确保模块始终可用。GOPROXY 的默认值是:
https://proxy.golang.org,direct
,由于某些原因国内无法正常访问该地址,所以我们通常需要配置一个可访问的地址。目前国内社区使用比较多的有两个 https://goproxy.cn和 https://goproxy.io,当然如果你的公司有提供GOPROXY地址那么就直接使用。并且修改的代理,通过go get
命令下载自己的公共模块,也会同步到 https://pkg.go.dev/。设置GOPAROXY的命令如下:
1
go env -w GOPROXY=https://goproxy.cn,direct
GOPROXY 允许设置多个代理地址,多个地址之间需使用英文逗号 “,” 分隔。最后的 “direct” 是一个特殊指示符,用于指示 Go 回源到源地址去抓取(比如 GitHub 等)。当配置有多个代理地址时,如果第一个代理地址返回 404 或 410 错误时,Go 会自动尝试下一个代理地址,当遇见 “direct” 时触发回源,也就是回到源地址去抓取。就是代理失败之后用传统方式(源地址下载模块)。
GOPRIVATE
GONOPROXY/GONOSUMDB/GOPRIVATE
这三个环境变量都是用在当前项目依赖了私有模块,例如像是你公司的私有 git 仓库,又或是 github 中的私有库,都是属于私有模块,都是要进行设置的,否则会拉取失败。
更细致来讲,就是依赖了由 GOPROXY 指定的 Go 模块代理或由 GOSUMDB 指定 Go checksum database 都无法访问到的模块时的场景。
而一般建议直接设置 GOPRIVATE,它的值将作为 GONOPROXY 和 GONOSUMDB 的默认值,所以建议的最佳姿势是直接使用 GOPRIVATE。
设置了GOPROXY 之后,go 命令就会从配置的代理地址拉取和校验依赖包。当我们在项目中引入了非公开的包(公司内部git仓库或 github 私有仓库等),此时便无法正常从代理拉取到这些非公开的依赖包,这个时候就需要配置 GOPRIVATE 环境变量。GOPRIVATE用来告诉 go 命令哪些仓库属于私有仓库,不必通过代理服务器拉取和校验。
GOPRIVATE 的值也可以设置多个,多个地址之间使用英文逗号 “,” 分隔。我们通常会把自己公司内部的代码仓库设置到 GOPRIVATE 中,例如:
1
$ go env -w GOPRIVATE="git.example.com,github.com/arlettebrook/demo"
设置后,前缀为 git.xxx.com 和 github.com/arlettebrook/demo的模块都会被认为是私有模块。
如果不想每次都重新设置,我们也可以利用通配符,例如:
1
$ go env -w GOPRIVATE="*.example.com"
这样子设置的话,所有模块路径为 example.com 的子域名(例如:git.example.com)都将不经过 Go module proxy 和 Go checksum database,需要注意的是不包括 example.com 本身。
此外,如果公司内部自建了 GOPROXY 服务,那么我们可以通过设置
GONOPROXY=none
,允许通内部代理拉取私有仓库的包。
go modules模块文件
初识化项目
在项目的根目录下运行
go mod init <project>
,如go mod init github.com/arlettebrook/demo
,demo
是项目名,github.com/arlettebrook/demo
是模块导入路径,当导入的时候,如果本地没有,会去该路径下载。go.mod 文件
在初始化项目时,会生成一个 go.mod 文件,是启用了 Go modules 项目所必须的最重要的标识,同时也是 GO111MODULE 值为 auto 时的识别标识,它描述了当前项目(也就是当前模块)的元信息,每一行都以一个动词开头。
示例文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
module github.com/arlettebrook/demo go 1.22.1 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 拉取下来的,也有可能是你所依赖的模块所依赖的,情况有好几种。incompatible:不兼容的go.sum 文件
在第一次拉取模块依赖后,会发现多出了一个 go.sum 文件,其详细罗列了当前项目直接或间接依赖的所有模块版本,并写明了那些模块版本的 SHA-256 哈希值以备 Go 在今后的操作中保证项目所依赖的那些模块版本不会被篡改。
1 2 3 4
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
可以看到一个模块路径可能有如下两种:
1 2
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
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 的情况。
go.mod
和go.sum
都应该被提交到git仓库中去。当别人使用你的项目时,mod保证依赖版本一直,sum保证依赖不被篡改。
go modules模块下载
我们下载、添加模块使用
go get -u <module path>
。默认下载、添加最新版本,首先会检查本地(pkg:全局模块缓存)是否存在,没有,在去下载。
在项目中下载会自动添加到
go.mod
文件中。-u
选项会更新模块的依赖包到最新版本,推荐加上。还可以指定下载版本
命令 作用 go get golang.org/x/text@latest 拉取最新的版本,若存在tag,则优先使用。可以省略。 go get golang.org/x/text@master 拉取 master 分支的最新 commit。@branch go get golang.org/x/text@v0.3.2 拉取 tag 为 v0.3.2 的 commit。@version,version必须满足语义化版本规范且前面加v。 go get golang.org/x/text@342b2e 拉取 hash 为 342b231 的 commit,最终会被转换为 v0.3.2。@commit go get golang.org/x/text/v2 下载主版本号为2的最新版 最新版本的选择
分两种情况
- 最新版本有发布tags:就以发布的版本,version一般为
标签名
,如v2.1.2
- 最新版本没有发布tags:就以提交的最新版本,version一般为
已发布标签-最新提交日期-最新提交哈希+版本编译信息
,版本编译信息一般没有。如v2.1.2-20240416160154-fe59bbe5cc7f
,如果一次tags也没有发布,版本号则为v0.0.0,如v0.0.0-20240416160154-fe59bbe5cc7f
- 子模块同理
- 最新版本有发布tags:就以发布的版本,version一般为
go modules全局缓存
Go module 会把下载到本地的依赖包会以类似下面的形式保存在 $GOPATH/pkg/mod
目录下,每个依赖包都会带有版本号进行区分,这样就允许在本地存在同一个包的多个不同版本。
|
|
如果想清除所有本地已缓存的依赖包数据,可以执行 go clean -modcache
命令。
go modules模块导入
go模块导入用
import "模块路径"
当导入多个模块的时候用
1 2 3 4 5
import ( "fmt" "os" "path/filepath" )
别名导入用
import 别名 "模块路径"
1
import f "fmt"
点导入用
import . "模块路径"
点导入是一种特殊的导入方式,它将包中的所有公共标识符(函数、变量、类型等)提升到当前文件的命名空间中,这样在代码中就可以直接使用这些标识符,而不需要加上包名前缀。但是,这种方式可能会导致命名冲突和代码可读性下降,因此一般不建议使用。
空导入用
import _ "模块路径"
空导入通常用于初始化包中的变量或者执行包中的初始化函数,而不直接使用该包中的其他标识符。
注意事项
当模块的主版本号为0或1的时候省略了主版本标识。
当主版本号为2及以上时,不能省略主版本标识。否则会出现冲突。
主版本标识只能为
/v主版本号
,不能用@version
,一般使用主版本的最新版,这与语义化版本规范有关。如:
1 2
import "github.com/jordan-wright/email" import "github.com/jordan-wright/email/v4"
为什么忽略 v0 和 v1 的主版本号
还是与语义化版本规范有关,v0属于开发初始阶段,其公共api不被视为稳定版,当版本到达v1,其公共api基本确定,在此之后如果不出现不兼容api的修改,是不会修改主版本号的。后续的次版本、修订号会向下兼容。这是官方所鼓励的。当api做了不兼容的修改,主版本号就会修改。为了不出现冲突就会加上主版本标识。
gopkg.in介绍
gopkg.in
是旧go包管理工具中的一个,并不是官方包管理工具。作用是下载时重定向到相应github仓库。优点是: URL 更干净、更短、导入路径稳定、易于使用、支持版本控制。浏览器打开链接,会提供对应包的godoc在线链接以及github仓库链接。
gopkg.in/ini.v1
对应github仓库为githu.com/go-ini/ini
,当没有指定用户名时,用户名默认为go-包名
。1 2
gopkg.in/pkg.v3 → github.com/go-pkg/pkg (branch/tag v3, v3.N, or v3.N.M) gopkg.in/user/pkg.v3 → github.com/user/pkg (branch/tag v3, v3.N, or v3.N.M)
版本控制用
.vNumber
表示.。与
go modules
的区别:- v1
gopkg.in
必须指定。go mod
不用。 gopkg.in
分隔符是.
(go mod
是/
)。- v0为开发版、不稳定版,不指定默认为开发版,
go mod
不指定默认为v0或v1。 gopkg.in
主版本为1就要指定主版本标识。go mod
主版本为2才需要指定。
- v1
如何让
gopkg.in
收录自己的模块:- 与
go mod
一样,当我们使用go get
下载已经存在的版本仓库时,会自动同步到在线的godoc中。 - 建议仓库名与用户名关系是
pkg
与go-pkg
,推荐gopkg.in
。当然也可以直接使用github仓库路径。 - 其他情况可以使用
go mod
。并通过像proxy.golang.org
这样的代理服务器来分发你的模块。
- 与
gopkg.in
版本控制同样遵循[语义化版本控制](#Go Module 语义化版本规范)。
总结
至此我们大致介绍了 Go modules 的前世今生、语义化版本规范以及基本使用。
Go modules 的成长和发展经历了一定的过程,如果你是刚接触的读者,直接基于 Go modules 的项目开始即可,如果既有老项目,那么是时候考虑切换过来了,Go1.14起已经准备就绪,并推荐你使用。