2020-02-09 00:21:39 -05:00
|
|
|
import * as core from '@actions/core';
|
2020-03-26 12:40:41 -04:00
|
|
|
import * as io from '@actions/io';
|
2020-02-09 00:21:39 -05:00
|
|
|
import * as installer from './installer';
|
2022-03-14 12:21:30 -04:00
|
|
|
import * as semver from 'semver';
|
2020-03-27 00:55:12 -04:00
|
|
|
import path from 'path';
|
2022-05-25 12:07:29 +02:00
|
|
|
import {restoreCache} from './cache-restore';
|
2022-12-12 10:58:49 +01:00
|
|
|
import {isCacheFeatureAvailable} from './cache-utils';
|
2020-03-27 00:55:12 -04:00
|
|
|
import cp from 'child_process';
|
|
|
|
import fs from 'fs';
|
2022-08-12 12:29:48 +02:00
|
|
|
import os from 'os';
|
2020-02-09 00:21:39 -05:00
|
|
|
|
|
|
|
export async function run() {
|
|
|
|
try {
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
//
|
2022-05-12 17:04:39 +09:00
|
|
|
const versionSpec = resolveVersionInput();
|
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] https://github.com/docker-library/golang/blob/dae3405a325073e8ad7c8c378ebdf2540d8565c4/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
2025-08-29 04:21:56 +01:00
|
|
|
setGoToolchain();
|
2020-02-09 08:47:38 -05:00
|
|
|
|
2022-05-25 12:07:29 +02:00
|
|
|
const cache = core.getBooleanInput('cache');
|
2022-02-28 10:16:32 +03:00
|
|
|
core.info(`Setup go version spec ${versionSpec}`);
|
2020-02-09 22:39:44 -05:00
|
|
|
|
2022-08-12 12:29:48 +02:00
|
|
|
let arch = core.getInput('architecture');
|
|
|
|
|
|
|
|
if (!arch) {
|
|
|
|
arch = os.arch();
|
|
|
|
}
|
|
|
|
|
2020-02-09 00:21:39 -05:00
|
|
|
if (versionSpec) {
|
2023-03-08 10:45:16 +02:00
|
|
|
const token = core.getInput('token');
|
|
|
|
const auth = !token ? undefined : `token ${token}`;
|
2020-02-09 00:21:39 -05:00
|
|
|
|
2022-02-09 14:59:04 +03:00
|
|
|
const checkLatest = core.getBooleanInput('check-latest');
|
2022-12-12 10:58:49 +01:00
|
|
|
|
2022-08-12 12:29:48 +02:00
|
|
|
const installDir = await installer.getGo(
|
|
|
|
versionSpec,
|
|
|
|
checkLatest,
|
|
|
|
auth,
|
|
|
|
arch
|
|
|
|
);
|
2020-02-09 00:29:21 -05:00
|
|
|
|
2022-12-12 10:58:49 +01:00
|
|
|
const installDirVersion = path.basename(path.dirname(installDir));
|
|
|
|
|
2020-06-29 18:41:13 +03:00
|
|
|
core.addPath(path.join(installDir, 'bin'));
|
|
|
|
core.info('Added go to the path');
|
2020-03-26 12:02:52 -04:00
|
|
|
|
2022-12-12 10:58:49 +01:00
|
|
|
const version = installer.makeSemver(installDirVersion);
|
2022-03-14 12:21:30 -04:00
|
|
|
// Go versions less than 1.9 require GOROOT to be set
|
|
|
|
if (semver.lt(version, '1.9.0')) {
|
2022-03-14 12:23:03 -04:00
|
|
|
core.info('Setting GOROOT for Go version < 1.9');
|
2022-03-14 12:21:30 -04:00
|
|
|
core.exportVariable('GOROOT', installDir);
|
|
|
|
}
|
|
|
|
|
2022-05-03 08:43:40 -04:00
|
|
|
core.info(`Successfully set up Go version ${versionSpec}`);
|
2023-03-14 16:07:41 +01:00
|
|
|
} else {
|
|
|
|
core.info(
|
|
|
|
'[warning]go-version input was not specified. The action will try to use pre-installed version.'
|
|
|
|
);
|
2020-02-09 00:21:39 -05:00
|
|
|
}
|
|
|
|
|
2023-03-14 16:29:10 +01:00
|
|
|
const added = await addBinToPath();
|
|
|
|
core.debug(`add bin ${added}`);
|
|
|
|
|
2023-03-08 10:45:16 +02:00
|
|
|
const goPath = await io.which('go');
|
|
|
|
const goVersion = (cp.execSync(`${goPath} version`) || '').toString();
|
2023-01-20 01:28:58 +01:00
|
|
|
|
2022-05-25 12:07:29 +02:00
|
|
|
if (cache && isCacheFeatureAvailable()) {
|
|
|
|
const packageManager = 'default';
|
|
|
|
const cacheDependencyPath = core.getInput('cache-dependency-path');
|
2023-03-10 16:25:35 +01:00
|
|
|
try {
|
|
|
|
await restoreCache(
|
|
|
|
parseGoVersion(goVersion),
|
|
|
|
packageManager,
|
|
|
|
cacheDependencyPath
|
|
|
|
);
|
|
|
|
} catch (error) {
|
2023-12-05 17:50:42 +01:00
|
|
|
core.warning(`Restore cache failed: ${(error as Error).message}`);
|
2023-03-10 16:25:35 +01:00
|
|
|
}
|
2022-05-25 12:07:29 +02:00
|
|
|
}
|
|
|
|
|
2020-02-09 00:21:39 -05:00
|
|
|
// add problem matchers
|
2022-05-25 12:07:29 +02:00
|
|
|
const matchersPath = path.join(__dirname, '../..', 'matchers.json');
|
2020-06-29 18:41:13 +03:00
|
|
|
core.info(`##[add-matcher]${matchersPath}`);
|
2020-03-31 10:32:03 -04:00
|
|
|
|
|
|
|
// output the version actually being used
|
2020-06-29 18:41:13 +03:00
|
|
|
core.info(goVersion);
|
2020-04-06 08:42:55 -04:00
|
|
|
|
2022-04-08 12:23:10 -04:00
|
|
|
core.setOutput('go-version', parseGoVersion(goVersion));
|
|
|
|
|
2020-04-06 08:42:55 -04:00
|
|
|
core.startGroup('go env');
|
2023-03-08 10:45:16 +02:00
|
|
|
const goEnv = (cp.execSync(`${goPath} env`) || '').toString();
|
2020-06-29 18:41:13 +03:00
|
|
|
core.info(goEnv);
|
2020-04-06 08:42:55 -04:00
|
|
|
core.endGroup();
|
2020-02-09 00:21:39 -05:00
|
|
|
} catch (error) {
|
2023-12-05 17:50:42 +01:00
|
|
|
core.setFailed((error as Error).message);
|
2020-02-09 00:21:39 -05:00
|
|
|
}
|
2020-02-09 00:29:21 -05:00
|
|
|
}
|
2020-03-26 12:02:52 -04:00
|
|
|
|
2020-03-27 00:55:12 -04:00
|
|
|
export async function addBinToPath(): Promise<boolean> {
|
2020-03-26 12:02:52 -04:00
|
|
|
let added = false;
|
2023-03-08 10:45:16 +02:00
|
|
|
const g = await io.which('go');
|
2020-06-29 18:41:13 +03:00
|
|
|
core.debug(`which go :${g}:`);
|
2020-03-26 12:40:41 -04:00
|
|
|
if (!g) {
|
2020-06-29 18:41:13 +03:00
|
|
|
core.debug('go not in the path');
|
2020-03-26 12:40:41 -04:00
|
|
|
return added;
|
|
|
|
}
|
2020-03-26 12:17:32 -04:00
|
|
|
|
2023-03-08 10:45:16 +02:00
|
|
|
const buf = cp.execSync('go env GOPATH');
|
2022-04-17 17:36:51 +02:00
|
|
|
if (buf.length > 1) {
|
2023-03-08 10:45:16 +02:00
|
|
|
const gp = buf.toString().trim();
|
2020-06-29 18:41:13 +03:00
|
|
|
core.debug(`go env GOPATH :${gp}:`);
|
2020-03-26 13:00:45 -04:00
|
|
|
if (!fs.existsSync(gp)) {
|
|
|
|
// some of the hosted images have go install but not profile dir
|
2020-06-29 18:41:13 +03:00
|
|
|
core.debug(`creating ${gp}`);
|
2022-03-28 10:54:44 +01:00
|
|
|
await io.mkdirP(gp);
|
2020-03-26 13:00:45 -04:00
|
|
|
}
|
2020-03-26 12:23:38 -04:00
|
|
|
|
2023-03-08 10:45:16 +02:00
|
|
|
const bp = path.join(gp, 'bin');
|
2020-03-26 13:00:45 -04:00
|
|
|
if (!fs.existsSync(bp)) {
|
2020-06-29 18:41:13 +03:00
|
|
|
core.debug(`creating ${bp}`);
|
2022-03-28 10:54:44 +01:00
|
|
|
await io.mkdirP(bp);
|
2020-03-26 12:54:21 -04:00
|
|
|
}
|
2020-03-26 13:00:45 -04:00
|
|
|
|
|
|
|
core.addPath(bp);
|
|
|
|
added = true;
|
2020-03-26 12:02:52 -04:00
|
|
|
}
|
|
|
|
return added;
|
|
|
|
}
|
2020-03-27 00:55:12 -04:00
|
|
|
|
2022-04-08 12:23:10 -04:00
|
|
|
export function parseGoVersion(versionString: string): string {
|
|
|
|
// get the installed version as an Action output
|
|
|
|
// based on go/src/cmd/go/internal/version/version.go:
|
|
|
|
// fmt.Printf("go version %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
|
|
|
// expecting go<version> for runtime.Version()
|
|
|
|
return versionString.split(' ')[2].slice('go'.length);
|
|
|
|
}
|
2022-05-12 17:04:39 +09:00
|
|
|
|
|
|
|
function resolveVersionInput(): string {
|
|
|
|
let version = core.getInput('go-version');
|
|
|
|
const versionFilePath = core.getInput('go-version-file');
|
|
|
|
|
|
|
|
if (version && versionFilePath) {
|
|
|
|
core.warning(
|
|
|
|
'Both go-version and go-version-file inputs are specified, only go-version will be used'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (version) {
|
|
|
|
return version;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (versionFilePath) {
|
|
|
|
if (!fs.existsSync(versionFilePath)) {
|
|
|
|
throw new Error(
|
|
|
|
`The specified go version file at: ${versionFilePath} does not exist`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
version = installer.parseGoVersionFile(versionFilePath);
|
|
|
|
}
|
|
|
|
|
|
|
|
return version;
|
|
|
|
}
|
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] https://github.com/docker-library/golang/blob/dae3405a325073e8ad7c8c378ebdf2540d8565c4/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
2025-08-29 04:21:56 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
);
|
|
|
|
}
|