fix: parse go-version-file by content

The `parseGoVersionFile` function used to only
check for exact filenames like "go.{mod,work}".
When `go-version-file` pointed to something like a
tools modfile ("go.tool.mod"), it would just dump
the whole file contents back, which then got
mistaken for a version spec and broke the
manifest/dist resolution.

Now it looks at the file content to spot Go module
or workspace files and pulls out the `toolchain`
or `go` directives. Left ".tool-versions" handling
alone.

Fixes #746

Signed-off-by: Dwi Siswanto <git@dw1.io>
This commit is contained in:
Dwi Siswanto 2026-04-10 15:03:39 +07:00
parent 4a3601121d
commit 493769a1ce
No known key found for this signature in database
GPG key ID: 3BB198907EF44CED
4 changed files with 61 additions and 21 deletions

View file

@ -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...');
});
it('reads version from .tool-versions', async () => {
inputs['go-version-file'] = '.tool-versions';
existsSpy.mockImplementation(() => true);
@ -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)),

23
dist/setup/index.js vendored
View file

@ -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) {

View file

@ -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)).

View file

@ -651,30 +651,38 @@ export function makeSemver(version: string): string {
}
export function parseGoVersionFile(versionFilePath: string): string {
const contents = fs.readFileSync(versionFilePath).toString();
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;
if (
path.basename(versionFilePath) === 'go.mod' ||
path.basename(versionFilePath) === 'go.work'
) {
const contents = fs.readFileSync(versionFilePath).toString();
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[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();