mirror of
				https://github.com/actions/setup-go.git
				synced 2025-10-31 16:23:42 +00:00 
			
		
		
		
	Improve toolchain handling (#460)
* 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: if the `toolchain` version in `go.mod` was
  greated than the `go` version, the version from the `go` directive
  would be installed, then Go would detect the `toolchain` version and
  additionally install that
* 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`
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] dae3405a32/Dockerfile-linux.template (L163)
[3] https://github.com/docker-library/golang/issues/472
* Prefer installing version from `toolchain` directive
Prefer this over the version from the `go` directive. Per the docs[1]
> The toolchain line declares a suggested toolchain to use with the
module or workspace
It seems reasonable to use this, since running this action in a
directory containing a `go.mod` (or `go.work`) suggests the user is
wishing to work _with the module or workspace_.
Link: https://go.dev/doc/toolchain#config [1]
Issue: https://github.com/actions/setup-go/issues/457
* squash! Configure environment to avoid toolchain installs
Only modify env if `GOTOOLCHAIN` is not set
* squash! Prefer installing version from `toolchain` directive
Avoid installing from `toolchain` if `GOTOOLCHAIN` is `local`, also
better regex for matching toolchain directive
			
			
This commit is contained in:
		
							parent
							
								
									e75c3e80bc
								
							
						
					
					
						commit
						1d76b952eb
					
				
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @ -191,9 +191,15 @@ steps: | |||||||
| 
 | 
 | ||||||
| ## Getting go version from the go.mod file | ## Getting go version from the go.mod file | ||||||
| 
 | 
 | ||||||
| The `go-version-file` input accepts a path to a `go.mod` file or a `go.work` file that contains the version of Go to be used by a project. | The `go-version-file` input accepts a path to a `go.mod` file or a `go.work` | ||||||
|  | file that contains the version of Go to be used by a project. The version taken | ||||||
|  | from thils file will be: | ||||||
|  | 
 | ||||||
|  |   - The version from the `toolchain` directive, if there is one, otherwise | ||||||
|  |   - The version from the `go` directive | ||||||
|  | 
 | ||||||
|  | The version can specify a patch version or omit it altogether (e.g., `go 1.22.0` or `go 1.22`). | ||||||
| 
 | 
 | ||||||
| The `go` directive in `go.mod` can specify a patch version or omit it altogether (e.g., `go 1.22.0` or `go 1.22`).   |  | ||||||
| If a patch version is specified, that specific patch version will be used.   | If a patch version is specified, that specific patch version will be used.   | ||||||
| If no patch version is specified, it will search for the latest available patch version in the cache, | If no patch version is specified, it will search for the latest available patch version in the cache, | ||||||
| [versions-manifest.json](https://github.com/actions/go-versions/blob/main/versions-manifest.json), and the | [versions-manifest.json](https://github.com/actions/go-versions/blob/main/versions-manifest.json), and the | ||||||
|  | |||||||
| @ -129,6 +129,9 @@ describe('setup-go', () => { | |||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   afterEach(() => { |   afterEach(() => { | ||||||
|  |     // clear out env var set during 'run'
 | ||||||
|  |     delete process.env[im.GOTOOLCHAIN_ENV_VAR]; | ||||||
|  | 
 | ||||||
|     //jest.resetAllMocks();
 |     //jest.resetAllMocks();
 | ||||||
|     jest.clearAllMocks(); |     jest.clearAllMocks(); | ||||||
|     //jest.restoreAllMocks();
 |     //jest.restoreAllMocks();
 | ||||||
| @ -285,7 +288,7 @@ describe('setup-go', () => { | |||||||
|     expect(logSpy).toHaveBeenCalledWith(`Setup go version spec 1.13.0`); |     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'; |     inputs['go-version'] = '1.13.0'; | ||||||
|     inSpy.mockImplementation(name => inputs[name]); |     inSpy.mockImplementation(name => inputs[name]); | ||||||
| 
 | 
 | ||||||
| @ -298,7 +301,7 @@ describe('setup-go', () => { | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     await main.run(); |     await main.run(); | ||||||
|     expect(vars).toStrictEqual({}); |     expect(vars).not.toHaveProperty('GOROOT'); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   it('exports GOROOT for Go versions <1.9', async () => { |   it('exports GOROOT for Go versions <1.9', async () => { | ||||||
| @ -314,9 +317,7 @@ describe('setup-go', () => { | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     await main.run(); |     await main.run(); | ||||||
|     expect(vars).toStrictEqual({ |     expect(vars).toHaveProperty('GOROOT', toolPath); | ||||||
|       GOROOT: toolPath |  | ||||||
|     }); |  | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   it('finds a version of go already in the cache', async () => { |   it('finds a version of go already in the cache', async () => { | ||||||
| @ -989,4 +990,104 @@ use . | |||||||
|       } |       } | ||||||
|     ); |     ); | ||||||
|   }); |   }); | ||||||
|  | 
 | ||||||
|  |   describe('go-version-file-toolchain', () => { | ||||||
|  |     const goVersions = ['1.22.0', '1.21rc2', '1.18']; | ||||||
|  |     const placeholderVersion = '1.19'; | ||||||
|  |     const buildGoMod = ( | ||||||
|  |       goVersion: string, | ||||||
|  |       toolchainVersion: string | ||||||
|  |     ) => `module example.com/mymodule
 | ||||||
|  | 
 | ||||||
|  | go ${goVersion} | ||||||
|  | 
 | ||||||
|  | toolchain go${toolchainVersion} | ||||||
|  | 
 | ||||||
|  | require ( | ||||||
|  | 	example.com/othermodule v1.2.3 | ||||||
|  | 	example.com/thismodule v1.2.3 | ||||||
|  | 	example.com/thatmodule v1.2.3 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | replace example.com/thatmodule => ../thatmodule | ||||||
|  | exclude example.com/thismodule v1.3.0 | ||||||
|  | `;
 | ||||||
|  | 
 | ||||||
|  |     const buildGoWork = ( | ||||||
|  |       goVersion: string, | ||||||
|  |       toolchainVersion: string | ||||||
|  |     ) => `go 1.19
 | ||||||
|  | 
 | ||||||
|  | toolchain go${toolchainVersion} | ||||||
|  | 
 | ||||||
|  | use . | ||||||
|  | 
 | ||||||
|  | `;
 | ||||||
|  | 
 | ||||||
|  |     goVersions.forEach(version => { | ||||||
|  |       [ | ||||||
|  |         { | ||||||
|  |           goVersionfile: 'go.mod', | ||||||
|  |           fileContents: Buffer.from(buildGoMod(placeholderVersion, version)), | ||||||
|  |           expected_version: version, | ||||||
|  |           desc: 'from toolchain directive' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           goVersionfile: 'go.work', | ||||||
|  |           fileContents: Buffer.from(buildGoMod(placeholderVersion, version)), | ||||||
|  |           expected_version: version, | ||||||
|  |           desc: 'from toolchain directive' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           goVersionfile: 'go.mod', | ||||||
|  |           fileContents: Buffer.from(buildGoMod(placeholderVersion, version)), | ||||||
|  |           gotoolchain_env: 'local', | ||||||
|  |           expected_version: placeholderVersion, | ||||||
|  |           desc: 'from go directive when GOTOOLCHAIN is local' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           goVersionfile: 'go.work', | ||||||
|  |           fileContents: Buffer.from(buildGoMod(placeholderVersion, version)), | ||||||
|  |           gotoolchain_env: 'local', | ||||||
|  |           expected_version: placeholderVersion, | ||||||
|  |           desc: 'from go directive when GOTOOLCHAIN is local' | ||||||
|  |         } | ||||||
|  |       ].forEach(test => { | ||||||
|  |         it(`reads version (${version}) in ${test.goVersionfile} ${test.desc}`, async () => { | ||||||
|  |           inputs['go-version-file'] = test.goVersionfile; | ||||||
|  |           if (test.gotoolchain_env !== undefined) { | ||||||
|  |             process.env[im.GOTOOLCHAIN_ENV_VAR] = test.gotoolchain_env; | ||||||
|  |           } | ||||||
|  |           existsSpy.mockImplementation(() => true); | ||||||
|  |           readFileSpy.mockImplementation(() => Buffer.from(test.fileContents)); | ||||||
|  | 
 | ||||||
|  |           await main.run(); | ||||||
|  | 
 | ||||||
|  |           expect(logSpy).toHaveBeenCalledWith( | ||||||
|  |             `Setup go version spec ${test.expected_version}` | ||||||
|  |           ); | ||||||
|  |           expect(logSpy).toHaveBeenCalledWith( | ||||||
|  |             `Attempting to download ${test.expected_version}...` | ||||||
|  |           ); | ||||||
|  |           expect(logSpy).toHaveBeenCalledWith( | ||||||
|  |             `matching ${test.expected_version}...` | ||||||
|  |           ); | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   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'); | ||||||
|  |   }); | ||||||
| }); | }); | ||||||
|  | |||||||
							
								
								
									
										31
									
								
								dist/setup/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								dist/setup/index.js
									
									
									
									
										vendored
									
									
								
							| @ -94312,6 +94312,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { | |||||||
|     return (mod && mod.__esModule) ? mod : { "default": mod }; |     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||||||
| }; | }; | ||||||
| Object.defineProperty(exports, "__esModule", ({ value: true })); | Object.defineProperty(exports, "__esModule", ({ value: true })); | ||||||
|  | exports.GOTOOLCHAIN_LOCAL_VAL = exports.GOTOOLCHAIN_ENV_VAR = void 0; | ||||||
| exports.getGo = getGo; | exports.getGo = getGo; | ||||||
| exports.extractGoArchive = extractGoArchive; | exports.extractGoArchive = extractGoArchive; | ||||||
| exports.getManifest = getManifest; | exports.getManifest = getManifest; | ||||||
| @ -94330,6 +94331,8 @@ const sys = __importStar(__nccwpck_require__(5632)); | |||||||
| const fs_1 = __importDefault(__nccwpck_require__(7147)); | const fs_1 = __importDefault(__nccwpck_require__(7147)); | ||||||
| const os_1 = __importDefault(__nccwpck_require__(2037)); | const os_1 = __importDefault(__nccwpck_require__(2037)); | ||||||
| const utils_1 = __nccwpck_require__(1314); | const utils_1 = __nccwpck_require__(1314); | ||||||
|  | exports.GOTOOLCHAIN_ENV_VAR = 'GOTOOLCHAIN'; | ||||||
|  | exports.GOTOOLCHAIN_LOCAL_VAL = 'local'; | ||||||
| const MANIFEST_REPO_OWNER = 'actions'; | const MANIFEST_REPO_OWNER = 'actions'; | ||||||
| const MANIFEST_REPO_NAME = 'go-versions'; | const MANIFEST_REPO_NAME = 'go-versions'; | ||||||
| const MANIFEST_REPO_BRANCH = 'main'; | const MANIFEST_REPO_BRANCH = 'main'; | ||||||
| @ -94663,8 +94666,18 @@ function parseGoVersionFile(versionFilePath) { | |||||||
|     const contents = fs_1.default.readFileSync(versionFilePath).toString(); |     const contents = fs_1.default.readFileSync(versionFilePath).toString(); | ||||||
|     if (path.basename(versionFilePath) === 'go.mod' || |     if (path.basename(versionFilePath) === 'go.mod' || | ||||||
|         path.basename(versionFilePath) === 'go.work') { |         path.basename(versionFilePath) === 'go.work') { | ||||||
|         const match = contents.match(/^go (\d+(\.\d+)*)/m); |         // for backwards compatibility: use version from go directive if
 | ||||||
|         return match ? match[1] : ''; |         // 'GOTOOLCHAIN' has been explicitly set
 | ||||||
|  |         if (process.env[exports.GOTOOLCHAIN_ENV_VAR] !== exports.GOTOOLCHAIN_LOCAL_VAL) { | ||||||
|  |             // toolchain directive: https://go.dev/ref/mod#go-mod-file-toolchain
 | ||||||
|  |             const matchToolchain = contents.match(/^toolchain go(1\.\d+(?:\.\d+|rc\d+)?)/m); | ||||||
|  |             if (matchToolchain) { | ||||||
|  |                 return matchToolchain[1]; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         // go directive: https://go.dev/ref/mod#go-mod-file-go
 | ||||||
|  |         const matchGo = contents.match(/^go (\d+(\.\d+)*)/m); | ||||||
|  |         return matchGo ? matchGo[1] : ''; | ||||||
|     } |     } | ||||||
|     return contents.trim(); |     return contents.trim(); | ||||||
| } | } | ||||||
| @ -94782,6 +94795,7 @@ function run() { | |||||||
|             // If not supplied then problem matchers will still be setup.  Useful for self-hosted.
 |             // If not supplied then problem matchers will still be setup.  Useful for self-hosted.
 | ||||||
|             //
 |             //
 | ||||||
|             const versionSpec = resolveVersionInput(); |             const versionSpec = resolveVersionInput(); | ||||||
|  |             setGoToolchain(); | ||||||
|             const cache = core.getBooleanInput('cache'); |             const cache = core.getBooleanInput('cache'); | ||||||
|             core.info(`Setup go version spec ${versionSpec}`); |             core.info(`Setup go version spec ${versionSpec}`); | ||||||
|             let arch = core.getInput('architecture'); |             let arch = core.getInput('architecture'); | ||||||
| @ -94890,6 +94904,19 @@ function resolveVersionInput() { | |||||||
|     } |     } | ||||||
|     return version; |     return version; | ||||||
| } | } | ||||||
|  | function setGoToolchain() { | ||||||
|  |     // 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.
 | ||||||
|  |     // Set the value in process env so any `go` commands run as child-process
 | ||||||
|  |     // don't cause toolchain downloads
 | ||||||
|  |     process.env[installer.GOTOOLCHAIN_ENV_VAR] = installer.GOTOOLCHAIN_LOCAL_VAL; | ||||||
|  |     // and in the runner env so e.g. a user running `go mod tidy` won't cause it
 | ||||||
|  |     core.exportVariable(installer.GOTOOLCHAIN_ENV_VAR, installer.GOTOOLCHAIN_LOCAL_VAL); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /***/ }), | /***/ }), | ||||||
|  | |||||||
| @ -8,6 +8,8 @@ import fs from 'fs'; | |||||||
| import os from 'os'; | import os from 'os'; | ||||||
| import {StableReleaseAlias, isSelfHosted} from './utils'; | import {StableReleaseAlias, isSelfHosted} from './utils'; | ||||||
| 
 | 
 | ||||||
|  | export const GOTOOLCHAIN_ENV_VAR = 'GOTOOLCHAIN'; | ||||||
|  | export const GOTOOLCHAIN_LOCAL_VAL = 'local'; | ||||||
| const MANIFEST_REPO_OWNER = 'actions'; | const MANIFEST_REPO_OWNER = 'actions'; | ||||||
| const MANIFEST_REPO_NAME = 'go-versions'; | const MANIFEST_REPO_NAME = 'go-versions'; | ||||||
| const MANIFEST_REPO_BRANCH = 'main'; | const MANIFEST_REPO_BRANCH = 'main'; | ||||||
| @ -495,8 +497,21 @@ export function parseGoVersionFile(versionFilePath: string): string { | |||||||
|     path.basename(versionFilePath) === 'go.mod' || |     path.basename(versionFilePath) === 'go.mod' || | ||||||
|     path.basename(versionFilePath) === 'go.work' |     path.basename(versionFilePath) === 'go.work' | ||||||
|   ) { |   ) { | ||||||
|     const match = contents.match(/^go (\d+(\.\d+)*)/m); |     // for backwards compatibility: use version from go directive if
 | ||||||
|     return match ? match[1] : ''; |     // 'GOTOOLCHAIN' has been explicitly set
 | ||||||
|  |     if (process.env[GOTOOLCHAIN_ENV_VAR] !== GOTOOLCHAIN_LOCAL_VAL) { | ||||||
|  |       // toolchain directive: https://go.dev/ref/mod#go-mod-file-toolchain
 | ||||||
|  |       const matchToolchain = contents.match( | ||||||
|  |         /^toolchain go(1\.\d+(?:\.\d+|rc\d+)?)/m | ||||||
|  |       ); | ||||||
|  |       if (matchToolchain) { | ||||||
|  |         return matchToolchain[1]; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // go directive: https://go.dev/ref/mod#go-mod-file-go
 | ||||||
|  |     const matchGo = contents.match(/^go (\d+(\.\d+)*)/m); | ||||||
|  |     return matchGo ? matchGo[1] : ''; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return contents.trim(); |   return contents.trim(); | ||||||
|  | |||||||
							
								
								
									
										18
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/main.ts
									
									
									
									
									
								
							| @ -16,6 +16,7 @@ export async function run() { | |||||||
|     // If not supplied then problem matchers will still be setup.  Useful for self-hosted.
 |     // If not supplied then problem matchers will still be setup.  Useful for self-hosted.
 | ||||||
|     //
 |     //
 | ||||||
|     const versionSpec = resolveVersionInput(); |     const versionSpec = resolveVersionInput(); | ||||||
|  |     setGoToolchain(); | ||||||
| 
 | 
 | ||||||
|     const cache = core.getBooleanInput('cache'); |     const cache = core.getBooleanInput('cache'); | ||||||
|     core.info(`Setup go version spec ${versionSpec}`); |     core.info(`Setup go version spec ${versionSpec}`); | ||||||
| @ -160,3 +161,20 @@ function resolveVersionInput(): string { | |||||||
| 
 | 
 | ||||||
|   return version; |   return version; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | function setGoToolchain() { | ||||||
|  |   // 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.
 | ||||||
|  |   // Set the value in process env so any `go` commands run as child-process
 | ||||||
|  |   // don't cause toolchain downloads
 | ||||||
|  |   process.env[installer.GOTOOLCHAIN_ENV_VAR] = installer.GOTOOLCHAIN_LOCAL_VAL; | ||||||
|  |   // and in the runner env so e.g. a user running `go mod tidy` won't cause it
 | ||||||
|  |   core.exportVariable( | ||||||
|  |     installer.GOTOOLCHAIN_ENV_VAR, | ||||||
|  |     installer.GOTOOLCHAIN_LOCAL_VAL | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user