Skip to content

cmd/go/tool: tool directive with local relative path replace #75497

@dnaeon

Description

@dnaeon

Go version

go version go1.25.1 darwin/arm64

Output of go env in your module/workspace:

AR='ar'
CC='cc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='c++'
GCCGO='gccgo'
GO111MODULE='on'
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN='/Users/abc/workspace/Projects/golang/bin'
GOCACHE='/Users/abc/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/abc/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/Users/abc/workspace/Projects/golang/cache'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/abc/workspace/Projects/golang'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/homebrew/Cellar/go/1.25.1/libexec'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/abc/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.25.1/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.25.1'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

There appears to be an issue with how go tool interprets relative replace statements when used in combination with -modfile flag.

Here's an example, which can be used to reproduce the issue.

Create a new Go module.

  mkdir go-tool-modfile-replace
  cd go-tool-modfile-replace
  go mod init go-tool-modfile-replace

Create a dummy tool, which we will use.

  mkdir -p cmd/dummy
  cd cmd/dummy
  go mod init my/dummy/cmd

This is how cmd/dummy/main.go looks like.

package main

import (
	"fmt"
	"os"
)

func main() {
	pwd, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	fmt.Printf("dummy was called. cwd is %s\n", pwd)
}

Next, create a dedicated tools module, which will be used for tracking various tool dependencies. These tools may be related to anything the project uses as part of the lifecycle, e.g. linting, formatting, etc. It will also include our simple dummy tool.

  repo-root $ mkdir -p tools
  tools $ cd tools
  tools $ go mod init my/tools/mod

We can now add any tools we need using go get -tool, and dependencies will be tracked correctly. However, we want to include our dummy tool as well, so that we can call each utility tool via go tool <name>.

This is what tools/go.mod looks like, when it contains only our dummy tool.

module my/tools/mod

go 1.25.1

tool my/dummy/cmd

replace my/dummy/cmd => ../cmd/dummy

require my/dummy/cmd v0.0.0-00010101000000-000000000000 // indirect

Now we can run our dummy tool when we are within the tools/ directory (module).

tools $ go tool my/dummy/cmd
dummy was called. cwd is /Users/abc/go-tool-modfile-replace/tools

However, if we go back to the repo root and try to call the tool it would fail.

repo-root $ go tool -modfile tools/go.mod my/dummy/cmd
my/dummy/cmd: my/dummy/cmd@v0.0.0-00010101000000-000000000000: replacement directory ../cmd/dummy does not exist

The reason it fails here is (probably) because the relative path replace in tools/go.mod is relative to the current directory of the calling process, instead of being relative to the path where tools/go.mod resides.

If we use go tool -C tools my/dummy/cmd the command would work fine, but only because the current directory will already be set (via the -C flag), and the replace statement would be valid, because our current workdir is the same as the location for go.mod as well.

go tool -C tools my/dummy/cmd
dummy was called. cwd is /Users/abc/go-tool-modfile-replace/tools

What did you see happen?

Please see details above.

What did you expect to see?

Calling go tool -modfile path/to/go.mod some-tool should work fine when the path/to/go.mod contains local relative replace entries.

When using relative replace entries with relative local paths, perhaps the paths should be relative to the directory of the go.mod file itself.

For example when calling go tool -modfile path/to/go.mod some-tool any local relative replace entries in path/to/go.mod should be considered relative in regards to the path/to directory, which contains the go.mod file.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions