Skip to content

Toolchains in Go

This is a blog for Go toolchains, I will try to explain what is toolchain, how to use it and why Go needs it.

Why Go Toolchain

Go toolchain actually bring 2 useful parts:

  1. Be able to use any go version from the global go as an entry. It also enables users to build the projects with the same version easier.
  2. Specify the minimal module go toolchain requirement, so go will do a selection when it attempts to compile. For example, you are using os.CopyFS supported since go1.23, so you set toolchain in go.mod as go1.23.0, if someone tries to compile your project by go1.22.7, go will select the higher go version and continue to compile.

How to Use Go Toolchain

Go toolchain feature was introduced since Go1.21. The go distribution consists a go command and a bundled go toolchain, which is the std libs as well as the compiler, assembler, and other tools.

$ pwd
/usr/local/go

$ ls bin 
go    gofmt

$ ls pkg/tool/darwin_arm64 
addr2line  buildid    compile    cover      fix        
nm         pack       preprofile trace
asm        cgo        covdata    doc        
link       objdump    pprof      test2json  vet

config it from:

  • env GOTOOLCHAIN
  • toolchain in go.mod
  • current workspace go.work

The go has a bundle toolchain, and the toolchain keyword restricts the go version applies to it. Higher go is compatible to the lower one, but if it's lower go will try to use cache or download the higher go version specified in toolchain keyword.

For a go module used as a dependency by the others, the go version is usually lower than toolchain version, so when working in this module toolchain takes a higher precedence.

The go and toolchain lines can be thought of as specifying the version requirements for the module’s dependency on the Go toolchain itself, just as the require lines in go.mod specify the version requirements for dependencies on other modules.

GOTOOLCHAIN=go1.21rc3 go test

# begin decision with go1.21.3, instead of default go
GOTOOLCHAIN=go1.21.3+auto 

By specifying GOTOOLCHAIN, actually you can use your global go to trigger any go version you like.

$ GOTOOLCHAIN=go1.21.3+auto go version
go: downloading go1.21.3 (darwin/arm64)
go version go1.21.3 darwin/arm64

$ GOTOOLCHAIN=go1.21.3+auto go version
go version go1.21.3 darwin/arm64

$ go version
go version go1.23.1 darwin/arm64

Inside a project that has toolchain setting up, it will calculate to pick up a proper go version with its toolchain bundle to use. For example, when you run the toolchain under a project with toolchain setting up, it will help you automatically use a suitable go version for your project.

$ cat go.mod | grep toolchain
toolchain go1.22.7

$ go version
go version go1.23.1 darwin/arm64
$ GOTOOLCHAIN=local go version
go version go1.23.1 darwin/arm64

$ GOTOOLCHAIN=go1.21.3+auto go version
go: downloading go1.22.7 (darwin/arm64)
go version go1.22.7 darwin/arm64

$ GOTOOLCHAIN=go1.21.3 go version 
go version go1.21.3 darwin/arm64

The toolchain line declares a suggested toolchain to use with the module or workspace.

  1. have toolchain -> use higher one between toolchain and the go you run
  2. omit -> the same toolchain with go directive line. if go is omitted, go1.16 for go.mod and go1.18 for go.work.

Go toolchain refuses to load a module whose go version is higher(e.g. go1.22.1 wants to load go1.22.2).

The go line for each module sets the language version the compiler enforces when compiling packages in that module.

Go toolchain is useful when you want to use different version of go compiler to test, you can just set the env var so you can easily build your program with different go versions.

//go:build go1.22

package main

import (
    "fmt"
    "runtime"
)

func init() {
    fmt.Println(runtime.Version())
}

Before Go 1.21, Go toolchains treated the go line as an advisory requirement: if builds succeeded the toolchain assumed everything worked, and if not it printed a note about the potential version mismatch. Go 1.21 changed the go line to be a mandatory requirement instead. Before Go 1.21, toolchains did not require a module or workspace to have a go line greater than or equal to the go version required by each of its dependency modules.

GOTOOLCHAIN Setting

  • env first
  • default value of go env -w
  • $GOROOT/go.env: you can view by cat $(go env GOROOT)/go.env

    # This file contains the initial defaults for go command configuration.
    # Values set by 'go env -w' and written to the user's go/env file override.
    # The environment overrides everything else.
    
    # Use the Go module mirror and checksum database by default.
    # See https://proxy.golang.org for details.
    GOPROXY=https://proxy.golang.org,direct
    GOSUMDB=sum.golang.org
    
    # Automatically download newer toolchains as directed by go.mod files.
    # See https://go.dev/doc/toolchain for details.
    GOTOOLCHAIN=auto
    

  • the fallback value is GOTOOLCHAIN=local

GOTOOLCHAIN Selection

  • <name> -> always run the specified go version.
  • <name>+auto -> select as needed, will consult the toolchain in go.mod or go.work
  • <name>+path -> ditto, but it will disable the fallback. so if not found in PATH, error is reported.
  • local -> the bundle toolchain with the go you run.
  • GOTOOLCHAIN=auto equals GOTOOLCHAIN=local+auto
  • GOTOOLCHAIN=path = GOTOOLCHAIN=local+path

Manage Go Version by 'go get'

Go provides the go get support for go and toolchain versions.

$ go get [email protected] [email protected]
go: upgraded go 1.21 => 1.22.7
The go helps you to upgrade the go version to avoid
$ git --no-pager diff
diff --git a/go.mod b/go.mod
index 6d7863e..7aeee8c 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
-go 1.21
+go 1.22.7

Differences Between 'go' and 'toolchain' in go.mod/go.work

After introducing toolchain feature in go1.21, a new configuration line toolchain is introduced inside the go.mod/go.work. They're similar with each other, so a clarification is necessary.

go line

Before Go 1.21, Go toolchains treated the go line as an advisory requirement. It means that go will use its own toolchain for your project whatever the go line value in your module.

But after Go 1.21, it's a requirement and go compiler will refuse to continue compiling if the version in go line is higher than itself.

toolchain line

The toolchain line declares a suggested toolchain to use with the module or workspace.

Toolchain is a suggestion for the go command to run, and this suggestion could indeed affect users if their GOTOOLCHAIN setting is auto. The toolchain line version is usually higher than the go version.

For example, you're maintaining a module requires at least go1.22 but you highly recommend them to use a higher go version such as go1.23 because the newer, the better for a keep improving project.

But as a dependency of the other modules, you have had to set a minimum Go version requirement lower than the desired toolchain version. It's not viable to change your go line into a higher go version like go1.23 directly.

In such case, you can use toolchain line to suggest the compiler to select a higher version when it tries to compile. This is very useful as a library maintainer.