Closed myitcv closed 5 years ago
Thanks, @thepudds. That is a nice example demonstrating what the original author might do. But unless I'm mistaken, it doesn't cover how someone else working on the same project might get the same.
That is, after the original author creates the module,go install
s dependencies, and alters their path, what steps should someone follow after git clone
if they wanted go generate
to produce the same?
@tschaub there is no standard way to do that with go, however there is an additional tool you can introduce to sort of get that outcome.
Its called gobin and its use is described in https://github.com/go-modules-by-example/index/tree/master/017_using_gobin
This makes the minimum common set of dependencies for your team simply go + gobin, and gobin's installation can be automated with a makefile.
Here is an example I set up on the netlify open-api repo when I worked there:
https://github.com/netlify/open-api/blob/813b6ad88723e32e8c6760c0a28dfd7a254d2199/Makefile#L13-L14
and the accompanying generate file:
https://github.com/netlify/open-api/blob/813b6ad88723e32e8c6760c0a28dfd7a254d2199/generate.go#L3
This also takes advantage of the tools.go pattern.
I would love to see this practice standardized however it doesn't look like it will happen anytime soon.
Since this is showing up in my searches when I'm trying to find the other reference that I made, I'm going to add it here as well:
I was not seeing the dependency that I wanted added to the go.mod and I was getting this error:
tools/tools.go:6:5: import "git.rootprojects.org/root/go-gitver" is a program, not an importable package
(go-gitver
is the thing I'm trying to add)
I'm not 100% clear on the sequence of events that fixed it, but I did all of these things:
I made a tools
directory:
mkdir -p tools
I put the tools package inside of it (as mentioned above):
// +build tools
package tools
import (
_ "git.rootprojects.org/root/go-gitver"
)
Note that the tag is mostly not important. You could use foo:
// +build foo
However, you cannot use ignore
. That's a special predefined tag.
// +build ignore
// NO NO NO NO NO
// `ignore` is a special keyword which (surprise) will cause
// the file to be ignore, even for dependencies
The best way is probably to run go mod tidy
:
go mod tidy
However, before I did that I ran a number of commands trying to figure out which one would cause it to go into go.mod
:
go install git.rootprojects.org/root/go-gitver # didn't seem to do the trick
go get
go generate ./...
go build ./...
go install ./...
go mod vendor
Later I did a git reset
and rm -rf ~/go/pkg/mod; mkdir ~/go/pkg/mod
and found that go mod tidy
did well enough on its own.
In order to actually take advantage of the modules cache in a project you need to copy-in the source code
go mod vendor
That will grab all dependencies from go.mod
You also need to change nearly all of your go commands to use -mod=vendor
in any Makefile
s, Dockerfile
s or other scripts.
go fmt -mod=vendor ./... # this needs a fix that is scheduled for go1.15 I believe
go generate -mod=vendor ./...
go build -mod=vendor ./...
That includes go build
, go get
, go install
, and any go run
called by go generate
(and even the go generate
itself)
//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver --fail
package main
// ...
Here is a modification to the best-practices example that makes things reproducible (without additional installs or path modifications) on Go 1.13.8: https://gist.github.com/tschaub/66f5feb20ae1b5166e9fe928c5cba5e4
tl;dr - use go run golang.org/x/tools/cmd/stringer
instead of stringer
in your go:generate
comment.
Update: I see now that this same suggestion appears above (https://github.com/golang/go/issues/25922#issuecomment-398792589)
@tv42 What needs improvement in my example? As far as I can tell I've collected the best information from various threads and combined them into a single set of "just works" instructions, but I'd love to continue to improve it.
Really like the
go run tool@version
idea.
But on Go 1.14, only go get
supports the @version
syntax.
Is there a way run or install a specific version of a Go executable in Go 1.14?
@ags799 use https://github.com/myitcv/gobin
Hi Awesome Go Community! :wave:
Thanks for this thread @myitcv and everyone else who contributed to this discussion. Maybe it's not properly appreciated but a huge amount of people traverse through those threads carefully every day to find answers, so big kudos to you all. Especially this thread, for us, was extremely insightful and gave us many pointers in the space of tools versioning.
This being said, we are maintaining several big(-ish) Go projects in the open-source (e.g Prometheus, Thanos, prometheus-operator and more) and we were looking for a solid solution to this problem for a few months already. While there are good experiments, we were looking for something we can rely on now and long-term.
This is why we created the open-source CLI called bingo, that (hopefully) makes it completely seamless. :muscle:
So, If you are looking for some smooth experience for versioning Go tools on top Go Modules
for any project (even non Go projects that just use Go tools!) I would recommend taking a look and trying out! :hugs: Feel free to use and please contribute & give us feedback! We started to use it recently in many projects we maintain e.g Thanos and so far it works quite well... :rocket: Hopefully we can maintain it together and improve on the way.
After all, even if Go Team will have an answer to this problem someday (I am pretty sure they will!), I think it's only helpful if the community can try to solve this problems much earlier on top of existing tooling (Go Modules
). I think it might be good experience for Go Team as well to tell what requirements matter here. :hugs:
I also wrote some detailed blog post on the way about this problem space and a little bit on how bingo was created: https://www.bwplotka.dev/2020/bingo/ Feedback welcome on this as well (e.g by creating issue here)
@bwplotka we're just now starting to explore this space as mismatched versions of tooling is causing some pain points. Bingo looks interesting, but what prevents it from running on Windows? That would be a non-starter for us, unfortunately.
We just don't use windows, so not yet tackled, that's it (: There are huge differences in how the path, shell (PowerShell!) and $PATH works, etc. Otherwise, there is no blocker TBH (: I added an issue on bingo to track this: https://github.com/bwplotka/bingo/issues/26 it should be actually quite a quick fix to make it work if you guys want to help and contribute :muscle:
I'm curious why tools.go
with // +build tools
? Wouldn't something like tools_test.go
work as well?
Hi Kamil :) https://github.com/golang/go/issues/25922#issuecomment-414677877 To avoid init()
side-effects
@AlekSi Hi :) I was asking to use tools_test.go
instead of tools.go
. init()
side-effects still apply but just for tests so 🤷 ... however I realized go test
would pick it up and that would be much more annoying and not working:) Thanks for explanation.
Change https://golang.org/cl/261499 mentions this issue: internal/tools: add a dummy package that imports mkwinsyscall
In one of the projects we had a separate go.tools.mod
file where we've defined our tooling dependencies.
Installation is simple but with 1 additional param, ex: go get -modfile=go.tools.mod github.com/foo/bar
.
Example from open source https://github.com/mattermost/mattermost-server/blob/master/Makefile#L573 and https://github.com/mattermost/mattermost-server/blob/master/go.tools.mod (kindly ping @agnivade as one of the authors and Go contributor)
Hey there, I'm not exactly sure what is it you wanted me to do?
As mentioned above, @cristaloleg even separate go.tools.mod
will not work for most of the cases.
I wrote about this in detail here but TL;DR on using go.tools.mod
:
go.tools.mod
(unless you have one tool)replace
/ exclude
manual hackery (which is required for "broken" modules) - you are stuck. You can't pin such a tool or you need to manually fight a lot.go.tools.mod
requires go.mod to be in repo (!), so you cannot pin go tools in non-go projects.And there is more 🤗 but hope it's enough to motivate the bingo existence. Bingo solves all those painpoints. We recently released a new version v0.3.0 so you are more than welcome to try it out! I have a full-time job, and other much bigger open-source projects to maintain, so please use, contribute and help us to make this even better.
... and hopefully, this tool will inspire Go native tools for a similar user experience (happy to contribute anything!) 🤗
re: @rsc in https://github.com/golang/go/issues/25922#issuecomment-413898264
Best practice remains creating the tools.go file. It's true that go mod init does not auto-create a tools.go from dep's config, but I think doing so is getting a bit beyond scope.
The best practice described currently triggers a go vet
warning (see e.g. https://github.com/llir/llvm/issues/196)
From llir/llvm/ir/enum/tools.go:
//go:build tools
package enum
import (
_ "golang.org/x/tools/cmd/stringer"
)
Running go vet -tags tools github.com/llir/llvm/ir/enum
results in the following warning:
ir/enum/tools.go:6:2: import "golang.org/x/tools/cmd/stringer" is a program, not an importable package
Is using a tools.go
file with a go:build tools
build tag still the recommended best practice for handling tools dependencies? Or is there another approach that is now recommended?
Cheers, Robin
@mewmew In this case, don't run go vet
with -tags tools
. The tools.go file is not meant to be part of a buildable package, so vet won't provide useful feedback on it. Without -tags tools
, everything except go mod tidy
will ignore tools.go.
I've moved to installing tools.go
dependencies using make
with the power of go list
:
install:
go install $$(go list -f '{{join .Imports " "}}' tools.go)
I've not looked into this, but I feel //go:build tools
should become obsolete doing so.
This will actually install to the users home directory somewhere, polluting the machine outside the repo and is generally a bad practice regardless of the build system being used (and especially with Make where "install" is a phony target and the outputs can't be kept track of).
The benefit of using a tools.go and then running the deps with go run is that you always wind up using the correct version and don't have to do the install every single time (once the temporary files are downloaded and properly versioned/hashed Go is happy to skip fetching them). This should work fine with a properly written Makefile because tools.go will be a dependency of whatever your final build target is, eg. if you do something like this:
GOFILES!=find . -name '*.go' tool_generated_file: tools.go go run sometool mybinary: tool_generated_file $(GOFILES) go build -o $@
Then when the tools.go file changes the tool will be re-run and your files regenerated.
—Sam
On Thu, Oct 14, 2021, at 08:40, andig wrote:
I've moved to installing
tools.go
dependencies usingmake
with the power ofgo list
:install: go install $$(go list -f '{{join .Imports " "}}' tools.go)
I've not looked into this, but I feel
//go:build tools
should become obsolete doing so.
-- Sam Whited
@rsc
Best practice remains creating the tools.go file. It's true that go mod init does not auto-create a tools.go from dep's config, but I think doing so is getting a bit beyond scope.
Another data point: I'm urgently porting an older large project from git submodules to go modules and tools.go approach breaks go list -deps
, which I'm using as a precise audit step together with a custom GOPROXY with a more relaxed general allow list:
// +build tools
package x
import (
_ "golang.org/x/tools/cmd/stringer"
)
C:\Users\egon\Desktop\x>go list -deps -tags tools -f "{{with .Module}}{{.}}{{end}}"
tools.go:6:2: import "golang.org/x/tools/cmd/stringer" is a program, not an importable package
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654
golang.org/x/tools v0.1.8
...
I can get rid of the error by using -e
, but I prefer hard errors. An alternative is to check the final binaries with go version -m
or maybe complicate the GOPROXY infrastructure with more precise rules. Not a smooth experience...
@bcmills
I think I'll just summarize my findings down here; it didn't all seem obvious and took some googling and tinkering, as some sources are a bit ambiguous on some of the points.
tools.go
The best practice as endorsed by the Go team (https://github.com/golang/go/issues/25922#issuecomment-413898264).
//go:build tools
// +build tools
package yourpackage
import (
_ "golang.org/x/tools/cmd/stringer"
// ...
)
Change yourpackage
to the name of the package that you put this in, e.g., use qux
if you put it at the root of your module foo/bar/qux
. Only use tools
when you actually put it in a tools
package (directory).
If you're on Go 1.17+, you don't need the // +build tools
line.
go.mod
To add the dependencies and the latest versions of these tools to your go.mod
and freshen the module cache, run
go mod tidy
gopls
If you use gopls
, configure it to include the tools
build tag, e.g., for VSCode, add the following to your settings.json
:
"gopls": {
"build.buildFlags": ["-tags=tools"],
}
go.mod
versions of all the tools to your $GOBIN
directory (probably not the best idea, expand for details)Makefile
Makefile
.bingo
You can use bingo
to install version-suffixed executables of your module's tools in the global $GOBIN
directory. This is an entirely different approach that avoids conflicts among tools and tool dependencies in larger projects, but it doesn't integrate with your module's go.mod
(which might also be a good thing).
The author introduced this tool in https://github.com/golang/go/issues/25922#issuecomment-639057308 and further expanded on it in https://github.com/golang/go/issues/25922#issuecomment-759774278
(gopls/doc/settings.md at master · golang/tools)
(google/wire: Compile-time Dependency Injection for Go)
(bwplotka/bingo: Like `go get` but for Go tools! CI Automating versioning of Go binaries in a nested, isolated Go modules.)
I think golang needs to:
go.mod
only.go execute <tool> <args>
.Edit: It appears go run package@version
already provides point 2, so really only point 1 is missing for complete management of tool dependencies.
@antichris, I still dont understand how I get the binary from that tools file and can use it in go generate?
I have this in the main package.
//go:build tools
package main
import (
_ "github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/github"
)
I ran go mod tidy but how can I use the binary now?
package main
//go:generate migrate -source=db
func main() {
}
$ go generate
main.go:3: running "migrate": exec: "migrate": executable file not found in $PATH
Unlike many projects, the Go project does not use GitHub Issues for general discussion or asking questions. GitHub Issues are used for tracking bugs and proposals only.
For questions please refer to https://github.com/golang/go/wiki/Questions
@bluebrown In general, the common practice would be running the tool using full package path in a go:generate
directive, like so:
//go:generate go run golang.org/x/tools/cmd/stringer -type Foo
This would work if you have import
ed the respective package in your tools.go
, e.g.:
import _ "golang.org/x/tools/cmd/stringer"
In your specific case, you'd have to import github.com/golang-migrate/migrate/v4/cmd/migrate
:
import _ "github.com/golang-migrate/migrate/v4/cmd/migrate"
And, according to their documentation, add the appropriate build tags when running the COMMAND
that you need:
//go:generate go run -tags postgres github.com/golang-migrate/migrate/v4/cmd/migrate -source=db COMMAND
Please answer these questions before submitting your issue. Thanks!
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes; and latest
vgo
commit (per above)What operating system and processor architecture are you using (
go env
)?What did you do?
Off the back of https://github.com/golang/go/issues/25624#issuecomment-395556484, I'd like to confirm that the following represents the "best practice" advice when adding and installing tool dependencies:
The
go.mod
and.Target
forstringer
look fine:The issue however is that running
vgo mod -sync
then removes our module requirement ongolang.org/x/tools
- I suspect this is a bug:If we assume this is a bug and ignore it for now, I also wonder whether we can improve this workflow for adding tool dependencies somehow. The following steps feel a bit "boilerplate" and unnecessary:
tools.go
filemain
package, so I'm not sure we can ever safely verifytools.go
is "good"?)GOBIN
to an appropriate locationvgo install tool
PATH
includesGOBIN
I wonder whether we could in fact obviate all of this by having something like:
Thoughts?
vgo run tool
is possible as a result of https://github.com/golang/go/issues/22726, but because of https://github.com/golang/go/issues/25416 it effectively requires a link step each time.What did you expect to see?
With respect to what I think is a bug with
vgo mod -sync
go.mod
unchanged by thevgo mod -sync
What did you see instead?
The
golang.org/x/tools
requirement removed./cc @rsc @bcmills