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: 'go build ...' builds all packages in my homedir when using modules #31063

Closed
integrii opened this issue Mar 26, 2019 · 6 comments
Closed
Labels
FrozenDueToAge modules WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided.

Comments

@integrii
Copy link

What version of Go are you using (go version)?

go version go1.12.1 darwin/amd64

Does this issue reproduce with the latest release?

What operating system and processor architecture are you using (go env)?

go env Output
                                                                                                                                                                                                                                                        master  12:33:11P 
GOARCH="amd64"
GOBIN="/Users/egreer200/go/bin"
GOCACHE="/Users/egreer200/Library/Caches/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/egreer200/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/Cellar/go/1.12.1/libexec"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.12.1/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/egreer200/git/kuberhealthy/pkg/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/7h/qd9xrbq10lb03g4lkqcl_cdr0000gp/T/go-build916270019=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

I went into a directory where I wanted a module to live, which contained several levels of packages below it. I then attempted to run go build ... to build all packages recursively.

What did you expect to see?

The used packages in all recursive package dependencies added to the module.

What did you see instead?

I saw that go build ... started downloading literally every package dependency it could find on my entire laptop, in any place in the home directory structure.

This caused me to mistakenly open issue #31059

Running go build ./... properly builds recursively and adds the dependencies expected. This used to work without the leading ./ without modules. I think we should maintain that behaviour instead of attempting to compile the user's entire laptop.

@integrii integrii changed the title cmd/go: go build ... builds all packages in my homedir when using modules cmd/go: 'go build ...' builds all packages in my homedir when using modules Mar 26, 2019
@jayconrod
Copy link
Contributor

In Go 1.12.1, any go build command outside of a module can create a go.mod file if it can be reasonably sure of the module path and the repository root. This was been changed at master: there's now a command-line-arguments module with no requirements that just includes explicitly named source files. But in 1.12.1, running go build ... would be equivalent to running go mod init in the repository root directory and then go build ....

... is an extremely broad pattern. It's actually broader than all. It means all packages in the current module and all packages in other modules whose existence is even slightly hinted at by require statements in go.mod files or imports in packages in any of those modules, even if those packages are not transitively imported by your module.

So this should fetch and build a lot of things.

That said, it shouldn't be scanning your entire laptop. Are you seeing this command build packages outside your repo that aren't published elsewhere? If so, can you narrow this down to a smaller test case we can look at?

@jayconrod jayconrod added WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. modules labels Mar 27, 2019
@integrii
Copy link
Author

Thanks for clarifying the behavior of go build ... in the new modules system.

I admit that I didn't actually verify that it was the "entire laptop". I just know that it was hundreds of packages. Let me try to explain what I did a little clearer and you can tell me if this is intended behavior or not.

I was in a file structure like this:

~/git/kuberhealthy/pkg/

~/git/kuberhealthy/pkg/
├── checks
│   ├── componentStatus
│   │   └── componentStatus.go
│   ├── daemonSet
│   │   ├── daemonSet.go
│   │   ├── daemonSet_test.go
│   │   └── util.go
│   ├── podRestarts
│   │   ├── podRestarts.go
│   │   └── podRestarts_test.go
│   └── podStatus
│       └── podStatus.go
├── go.mod
├── go.sum
├── health
│   ├── checkDetails.go
│   └── state.go
├── khstatecrd
│   ├── api.go
│   ├── functions.go
│   ├── functions_test.go
│   ├── register.go
│   ├── statecrd.go
│   └── statecrdlist.go
├── kubeClient
│   └── client.go
├── masterCalculation
│   ├── masterCalculation.go
│   └── masterCalculation_test.go
└── metrics
    ├── exporter.go
    └── exporter_test.go

From the ~/git/kuberhealthy/pkg directory at the top of this tree, which has a go.mod file in it, I ran go build .... This caused a very large number of packages to be found as dependencies.

I then ran go mod tidy and saw that the vast majority of items from the go.mod file were removed.

I then, bewildered, tried running go build ./... and noticed that no extra dependencies were re-created and all packages were built as expected. The go mod file stayed small.

Is this expected behavior of go build ... when a module file exists? If it is, this is vastly different than the regular go build ... functionality from pre-modules versions. I am still not entirely sure why so many extra dependencies were added. I had presumed it spidered my entire home directory, but I do not know for a fact that it did.

Thanks for your help!

@jayconrod
Copy link
Contributor

