模块入门


Go 在 1.11 时内置了实验性的模块管理功能,并借由GO111MODULE来决定是否启用,可设定的值是auto(1.11 ~ 1.13 默认)、onoff

若使用 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




展开阅读全文