cmd/go: introduce the GOVERSION env variable

This is an extra variable available via 'go env', but not read from the
user's environment. It corresponds to the same string that
runtime.Version returns, assuming a program is built by the same version
of Go.

It's similar to the output of 'go version', but without the "go version"
prefix nor the "$GOOS/$GOARCH" suffix.

The main use case here is tools, which often use 'go env' to query basic
information about the installed Go tree. Its version was one missing
piece of information, which required an extra call to 'go version'
before this change.

Fixes #41116.

Change-Id: I5c9d8c2ba856c816c9f4c462ba73c907b3441445
Reviewed-on: https://go-review.googlesource.com/c/go/+/265637
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
Trust: Jay Conrod <jayconrod@google.com>
Trust: Daniel Martí <mvdan@mvdan.cc>
diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go
index e8bfff1..4707657 100644
--- a/src/cmd/go/alldocs.go
+++ b/src/cmd/go/alldocs.go
@@ -1916,6 +1916,8 @@
 // 		If module-aware mode is disabled, GOMOD will be the empty string.
 // 	GOTOOLDIR
 // 		The directory where the go tools (compile, cover, doc, etc...) are installed.
+// 	GOVERSION
+// 		The version of the installed Go tree, as reported by runtime.Version.
 //
 //
 // File types
diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go
index ee0cd8e2..a02231f 100644
--- a/src/cmd/go/go_test.go
+++ b/src/cmd/go/go_test.go
@@ -1886,6 +1886,18 @@
 	tg.grepStdout("gcc", "CC not found")
 	tg.run("env", "GOGCCFLAGS")
 	tg.grepStdout("-ffaster", "CC arguments not found")
+
+	tg.run("env", "GOVERSION")
+	envVersion := strings.TrimSpace(tg.stdout.String())
+
+	tg.run("version")
+	cmdVersion := strings.TrimSpace(tg.stdout.String())
+
+	// If 'go version' is "go version <version> <goos>/<goarch>", then
+	// 'go env GOVERSION' is just "<version>".
+	if cmdVersion == envVersion || !strings.Contains(cmdVersion, envVersion) {
+		t.Fatalf("'go env GOVERSION' %q should be a shorter substring of 'go version' %q", envVersion, cmdVersion)
+	}
 }
 
 const (
diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go
index d65ace8..46af36e 100644
--- a/src/cmd/go/internal/envcmd/env.go
+++ b/src/cmd/go/internal/envcmd/env.go
@@ -88,6 +88,7 @@
 		{Name: "GOTMPDIR", Value: cfg.Getenv("GOTMPDIR")},
 		{Name: "GOTOOLDIR", Value: base.ToolDir},
 		{Name: "GOVCS", Value: cfg.GOVCS},
+		{Name: "GOVERSION", Value: runtime.Version()},
 	}
 
 	if work.GccgoBin != "" {
@@ -399,7 +400,7 @@
 
 func checkEnvWrite(key, val string) error {
 	switch key {
-	case "GOEXE", "GOGCCFLAGS", "GOHOSTARCH", "GOHOSTOS", "GOMOD", "GOTOOLDIR":
+	case "GOEXE", "GOGCCFLAGS", "GOHOSTARCH", "GOHOSTOS", "GOMOD", "GOTOOLDIR", "GOVERSION":
 		return fmt.Errorf("%s cannot be modified", key)
 	case "GOENV":
 		return fmt.Errorf("%s can only be set using the OS environment", key)
diff --git a/src/cmd/go/internal/help/helpdoc.go b/src/cmd/go/internal/help/helpdoc.go
index 50cf911..98f5844 100644
--- a/src/cmd/go/internal/help/helpdoc.go
+++ b/src/cmd/go/internal/help/helpdoc.go
@@ -632,6 +632,8 @@
 		If module-aware mode is disabled, GOMOD will be the empty string.
 	GOTOOLDIR
 		The directory where the go tools (compile, cover, doc, etc...) are installed.
+	GOVERSION
+		The version of the installed Go tree, as reported by runtime.Version.
 	`,
 }
 
diff --git a/src/cmd/go/testdata/script/env_write.txt b/src/cmd/go/testdata/script/env_write.txt
index 0af22ed..bda1e57 100644
--- a/src/cmd/go/testdata/script/env_write.txt
+++ b/src/cmd/go/testdata/script/env_write.txt
@@ -69,6 +69,8 @@
 stderr 'unknown go command variable GODEBUG'
 ! go env -w GOEXE=.bat
 stderr 'GOEXE cannot be modified'
+! go env -w GOVERSION=customversion
+stderr 'GOVERSION cannot be modified'
 ! go env -w GOENV=/env
 stderr 'GOENV can only be set using the OS environment'