mirror of
https://code.forgejo.org/actions/setup-go.git
synced 2026-08-28 04:54:58 -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.
|
# 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,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', () => {
|
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). 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
|
||||||
|
|
|
||||||
22
dist/setup/index.js
vendored
22
dist/setup/index.js
vendored
|
|
@ -43581,6 +43581,17 @@ function parseGoVersionFile(versionFilePath) {
|
||||||
}
|
}
|
||||||
return contents.trim();
|
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) {
|
async function resolveStableVersionDist(versionSpec, arch) {
|
||||||
const archFilter = getArch(arch);
|
const archFilter = getArch(arch);
|
||||||
const platFilter = getPlatform();
|
const platFilter = getPlatform();
|
||||||
|
|
@ -100360,6 +100371,17 @@ function resolveVersionInput() {
|
||||||
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);
|
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;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,24 @@ 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
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
## 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
|
||||||
|
|
|
||||||
|
|
@ -672,6 +672,18 @@ export function parseGoVersionFile(versionFilePath: string): string {
|
||||||
return contents.trim();
|
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(
|
async function resolveStableVersionDist(
|
||||||
versionSpec: string,
|
versionSpec: string,
|
||||||
arch: Architecture
|
arch: Architecture
|
||||||
|
|
|
||||||
15
src/main.ts
15
src/main.ts
|
|
@ -173,6 +173,21 @@ function resolveVersionInput(): string {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
version = installer.parseGoVersionFile(versionFilePath);
|
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;
|
return version;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue