mirror of
https://code.forgejo.org/actions/setup-go.git
synced 2026-08-27 20:44:57 -04:00
Merge f75efb3f19 into fba3fb4ead
This commit is contained in:
commit
80d79fad81
7 changed files with 387 additions and 24 deletions
|
|
@ -44,6 +44,11 @@ See [action.yml](action.yml).
|
||||||
# Note: if both go-version and go-version-file are provided, go-version takes precedence.
|
# Note: if both go-version and go-version-file are provided, go-version takes precedence.
|
||||||
go-version-file: 'go.mod'
|
go-version-file: 'go.mod'
|
||||||
|
|
||||||
|
# How to interpret an exact version read from go-version-file.
|
||||||
|
# Set to latest-patch to use the newest patch release of the same minor version.
|
||||||
|
# Default: exact
|
||||||
|
go-version-file-behavior: 'exact'
|
||||||
|
|
||||||
# Set this option if you want the action to check for the latest available version
|
# Set this option if you want the action to check for the latest available version
|
||||||
# Default: false
|
# Default: false
|
||||||
check-latest: false
|
check-latest: false
|
||||||
|
|
|
||||||
|
|
@ -1089,6 +1089,186 @@ use .
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('go-version-file-behavior', () => {
|
||||||
|
const buildGoMod = (goVersion: string) => `module example.com/mymodule
|
||||||
|
|
||||||
|
go ${goVersion}
|
||||||
|
`;
|
||||||
|
|
||||||
|
it('resolves the latest patch of the minor with latest-patch', async () => {
|
||||||
|
os.platform = 'linux';
|
||||||
|
os.arch = 'x64';
|
||||||
|
|
||||||
|
inputs['go-version-file'] = 'go.mod';
|
||||||
|
inputs['go-version-file-behavior'] = 'latest-patch';
|
||||||
|
inputs['token'] = 'faketoken';
|
||||||
|
existsSpy.mockImplementation(() => true);
|
||||||
|
readFileSpy.mockImplementation(() => Buffer.from(buildGoMod('1.12.16')));
|
||||||
|
|
||||||
|
const expectedUrl =
|
||||||
|
'https://github.com/actions/go-versions/releases/download/1.12.17-20200616.21/go-1.12.17-linux-x64.tar.gz';
|
||||||
|
|
||||||
|
// ... but not in the local cache
|
||||||
|
findSpy.mockImplementation(() => '');
|
||||||
|
|
||||||
|
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||||
|
const toolPath = path.normalize('/cache/go/1.12.17/x64');
|
||||||
|
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||||
|
cacheSpy.mockImplementation(async () => toolPath);
|
||||||
|
|
||||||
|
await main.run();
|
||||||
|
|
||||||
|
expect(logSpy).toHaveBeenCalledWith(
|
||||||
|
'Using latest patch release satisfying ~1.12.16 (version file specifies 1.12.16)'
|
||||||
|
);
|
||||||
|
expect(logSpy).toHaveBeenCalledWith('Setup go version spec ~1.12.16');
|
||||||
|
expect(logSpy).toHaveBeenCalledWith(
|
||||||
|
'go-version-file-behavior: latest-patch implies check-latest'
|
||||||
|
);
|
||||||
|
expect(logSpy).toHaveBeenCalledWith(
|
||||||
|
'Attempting to resolve the latest version from the manifest...'
|
||||||
|
);
|
||||||
|
expect(logSpy).toHaveBeenCalledWith(
|
||||||
|
`Acquiring 1.12.17 from ${expectedUrl}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('warns and falls back when the manifest cannot be resolved', async () => {
|
||||||
|
os.platform = 'linux';
|
||||||
|
os.arch = 'x64';
|
||||||
|
|
||||||
|
inputs['go-version-file'] = 'go.mod';
|
||||||
|
inputs['go-version-file-behavior'] = 'latest-patch';
|
||||||
|
inputs['token'] = 'faketoken';
|
||||||
|
existsSpy.mockImplementation(() => true);
|
||||||
|
readFileSpy.mockImplementation(() => Buffer.from(buildGoMod('1.12.16')));
|
||||||
|
|
||||||
|
getManifestSpy.mockImplementation(() => {
|
||||||
|
throw new Error('Unable to download manifest');
|
||||||
|
});
|
||||||
|
(httpmGetJsonSpy as jest.Mock<any>).mockRejectedValue(
|
||||||
|
new Error('Unable to download manifest from raw URL')
|
||||||
|
);
|
||||||
|
|
||||||
|
// ... and not in the local cache, so the dist fallback downloads
|
||||||
|
findSpy.mockImplementation(() => '');
|
||||||
|
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||||
|
const toolPath = path.normalize('/cache/go/1.12.17/x64');
|
||||||
|
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||||
|
cacheSpy.mockImplementation(async () => toolPath);
|
||||||
|
|
||||||
|
await main.run();
|
||||||
|
|
||||||
|
expect(cnSpy).toHaveBeenCalledWith(
|
||||||
|
`::warning::go-version-file-behavior: latest-patch could not be honored: unable to resolve ~1.12.16 from the versions manifest. Falling back to the version spec, which may resolve to an older patch release from the runner's tool cache.${osm.EOL}`
|
||||||
|
);
|
||||||
|
expect(dlSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('leaves a bare minor version unchanged with latest-patch', async () => {
|
||||||
|
inputs['go-version-file'] = 'go.mod';
|
||||||
|
inputs['go-version-file-behavior'] = 'latest-patch';
|
||||||
|
existsSpy.mockImplementation(() => true);
|
||||||
|
readFileSpy.mockImplementation(() => Buffer.from(buildGoMod('1.14')));
|
||||||
|
|
||||||
|
await main.run();
|
||||||
|
|
||||||
|
expect(logSpy).toHaveBeenCalledWith(
|
||||||
|
'Using version 1.14 as written (latest-patch only widens exact major.minor.patch versions)'
|
||||||
|
);
|
||||||
|
expect(logSpy).toHaveBeenCalledWith('Setup go version spec 1.14');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not widen an explicit toolchain directive pin', async () => {
|
||||||
|
inputs['go-version-file'] = 'go.mod';
|
||||||
|
inputs['go-version-file-behavior'] = 'latest-patch';
|
||||||
|
existsSpy.mockImplementation(() => true);
|
||||||
|
readFileSpy.mockImplementation(() =>
|
||||||
|
Buffer.from(`module example.com/mymodule
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
toolchain go1.22.3
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
|
||||||
|
await main.run();
|
||||||
|
|
||||||
|
expect(logSpy).toHaveBeenCalledWith(
|
||||||
|
'Using toolchain directive version 1.22.3 as written (latest-patch does not widen an explicit toolchain pin)'
|
||||||
|
);
|
||||||
|
expect(logSpy).toHaveBeenCalledWith('Setup go version spec 1.22.3');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails when combined with a custom download base URL', async () => {
|
||||||
|
inputs['go-version-file'] = 'go.mod';
|
||||||
|
inputs['go-version-file-behavior'] = 'latest-patch';
|
||||||
|
inputs['go-download-base-url'] = 'https://internal.example.com/go';
|
||||||
|
existsSpy.mockImplementation(() => true);
|
||||||
|
readFileSpy.mockImplementation(() => Buffer.from(buildGoMod('1.22.0')));
|
||||||
|
|
||||||
|
await main.run();
|
||||||
|
|
||||||
|
expect(cnSpy).toHaveBeenCalledWith(
|
||||||
|
`::error::go-version-file-behavior: 'latest-patch' is not supported with a custom download base URL because version ranges cannot be resolved against it. Use the default 'exact' behavior.${osm.EOL}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the exact version by default', async () => {
|
||||||
|
inputs['go-version-file'] = 'go.mod';
|
||||||
|
existsSpy.mockImplementation(() => true);
|
||||||
|
readFileSpy.mockImplementation(() => Buffer.from(buildGoMod('1.12.16')));
|
||||||
|
|
||||||
|
await main.run();
|
||||||
|
|
||||||
|
expect(logSpy).toHaveBeenCalledWith('Setup go version spec 1.12.16');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not apply to the go-version input', async () => {
|
||||||
|
inputs['go-version'] = '1.12.16';
|
||||||
|
inputs['go-version-file-behavior'] = 'latest-patch';
|
||||||
|
|
||||||
|
await main.run();
|
||||||
|
|
||||||
|
expect(logSpy).toHaveBeenCalledWith('Setup go version spec 1.12.16');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails on an unsupported value', async () => {
|
||||||
|
inputs['go-version-file'] = 'go.mod';
|
||||||
|
inputs['go-version-file-behavior'] = 'newest';
|
||||||
|
existsSpy.mockImplementation(() => true);
|
||||||
|
readFileSpy.mockImplementation(() => Buffer.from(buildGoMod('1.12.16')));
|
||||||
|
|
||||||
|
await main.run();
|
||||||
|
|
||||||
|
expect(cnSpy).toHaveBeenCalledWith(
|
||||||
|
`::error::Invalid go-version-file-behavior: 'newest'. Supported values: 'exact', 'latest-patch'${osm.EOL}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails on an unsupported value even when go-version is used', async () => {
|
||||||
|
inputs['go-version'] = '1.12.16';
|
||||||
|
inputs['go-version-file-behavior'] = 'newest';
|
||||||
|
|
||||||
|
await main.run();
|
||||||
|
|
||||||
|
expect(cnSpy).toHaveBeenCalledWith(
|
||||||
|
`::error::Invalid go-version-file-behavior: 'newest'. Supported values: 'exact', 'latest-patch'${osm.EOL}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
['1.22.0', '~1.22.0'],
|
||||||
|
['v1.22.0', '~1.22.0'],
|
||||||
|
['1.22', '1.22'],
|
||||||
|
['1.21rc2', '1.21rc2'],
|
||||||
|
['1.22.x', '1.22.x'],
|
||||||
|
['>=1.22.0', '>=1.22.0']
|
||||||
|
])('latestPatchSpec(%s) == %s', (version, expected) => {
|
||||||
|
expect(im.latestPatchSpec(version)).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('go-version-file-toolchain', () => {
|
describe('go-version-file-toolchain', () => {
|
||||||
const goVersions = ['1.22.0', '1.21rc2', '1.18'];
|
const goVersions = ['1.22.0', '1.21rc2', '1.18'];
|
||||||
const placeholderVersion = '1.19';
|
const placeholderVersion = '1.19';
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ inputs:
|
||||||
description: 'The Go version to download (if necessary) and use. Supports semver spec and ranges. Be sure to enclose this option in single quotation marks.'
|
description: 'The Go version to download (if necessary) and use. Supports semver spec and ranges. Be sure to enclose this option in single quotation marks.'
|
||||||
go-version-file:
|
go-version-file:
|
||||||
description: 'Path to the go.mod, go.work, .go-version, or .tool-versions file.'
|
description: 'Path to the go.mod, go.work, .go-version, or .tool-versions file.'
|
||||||
|
go-version-file-behavior:
|
||||||
|
description: 'How to interpret an exact version read from go-version-file. Use "latest-patch" to resolve the newest available patch release of the same minor version (e.g. "1.22.0" in go.mod resolves to the newest 1.22.x); this implies check-latest and is not supported with go-download-base-url. Defaults to "exact", which uses the version as written.'
|
||||||
|
default: exact
|
||||||
check-latest:
|
check-latest:
|
||||||
description: 'Set this option to true if you want the action to always check for the latest available version that satisfies the version spec'
|
description: 'Set this option to true if you want the action to always check for the latest available version that satisfies the version spec'
|
||||||
default: false
|
default: false
|
||||||
|
|
|
||||||
76
dist/setup/index.js
vendored
76
dist/setup/index.js
vendored
|
|
@ -43462,7 +43462,7 @@ const GOLANG_DOWNLOAD_URL = 'https://go.dev/dl/?mode=json&include=all';
|
||||||
// For these URLs we skip the getInfoFromDist() call entirely and construct
|
// For these URLs we skip the getInfoFromDist() call entirely and construct
|
||||||
// the download URL directly, avoiding a guaranteed-404 HTTP request.
|
// the download URL directly, avoiding a guaranteed-404 HTTP request.
|
||||||
const NO_VERSION_LISTING_BASE_URLS = ['https://aka.ms/golang/release/latest'];
|
const NO_VERSION_LISTING_BASE_URLS = ['https://aka.ms/golang/release/latest'];
|
||||||
async function getGo(versionSpec, checkLatest, auth, arch = external_os_default().arch(), goDownloadBaseUrl) {
|
async function getGo(versionSpec, checkLatest, auth, arch = external_os_default().arch(), goDownloadBaseUrl, latestPatchApplied = false) {
|
||||||
let manifest;
|
let manifest;
|
||||||
const osPlat = external_os_default().platform();
|
const osPlat = external_os_default().platform();
|
||||||
const customBaseUrl = goDownloadBaseUrl?.replace(/\/+$/, '');
|
const customBaseUrl = goDownloadBaseUrl?.replace(/\/+$/, '');
|
||||||
|
|
@ -43493,6 +43493,11 @@ async function getGo(versionSpec, checkLatest, auth, arch = external_os_default(
|
||||||
versionSpec = resolvedVersion;
|
versionSpec = resolvedVersion;
|
||||||
core_info(`Resolved as '${versionSpec}'`);
|
core_info(`Resolved as '${versionSpec}'`);
|
||||||
}
|
}
|
||||||
|
else if (latestPatchApplied) {
|
||||||
|
// latest-patch depends on the manifest to see patches newer than
|
||||||
|
// the runner's tool cache, so a silent info line is not enough here
|
||||||
|
warning(`go-version-file-behavior: latest-patch could not be honored: unable to resolve ${versionSpec} from the versions manifest. Falling back to the version spec, which may resolve to an older patch release from the runner's tool cache.`);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
core_info(`Failed to resolve version ${versionSpec} from manifest`);
|
core_info(`Failed to resolve version ${versionSpec} from manifest`);
|
||||||
}
|
}
|
||||||
|
|
@ -43865,18 +43870,37 @@ function parseGoVersionFile(versionFilePath) {
|
||||||
// toolchain directive: https://go.dev/ref/mod#go-mod-file-toolchain
|
// toolchain directive: https://go.dev/ref/mod#go-mod-file-toolchain
|
||||||
const matchToolchain = contents.match(/^toolchain go(1\.\d+(?:\.\d+|rc\d+)?)/m);
|
const matchToolchain = contents.match(/^toolchain go(1\.\d+(?:\.\d+|rc\d+)?)/m);
|
||||||
if (matchToolchain) {
|
if (matchToolchain) {
|
||||||
return matchToolchain[1];
|
return { version: matchToolchain[1], fromToolchainDirective: true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// go directive: https://go.dev/ref/mod#go-mod-file-go
|
// go directive: https://go.dev/ref/mod#go-mod-file-go
|
||||||
const matchGo = contents.match(/^go (\d+(\.\d+)*)/m);
|
const matchGo = contents.match(/^go (\d+(\.\d+)*)/m);
|
||||||
return matchGo ? matchGo[1] : '';
|
return {
|
||||||
|
version: matchGo ? matchGo[1] : '',
|
||||||
|
fromToolchainDirective: false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
else if (external_path_.basename(versionFilePath) === '.tool-versions') {
|
else if (external_path_.basename(versionFilePath) === '.tool-versions') {
|
||||||
const match = contents.match(/^golang\s+([^\n#]+)/m);
|
const match = contents.match(/^golang\s+([^\n#]+)/m);
|
||||||
return match ? match[1].trim() : '';
|
return {
|
||||||
|
version: match ? match[1].trim() : '',
|
||||||
|
fromToolchainDirective: false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return contents.trim();
|
return { version: contents.trim(), fromToolchainDirective: false };
|
||||||
|
}
|
||||||
|
// Widen an exact version from a version file into a semver range matching
|
||||||
|
// the newest patch release of the same minor (go-version-file-behavior:
|
||||||
|
// latest-patch). Only exact major.minor.patch versions are widened,
|
||||||
|
// optionally with a leading 'v' as found in some .go-version files: bare
|
||||||
|
// minors like '1.22' already resolve to the newest patch, and prereleases
|
||||||
|
// like '1.21rc2' have no patch series to float within.
|
||||||
|
function latestPatchSpec(version) {
|
||||||
|
const match = version.match(/^v?(\d+\.\d+\.\d+)$/);
|
||||||
|
if (!match) {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
return `~${match[1]}`;
|
||||||
}
|
}
|
||||||
async function resolveStableVersionDist(versionSpec, arch) {
|
async function resolveStableVersionDist(versionSpec, arch) {
|
||||||
const archFilter = getArch(arch);
|
const archFilter = getArch(arch);
|
||||||
|
|
@ -100573,7 +100597,7 @@ async function run() {
|
||||||
// versionSpec is optional. If supplied, install / use from the tool cache
|
// 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.
|
// If not supplied then problem matchers will still be setup. Useful for self-hosted.
|
||||||
//
|
//
|
||||||
const versionSpec = resolveVersionInput();
|
const { version: versionSpec, latestPatchApplied } = resolveVersionInput();
|
||||||
setGoToolchain();
|
setGoToolchain();
|
||||||
const cache = getBooleanInput('cache');
|
const cache = getBooleanInput('cache');
|
||||||
core_info(`Setup go version spec ${versionSpec}`);
|
core_info(`Setup go version spec ${versionSpec}`);
|
||||||
|
|
@ -100584,14 +100608,20 @@ async function run() {
|
||||||
if (versionSpec) {
|
if (versionSpec) {
|
||||||
const token = getInput('token');
|
const token = getInput('token');
|
||||||
const auth = !token ? undefined : `token ${token}`;
|
const auth = !token ? undefined : `token ${token}`;
|
||||||
const checkLatest = getBooleanInput('check-latest');
|
let checkLatest = getBooleanInput('check-latest');
|
||||||
|
if (latestPatchApplied && !checkLatest) {
|
||||||
|
// the runner's tool cache may only hold a stale patch release; the
|
||||||
|
// newest one has to come from the versions manifest
|
||||||
|
core_info('go-version-file-behavior: latest-patch implies check-latest');
|
||||||
|
checkLatest = true;
|
||||||
|
}
|
||||||
const goDownloadBaseUrl = getInput('go-download-base-url') ||
|
const goDownloadBaseUrl = getInput('go-download-base-url') ||
|
||||||
process.env['GO_DOWNLOAD_BASE_URL'] ||
|
process.env['GO_DOWNLOAD_BASE_URL'] ||
|
||||||
undefined;
|
undefined;
|
||||||
if (goDownloadBaseUrl) {
|
if (goDownloadBaseUrl) {
|
||||||
core_info(`Using custom Go download base URL: ${goDownloadBaseUrl}`);
|
core_info(`Using custom Go download base URL: ${goDownloadBaseUrl}`);
|
||||||
}
|
}
|
||||||
const installDir = await getGo(versionSpec, checkLatest, auth, arch, goDownloadBaseUrl);
|
const installDir = await getGo(versionSpec, checkLatest, auth, arch, goDownloadBaseUrl, latestPatchApplied);
|
||||||
const installDirVersion = external_path_default().basename(external_path_default().dirname(installDir));
|
const installDirVersion = external_path_default().basename(external_path_default().dirname(installDir));
|
||||||
addPath(external_path_default().join(installDir, 'bin'));
|
addPath(external_path_default().join(installDir, 'bin'));
|
||||||
core_info('Added go to the path');
|
core_info('Added go to the path');
|
||||||
|
|
@ -100672,19 +100702,43 @@ function parseGoVersion(versionString) {
|
||||||
function resolveVersionInput() {
|
function resolveVersionInput() {
|
||||||
let version = getInput('go-version');
|
let version = getInput('go-version');
|
||||||
const versionFilePath = getInput('go-version-file');
|
const versionFilePath = getInput('go-version-file');
|
||||||
|
const behavior = getInput('go-version-file-behavior') || 'exact';
|
||||||
|
if (behavior !== 'exact' && behavior !== 'latest-patch') {
|
||||||
|
throw new Error(`Invalid go-version-file-behavior: '${behavior}'. Supported values: 'exact', 'latest-patch'`);
|
||||||
|
}
|
||||||
if (version && versionFilePath) {
|
if (version && versionFilePath) {
|
||||||
warning('Both go-version and go-version-file inputs are specified, only go-version will be used');
|
warning('Both go-version and go-version-file inputs are specified, only go-version will be used');
|
||||||
}
|
}
|
||||||
if (version) {
|
if (version) {
|
||||||
return version;
|
return { version, latestPatchApplied: false };
|
||||||
}
|
}
|
||||||
if (versionFilePath) {
|
if (versionFilePath) {
|
||||||
if (!external_fs_default().existsSync(versionFilePath)) {
|
if (!external_fs_default().existsSync(versionFilePath)) {
|
||||||
throw new Error(`The specified go version file at: ${versionFilePath} does not exist`);
|
throw new Error(`The specified go version file at: ${versionFilePath} does not exist`);
|
||||||
}
|
}
|
||||||
version = parseGoVersionFile(versionFilePath);
|
const versionFile = parseGoVersionFile(versionFilePath);
|
||||||
|
version = versionFile.version;
|
||||||
|
if (behavior === 'latest-patch' && version) {
|
||||||
|
if (versionFile.fromToolchainDirective) {
|
||||||
|
core_info(`Using toolchain directive version ${version} as written (latest-patch does not widen an explicit toolchain pin)`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const spec = latestPatchSpec(version);
|
||||||
|
if (spec === version) {
|
||||||
|
core_info(`Using version ${version} as written (latest-patch only widens exact major.minor.patch versions)`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (getInput('go-download-base-url') ||
|
||||||
|
process.env['GO_DOWNLOAD_BASE_URL']) {
|
||||||
|
throw new Error(`go-version-file-behavior: 'latest-patch' is not supported with a custom download base URL because version ranges cannot be resolved against it. Use the default 'exact' behavior.`);
|
||||||
|
}
|
||||||
|
core_info(`Using latest patch release satisfying ${spec} (version file specifies ${version})`);
|
||||||
|
return { version: spec, latestPatchApplied: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return version;
|
return { version, latestPatchApplied: false };
|
||||||
}
|
}
|
||||||
function setGoToolchain() {
|
function setGoToolchain() {
|
||||||
// docs: https://go.dev/doc/toolchain
|
// docs: https://go.dev/doc/toolchain
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
- [Specifying a go version](advanced-usage.md#specifying-a-go-version)
|
- [Specifying a go version](advanced-usage.md#specifying-a-go-version)
|
||||||
- [Matrix testing](advanced-usage.md#matrix-testing)
|
- [Matrix testing](advanced-usage.md#matrix-testing)
|
||||||
- [Using the go-version-file input](advanced-usage.md#using-the-go-version-file-input)
|
- [Using the go-version-file input](advanced-usage.md#using-the-go-version-file-input)
|
||||||
|
- [Using the latest patch release](advanced-usage.md#using-the-latest-patch-release)
|
||||||
- [Check latest version](advanced-usage.md#check-latest-version)
|
- [Check latest version](advanced-usage.md#check-latest-version)
|
||||||
- [Caching](advanced-usage.md#caching)
|
- [Caching](advanced-usage.md#caching)
|
||||||
- [Caching in monorepos](advanced-usage.md#caching-in-monorepos)
|
- [Caching in monorepos](advanced-usage.md#caching-in-monorepos)
|
||||||
|
|
@ -212,6 +213,37 @@ steps:
|
||||||
- run: go version
|
- run: go version
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Using the latest patch release
|
||||||
|
|
||||||
|
By default, an exact version read from the version file is used as written: a `go 1.22.0` directive installs exactly Go 1.22.0, even if newer 1.22.x patch releases with security fixes are available.
|
||||||
|
|
||||||
|
Set `go-version-file-behavior` to `latest-patch` to instead resolve the newest available patch release of the same minor version that is at least the version in the file (e.g., `go 1.22.0` resolves to the newest 1.22.x):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- uses: actions/setup-go@v7
|
||||||
|
with:
|
||||||
|
go-version-file: 'go.mod'
|
||||||
|
go-version-file-behavior: 'latest-patch'
|
||||||
|
- run: go version
|
||||||
|
```
|
||||||
|
|
||||||
|
Because the newest patch release is often not yet present in the runner's tool cache, `latest-patch` implies `check-latest`: the newest matching patch is resolved from the versions manifest rather than from whatever the cache happens to hold.
|
||||||
|
|
||||||
|
Two operational effects to be aware of:
|
||||||
|
|
||||||
|
- The dependency cache key includes the installed Go version, so with `cache: true` each new Go patch release changes the key: the first run after a patch release rebuilds the module and build caches from scratch.
|
||||||
|
- If the versions manifest cannot be reached (for example on GitHub Enterprise Server or other runners without github.com access), the action emits a warning and falls back to resolving the version range locally, which may install an older patch release from the runner's tool cache.
|
||||||
|
|
||||||
|
Some versions are always used as written and are not affected by `latest-patch`:
|
||||||
|
|
||||||
|
- Versions without a patch component (e.g., `go 1.22`), which already resolve to the latest available patch release.
|
||||||
|
- Prerelease versions (e.g., `go1.22rc1`), which have no patch series to float within.
|
||||||
|
- An exact version pinned by a go.mod or go.work `toolchain` directive (e.g., `toolchain go1.22.3`): the pin is deliberate and is never widened.
|
||||||
|
|
||||||
|
`latest-patch` is not supported together with `go-download-base-url`, which requires an exact version.
|
||||||
|
|
||||||
## Check latest version
|
## Check latest version
|
||||||
|
|
||||||
The `check-latest` flag defaults to `false`. Use the default or set `check-latest` to `false` if you prefer stability
|
The `check-latest` flag defaults to `false`. Use the default or set `check-latest` to `false` if you prefer stability
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,8 @@ export async function getGo(
|
||||||
checkLatest: boolean,
|
checkLatest: boolean,
|
||||||
auth: string | undefined,
|
auth: string | undefined,
|
||||||
arch: Architecture = os.arch() as Architecture,
|
arch: Architecture = os.arch() as Architecture,
|
||||||
goDownloadBaseUrl?: string
|
goDownloadBaseUrl?: string,
|
||||||
|
latestPatchApplied = false
|
||||||
) {
|
) {
|
||||||
let manifest: tc.IToolRelease[] | undefined;
|
let manifest: tc.IToolRelease[] | undefined;
|
||||||
const osPlat: string = os.platform();
|
const osPlat: string = os.platform();
|
||||||
|
|
@ -111,6 +112,12 @@ export async function getGo(
|
||||||
if (resolvedVersion) {
|
if (resolvedVersion) {
|
||||||
versionSpec = resolvedVersion;
|
versionSpec = resolvedVersion;
|
||||||
core.info(`Resolved as '${versionSpec}'`);
|
core.info(`Resolved as '${versionSpec}'`);
|
||||||
|
} else if (latestPatchApplied) {
|
||||||
|
// latest-patch depends on the manifest to see patches newer than
|
||||||
|
// the runner's tool cache, so a silent info line is not enough here
|
||||||
|
core.warning(
|
||||||
|
`go-version-file-behavior: latest-patch could not be honored: unable to resolve ${versionSpec} from the versions manifest. Falling back to the version spec, which may resolve to an older patch release from the runner's tool cache.`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
core.info(`Failed to resolve version ${versionSpec} from manifest`);
|
core.info(`Failed to resolve version ${versionSpec} from manifest`);
|
||||||
}
|
}
|
||||||
|
|
@ -642,7 +649,16 @@ export function makeSemver(version: string): string {
|
||||||
return fullVersion;
|
return fullVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseGoVersionFile(versionFilePath: string): string {
|
export interface GoVersionFileResult {
|
||||||
|
version: string;
|
||||||
|
// true when the version came from a go.mod/go.work toolchain directive,
|
||||||
|
// an explicit pin that must be respected as written
|
||||||
|
fromToolchainDirective: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseGoVersionFile(
|
||||||
|
versionFilePath: string
|
||||||
|
): GoVersionFileResult {
|
||||||
const contents = fs.readFileSync(versionFilePath).toString();
|
const contents = fs.readFileSync(versionFilePath).toString();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
@ -657,19 +673,39 @@ export function parseGoVersionFile(versionFilePath: string): string {
|
||||||
/^toolchain go(1\.\d+(?:\.\d+|rc\d+)?)/m
|
/^toolchain go(1\.\d+(?:\.\d+|rc\d+)?)/m
|
||||||
);
|
);
|
||||||
if (matchToolchain) {
|
if (matchToolchain) {
|
||||||
return matchToolchain[1];
|
return {version: matchToolchain[1], fromToolchainDirective: true};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// go directive: https://go.dev/ref/mod#go-mod-file-go
|
// go directive: https://go.dev/ref/mod#go-mod-file-go
|
||||||
const matchGo = contents.match(/^go (\d+(\.\d+)*)/m);
|
const matchGo = contents.match(/^go (\d+(\.\d+)*)/m);
|
||||||
return matchGo ? matchGo[1] : '';
|
return {
|
||||||
|
version: matchGo ? matchGo[1] : '',
|
||||||
|
fromToolchainDirective: false
|
||||||
|
};
|
||||||
} else if (path.basename(versionFilePath) === '.tool-versions') {
|
} else if (path.basename(versionFilePath) === '.tool-versions') {
|
||||||
const match = contents.match(/^golang\s+([^\n#]+)/m);
|
const match = contents.match(/^golang\s+([^\n#]+)/m);
|
||||||
return match ? match[1].trim() : '';
|
return {
|
||||||
|
version: match ? match[1].trim() : '',
|
||||||
|
fromToolchainDirective: false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return contents.trim();
|
return {version: contents.trim(), fromToolchainDirective: false};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widen an exact version from a version file into a semver range matching
|
||||||
|
// the newest patch release of the same minor (go-version-file-behavior:
|
||||||
|
// latest-patch). Only exact major.minor.patch versions are widened,
|
||||||
|
// optionally with a leading 'v' as found in some .go-version files: bare
|
||||||
|
// minors like '1.22' already resolve to the newest patch, and prereleases
|
||||||
|
// like '1.21rc2' have no patch series to float within.
|
||||||
|
export function latestPatchSpec(version: string): string {
|
||||||
|
const match = version.match(/^v?(\d+\.\d+\.\d+)$/);
|
||||||
|
if (!match) {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
return `~${match[1]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveStableVersionDist(
|
async function resolveStableVersionDist(
|
||||||
|
|
|
||||||
67
src/main.ts
67
src/main.ts
|
|
@ -17,7 +17,7 @@ export async function run() {
|
||||||
// versionSpec is optional. If supplied, install / use from the tool cache
|
// 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.
|
// If not supplied then problem matchers will still be setup. Useful for self-hosted.
|
||||||
//
|
//
|
||||||
const versionSpec = resolveVersionInput();
|
const {version: versionSpec, latestPatchApplied} = resolveVersionInput();
|
||||||
setGoToolchain();
|
setGoToolchain();
|
||||||
|
|
||||||
const cache = core.getBooleanInput('cache');
|
const cache = core.getBooleanInput('cache');
|
||||||
|
|
@ -33,7 +33,15 @@ export async function run() {
|
||||||
const token = core.getInput('token');
|
const token = core.getInput('token');
|
||||||
const auth = !token ? undefined : `token ${token}`;
|
const auth = !token ? undefined : `token ${token}`;
|
||||||
|
|
||||||
const checkLatest = core.getBooleanInput('check-latest');
|
let checkLatest = core.getBooleanInput('check-latest');
|
||||||
|
if (latestPatchApplied && !checkLatest) {
|
||||||
|
// the runner's tool cache may only hold a stale patch release; the
|
||||||
|
// newest one has to come from the versions manifest
|
||||||
|
core.info(
|
||||||
|
'go-version-file-behavior: latest-patch implies check-latest'
|
||||||
|
);
|
||||||
|
checkLatest = true;
|
||||||
|
}
|
||||||
|
|
||||||
const goDownloadBaseUrl =
|
const goDownloadBaseUrl =
|
||||||
core.getInput('go-download-base-url') ||
|
core.getInput('go-download-base-url') ||
|
||||||
|
|
@ -49,7 +57,8 @@ export async function run() {
|
||||||
checkLatest,
|
checkLatest,
|
||||||
auth,
|
auth,
|
||||||
arch,
|
arch,
|
||||||
goDownloadBaseUrl
|
goDownloadBaseUrl,
|
||||||
|
latestPatchApplied
|
||||||
);
|
);
|
||||||
|
|
||||||
const installDirVersion = path.basename(path.dirname(installDir));
|
const installDirVersion = path.basename(path.dirname(installDir));
|
||||||
|
|
@ -152,10 +161,25 @@ export function parseGoVersion(versionString: string): string {
|
||||||
return versionString.split(' ')[2].slice('go'.length);
|
return versionString.split(' ')[2].slice('go'.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveVersionInput(): string {
|
interface ResolvedVersionInput {
|
||||||
|
version: string;
|
||||||
|
// true when latest-patch widened the version into a range; the newest
|
||||||
|
// matching patch must then be resolved from the versions manifest, not
|
||||||
|
// just the runner's local tool cache
|
||||||
|
latestPatchApplied: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveVersionInput(): ResolvedVersionInput {
|
||||||
let version = core.getInput('go-version');
|
let version = core.getInput('go-version');
|
||||||
const versionFilePath = core.getInput('go-version-file');
|
const versionFilePath = core.getInput('go-version-file');
|
||||||
|
|
||||||
|
const behavior = core.getInput('go-version-file-behavior') || 'exact';
|
||||||
|
if (behavior !== 'exact' && behavior !== 'latest-patch') {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid go-version-file-behavior: '${behavior}'. Supported values: 'exact', 'latest-patch'`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (version && versionFilePath) {
|
if (version && versionFilePath) {
|
||||||
core.warning(
|
core.warning(
|
||||||
'Both go-version and go-version-file inputs are specified, only go-version will be used'
|
'Both go-version and go-version-file inputs are specified, only go-version will be used'
|
||||||
|
|
@ -163,7 +187,7 @@ function resolveVersionInput(): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version) {
|
if (version) {
|
||||||
return version;
|
return {version, latestPatchApplied: false};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (versionFilePath) {
|
if (versionFilePath) {
|
||||||
|
|
@ -172,10 +196,39 @@ function resolveVersionInput(): string {
|
||||||
`The specified go version file at: ${versionFilePath} does not exist`
|
`The specified go version file at: ${versionFilePath} does not exist`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
version = installer.parseGoVersionFile(versionFilePath);
|
const versionFile = installer.parseGoVersionFile(versionFilePath);
|
||||||
|
version = versionFile.version;
|
||||||
|
|
||||||
|
if (behavior === 'latest-patch' && version) {
|
||||||
|
if (versionFile.fromToolchainDirective) {
|
||||||
|
core.info(
|
||||||
|
`Using toolchain directive version ${version} as written (latest-patch does not widen an explicit toolchain pin)`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const spec = installer.latestPatchSpec(version);
|
||||||
|
if (spec === version) {
|
||||||
|
core.info(
|
||||||
|
`Using version ${version} as written (latest-patch only widens exact major.minor.patch versions)`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
core.getInput('go-download-base-url') ||
|
||||||
|
process.env['GO_DOWNLOAD_BASE_URL']
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`go-version-file-behavior: 'latest-patch' is not supported with a custom download base URL because version ranges cannot be resolved against it. Use the default 'exact' behavior.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
core.info(
|
||||||
|
`Using latest patch release satisfying ${spec} (version file specifies ${version})`
|
||||||
|
);
|
||||||
|
return {version: spec, latestPatchApplied: true};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return version;
|
return {version, latestPatchApplied: false};
|
||||||
}
|
}
|
||||||
|
|
||||||
function setGoToolchain() {
|
function setGoToolchain() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue