Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/go: confusing situation with 'go run' and '//go:build ignore' #73152

Open
josharian opened this issue Apr 3, 2025 · 20 comments
Labels
BugReport Issues describing a possible bug in the Go implementation. DevExp anything around developer experience GoCommand cmd/go NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.

Comments

@josharian
Copy link
Contributor

Go version

go version go1.24.2 darwin/arm64

Output of go env in your module/workspace:

AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN='/Users/josh/bin'
GOCACHE='/Users/josh/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/josh/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/vm/htvrhp4177v2dfhdjlvl0pqh0000gn/T/go-build275806226=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD=[redacted]
GOMODCACHE='/Users/josh/pkg/mod'
GONOPROXY=[redacted]
GONOSUMDB=[redacted]
GOOS='darwin'
GOPATH='/Users/josh'
GOPRIVATE=[redacted]
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/josh/pkg/mod/golang.org/[email protected]'
GOSUMDB='sum.golang.org'
GOTELEMETRY='on'
GOTELEMETRYDIR='/Users/josh/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/Users/josh/pkg/mod/golang.org/[email protected]/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.24.2'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

Here's my terminal transcript:

$ go run x.go
go: updates to go.mod needed; to update it:
	go mod tidy
$ go mod tidy
$ go run x.go
go: updates to go.mod needed; to update it:
	go mod tidy
$ go mod tidy
$ go run x.go
go: updates to go.mod needed; to update it:
	go mod tidy

After quite a while spend debugging, it turns out the problem is that x.go had //go:build ignore, and so go mod tidy ignored it...leaving it in a broken state when actually running it.

Even once I had a diagnosis, it was non-trivial to work around, because go mod tidy has no place to specify build tags. I had to move the file to a new location, remove the build tag, run go mod tidy, and then put it all back together again.

What did you see happen?

covered above.

What did you expect to see?

again, covered above.

@seankhliao
Copy link
Member

Related #54993

@seankhliao
Copy link
Member

you could have run go run -mod=mod x.go

@josharian
Copy link
Contributor Author

you could have run go run -mod=mod x.go

certainly having that in the error message would have helped

@seankhliao
Copy link
Member

taking a step back though, would it have been better for your script to be tracked as a tool package (and have its dependencies tracked)?
perhaps we should more strongly discourage the use of go run file.go.

@seankhliao seankhliao added NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. GoCommand cmd/go labels Apr 3, 2025
@gabyhelp gabyhelp added the BugReport Issues describing a possible bug in the Go implementation. label Apr 3, 2025
@josharian
Copy link
Contributor Author

taking a step back though, would it have been better for your script to be tracked as a tool package (and have its dependencies tracked)?

i don't think so, no.

perhaps we should more strongly discourage the use of go run file.go.

it's really, really useful to be able to have little scripts.

@thepudds
Copy link
Contributor

thepudds commented Apr 3, 2025

Hi @josharian, improving the error here would likely be helpful.

