Go’s dependency management, or Go Module, has been around for some years now, and has been the subject of much criticism and refinement.

Go 1.18 will introduce a new feature, Multi-Module Workspaces, to support multiple workspaces for modules, which will solve a number of problems.

Background

When working with Go projects on a daily basis, there are 2 classic problems that are particularly tedious.

They are as follows.

  1. relying on a local replace module.
  2. relying on a local unpublished module.

replace module

The first scenario: For example, in a Go project, we would use replace to resolve some local dependencies or to customize the code. We will use replace in the go.mod file to do this.

The following code.

1
replace golang.org/x/net => /Users/eddycjy/go/awesomeProject

This allows for accuracy when linking local development.

Here’s the problem.

  • Local paths: the replace set up essentially converts to a local path, which means that everyone is different.
  • Repository Dependencies: the file changes are uploaded to the Git repository, so if you accidentally upload a file, it affects other developers, or you have to change it back every time you upload it.

This is a very poor user experience and a pain in the ass.

Unpublished modules

The second scenario: when you are working on a local Go project, you may be working on multiple libraries (project libraries, tool libraries, third-party libraries) at the same time.

The following code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

package main

import (
    "github.com/eddycjy/pkgutil"
)

func main() {
    pkgutil.PrintFish()
}

If you run go run or go mod tidy at this point, it will not work and will fail.

An error like the following will be thrown.

1
fatal: repository 'https://github.com/eddycjy/pkgutil/' not found

This exception is because the library github.com/eddycjy/pkgutil is not available on GitHub, and therefore cannot be pulled.

Solution: Prior to Go 1.18, we would either replace, or upload directly to Github, and the dependencies would be pulled by the Go toolchain.

Many users have questioned this: do all Go dependencies have to be uploaded to GitHub, with strong bindings?

It’s very unfriendly and damaging to newcomers.

Workspace model

After many rounds of feedback from the community, Michael Matloob made the proposal Proposal: Multi-Module Workspaces in cmd/go which has been discussed and implemented extensively and was officially implemented in Go 1.18.

One of the core concepts of the new proposal is the addition of the go work workspace concept, which targets the Go Module dependency management model.

It is possible to set a series of dependent module local paths in the local project’s go.work file, and then compose the modules under the path into a workspace for the current Go project, i.e. N Go Modules into 1 Go Work, with the workspace having the highest read priority.

We can see this with go help as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ go1.18beta1 help work
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.

Simply execute go work init to initialise a new workspace, followed by the argument to the specific submodule mod to be generated.

The command is as follows.

1
go work init ./mod ./tools

The project catalogue is as follows.

1
2
3
4
5
6
7
8
awesomeProject
├── mod
│   ├── go.mod      // 子模块
│   └── main.go
├── go.work         // 工作区
└── tools
    ├── fish.go
    └── go.mod      // 子模块

The contents of the generated go.work file.

1
2
3
4
5
6
go 1.18

use (
    ./mod 
    ./tools
)

The new go.work has the same syntax as go.mod and can also be used with the replace syntax.

1
2
3
4
5
6

go 1.18

use (...)

replace golang.org/x/net => example.com/fork/net v1.4.5

A total of three directives are supported within the go.work file.

  • go: declares the go version number, mainly for subsequent version control of new semantics.
  • use: declares the specific file path of a module on which the application depends. The path can be either absolute or relative, and can be outside the application’s destiny directory.
  • replace: Declares that the import path of a module dependency is replaced, with priority over the replace directive in go.mod.

If you want to disable workspace mode, you can specify it with the -workfile=off command.

That is, execute the following command at runtime.

1
2
3
go run -workfile=off main.go

go build -workfile=off

The go.work file doesn’t need to be committed to a Git repository, otherwise it’s a bit of a toss-up.

As long as you have go.work set up in your Go project, you will be in workspace mode at runtime and compile time, and the workspace configuration will be given highest priority to suit your local development needs.

This concludes the core knowledge of the workspace.

Summary

Today we have introduced a new feature of Go 1.18: the Multi-Module workspace model. It is still essentially a solution to the need for local development.

Since go.mod files are strongly associated with projects, they are basically uploaded to a Git repository, so it’s hard to do anything about it. So we just built go.work to be purely local and easy to use.

With the new go.work, you can work on completely local files without affecting other members of the development team.

What do you think? What do you think?)