tl;dr: 1.6 brings support for /vendor/
to most tools (like the oracle) out of the box; use the Beta to rebuild them.
GO15VENDOREXPERIMENT is the native vendoring support added in Go 1.5. In short it allows you to put a package at a/vendor/x
and import it as x
from a
. You enable it by setting the env var GO15VENDOREXPERIMENT=1
in 1.5 and it's on by default in 1.6.
The most annoying thing when using this scheme is that many analysis and refactoring tools, like the oracle, varcheck, gorename and goimports stop working, because the type checker don't support it:
$ mkdir -p $GOPATH/src/filosottile.info/tmp/go16vendor
$ cd !$
$ mkdir -p vendor/example.com/hello
$ cat > vendor/example.com/hello/hello.go
package hello
var Hello = "Hello, World!"
$ cat > main.go
package main
import "example.com/hello"
func main() {
println(hello.Hello)
}
$ go run main.go
main.go:3:8: cannot find package "example.com/hello" in any of:
$GOROOT/src/example.com/hello (from $GOROOT)
$GOPATH/src/example.com/hello (from $GOPATH)
$ GO15VENDOREXPERIMENT=1 go run main.go
Hello, World!
$ oracle callstack main.go
main.go:3:8: could not import example.com/hello (cannot find package "example.com/hello" in any of:
$GOROOT/src/example.com/hello (from $GOROOT)
$GOPATH/src/example.com/hello (from $GOPATH))
main.go:6:10: undeclared name: hello
oracle: couldn't load packages due to errors: main
$ oracle -pos=main.go:#75 definition main.go
oracle: no object for identifier
$ varcheck
/Users/filippo/go/src/filosottile.info/tmp/go16vendor/main.go:3:8: could not import example.com/hello (cannot find package "example.com/hello" in any of:
$GOROOT/src/example.com/hello (from $GOROOT)
$GOPATH/src/example.com/hello (from $GOPATH))
$GOPATH/src/filosottile.info/tmp/go16vendor/main.go:6:10: undeclared name: hello
2015/12/18 14:33:52 could not type check: couldn't load packages due to errors: filosottile.info/tmp/go16vendor
Well, good news! As you can see at the bottom of #12278, support was added to go/build
and go/loader
, which will make 90% of the tools just work out of the box!
Backporting this is a bit tricky, because go/loader
is not part of the main tree, so it checks if Go is at least 1.6 to be sure that go/build
support is there. We could still do it, but it's much simpler to just build the tools with the 1.6 Beta.
You probably don't want to switch to 1.6 for compilation yet, but you can use it to build just the tools. This way they'll get vendor folder support, and they will work seamlessly thanks to the Go 1 Compatibility Guarantee. (Unless you step into the stdlib, in which case the tools will probably go to the 1.6 tree. It shouldn't be an issue, but if it bugs you, you might be able to override it by setting GOROOT.)
Here's how!
First, install the 1.6 beta somewhere. I build it from source so that I don't have to set GOROOT when using it (the compile path gets baked in) and I can just symlink the binary.
$ download https://storage.googleapis.com/golang/go1.6beta1.src.tar.gz
$ tar xvf go1.6beta1.src.tar.gz
$ mv go /usr/local/go16
$ cd /usr/local/go16/src
$ ./all.bash
$ ln -s /usr/local/go16/bin/go /usr/local/bin/go16
$ cd $GOPATH/src/filosottile.info/tmp/go16vendor
$ go16 version
go version go1.6beta1 darwin/amd64
$ go16 run main.go
Hello, World!
Then just reinstall the tools you want to use with go16 get
.
$ go16 get -u golang.org/x/tools/cmd/oracle
$ oracle -pos=main.go:#75 definition main.go
$GOPATH/src/filosottile.info/tmp/go16vendor/vendor/example.com/hello/hello.go:3:5: defined here as var filosottile.info/tmp/go16vendor/vendor/example.com/hello.Hello string
$ go16 get -u github.com/opennota/check/cmd/varcheck
$ varcheck
Done :)
About goimports
NOTE: maybe by the time you read this, CL17728 is merged and goimports just works.
goimports is tougher, because not only it needs to find the code, but it also needs to know to strip everything up to /vendor/
when making the import path.
$ cat > main.go
package main
func main() {
println(hello.Hello)
}
$ go16 get -u golang.org/x/tools/cmd/goimports
$ goimports main.go
package main
import "filosottile.info/tmp/go16vendor/vendor/example.com/hello"
func main() {
println(hello.Hello)
}
Not much improvement :( There are a ticket and a CL to solve this. We can start playing with it by cherry-picking:
$ cd $GOPATH/src/golang.org/x/tools/cmd/goimports
$ git fetch https://go.googlesource.com/tools refs/changes/28/17728/1 && git cherry-pick FETCH_HEAD
$ go16 install golang.org/x/tools/cmd/goimports
$ cd $GOPATH/src/filosottile.info/tmp/go16vendor
$ goimports main.go
package main
import "example.com/hello"
func main() {
println(hello.Hello)
}
Olè!
The current CL won't work with most editor integrations (see the comments), and the integrations will need to be modified anyway, because editors usually use temp files, and don't tell goimports where the file is supposed to live. This used to be fine, but to find vendored packages goimports now needs to know the path of the file.
(You might have to nuke $GOPATH/src/golang.org/x/tools/cmd/goimports
to get updates again after this, depending on how careful go get -u
is.)
For more Go tooling acrobatics, follow me on Twitter.