One question though regarding your use of //go:build ignore in the x.go file. Do you:

  1. want the imports in x.go to not "pollute" the results of a normal go mod tidy (and maybe that's why you are using //go:build ignore), or
  2. want the imports in x.go in general to influence what's in your go.mod, but you just don't want something like go build to build x.go, or
  3. maybe want something else?

For your particular case, would it have helped if x.go instead had //go:build run (or some other build tag instead of //go:build ignore)?

It sounds like you are well aware of the underlying mechanisms, but at least for my reference, from the go mod tidy doc:

go mod tidy acts as if all build tags are enabled, so it will consider platform-specific source files and files that require custom build tags, even if those source files wouldn’t normally be built. There is one exception: the ignore build tag is not enabled, so a file with the build constraint // +build ignore will not be considered.

and from the go run doc:

Typically the package is specified as a list of .go source files from a single directory, but it may also be an import path, file system path, or pattern matching a single known package [...]

If the package argument doesn't have a version suffix, "go run" may run in module-aware mode or GOPATH mode, depending on the GO111MODULE environment variable and the presence of a go.mod file. [...] If module-aware mode is enabled, "go run" runs in the context of the main module.

@josharian
Copy link
Contributor Author

Thanks.

The use of //go:build ignore is because (a) the "natural" place for this script is amongst the other files in that directory and (b) using //go:build ignore makes gopls usable with the script. IIRC, but maybe I'm wrong, using something other than ignore as the build tag doesn't help with gopls.

I'm genuinely unsure about what the right fix is here.

@thepudds
Copy link
Contributor

thepudds commented Apr 3, 2025

For gopls, it looks like you could add a run build tag (or whatever) to the standaloneTags setting, which otherwise defaults to ignore:

https://github.com/golang/tools/blob/master/gopls/doc/settings.md#standalonetags-string

standaloneTags specifies a set of build constraints that identify individual Go source files that make up the entire main package of an executable.

A common example of standalone main files is the convention of using the directive //go:build ignore to denote files that are not intended to be included in any package, for example because they are invoked directly by the developer using go run.

Gopls considers a file to be a standalone main file if and only if it has package name "main" and has a build directive of the exact form "//go:build tag" or "// +build tag", where tag is among the list of tags configured by this setting. Notably, if the build constraint is more complicated than a simple tag (such as the composite constraint //go:build tag && go1.18), the file is not considered to be a standalone main file.

Default: ["ignore"].

@josharian
Copy link
Contributor Author

Good to know, thanks!

Definitely feels a bit like the defaults are fighting with each other here. :P

@thepudds
Copy link
Contributor

thepudds commented Apr 3, 2025

Perhaps some new build tag convention could be established for standalone main files, and then the default gopls setting could use that convention in addition to ignore, though maybe that is not desirable (and is not an immediate fix, and would take some discussion, etc.).

That said, I guess I'm curious what was out of sync in your particular example.

In at least some common cases, it does seem to give a better error. For example, if I have a package main file with a //go:build ignore and it has an import that is not covered by the require directives in the current module's go.mod, and I try to run that file, I get an error like:

$ go run t.go
t.go:8:2: no required module provides package rsc.io/quote; to add it:
        go get rsc.io/quote

From a quick look at the cmd/go code, the error you saw can happen when it thinks the go.mod is dirty in another way. For example, if I edit a version in my go.mod to no longer be in canonical form (e.g., require golang.org/x/text 14c0d48ead0c), I can get:

$ go run t.go
go: updates to go.mod needed; to update it:
        go mod tidy

That's probably not exactly what you hit, though, and while I could probably trigger that specific error in other ways, I'm curious what was out of sync for you in particular (if that was apparent based on your debugging).

@josharian
Copy link
Contributor Author

It was missing some imports. Not sure why it differed from your experiments.

@jakebailey
Copy link
Contributor

I've had this same problem previously, and my solution then was to cheat and stick the import in question into my tools.go file (pre go get -tool) to ensure it was referenced.

A dedicated way to say "this is a standalone main file but please include its deps" would be great.

(I don't particularly care about mixing deps; that's not unreasonably handled with -mod= or -modfile already.)

@seankhliao
Copy link
Member

Any other build tag than ignore works, ignore really means ignore...

@josharian
Copy link
Contributor Author

@seankhliao I keep getting the sense from you that you feel that this is user error. I respectfully disagree.

It is true that I didn't know some of the mechanics that @thepudds pointed out, but I very much doubt many other folks do either; I am probably as least as informed about the workings of cmd/go as the median gopher.

After the conversation above, it seems to me that there are at least two real issues here:

  • The output is genuinely confusing. I did exactly what the tool told me to do and it did not work. There were no hints or indications about how to debug. In the end, my third visit to an LLM found a path to getting me unstuck, so it's not even obvious to the internet-in-a-box.

  • gopls's default behavior ("use the ignore tag for standalone main") plays badly with the go tool's behavior ("use the ignore tag to fully ignore things"). This is straightforwardly causally responsible for the setup that generated the confusing situation.

@seankhliao
Copy link
Member

I agree it's a poor interplay of defaults, but my stance would be more that:

  • modules ignoring ignore is a misfeature
  • go run <list of files> in any context outside of the most trivial of code is a bad pattern that should be avoided.

@thepudds
Copy link
Contributor

thepudds commented Apr 4, 2025

It looks like support for gopls understanding standalone main files was added to gopls via #49657 (where the opening comment there calls them "scripts"). It seems there was a fair amount of enthusiasm expressed there. I don't see any explicit discussion there of the interaction between ignore build tags and go mod tidy, though maybe I missed it.

Using ignore as a convention for a build tag I think stretches back to Go 1.0 or earlier. For example, here is a description from ~14 years ago:

// To keep a file from being considered for the build:
//
// // +build ignore
//
// (any other unsatisfied word will work as well, but ``ignore'' is conventional.)

But today, ignore might be the wrong convention for someone who wants to have a standalone main file that lives inside a module with other go files, especially if the standalone main file has dependencies outside of the stdlib. Currently, the convention of using ignore in this situation exists both in practice (many gophers do it today) and is at least hinted at in semi-official Go documentation (e.g., the gopls doc I quoted in #73152 (comment)).

It is probably too disruptive to change go mod tidy behavior here (or at least, too disruptive to change its default behavior; maybe some new flag or similar could be added, though that does not seem very appealing and has a reasonably high bar).

One approach could be to document a new convention for what build tag to use in this scenario, with some name selected that is not ignore. (For example, I had thrown out run in #73152 (comment) above, though not suggesting that should be it). If there is a new convention, gopls could respect it by default, similar to what it does with the standaloneTags gopls setting today, and it would work I think with go mod tidy without needing to change cmd/go. New conventions take some time to spread, but they usually do spread if they make sense.

A different approach might be to do something like add a ~sentence in the cmd/go or modules documentation somewhere that effectively suggests that any build tag other than ignore is a better choice for this situation, but without picking some new build tag name. That would play better with go mod tidy in this scenario, but not with default gopls behavior, which currently has ~3-4 requirements for recognizing a standalone main file, including defaulting to requiring ignore. If this is the path, perhaps the standaloneTags gopls setting could instead default to empty, and if empty, perhaps gopls could recognize any build tag used in the appropriate manner with a package main file that would otherwise be excluded, though maybe that is a performance concern or maybe there is some other reason why reading a value from the standaloneTags setting needs to be relied upon today. (TBH, I don't know the exact approach gopls takes in general for evolving its default settings, but I would guess there is some evolution that can happen if worthwhile).

Or if all that is flawed, there probably is some other way to establish a better best practice here.

@josharian
Copy link
Contributor Author

If there is a new convention, gopls could respect it by default

This seems like a good idea, and adding another default tag is cheap, at least code-wise. run seems fine to me. (Other colors include main, script, standalone, go-run.)

Would that need to go through a proposal process, or can someone affiliated with gopls just decide? cc @alandonovan

@adonovan
Copy link
Member

adonovan commented Apr 4, 2025

Would that need to go through a proposal process, or can someone affiliated with gopls just decide? cc @alandonovan

I'm not opposed to the idea, but I do think it warrants a proposal, as it is unquestionably a change to an existing interface, and to existing habits.

@dmitshur
Copy link
Contributor

dmitshur commented Apr 7, 2025

CC @matloob, @samthanawalla (via owners).

@dmitshur dmitshur added the DevExp anything around developer experience label Apr 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BugReport Issues describing a possible bug in the Go implementation. DevExp anything around developer experience GoCommand cmd/go NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

7 participants