feat: add go-download-base-url input for custom Go distributions (#721)
Some checks failed
Validate Microsoft build of Go / Microsoft build of Go 1.24 on macos-latest (push) Waiting to run
Validate Microsoft build of Go / Microsoft build of Go 1.24 on windows-latest (push) Waiting to run
Validate Microsoft build of Go / Microsoft build of Go 1.25 on macos-latest (push) Waiting to run
Validate Microsoft build of Go / Microsoft build of Go 1.25 on windows-latest (push) Waiting to run
Validate Microsoft build of Go / Microsoft build of Go via env var on macos-latest (push) Waiting to run
Validate Microsoft build of Go / Microsoft build of Go via env var on windows-latest (push) Waiting to run
Validate Microsoft build of Go / Microsoft build of Go arch arm64 on macos-latest (push) Waiting to run
Validate Microsoft build of Go / Microsoft build of Go arch x64 on macos-latest (push) Waiting to run
Validate Microsoft build of Go / Microsoft build of Go with caching on macos-latest (push) Waiting to run
Validate Microsoft build of Go / Microsoft build of Go with caching on windows-latest (push) Waiting to run
Validate 'setup-go' / stable (macos-latest) (push) Waiting to run
Validate 'setup-go' / stable (macos-latest-large) (push) Waiting to run
Validate 'setup-go' / stable (windows-latest) (push) Waiting to run
Validate 'setup-go' / oldstable (macos-latest) (push) Waiting to run
Validate 'setup-go' / oldstable (macos-latest-large) (push) Waiting to run
Validate 'setup-go' / oldstable (windows-latest) (push) Waiting to run
Validate 'setup-go' / aliases-arch (x32, windows-latest, oldstable) (push) Waiting to run
Validate 'setup-go' / aliases-arch (x32, windows-latest, stable) (push) Waiting to run
Validate 'setup-go' / aliases-arch (x64, macos-latest, oldstable) (push) Waiting to run
Validate 'setup-go' / aliases-arch (x64, macos-latest, stable) (push) Waiting to run
Validate 'setup-go' / aliases-arch (x64, macos-latest-large, oldstable) (push) Waiting to run
Validate 'setup-go' / aliases-arch (x64, macos-latest-large, stable) (push) Waiting to run
Validate 'setup-go' / aliases-arch (x64, windows-latest, oldstable) (push) Waiting to run
Validate 'setup-go' / aliases-arch (x64, windows-latest, stable) (push) Waiting to run
Validate 'setup-go' / Setup local-cache version (1.20.14, windows-latest) (push) Waiting to run
Validate 'setup-go' / Setup local-cache version (1.21.13, macos-latest) (push) Waiting to run
Validate 'setup-go' / Setup local-cache version (1.21.13, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / Setup local-cache version (1.21.13, windows-latest) (push) Waiting to run
Validate 'setup-go' / Setup local-cache version (1.22.8, macos-latest) (push) Waiting to run
Validate 'setup-go' / Setup local-cache version (1.22.8, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / Setup local-cache version (1.22.8, windows-latest) (push) Waiting to run
Validate 'setup-go' / Setup local-cache version (1.23.2, macos-latest) (push) Waiting to run
Validate 'setup-go' / Setup local-cache version (1.23.2, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / check-latest (1.20, macos-latest) (push) Waiting to run
Validate 'setup-go' / check-latest (1.20, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / check-latest (1.20, windows-latest) (push) Waiting to run
Validate 'setup-go' / check-latest (1.21, macos-latest) (push) Waiting to run
Validate 'setup-go' / check-latest (1.21, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / check-latest (1.21, windows-latest) (push) Waiting to run
Validate 'setup-go' / check-latest (1.22, macos-latest) (push) Waiting to run
Validate 'setup-go' / check-latest (1.22, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / check-latest (1.22, windows-latest) (push) Waiting to run
Validate 'setup-go' / check-latest (1.23, macos-latest) (push) Waiting to run
Validate 'setup-go' / check-latest (1.23, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / check-latest (1.23, windows-latest) (push) Waiting to run
Validate 'setup-go' / go-version-file (macos-latest) (push) Waiting to run
Validate 'setup-go' / go-version-file (macos-latest-large) (push) Waiting to run
Validate 'setup-go' / go-version-file (windows-latest) (push) Waiting to run
Validate 'setup-go' / go-version-file-with-gowork (macos-latest) (push) Waiting to run
Validate 'setup-go' / go-version-file-with-gowork (macos-latest-large) (push) Waiting to run
Validate 'setup-go' / go-version-file-with-gowork (windows-latest) (push) Waiting to run
Validate 'setup-go' / go-version-file-with-tool-versions (macos-latest) (push) Waiting to run
Validate 'setup-go' / go-version-file-with-tool-versions (macos-latest-large) (push) Waiting to run
Validate 'setup-go' / go-version-file-with-tool-versions (windows-latest) (push) Waiting to run
Validate 'setup-go' / go-version-file-with-go-version (macos-latest) (push) Waiting to run
Validate 'setup-go' / go-version-file-with-go-version (macos-latest-large) (push) Waiting to run
Validate 'setup-go' / go-version-file-with-go-version (windows-latest) (push) Waiting to run
Validate 'setup-go' / setup-versions-from-manifest (1.20.14, macos-latest) (push) Waiting to run
Validate 'setup-go' / setup-versions-from-manifest (1.20.14, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / setup-versions-from-manifest (1.20.14, windows-latest) (push) Waiting to run
Validate 'setup-go' / setup-versions-from-manifest (1.21.10, macos-latest) (push) Waiting to run
Validate 'setup-go' / setup-versions-from-manifest (1.21.10, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / setup-versions-from-manifest (1.21.10, windows-latest) (push) Waiting to run
Validate 'setup-go' / setup-versions-from-manifest (1.22.8, macos-latest) (push) Waiting to run
Validate 'setup-go' / setup-versions-from-manifest (1.22.8, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / setup-versions-from-manifest (1.22.8, windows-latest) (push) Waiting to run
Validate 'setup-go' / setup-versions-from-manifest (1.23.2, macos-latest) (push) Waiting to run
Validate 'setup-go' / setup-versions-from-manifest (1.23.2, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / setup-versions-from-manifest (1.23.2, windows-latest) (push) Waiting to run
Validate 'setup-go' / setup-versions-from-dist (1.11.12, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / setup-versions-from-dist (1.11.12, windows-latest) (push) Waiting to run
Validate 'setup-go' / architecture (arm64, 1.20.14, macos-latest) (push) Waiting to run
Validate 'setup-go' / architecture (arm64, 1.21, macos-latest) (push) Waiting to run
Validate 'setup-go' / architecture (arm64, 1.22, macos-latest) (push) Waiting to run
Validate 'setup-go' / architecture (arm64, 1.23, macos-latest) (push) Waiting to run
Validate 'setup-go' / architecture (x64, 1.20.14, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / architecture (x64, 1.20.14, windows-latest) (push) Waiting to run
Validate 'setup-go' / architecture (x64, 1.21, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / architecture (x64, 1.21, windows-latest) (push) Waiting to run
Validate 'setup-go' / architecture (x64, 1.22, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / architecture (x64, 1.22, windows-latest) (push) Waiting to run
Validate 'setup-go' / architecture (x64, 1.23, macos-latest-large) (push) Waiting to run
Validate 'setup-go' / architecture (x64, 1.23, windows-latest) (push) Waiting to run
Validate Windows installation / Validate if symlink is created (false, 1.20.1) (push) Waiting to run
Validate Windows installation / Validate if symlink is created (true, 1.20.1) (push) Waiting to run
Validate Windows installation / Find default go version (push) Waiting to run
Validate Windows installation / Validate if symlink is not created for default go (false) (push) Blocked by required conditions
Validate Windows installation / Validate if symlink is not created for default go (true) (push) Blocked by required conditions
Validate Windows installation / Validate if hostedtoolcache works as expected (false, 1.20.1) (push) Waiting to run
Basic validation / Basic validation (push) Failing after 37s
Validate Microsoft build of Go / Microsoft build of Go 1.24 on ubuntu-latest (push) Failing after 39s
Validate Microsoft build of Go / Microsoft build of Go 1.25 on ubuntu-latest (push) Failing after 40s
Validate Microsoft build of Go / Microsoft build of Go via env var on ubuntu-latest (push) Failing after 40s
Validate Microsoft build of Go / Microsoft build of Go arch x64 on ubuntu-latest (push) Failing after 40s
Validate Microsoft build of Go / Microsoft build of Go with caching on ubuntu-latest (push) Failing after 40s
Check dist/ / Check dist/ (push) Failing after 1m20s
CodeQL analysis / CodeQL analysis (push) Failing after 1m20s
Licensed / Licensed (push) Failing after 1m19s
Validate 'setup-go' / stable (ubuntu-latest) (push) Failing after 4s
Validate 'setup-go' / oldstable (ubuntu-latest) (push) Failing after 4s
Validate 'setup-go' / aliases-arch (x32, ubuntu-latest, oldstable) (push) Failing after 5s
Validate 'setup-go' / aliases-arch (x32, ubuntu-latest, stable) (push) Failing after 6s
Validate 'setup-go' / aliases-arch (x64, ubuntu-latest, oldstable) (push) Failing after 6s
Validate 'setup-go' / aliases-arch (x64, ubuntu-latest, stable) (push) Failing after 7s
Validate 'setup-go' / Setup local-cache version (1.21.13, ubuntu-latest) (push) Failing after 7s
Validate 'setup-go' / Setup local-cache version (1.22.8, ubuntu-latest) (push) Failing after 6s
Validate 'setup-go' / Setup local-cache version (1.23.2, ubuntu-latest) (push) Failing after 8s
Validate 'setup-go' / check-latest (1.20, ubuntu-latest) (push) Failing after 8s
Validate 'setup-go' / check-latest (1.21, ubuntu-latest) (push) Failing after 8s
Validate 'setup-go' / check-latest (1.22, ubuntu-latest) (push) Failing after 8s
Validate 'setup-go' / check-latest (1.23, ubuntu-latest) (push) Failing after 8s
Validate 'setup-go' / go-version-file (ubuntu-latest) (push) Failing after 8s
Validate 'setup-go' / go-version-file-with-gowork (ubuntu-latest) (push) Failing after 9s
Validate 'setup-go' / go-version-file-with-tool-versions (ubuntu-latest) (push) Failing after 9s
Validate 'setup-go' / go-version-file-with-go-version (ubuntu-latest) (push) Failing after 16s
Validate 'setup-go' / setup-versions-from-manifest (1.20.14, ubuntu-latest) (push) Failing after 17s
Validate 'setup-go' / setup-versions-from-manifest (1.21.10, ubuntu-latest) (push) Failing after 19s
Validate 'setup-go' / setup-versions-from-manifest (1.22.8, ubuntu-latest) (push) Failing after 21s
Validate 'setup-go' / setup-versions-from-manifest (1.23.2, ubuntu-latest) (push) Failing after 22s
Validate 'setup-go' / setup-versions-from-dist (1.11.12, ubuntu-latest) (push) Failing after 24s
Validate 'setup-go' / architecture (x64, 1.20.14, ubuntu-latest) (push) Failing after 25s
Validate 'setup-go' / architecture (x64, 1.21, ubuntu-latest) (push) Failing after 25s
Validate 'setup-go' / architecture (x64, 1.22, ubuntu-latest) (push) Failing after 29s
Validate 'setup-go' / architecture (x64, 1.23, ubuntu-latest) (push) Failing after 28s

* feat: add go-download-base-url input for custom Go distributions

Add support for downloading Go from custom sources such as Microsoft Go
(aka.ms). Users can specify a custom download base URL via the
`go-download-base-url` input or the `GO_DOWNLOAD_BASE_URL` environment
variable (input takes precedence).

When a custom URL is provided, the action skips the GitHub-hosted
manifest and attempts to resolve versions from the custom URL's JSON
listing. If the listing is unavailable (as with aka.ms redirect links),
it falls back to constructing the download URL directly from the
version, platform, and architecture.

Usage:
  - uses: actions/setup-go@v6
    with:
      go-version: '1.25'
      go-download-base-url: 'https://aka.ms/golang/release/latest'

Changes:
- action.yml: add go-download-base-url optional input
- installer.ts: add getInfoFromDirectDownload() for URL construction
  fallback, thread custom URL through getGo/getInfoFromDist/findMatch
- main.ts: read new input and GO_DOWNLOAD_BASE_URL env var
- setup-go.test.ts: add 12 unit tests for custom URL behavior
- microsoft-validation.yml: add E2E workflow testing Microsoft build of Go
  across ubuntu/windows/macos with versions 1.24 and 1.25
- README.md: document new input with Microsoft build of Go examples

* run prettier

* fixup PR review

* revert cache-save

* fixup

* handle distinct cache

* skip json for known URL

* fix bug in JSON with custom URL
This commit is contained in:
George Adams
2026-03-16 17:43:44 +00:00
committed by GitHub
parent 27fdb267c1
commit 8f19afcc70
8 changed files with 1062 additions and 117 deletions

View File

@@ -0,0 +1,143 @@
name: Validate Microsoft build of Go
on:
push:
branches:
- main
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
jobs:
microsoft-basic:
name: 'Microsoft build of Go ${{ matrix.go-version }} on ${{ matrix.os }}'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
go-version: ['1.25', '1.24']
steps:
- uses: actions/checkout@v6
- name: Setup Microsoft build of Go ${{ matrix.go-version }}
uses: ./
with:
go-version: ${{ matrix.go-version }}
go-download-base-url: 'https://aka.ms/golang/release/latest'
cache: false
- name: Verify Go installation
run: go version
- name: Verify Go env
run: go env
- name: Verify Go is functional
shell: bash
run: |
# Create a simple Go program and run it
mkdir -p /tmp/test-go && cd /tmp/test-go
cat > main.go << 'EOF'
package main
import "fmt"
func main() {
fmt.Println("Hello from Microsoft build of Go!")
}
EOF
go run main.go
microsoft-env-var:
name: 'Microsoft build of Go via env var on ${{ matrix.os }}'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
env:
GO_DOWNLOAD_BASE_URL: 'https://aka.ms/golang/release/latest'
steps:
- uses: actions/checkout@v6
- name: Setup Microsoft build of Go via environment variable
uses: ./
with:
go-version: '1.25'
cache: false
- name: Verify Go installation
run: go version
- name: Verify Go is functional
shell: bash
run: |
mkdir -p /tmp/test-go && cd /tmp/test-go
cat > main.go << 'EOF'
package main
import "fmt"
func main() {
fmt.Println("Hello from Microsoft build of Go via env var!")
}
EOF
go run main.go
microsoft-architecture:
name: 'Microsoft build of Go arch ${{ matrix.architecture }} on ${{ matrix.os }}'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
architecture: [x64]
include:
- os: macos-latest
architecture: arm64
steps:
- uses: actions/checkout@v6
- name: Setup Microsoft build of Go with architecture
uses: ./
with:
go-version: '1.25'
go-download-base-url: 'https://aka.ms/golang/release/latest'
architecture: ${{ matrix.architecture }}
cache: false
- name: Verify Go installation
run: go version
microsoft-with-cache:
name: 'Microsoft build of Go with caching on ${{ matrix.os }}'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v6
- name: Setup Microsoft build of Go with caching
uses: ./
with:
go-version: '1.25'
go-download-base-url: 'https://aka.ms/golang/release/latest'
cache: true
- name: Verify Go installation
run: go version
- name: Verify Go is functional
shell: bash
run: |
mkdir -p /tmp/test-go && cd /tmp/test-go
go mod init test
cat > main.go << 'EOF'
package main
import "fmt"
func main() {
fmt.Println("Hello from cached Microsoft build of Go!")
}
EOF
go run main.go

View File

@@ -54,6 +54,9 @@ See [action.yml](action.yml).
# Architecture to install (auto-detected if not specified) # Architecture to install (auto-detected if not specified)
architecture: 'x64' architecture: 'x64'
# Custom base URL for Go downloads (e.g., for mirrors)
go-download-base-url: ''
``` ```
<!-- end usage --> <!-- end usage -->
@@ -130,6 +133,7 @@ For examples of using `cache-dependency-path`, see the [Caching](docs/advanced-u
- [Check latest version](docs/advanced-usage.md#check-latest-version) - [Check latest version](docs/advanced-usage.md#check-latest-version)
- [Caching](docs/advanced-usage.md#caching) - [Caching](docs/advanced-usage.md#caching)
- [Outputs](docs/advanced-usage.md#outputs) - [Outputs](docs/advanced-usage.md#outputs)
- [Custom download URL](docs/advanced-usage.md#custom-download-url)
- [Using `setup-go` on GHES](docs/advanced-usage.md#using-setup-go-on-ghes) - [Using `setup-go` on GHES](docs/advanced-usage.md#using-setup-go-on-ghes)
## License ## License

View File

@@ -45,6 +45,7 @@ describe('setup-go', () => {
let mkdirSpy: jest.SpyInstance; let mkdirSpy: jest.SpyInstance;
let symlinkSpy: jest.SpyInstance; let symlinkSpy: jest.SpyInstance;
let execSpy: jest.SpyInstance; let execSpy: jest.SpyInstance;
let execFileSpy: jest.SpyInstance;
let getManifestSpy: jest.SpyInstance; let getManifestSpy: jest.SpyInstance;
let getAllVersionsSpy: jest.SpyInstance; let getAllVersionsSpy: jest.SpyInstance;
let httpmGetJsonSpy: jest.SpyInstance; let httpmGetJsonSpy: jest.SpyInstance;
@@ -71,6 +72,10 @@ describe('setup-go', () => {
archSpy = jest.spyOn(osm, 'arch'); archSpy = jest.spyOn(osm, 'arch');
archSpy.mockImplementation(() => os['arch']); archSpy.mockImplementation(() => os['arch']);
execSpy = jest.spyOn(cp, 'execSync'); execSpy = jest.spyOn(cp, 'execSync');
execFileSpy = jest.spyOn(cp, 'execFileSync');
execFileSpy.mockImplementation(() => {
throw new Error('ENOENT');
});
// switch path join behaviour based on set os.platform // switch path join behaviour based on set os.platform
joinSpy = jest.spyOn(path, 'join'); joinSpy = jest.spyOn(path, 'join');
@@ -129,8 +134,9 @@ describe('setup-go', () => {
}); });
afterEach(() => { afterEach(() => {
// clear out env var set during 'run' // clear out env vars set during 'run'
delete process.env[im.GOTOOLCHAIN_ENV_VAR]; delete process.env[im.GOTOOLCHAIN_ENV_VAR];
delete process.env['GO_DOWNLOAD_BASE_URL'];
//jest.resetAllMocks(); //jest.resetAllMocks();
jest.clearAllMocks(); jest.clearAllMocks();
@@ -1105,4 +1111,456 @@ use .
expect(vars).toStrictEqual({GOTOOLCHAIN: 'local'}); expect(vars).toStrictEqual({GOTOOLCHAIN: 'local'});
expect(process.env).toHaveProperty('GOTOOLCHAIN', 'local'); expect(process.env).toHaveProperty('GOTOOLCHAIN', 'local');
}); });
describe('go-download-base-url', () => {
it('downloads a version from custom base URL using version listing', async () => {
os.platform = 'linux';
os.arch = 'x64';
const versionSpec = '1.13.1';
const customBaseUrl = 'https://example.com/golang';
inputs['go-version'] = versionSpec;
inputs['go-download-base-url'] = customBaseUrl;
findSpy.mockImplementation(() => '');
dlSpy.mockImplementation(async () => '/some/temp/path');
const toolPath = path.normalize('/cache/go/1.13.1/x64');
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
cacheSpy.mockImplementation(async () => toolPath);
await main.run();
const expPath = path.join(toolPath, 'bin');
expect(logSpy).toHaveBeenCalledWith(
`Using custom Go download base URL: ${customBaseUrl}`
);
expect(logSpy).toHaveBeenCalledWith('Install from custom download URL');
// Version listing should use custom base URL, not go.dev
expect(getSpy).toHaveBeenCalledWith(
`${customBaseUrl}/?mode=json&include=all`
);
expect(dlSpy).toHaveBeenCalled();
expect(extractTarSpy).toHaveBeenCalled();
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
});
it('skips version listing for known direct-download URL (aka.ms)', async () => {
os.platform = 'linux';
os.arch = 'x64';
const versionSpec = '1.25.0';
const customBaseUrl = 'https://aka.ms/golang/release/latest';
inputs['go-version'] = versionSpec;
inputs['go-download-base-url'] = customBaseUrl;
findSpy.mockImplementation(() => '');
dlSpy.mockImplementation(async () => '/some/temp/path');
const toolPath = path.normalize('/cache/go/1.25.0/x64');
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
cacheSpy.mockImplementation(async () => toolPath);
await main.run();
const expPath = path.join(toolPath, 'bin');
expect(logSpy).toHaveBeenCalledWith(
'Skipping version listing for known direct-download URL. Constructing download URL directly.'
);
expect(logSpy).toHaveBeenCalledWith(
`Constructed direct download URL: ${customBaseUrl}/go1.25.0.linux-amd64.tar.gz`
);
expect(logSpy).toHaveBeenCalledWith('Install from custom download URL');
expect(getSpy).not.toHaveBeenCalled();
expect(dlSpy).toHaveBeenCalled();
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
});
it('constructs correct direct download URL for windows (aka.ms)', async () => {
os.platform = 'win32';
os.arch = 'x64';
const versionSpec = '1.25.0';
const customBaseUrl = 'https://aka.ms/golang/release/latest';
inputs['go-version'] = versionSpec;
inputs['go-download-base-url'] = customBaseUrl;
process.env['RUNNER_TEMP'] = 'C:\\temp\\';
findSpy.mockImplementation(() => '');
dlSpy.mockImplementation(async () => 'C:\\temp\\some\\path');
extractZipSpy.mockImplementation(() => 'C:\\temp\\some\\other\\path');
const toolPath = path.normalize('C:\\cache\\go\\1.25.0\\x64');
cacheSpy.mockImplementation(async () => toolPath);
await main.run();
expect(getSpy).not.toHaveBeenCalled();
expect(dlSpy).toHaveBeenCalledWith(
`${customBaseUrl}/go1.25.0.windows-amd64.zip`,
'C:\\temp\\go1.25.0.windows-amd64.zip',
undefined
);
});
it('skips manifest and downloads directly from custom URL', async () => {
os.platform = 'linux';
os.arch = 'x64';
const versionSpec = '1.12.16';
const customBaseUrl = 'https://example.com/golang';
inputs['go-version'] = versionSpec;
inputs['go-download-base-url'] = customBaseUrl;
inputs['token'] = 'faketoken';
findSpy.mockImplementation(() => '');
dlSpy.mockImplementation(async () => '/some/temp/path');
const toolPath = path.normalize('/cache/go/1.12.16/x64');
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
cacheSpy.mockImplementation(async () => toolPath);
await main.run();
// Should not try to use the manifest at all
expect(logSpy).not.toHaveBeenCalledWith(
expect.stringContaining('Not found in manifest')
);
expect(logSpy).toHaveBeenCalledWith('Install from custom download URL');
});
it('strips trailing slashes from custom base URL', async () => {
os.platform = 'linux';
os.arch = 'x64';
const versionSpec = '1.13.1';
const customBaseUrl = 'https://example.com/golang/';
inputs['go-version'] = versionSpec;
inputs['go-download-base-url'] = customBaseUrl;
findSpy.mockImplementation(() => '');
dlSpy.mockImplementation(async () => '/some/temp/path');
const toolPath = path.normalize('/cache/go/1.13.1/x64');
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
cacheSpy.mockImplementation(async () => toolPath);
await main.run();
expect(logSpy).toHaveBeenCalledWith(
`Acquiring go1.13.1 from https://example.com/golang/go1.13.1.linux-amd64.tar.gz`
);
});
it('reads custom base URL from environment variable', async () => {
os.platform = 'linux';
os.arch = 'x64';
const versionSpec = '1.13.1';
const customBaseUrl = 'https://example.com/golang';
inputs['go-version'] = versionSpec;
process.env['GO_DOWNLOAD_BASE_URL'] = customBaseUrl;
findSpy.mockImplementation(() => '');
dlSpy.mockImplementation(async () => '/some/temp/path');
const toolPath = path.normalize('/cache/go/1.13.1/x64');
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
cacheSpy.mockImplementation(async () => toolPath);
await main.run();
expect(logSpy).toHaveBeenCalledWith(
`Using custom Go download base URL: ${customBaseUrl}`
);
expect(logSpy).toHaveBeenCalledWith('Install from custom download URL');
});
it('input takes precedence over environment variable', async () => {
os.platform = 'linux';
os.arch = 'x64';
const versionSpec = '1.13.1';
const inputUrl = 'https://input.example.com/golang';
const envUrl = 'https://env.example.com/golang';
inputs['go-version'] = versionSpec;
inputs['go-download-base-url'] = inputUrl;
process.env['GO_DOWNLOAD_BASE_URL'] = envUrl;
findSpy.mockImplementation(() => '');
dlSpy.mockImplementation(async () => '/some/temp/path');
const toolPath = path.normalize('/cache/go/1.13.1/x64');
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
cacheSpy.mockImplementation(async () => toolPath);
await main.run();
expect(logSpy).toHaveBeenCalledWith(
`Using custom Go download base URL: ${inputUrl}`
);
expect(logSpy).toHaveBeenCalledWith(
`Acquiring go1.13.1 from ${inputUrl}/go1.13.1.linux-amd64.tar.gz`
);
});
it('errors when stable alias is used with custom URL', async () => {
os.platform = 'linux';
os.arch = 'x64';
inputs['go-version'] = 'stable';
inputs['go-download-base-url'] = 'https://example.com/golang';
findSpy.mockImplementation(() => '');
await main.run();
expect(cnSpy).toHaveBeenCalledWith(
`::error::Version aliases 'stable' are not supported with a custom download base URL. Please specify an exact Go version.${osm.EOL}`
);
});
it('logs info when check-latest is used with custom URL', async () => {
os.platform = 'linux';
os.arch = 'x64';
const versionSpec = '1.13.1';
const customBaseUrl = 'https://example.com/golang';
inputs['go-version'] = versionSpec;
inputs['go-download-base-url'] = customBaseUrl;
inputs['check-latest'] = true;
findSpy.mockImplementation(() => '');
dlSpy.mockImplementation(async () => '/some/temp/path');
const toolPath = path.normalize('/cache/go/1.13.1/x64');
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
cacheSpy.mockImplementation(async () => toolPath);
await main.run();
expect(logSpy).toHaveBeenCalledWith(
'check-latest is not supported with a custom download base URL. Using the provided version spec directly.'
);
});
it('constructs direct download info correctly', () => {
os.platform = 'linux';
os.arch = 'x64';
const info = im.getInfoFromDirectDownload(
'1.25.0',
'x64',
'https://aka.ms/golang/release/latest'
);
expect(info.type).toBe('dist');
expect(info.downloadUrl).toBe(
'https://aka.ms/golang/release/latest/go1.25.0.linux-amd64.tar.gz'
);
expect(info.fileName).toBe('go1.25.0.linux-amd64.tar.gz');
expect(info.resolvedVersion).toBe('1.25.0');
});
it('constructs direct download info for windows', () => {
os.platform = 'win32';
os.arch = 'x64';
const info = im.getInfoFromDirectDownload(
'1.25.0',
'x64',
'https://aka.ms/golang/release/latest'
);
expect(info.type).toBe('dist');
expect(info.downloadUrl).toBe(
'https://aka.ms/golang/release/latest/go1.25.0.windows-amd64.zip'
);
expect(info.fileName).toBe('go1.25.0.windows-amd64.zip');
});
it('constructs direct download info for arm64', () => {
os.platform = 'darwin';
os.arch = 'arm64';
const info = im.getInfoFromDirectDownload(
'1.25.0',
'arm64',
'https://aka.ms/golang/release/latest'
);
expect(info.type).toBe('dist');
expect(info.downloadUrl).toBe(
'https://aka.ms/golang/release/latest/go1.25.0.darwin-arm64.tar.gz'
);
expect(info.fileName).toBe('go1.25.0.darwin-arm64.tar.gz');
});
it('caches under actual installed version when it differs from input spec', async () => {
os.platform = 'linux';
os.arch = 'x64';
const versionSpec = '1.20';
const customBaseUrl = 'https://aka.ms/golang/release/latest';
inputs['go-version'] = versionSpec;
inputs['go-download-base-url'] = customBaseUrl;
findSpy.mockImplementation(() => '');
dlSpy.mockImplementation(async () => '/some/temp/path');
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
// Mock the installed Go binary reporting a different patch version
execFileSpy.mockImplementation(() => 'go version go1.20.14 linux/amd64');
const expectedToolName = im.customToolCacheName(customBaseUrl);
const toolPath = path.normalize(`/cache/${expectedToolName}/1.20.14/x64`);
cacheSpy.mockImplementation(async () => toolPath);
await main.run();
expect(logSpy).toHaveBeenCalledWith(
"Requested version '1.20' resolved to installed version '1.20.14'"
);
// Cache key should use actual version, not the input spec
expect(cacheSpy).toHaveBeenCalledWith(
expect.any(String),
expectedToolName,
'1.20.14',
'x64'
);
});
it('shows clear error with platform/arch and URL on 404', async () => {
os.platform = 'linux';
os.arch = 'arm64';
const versionSpec = '1.25.0';
const customBaseUrl = 'https://example.com/golang';
inputs['go-version'] = versionSpec;
inputs['go-download-base-url'] = customBaseUrl;
getSpy.mockImplementationOnce(() => {
throw new Error('Not a JSON endpoint');
});
findSpy.mockImplementation(() => '');
const httpError = new tc.HTTPError(404);
dlSpy.mockImplementation(() => {
throw httpError;
});
await main.run();
expect(cnSpy).toHaveBeenCalledWith(
expect.stringContaining(
'The requested Go version 1.25.0 is not available for platform linux/arm64'
)
);
expect(cnSpy).toHaveBeenCalledWith(expect.stringContaining('HTTP 404'));
});
it('shows clear error with platform/arch and URL on download failure', async () => {
os.platform = 'linux';
os.arch = 'x64';
const versionSpec = '1.25.0';
const customBaseUrl = 'https://example.com/golang';
inputs['go-version'] = versionSpec;
inputs['go-download-base-url'] = customBaseUrl;
getSpy.mockImplementationOnce(() => {
throw new Error('Not a JSON endpoint');
});
findSpy.mockImplementation(() => '');
dlSpy.mockImplementation(() => {
throw new Error('connection refused');
});
await main.run();
expect(cnSpy).toHaveBeenCalledWith(
expect.stringContaining(
'Failed to download Go 1.25.0 for platform linux/x64'
)
);
expect(cnSpy).toHaveBeenCalledWith(
expect.stringContaining(customBaseUrl)
);
});
it.each(['^1.25.0', '~1.25', '>=1.25.0', '<1.26.0', '1.25.x', '1.x'])(
'errors on version range "%s" when version listing is unavailable',
async versionSpec => {
os.platform = 'linux';
os.arch = 'x64';
inputs['go-version'] = versionSpec;
inputs['go-download-base-url'] = 'https://example.com/golang';
// Simulate version listing not available
getSpy.mockImplementationOnce(() => {
throw new Error('Not a JSON endpoint');
});
findSpy.mockImplementation(() => '');
await main.run();
expect(cnSpy).toHaveBeenCalledWith(
expect.stringContaining(
`Version range '${versionSpec}' is not supported with a custom download base URL`
)
);
}
);
it('rejects version range in getInfoFromDirectDownload', () => {
os.platform = 'linux';
os.arch = 'x64';
expect(() =>
im.getInfoFromDirectDownload(
'^1.25.0',
'x64',
'https://example.com/golang'
)
).toThrow(
"Version range '^1.25.0' is not supported with a custom download base URL"
);
});
it('passes token as auth header for custom URL downloads', async () => {
os.platform = 'linux';
os.arch = 'x64';
const versionSpec = '1.25.0';
const customBaseUrl = 'https://private-mirror.example.com/golang';
inputs['go-version'] = versionSpec;
inputs['go-download-base-url'] = customBaseUrl;
inputs['token'] = 'ghp_testtoken123';
getSpy.mockImplementationOnce(() => {
throw new Error('Not a JSON endpoint');
});
findSpy.mockImplementation(() => '');
dlSpy.mockImplementation(async () => '/some/temp/path');
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
const expectedToolName = im.customToolCacheName(customBaseUrl);
const toolPath = path.normalize(`/cache/${expectedToolName}/1.25.0/x64`);
cacheSpy.mockImplementation(async () => toolPath);
await main.run();
expect(dlSpy).toHaveBeenCalledWith(
`${customBaseUrl}/go1.25.0.linux-amd64.tar.gz`,
undefined,
'token ghp_testtoken123'
);
});
});
}); });

View File

@@ -19,6 +19,8 @@ inputs:
description: 'Used to specify the path to a dependency file (e.g., go.mod, go.sum)' description: 'Used to specify the path to a dependency file (e.g., go.mod, go.sum)'
architecture: architecture:
description: 'Target architecture for Go to use. Examples: x86, x64. Will use system architecture by default.' description: 'Target architecture for Go to use. Examples: x86, x64. Will use system architecture by default.'
go-download-base-url:
description: 'Custom base URL for downloading Go distributions. Use this to download Go from a mirror or custom source. Defaults to "https://go.dev/dl". Can also be set via the GO_DOWNLOAD_BASE_URL environment variable. The input takes precedence over the environment variable.'
outputs: outputs:
go-version: go-version:
description: 'The installed Go version. Useful when given a version range as input.' description: 'The installed Go version. Useful when given a version range as input.'

148
dist/setup/index.js vendored
View File

@@ -77034,9 +77034,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.GOTOOLCHAIN_LOCAL_VAL = exports.GOTOOLCHAIN_ENV_VAR = void 0; exports.GOTOOLCHAIN_LOCAL_VAL = exports.GOTOOLCHAIN_ENV_VAR = void 0;
exports.getGo = getGo; exports.getGo = getGo;
exports.customToolCacheName = customToolCacheName;
exports.extractGoArchive = extractGoArchive; exports.extractGoArchive = extractGoArchive;
exports.getManifest = getManifest; exports.getManifest = getManifest;
exports.getInfoFromManifest = getInfoFromManifest; exports.getInfoFromManifest = getInfoFromManifest;
exports.getInfoFromDirectDownload = getInfoFromDirectDownload;
exports.findMatch = findMatch; exports.findMatch = findMatch;
exports.getVersionsDist = getVersionsDist; exports.getVersionsDist = getVersionsDist;
exports.makeSemver = makeSemver; exports.makeSemver = makeSemver;
@@ -77048,6 +77050,8 @@ const path = __importStar(__nccwpck_require__(16928));
const semver = __importStar(__nccwpck_require__(62088)); const semver = __importStar(__nccwpck_require__(62088));
const httpm = __importStar(__nccwpck_require__(54844)); const httpm = __importStar(__nccwpck_require__(54844));
const sys = __importStar(__nccwpck_require__(57666)); const sys = __importStar(__nccwpck_require__(57666));
const crypto_1 = __importDefault(__nccwpck_require__(76982));
const child_process_1 = __importDefault(__nccwpck_require__(35317));
const fs_1 = __importDefault(__nccwpck_require__(79896)); const fs_1 = __importDefault(__nccwpck_require__(79896));
const os_1 = __importDefault(__nccwpck_require__(70857)); const os_1 = __importDefault(__nccwpck_require__(70857));
const utils_1 = __nccwpck_require__(71798); const utils_1 = __nccwpck_require__(71798);
@@ -77057,14 +77061,23 @@ const MANIFEST_REPO_OWNER = 'actions';
const MANIFEST_REPO_NAME = 'go-versions'; const MANIFEST_REPO_NAME = 'go-versions';
const MANIFEST_REPO_BRANCH = 'main'; const MANIFEST_REPO_BRANCH = 'main';
const MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`; const MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`;
const DEFAULT_GO_DOWNLOAD_BASE_URL = 'https://go.dev/dl';
const GOLANG_DOWNLOAD_URL = 'https://go.dev/dl/?mode=json&include=all'; const GOLANG_DOWNLOAD_URL = 'https://go.dev/dl/?mode=json&include=all';
// Base URLs known to not serve a version listing JSON endpoint.
// For these URLs we skip the getInfoFromDist() call entirely and construct
// the download URL directly, avoiding a guaranteed-404 HTTP request.
const NO_VERSION_LISTING_BASE_URLS = ['https://aka.ms/golang/release/latest'];
function getGo(versionSpec_1, checkLatest_1, auth_1) { function getGo(versionSpec_1, checkLatest_1, auth_1) {
return __awaiter(this, arguments, void 0, function* (versionSpec, checkLatest, auth, arch = os_1.default.arch()) { return __awaiter(this, arguments, void 0, function* (versionSpec, checkLatest, auth, arch = os_1.default.arch(), goDownloadBaseUrl) {
var _a; var _a;
let manifest; let manifest;
const osPlat = os_1.default.platform(); const osPlat = os_1.default.platform();
const customBaseUrl = goDownloadBaseUrl === null || goDownloadBaseUrl === void 0 ? void 0 : goDownloadBaseUrl.replace(/\/+$/, '');
if (versionSpec === utils_1.StableReleaseAlias.Stable || if (versionSpec === utils_1.StableReleaseAlias.Stable ||
versionSpec === utils_1.StableReleaseAlias.OldStable) { versionSpec === utils_1.StableReleaseAlias.OldStable) {
if (customBaseUrl) {
throw new Error(`Version aliases '${versionSpec}' are not supported with a custom download base URL. Please specify an exact Go version.`);
}
manifest = yield getManifest(auth); manifest = yield getManifest(auth);
let stableVersion = yield resolveStableVersionInput(versionSpec, arch, osPlat, manifest); let stableVersion = yield resolveStableVersionInput(versionSpec, arch, osPlat, manifest);
if (!stableVersion) { if (!stableVersion) {
@@ -77077,6 +77090,10 @@ function getGo(versionSpec_1, checkLatest_1, auth_1) {
versionSpec = stableVersion; versionSpec = stableVersion;
} }
if (checkLatest) { if (checkLatest) {
if (customBaseUrl) {
core.info('check-latest is not supported with a custom download base URL. Using the provided version spec directly.');
}
else {
core.info('Attempting to resolve the latest version from the manifest...'); core.info('Attempting to resolve the latest version from the manifest...');
const resolvedVersion = yield resolveVersionFromManifest(versionSpec, true, auth, arch, manifest); const resolvedVersion = yield resolveVersionFromManifest(versionSpec, true, auth, arch, manifest);
if (resolvedVersion) { if (resolvedVersion) {
@@ -77087,8 +77104,14 @@ function getGo(versionSpec_1, checkLatest_1, auth_1) {
core.info(`Failed to resolve version ${versionSpec} from manifest`); core.info(`Failed to resolve version ${versionSpec} from manifest`);
} }
} }
}
// Use a distinct tool cache name for custom downloads to avoid
// colliding with the runner's pre-installed Go
const toolCacheName = customBaseUrl
? customToolCacheName(customBaseUrl)
: 'go';
// check cache // check cache
const toolPath = tc.find('go', versionSpec, arch); const toolPath = tc.find(toolCacheName, versionSpec, arch);
// If not found in cache, download // If not found in cache, download
if (toolPath) { if (toolPath) {
core.info(`Found in cache @ ${toolPath}`); core.info(`Found in cache @ ${toolPath}`);
@@ -77097,6 +77120,41 @@ function getGo(versionSpec_1, checkLatest_1, auth_1) {
core.info(`Attempting to download ${versionSpec}...`); core.info(`Attempting to download ${versionSpec}...`);
let downloadPath = ''; let downloadPath = '';
let info = null; let info = null;
if (customBaseUrl) {
//
// Download from custom base URL
//
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);
}
else {
try {
info = yield getInfoFromDist(versionSpec, arch, customBaseUrl);
}
catch (_b) {
core.info('Version listing not available from custom URL. Constructing download URL directly.');
}
if (!info) {
info = getInfoFromDirectDownload(versionSpec, arch, customBaseUrl);
}
}
try {
core.info('Install from custom download URL');
downloadPath = yield installGoVersion(info, auth, arch, toolCacheName);
}
catch (err) {
const downloadUrl = (info === null || info === void 0 ? void 0 : 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}. ` +
`Download URL returned HTTP 404: ${downloadUrl}`);
}
throw new Error(`Failed to download Go ${versionSpec} for platform ${osPlat}/${arch} ` +
`from ${downloadUrl}: ${err}`);
}
}
else {
// //
// Try download from internal distribution (popular versions only) // Try download from internal distribution (popular versions only)
// //
@@ -77136,6 +77194,7 @@ function getGo(versionSpec_1, checkLatest_1, auth_1) {
throw new Error(`Failed to download version ${versionSpec}: ${err}`); throw new Error(`Failed to download version ${versionSpec}: ${err}`);
} }
} }
}
return downloadPath; return downloadPath;
}); });
} }
@@ -77186,16 +77245,19 @@ function cacheWindowsDir(extPath, tool, version, arch) {
return defaultToolCacheDir; return defaultToolCacheDir;
}); });
} }
function addExecutablesToToolCache(extPath, info, arch) { function addExecutablesToToolCache(extPath_1, info_1, arch_1) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, arguments, void 0, function* (extPath, info, arch, toolName = 'go') {
const tool = 'go';
const version = makeSemver(info.resolvedVersion); const version = makeSemver(info.resolvedVersion);
return ((yield cacheWindowsDir(extPath, tool, version, arch)) || return ((yield cacheWindowsDir(extPath, toolName, version, arch)) ||
(yield tc.cacheDir(extPath, tool, version, arch))); (yield tc.cacheDir(extPath, toolName, version, arch)));
}); });
} }
function installGoVersion(info, auth, arch) { function customToolCacheName(baseUrl) {
return __awaiter(this, void 0, void 0, function* () { const hash = crypto_1.default.createHash('sha256').update(baseUrl).digest('hex');
return `go-${hash.substring(0, 8)}`;
}
function installGoVersion(info_1, auth_1, arch_1) {
return __awaiter(this, arguments, void 0, function* (info, auth, arch, toolName = 'go') {
core.info(`Acquiring ${info.resolvedVersion} from ${info.downloadUrl}`); core.info(`Acquiring ${info.resolvedVersion} from ${info.downloadUrl}`);
// Windows requires that we keep the extension (.zip) for extraction // Windows requires that we keep the extension (.zip) for extraction
const isWindows = os_1.default.platform() === 'win32'; const isWindows = os_1.default.platform() === 'win32';
@@ -77208,12 +77270,33 @@ function installGoVersion(info, auth, arch) {
if (info.type === 'dist') { if (info.type === 'dist') {
extPath = path.join(extPath, 'go'); extPath = path.join(extPath, 'go');
} }
// For custom downloads, detect the actual installed version so the cache
// key reflects the real patch level (e.g. input "1.20" may install 1.20.14).
if (toolName !== 'go') {
const actualVersion = detectInstalledGoVersion(extPath);
if (actualVersion && actualVersion !== info.resolvedVersion) {
core.info(`Requested version '${info.resolvedVersion}' resolved to installed version '${actualVersion}'`);
info.resolvedVersion = actualVersion;
}
}
core.info('Adding to the cache ...'); core.info('Adding to the cache ...');
const toolCacheDir = yield addExecutablesToToolCache(extPath, info, arch); const toolCacheDir = yield addExecutablesToToolCache(extPath, info, arch, toolName);
core.info(`Successfully cached go to ${toolCacheDir}`); core.info(`Successfully cached go to ${toolCacheDir}`);
return toolCacheDir; return toolCacheDir;
}); });
} }
function detectInstalledGoVersion(goDir) {
try {
const goBin = path.join(goDir, 'bin', os_1.default.platform() === 'win32' ? 'go.exe' : 'go');
const output = child_process_1.default.execFileSync(goBin, ['version'], { encoding: 'utf8' });
const match = output.match(/go version go(\S+)/);
return match ? match[1] : null;
}
catch (err) {
core.debug(`Failed to detect installed Go version: ${err.message}`);
return null;
}
}
function extractGoArchive(archivePath) { function extractGoArchive(archivePath) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const platform = os_1.default.platform(); const platform = os_1.default.platform();
@@ -77298,13 +77381,17 @@ function getInfoFromManifest(versionSpec_1, stable_1, auth_1) {
return info; return info;
}); });
} }
function getInfoFromDist(versionSpec, arch) { function getInfoFromDist(versionSpec, arch, goDownloadBaseUrl) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const version = yield findMatch(versionSpec, arch); const dlUrl = goDownloadBaseUrl
? `${goDownloadBaseUrl}/?mode=json&include=all`
: GOLANG_DOWNLOAD_URL;
const version = yield findMatch(versionSpec, arch, dlUrl);
if (!version) { if (!version) {
return null; return null;
} }
const downloadUrl = `https://go.dev/dl/${version.files[0].filename}`; const baseUrl = goDownloadBaseUrl || DEFAULT_GO_DOWNLOAD_BASE_URL;
const downloadUrl = `${baseUrl}/${version.files[0].filename}`;
return { return {
type: 'dist', type: 'dist',
downloadUrl: downloadUrl, downloadUrl: downloadUrl,
@@ -77313,13 +77400,36 @@ function getInfoFromDist(versionSpec, arch) {
}; };
}); });
} }
function getInfoFromDirectDownload(versionSpec, arch, goDownloadBaseUrl) {
// Reject version specs that can't map to an artifact filename
if (/[~^>=<|*x]/.test(versionSpec)) {
throw new Error(`Version range '${versionSpec}' is not supported with a custom download base URL ` +
`when version listing is unavailable. Please specify an exact version (e.g., '1.25.0').`);
}
const archStr = sys.getArch(arch);
const platStr = sys.getPlatform();
const extension = platStr === 'windows' ? 'zip' : 'tar.gz';
// Ensure version has the 'go' prefix for the filename
const goVersion = versionSpec.startsWith('go')
? versionSpec
: `go${versionSpec}`;
const fileName = `${goVersion}.${platStr}-${archStr}.${extension}`;
const downloadUrl = `${goDownloadBaseUrl}/${fileName}`;
core.info(`Constructed direct download URL: ${downloadUrl}`);
return {
type: 'dist',
downloadUrl: downloadUrl,
resolvedVersion: versionSpec.replace(/^go/, ''),
fileName: fileName
};
}
function findMatch(versionSpec_1) { function findMatch(versionSpec_1) {
return __awaiter(this, arguments, void 0, function* (versionSpec, arch = os_1.default.arch()) { return __awaiter(this, arguments, void 0, function* (versionSpec, arch = os_1.default.arch(), dlUrl = GOLANG_DOWNLOAD_URL) {
const archFilter = sys.getArch(arch); const archFilter = sys.getArch(arch);
const platFilter = sys.getPlatform(); const platFilter = sys.getPlatform();
let result; let result;
let match; let match;
const candidates = yield module.exports.getVersionsDist(GOLANG_DOWNLOAD_URL); const candidates = yield module.exports.getVersionsDist(dlUrl);
if (!candidates) { if (!candidates) {
throw new Error(`golang download url did not return results`); throw new Error(`golang download url did not return results`);
} }
@@ -77529,7 +77639,13 @@ 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'); const checkLatest = core.getBooleanInput('check-latest');
const installDir = yield installer.getGo(versionSpec, checkLatest, auth, arch); const goDownloadBaseUrl = core.getInput('go-download-base-url') ||
process.env['GO_DOWNLOAD_BASE_URL'] ||
undefined;
if (goDownloadBaseUrl) {
core.info(`Using custom Go download base URL: ${goDownloadBaseUrl}`);
}
const installDir = yield installer.getGo(versionSpec, checkLatest, auth, arch, goDownloadBaseUrl);
const installDirVersion = path_1.default.basename(path_1.default.dirname(installDir)); const installDirVersion = path_1.default.basename(path_1.default.dirname(installDir));
core.addPath(path_1.default.join(installDir, 'bin')); core.addPath(path_1.default.join(installDir, 'bin'));
core.info('Added go to the path'); core.info('Added go to the path');

View File

@@ -12,6 +12,7 @@
- [Restore-only caches](advanced-usage.md#restore-only-caches) - [Restore-only caches](advanced-usage.md#restore-only-caches)
- [Parallel builds](advanced-usage.md#parallel-builds) - [Parallel builds](advanced-usage.md#parallel-builds)
- [Outputs](advanced-usage.md#outputs) - [Outputs](advanced-usage.md#outputs)
- [Custom download URL](advanced-usage.md#custom-download-url)
- [Using `setup-go` on GHES](advanced-usage.md#using-setup-go-on-ghes) - [Using `setup-go` on GHES](advanced-usage.md#using-setup-go-on-ghes)
## Using the `go-version` input ## Using the `go-version` input
@@ -222,6 +223,8 @@ want the most up-to-date Go version to always be used. It supports major (e.g.,
> Setting `check-latest` to `true` has performance implications as downloading Go versions is slower than using cached > Setting `check-latest` to `true` has performance implications as downloading Go versions is slower than using cached
> versions. > versions.
>
> `check-latest` is ignored when `go-download-base-url` is set. See [Custom download URL](#custom-download-url) for details.
```yaml ```yaml
steps: steps:
@@ -417,6 +420,57 @@ jobs:
- run: echo "Was the Go cache restored? ${{ steps.go124.outputs.cache-hit }}" # true if cache-hit occurred - run: echo "Was the Go cache restored? ${{ steps.go124.outputs.cache-hit }}" # true if cache-hit occurred
``` ```
## Custom download URL
The `go-download-base-url` input lets you download Go from a mirror or alternative source instead of the default `https://go.dev/dl`. This can also be set via the `GO_DOWNLOAD_BASE_URL` environment variable; the input takes precedence over the environment variable.
When a custom base URL is provided, the action skips the `actions/go-versions` manifest lookup and downloads directly from the specified URL.
**Using the [Microsoft build of Go](https://github.com/nicholasgasior/microsoft-go):**
```yaml
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: '1.25'
go-download-base-url: 'https://aka.ms/golang/release/latest'
- run: go version
```
**Using an environment variable:**
```yaml
env:
GO_DOWNLOAD_BASE_URL: 'https://aka.ms/golang/release/latest'
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: '1.25'
- run: go version
```
> **Note:** Version range syntax (`^1.25`, `~1.24`, `>=1.25.0`) and aliases (`stable`, `oldstable`) are not supported with custom download URLs. Use exact versions such as `1.25`, `1.25.0`, or `1.25.0-1` (for sources that use revision numbers). If the custom server provides a version listing endpoint (`/?mode=json&include=all`), semver ranges will work; otherwise only exact versions are accepted.
> **Note:** The `check-latest` option is ignored when a custom download base URL is set. The action cannot query the custom server for the latest version, so it uses the version you specify directly. If you provide a partial version like `1.25`, the server determines which patch release to serve.
**Authenticated downloads:**
If your custom download source requires authentication, the `token` input is forwarded as an `Authorization` header. For example, to download from a private mirror:
```yaml
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: '1.25'
go-download-base-url: 'https://private-mirror.example.com/golang'
token: ${{ secrets.MIRROR_TOKEN }}
- run: go version
```
## Using `setup-go` on GHES ## Using `setup-go` on GHES
### Avoiding rate limit issues ### Avoiding rate limit issues

View File

@@ -4,6 +4,8 @@ import * as path from 'path';
import * as semver from 'semver'; import * as semver from 'semver';
import * as httpm from '@actions/http-client'; import * as httpm from '@actions/http-client';
import * as sys from './system'; import * as sys from './system';
import crypto from 'crypto';
import cp from 'child_process';
import fs from 'fs'; import fs from 'fs';
import os from 'os'; import os from 'os';
import {StableReleaseAlias, isSelfHosted} from './utils'; import {StableReleaseAlias, isSelfHosted} from './utils';
@@ -15,11 +17,17 @@ const MANIFEST_REPO_OWNER = 'actions';
const MANIFEST_REPO_NAME = 'go-versions'; const MANIFEST_REPO_NAME = 'go-versions';
const MANIFEST_REPO_BRANCH = 'main'; const MANIFEST_REPO_BRANCH = 'main';
const MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`; const MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`;
const DEFAULT_GO_DOWNLOAD_BASE_URL = 'https://go.dev/dl';
type InstallationType = 'dist' | 'manifest'; type InstallationType = 'dist' | 'manifest';
const GOLANG_DOWNLOAD_URL = 'https://go.dev/dl/?mode=json&include=all'; const GOLANG_DOWNLOAD_URL = 'https://go.dev/dl/?mode=json&include=all';
// Base URLs known to not serve a version listing JSON endpoint.
// For these URLs we skip the getInfoFromDist() call entirely and construct
// the download URL directly, avoiding a guaranteed-404 HTTP request.
const NO_VERSION_LISTING_BASE_URLS = ['https://aka.ms/golang/release/latest'];
export interface IGoVersionFile { export interface IGoVersionFile {
filename: string; filename: string;
// darwin, linux, windows // darwin, linux, windows
@@ -44,15 +52,23 @@ export async function getGo(
versionSpec: string, versionSpec: string,
checkLatest: boolean, checkLatest: boolean,
auth: string | undefined, auth: string | undefined,
arch: Architecture = os.arch() as Architecture arch: Architecture = os.arch() as Architecture,
goDownloadBaseUrl?: string
) { ) {
let manifest: tc.IToolRelease[] | undefined; let manifest: tc.IToolRelease[] | undefined;
const osPlat: string = os.platform(); const osPlat: string = os.platform();
const customBaseUrl = goDownloadBaseUrl?.replace(/\/+$/, '');
if ( if (
versionSpec === StableReleaseAlias.Stable || versionSpec === StableReleaseAlias.Stable ||
versionSpec === StableReleaseAlias.OldStable versionSpec === StableReleaseAlias.OldStable
) { ) {
if (customBaseUrl) {
throw new Error(
`Version aliases '${versionSpec}' are not supported with a custom download base URL. Please specify an exact Go version.`
);
}
manifest = await getManifest(auth); manifest = await getManifest(auth);
let stableVersion = await resolveStableVersionInput( let stableVersion = await resolveStableVersionInput(
versionSpec, versionSpec,
@@ -76,7 +92,14 @@ export async function getGo(
} }
if (checkLatest) { if (checkLatest) {
core.info('Attempting to resolve the latest version from the manifest...'); if (customBaseUrl) {
core.info(
'check-latest is not supported with a custom download base URL. Using the provided version spec directly.'
);
} else {
core.info(
'Attempting to resolve the latest version from the manifest...'
);
const resolvedVersion = await resolveVersionFromManifest( const resolvedVersion = await resolveVersionFromManifest(
versionSpec, versionSpec,
true, true,
@@ -91,9 +114,16 @@ export async function getGo(
core.info(`Failed to resolve version ${versionSpec} from manifest`); core.info(`Failed to resolve version ${versionSpec} from manifest`);
} }
} }
}
// Use a distinct tool cache name for custom downloads to avoid
// colliding with the runner's pre-installed Go
const toolCacheName = customBaseUrl
? customToolCacheName(customBaseUrl)
: 'go';
// check cache // check cache
const toolPath = tc.find('go', versionSpec, arch); const toolPath = tc.find(toolCacheName, versionSpec, arch);
// If not found in cache, download // If not found in cache, download
if (toolPath) { if (toolPath) {
core.info(`Found in cache @ ${toolPath}`); core.info(`Found in cache @ ${toolPath}`);
@@ -103,6 +133,49 @@ export async function getGo(
let downloadPath = ''; let downloadPath = '';
let info: IGoVersionInfo | null = null; let info: IGoVersionInfo | null = null;
if (customBaseUrl) {
//
// Download from custom base URL
//
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);
} else {
try {
info = await getInfoFromDist(versionSpec, arch, customBaseUrl);
} catch {
core.info(
'Version listing not available from custom URL. Constructing download URL directly.'
);
}
if (!info) {
info = getInfoFromDirectDownload(versionSpec, arch, customBaseUrl);
}
}
try {
core.info('Install from custom download URL');
downloadPath = await installGoVersion(info, auth, arch, toolCacheName);
} catch (err) {
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}. ` +
`Download URL returned HTTP 404: ${downloadUrl}`
);
}
throw new Error(
`Failed to download Go ${versionSpec} for platform ${osPlat}/${arch} ` +
`from ${downloadUrl}: ${err}`
);
}
} else {
// //
// Try download from internal distribution (popular versions only) // Try download from internal distribution (popular versions only)
// //
@@ -148,6 +221,7 @@ export async function getGo(
throw new Error(`Failed to download version ${versionSpec}: ${err}`); throw new Error(`Failed to download version ${versionSpec}: ${err}`);
} }
} }
}
return downloadPath; return downloadPath;
} }
@@ -229,20 +303,26 @@ async function cacheWindowsDir(
async function addExecutablesToToolCache( async function addExecutablesToToolCache(
extPath: string, extPath: string,
info: IGoVersionInfo, info: IGoVersionInfo,
arch: string arch: string,
toolName: string = 'go'
): Promise<string> { ): Promise<string> {
const tool = 'go';
const version = makeSemver(info.resolvedVersion); const version = makeSemver(info.resolvedVersion);
return ( return (
(await cacheWindowsDir(extPath, tool, version, arch)) || (await cacheWindowsDir(extPath, toolName, version, arch)) ||
(await tc.cacheDir(extPath, tool, version, arch)) (await tc.cacheDir(extPath, toolName, version, arch))
); );
} }
export function customToolCacheName(baseUrl: string): string {
const hash = crypto.createHash('sha256').update(baseUrl).digest('hex');
return `go-${hash.substring(0, 8)}`;
}
async function installGoVersion( async function installGoVersion(
info: IGoVersionInfo, info: IGoVersionInfo,
auth: string | undefined, auth: string | undefined,
arch: string arch: string,
toolName: string = 'go'
): Promise<string> { ): Promise<string> {
core.info(`Acquiring ${info.resolvedVersion} from ${info.downloadUrl}`); core.info(`Acquiring ${info.resolvedVersion} from ${info.downloadUrl}`);
@@ -260,13 +340,48 @@ async function installGoVersion(
extPath = path.join(extPath, 'go'); extPath = path.join(extPath, 'go');
} }
// For custom downloads, detect the actual installed version so the cache
// key reflects the real patch level (e.g. input "1.20" may install 1.20.14).
if (toolName !== 'go') {
const actualVersion = detectInstalledGoVersion(extPath);
if (actualVersion && actualVersion !== info.resolvedVersion) {
core.info(
`Requested version '${info.resolvedVersion}' resolved to installed version '${actualVersion}'`
);
info.resolvedVersion = actualVersion;
}
}
core.info('Adding to the cache ...'); core.info('Adding to the cache ...');
const toolCacheDir = await addExecutablesToToolCache(extPath, info, arch); const toolCacheDir = await addExecutablesToToolCache(
extPath,
info,
arch,
toolName
);
core.info(`Successfully cached go to ${toolCacheDir}`); core.info(`Successfully cached go to ${toolCacheDir}`);
return toolCacheDir; return toolCacheDir;
} }
function detectInstalledGoVersion(goDir: string): string | null {
try {
const goBin = path.join(
goDir,
'bin',
os.platform() === 'win32' ? 'go.exe' : 'go'
);
const output = cp.execFileSync(goBin, ['version'], {encoding: 'utf8'});
const match = output.match(/go version go(\S+)/);
return match ? match[1] : null;
} catch (err) {
core.debug(
`Failed to detect installed Go version: ${(err as Error).message}`
);
return null;
}
}
export async function extractGoArchive(archivePath: string): Promise<string> { export async function extractGoArchive(archivePath: string): Promise<string> {
const platform = os.platform(); const platform = os.platform();
let extPath: string; let extPath: string;
@@ -384,14 +499,23 @@ export async function getInfoFromManifest(
async function getInfoFromDist( async function getInfoFromDist(
versionSpec: string, versionSpec: string,
arch: Architecture arch: Architecture,
goDownloadBaseUrl?: string
): Promise<IGoVersionInfo | null> { ): Promise<IGoVersionInfo | null> {
const version: IGoVersion | undefined = await findMatch(versionSpec, arch); const dlUrl = goDownloadBaseUrl
? `${goDownloadBaseUrl}/?mode=json&include=all`
: GOLANG_DOWNLOAD_URL;
const version: IGoVersion | undefined = await findMatch(
versionSpec,
arch,
dlUrl
);
if (!version) { if (!version) {
return null; return null;
} }
const downloadUrl = `https://go.dev/dl/${version.files[0].filename}`; const baseUrl = goDownloadBaseUrl || DEFAULT_GO_DOWNLOAD_BASE_URL;
const downloadUrl = `${baseUrl}/${version.files[0].filename}`;
return <IGoVersionInfo>{ return <IGoVersionInfo>{
type: 'dist', type: 'dist',
@@ -401,9 +525,43 @@ async function getInfoFromDist(
}; };
} }
export function getInfoFromDirectDownload(
versionSpec: string,
arch: Architecture,
goDownloadBaseUrl: string
): IGoVersionInfo {
// Reject version specs that can't map to an artifact filename
if (/[~^>=<|*x]/.test(versionSpec)) {
throw new Error(
`Version range '${versionSpec}' is not supported with a custom download base URL ` +
`when version listing is unavailable. Please specify an exact version (e.g., '1.25.0').`
);
}
const archStr = sys.getArch(arch);
const platStr = sys.getPlatform();
const extension = platStr === 'windows' ? 'zip' : 'tar.gz';
// Ensure version has the 'go' prefix for the filename
const goVersion = versionSpec.startsWith('go')
? versionSpec
: `go${versionSpec}`;
const fileName = `${goVersion}.${platStr}-${archStr}.${extension}`;
const downloadUrl = `${goDownloadBaseUrl}/${fileName}`;
core.info(`Constructed direct download URL: ${downloadUrl}`);
return <IGoVersionInfo>{
type: 'dist',
downloadUrl: downloadUrl,
resolvedVersion: versionSpec.replace(/^go/, ''),
fileName: fileName
};
}
export async function findMatch( export async function findMatch(
versionSpec: string, versionSpec: string,
arch: Architecture = os.arch() as Architecture arch: Architecture = os.arch() as Architecture,
dlUrl: string = GOLANG_DOWNLOAD_URL
): Promise<IGoVersion | undefined> { ): Promise<IGoVersion | undefined> {
const archFilter = sys.getArch(arch); const archFilter = sys.getArch(arch);
const platFilter = sys.getPlatform(); const platFilter = sys.getPlatform();
@@ -412,7 +570,7 @@ export async function findMatch(
let match: IGoVersion | undefined; let match: IGoVersion | undefined;
const candidates: IGoVersion[] | null = await module.exports.getVersionsDist( const candidates: IGoVersion[] | null = await module.exports.getVersionsDist(
GOLANG_DOWNLOAD_URL dlUrl
); );
if (!candidates) { if (!candidates) {
throw new Error(`golang download url did not return results`); throw new Error(`golang download url did not return results`);

View File

@@ -34,11 +34,21 @@ export async function run() {
const checkLatest = core.getBooleanInput('check-latest'); const checkLatest = core.getBooleanInput('check-latest');
const goDownloadBaseUrl =
core.getInput('go-download-base-url') ||
process.env['GO_DOWNLOAD_BASE_URL'] ||
undefined;
if (goDownloadBaseUrl) {
core.info(`Using custom Go download base URL: ${goDownloadBaseUrl}`);
}
const installDir = await installer.getGo( const installDir = await installer.getGo(
versionSpec, versionSpec,
checkLatest, checkLatest,
auth, auth,
arch arch,
goDownloadBaseUrl
); );
const installDirVersion = path.basename(path.dirname(installDir)); const installDirVersion = path.basename(path.dirname(installDir));