[golang] GO111MODULE 及 Go 模块
GO111MODULE 及 Go 模块
安装使用三方库时经常会遇到 GO111MODULE=on/off
这个关键字,具体是什么,这与 Go 模块有关。
GO111MODULE
的由来
如其名字所暗示,GO111MODULE
是 Go 1.11 引入的新版模块管理方式。之前的版本中,安装的三方库比如 go-cmp
,要求模块存在于 GOPATH
下,否则编译时会找不到。
GOPATH
的目录结构如下:
$GOPATH
bin/ # Directory where executable binaries are stored
src/ # Directory where Go source files of the dependent packages are stored
github.com/google/go-cmp/
...
pkg/ # Directory where compiled Go object files are stored
...
其中:
bin
存放编译后生成的可执行文件src
存放源码pkg
则存放编译后的包内容
而 Go 1.11 之前,我们通过 go get xxx
安装的包则存在于上述 $GOPATH/src
目录下。而新版 Go 中,通过设置 GO111MODULE=off
表示使用旧的模块管理方式安装,安装后则包会存放在 $GOPATH
下。以下是一个真实示例,安装 go-cmp
后查看其存放的位置:
$ docker run -it golang:1.13 /bin/bash
$ GO111MODULE=off go get github.com/google/go-cmp/cmp
$ tree -d -L 5 $GOPATH # assume tree is installed
/go
|-- bin
|-- pkg/linux_amd64/github.com/google/go-cmp
`-- src/github.com/google/go-cmp/cmp
-
如果
tree
命令缺失的话可通过如下脚本进行安装:$ sudo apt-get update && apt-get install tree
根据 Go 1.11 发版时的声明:
This release adds preliminary support for a new concept called “modules,” an alternative to GOPATH with integrated support for versioning and package distribution.
— Go 1.11 is released. August 24, 2018
启用 module
后(通过 go module init xxx
初始化的项目),包仍然下载到 $GOPATH
下但目录结构有所变化:
$ GO111MODULE=on go get github.com/google/go-cmp/cmp
$ tree -d -L 5 $GOPATH
/go
`-- pkg
|-- mod
| |-- cache
| | `-- download
| | |-- github.com
| | |-- golang.org
| | `-- sumdb
| `-- github.com
| `-- google
| `-- go-cmp@v0.4.0
`-- sumdb
`-- sum.golang.org
那启用 module
与否实际的差异是什么呢?
Module
Module 并没有完全替换原来的 $GOPATH
,而只是在版本控制和包分发这两处起作用。具体来说,当分发 package 时如果其是个 module 则不必限制在 $GOPATH
下了,否则项目是需要保存在 $GOPATH
下才能正常被编译器加载到的。
以下是一个示例,尝试在 $GOPATH
外创建一个项目 testproject
然后运行:
main.go:
// main.go
package main
func main() {
TestFunc()
}
test_func.go:
// test_func.go
package main
import "k8s.io/klog"
func TestFunc() {
klog.Infoln("Hello Go Modules!")
}
尝试编译并运行,发现能正常跑起来:
/anywhere/outside/gopath/testproject $ GO111MODULE=off go get k8s.io/klog
/anywhere/outside/gopath/testproject $ GO111MODULE=off go run .
I0404 14:58:55.589292 1445 test_func.go:6] Hello Go Modules!
之所以能运行是上面的示例只有一个 main
模块,而 $GOPATH
的限制是在多个模块的场景下,即项目中存在子模块时,修改上面的示例如下。
将打印信息的方法移到单独模块中,目录结构变成了:
$ tree /anywhere/outside/gopath/testproject
/anywhere/outside/gopath/testproject
|-- main.go
`-- test
`-- func.go
test/func.go:
// test/func.go
package test
import "k8s.io/klog"
func TestFunc() {
klog.Infoln("Hello Go Modules!")
}
同时更新 main.go 中的调用:
// main.go
package main
import "test"
func main() {
XXX.TestFunc()
}
尝试运行会有如下报错:
$ GO111MODULE=off go run .
main.go:3:8: cannot find package "test" in any of:
/usr/local/go/src/test (from $GOROOT)
/go/src/test (from $GOPATH)
root@7d47e56e5fcc:/wy/testproject# vi main.go
注意上面 main.go
中直接使用了 test
来引入,这其实是行不通的,因为项目本身并没有初始化成一个 module
同时 GO111MODULE=off
也表明使用旧的依赖管理模式。同时上面的报错也清楚地表明,编译器会从 $GOPATH
找依赖的包。
解决办法可以是将项目遵从旧模式,移入 $GOPATH
下,这样通过 GO111MODULE=off
便可以跑通:
项目移入 $GOPATH
后的结构:
$ tree $GOPATH/src/insujang.github.io
/go/src/insujang.github.io
`-- testproject
|-- main.go
`-- test
`-- func.go
同时修改 main.go
中的引用方式:
import "insujang.github.io/testproject/test"
再次运行便可以成功了:
$ $GOPATH/src/insujang.github.io/testproject $ GO111MODULE=off go run .
I0404 15:09:40.239612 2034 func.go:6] Hello Go Modules!
Module 的使用
除上回退到旧版依赖管理方式上,还可以使用 module 来解决上面的问题。
首先将项目初始化成 module:
$ /anywhere/outside/gopath/testproject $ go mod init insujang.github.io/testproject
/anywhere/outside/gopath/testproject $ GO111MODULE=on go run .
I0404 15:18:36.553399 2303 func.go:6] Hello Go Modules!
main.go
中引用:
// main.go
package main
import "insujang.github.io/testproject/test"
func main() {
test.TestFunc()
}
虽然项目在 $GOPATH
外,但因为初始化成了一个模块且有模块名 insujang.github.io/testproject
那么在进行包引入时就可通过这个唯一的模块名来进行。
项目正常运行:
$ /anywhere/outside/gopath/testproject $ GO111MODULE=off go run .
main.go:3:8: cannot find package "insujang.github.io/testproject/test" in any of:
/usr/local/go/src/insujang.github.io/testproject/test (from $GOROOT)
/go/src/insujang.github.io/testproject/test (from $GOPATH)
GO111MODULE 的默认值
上述示例中均显式设置了 GO111MODULE
以运行 Go 命令,默认情况下其值为 auto
,会根据不同 Go 版本而有不同的行为表现,见下表:
随着版本推进可以看到,只要项目有 go.mod
无论在 $GOPATH
内外都使用 module 方式。