Relocatable go env (#2385)

**What type of PR is this?**

Feature

**What package or component does this PR mostly affect?**

go repository cache

**What does this PR do? Why is it needed?**
 go.env having user path dependent means that after performing bazel
    vendoring, users of another machine from the machine that performs
vendoring cannot use said dependency anymore without IGNORE-ing gazelle.


**Which issues(s) does this PR fix?**

Makes go.env path independent. Fixes #2382

**Other notes for review**

---------

Co-authored-by: Jay Conrod <jay@engflow.com>
diff --git a/internal/go_repository_cache.bzl b/internal/go_repository_cache.bzl
index 33f1c3c..ee1e432 100644
--- a/internal/go_repository_cache.bzl
+++ b/internal/go_repository_cache.bzl
@@ -36,8 +36,8 @@
     go_sdk_label = Label("@" + go_sdk_name + "//:ROOT")
 
     go_root = str(ctx.path(go_sdk_label).dirname)
-    go_path = str(ctx.path("."))
-    go_cache = str(ctx.path("gocache"))
+    go_path = ""  # default: the cache repo itself; recomputed by read_cache_env()
+    go_cache = ""  # default: <cache repo>/gocache; recomputed by read_cache_env()
     go_mod_cache = ""
     if getenv(ctx, "GO_REPOSITORY_USE_HOST_MODCACHE") == "1":
         extension = executable_extension(ctx)
@@ -62,7 +62,6 @@
         # This avoids a class of staleness issues, both with and without repo
         # content caches.
         "GOROOT": str(go_sdk_label),
-        "GOCACHE": go_cache,
 
         # Since Go v1.21.0, set GOTOOLCHAIN to "local" to use the current toolchain
         # of the Go SDK. This is required to avoid `go mod download` commands
@@ -76,6 +75,8 @@
     }
     if go_path:
         cache_env["GOPATH"] = go_path
+    if go_cache:
+        cache_env["GOCACHE"] = go_cache
     if go_mod_cache:
         cache_env["GOMODCACHE"] = go_mod_cache
 
@@ -83,7 +84,7 @@
         ctx,
         direct = ctx.attr.go_env,
         inherit = ctx.attr.go_env_inherit,
-        reserved = cache_env.keys(),
+        reserved = cache_env.keys() + ["GOCACHE", "GOPATH"],
     ))
     env_content = "\n".join(["{k}='{v}'\n".format(k = k, v = v) for k, v in cache_env.items()])
 
@@ -127,6 +128,9 @@
     # path and register a dependency by doing so.
     if env.get("GOROOT"):
         env["GOROOT"] = str(ctx.path(Label(env["GOROOT"])).dirname)
+    cache_dir = str(ctx.path(path).dirname)
+    env.setdefault("GOPATH", cache_dir)
+    env.setdefault("GOCACHE", cache_dir + "/gocache")
     return env
 
 # copied from rules_go. Keep in sync.
diff --git a/internal/go_repository_test.go b/internal/go_repository_test.go
index 2dff055..8ae7b2a 100644
--- a/internal/go_repository_test.go
+++ b/internal/go_repository_test.go
@@ -262,6 +262,25 @@
 		}
 	}
 
+	// The cache env must stay relocatable so that vendoring across users can use said go.env.
+	for _, line := range strings.Split(string(gotBytes), "\n") {
+		line = strings.TrimSpace(line)
+		if line == "" || strings.HasPrefix(line, "#") {
+			continue
+		}
+		key, value, found := strings.Cut(line, "=")
+		if !found {
+			t.Fatalf("malformed go.env line %q", line)
+		}
+		value = strings.Trim(value, "'")
+		if filepath.IsAbs(value) {
+			t.Errorf("go.env: %s=%s is an absolute path; go.env must be relocatable", key, value)
+		}
+		if strings.Contains(value, outputBase) {
+			t.Errorf("go.env: %s=%s references the output base; go.env must be relocatable", key, value)
+		}
+	}
+
 	goEnvBzlPath := filepath.Join(outputBase, "external/bazel_gazelle_go_repository_config", "go_env.bzl")
 	goEnvBzl, err := os.ReadFile(goEnvBzlPath)
 	if err != nil {