Manage multiple versions of the Go programming language

Installing Go

All versions of Go are available to download here: https://go.dev/dl/

The installation directory will be: /usr/local/go.

Once you have Go installed, you can then use the go command to either install go based tools/binaries or other versions of Go.

NOTE: Additionally, you can automate the install via a terminal using the following shell function (only tested on macOS):

# you can swap `ag` for `grep` if you prefer
alias golatest="curl -L https://github.com/golang/go/tags 2>&1 | ag '/golang/go/releases/tag/go[\w.]+' -o | cut -d '/' -f 6 | awk NR==1 | ag '\d.+' -o"

function go_install {
  if [ -z "$1" ]; then
    echo USAGE: go_install 1.18.1 OR go_install \$\(golatest\)
    return
  fi
  v=$1
  osname=$(uname -s | tr '[:upper:]' '[:lower:]')
  hardware=$(uname -m)
  mkdir -p ~/goversions
  if ! test -f "~/goversions/go$v.$osname-$hardware.pkg"; then
    printf "\nDownloading %s\n\n" "go$v.$osname-$hardware"
    curl -L -o ~/goversions/go$v.$osname-$hardware.pkg https://go.dev/dl/go$v.$osname-$hardware.pkg
  fi
  echo ""
  sudo rm -rf /usr/local/go; sudo installer -pkg ~/goversions/go$v.$osname-$hardware.pkg -target /usr/local/
  clear
  go version
}

Installing binaries

As of version go 1.16 go install is now responsible only for installing binaries, not modifying a go.mod file (that’s what go get is for).

go install example.com/cmd@v1.0.0
go install example.com/cmd@latest

NOTE: See reference documentation.

Installing different go versions

The following is the ‘official’ approach…

NOTE: See reference documentation.

go install golang.org/dl/go1.18@latest
go1.18 download
go1.18 version # go version go1.18 darwin/amd64
alias go=go1.18

If you want a simple binary overwrite of the global go command (for example, an alias doesn’t work with Makefiles that reference go because make targets run in a subshell):

go install golang.org/dl/go1.18@latest
go1.18 download
sudo cp $(which go1.18) $(which go)

NOTE: This requires sudo as it’s copying into /usr/local/....

If you want the latest ‘tip’ release (and maybe with additional/unreleased features, at the time of writing that might have included something like ‘fuzzing’ which was made available in go1.18):

go install golang.org/dl/gotip
gotip download dev.fuzz # also download a dev/tip specific tool/feature
gotip test -fuzz=FuzzFoo

Basic switcher using go.mod as reference

# This function identifies the go version specified in a project's go.mod
# It then attempts to switch to a binary of that version.
# If none exists it will instruct you how to download it.
#
# NOTE: Some tools, e.g. TinyGo, won't work with this approach because we're
# just replacing the go binary and the VERSION file, where the originally
# installed version of go will have things like CGO files that TinyGo will try
# to use and if those don't align with the version of the binary we've switched
# to, then it means TinyGo will fail to compile. 
#
# In that scenario you're better off using the `go_install` shell function 
# approach at the top of the page.
function go_version {
    if [ -f "go.mod" ]; then
        v=$(grep -E '^go \d.+$' ./go.mod | grep -oE '\d.+$')
        if [[ ! $(go version | grep "go$v") ]]; then
          echo ""
          echo "About to switch go version to: $v"
          if ! command -v "$HOME/go/bin/go$v" &> /dev/null
          then
            echo "run: go install golang.org/dl/go$v@latest && go$v download && sudo cp \$(which go$v) \$(which go)"
            return
          fi
          sudo cp $(which go$v) $(which go)
          echo -n go$v | sudo tee $(dirname $(dirname $(which go)))/VERSION > /dev/null
        fi
    fi
}

# To support the configuring our go environment we will override the cd
# command to call the go logic for checking the go version.
#
# NOTE: We use `command` and not `builtin` because the latter doesn't take into
# account anything available on the user's $PATH but also because it didn't
# work with the Starship prompt which seems to override cd also.
function cd {
  command cd "$@"
  RET=$?
  go_version
  return $RET
}

Unofficial: goenv

https://github.com/syndbg/goenv

The nice thing about goenv is that it lets you more easily automate a switch between projects using either GOENV_VERSION or .go-version (docs).