mirror of
https://code.forgejo.org/actions/setup-go.git
synced 2026-08-28 04:54:58 -04:00
Resolve latest-patch from the versions manifest and tighten input handling
- latest-patch implies check-latest so the newest patch release comes from the versions manifest instead of a possibly stale runner tool cache, with a warning when the manifest cannot be reached - fail fast when latest-patch is combined with a custom download base URL - never widen an exact toolchain directive pin - validate go-version-file-behavior on every input path - widen v-prefixed versions and log when a version is used as written - document dependency cache invalidation on new patch releases
This commit is contained in:
parent
71b6f8926f
commit
f75efb3f19
6 changed files with 246 additions and 51 deletions
|
|
@ -1122,11 +1122,49 @@ go ${goVersion}
|
||||||
'Using latest patch release satisfying ~1.12.16 (version file specifies 1.12.16)'
|
'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('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(
|
expect(logSpy).toHaveBeenCalledWith(
|
||||||
`Acquiring 1.12.17 from ${expectedUrl}`
|
`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 () => {
|
it('leaves a bare minor version unchanged with latest-patch', async () => {
|
||||||
inputs['go-version-file'] = 'go.mod';
|
inputs['go-version-file'] = 'go.mod';
|
||||||
inputs['go-version-file-behavior'] = 'latest-patch';
|
inputs['go-version-file-behavior'] = 'latest-patch';
|
||||||
|
|
@ -1135,9 +1173,47 @@ go ${goVersion}
|
||||||
|
|
||||||
await main.run();
|
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');
|
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 () => {
|
it('uses the exact version by default', async () => {
|
||||||
inputs['go-version-file'] = 'go.mod';
|
inputs['go-version-file'] = 'go.mod';
|
||||||
existsSpy.mockImplementation(() => true);
|
existsSpy.mockImplementation(() => true);
|
||||||
|
|
@ -1170,8 +1246,20 @@ go ${goVersion}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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([
|
it.each([
|
||||||
['1.22.0', '~1.22.0'],
|
['1.22.0', '~1.22.0'],
|
||||||
|
['v1.22.0', '~1.22.0'],
|
||||||
['1.22', '1.22'],
|
['1.22', '1.22'],
|
||||||
['1.21rc2', '1.21rc2'],
|
['1.21rc2', '1.21rc2'],
|
||||||
['1.22.x', '1.22.x'],
|
['1.22.x', '1.22.x'],
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ inputs:
|
||||||
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:
|
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). Defaults to "exact", which uses the version as written.'
|
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
|
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'
|
||||||
|
|
|
||||||
78
dist/setup/index.js
vendored
78
dist/setup/index.js
vendored
|
|
@ -43165,7 +43165,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(/\/+$/, '');
|
||||||
|
|
@ -43196,6 +43196,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`);
|
||||||
}
|
}
|
||||||
|
|
@ -43568,29 +43573,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
|
// 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:
|
// the newest patch release of the same minor (go-version-file-behavior:
|
||||||
// latest-patch). Only exact major.minor.patch versions are widened: bare
|
// 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
|
// minors like '1.22' already resolve to the newest patch, and prereleases
|
||||||
// like '1.21rc2' have no patch series to float within.
|
// like '1.21rc2' have no patch series to float within.
|
||||||
function latestPatchSpec(version) {
|
function latestPatchSpec(version) {
|
||||||
if (!/^\d+\.\d+\.\d+$/.test(version)) {
|
const match = version.match(/^v?(\d+\.\d+\.\d+)$/);
|
||||||
|
if (!match) {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
return `~${version}`;
|
return `~${match[1]}`;
|
||||||
}
|
}
|
||||||
async function resolveStableVersionDist(versionSpec, arch) {
|
async function resolveStableVersionDist(versionSpec, arch) {
|
||||||
const archFilter = getArch(arch);
|
const archFilter = getArch(arch);
|
||||||
|
|
@ -100261,7 +100274,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}`);
|
||||||
|
|
@ -100272,14 +100285,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');
|
||||||
|
|
@ -100360,30 +100379,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);
|
||||||
const behavior = getInput('go-version-file-behavior') || 'exact';
|
version = versionFile.version;
|
||||||
if (behavior === 'latest-patch') {
|
if (behavior === 'latest-patch' && version) {
|
||||||
const spec = latestPatchSpec(version);
|
if (versionFile.fromToolchainDirective) {
|
||||||
if (spec !== version) {
|
core_info(`Using toolchain directive version ${version} as written (latest-patch does not widen an explicit toolchain pin)`);
|
||||||
core_info(`Using latest patch release satisfying ${spec} (version file specifies ${version})`);
|
}
|
||||||
version = spec;
|
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 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (behavior !== 'exact') {
|
|
||||||
throw new Error(`Invalid go-version-file-behavior: '${behavior}'. Supported values: 'exact', 'latest-patch'`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return version;
|
return { version, latestPatchApplied: false };
|
||||||
}
|
}
|
||||||
function setGoToolchain() {
|
function setGoToolchain() {
|
||||||
// docs: https://go.dev/doc/toolchain
|
// docs: https://go.dev/doc/toolchain
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,20 @@ steps:
|
||||||
- run: go version
|
- run: go version
|
||||||
```
|
```
|
||||||
|
|
||||||
Versions without a patch component (e.g., `go 1.22`) already resolve to the latest available patch release, and prerelease versions (e.g., `go1.22rc1` from a `toolchain` directive) are always used as written, so `latest-patch` leaves both unchanged. As with any version range, the resolved patch release depends on what is available in the runner's tool cache and the versions manifest.
|
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,31 +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
|
// 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:
|
// the newest patch release of the same minor (go-version-file-behavior:
|
||||||
// latest-patch). Only exact major.minor.patch versions are widened: bare
|
// 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
|
// minors like '1.22' already resolve to the newest patch, and prereleases
|
||||||
// like '1.21rc2' have no patch series to float within.
|
// like '1.21rc2' have no patch series to float within.
|
||||||
export function latestPatchSpec(version: string): string {
|
export function latestPatchSpec(version: string): string {
|
||||||
if (!/^\d+\.\d+\.\d+$/.test(version)) {
|
const match = version.match(/^v?(\d+\.\d+\.\d+)$/);
|
||||||
|
if (!match) {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
return `~${version}`;
|
return `~${match[1]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveStableVersionDist(
|
async function resolveStableVersionDist(
|
||||||
|
|
|
||||||
72
src/main.ts
72
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,25 +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;
|
||||||
|
|
||||||
const behavior = core.getInput('go-version-file-behavior') || 'exact';
|
if (behavior === 'latest-patch' && version) {
|
||||||
if (behavior === 'latest-patch') {
|
if (versionFile.fromToolchainDirective) {
|
||||||
const spec = installer.latestPatchSpec(version);
|
|
||||||
if (spec !== version) {
|
|
||||||
core.info(
|
core.info(
|
||||||
`Using latest patch release satisfying ${spec} (version file specifies ${version})`
|
`Using toolchain directive version ${version} as written (latest-patch does not widen an explicit toolchain pin)`
|
||||||
);
|
);
|
||||||
version = spec;
|
} 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};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (behavior !== 'exact') {
|
|
||||||
throw new Error(
|
|
||||||
`Invalid go-version-file-behavior: '${behavior}'. Supported values: 'exact', 'latest-patch'`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return version;
|
return {version, latestPatchApplied: false};
|
||||||
}
|
}
|
||||||
|
|
||||||
function setGoToolchain() {
|
function setGoToolchain() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue