cmd/go: reject GOCACHE=off when the default cache is initialized

Allow GOCACHE=off only for operations that never actually write
anything to the cache (in which case the GOCACHE setting should not
matter at all).

Fixes #29127

Change-Id: I733d02cd2fbcf3671f5adcfb73522865d131e360
Reviewed-on: https://go-review.googlesource.com/c/153462
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
diff --git a/doc/go1.12.html b/doc/go1.12.html
index f036180..f204c97 100644
--- a/doc/go1.12.html
+++ b/doc/go1.12.html
@@ -91,11 +91,11 @@
 <h3 id="gocache">Build cache requirement</h3>
 
 <p>
-  The build cache is now required as a step toward eliminating
+  The <a href="/cmd/go/#hdr-Build_and_test_caching">build cache</a> is now
+  required as a step toward eliminating
   <code>$GOPATH/pkg</code>. Setting the environment variable
-  <code>GOCACHE=off</code> to disable the
-  <a href="/cmd/go/#hdr-Build_and_test_caching">build cache</a>
-  has no effect in Go 1.12.
+  <code>GOCACHE=off</code> will cause <code>go</code> commands that write to the
+  cache to fail.
 </p>
 
 <h3 id="binary-only">Binary-only packages</h3>
diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go
index c88a7c0..ac18230 100644
--- a/src/cmd/dist/test.go
+++ b/src/cmd/dist/test.go
@@ -519,7 +519,6 @@
 				}
 
 				// Run `go test fmt` in the moved GOROOT.
-				// Disable GOCACHE because it points back at the old GOROOT.
 				cmd := exec.Command(filepath.Join(moved, "bin", "go"), "test", "fmt")
 				cmd.Stdout = os.Stdout
 				cmd.Stderr = os.Stderr
@@ -529,7 +528,6 @@
 						cmd.Env = append(cmd.Env, e)
 					}
 				}
-				cmd.Env = append(cmd.Env, "GOCACHE=off")
 				err := cmd.Run()
 
 				if rerr := os.Rename(moved, goroot); rerr != nil {
diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go
index 6c31f98..d16ab3d 100644
--- a/src/cmd/go/go_test.go
+++ b/src/cmd/go/go_test.go
@@ -5011,7 +5011,8 @@
 	tg := testgo(t)
 	defer tg.cleanup()
 
-	tg.setenv("GOCACHE", "off")
+	tg.tempDir("cache")
+	tg.setenv("GOCACHE", tg.path("cache"))
 
 	tg.tempFile("main.go", `package main; import "C"; func main() { print("hello") }`)
 	src := tg.path("main.go")
@@ -5542,30 +5543,6 @@
 	}
 }
 
-func TestNoCache(t *testing.T) {
-	switch runtime.GOOS {
-	case "windows":
-		t.Skipf("no unwritable directories on %s", runtime.GOOS)
-	}
-	if os.Getuid() == 0 {
-		t.Skip("skipping test because running as root")
-	}
-
-	tg := testgo(t)
-	defer tg.cleanup()
-	tg.parallel()
-	tg.tempFile("triv.go", `package main; func main() {}`)
-	tg.must(os.MkdirAll(tg.path("unwritable"), 0555))
-	home := "HOME"
-	if runtime.GOOS == "plan9" {
-		home = "home"
-	}
-	tg.setenv(home, tg.path(filepath.Join("unwritable", "home")))
-	tg.unsetenv("GOCACHE")
-	tg.run("build", "-o", tg.path("triv"), tg.path("triv.go"))
-	tg.grepStderr("disabling cache", "did not disable cache")
-}
-
 func TestTestVet(t *testing.T) {
 	tooSlow(t)
 	tg := testgo(t)
@@ -5715,17 +5692,6 @@
 	tg.run("fmt", "-n", "exclude")
 }
 
-func TestRelativePkgdir(t *testing.T) {
-	tooSlow(t)
-	tg := testgo(t)
-	defer tg.cleanup()
-	tg.makeTempdir()
-	tg.setenv("GOCACHE", "off")
-	tg.cd(tg.tempdir)
-
-	tg.run("build", "-i", "-pkgdir=.", "runtime")
-}
-
 func TestGoTestMinusN(t *testing.T) {
 	// Intent here is to verify that 'go test -n' works without crashing.
 	// This reuses flag_test.go, but really any test would do.
@@ -6107,28 +6073,6 @@
 	}
 }
 
