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
proposal: cmd/go: track tool dependencies in go.mod #48429
Comments
I like this, I find it annoying to use the If this proposal moves forward, where does the dependency go in the |
Good question! I'm not so familiar with the reasoning behind the multiple blocks... something to do with lazy loading? I'd defer to those with more experience in this area |
Personally, I think #42088 is already a pretty good solution. With it, one can write
Similarly, Another big advantage is that you can pick specific versions of tools, and they won't interfere with your main |
The only downside to #42088 is that, if you repeat the same |
Or |
Oh interesting, thanks @mvdan I wasn't aware of that solution. A few concerns immediately come to mind...
|
Also this |
In module mode, |
It certainly warrants a mention. I'm not sure we should bless it as the only best practice, though, because there can be legitimate reasons for versioning, downloading, and running tools some other way. Perhaps some of your tools aren't written in Go, such as protoc, so you use a "tool bundler" that's entirely separate to Go. Or perhaps you do need your tools to share the same MVS graph with your main module for proper compatibility, so you want them to share a |
Gotta say though...
|
So even with the |
Also worth noting: codegen tools like gqlgen and protobuf are often comprised of a generator command and a runtime, both of which typically need to be versioned in lock-step. This proposal solves that case rather neatly, allowing go.mod to manage both generator and runtime versions. |
We used to do that. Then people would have that replicated across different files and the version wouldn't always match, and we wanted to automate tool updating, so we figured that migrating to Again, |
@jayconrod has previously suggested something similar, using a new directive (perhaps Personally, I prefer the approach of adding a new directive — today we do treat requirements with A new |
@bcmills would such tool requirements be part of the same MVS module graph? |
The In particular:
|
Or |
I like this proposal. I've had something similar in my drafts folder for a while. @bcmills touched on the main difference.
I don't think |
Yeah I like the A |
Or have I got that wrong? Is sharing indirect dependencies between tools and other dependencies a desirable feature? |
Right. The go command reports errors for unknown directives in the main module's go.mod file, but it ignores unknown directives in dependencies' go.mod files. So everyone working on a module that used this would need to upgrade to a version of Go that supports it (same as most other new features), but their users would be unaffected.
My suggestion is to have If you don't want to mix tool and library dependencies in |
Yep that makes a lot of sense @jayconrod |
This proposal has been added to the active column of the proposals project |
@jayconrod Did you want to write up the |
Firstly, I also support the proposed dedicated re:
Just a quick comment regarding tool dependencies used by Go modules. Not all external tools will be developed in Go, some external tools may be developed in arbitrary languages, but we may still wish to pin specific Git commit revisions or tagged versions to ensure that all build requirements of our Go modules are satisfied and don't go out of sync with the required versions of external tools. A real world example of this is the Textmapper tool (written in Java) used by github.com/llir/llvm to generate lexers and parses for LLVM IR from a BNF grammar. Since the Textmapper tool is not written in Go, it is currently tracked by a Git submodule: https://github.com/llir/ll/tree/master/tools Just put this out here, to keep in consideration when working on dependency handling of tools (e.g. build tools) required by the Go module. Should Cheers, P.S. @inspirer is working on a Go version of Textmapper (inspirer/textmapper#6), but it has yet to reach feature parity with the Java version. (The above still applies to other tool dependencies used by Go modules and developed in other languages than Go of course.) |
@rsc This should be able to be taken off hold right? Also, I am a bit confused why this went on hold in the first place. I understand that there was a lot of 1.18 work, but why did that necessitate this going on hold again? |
@bcmills Is there more work pending, or should this come off hold? Thanks. |
This should come off hold. |
I like the proposal to add a Instead of adding Unlike the proposal by @jayconrod, I would not merge the tools' dependencies into the package's dependencies (and I'd build the tools ignoring the go modules require and replace directives). Each tool should be built in standalone mode, because it would be surprising for me that adding a tool could affect my main module's build. As such I'd make the syntax inclusive of a version number:
At this point running |
Thinking further... One problem with the current approach to bundling tools with repositories is that in order to manually run the tool I need to either type Maybe a better way of avoiding this is to focus on fixing the ergonomics of running tools that have been bundled with the module, and making it easy to run the correct version (c.f. #57001 for go itself). I propose the following:
Although I like @jayconrod's idea of the Edit: this was updated to reflect a slightly tighter scope; and to rename the directive to "run" instead of "tool" to (maybe) reduce confusion (as go tool does something completely different from go run). (Though maybe Previous versionI propose the following:
This would not support non-go tools, as I think specifying a way to version arbitrary binaries is probably out of scope for go's tooling. I am not sure whether This does require teaching people to use |
Change https://go.dev/cl/472755 mentions this issue: |
@rsc is there a way to ask the proposal committee to take a look at this when you next meet? There are roughly two options proposed here: which seems like the right direction, and what are the remaining things to resolve before something like this could be accepted? I'd be interested in trying to implement either of these approaches (or an alternate idea) for go 1.22; but I'd love some input from you all on what makes sense as a next step; and if it'd be helpful I'm happy to write a more detailed proposal doc. |
This proposal has been added to the active column of the proposals project |
I have some questions about @ConradIrwin 's proposal:
I had a thought about a separate tool management system related to
When run without arguments, in a module with a
These compiled binaries will be cached in the same way as @ConradIrwin's proposal. When calling I like that this idea cleanly separates tools used to interact with a module (and may do nothing with the Go source at all), and dependency versions required to build and test the module's Go code. |
I think @ConradIrwin's proposal (#48429 (comment)) works for most of the Go repositories that I work in and I think I would use it, but it feels unnecessary given the +1 @joeblubaugh's concern (#48429 (comment)) about the tools affecting version selection. I think this would create some big surprises. There are times where it would be super useful if I'm using a tool that has a corresponding library that have to be aligned. But there are also times where it would be surprising if my transitive dependencies controlled which version of a tool was in use. For the most part I've found the I think the main thing these proposals are adding is aliasing shorter names to paths. If paths are truly too long to be convenient maybe a general path aliasing system would be appropriate. Other systems have done this. For example, Deno added support for aliasing with their import map file (e.g. https://deno.land/manual@v1.31.0/basics/import_maps). I'm not advocating for aliasing, I don't think the go tool should adopt path aliasing as a feature, but that's what it feels like these proposals are adding to the go tool, narrowly for tools. |
I think there are two separate concerns. One concern is adding tools to the dependency graph, particularly for The other concern is making it easier to run tools; I think that's what the aliasing is getting at. I could see that being useful for, say, tests that run those tools. But I think there is a lot of complexity there that would need to be resolved — for example, if I run |
@joeblubaugh / @leighmcculloch I went back and forth on "should they contribute to MVS or not". Originally I thought maybe the module author should be able to chose, but the distinction is subtle. I landed on "yes, they should" to give module authors control over which dependencies are pulled in. You can of course still I am not sure whether A separate @joeblubaugh I think that change to @leighmcculloch glad to hear you would use this! I do hear your point around aliasing being unnecessary (and indeed I'd be happy to have something that did the versioning and caching without the aliasing). Adding the aliasing I think makes |
@bcmills interesting thought. Currently It does mean that if you have a different version of the tool required by the main module and by example.com/m then the test will be compiled with one version but I think it would be theoretically possible to fix the version mismatch in the specific case of I'm sure we shouldn't try to fix this in the case that you I don't think this is a problem for other go commands ( |
Overall, this seems useful, the tools.go situation has always been an annoyance to me. I tend to put The proposal so far doesn't mention any Finally, a bit of devil's advocate... currently I can create a |
@perj Good to know that this would be useful to you! And good call on I hadn't realized about /vendor/x – if you have both (and they point to different things) the ones in |
Meanwhile I have published a basic CLI tool to pin Go dev tool versions. https://github.com/mcandre/accio As much as I enjoy contributing developer tools, I would prefer to be able to deprecate this workaround and just use the builtin go mod system. (Would also love to be able to ditch modvendor, and have go mod vendor stop deleting critical cgo source files, for the same reason. But that's uh off topic for this discussion ) |
@mcandre Thanks for sharing! Would the change proposed in #48429 (comment) give you the benefit you get from accio? I notice it takes quite a different approach (installing specific dependency versions into the path, vs requiring |
Thanks for the proposal @ConradIrwin and others! Happy to see this moving forward 💪🏽 Similar to @mcandre I would love to switch to native solution. In the meantime https://github.com/bwplotka/bingo is still up to date and got quite some traction. My 2c of the discussion so far, assuming we iterate over @ConradIrwin proposal:
Also agree with majority of @leighmcculloch comment, except this one:
If that's true, then we might be missing the point. To me the main problem of the issue we are trying to solve here is to be able to save and track versions of tools and its dependencies (including potential replace directives) in declarative way for the portability of the project development. I think it has to be a separate dependency graph as mentioned above. |
@bwplotka I strongly agree that the main issue to solve is versioning (though I think aliases help, I'd be happy to defer to a second round too). Responding to other points:
Not as proposed. Similar to how it works for go libraries, you can only have one version of a given module in your go.mod.
Agreed! With this proposal if your module depends on a module that has That said, we still maintain the invariant that your There are some advantages to this: you can be sure that a tool used in go:generate has the same version as any library it includes in your code, and you can control the dependencies of your tools with (It's maybe worth noting that this proposal does not aim to solve the problem for every tool. You call out prometheus in the bingo blog post, but as you cannot |
Background
The current best-practice to track tool dependencies for a module is to add a
tools.go
file to your module that includes import statements for the tools of interest. This has been extensively discussed in #25922 and is the recommended approach in the Modules FAQThis approach works, but managing the tool dependencies still feels like a missing piece in the
go mod
toolchain. For example, the instructions for getting a user set up with a new project using gqlgen (a codegen tool) looks like thisThe
printf
line above really stands out as an arbitrary command to "add a tool" and reflects a poor developer experience when managing tools. For example, an immediate problem is that theprintf
line will only work on unix systems and not windows. And what happens iftools.go
already exists?So while we have some excellent tools for managing dependencies within the
go.mod
file usinggo get
andgo mod edit
, there is no such equivalent for managing tools in thetools.go
file.Proposed Solution
The
go.mod
file uses the// indirect
comment to track some dependencies. An// indirect
comment indicates that no package from the required module is directly imported by any package in the main module (source).I propose that this same mechanism be used to add tool dependencies, using a
// tool
comment.Users could add a tool with something like
or
A
go.mod
would then look something likeAnd would allow users to subsequently run the tool with
go run github.com/99designs/gqlgen
This would mean a separate
tools.go
file would no longer be required as the tool dependency is tracked in thego.mod
file.Go modules would be "tool" aware. For example:
go mod tidy
would not remove the// tool
dependency, even though it is not referenced directly in the module// tool
dependency is imported by another module, Go modules understands that the// tool
dependency is not required as an indirect dependency. Currently when usingtools.go
, go modules does not have that context and the tool is treated like any other indirect dependencygo get -tool [packages]
would only add a dependency with amain
packageThe text was updated successfully, but these errors were encountered: