mirror of
https://code.forgejo.org/actions/setup-go.git
synced 2026-08-27 20:44:57 -04:00
Add go-version-file-behavior input
By default an exact version read from go-version-file is used as written, so a "go 1.22.0" directive pins CI to the oldest patch release of the minor even when newer patches with security fixes exist. Dependencies can force such an exact version into go.mod. Setting go-version-file-behavior to latest-patch widens an exact major.minor.patch version into a ~X.Y.Z range, resolving the newest available patch release of the same minor while keeping the file's version as a floor. Bare minors and prereleases pass through unchanged. The default behavior (exact) is unchanged.
This commit is contained in:
parent
5064d95ba1
commit
71b6f8926f
7 changed files with 168 additions and 0 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.
|
||||
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
|
||||
# Default: false
|
||||
check-latest: false
|
||||
|
|
|
|||
|
|
@ -1089,6 +1089,98 @@ 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(
|
||||
`Acquiring 1.12.17 from ${expectedUrl}`
|
||||
);
|
||||
});
|
||||
|
||||
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('Setup go version spec 1.14');
|
||||
});
|
||||
|
||||
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.each([
|
||||
['1.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', () => {
|
||||
const goVersions = ['1.22.0', '1.21rc2', '1.18'];
|
||||
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.'
|
||||
go-version-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). Defaults to "exact", which uses the version as written.'
|
||||
default: exact
|
||||
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'
|
||||
default: false
|
||||
|
|
|
|||
22
dist/setup/index.js
vendored
22
dist/setup/index.js
vendored
|
|
@ -43581,6 +43581,17 @@ function parseGoVersionFile(versionFilePath) {
|
|||
}
|
||||
return contents.trim();
|
||||
}
|
||||
// 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
|
||||
// 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) {
|
||||
if (!/^\d+\.\d+\.\d+$/.test(version)) {
|
||||
return version;
|
||||
}
|
||||
return `~${version}`;
|
||||
}
|
||||
async function resolveStableVersionDist(versionSpec, arch) {
|
||||
const archFilter = getArch(arch);
|
||||
const platFilter = getPlatform();
|
||||
|
|
@ -100360,6 +100371,17 @@ function resolveVersionInput() {
|
|||
throw new Error(`The specified go version file at: ${versionFilePath} does not exist`);
|
||||
}
|
||||
version = parseGoVersionFile(versionFilePath);
|
||||
const behavior = getInput('go-version-file-behavior') || 'exact';
|
||||
if (behavior === 'latest-patch') {
|
||||
const spec = latestPatchSpec(version);
|
||||
if (spec !== version) {
|
||||
core_info(`Using latest patch release satisfying ${spec} (version file specifies ${version})`);
|
||||
version = spec;
|
||||
}
|
||||
}
|
||||
else if (behavior !== 'exact') {
|
||||
throw new Error(`Invalid go-version-file-behavior: '${behavior}'. Supported values: 'exact', 'latest-patch'`);
|
||||
}
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
- [Specifying a go version](advanced-usage.md#specifying-a-go-version)
|
||||
- [Matrix testing](advanced-usage.md#matrix-testing)
|
||||
- [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)
|
||||
- [Caching](advanced-usage.md#caching)
|
||||
- [Caching in monorepos](advanced-usage.md#caching-in-monorepos)
|
||||
|
|
@ -212,6 +213,24 @@ steps:
|
|||
- 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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Check latest version
|
||||
|
||||
The `check-latest` flag defaults to `false`. Use the default or set `check-latest` to `false` if you prefer stability
|
||||
|
|
|
|||
|
|
@ -672,6 +672,18 @@ export function parseGoVersionFile(versionFilePath: string): string {
|
|||
return contents.trim();
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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)) {
|
||||
return version;
|
||||
}
|
||||
return `~${version}`;
|
||||
}
|
||||
|
||||
async function resolveStableVersionDist(
|
||||
versionSpec: string,
|
||||
arch: Architecture
|
||||
|
|
|
|||
15
src/main.ts
15
src/main.ts
|
|
@ -173,6 +173,21 @@ function resolveVersionInput(): string {
|
|||
);
|
||||
}
|
||||
version = installer.parseGoVersionFile(versionFilePath);
|
||||
|
||||
const behavior = core.getInput('go-version-file-behavior') || 'exact';
|
||||
if (behavior === 'latest-patch') {
|
||||
const spec = installer.latestPatchSpec(version);
|
||||
if (spec !== version) {
|
||||
core.info(
|
||||
`Using latest patch release satisfying ${spec} (version file specifies ${version})`
|
||||
);
|
||||
version = spec;
|
||||
}
|
||||
} else if (behavior !== 'exact') {
|
||||
throw new Error(
|
||||
`Invalid go-version-file-behavior: '${behavior}'. Supported values: 'exact', 'latest-patch'`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return version;
|
||||
|
|
|
|||
Loading…
Reference in a new issue