-// Issue 23264.
-func TestNoRelativeTmpdir(t *testing.T) {
-	tg := testgo(t)
-	defer tg.cleanup()
-
-	tg.tempFile("src/a/a.go", `package a`)
-	tg.cd(tg.path("."))
-	tg.must(os.Mkdir("tmp", 0777))
-
-	tg.setenv("GOCACHE", "off")
-	tg.setenv("GOPATH", tg.path("."))
-	tg.setenv("GOTMPDIR", "tmp")
-	tg.run("build", "-work", "a")
-	tg.grepStderr("WORK=[^t]", "work should be absolute path")
-
-	tg.unsetenv("GOTMPDIR")
-	tg.setenv("TMP", "tmp")    // windows
-	tg.setenv("TMPDIR", "tmp") // unix
-	tg.run("build", "-work", "a")
-	tg.grepStderr("WORK=[^t]", "work should be absolute path")
-}
-
 // Issue 24704.
 func TestLinkerTmpDirIsDeleted(t *testing.T) {
 	skipIfGccgo(t, "gccgo does not use cmd/link")
diff --git a/src/cmd/go/internal/cache/default.go b/src/cmd/go/internal/cache/default.go
index 4a69bf2..52a1fc8 100644
--- a/src/cmd/go/internal/cache/default.go
+++ b/src/cmd/go/internal/cache/default.go
@@ -10,6 +10,8 @@
 	"os"
 	"path/filepath"
 	"sync"
+
+	"cmd/go/internal/base"
 )
 
 // Default returns the default cache to use, or nil if no cache should be used.
