Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I think it's a bad addition since it pushes people towards a worse solution to a common problem.

Using "go tool" forces you to have a bunch of dependencies in your go.mod that can conflict with your software's real dependency requirements, when there's zero reason those matter. You shouldn't have to care if one of your developer tools depends on a different version of a library than you.

It makes it so the tools themselves also are being run with a version of software they weren't tested with.

If, for example, you used "shell.nix" or a dockerfile with the tool built from source, the tool's dependencies would match it's go.mod.

Now they have to merge with your go.mod...

And then, of course, you _still_ need something like shell.nix or a flox environment (https://flox.dev/) since you need to control the version of go, control the version of non-go tools like "protoc", and so you already have a better solution to downloading and executing a known version of a program in most non-trivial repos.



Yep, unfortunately this concern was mostly shrugged off by the Go team when it was brought up (because it would've required a lot of work to fix IIRC, which I think is a bad excuse for such a problem). IMO, a `go tool` dependency should've worked exactly the same way that doing `go install ...` works with a specific @tag: it should resolve the dependencies for that tool completely independently. Because it doesn't, you really, really shouldn't use this mechanism for things like golangci-lint, unfortunately. In fact, I honestly just recommend not using it at all...


i think it's more an argument to not use the dumpster fire that is golangci-lint


No, it really is a problem with this design and not an issue with golangci-lint.

The trouble is that MVS will happen across all of your dependencies, including direct and other tool dependencies. If everything very strictly followed Go's own versioning guidelines, then this would be OK since any breaking change would be forced off into a separate module identity. However, even Google's own modules don't always follow this rule, so in reality it's just kind of unrealistic.

You don't need something huge like golangci-lint to run into problems. It's just easier to see it happen because the large number of dependencies makes it a lot more likely.


I've been working on a version manager for golangci-lint in the quiet moments [1]

It already works, but it's work in progress. Happy to get feedback

[1] https://github.com/anttiharju/vmatch


What's the use case you have over "just" using the official install process (`curl | sh`)?


As long as a team agrees to have .golangci-version as the source of truth, the people using the tool don't have to worry about having the right version installed, as the wrapper fetches it on demand.

Having the wrong version installed between collaborators is problematic as then they may get different results and spend time wondering why.

Works across branches and projects.


Interesting - would something like `make lint` (which then installs to `$PWD/bin`) work? That's how I've been doing it on the projects I've been working on, and it's worked nicely - including automated updates via Renovate (https://www.jvt.me/posts/2022/12/15/renovate-golangci-lint/)


A simpler approach is to use `go run` with a specific version. e.g.:

    go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4 run
Easy enough to stuff in a Makefile or whatever.

Even better in Go 1.24 since according to this article the invocations of go run will also be cached (rather than just utilizing the compilation cache.) So there shouldn't be much of an advantage to pre-emptively installing binaries anymore versus just go running them.


> Using "go tool" forces you to have a bunch of dependencies in your go.mod

No, it doesn't. You can use "go tool -modfile=tools.mod mytool" if you want them separate.


I built a simple tool to generate binstubs and work around this problem: https://github.com/jcmfernandes/go-tools-binstubs

However, having multiple tools share a single mod file still proves problematic occasionally, due to incompatible dependencies.


This should almost certainly be the default recommendation.


That seems like it should have been the default


Why do ecosystems continue to screw up dependency management in 2025? You would think the single most widely used feature by programmers everywhere would be a solved problem by now.


Go launched without any dependency management. Go people believe that anything non-trivial is either a useless abstraction, or too difficult for the average developer. So their solution is to simply not add it, and tell anyone claiming to need it that their mind has been poisoned by other languages...

Until that time when they realize everyone else was right, and they add an over simplified, bad solution to their "great" language.


Your reply is polemical and somewhat detached from the experience of working with Go.

From my experience, Go's dependency management is far better than anything else I've worked with (across languages including Java, Scala, Javascript/Typescript, Python).

Your criticism is perhaps relevant in relation to language features (though I would disagree with your position) but has no basis in relation to tooling and the standard library, both of which are best in class.


I think in this case you should give rust a try if only for cargo. Because the mentioned issues are non existent there. Also because it’s the language mostly referred to as being completely on the other side of the spectrum when it comes to language design philosophy.


I have no beef with Rust, but objectively I think it's dependency management, general tooling, and standard library are significantly worse than Go's. The language, as you say, has a quite different philosophy from Go which many favour - but that is only part of the story.

Could you elucidate which of the 'mentioned issues' you think are present for Go (in relation to tooling) that do not apply to Rust/cargo? Is your critique based solely on the new `go tool` command or more widespread? And are you aware that the parent criticism is at least partially misguided given it is possible to store tooling in a separate .mod file or simply ignore this feature altogether?


Each rust crate is compiled on their own. Means if a library you pull in, needs the same crate as a another dependency, even with incompatible versions, it doesn’t matter. Then cargo understands the difference between test and build dependencies next to the dependencies for the actual lib/binaries.

The fact that each library and its dependencies gets compiled separately adds quite a lot in build time depending how many crates you reference. But you usually don‘t fight with dependency version issues. The only thing which is not expressed in crates is the minimum rust version needed. This is a pain point when you want or need to stay on a specific toolchain version. Because transient dependencies are defined by default like „major.minor.patch“ without any locking, cargo will pull the latest compatible version during an update or fresh checkout (means anything that is still compatible in terms of semantic versioning; e.g 1.1.0 means resolve a version that is >= 1.1.0 && < 2.0.0) And because toolchain version updates usually happen as minor updates it happens that a build suddenly fails after an update. Hope this makes sense.


> Java, Scala, Javascript/Typescript, Python

You seem to only use languages with bad dependency management. Which sounds tongue in cheek, but it's true. These are the languages (along with Go) where people hate the dependency management solutions.


Haha perhaps - though I think you'll find that (with the exception of Scala) these are some of the most popular programming languages.

Having sad that, I don't really know anyone with significant Go experience who dislikes its dependency management - most critiques are now quite out of date/predate current solutions by several years.

Finally, I'm not really sure which language you are proposing has better dependency management than Go - whether in relation to security, avoiding the 'diamond' problem, simplicity, etc. Rustaceans like to critique Go (no idea if you are one but my experience is that criticism is often from this crown) but I'm not sure how, from my own Rust experience/knowledge, it is actually better. In fact, I think there are several grounds to think that it is worse (even if still very solid compared to some of the aforementioned languages).


I haven't used Java much in the last few years but it was pretty straight forward with Maven with the slight caveat you use an IDE to do everything.

Python isn't too bad now-a-days with poetry although the repo spec could use more constraints


I fail to see how working directly from code with direct url mappings to source code repositories is better than Maven, Graal, NuGET.

Anything is better than npm, with one function per package.

Python, thankfully I only need batteries for OS scripting.


Speaking as someone who comes from the opposite end of the spectrum (Scala both professionally and by personal preference) and who doesn't enjoy Go as a language, I think there's a lot to be said for a language that evolves that way. Being able to make decisions with literally years of hindsight is powerful. Looking at the top critiques here and how they've been addressed, this seems like a pretty thoroughly baked approach.

I would rather scratch my eyes out than use Go for the kind of code I write day-to-day, but Go looks amazing for the kinds of things where you can achieve high levels of boringness, which is what we should all be striving for.


Go is more like "decades in hindsight", though. Literally the case with generics, just to give you one example - and for all the talk about how other languages aren't doing it right and they're waiting to figure it out, the resulting design is exactly the same as those other languages, except that now they had to bolt it onto the language that evolved without them for a decade, with all the inconsistencies this creates.


Different languages speak to different people. I feel the same way about Scala and most other functional languages. To me, they’re fun and all, but I wouldn’t build anything large-scale with them. My problem space is interesting enough that I don’t have to worry about Go being boring.


To be clear, I mean "boring" in the positive engineering sense of well-defined and reliable. The kind of work that I don't like to do in Go is "interesting" in the negative sense of lots of special cases, complicated branching based on combinations of data, complicated error handling... stuff where pattern matching and use of types like Option and Either shine (hell, even exception handling helps!) Basically the kind of stuff that good engineers would design out of existence if the product managers and salespeople would let us.


go mod is best in class dependecy manager, it is way better than what most mainstream languages have, it's not even close.

I worked with C++/ruby/python/js/java and C#, go mod is superior to all them.

The one that is similar is Cargo ( rust ).


If all dependencies live in source code repos, and no one ever migrates their projects elsewhere, and placing URLs as imports directly on the source code, no thanks.


This isn't exactly true, as the dependency management was simple - clone the relevant project. Dependencies being source is great as it encourages open source.

Of course, the problem with that was that it was pretty much impossible to version properly, causing breakages and such.


If you don't like Go, why did you come and comment in this thread? didn't your mother ever tell you that if you don't have something nice to say, you shouldn't say anything?

at least Go doesn't have virtualenvs


...yet


This situation has improved over the last 9+ years or so. I agree that's where things started and I felt much the same way at the time.

That said, I still strongly dislike that the Go ecosystem conflates a library's repo URL with it's in-language URI. Having `github.com` everywhere in your sourcecode just ignores other use-cases for dependency management, like running an artifactory in an enterprise setting. My point being: there's still room for improvement.


Go has been a masterclass in disparaging industry learnings as "unnecessary" (dep management, generics, null safety), then gradually bolting them into the language. It's kinda hilarious to watch.


It would be, if they didn't hit jackpot with Docker and Kubernetes adoption, and now many of us on DevOps space have to deal with it in some form.

A bit like C ignoring previous experience in safe systems, getting lucky with UNIX's adoption, and we are still trying to fix that to this day.


Because everyone has their own opinion about it, like most other standards.

Personally, I think the way PHP handles dependencies is vastly preferable to every other ecosystem I've developed in, for most types of development - but I know its somewhat inflexible approach would be a headache for some small percentage of developers too.


> Using "go tool" forces you to have a bunch of dependencies in your go.mod that can conflict with your software's real dependency requirements, when there's zero reason those matter. You shouldn't have to care if one of your developer tools depends on a different version of a library than you.

Heh, were the people who made 'go tool' the same people who made Maven? Would make sense :P


At least build time dependencies are separated from runtime dependencies.


I ask this out of curiosity, not accusation, do you work for Flox? Can't say I've ever seen it mentioned "in the wild".


I do not, just aware of it as a hip tool in the general space of "I want to install and version tools per-project, but I don't want to learn the nix programming language"


Hehe, I was also wondering this.


I agree — tools should be shared artifacts that the team downloads and can guarantee are the same for everyone. I usually setup a flake.nix for everyone but flox, earthly, devenv, jetify, are all great alternatives. Ideally your tools are declaratively configured and shared between your team and your CI/CD environment, too.

The `go tool` stuff has always seemed like a junior engineering hack — literally the wrong tool for the job, yeah sure it gets it working, but other than that it's gross.


well now it leans in to go's reproducible tooling so it's declaratively configured and shared between your team and your CI/CD environment too.


There are many tools that arent written in golang, and therefore not manageable via this approach. I don’t really understand why I would use this.


control the go version with the `toolchain` directive, replace protoc with buf.build (which is way better anyway).




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: