Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions __tests__/setup-go.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,18 @@ use .
expect(logSpy).toHaveBeenCalledWith('matching 1.19...');
});

it('reads version from go.mod-like file name', async () => {
inputs['go-version-file'] = 'go.tool.mod';
existsSpy.mockImplementation(() => true);
readFileSpy.mockImplementation(() => Buffer.from(goModContents));

await main.run();

expect(logSpy).toHaveBeenCalledWith('Setup go version spec 1.14');
expect(logSpy).toHaveBeenCalledWith('Attempting to download 1.14...');
expect(logSpy).toHaveBeenCalledWith('matching 1.14...');
});
Comment on lines +904 to +914

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests cover a custom go.mod-like filename (go.tool.mod), but the new content-based detection also supports go.work-style files with custom names (via use directive). Consider adding a test that sets go-version-file to a non-go.work filename containing go.work-style contents to ensure workspace detection works as intended.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed IMO.


it('reads version from .tool-versions', async () => {
inputs['go-version-file'] = '.tool-versions';
existsSpy.mockImplementation(() => true);
Expand Down Expand Up @@ -1066,6 +1078,19 @@ use .
expected_version: placeholderVersion,
desc: 'from go directive when GOTOOLCHAIN is local'
},
{
goVersionfile: 'go.tool.mod',
fileContents: Buffer.from(buildGoMod(placeholderVersion, version)),
expected_version: version,
desc: 'from toolchain directive'
},
{
goVersionfile: 'go.tool.mod',
fileContents: Buffer.from(buildGoMod(placeholderVersion, version)),
gotoolchain_env: 'local',
expected_version: placeholderVersion,
desc: 'from go directive when GOTOOLCHAIN is local'
},
{
goVersionfile: 'go.work',
fileContents: Buffer.from(buildGoMod(placeholderVersion, version)),
Expand Down
23 changes: 15 additions & 8 deletions dist/setup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77493,26 +77493,33 @@ function makeSemver(version) {
return fullVersion;
}
function parseGoVersionFile(versionFilePath) {
const moduleOrWorkspaceDirective = /^\s*go\s+(\d+(?:\.\d+)*)/m;
const toolchainDirective = /^\s*toolchain\s+go(1\.\d+(?:\.\d+|rc\d+)?)/m;
const moduleDeclaration = /^\s*module\s+\S+/m;
const workspaceUseDirective = /^\s*use(?:\s+\S+|\s*\()/m;
const contents = fs_1.default.readFileSync(versionFilePath).toString();
if (path.basename(versionFilePath) === 'go.mod' ||
path.basename(versionFilePath) === 'go.work') {
const fileName = path.basename(versionFilePath);
const isGoModOrWorkFileName = fileName === 'go.mod' || fileName === 'go.work';
const isGoModuleOrWorkspaceLike = moduleOrWorkspaceDirective.test(contents) &&
(moduleDeclaration.test(contents) || workspaceUseDirective.test(contents));
if (fileName === '.tool-versions') {
const match = contents.match(/^golang\s+([^\n#]+)/m);
return match ? match[1].trim() : '';
}
if (isGoModOrWorkFileName || isGoModuleOrWorkspaceLike) {
// for backwards compatibility: use version from go directive if
// 'GOTOOLCHAIN' has been explicitly set
if (process.env[exports.GOTOOLCHAIN_ENV_VAR] !== exports.GOTOOLCHAIN_LOCAL_VAL) {
// toolchain directive: https://go.dev/ref/mod#go-mod-file-toolchain
const matchToolchain = contents.match(/^toolchain go(1\.\d+(?:\.\d+|rc\d+)?)/m);
const matchToolchain = contents.match(toolchainDirective);
if (matchToolchain) {
return matchToolchain[1];
}
}
// go directive: https://go.dev/ref/mod#go-mod-file-go
const matchGo = contents.match(/^go (\d+(\.\d+)*)/m);
const matchGo = contents.match(moduleOrWorkspaceDirective);
return matchGo ? matchGo[1] : '';
}
else if (path.basename(versionFilePath) === '.tool-versions') {
const match = contents.match(/^golang\s+([^\n#]+)/m);
return match ? match[1].trim() : '';
}
return contents.trim();
}
function resolveStableVersionDist(versionSpec, arch) {
Expand Down
2 changes: 1 addition & 1 deletion docs/advanced-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ jobs:

## Using the `go-version-file` input

`setup-go` action can read the Go version from a version file. `go-version-file` input is used for specifying the path to the version file. If the file supplied to the `go-version-file` input doesn't exist, the action will fail with an error. This input supports go.mod, go.work, .go-version, and .tool-versions files.
`setup-go` action can read the Go version from a version file. `go-version-file` input is used for specifying the path to the version file. If the file supplied to the `go-version-file` input doesn't exist, the action will fail with an error. This input supports standard `go.mod` and `go.work` files, custom files that use the same `go.mod`/`go.work` format (for example, `go.tool.mod`), and `.go-version` and `.tool-versions` files.

If both the `go-version` and the `go-version-file` inputs are provided then the `go-version` input is used. The `.tool-versions` file supports version specifications in accordance with asdf standards, adhering to Semantic Versioning ([semver](https://semver.org)).

Expand Down
30 changes: 19 additions & 11 deletions src/installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -651,30 +651,38 @@ export function makeSemver(version: string): string {
}

export function parseGoVersionFile(versionFilePath: string): string {
const moduleOrWorkspaceDirective = /^\s*go\s+(\d+(?:\.\d+)*)/m;
const toolchainDirective = /^\s*toolchain\s+go(1\.\d+(?:\.\d+|rc\d+)?)/m;
const moduleDeclaration = /^\s*module\s+\S+/m;
const workspaceUseDirective = /^\s*use(?:\s+\S+|\s*\()/m;

const contents = fs.readFileSync(versionFilePath).toString();
const fileName = path.basename(versionFilePath);
Comment thread
dwisiswant0 marked this conversation as resolved.

if (
path.basename(versionFilePath) === 'go.mod' ||
path.basename(versionFilePath) === 'go.work'
) {
const isGoModOrWorkFileName = fileName === 'go.mod' || fileName === 'go.work';
const isGoModuleOrWorkspaceLike =
moduleOrWorkspaceDirective.test(contents) &&
(moduleDeclaration.test(contents) || workspaceUseDirective.test(contents));

if (fileName === '.tool-versions') {
const match = contents.match(/^golang\s+([^\n#]+)/m);
return match ? match[1].trim() : '';
}

if (isGoModOrWorkFileName || isGoModuleOrWorkspaceLike) {
// for backwards compatibility: use version from go directive if
// 'GOTOOLCHAIN' has been explicitly set
if (process.env[GOTOOLCHAIN_ENV_VAR] !== GOTOOLCHAIN_LOCAL_VAL) {
// toolchain directive: https://go.dev/ref/mod#go-mod-file-toolchain
const matchToolchain = contents.match(
/^toolchain go(1\.\d+(?:\.\d+|rc\d+)?)/m
);
const matchToolchain = contents.match(toolchainDirective);
if (matchToolchain) {
return matchToolchain[1];
}
}

// go directive: https://go.dev/ref/mod#go-mod-file-go
const matchGo = contents.match(/^go (\d+(\.\d+)*)/m);
const matchGo = contents.match(moduleOrWorkspaceDirective);
return matchGo ? matchGo[1] : '';
} else if (path.basename(versionFilePath) === '.tool-versions') {
const match = contents.match(/^golang\s+([^\n#]+)/m);
return match ? match[1].trim() : '';
}

return contents.trim();
Expand Down