From 763ffebccec53ce64511bae4656a00972ceb3da6 Mon Sep 17 00:00:00 2001
From: Matthew Hughes <matthewhughes934@gmail.com>
Date: Sun, 3 Mar 2024 09:06:34 +0000
Subject: [PATCH] Configure environment to avoid toolchain installs

Force `go` to always use the local toolchain (i.e. the one the one that
shipped with the go command being run) via setting the `GOTOOLCHAIN`
environment variable to `local`[1]:

> When GOTOOLCHAIN is set to local, the go command always runs the
bundled Go toolchain.

This is how things are setup in the official Docker images (e.g.[2], see
also the discussion around that change[3]). The motivation behind this
is to:

* Reduce duplicate work, the action will install a version of Go, a
  toolchain will be detected, the toolchain will be detected and then
  another version of Go installed
* Avoid Unexpected behaviour: if you specify this action runs with some Go
  version (e.g. `1.21.0`) but your go.mod contains a `toolchain` or `go`
  directive for a newer version (e.g. `1.22.0`) then, without any other
  configuration/environment setup, any go commands will be run using go
  `1.22.0`
* TODO: link image

This will be a **breaking change** for some workflows. Given a `go.mod`
like:

    module proj

    go 1.22.0

Then running any `go` command, e.g. `go mod tidy`, in an environment
where only go versions before `1.22.0` were installed would previously
trigger a toolchain download of Go `1.22.0` and that version being used
to execute the command. With this change the above would error out with
something like:

> go: go.mod requires go >= 1.22.0 (running go 1.21.7;
GOTOOLCHAIN=local)

[1] https://go.dev/doc/toolchain#select
[2] https://github.com/docker-library/golang/blob/dae3405a325073e8ad7c8c378ebdf2540d8565c4/Dockerfile-linux.template#L163
[3] https://github.com/docker-library/golang/issues/472
---
 __tests__/setup-go.test.ts | 22 +++++++++++++++++-----
 dist/setup/index.js        | 16 ++++++++++++++++
 src/main.ts                | 18 ++++++++++++++++++
 3 files changed, 51 insertions(+), 5 deletions(-)

diff --git a/__tests__/setup-go.test.ts b/__tests__/setup-go.test.ts
index 70f2166..415c958 100644
--- a/__tests__/setup-go.test.ts
+++ b/__tests__/setup-go.test.ts
@@ -265,7 +265,7 @@ describe('setup-go', () => {
     expect(logSpy).toHaveBeenCalledWith(`Setup go version spec 1.13.0`);
   });
 
-  it('does not export any variables for Go versions >=1.9', async () => {
+  it('does not export GOROOT for Go versions >=1.9', async () => {
     inputs['go-version'] = '1.13.0';
     inSpy.mockImplementation(name => inputs[name]);
 
@@ -278,7 +278,7 @@ describe('setup-go', () => {
     });
 
     await main.run();
-    expect(vars).toStrictEqual({});
+    expect(vars).not.toHaveProperty('GOROOT');
   });
 
   it('exports GOROOT for Go versions <1.9', async () => {
@@ -294,9 +294,7 @@ describe('setup-go', () => {
     });
 
     await main.run();
-    expect(vars).toStrictEqual({
-      GOROOT: toolPath
-    });
+    expect(vars).toHaveProperty('GOROOT', toolPath);
   });
 
   it('finds a version of go already in the cache', async () => {
@@ -966,4 +964,18 @@ use .
       }
     );
   });
+
+  it('exports GOTOOLCHAIN and sets it in current process env', async () => {
+    inputs['go-version'] = '1.21.0';
+    inSpy.mockImplementation(name => inputs[name]);
+
+    const vars: {[key: string]: string} = {};
+    exportVarSpy.mockImplementation((name: string, val: string) => {
+      vars[name] = val;
+    });
+
+    await main.run();
+    expect(vars).toStrictEqual({GOTOOLCHAIN: 'local'});
+    expect(process.env).toHaveProperty('GOTOOLCHAIN', 'local');
+  });
 });
diff --git a/dist/setup/index.js b/dist/setup/index.js
index d4b4aa8..b44aec3 100644
--- a/dist/setup/index.js
+++ b/dist/setup/index.js
@@ -88649,6 +88649,7 @@ const os_1 = __importDefault(__nccwpck_require__(2037));
 function run() {
     return __awaiter(this, void 0, void 0, function* () {
         try {
+            setToolchain();
             //
             // versionSpec is optional.  If supplied, install / use from the tool cache
             // If not supplied then problem matchers will still be setup.  Useful for self-hosted.
@@ -88765,6 +88766,21 @@ function resolveVersionInput() {
     }
     return version;
 }
+function setToolchain() {
+    // docs: https://go.dev/doc/toolchain
+    // "local indicates the bundled Go toolchain (the one that shipped with the go command being run)"
+    // this is so any 'go' command is run with the selected Go version
+    // and doesn't trigger a toolchain download and run commands with that
+    // see e.g. issue #424
+    // and a similar discussion: https://github.com/docker-library/golang/issues/472
+    const toolchain = 'local';
+    const toolchainVar = 'GOTOOLCHAIN';
+    // set the value in process env so any `go` commands run as child-process
+    // don't cause toolchain downloads
+    process.env[toolchainVar] = toolchain;
+    // and in the runner env so e.g. a user running `go mod tidy` won't cause it
+    core.exportVariable(toolchainVar, toolchain);
+}
 
 
 /***/ }),
diff --git a/src/main.ts b/src/main.ts
index 690d277..a52951c 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -11,6 +11,7 @@ import os from 'os';
 
 export async function run() {
   try {
+    setToolchain();
     //
     // versionSpec is optional.  If supplied, install / use from the tool cache
     // If not supplied then problem matchers will still be setup.  Useful for self-hosted.
@@ -160,3 +161,20 @@ function resolveVersionInput(): string {
 
   return version;
 }
+
+function setToolchain() {
+  // docs: https://go.dev/doc/toolchain
+  // "local indicates the bundled Go toolchain (the one that shipped with the go command being run)"
+  // this is so any 'go' command is run with the selected Go version
+  // and doesn't trigger a toolchain download and run commands with that
+  // see e.g. issue #424
+  // and a similar discussion: https://github.com/docker-library/golang/issues/472
+  const toolchain = 'local';
+  const toolchainVar = 'GOTOOLCHAIN';
+
+  // set the value in process env so any `go` commands run as child-process
+  // don't cause toolchain downloads
+  process.env[toolchainVar] = toolchain;
+  // and in the runner env so e.g. a user running `go mod tidy` won't cause it
+  core.exportVariable(toolchainVar, toolchain);
+}