mirror of
https://code.forgejo.org/actions/setup-go.git
synced 2026-08-27 20:44:57 -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
|
|
@ -54,7 +54,8 @@ export async function getGo(
|
|||
checkLatest: boolean,
|
||||
auth: string | undefined,
|
||||
arch: Architecture = os.arch() as Architecture,
|
||||
goDownloadBaseUrl?: string
|
||||
goDownloadBaseUrl?: string,
|
||||
latestPatchApplied = false
|
||||
) {
|
||||
let manifest: tc.IToolRelease[] | undefined;
|
||||
const osPlat: string = os.platform();
|
||||
|
|
@ -111,6 +112,12 @@ export async function getGo(
|
|||
if (resolvedVersion) {
|
||||
versionSpec = resolvedVersion;
|
||||
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 {
|
||||
core.info(`Failed to resolve version ${versionSpec} from manifest`);
|
||||
}
|
||||
|
|
@ -642,7 +649,16 @@ export function makeSemver(version: string): string {
|
|||
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();
|
||||
|
||||
if (
|
||||
|
|
@ -657,31 +673,39 @@ export function parseGoVersionFile(versionFilePath: string): string {
|
|||
/^toolchain go(1\.\d+(?:\.\d+|rc\d+)?)/m
|
||||
);
|
||||
if (matchToolchain) {
|
||||
return matchToolchain[1];
|
||||
return {version: matchToolchain[1], fromToolchainDirective: true};
|
||||
}
|
||||
}
|
||||
|
||||
// go directive: https://go.dev/ref/mod#go-mod-file-go
|
||||
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') {
|
||||
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: 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
|
||||
// like '1.21rc2' have no patch series to float within.
|
||||
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 `~${match[1]}`;
|
||||
}
|
||||
|
||||
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
|
||||
// If not supplied then problem matchers will still be setup. Useful for self-hosted.
|
||||
//
|
||||
const versionSpec = resolveVersionInput();
|
||||
const {version: versionSpec, latestPatchApplied} = resolveVersionInput();
|
||||
setGoToolchain();
|
||||
|
||||
const cache = core.getBooleanInput('cache');
|
||||
|
|
@ -33,7 +33,15 @@ export async function run() {
|
|||
const token = core.getInput('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 =
|
||||
core.getInput('go-download-base-url') ||
|
||||
|
|
@ -49,7 +57,8 @@ export async function run() {
|
|||
checkLatest,
|
||||
auth,
|
||||
arch,
|
||||
goDownloadBaseUrl
|
||||
goDownloadBaseUrl,
|
||||
latestPatchApplied
|
||||
);
|
||||
|
||||
const installDirVersion = path.basename(path.dirname(installDir));
|
||||
|
|
@ -152,10 +161,25 @@ export function parseGoVersion(versionString: string): string {
|
|||
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');
|
||||
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) {
|
||||
core.warning(
|
||||
'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) {
|
||||
return version;
|
||||
return {version, latestPatchApplied: false};
|
||||
}
|
||||
|
||||
if (versionFilePath) {
|
||||
|
|
@ -172,25 +196,39 @@ function resolveVersionInput(): string {
|
|||
`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') {
|
||||
const spec = installer.latestPatchSpec(version);
|
||||
if (spec !== version) {
|
||||
if (behavior === 'latest-patch' && version) {
|
||||
if (versionFile.fromToolchainDirective) {
|
||||
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() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue