mirror of
https://github.com/actions/setup-go.git
synced 2026-09-19 01:51:15 +00:00
Expose go env variables as action outputs (#791)
* feature implementation - output go env * update action.yml * fix action.yml --------- Co-authored-by: mahabaleshwars <147705296+mahabaleshwars@users.noreply.github.com>
This commit is contained in:
@@ -43,6 +43,31 @@ jobs:
|
||||
- name: Verify Go
|
||||
run: go version
|
||||
|
||||
go-env-outputs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go
|
||||
id: setup-go
|
||||
uses: ./
|
||||
with:
|
||||
go-version: stable
|
||||
- name: Show Go environment outputs
|
||||
run: |
|
||||
echo "go-path: ${{ steps.setup-go.outputs.go-path }}"
|
||||
echo "go-bin: ${{ steps.setup-go.outputs.go-bin }}"
|
||||
echo "go-bin-path: ${{ steps.setup-go.outputs.go-bin-path }}"
|
||||
echo "go-root: ${{ steps.setup-go.outputs.go-root }}"
|
||||
echo "go-cache: ${{ steps.setup-go.outputs.go-cache }}"
|
||||
echo "go-mod-cache: ${{ steps.setup-go.outputs.go-mod-cache }}"
|
||||
echo "go-os: ${{ steps.setup-go.outputs.go-os }}"
|
||||
echo "go-arch: ${{ steps.setup-go.outputs.go-arch }}"
|
||||
echo "go-tool-dir: ${{ steps.setup-go.outputs.go-tool-dir }}"
|
||||
|
||||
aliases-arch:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
|
||||
@@ -28,6 +28,8 @@ jest.unstable_mockModule('@actions/core', () => ({
|
||||
getBooleanInput: jest.fn(),
|
||||
info: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
exportVariable: jest.fn()
|
||||
}));
|
||||
|
||||
@@ -110,6 +112,8 @@ describe('setup-go', () => {
|
||||
let findSpy: jest.Mock;
|
||||
let cnSpy: jest.SpiedFunction<typeof process.stdout.write>;
|
||||
let logSpy: jest.Mock;
|
||||
let setOutputSpy: jest.Mock<typeof core.setOutput>;
|
||||
let warningSpy: jest.Mock<typeof core.warning>;
|
||||
let getSpy: jest.Mock;
|
||||
let platSpy: jest.Mock;
|
||||
let archSpy: jest.Mock;
|
||||
@@ -198,6 +202,8 @@ describe('setup-go', () => {
|
||||
// writes
|
||||
cnSpy = jest.spyOn(process.stdout, 'write');
|
||||
logSpy = core.info as jest.Mock;
|
||||
setOutputSpy = core.setOutput as jest.Mock<typeof core.setOutput>;
|
||||
warningSpy = core.warning as jest.Mock<typeof core.warning>;
|
||||
dbgSpy = core.debug as jest.Mock;
|
||||
getSpy.mockImplementation(() => goJsonData as IGoVersion[] | null);
|
||||
cnSpy.mockImplementation(() => true);
|
||||
@@ -646,6 +652,104 @@ describe('setup-go', () => {
|
||||
expect(added).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('go env outputs', () => {
|
||||
const goEnv = {
|
||||
GOPATH: '/Users/testuser/go',
|
||||
GOBIN: '',
|
||||
GOROOT: '/usr/local/go',
|
||||
GOCACHE: '/Users/testuser/.cache/go-build',
|
||||
GOMODCACHE: '/Users/testuser/go/pkg/mod',
|
||||
GOOS: 'darwin',
|
||||
GOARCH: 'arm64',
|
||||
GOTOOLDIR: '/usr/local/go/pkg/tool/darwin_arm64'
|
||||
};
|
||||
|
||||
it('parses the output of go env -json', () => {
|
||||
execFileSpy.mockImplementation(() => JSON.stringify(goEnv));
|
||||
|
||||
expect(main.readGoEnv('/usr/local/go/bin/go')).toEqual(goEnv);
|
||||
});
|
||||
|
||||
it('returns undefined when go env -json is not supported', () => {
|
||||
execFileSpy.mockImplementation(() => {
|
||||
throw new Error('flag provided but not defined: -json');
|
||||
});
|
||||
|
||||
expect(main.readGoEnv('/usr/local/go/bin/go')).toBeUndefined();
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('flag provided but not defined: -json')
|
||||
);
|
||||
expect(warningSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns undefined when the output is not valid JSON', () => {
|
||||
execFileSpy.mockImplementation(() => 'GOPATH="/Users/testuser/go"');
|
||||
|
||||
expect(main.readGoEnv('/usr/local/go/bin/go')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns undefined when the output is not a JSON object', () => {
|
||||
execFileSpy.mockImplementation(() => '[]');
|
||||
|
||||
expect(main.readGoEnv('/usr/local/go/bin/go')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('sets an output for each exposed variable', () => {
|
||||
main.setGoEnvOutputs(goEnv);
|
||||
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('go-path', goEnv.GOPATH);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('go-bin', '');
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('go-root', goEnv.GOROOT);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('go-cache', goEnv.GOCACHE);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(
|
||||
'go-mod-cache',
|
||||
goEnv.GOMODCACHE
|
||||
);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('go-os', goEnv.GOOS);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('go-arch', goEnv.GOARCH);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('go-tool-dir', goEnv.GOTOOLDIR);
|
||||
});
|
||||
|
||||
it('falls back to $GOPATH/bin when GOBIN is empty', () => {
|
||||
main.setGoEnvOutputs(goEnv);
|
||||
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(
|
||||
'go-bin-path',
|
||||
'/Users/testuser/go/bin'
|
||||
);
|
||||
});
|
||||
|
||||
it('prefers GOBIN over $GOPATH/bin when it is set', () => {
|
||||
main.setGoEnvOutputs({...goEnv, GOBIN: '/Users/testuser/bin'});
|
||||
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(
|
||||
'go-bin-path',
|
||||
'/Users/testuser/bin'
|
||||
);
|
||||
});
|
||||
|
||||
it('uses the first entry of a multi-entry GOPATH', () => {
|
||||
const GOPATH = ['/Users/testuser/go', '/Users/testuser/other'].join(
|
||||
path.delimiter
|
||||
);
|
||||
|
||||
main.setGoEnvOutputs({...goEnv, GOPATH});
|
||||
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('go-path', GOPATH);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(
|
||||
'go-bin-path',
|
||||
'/Users/testuser/go/bin'
|
||||
);
|
||||
});
|
||||
|
||||
it('leaves variables missing from go env -json empty', () => {
|
||||
main.setGoEnvOutputs({});
|
||||
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('go-path', '');
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('go-bin-path', '');
|
||||
});
|
||||
});
|
||||
|
||||
interface Annotation {
|
||||
file: string;
|
||||
line: number;
|
||||
|
||||
+18
@@ -26,6 +26,24 @@ outputs:
|
||||
description: 'The installed Go version. Useful when given a version range as input.'
|
||||
cache-hit:
|
||||
description: 'A boolean value to indicate if a cache was hit'
|
||||
go-path:
|
||||
description: 'The value of `go env GOPATH`.'
|
||||
go-bin:
|
||||
description: 'The value of `go env GOBIN`.'
|
||||
go-bin-path:
|
||||
description: 'The base Go binary directory: `GOBIN`, otherwise `bin` under the first `GOPATH` entry.'
|
||||
go-root:
|
||||
description: 'The value of `go env GOROOT`.'
|
||||
go-cache:
|
||||
description: 'The value of `go env GOCACHE`, the build cache directory.'
|
||||
go-mod-cache:
|
||||
description: 'The value of `go env GOMODCACHE`, the module cache directory.'
|
||||
go-os:
|
||||
description: 'The value of `go env GOOS`, the target OS in Go notation (linux, darwin, windows).'
|
||||
go-arch:
|
||||
description: 'The value of `go env GOARCH`, the target architecture in Go notation (amd64, arm64).'
|
||||
go-tool-dir:
|
||||
description: 'The value of `go env GOTOOLDIR`, the directory holding the toolchain binaries.'
|
||||
runs:
|
||||
using: 'node24'
|
||||
main: 'dist/setup/index.js'
|
||||
|
||||
Vendored
+9
@@ -95641,6 +95641,15 @@ var State;
|
||||
var Outputs;
|
||||
(function (Outputs) {
|
||||
Outputs["CacheHit"] = "cache-hit";
|
||||
Outputs["GoPath"] = "go-path";
|
||||
Outputs["GoBin"] = "go-bin";
|
||||
Outputs["GoBinPath"] = "go-bin-path";
|
||||
Outputs["GoRoot"] = "go-root";
|
||||
Outputs["GoCache"] = "go-cache";
|
||||
Outputs["GoModCache"] = "go-mod-cache";
|
||||
Outputs["GoOs"] = "go-os";
|
||||
Outputs["GoArch"] = "go-arch";
|
||||
Outputs["GoToolDir"] = "go-tool-dir";
|
||||
})(Outputs || (Outputs = {}));
|
||||
|
||||
;// CONCATENATED MODULE: ./src/package-managers.ts
|
||||
|
||||
Vendored
+53
@@ -100441,6 +100441,15 @@ var State;
|
||||
var Outputs;
|
||||
(function (Outputs) {
|
||||
Outputs["CacheHit"] = "cache-hit";
|
||||
Outputs["GoPath"] = "go-path";
|
||||
Outputs["GoBin"] = "go-bin";
|
||||
Outputs["GoBinPath"] = "go-bin-path";
|
||||
Outputs["GoRoot"] = "go-root";
|
||||
Outputs["GoCache"] = "go-cache";
|
||||
Outputs["GoModCache"] = "go-mod-cache";
|
||||
Outputs["GoOs"] = "go-os";
|
||||
Outputs["GoArch"] = "go-arch";
|
||||
Outputs["GoToolDir"] = "go-tool-dir";
|
||||
})(Outputs || (Outputs = {}));
|
||||
|
||||
;// CONCATENATED MODULE: ./src/package-managers.ts
|
||||
@@ -100567,6 +100576,7 @@ const findDependencyFile = (packageManager) => {
|
||||
|
||||
|
||||
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
//
|
||||
@@ -100616,6 +100626,10 @@ async function run() {
|
||||
core_debug(`add bin ${added}`);
|
||||
const goPath = await which('go');
|
||||
const goVersion = (external_child_process_default().execSync(`${goPath} version`) || '').toString();
|
||||
const goEnvJson = readGoEnv(goPath);
|
||||
if (goEnvJson) {
|
||||
setGoEnvOutputs(goEnvJson);
|
||||
}
|
||||
if (cache && isCacheFeatureAvailable()) {
|
||||
const packageManager = 'default';
|
||||
const cacheDependencyPath = getInput('cache-dependency-path');
|
||||
@@ -100641,6 +100655,45 @@ async function run() {
|
||||
setFailed(error.message);
|
||||
}
|
||||
}
|
||||
function readGoEnv(goPath) {
|
||||
try {
|
||||
const rawGoEnv = external_child_process_default().execFileSync(goPath, ['env', '-json'], {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
});
|
||||
const parsed = JSON.parse(rawGoEnv);
|
||||
if (typeof parsed !== 'object' ||
|
||||
parsed === null ||
|
||||
Array.isArray(parsed)) {
|
||||
throw new Error("'go env -json' did not return a JSON object");
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
catch (error) {
|
||||
core_info(`Unable to read 'go env -json', the Go environment outputs will not be set: ${error.message}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
const goEnvOutputs = [
|
||||
[Outputs.GoPath, 'GOPATH'],
|
||||
[Outputs.GoBin, 'GOBIN'],
|
||||
[Outputs.GoRoot, 'GOROOT'],
|
||||
[Outputs.GoCache, 'GOCACHE'],
|
||||
[Outputs.GoModCache, 'GOMODCACHE'],
|
||||
[Outputs.GoOs, 'GOOS'],
|
||||
[Outputs.GoArch, 'GOARCH'],
|
||||
[Outputs.GoToolDir, 'GOTOOLDIR']
|
||||
];
|
||||
function setGoEnvOutputs(goEnv) {
|
||||
for (const [output, variable] of goEnvOutputs) {
|
||||
setOutput(output, goEnv[variable] ?? '');
|
||||
}
|
||||
setOutput(Outputs.GoBinPath, goEnv['GOBIN'] || goPathBin(goEnv));
|
||||
}
|
||||
function goPathBin(goEnv) {
|
||||
const goPath = (goEnv['GOPATH'] ?? '').split((external_path_default()).delimiter)[0];
|
||||
return goPath ? external_path_default().join(goPath, 'bin') : '';
|
||||
}
|
||||
async function addBinToPath() {
|
||||
let added = false;
|
||||
const g = await which('go');
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
- [Restore-only caches](advanced-usage.md#restore-only-caches)
|
||||
- [Parallel builds](advanced-usage.md#parallel-builds)
|
||||
- [Outputs](advanced-usage.md#outputs)
|
||||
- [go-version](advanced-usage.md#go-version)
|
||||
- [cache-hit](advanced-usage.md#cache-hit)
|
||||
- [Go environment outputs](advanced-usage.md#go-environment-outputs)
|
||||
- [Custom download URL](advanced-usage.md#custom-download-url)
|
||||
- [Using `setup-go` on GHES](advanced-usage.md#using-setup-go-on-ghes)
|
||||
|
||||
@@ -420,6 +423,36 @@ jobs:
|
||||
- run: echo "Was the Go cache restored? ${{ steps.go124.outputs.cache-hit }}" # true if cache-hit occurred
|
||||
```
|
||||
|
||||
### Go environment outputs
|
||||
|
||||
Once Go is on the `PATH`, the action exposes the most commonly needed `go env` variables as outputs, so workflows don't have to query them in duplicated `bash`/`pwsh` steps — one expression works on Linux, macOS, and Windows. The values are a snapshot from setup time: anything a later step changes, such as setting `GOOS`/`GOARCH` to cross-compile, is not reflected.
|
||||
|
||||
| Output | `go env` variable | Notes |
|
||||
| --- | --- | --- |
|
||||
| `go-path` | `GOPATH` | Go workspace root; `bin` and `pkg/mod` live under it |
|
||||
| `go-bin` | `GOBIN` | Go 1.27+ reports an implicit `$GOPATH/bin` default; earlier releases are empty unless `GOBIN` is set |
|
||||
| `go-bin-path` | `GOBIN` or `$GOPATH/bin` | The base Go binary directory, using the first `GOPATH` entry. Cross-compiled binaries go one level deeper, in `$GOPATH/bin/$GOOS_$GOARCH` |
|
||||
| `go-root` | `GOROOT` | Installation directory of the Go toolchain in use |
|
||||
| `go-cache` | `GOCACHE` | Build cache directory |
|
||||
| `go-mod-cache` | `GOMODCACHE` | Module cache directory |
|
||||
| `go-os` | `GOOS` | Go notation (`linux`, `darwin`, `windows`), unlike `runner.os` |
|
||||
| `go-arch` | `GOARCH` | Go notation (`amd64`, `arm64`), unlike `runner.arch` or the `x64` in the action's cache key |
|
||||
| `go-tool-dir` | `GOTOOLDIR` | Holds `compile`, `link`, `vet` and the other toolchain binaries |
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/setup-go@v7
|
||||
id: setup-go
|
||||
with:
|
||||
go-version: '1.25.5'
|
||||
- run: echo "Modules are cached in ${{ steps.setup-go.outputs.go-mod-cache }}"
|
||||
```
|
||||
|
||||
Quote `go-bin-path` in shell commands, since it may contain spaces. Variables without a dedicated output are not exposed; run `go env <NAME>` in a step when you need one.
|
||||
|
||||
> [!NOTE]
|
||||
> These outputs need Go 1.9 or newer (`go env -json`); on older releases the action logs a message, leaves them unset and does not fail. `go-cache` needs Go 1.10 and `go-mod-cache` needs Go 1.15.
|
||||
|
||||
## 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.
|
||||
|
||||
+10
-1
@@ -4,5 +4,14 @@ export enum State {
|
||||
}
|
||||
|
||||
export enum Outputs {
|
||||
CacheHit = 'cache-hit'
|
||||
CacheHit = 'cache-hit',
|
||||
GoPath = 'go-path',
|
||||
GoBin = 'go-bin',
|
||||
GoBinPath = 'go-bin-path',
|
||||
GoRoot = 'go-root',
|
||||
GoCache = 'go-cache',
|
||||
GoModCache = 'go-mod-cache',
|
||||
GoOs = 'go-os',
|
||||
GoArch = 'go-arch',
|
||||
GoToolDir = 'go-tool-dir'
|
||||
}
|
||||
|
||||
+57
@@ -10,6 +10,7 @@ import cp from 'child_process';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import {Architecture} from './types.js';
|
||||
import {Outputs} from './constants.js';
|
||||
|
||||
export async function run() {
|
||||
try {
|
||||
@@ -82,6 +83,11 @@ export async function run() {
|
||||
|
||||
const goPath = await io.which('go');
|
||||
const goVersion = (cp.execSync(`${goPath} version`) || '').toString();
|
||||
const goEnvJson = readGoEnv(goPath);
|
||||
|
||||
if (goEnvJson) {
|
||||
setGoEnvOutputs(goEnvJson);
|
||||
}
|
||||
|
||||
if (cache && isCacheFeatureAvailable()) {
|
||||
const packageManager = 'default';
|
||||
@@ -119,6 +125,57 @@ export async function run() {
|
||||
}
|
||||
}
|
||||
|
||||
export function readGoEnv(goPath: string): Record<string, string> | undefined {
|
||||
try {
|
||||
const rawGoEnv = cp.execFileSync(goPath, ['env', '-json'], {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
});
|
||||
const parsed: unknown = JSON.parse(rawGoEnv);
|
||||
|
||||
if (
|
||||
typeof parsed !== 'object' ||
|
||||
parsed === null ||
|
||||
Array.isArray(parsed)
|
||||
) {
|
||||
throw new Error("'go env -json' did not return a JSON object");
|
||||
}
|
||||
|
||||
return parsed as Record<string, string>;
|
||||
} catch (error) {
|
||||
core.info(
|
||||
`Unable to read 'go env -json', the Go environment outputs will not be set: ${
|
||||
(error as Error).message
|
||||
}`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const goEnvOutputs: ReadonlyArray<[Outputs, string]> = [
|
||||
[Outputs.GoPath, 'GOPATH'],
|
||||
[Outputs.GoBin, 'GOBIN'],
|
||||
[Outputs.GoRoot, 'GOROOT'],
|
||||
[Outputs.GoCache, 'GOCACHE'],
|
||||
[Outputs.GoModCache, 'GOMODCACHE'],
|
||||
[Outputs.GoOs, 'GOOS'],
|
||||
[Outputs.GoArch, 'GOARCH'],
|
||||
[Outputs.GoToolDir, 'GOTOOLDIR']
|
||||
];
|
||||
|
||||
export function setGoEnvOutputs(goEnv: Record<string, string>): void {
|
||||
for (const [output, variable] of goEnvOutputs) {
|
||||
core.setOutput(output, goEnv[variable] ?? '');
|
||||
}
|
||||
|
||||
core.setOutput(Outputs.GoBinPath, goEnv['GOBIN'] || goPathBin(goEnv));
|
||||
}
|
||||
|
||||
function goPathBin(goEnv: Record<string, string>): string {
|
||||
const goPath = (goEnv['GOPATH'] ?? '').split(path.delimiter)[0];
|
||||
return goPath ? path.join(goPath, 'bin') : '';
|
||||
}
|
||||
|
||||
export async function addBinToPath(): Promise<boolean> {
|
||||
let added = false;
|
||||
const g = await io.which('go');
|
||||
|
||||
Reference in New Issue
Block a user