为了大家全面理解工作区模式,通过一个具体例子讲解。

本地有两个项目,分别是两个 module:mypkgexample。(Windows 系统请按自己方式创建目录)

cd ~/
mkdir iw3c
mkdir mypkg example
cd mypkg
go mod init github.com/arieslee/mypkg
touch bar.go

在 bar.go 中增加如下示例代码:

package mypkg

func Bar() {
	println("This is package mypkg")
}

接着,在 example 模块中处理:

cd ~/iw3c/example
go mod init github.com/arieslee/example
touch main.go

在 main.go 中增加如下内容:

package main

import (
	"github.com/arieslee/mypkg"
)

func main() {
	mypkg.Bar()
}

这时候,如果我们运行 go mod tidy,肯定会报错,因为我们的 mypkg 包根本没有提交到 github 上,肯定找不到。
fatal: repository 'https://github.com/arieslee/mypkg/' not found

go run main.go 也就不成功。

我们当然可以提交 mypkg 到 github,但我们没修改一次 mypkg,就需要提交,否则 example 中就没法使用上最新的。

针对这种情况,目前是建议通过 replace 来解决,即在 example 中的 go.mod 增加如下 replace:(v1.0.0 根据具体情况修改,还未提交,可以使用 v1.0.0)

module github.com/arieslee/example

go 1.17

require github.com/arieslee/mypkg v1.0.0

replace github.com/arieslee/mypkg => ../mypkg

再次运行 go run main.go,输出如下:

go run main.go
This is package mypkg

当都开发完成时,我们需要手动删除 replace,并执行 go mod tidy 后提交,否则别人使用就报错了。

这还是挺不方便的,如果本地有多个 module,每一个都得这么处理。

工作区模式

针对上面的这个问题,Michael Matloob 提出了 Workspace Mode(工作区模式)。相关 issue 讨论:[cmd/go: add a workspace mode](cmd/go: add a workspace mode "cmd/go: add a workspace mode"),这里是 Proposal^[1]^ 。

为了能够试验工作区,请在本地使用 gotip,建议通过 goup 切换 Go 版本

$ goup install tip
$ goup show
|  VERSION  | ACTIVE |
|-----------|--------|
|   1.0.1   |        |
|    1.1    |        |
|  1.10.8   |        |
|  1.14.9   |        |
|  1.15.2   |        |
|  1.15.3   |        |
|  1.15.4   |        |
|   1.16    |        |
|  1.16.2   |        |
| 1.16beta1 |        |
|   1.17    |        |
| 1.17beta1 |        |
|    1.4    |        |
|   1.4.3   |        |
|    tip    |   *    |

我本地当前 tip 版本:

$ go version
go version devel go1.18-a4b2c579e9 Wed Nov 3 00:49:50 2021 +0000 darwin/amd64

通过 go help mod 可以看到,新增了 work 相关的两个子命令:

The commands are:
  ...
 editwork    edit go.work from tools or scripts
 ...
 initwork    initialize workspace file
 ...

根据这个提示,我们初始化 workspace

$ cd ~/iw3c
$ go work init mypkg example
$ tree
.
├── example
│   ├── go.mod
│   └── main.go
├── go.work
└── mypkg
    ├── bar.go
    └── go.mod

注意几点:

  • 多个子模块应该在一个目录下。比如这里的 polarisxu 目录;(这不是必须的,但更好管理,否则 go mod initwork 需要提供正确的子模块路径)
  • go mod initwork 需要在 polarisxu 目录执行;
  • go mod initwork 之后跟上需要本地开发的子模块目录名;

打开 go.work 看看长什么样:

go 1.18

directory (
    ./example
    ./mypkg
)

go.work 文件的语法和 go.mod 类似,因此也支持 replace。

现在,我们将 example/go.mod 中的 replace 语句删除,再次执行 go run main.go(在 example 目录下),得到了正常的输出。也可以在 polarisxu 目录下,这么运行:go run example/main.go,也能正常。

注意,go.work 不需要提交到 Git 中,因为它只是你本地开发使用的。

如果想要禁用 workspace,可以通过 -workfile=off 实现。

-workfile file
in module aware mode, use the given go.work file as a workspace file.
By default or when -workfile is "auto", the go command searches for a
file named go.work in the current directory and then containing directories
until one is found. If a valid go.work file is found, the modules
specified will collectively be used as the main modules. If -workfile
is "off", or a go.work file is not found in "auto" mode, workspace
mode is disabled.
比如:go run -workfile=off main.gogo build -workfile=off,这样运行你会发现又报错了。但通过这种方式,你可以验证依赖包提交到 github 上之后的情况。

总结

在 GOPATH 年代,多 GOPATH 是一个头疼的问题。当时没有很好的解决,Module 就出现了,多 GOPATH 问题因此消失。但多 Module 问题随之出现。Workspace 方案较好的解决了这个问题。

参考资料

https://go.googlesource.com/proposal/+/master/design/45713-workspace.md