extending Go forward compatibility #55092
Replies: 27 comments 56 replies
-
I want this very much. One possible minor downside is execution cost in transient (CI) environments, where the toolchain download and unpack might happen every time. If a dependency gets updated, and there’s no explicit toolchain line, CI builds could silently get slower and costlier until someone notices. |
Beta Was this translation helpful? Give feedback.
-
Interesting idea. Can you elaborate a bit more on how this might work with other Or would it be exclusive for |
Beta Was this translation helpful? Give feedback.
-
Creating a new module defaults the Combined with the new rule above, I can imagine this causing more churn than some users would like, particularly if they are using All that said, I think this has a lot of parallels with modules, and ultimately isn't a big deal. Users regularly update the versions they "require" of their dependencies, but only as a periodic update, not because they really need something from the new version. This new "requirement" propagates to dependents, which now must update the newer versions. |
Beta Was this translation helpful? Give feedback.
-
Some more background would be helpful. Does this mean that a particular Go version's toolchain can already expand, contract, or change the available features of the Go it builds to target a specific Go version? I'm unclear on why the language semantics and toolchain versions are being distinguished here. Doesn't Go 1.19 build a module targeting Go 1.17 as a Go 1.19 binary?
Could there be a mismatch of supported architectures or operating systems? If so, how would those be handled?
What if an explicit command-line option given by the user to the installed version isn't supported by the future version, or the meaning of it changed? The Go 1 compat promise doesn't seem to apply to the toolchain. How would differences there be accounted for, or do they even need to be?
The idea here doesn't seem to want to break the Go 1 compat promise, so why not instead fail the build, and force the user to upgrade their Go version? That seems simpler than essentially re-inventing a command-line package manager just for Go versions. It seems surprising (and perhaps wrong) to have Go 1.20 installed, but get a Go 1.21 build. What if, say, the range of times representable by time.Time in Go 1.21 narrows drastically, and you want to build with Go 1.20 instead? I do sympathize with users who use OSes that don't bump the Go version right away, but perhaps that's an intended stability or security feature. If users really want the latest and greatest, they can always download and install a standalone binary from https://go.dev/dl/, or define their own package in their OS's package manager. I can't think of a scenario in which you're building your own main package, in your own module, where you control the |
Beta Was this translation helpful? Give feedback.
-
since go versions do not follow semver (see #32450), this may confuse people: (and I see why you used
do we have a way of saying I want the latest 1.80 toolchain or at least easily hiding this complexity from developers? |
Beta Was this translation helpful? Give feedback.
-
It's just one data point, but I've been living in something like this world for a while. I have a zsh |
Beta Was this translation helpful? Give feedback.
-
I can imagine that Tailscale might like the option to have |
Beta Was this translation helpful? Give feedback.
-
One minor downside to this is that it will increase Go cache pressure. If I'm working across a handful of projects, each with a slightly different toolchain, I'm going to end up with a lot more in my Go cache--not just N toolchains, but N different compiled versions of shared dependencies. |
Beta Was this translation helpful? Give feedback.
-
It might be worthwhile to be more explicit about the minor Go revision in the overall discussion (that is, the For example:
My understanding from the discussion is that would mean the latest minor Go revision? Or if we look at:
With regular modules, you can write |
Beta Was this translation helpful? Give feedback.
-
How does / how should this interact with (rare) security issues in the Go toolchain (e.g. https://go.dev/blog/path-security). Could a malicious package author specify an older toolchain that hasn't recieved the security update in order to achieve RCE in the build environment? |
Beta Was this translation helpful? Give feedback.
-
Why can't we download and run go1.88? |
Beta Was this translation helpful? Give feedback.
-
How does toolchain selection affect non-gc toolchains, like gccgo and TinyGo? |
Beta Was this translation helpful? Give feedback.
-
Interesting idea 👍 , a concern and an opinion
|
Beta Was this translation helpful? Give feedback.
-
It seems like this would make for a very poor DX on Alpine or other musl systems, where GOOS/GOARCH look like a supported pair but you can't use the official binary distributions. |
Beta Was this translation helpful? Give feedback.
-
If I'm still behind the times and using Go 1.113 while Go 1.119 has been released, would I be able to use |
Beta Was this translation helpful? Give feedback.
-
I think this would be helpful, mainly because we could make the My company currently has to keep the Go version we use for each application updated in several places:
We currently use a script to keep them all synchronized. It is just annoying enough that we don't always stay up-to-date. I wish we could just have all tools read the version in To prevent this from slowing down builds, it would be great if the |
Beta Was this translation helpful? Give feedback.
-
Automatically downloading binary distributions may run into issues similar to #43996 when people (for whatever reason) are stuck with an old OS. I encountered this recently when we upgraded Go to 1.16. We had to change the affected Docker image to build Go from source instead of downloading the prepackaged binaries. I understand that would still be possible and using |
Beta Was this translation helpful? Give feedback.
-
I like this proposal. I have thought that Go needed some form of "target" Go version in
|
Beta Was this translation helpful? Give feedback.
-
The features described here make some assumptions about the environment go is used in that don't match with some of my main use cases. I'll attempt to describe those here as data points for consideration. If implemented as currently described, I believe I would be using I build and deploy custom Linux images, using Yocto, for single board computers. Those images contain both go (currently version 1.15.8) and custom software written in Go (with some requiring Cgo). Yocto builds and/or version controls both the software used to build the image and the software built for the image. If multiple or different versions of go are required, it would need to be explicitly specified at a higher level than go.mod. While my experience is limited to Yocto, I assume this is a model shared by many Linux distro build systems. Once deployed, software written in Go can be built on those single board computers. This is mostly done for faster development iteration and for troubleshooting. Typically, those hosts are not connected to the Internet or even a network that might host something like Gitlab or a Go modules proxy. Vendoring is used to enable building on these hosts. Also, installing multiple versions of go would have a negative effect on image size. |
Beta Was this translation helpful? Give feedback.
-
One thing I haven't seen mentioned is how distributions / package maintainers are expected to handle this sort of thing; my understanding is that this proposal is effectively saying that Go will download prebuilt binaries, rather than using what was actually installed on the system. I think that's going to end up really surprising (or further, undesirable) to package maintainers, where the expectation is that users are getting the version of Go that they installed. It would be really surprising for someone like Ubuntu to backport a fix to their Go distribution, only to have Go completely ignore that and invoke some other, unpackaged version. Also, distros like Nix/NixOS cannot be used out-of-the-box with prebuilt binaries intended for a "regular" Linux system; trying to execute releases straight from golang.org is very likely to fail, because things like libc and such live in completely different locations. In the same vein, I'm aware of corporate environments which have their own custom "approved" Go distribution; those similarly would be harmed by logic that explicitly tries to invoke a toolchain that's not the installed one. Those environments already require some element of patching, so maybe it's not such a big deal to also set a different default, but it's another knob that upstream isn't expecting to be changed. |
Beta Was this translation helpful? Give feedback.
-
I am using Go for undergraduate teaching. In early September, I send an e-mail to our administrators asking them to install some recent version of the Go compiler ("Please install go-1.18 or later, you may verify by typing "go version"). I then make sure that the practicals work with the version of Go that was installed. The admins might occasionally upgrade the system in the middle of the semester, sometimes without warning me. At first sight, your proposal might cause a number of issues:
My use case is probably not unique. Please make sure to take it into account. |
Beta Was this translation helpful? Give feedback.
-
This discussion has been very helpful. Thanks everyone! |
Beta Was this translation helpful? Give feedback.
-
Personally, I'm uncomfortable with downloading the whole toolchain automatically on demand, just because some dependency happens to be using a newer version of go than me (which may or may not need new features; it just happens to declare the latest version in Also I might want my software built with 1.18, say, because I'm not ready for some upcoming potentially-breaking feature in 1.19. I do realise those cases are rare (async preemption is one that springs to mind). However, a mechanism like Right now, the go binary release tarballs (for Linux at least) have What I find myself doing is to untar, then rename the outer directory to (say) The difference between "go" doing this, and a separate "goup" or whatever, is about whether it takes place automatically. If "go" itself does auto-upgrades, then in the limit, you could turn "go" into a wrapper which knows nothing except how to download a published toolchain into your local package cache and invoke it. Minor point: on multi-user systems with N users and M different newer versions of go referenced from dependencies, you'll get NxM copies of the toolchain installed. But who uses timesharing these days? :-) |
Beta Was this translation helpful? Give feedback.
-
In support of the need for something like this proposal: we already see CI systems trying to overload the meaning of the go version in the go.mod file. In https://github.com/actions/setup-go, the baseline Go environment setup action for GitHub Actions, the It seems to me that this fits in neatly with the proposal: the action would:
So I am very much in favor of providing a directive guiding tooling to "which version of Go should be used", vs "which is the baseline set of semantics which should be used when compiling this code, which might be several releases behind the minimum supported because the code just isn't using anything new". None of the above requires the automatic download side of things, and I can see that running into compliance and regulatory issues for developer environments (even if automatic download of modules is somehow handwaved away already). So I'm neutral on the auto-download, but very firmly in favor of the directive itself (even if the default Go tools end up doing absolutely nothing with it). |
Beta Was this translation helpful? Give feedback.
-
This is now a proposal at #57001. |
Beta Was this translation helpful? Give feedback.
-
@rsc I can see what you're trying to do here, but this is really "over engineering". I've never seen build tools attempt to reach out to the internet as much as much as what you are proposing here and in the other controversial issue. Why do you feel the need to make If I want to use a new Go compiler version, I will just go upgrade to that new version. I really do not want Please keep Go simple. It's a great language. |
Beta Was this translation helpful? Give feedback.
-
I think this could go against (or at least have some "friction" with) a possible corporate practice and requirement to only install the Go toolchain from the internal mirrors (ex. corporate Artifactory). Please consider this scenario if/when moving to the implementation! 😄 As an aside - it would be fantastic to have a |
Beta Was this translation helpful? Give feedback.
-
This discussion is about forward compatibility, meaning old versions of Go compiling newer Go code. For the problem of new versions of Go compiling older Go code, see this other discussion about backward compatibility.
It seems strange to me that you can edit go.mod to update all the code your program depends on except Go itself. Suppose go.mod says
go 1.17
. Why shouldn't changing that togo 1.18
make the build use Go 1.18? I think probably it should. Here is how that might work.To start, let's get the machinery of an old go version downloading and running another out of the way. Assume that we publish all binary Go distributions as module zip files (in addition to the usual zip files and other installers). Then if the go command notices it needs a different distribution, it can download it from the module proxy and authenticate it using the checksum database. (We don't need to invent a separate authenticated download channel, nor a separate caching mechanism.) After unpacking the distribution somewhere appropriate (perhaps the module cache) and restoring the execute bits on the binaries, the old go command can reinvoke the newly downloaded go command. On a future invocation, the old go version finds the cached copy directly and runs it, no download required.
Having gotten the “is it possible?” out of the way, let's turn to what should cause that to happen. I am thinking about something along these lines:
By default, if the go line in go.mod in the current module or workspace lists a newer version of Go than the current one being run, the current one goes and gets the new one and reinvokes it. Supposing the change happens in Go 1.90, if you are running Go 1.90 and change your go.mod line to
go 1.92
, any go command likego build
orgo test
will download and reinvoke Go 1.92.On the other hand, if you change the line to
go 1.88
, Go 1.90 will keep running and will do its best to emulate Go 1.88. (See also this discussion about backward compatibility.)If you want more fine-grained control, a new go.mod line
toolchain
, which is only respected in the current module's go.mod (just likereplace
) sets a specific toolchain to use. For example you could do(with Go 1.90 still installed) to stay on Go 1.91 semantics but update to the Go 1.92.4 toolchain. Similarly, you can use toolchain to “downgrade”, as in:
(with Go 1.90 still installed).
The
toolchain
line overrides the version selection logic in step 1.To ensure accurate compilation of code written for newer Go versions, the go command would update the toolchain line when adding a dependency with a newer version of Go to maintain the invariant that the toolchain (or the implied toolchain from the go line) is at least as new as the go version required by any dependent module. Continuing the examples above, if you
go get
a new or updated dependency that has a go.mod that saysgo 1.100
, then the current module’s go.mod toolchain line would be updated totoolchain go1.100
, with a message to standard error about the update.For command-line control, setting
GOTOOLCHAIN
overrides any go.mod toolchain line, like:Using
GOTOOLCHAIN=local
would use the local toolchain, whatever its version (Go 1.90 in this running example).And
GOTOOLCHAIN=auto
would be an explicit name for what happens when there's no toolchain line, so that you could have the go.mod above and runto get Go 1.91.
No matter how the effective toolchain is specified, it would become a build error to attempt to use an old toolchain with new code. (Today the build proceeds and hopes for the best; in case of a compilation error, the go command prints a final note about the version mismatch as a possible explanation of the compilation error.)
This mechanism would make it a lot easier to try out new Go releases, as well as betas and release candidates: just edit your go.mod files and commit them, and everything building your code knows to use the new release. This is analogous to when you edit your go.mod to update a dependency version and everything building your code knows to use that newer version. (In contrast, what happens today with Go toolchain selection is very much like the old days of GOPATH, when each different build machine injects its own local state into the build.)
This mechanism would also help a lot with Go packagers that don't always keep up with Go releases, such as some Linux distributions and cloud providers that I wont name.
If we ever moved toward issuing more frequent ephemeral Go releases (like monthly alpha releases when the tree is open), this mechanism would make those easier to consume.
Finally, I started a separate discussion about a Go toolchain being able to emulate an older one’s runtime semantics based on the go.mod
go
line. And Go toolchains already emulate an older one’s language semantics based on each package’s go.mod’sgo
line. The mechanism discussed above is a nice complement to those, allowing a Go toolchain to emulate a newer one based on the go.modgo
line.This is a discussion, not a proposal. I haven't implemented all of this nor even worked out all the implications. I'm curious what people think and what concerns they have. Thanks!
Beta Was this translation helpful? Give feedback.
All reactions