Go's old $GOPATH story for development and dependencies
utcc.utoronto.ca
utcc.utoronto.ca
His last sentence:
> It could have worked, at least in theory, in a world where preserving API compatibility (in a broad sense) is much more common than it clearly is (or isn't) in this one.
Is he implying that using vendor packages is more clear that using version tags in a go.mod? That would certainly be the first I’ve ever heard of someone preferring dependency management via vendor packages over a go.mod & ‘go get’.
Personally I absolutely love the way go manages dependencies. I have way more confidence that my old go programs will run with minimal dependency related issues compared to programs written in something like python.
It for sure is easier to reason about a declarative text file than 18 quadrillion .go files in a subdirectory, but using go.mod does come with tradeoffs
(I'm the author of the linked-to entry. I wrote the entry because my impression is that a lot of modern Go programmers don't have this view of pre-module Go, and especially Go when you had to set $GOPATH and it was expected that you'd change it, instead of a default $HOME/go that you used almost all the time.)
For me, one of the reasons I disliked $GOPATH is that source code was not in $GOPATH; it was in `$GOPATH/src`.
The fact that they are very strongly against relative imports also didn't help, even if I get why they do it.
There are certainly nice things that the current Go module system buys; but one thing I miss is that under the old system, if one of the packages wasn't working the way you expected, the code was right there already; all you had to do was to go to that directory and start editing it, because it was already cloned and ready to go. The current thing where you have to clone the repo locally, add a "go.work" to replace just that package locally to do experimentation, and then remove the "go.work" afterwards isn't terrible, but it's just that extra bit of annoying busy-work.
But being able to downgrade by simply changing the version in go.mod is certainly a big improvement, as is having the hashes to make supply chain attacks more difficult.
1. Download the code (or update the copy you downloaded last time you had an issue)
2. Redirect `go build` to use the downloaded copy rather than the upstream copy (either by modifying go.mod or go.work)
3. Do the editing
4. Undo #2
Under the old system, you just had to do #3. And sure, it's not that much work, but it's a bit more than I had to do before.
Isn't it the case that Debian builds of go 'packages' (i.e. making a .deb package') are using the pre modules mechanisms? If so, then they would seem to prefer it for some reason.
As far as I know, yes: https://salsa.debian.org/go-team/packages/dh-golang/-/blob/d...
The big reasons I usually call out are:
- packages should be buildable without calling out to any network resources. (this is the primary reason Debian dh-make-golang disables 1.11 style gomod support)
- Debian specifically avoids vendored copies of code. This does create functionality and miantenance challenges, but it also produces a leaner installed code base. One again, for better or worse, this is a standing part of Debian package policy and warts from this particular choice stand out all over, not just golang. (newer Debian python, for example outright disables pip for non-venv python)
There are other nits and warts that become apparent, but I think those all waterfall out from the two points I've called out here.
(The Debian package policy in question: https://www.debian.org/doc/debian-policy/ch-source.html#embe...)
As an example, I wanted to install "gopls", the language server for go. There was a command that could install the dev tools, but it also generated a $HOME/sdk. With such a generic name in my home directory, there's not a chance that in 6 months time, I'd remember what it was for, why it was there, and whether it is safe to delete. No amount of changing $GOROOT could convince it to write into a different directory, nor was the go language server available through any other installer.
(Partly because docker has it's own set of obnoxious quirks, as it assumes everything can and should be run as root. And rootless docker cannot coexist on a system with over-permissioned docker. I really need to switch over to podman at some point.)
go mod init example.com/hello
and also hardcoded in all the import statements
import "example.com/hello"
what if you move the code to other domain ? you need to change the module and all the references to it instead of only the references
I find it quite cumbersome/counter intuitive even after all these years.
It is bad enough how much of a monopoly and tie-in github today has (probably the reason Microsoft bought it) and a language environment shouldn't contribute or even aplify that role.
Further discussion: https://stackoverflow.com/questions/40288843/installing-npm-...
It's just that most people don't do that, same as how most people will just store their code in GitHub.
Some people went with the forgejo fork: https://forgejo.org/ though Gitea itself was a fork of Gogs, if I remember correctly: https://gogs.io/
I also ran GitLab in the past: https://about.gitlab.com/ but keeping it updated and giving it enough resources for it to be happy was troublesome.
There's also GitBucket: https://gitbucket.github.io/ and some other platforms, though those tend to be a little bit more niche.
Either way, there's lots of nice options out there, albeit I'd still have to admit that just using GitHub or cloud GitLab version would be easier for most folks. Convenience and all.
Creating a central authority for names is a lot of work.
Im not that experienced with Go, but I believe it's possible to create a vanity package name on a domain you control. If you want to change hosts, you can just point your domain to something else.
I may be wrong there though!
The go-import tag is documented here: https://go.dev/ref/mod#vcs-find
It's a bit more fiddly if your module is part of a monorepo and doesn't live at the root. In that case your go-import tag needs to point to a GOPROXY server. I have a proxy server here: https://github.com/more-please/more-stuff/tree/main/gosub
https://pkg.go.dev/cmd/go#hdr-Remote_import_paths
Also AFAIK there's still no good UX for private packages that require auth.
It's the part of Go I find the weirdest
Node/Npn does this best.
'ShinyModule' https://github.com/shinyAuthor/ShinyModule or
'ShinyModule' ../../ShinyModule or
'ShinyModule' \\something\something\ShinyModule
and if there is no 'download' description for an import, the default should be ..\ShinyModule relative to the current/importing module
It's not because the tooling is better, which also happens to be true by far, but because they didn't tie themselves down to a domain name scheme. Funny, given that go waited a long time to take a shot.
<meta name="go-import" content="root-path vcs repo-url">How exactly? If e.g. the registrar seizes the domain name that I've originally used and gives it to someone else, what, exactly, are my plans supposed to be for that?
If you want to move to another domain the “go mod edit —replace” cli tool is designed for this.
If you want to host somewhere else, or maybe change to a vanity url or something, you can just publish a go-import meta tag and it all just works https://pkg.go.dev/cmd/go#hdr-Remote_import_paths
I don’t write go myself but man did they get a lot of big decisions right. I’d be totally open to writing go in future but I have java so don’t really need it. I envy go’s build and deploy stories though.
So really, Go's decisions about modules, vendoring, source layout, and the "go" command itself are all kind of irrelevant within Google. (The underlying Go compiler is used of course)
The feeling I got about GOPATH, back when I first encountered it, was not that it should be used like a Python virtual environment, where you'd have a separate one for each of your projects. What I understood back then was that you were supposed to set up GOPATH only once for your user, in your shell's startup scripts, and all of your projects would have to live within that directory, in the rigid structure the Go tools dictated. The default for GOPATH added later was just to avoid confusing newbies who aren't used to adding environment variables to their shell startup scripts.
However, the biggest problem with depending on packages in Go back then was that it was difficult to communicate to other people on your project the exact version of the dependency that you wanted them to install. For projects in which you were the only developer, this wasn't an issue...but as soon as you started to use Go in a team setting, it became a real blocker toward getting your work done. Go developers needed _some_ way to communicate what version of each package they wanted to install, and a bunch of solutions popped up to help with that. But they were all still bound by the `$GOPATH` constraint and such.
Although it took a lot longer than many predicted, I'm still pretty happy with how Go approached dependency management at the end of the day. Generally, all I have to do is import a dependency from a known URL and my editor/Go modules will take care of the installation work. This is way better than the JS world, in which if I want to just sketch something out I have to actually install the dependencies I need otherwise TypeScript will yell at me. With Go, it all seems to happen automatically.
What has fundamentally changed since then, in your opinion?
- If you want to make a change in one of your dependencies, you have to fork the repository, and in the forked repository you have to textually rename the package to the new name. This makes for abysmal maintenance and unneeded merge conflicts if you want to maintain parity with upstream code.
- There is the go replace directive, but this does not transitively apply to dependencies of the module that declares the go replace directive.
- If your patch gets into the upstream repository, now you have to undo the forking (again via a large textual rename).
- If a dependency of your code and your code both depend on the same package, you are forced to take one version per binary that gets compiled. This is just plain absurd and leads to situations where you cannot bump the dependencies independently. If you have a tree of these dependencies, you must update each dependency in the order that respects the dependency tree. This sort of defeats the purpose of specifying and locking the dependency version.
- Overall, go was designed for a mono-repo in a company (Google) that does not version their software (everything runs at tip), and it shows in any type of effort that attempts to re-use software in non-trivial fashion, with distributed development that happens at different rates in different repositories.