What you're describing sounds like intended behavior, though I admit it's a little surprising at first.

  • go build ... will match every package in your module and every transitively required module. For any imported packages that aren't provided by that set of modules, the Go command will fetch new modules, add new requirements, apply version constraints. The pattern ... will match all packages in the expanded set of modules, and the process will repeat until all packages are found.
  • The new modules will be added to your go.mod. If newer versions of your existing requirements were needed, those will be upgraded, too. This ensures that if you run the same command again, you'll get the same versions of everything. Deterministic builds is a primary goal of modules.
  • go mod tidy determines the set of modules needed for the packages in your module and their transitive imports, including test imports. It deletes everything that's not needed.
  • go build ./... will build a subset of the things go mod tidy considers. This is almost certainly what you want.

In general, it is expected that go build may build things outside the current set of required modules. I use that frequently for testing and tools: I'll set up a temporary transient module with a specific set of requirements and then build or test something with those constraints.

In GOPATH mode go build ... will build everything in GOROOT and GOPATH, regardless of the current directory. So depending on what you have checked out in each case, that can actually be much broader.

@integrii
Copy link
Author

integrii commented Mar 28, 2019

Thanks for writing all that up. Very helpful! Is this behavior detailed in such a way somewhere in official help?

To parrot this back to you:

  • Using a normal go build will climb up the directory tree until it finds a go.mod file, which it will use to learn it's module name before fetching all non-test dependencies (along with their dependencies) and placing them in the applicable go.mod file, but only for the directory you are currently in.
  • Using go build ./... Same thing, but it will recurse down from your current directory to find all packages in the file tree below here you are. Test dependencies are still not included.
  • Using go build ... Does the same thing as go build ./..., but also includes test dependencies.
  • Using go mod tidy will go up the file system tree until it finds a module, then go down to all directories below there and compile a list of dependencies. All dependencies in the go.mod file not found are purged.

Questions:

  • The major difference between go get ... and go get ./... is that tests are included when go get ... is run. Correct?
  • How would one properly run a go mod tidy that takes tests into account like go get ... does?
  • Are we sure this go get ... functionality would not be better suited as a simple flag? This would allow us to maintain a similar functionality to old GOPATH behavior when running go build ... in your project. (Previously, running go build ... would recurse down from your working directory and build all packages found.)

Thanks again for your help.

@jayconrod
Copy link
Contributor

Using a normal go build will climb up the directory tree until it finds a go.mod file, which it will use to learn it's module name before fetching all non-test dependencies (along with their dependencies) and placing them in the applicable go.mod file, but only for the directory you are currently in.

Right. go build is equivalent to go build ., so that will include the package in the current directory and the transitive imports. You can run go list -deps with the same positional arguments to find out what go build will do.

Using go build ./... Same thing, but it will recurse down from your current directory to find all packages in the file tree below here you are. Test dependencies are still not included.

Right.

Using go build ... Does the same thing as go build ./..., but also includes test dependencies.

Not quite. What you're describing is the special all pattern. The pattern ./... matches packages that start with ./. The pattern ... matches all packages and their dependencies in all required modules, plus all packages and their dependencies in newly discovered modules. It doesn't look at test dependencies, but given the breadth, they'll likely be included anyway.

Using go mod tidy will go up the file system tree until it finds a module, then go down to all directories below there and compile a list of dependencies. All dependencies in the go.mod file not found are purged.

Right. go mod tidy will consider the packages listed by go list -test -deps ./... (or something very close to that) when run at the module root directory. It will ensure modules providing those packages are in go.mod, and it will remove everything else.

The major difference between go get ... and go get ./... is that tests are included when go get ... is run. Correct?

There's a bigger difference (covered above).

How would one properly run a go mod tidy that takes tests into account like go get ... does?

No extra work needed. go mod tidy will pick up test dependencies.

Are we sure this go get ... functionality would not be better suited as a simple flag? This would allow us to maintain a similar functionality to old GOPATH behavior when running go build ... in your project. (Previously, running go build ... would recurse down from your working directory and build all packages found.)

In GOPATH mode, go build ... would build everything in GOPATH and GOROOT. It was always very broad scope. It's probably very rarely what people want, but it's consistent with other ... behavior. go build ... in module mode has slightly different semantics, but it's analogous.

Is this behavior detailed in such a way somewhere in official help?

go help is a concise reference for specifics, go help packages in particular here. The same text is at https://golang.org/cmd/go/. The Modules wiki is probably the best reference. There's a series of blog posts being written, too. The first one is Using Go Modules.

I'll close this issue because it seems like everything is working as intended, but feel free to reach out for more info on the golang-nuts mailing list or in the #modules channel on golang Slack.

@integrii
Copy link
Author

integrii commented Apr 4, 2019

Thanks @jayconrod - you have been extremely helpful!

@golang golang locked and limited conversation to collaborators Apr 3, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge modules WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided.
Projects
None yet
Development

No branches or pull requests

3 participants