mirror of
https://github.com/actions/setup-go.git
synced 2026-11-03 16:00:30 +08:00
Add support for native Go version syntax in go-version input (#804)
* Add support for native Go version syntax in go-version input * Enhance Go-native version handling and update tests and documentation * Update docs
This commit is contained in:
@@ -94,6 +94,7 @@ the [check-latest input](docs/advanced-usage.md#check-latest-version).
|
||||
The `go-version` input supports the following syntax:
|
||||
|
||||
- Specific versions: `1.25`, `1.24.11`, `1.24.0-rc.1`, `1.23.0-beta.1`
|
||||
- Native Go versions: `go1.21`, `go1.21.5`, `go1.18beta2`, `go1.21rc2`. `go1.21` resolves to exactly `1.21.0`, unlike `1.21`, which matches patch releases.
|
||||
- SemVer version range syntax: `^1.25.1`, `~1.24.1`, `>=1.25.0-rc.1`, `<1.25.0`, `>=1.22.0 <1.24.0`
|
||||
- Aliases: `stable`, `oldstable`
|
||||
- Wildcards: `1.25.x`, `1.x`
|
||||
|
||||
@@ -866,6 +866,11 @@ describe('setup-go', () => {
|
||||
expect(im.makeSemver('1.10rc1')).toBe('1.10.0-rc.1');
|
||||
});
|
||||
|
||||
it('preserves already-normalized prerelease versions', () => {
|
||||
expect(im.makeSemver('1.18.0-beta.2')).toBe('1.18.0-beta.2');
|
||||
expect(im.makeSemver('1.21.0-rc.2')).toBe('1.21.0-rc.2');
|
||||
});
|
||||
|
||||
it('converts dot zero versions', async () => {
|
||||
expect(im.makeSemver('1.13')).toBe('1.13.0');
|
||||
});
|
||||
@@ -874,6 +879,102 @@ describe('setup-go', () => {
|
||||
expect(im.makeSemver('1.13.1')).toBe('1.13.1');
|
||||
});
|
||||
|
||||
it('isGoNativeVersion accepts supported Go-native version formats', () => {
|
||||
expect(im.isGoNativeVersion('go1.21')).toBe(true);
|
||||
expect(im.isGoNativeVersion('go1.21.5')).toBe(true);
|
||||
expect(im.isGoNativeVersion('go1.18beta2')).toBe(true);
|
||||
expect(im.isGoNativeVersion('go1.21rc2')).toBe(true);
|
||||
|
||||
expect(im.isGoNativeVersion('go1.21.x')).toBe(false);
|
||||
expect(im.isGoNativeVersion('go^1.21')).toBe(false);
|
||||
expect(im.isGoNativeVersion('1.21')).toBe(false);
|
||||
expect(im.isGoNativeVersion('go1')).toBe(false);
|
||||
expect(im.isGoNativeVersion('go1.21.5beta1')).toBe(false);
|
||||
expect(im.isGoNativeVersion('go1.21.0rc1')).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts a Go-native go-version input and normalizes it', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
inputs['go-version'] = 'go1.21';
|
||||
findSpy.mockImplementation(() => path.normalize('/cache/go/1.21.0/x64'));
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith('Setup go version spec go1.21');
|
||||
expect(findSpy).toHaveBeenCalledWith('go', '1.21.0', 'x64');
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'Successfully set up Go version go1.21'
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects a malformed Go-native go-version input', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
inputs['go-version'] = 'go1.21.x';
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Invalid Go version "go1.21.x"')
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts a Go-native go-version input with beta suffix and normalizes it', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
inputs['go-version'] = 'go1.18beta2';
|
||||
findSpy.mockImplementation(() =>
|
||||
path.normalize('/cache/go/1.18.0-beta.2/x64')
|
||||
);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith('Setup go version spec go1.18beta2');
|
||||
expect(findSpy).toHaveBeenCalledWith('go', '1.18.0-beta.2', 'x64');
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'Successfully set up Go version go1.18beta2'
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts a Go-native go-version input with rc suffix and normalizes it', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
inputs['go-version'] = 'go1.21rc2';
|
||||
findSpy.mockImplementation(() =>
|
||||
path.normalize('/cache/go/1.21.0-rc.2/x64')
|
||||
);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith('Setup go version spec go1.21rc2');
|
||||
expect(findSpy).toHaveBeenCalledWith('go', '1.21.0-rc.2', 'x64');
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'Successfully set up Go version go1.21rc2'
|
||||
);
|
||||
});
|
||||
|
||||
it('downloads a normalized Go-native version on cache miss', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
inputs['go-version'] = 'go1.13';
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
cacheSpy.mockImplementation(async () =>
|
||||
path.normalize('/cache/go/1.13.0/x64')
|
||||
);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith('Attempting to download 1.13.0...');
|
||||
expect(dlSpy).toHaveBeenCalled();
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'Successfully set up Go version go1.13'
|
||||
);
|
||||
});
|
||||
|
||||
describe('check-latest flag', () => {
|
||||
it("use local version and don't check manifest if check-latest is not specified", async () => {
|
||||
os.platform = 'linux';
|
||||
@@ -1297,6 +1398,43 @@ use .
|
||||
});
|
||||
|
||||
describe('go-download-base-url', () => {
|
||||
it('normalizes native versions but preserves custom download filenames', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
const versionSpec = 'go1.21rc2';
|
||||
const customBaseUrl = 'https://example.com/golang';
|
||||
const toolName = im.customToolCacheName(customBaseUrl);
|
||||
const toolPath = path.normalize(`/cache/${toolName}/1.21.0-rc.2/x64`);
|
||||
|
||||
inputs['go-version'] = versionSpec;
|
||||
inputs['go-download-base-url'] = customBaseUrl;
|
||||
|
||||
getSpy.mockImplementationOnce(() => {
|
||||
throw new Error('Not a JSON endpoint');
|
||||
});
|
||||
findSpy.mockImplementation(() => '');
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
execFileSpy.mockImplementation(() => 'go version go1.21rc2 linux/amd64');
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(findSpy).toHaveBeenCalledWith(toolName, '1.21.0-rc.2', 'x64');
|
||||
expect(getSpy).toHaveBeenCalledWith(
|
||||
`${customBaseUrl}/?mode=json&include=all`
|
||||
);
|
||||
expect(dlSpy).toHaveBeenCalledWith(
|
||||
`${customBaseUrl}/go1.21rc2.linux-amd64.tar.gz`,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::add-path::${path.join(toolPath, 'bin')}${osm.EOL}`
|
||||
);
|
||||
});
|
||||
|
||||
it('downloads a version from custom base URL using version listing', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ description: 'Setup a Go environment and add it to the PATH'
|
||||
author: 'GitHub'
|
||||
inputs:
|
||||
go-version:
|
||||
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 native Go versions (e.g. go1.21.5), semver specs 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.'
|
||||
check-latest:
|
||||
|
||||
Vendored
+23
-4
@@ -41969,6 +41969,10 @@ async function getGo(versionSpec, checkLatest, auth, arch = external_os_default(
|
||||
let manifest;
|
||||
const osPlat = external_os_default().platform();
|
||||
const customBaseUrl = goDownloadBaseUrl?.replace(/\/+$/, '');
|
||||
const originalVersionSpec = versionSpec;
|
||||
if (isGoNativeVersion(versionSpec)) {
|
||||
versionSpec = makeSemver(versionSpec);
|
||||
}
|
||||
if (versionSpec === StableReleaseAlias.Stable ||
|
||||
versionSpec === StableReleaseAlias.OldStable) {
|
||||
if (customBaseUrl) {
|
||||
@@ -42023,7 +42027,7 @@ async function getGo(versionSpec, checkLatest, auth, arch = external_os_default(
|
||||
const skipVersionListing = NO_VERSION_LISTING_BASE_URLS.some(url => customBaseUrl.toLowerCase() === url.toLowerCase());
|
||||
if (skipVersionListing) {
|
||||
core_info('Skipping version listing for known direct-download URL. Constructing download URL directly.');
|
||||
info = getInfoFromDirectDownload(versionSpec, arch, customBaseUrl);
|
||||
info = getInfoFromDirectDownload(originalVersionSpec, arch, customBaseUrl);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
@@ -42033,7 +42037,7 @@ async function getGo(versionSpec, checkLatest, auth, arch = external_os_default(
|
||||
core_info('Version listing not available from custom URL. Constructing download URL directly.');
|
||||
}
|
||||
if (!info) {
|
||||
info = getInfoFromDirectDownload(versionSpec, arch, customBaseUrl);
|
||||
info = getInfoFromDirectDownload(originalVersionSpec, arch, customBaseUrl);
|
||||
}
|
||||
}
|
||||
try {
|
||||
@@ -42043,10 +42047,10 @@ async function getGo(versionSpec, checkLatest, auth, arch = external_os_default(
|
||||
catch (err) {
|
||||
const downloadUrl = info?.downloadUrl || customBaseUrl;
|
||||
if (err instanceof HTTPError && err.httpStatusCode === 404) {
|
||||
throw new Error(`The requested Go version ${versionSpec} is not available for platform ${osPlat}/${arch}. ` +
|
||||
throw new Error(`The requested Go version ${originalVersionSpec} is not available for platform ${osPlat}/${arch}. ` +
|
||||
`Download URL returned HTTP 404: ${downloadUrl}`, { cause: err });
|
||||
}
|
||||
throw new Error(`Failed to download Go ${versionSpec} for platform ${osPlat}/${arch} ` +
|
||||
throw new Error(`Failed to download Go ${originalVersionSpec} for platform ${osPlat}/${arch} ` +
|
||||
`from ${downloadUrl}: ${err}`, { cause: err });
|
||||
}
|
||||
}
|
||||
@@ -42335,6 +42339,10 @@ async function findMatch(versionSpec, arch = external_os_default().arch(), dlUrl
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function isGoNativeVersion(version) {
|
||||
const expression = /^go[1-9]\d*\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*)|(?:beta|rc)[1-9]\d*)?$/;
|
||||
return expression.test(version);
|
||||
}
|
||||
//
|
||||
// Convert the go version syntax into semver for semver matching
|
||||
// 1.13.1 => 1.13.1
|
||||
@@ -42342,6 +42350,10 @@ async function findMatch(versionSpec, arch = external_os_default().arch(), dlUrl
|
||||
// 1.10beta1 => 1.10.0-beta.1, 1.10rc1 => 1.10.0-rc.1
|
||||
// 1.8.5beta1 => 1.8.5-beta.1, 1.8.5rc1 => 1.8.5-rc.1
|
||||
function makeSemver(version) {
|
||||
const validVersion = node_modules_semver.valid(version);
|
||||
if (validVersion) {
|
||||
return validVersion;
|
||||
}
|
||||
version = version.replace('go', '');
|
||||
version = version.replace('beta', '-beta.').replace('rc', '-rc.');
|
||||
const parts = version.split('-');
|
||||
@@ -97835,6 +97847,13 @@ function resolveVersionInput() {
|
||||
warning('Both go-version and go-version-file inputs are specified, only go-version will be used');
|
||||
}
|
||||
if (version) {
|
||||
const customBaseUrl = getInput('go-download-base-url') ||
|
||||
process.env['GO_DOWNLOAD_BASE_URL'];
|
||||
if (version.startsWith('go') &&
|
||||
!customBaseUrl &&
|
||||
!isGoNativeVersion(version)) {
|
||||
throw new Error(`Invalid Go version "${version}". Go-native versions must look like go1.21, go1.21.5, go1.18beta2, or go1.21rc2. For version ranges, use SemVer syntax (e.g. ~1.21, ^1.21).`);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
if (versionFilePath) {
|
||||
|
||||
@@ -50,9 +50,13 @@ steps:
|
||||
- The preinstalled patch version is generally the latest available. When a new patch is released, it replaces the previously preinstalled version on the runner.
|
||||
- Using the most recent patch version speeds up setup because the required Go version is already installed on the runner and no download is needed.
|
||||
|
||||
The `go-version` input also accepts native Go version strings: `go1.21.5`, `go1.18beta2`, and `go1.21rc2` normalize to `1.21.5`, `1.18.0-beta.2`, and `1.21.0-rc.2`, respectively. `go1.21` resolves to exactly `1.21.0`; use `1.21` for matching patch releases. For ranges and wildcards, use SemVer syntax such as `~1.21` or `1.21.x`, not `go1.21.x`.
|
||||
|
||||
To try a **pre-release**:
|
||||
Download and use beta or RC Go versions as needed.
|
||||
|
||||
The `go-version` input also accepts native Go version strings: `go1.21.5`, `go1.18beta2`, and `go1.21rc2` normalize to `1.21.5`, `1.18.0-beta.2`, and `1.21.0-rc.2`, respectively. `go1.21` resolves to exactly `1.21.0`; use `1.21` for matching patch releases. For ranges and wildcards, use SemVer syntax such as `~1.21` or `1.21.x`, not `go1.21.x`.
|
||||
|
||||
```yaml
|
||||
# RC version
|
||||
steps:
|
||||
@@ -459,6 +463,8 @@ The `go-download-base-url` input lets you download Go from a mirror or alternati
|
||||
|
||||
When a custom base URL is provided, the action skips the `actions/go-versions` manifest lookup and downloads directly from the specified URL.
|
||||
|
||||
> **Note:** When constructing direct-download filenames, the original version string is preserved; native Go version normalization is used only for matching and tool cache lookups.
|
||||
|
||||
**Using the [Microsoft build of Go](https://github.com/microsoft/go):**
|
||||
|
||||
```yaml
|
||||
|
||||
+27
-4
@@ -60,6 +60,11 @@ export async function getGo(
|
||||
const osPlat: string = os.platform();
|
||||
const customBaseUrl = goDownloadBaseUrl?.replace(/\/+$/, '');
|
||||
|
||||
const originalVersionSpec = versionSpec;
|
||||
if (isGoNativeVersion(versionSpec)) {
|
||||
versionSpec = makeSemver(versionSpec);
|
||||
}
|
||||
|
||||
if (
|
||||
versionSpec === StableReleaseAlias.Stable ||
|
||||
versionSpec === StableReleaseAlias.OldStable
|
||||
@@ -146,7 +151,11 @@ export async function getGo(
|
||||
core.info(
|
||||
'Skipping version listing for known direct-download URL. Constructing download URL directly.'
|
||||
);
|
||||
info = getInfoFromDirectDownload(versionSpec, arch, customBaseUrl);
|
||||
info = getInfoFromDirectDownload(
|
||||
originalVersionSpec,
|
||||
arch,
|
||||
customBaseUrl
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
info = await getInfoFromDist(versionSpec, arch, customBaseUrl);
|
||||
@@ -156,7 +165,11 @@ export async function getGo(
|
||||
);
|
||||
}
|
||||
if (!info) {
|
||||
info = getInfoFromDirectDownload(versionSpec, arch, customBaseUrl);
|
||||
info = getInfoFromDirectDownload(
|
||||
originalVersionSpec,
|
||||
arch,
|
||||
customBaseUrl
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,13 +180,13 @@ export async function getGo(
|
||||
const downloadUrl = info?.downloadUrl || customBaseUrl;
|
||||
if (err instanceof tc.HTTPError && err.httpStatusCode === 404) {
|
||||
throw new Error(
|
||||
`The requested Go version ${versionSpec} is not available for platform ${osPlat}/${arch}. ` +
|
||||
`The requested Go version ${originalVersionSpec} is not available for platform ${osPlat}/${arch}. ` +
|
||||
`Download URL returned HTTP 404: ${downloadUrl}`,
|
||||
{cause: err}
|
||||
);
|
||||
}
|
||||
throw new Error(
|
||||
`Failed to download Go ${versionSpec} for platform ${osPlat}/${arch} ` +
|
||||
`Failed to download Go ${originalVersionSpec} for platform ${osPlat}/${arch} ` +
|
||||
`from ${downloadUrl}: ${err}`,
|
||||
{cause: err}
|
||||
);
|
||||
@@ -610,6 +623,12 @@ export async function findMatch(
|
||||
return result;
|
||||
}
|
||||
|
||||
export function isGoNativeVersion(version: string): boolean {
|
||||
const expression =
|
||||
/^go[1-9]\d*\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*)|(?:beta|rc)[1-9]\d*)?$/;
|
||||
return expression.test(version);
|
||||
}
|
||||
|
||||
//
|
||||
// Convert the go version syntax into semver for semver matching
|
||||
// 1.13.1 => 1.13.1
|
||||
@@ -617,6 +636,10 @@ export async function findMatch(
|
||||
// 1.10beta1 => 1.10.0-beta.1, 1.10rc1 => 1.10.0-rc.1
|
||||
// 1.8.5beta1 => 1.8.5-beta.1, 1.8.5rc1 => 1.8.5-rc.1
|
||||
export function makeSemver(version: string): string {
|
||||
const validVersion = semver.valid(version);
|
||||
if (validVersion) {
|
||||
return validVersion;
|
||||
}
|
||||
version = version.replace('go', '');
|
||||
version = version.replace('beta', '-beta.').replace('rc', '-rc.');
|
||||
const parts = version.split('-');
|
||||
|
||||
+12
@@ -215,6 +215,18 @@ function resolveVersionInput(): string {
|
||||
}
|
||||
|
||||
if (version) {
|
||||
const customBaseUrl =
|
||||
core.getInput('go-download-base-url') ||
|
||||
process.env['GO_DOWNLOAD_BASE_URL'];
|
||||
if (
|
||||
version.startsWith('go') &&
|
||||
!customBaseUrl &&
|
||||
!installer.isGoNativeVersion(version)
|
||||
) {
|
||||
throw new Error(
|
||||
`Invalid Go version "${version}". Go-native versions must look like go1.21, go1.21.5, go1.18beta2, or go1.21rc2. For version ranges, use SemVer syntax (e.g. ~1.21, ^1.21).`
|
||||
);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user