Files
setup-go/src/main.ts
George Adams 8f19afcc70 feat: add go-download-base-url input for custom Go distributions (#721)
* 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
2026-03-16 12:43:44 -05:00

192 lines
5.7 KiB
TypeScript

import * as core from '@actions/core';
import * as io from '@actions/io';
import * as installer from './installer';
import * as semver from 'semver';
import path from 'path';
import {restoreCache} from './cache-restore';
import {isCacheFeatureAvailable} from './cache-utils';
import cp from 'child_process';
import fs from 'fs';
import os from 'os';
import {Architecture} from './types';
export async function run() {
try {
//
// versionSpec is optional. If supplied, install / use from the tool cache
// If not supplied then problem matchers will still be setup. Useful for self-hosted.
//
const versionSpec = resolveVersionInput();
setGoToolchain();
const cache = core.getBooleanInput('cache');
core.info(`Setup go version spec ${versionSpec}`);
let arch = core.getInput('architecture') as Architecture;
if (!arch) {
arch = os.arch() as Architecture;
}
if (versionSpec) {
const token = core.getInput('token');
const auth = !token ? undefined : `token ${token}`;
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(
versionSpec,
checkLatest,
auth,
arch,
goDownloadBaseUrl
);
const installDirVersion = path.basename(path.dirname(installDir));
core.addPath(path.join(installDir, 'bin'));
core.info('Added go to the path');
const version = installer.makeSemver(installDirVersion);
// Go versions less than 1.9 require GOROOT to be set
if (semver.lt(version, '1.9.0')) {
core.info('Setting GOROOT for Go version < 1.9');
core.exportVariable('GOROOT', installDir);
}
core.info(`Successfully set up Go version ${versionSpec}`);
} else {
core.info(
'[warning]go-version input was not specified. The action will try to use pre-installed version.'
);
}
const added = await addBinToPath();
core.debug(`add bin ${added}`);
const goPath = await io.which('go');
const goVersion = (cp.execSync(`${goPath} version`) || '').toString();
if (cache && isCacheFeatureAvailable()) {
const packageManager = 'default';
const cacheDependencyPath = core.getInput('cache-dependency-path');
try {
await restoreCache(
parseGoVersion(goVersion),
packageManager,
cacheDependencyPath
);
} catch (error) {
core.warning(`Restore cache failed: ${(error as Error).message}`);
}
}
// add problem matchers
const matchersPath = path.join(__dirname, '../..', 'matchers.json');
core.info(`##[add-matcher]${matchersPath}`);
// output the version actually being used
core.info(goVersion);
core.setOutput('go-version', parseGoVersion(goVersion));
core.startGroup('go env');
const goEnv = (cp.execSync(`${goPath} env`) || '').toString();
core.info(goEnv);
core.endGroup();
} catch (error) {
core.setFailed((error as Error).message);
}
}
export async function addBinToPath(): Promise<boolean> {
let added = false;
const g = await io.which('go');
core.debug(`which go :${g}:`);
if (!g) {
core.debug('go not in the path');
return added;
}
const buf = cp.execSync('go env GOPATH');
if (buf.length > 1) {
const gp = buf.toString().trim();
core.debug(`go env GOPATH :${gp}:`);
if (!fs.existsSync(gp)) {
// some of the hosted images have go install but not profile dir
core.debug(`creating ${gp}`);
await io.mkdirP(gp);
}
const bp = path.join(gp, 'bin');
if (!fs.existsSync(bp)) {
core.debug(`creating ${bp}`);
await io.mkdirP(bp);
}
core.addPath(bp);
added = true;
}
return added;
}
export function parseGoVersion(versionString: string): string {
// get the installed version as an Action output
// based on go/src/cmd/go/internal/version/version.go:
// fmt.Printf("go version %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
// expecting go<version> for runtime.Version()
return versionString.split(' ')[2].slice('go'.length);
}
function resolveVersionInput(): string {
let version = core.getInput('go-version');
const versionFilePath = core.getInput('go-version-file');
if (version && versionFilePath) {
core.warning(
'Both go-version and go-version-file inputs are specified, only go-version will be used'
);
}
if (version) {
return version;
}
if (versionFilePath) {
if (!fs.existsSync(versionFilePath)) {
throw new Error(
`The specified go version file at: ${versionFilePath} does not exist`
);
}
version = installer.parseGoVersionFile(versionFilePath);
}
return version;
}
function setGoToolchain() {
// docs: https://go.dev/doc/toolchain
// "local indicates the bundled Go toolchain (the one that shipped with the go command being run)"
// this is so any 'go' command is run with the selected Go version
// and doesn't trigger a toolchain download and run commands with that
// see e.g. issue #424
// and a similar discussion: https://github.com/docker-library/golang/issues/472.
// Set the value in process env so any `go` commands run as child-process
// don't cause toolchain downloads
process.env[installer.GOTOOLCHAIN_ENV_VAR] = installer.GOTOOLCHAIN_LOCAL_VAL;
// and in the runner env so e.g. a user running `go mod tidy` won't cause it
core.exportVariable(
installer.GOTOOLCHAIN_ENV_VAR,
installer.GOTOOLCHAIN_LOCAL_VAL
);
}