@@ -34,15 +36,12 @@
 // initDefaultCache does the work of finding the default cache
 // the first time Default is called.
 func initDefaultCache() {
-	dir, showWarnings := defaultDir()
+	dir := DefaultDir()
 	if dir == "off" {
-		return
+		die()
 	}
 	if err := os.MkdirAll(dir, 0777); err != nil {
-		if showWarnings {
-			fmt.Fprintf(os.Stderr, "go: disabling cache (%s) due to initialization failure: %s\n", dir, err)
-		}
-		return
+		base.Fatalf("failed to initialize build cache at %s: %s\n", dir, err)
 	}
 	if _, err := os.Stat(filepath.Join(dir, "README")); err != nil {
 		// Best effort.
@@ -51,10 +50,7 @@
 
 	c, err := Open(dir)
 	if err != nil {
-		if showWarnings {
-			fmt.Fprintf(os.Stderr, "go: disabling cache (%s) due to initialization failure: %s\n", dir, err)
-		}
-		return
+		base.Fatalf("failed to initialize build cache at %s: %s\n", dir, err)
 	}
 	defaultCache = c
 }
@@ -62,34 +58,26 @@
 // DefaultDir returns the effective GOCACHE setting.
 // It returns "off" if the cache is disabled.
 func DefaultDir() string {
-	dir, _ := defaultDir()
-	return dir
-}
-
-// defaultDir returns the effective GOCACHE setting.
-// It returns "off" if the cache is disabled.
-// The second return value reports whether warnings should
-// be shown if the cache fails to initialize.
-func defaultDir() (string, bool) {
 	dir := os.Getenv("GOCACHE")
 	if dir != "" {
-		return dir, true
+		return dir
 	}
 
 	// Compute default location.
 	dir, err := os.UserCacheDir()
 	if err != nil {
-		return "off", true
+		return "off"
 	}
-	dir = filepath.Join(dir, "go-build")
+	return filepath.Join(dir, "go-build")
+}
 
-	// Do this after filepath.Join, so that the path has been cleaned.
-	showWarnings := true
-	switch dir {
-	case "/.cache/go-build":
-		// probably docker run with -u flag
-		// https://golang.org/issue/26280
-		showWarnings = false
+// die calls base.Fatalf with a message explaining why DefaultDir was "off".
+func die() {
+	if os.Getenv("GOCACHE") == "off" {
+		base.Fatalf("build cache is disabled by GOCACHE=off, but required as of Go 1.12")
 	}
-	return dir, showWarnings
+	if _, err := os.UserCacheDir(); err != nil {
+		base.Fatalf("build cache is required, but could not be located: %v", err)
+	}
+	panic(fmt.Sprintf("cache.die called unexpectedly with cache.DefaultDir() = %s", DefaultDir()))
 }
diff --git a/src/cmd/go/internal/cache/default_unix_test.go b/src/cmd/go/internal/cache/default_unix_test.go
deleted file mode 100644
index 1458201..0000000
--- a/src/cmd/go/internal/cache/default_unix_test.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2018 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build !windows,!darwin,!plan9
-
-package cache
-
-import (
-	"os"
-	"strings"
-	"testing"
-)
-
-func TestDefaultDir(t *testing.T) {
-	goCacheDir := "/tmp/test-go-cache"
-	xdgCacheDir := "/tmp/test-xdg-cache"
-	homeDir := "/tmp/test-home"
-
-	// undo env changes when finished
-	defer func(GOCACHE, XDG_CACHE_HOME, HOME string) {
-		os.Setenv("GOCACHE", GOCACHE)
-		os.Setenv("XDG_CACHE_HOME", XDG_CACHE_HOME)
-		os.Setenv("HOME", HOME)
-	}(os.Getenv("GOCACHE"), os.Getenv("XDG_CACHE_HOME"), os.Getenv("HOME"))
-
-	os.Setenv("GOCACHE", goCacheDir)
-	os.Setenv("XDG_CACHE_HOME", xdgCacheDir)
-	os.Setenv("HOME", homeDir)
-
-	dir, showWarnings := defaultDir()
-	if dir != goCacheDir {
-		t.Errorf("Cache DefaultDir %q should be $GOCACHE %q", dir, goCacheDir)
-	}
-	if !showWarnings {
-		t.Error("Warnings should be shown when $GOCACHE is set")
-	}
-
-	os.Unsetenv("GOCACHE")
-	dir, showWarnings = defaultDir()
-	if !strings.HasPrefix(dir, xdgCacheDir+"/") {
-		t.Errorf("Cache DefaultDir %q should be under $XDG_CACHE_HOME %q when $GOCACHE is unset", dir, xdgCacheDir)
-	}
-	if !showWarnings {
-		t.Error("Warnings should be shown when $XDG_CACHE_HOME is set")
-	}
-
-	os.Unsetenv("XDG_CACHE_HOME")
-	dir, showWarnings = defaultDir()
-	if !strings.HasPrefix(dir, homeDir+"/.cache/") {
-		t.Errorf("Cache DefaultDir %q should be under $HOME/.cache %q when $GOCACHE and $XDG_CACHE_HOME are unset", dir, homeDir+"/.cache")
-	}
-	if !showWarnings {
-		t.Error("Warnings should be shown when $HOME is not /")
-	}
-
-	os.Unsetenv("HOME")
-	if dir, _ := defaultDir(); dir != "off" {
-		t.Error("Cache not disabled when $GOCACHE, $XDG_CACHE_HOME, and $HOME are unset")
-	}
-
-	os.Setenv("HOME", "/")
-	if _, showWarnings := defaultDir(); showWarnings {
-		// https://golang.org/issue/26280
-		t.Error("Cache initialization warnings should be squelched when $GOCACHE and $XDG_CACHE_HOME are unset and $HOME is /")
-	}
-}
diff --git a/src/cmd/go/testdata/script/build_GOTMPDIR.txt b/src/cmd/go/testdata/script/build_GOTMPDIR.txt
index 4c387af..ea06dcc 100644
--- a/src/cmd/go/testdata/script/build_GOTMPDIR.txt
+++ b/src/cmd/go/testdata/script/build_GOTMPDIR.txt
@@ -1,6 +1,8 @@
+# Set GOCACHE to a clean directory to ensure that 'go build' has work to report.
+env GOCACHE=$WORK/gocache
+
 # Build should use GOTMPDIR if set.
 env GOTMPDIR=$WORK/my-favorite-tmpdir
-env GOCACHE=off
 mkdir $GOTMPDIR
 go build -work hello.go
 stderr ^WORK=.*my-favorite-tmpdir
@@ -8,4 +10,3 @@
 -- hello.go --
 package main
 func main() { println("hello") }
-
diff --git a/src/cmd/go/testdata/script/build_nocache.txt b/src/cmd/go/testdata/script/build_nocache.txt
new file mode 100644
index 0000000..61ea5c5
--- /dev/null
+++ b/src/cmd/go/testdata/script/build_nocache.txt
@@ -0,0 +1,19 @@
+# Set GOCACHE to a directory that doesn't allow writes.
+[windows] skip # Does not support unwritable directories.
+[root] skip # Can write to unwritable directories.
+
+mkdir $WORK/unwritable/home
+chmod 0555 $WORK/unwritable/home
+[!plan9] env HOME=$WORK/unwritable/home
+[plan9] env home=$WORK/unwritable/home
+
+env GOCACHE=$WORK/unwritable/home
+
+# As of Go 1.12, the module cache is required:
+# failure to write to it should cause builds to fail.
+! go build -o triv triv.go
+stderr 'failed to initialize build cache.* permission denied'
+
+-- triv.go --
+package main
+func main() {}
diff --git a/src/cmd/go/testdata/script/build_relative_pkgdir.txt b/src/cmd/go/testdata/script/build_relative_pkgdir.txt
new file mode 100644
index 0000000..76098a0
--- /dev/null
+++ b/src/cmd/go/testdata/script/build_relative_pkgdir.txt
@@ -0,0 +1,7 @@
+# Regression test for golang.org/issue/21309: accept relative -pkgdir argument.
+
+[short] skip
+
+mkdir $WORK/gocache
+env GOCACHE=$WORK/gocache
+go build -i -pkgdir=. runtime
diff --git a/src/cmd/go/testdata/script/build_relative_tmpdir.txt b/src/cmd/go/testdata/script/build_relative_tmpdir.txt
new file mode 100644
index 0000000..9490a28
--- /dev/null
+++ b/src/cmd/go/testdata/script/build_relative_tmpdir.txt
@@ -0,0 +1,16 @@
+# If GOTMPDIR is relative, 'go build' should derive an absolute $WORK directory.
+cd $WORK
+mkdir tmp
+env GOTMPDIR=tmp
+go build -work a
+stderr 'WORK=\$WORK' # the test script itself converts the absolute directory back to $WORK
+
+# Similarly if TMP/TMPDIR is relative.
+env GOTMPDIR=
+env TMP=tmp    # Windows
+env TMPDIR=tmp # Unix
+go build -work a
+stderr 'WORK=\$WORK'
+
+-- a/a.go --
+package a
diff --git a/src/cmd/go/testdata/script/cache_unix.txt b/src/cmd/go/testdata/script/cache_unix.txt
new file mode 100644
index 0000000..f700ebe
--- /dev/null
+++ b/src/cmd/go/testdata/script/cache_unix.txt
@@ -0,0 +1,34 @@
+# Integration test for cache directory calculation (cmd/go/internal/cache).
+
+[windows] skip
+[darwin] skip
+[plan9] skip
+
+mkdir $WORK/gocache
+mkdir $WORK/xdg
+mkdir $WORK/home
+
+# Set GOCACHE, XDG_CACHE_HOME, and HOME.
+env GOCACHE=$WORK/gocache
+env XDG_CACHE_HOME=$WORK/xdg
+env HOME=$WORK/home
+
+# With all three set, we should prefer GOCACHE.
+go env GOCACHE
+stdout '\$WORK/gocache$'
+
+# Without GOCACHE, we should prefer XDG_CACHE_HOME over HOME.
+env GOCACHE=
+go env GOCACHE
+stdout '\$WORK/xdg/go-build$$'
+
+# With only HOME set, we should use $HOME/.cache.
+env XDG_CACHE_HOME=
+go env GOCACHE
+stdout '\$WORK/home/.cache/go-build$'
+
+# With no guidance from the environment, we must disable the cache, but that
+# should not cause commands that do not write to the cache to fail.
+env HOME=
+go env GOCACHE
+stdout 'off'