一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情。
前段时间发布的 Go 1.18 增加了 workspace 模式的支持,允许开发者同时在多个 module 进行开发。今天我们来了解一下 workspace 到底是什么,如何使用。
如果本地还没有ready,你可以在这里下载 1.18 版本,以及了解 Release Note。
Workspaces
workspaces 支持开发者同时处理多个 module,而无需为每个 module 编辑 go.mod 文件。解决依赖关系时,工作空间中的每个 module 都被视为 root module。
此前,当我们需要将功能添加到一个 module 并在另一个 module 中使用它,我们有两个选项:
- 将更改发布到第一个 module;
- 使用
replace
编辑存在依赖 module 的 go.mod 文件以获取本地未发布的更改。当然,为了随后发布没有错误,我们还需要在发布本地更改后删除replace
。
有了 workspaces,我们可以使用 workspace 根目录中的 go.work 文件来控制所有依赖项。 go.work 文件具有覆盖各个 go.mod 文件的 use
和 replace
指令,因此无需单独编辑每个 go.mod 文件。
运行 go work init
即可创建一个 workspace,用空格分隔的一组module目录作为参数。workspace 不需要包含正在使用的 module。 init
命令会创建一个列出 workspace 中所有 module 的 go.work 文件。如果运行不带参数的 go work init
,该命令会创建一个空工作区。
要将 module 添加到 workspace,请运行 go work use [module directory]
或手动编辑 go.work 文件。运行 go work use -r
将以递归方式将参数中所有带有 go.mod 的目录添加到 workspace 中。如果一个目录没有 go.mod 文件,或者不存在,那么 use
指令将把该目录的从 go.work 文件中删除。
go.work 语法类似于 go.mod 文件,包含以下指令:
go
:go 版本,例如 1.18use
:将磁盘上的 module 添加到 workspace 的 main module 中。它的参数是包含 go.mod 目录的相对路径。replace
:与 go.mod 文件中的 replace 指令类似,go.work 文件中的 replace 指令将 module 的特定版本,或所有版本进行替换。
实战操作
光说不练假把式,其实作为一个长期使用 go mod 的开发者,直接看 Go 1.18 的介绍并不是非常直观。下面我们来尝试一下。开始前请确认本地 Go 版本已经升级到 >= 1.18。
了解一下 go work
能做什么
$ go work
Work provides access to operations on workspaces.
Note that support for workspaces is built into many other commands, not
just 'go work'.
See 'go help modules' for information about Go's module system of which
workspaces are a part.
See https://go.dev/ref/mod#workspaces for an in-depth reference on
workspaces.
See https://go.dev/doc/tutorial/workspaces for an introductory
tutorial on workspaces.
A workspace is specified by a go.work file that specifies a set of
module directories with the "use" directive. These modules are used as
root modules by the go command for builds and related operations. A
workspace that does not specify modules to be used cannot be used to do
builds from local modules.
go.work files are line-oriented. Each line holds a single directive,
made up of a keyword followed by arguments. For example:
go 1.18
use ../foo/bar
use ./baz
replace example.com/foo v1.2.3 => example.com/bar v1.4.5
The leading keyword can be factored out of adjacent lines to create a block,
like in Go imports.
use (
../foo/bar
./baz
)
The use directive specifies a module to be included in the workspace's
set of main modules. The argument to the use directive is the directory
containing the module's go.mod file.
The go directive specifies the version of Go the file was written at. It
is possible there may be future changes in the semantics of workspaces
that could be controlled by this version, but for now the version
specified has no effect.
The replace directive has the same syntax as the replace directive in a
go.mod file and takes precedence over replaces in go.mod files. It is
primarily intended to override conflicting replaces in different workspace
modules.
To determine whether the go command is operating in workspace mode, use
the "go env GOWORK" command. This will specify the workspace file being
used.
Usage:
go work <command> [arguments]
The commands are:
edit edit go.work from tools or scripts
init initialize workspace file
sync sync workspace build list to modules
use add modules to workspace file
Use "go help work <command>" for more information about a command.
创建一个 module
- 通过命令行创建一个目录
$ mkdir workspace
$ cd workspace
- 初始化 module
我们的示例将会创建一个新的 module
hello
,依赖golang.org/x/example
。
$ mkdir hello
$ cd hello
$ go mod init example.com/hello
go: creating new go.mod: module example.com/hello
使用 go get
添加 golang.org/x/example 依赖
$ go get golang.org/x/example
此时生成的 go.mod
内容为
module example.com/hello
go 1.18
require golang.org/x/example v0.0.0-20220412213650-2e68773dfca0 // indirect
在 hello 目录下,创建一个 hello.go
package main
import (
"fmt"
"golang.org/x/example/stringutil"
)
func main() {
fmt.Println(stringutil.Reverse("Hello"))
}
现在执行 go run example.com/hello
,得到结果:olleH
,这是字符串反转的正确结果。
创建 workspace
在上一步我们创建的workspace
目录下,执行
$ go work init ./hello
go work init 命令会创建一个 go.work 文件,其中包含了 ./hello 目录,内容如下
go 1.18
use ./hello
第一行的 go 1.18
和 go.mod 中并无差异,用意都在于告诉 Go 当前的文件应该以什么版本来解析。
第二行的 use
告诉 Go 在 ./hello
目录下的 module 应该作为 main module。这样一来,在 workspace
下的任何子目录,./hello
目录下的 module 都可用。
我们可以尝试在 workspace
目录下直接运行 go run example.com/hello
,结果同样是olleH
。
这样的机制,使得我们可以在一个 module
的目录之外引用它的 package
。
当然,如果你直接在 workspace
目录下运行 go run
是会报错的,因为 go
也不知道此时该用哪个 module。
下载并修改 golang.org/x/example module
在 workspace
目录下clone 仓库
$ git clone https://go.googlesource.com/example
Cloning into 'example'...
remote: Total 165 (delta 27), reused 165 (delta 27)
Receiving objects: 100% (165/165), 434.18 KiB | 1022.00 KiB/s, done.
Resolving deltas: 100% (27/27), done.
把 clone 下来的 module 加入我们的 workspace
$ go work use ./example
此时 go.work 的内容变成:
go 1.18
use (
./hello
./example
)
同时包含了 example.com/hello
以及 golang.org/x/example
两个 module。
这样我们就可以直接使用本地 golang.org/x/example 的实现,而不是通过 go get
从远程获取的。
下面我们尝试在本地的 golang.org/x/example/stringutil 目录下,增加一个将字符串转为大写的函数 ToUpper
。
在 workspace/example/stringutil
目录下创建一个新文件 toupper.go
package stringutil
import "unicode"
// ToUpper uppercases all the runes in its argument string.
func ToUpper(s string) string {
r := []rune(s)
for i := range r {
r[i] = unicode.ToUpper(r[i])
}
return string(r)
}
修改我们的 workspace/hello/hello.go
,从原来的反转字符串,改成将 hello
字符串修改为大写:
package main
import (
"fmt"
"golang.org/x/example/stringutil"
)
func main() {
fmt.Println(stringutil.ToUpper("Hello"))
}
现在,当我们重新回到workspace
目录,依然运行 go run example.com/hello
,你会发现,结果变成了 HELLO
。
$ go run example.com/hello
HELLO
这是因为,go.work 引入了 ./hello
以及./example
两个目录。
当执行 go run example.com/hello
时,从 ./hello
找到 example.com/hello
module。
同样的,从 ./example
找到了 golang.org/x/example
依赖。
所以,在涉及多个 module 并行开发时,go.work 可以替代replace
的作用。只要这些 module 在同一个 workspace 下,就可以很容易地在一个 module 中进行修改,在另一个 module 中直接使用。
补充几个官方提到的常用的命令:
go work use [-r] [dir]
adds ause
directive to thego.work
file fordir
, if it exists, and removes theuse
directory if the argument directory doesn’t exist. The-r
flag examines subdirectories ofdir
recursively.go work edit
edits thego.work
file similarly togo mod edit
go work sync
pushes the dependencies in thego.work
file back into thego.mod
files of each workspace module.
在GOPATH中使用 workspace
如果你依然在使用 $GOPATH,可以参照下面的步骤来创建一个 workspace:
- 在 GOPATH 的根目录执行
go work init
- 如果需要使用一个 local module,执行
go work use [path-to-module]
- 如果需要将 GOPATH 下所有 module 都添加到 workspace,可以执行
go work use -r
,这样会递归地将所有带有 go.mod 的目录都添加到 workspace 中。