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 版本而有不同的行为表现,见下表:

image

随着版本推进可以看到,只要项目有 go.mod 无论在 $GOPATH 内外都使用 module 方式。

相关资源