Go 在 1.11 时内置了实验性的模块管理功能,并借由GO111MODULE
来决定是否启用,可设定的值是auto
(1.11 ~ 1.13 默认)、on
与off
。
若使用 Go 1.13,当设定值是auto
,执行构造指令时,会看看是否有个 go.mod 文件(用来定义依赖的模块),若有就使用 Go 模块功能,如果没有 go.mod 文件,就采用旧式GOPATH
、vendor 的特性。
当设定值为on
时,就是始终使用 Go 模块功能(从 1.12 之后,go.mod 可以在必要时再新增),模块下载后会自动安装至GOPATH
。
go.mod 不可以在GOPATH
之中。
设定值为off
时就是不使用 Go 模块功能。
例如,现在有个 pkgfoo 释出了v1.0.0版,而你打算基于它写个 go-exercise,go-exercise 文件夹中有个 src/main/main.go:
package main
import "github.com/JustinSDK/pkgfoo"
func main() {
kgfoo.Hi()
pkgfoo.Hello()
}
现在进入你的 go-exercise 文件夹底下,执行go mod init go-exercise
,这会创建一个 go.mod,使用 Go 1.13 的话,默认内容是:
module go-exercise
go 1.13
从 Go 1.12 开始,默认的 go.mod 中会有版本字段,放置了 go.mod 的文件夹称为模块根(module root)目录,通常就是一个 repository 的根目录,该目录下的全部包都属于该模块(除了那些本身包含 go.mod 文件的子目录之外)。
接着执行go build -o bin/main.exe src/main/main.go
,这时会自动找出import
陈述,执行了包的下载并完成构造,此时会显示以下消息:
go: finding github.com/JustinSDK/pkgfoo v1.0.0
go: downloading github.com/JustinSDK/pkgfoo v1.0.0
go: extracting github.com/JustinSDK/pkgfoo v1.0.0
而 go.mod 也有了底下内容:
module exercise
go 1.13
require github.com/JustinSDK/pkgfoo v1.0.0
go.mod 定义了依赖的包与版本,若是第一次执行go build
,那么总是会下载最新版本,你也可以自行编辑 go.mod 的内容,来获取想要的版本,另外你也会发现多了个 go.sum,其中包含了包的 hash 等消息,这用来确认获取的是正确的版本:
github.com/JustinSDK/pkgfoo v1.0.0 h1:XOi67njsT9pcRrsT40Oi3LCA3b1TyIxHd6+9ceGwa0U=
github.com/JustinSDK/pkgfoo v1.0.0/go.mod h1:5PAHGmqvfj2XbzxxOeiJJjOflE/p6zTVRFfaiEeSn1w=
接着在执行构造出来的可执行文件时会看到:
Hi
Helo
喔!Hello 少了一个小写的 l,这是一个小 bug,在修正之后,发布了v1.0.1:
现在 appfoo 为了要能获取更新,可以使用go get -u
,这会升级到最新的 MINOR 或 PATCH 版本,像是从 1.0.0 到 1.0.1,或者是 1.0.0 到 1.1.0,是的,这采用的是Semantic Versioning;若是使用go get -u=patch all
,会将用到的包升级至最新的 PATCH 版本,像是从 1.0.0 到 1.0.1;若是使用go get package@version
,可以指定升级至某个版本号,例如go get github.com/JustinSDK/pkgfoo@v1.0.1
,然而,不建议以此方式升级至新的 MAJOR 版本,原因后述。
在这边因为只是小 bug 更新,就使用go get -u=patch all
,这会看到底下的消息:
go: finding github.com/JustinSDK/pkgfoo v1.0.1
go: downloading github.com/JustinSDK/pkgfoo v1.0.1
go: extracting github.com/JustinSDK/pkgfoo v1.0.1
go.mod 的内容也更新了(go.sum 也会更新):
module go-exercise
go 1.13
require github.com/JustinSDK/pkgfoo v1.0.1
重新执行go build
,就会显示正确的消息了:
Hi
Hello
假设现在 pkgfoo 中的消息都改成中文,并更新为 v2.0.0 了,虽然可以使用go get github.com/JustinSDK/pkgfoo@v2.0.0
来下载最新版本,然而会出现 +incompatible 字样:
go: finding github.com/JustinSDK/pkgfoo v2.0.0
go: downloading github.com/JustinSDK/pkgfoo v2.0.0+incompatible
虽然可以顺利构造,执行时也会是最新版本的结果,然而,若要升级至最新的 MAJOR 版本,依赖的包,必须明确地属于某个模块,因此,pkgfoo 中必须有个 go.mod,并定义版本信息:
module github.com/JustinSDK/pkgfoo/v2
go.mod 在加入了 pkgfoo 之后,发布了v2.0.0,现在 appfoo 打算使用这 v2.0.0,可以在import
时指定:
package main
import "github.com/JustinSDK/pkgfoo/v2"
func main() {
pkgfoo.Hi()
pkgfoo.Hello()
}
直接go build -o bin/main.exe src/main/main.go
,就会看到下载了 v2.0.0:
go: finding github.com/JustinSDK/pkgfoo/v2 v2.0.0
go: downloading github.com/JustinSDK/pkgfoo/v2 v2.0.0
go: extracting github.com/JustinSDK/pkgfoo/v2 v2.0.0
而且你可以看到 appfoo 的 go.mod 更新为:
module go-exercise
go 1.13
require (
github.com/JustinSDK/pkgfoo v1.0.1
github.com/JustinSDK/pkgfoo/v2 v2.0.0
)
现在它依赖在…两个版本?是的,事实上,你也可以同时在 appfoo 中使用:
package main
import "github.com/JustinSDK/pkgfoo/v2"
import pkgfooV1 "github.com/JustinSDK/pkgfoo"
func main() {
pkgfoo.Hi()
pkgfoo.Hello()
pkgfooV1.Hi()
pkgfooV1.Hello()
}
不同模块版本的包,被视为不同的包,上面的程序执行过时会显示:
嗨
哈啰
Hi
Hello