9 ms·
Go.sum is not a lockfile
- anishgupta 9mo agonice, if a module like github.com/foo/bar v1.2.3 is later modified or compromised upstream, the checksum in go.sum will cause the build to fail unless the exact same content is fetched.
- peterldowns 9mo agoCorrect, but it's been ages and the default actions/setup-go github action still uses go.sum instead. I see that someone already commented on the longstanding issue to reference this post, and that there is some hope that they'll update it! https://github.com/actions/setup-go/issues/478 https://github.com/actions/setup-go/issues/478
- Groxx 9mo agoThe private repo mention in the comments there is kinda a good one, unfortunately. If someone runs a private gosum/goproxy (relatively common) and amends a tag (hopefully very uncommon but I have personally seen it happen at least three times) then the cache could be wrong because go.mod didn't have to change. Which is Bad™ but it depends on what edge cases they want to handle automatically, vs optimization for the majority case. For well-behaving/stable/consistent setups I fully agree though, go.mod is both sufficient and better, and those other cases can probably just key off both instead. I think I've seen go.mod to change without go.sum changes (change an unused transitive dependency into a direct dependency), which can lead to your build needing something that wasn't cached because it was pruned in the previous version.
- saghm 9mo agoFrom a relatively naive outside perspective, it sounds like this would be pretty much only ever a self-inflicted probably (where "self" might be an org rather than an individual). What you're describing sounds almost like a force push to a private repo; if you're doing that, you might break things for anyone using it, so the risks should probably rest with you for asking so. "This breaks my setup if I modify history in a way that's expected to be immutable" isn't a super compelling argument for everyone behaving well to have to continue dealing with a suboptimal status quo.
- Groxx 9mo agoIt's basically a force-push, yeah. Which does happen, like with git, so you do kinda need a way to deal with it even if it's not automatic. Which it sounds like they have (planned), which seems like a good improvement.
- majewsky 9mo agoAt this point I'm just assuming that all of the official actions under https://github.com/actions https://github.com/actions are unmaintained garbage until proven otherwise, given that high-impact issues like this can sit for years without as much as an initial triage: https://github.com/actions/delete-package-versions/issues/162 https://github.com/actions/delete-package-versions/issues/16...
- Groxx 9mo ago>If the main module imports example.com/mod1/pkg1 and a separate example.com/mod1/pkg2 imports example.com/mod2, there is no way for example.com/mod2 to affect the build or run code on the developer’s machine, so you don’t need to consider it a dependency. iirc (I do not have a test setup at the moment to verify) it does affect your dependency resolution, and therefore your build, though its code does not exist in your binary. I know this because those AWS / K8S / Google Cloud libraries cause MASSIVE problems with their constant breaking changes without major version changes, and their importing other libraries that also have frequent breaking changes without major version changes, even if the dependency those unused subpackages include (and therefore raise the minimum version) is only used by some other module that needs a lower version (iirc). It's quite a headache sometimes, and could be rather easily solved if you could set upper bounds and not just lower. Or if those giga-projects would stop doing such obviously bad things. The version-affecting behavior is kinda unavoidable afaict. If they didn't include those unused version constraints, `go build ...` or just importing a new package within existing modules could cause your build to fail, forcing you to rerun version resolution. That'd probably just lead people to feel like "go is broken UGH", and go leans awfully hard towards avoiding that kind of thing. Mostly for the better, but not quite always / not in all ways.
- nasretdinov 9mo agoThese libraries are honestly so bad that whenever I have to interact with them I just split those dependencies into a separate binary :)
- supriyo-biswas 9mo agoI just write my own, small, focused client for these AWS services since the SDKs are generally just so unergonomic. With vibe coding becoming a thing it's become even easier to do that.
- kbolino 9mo agoYou may already know these things, but for others who may not: the AWS SDK for Go suffers from shockingly bad discoverability. For example, take the S3 library, github.com/aws/aws-sdk-go-v2/service/s3. If you have an s3.Client and you look at e.g. the ListObjectsV2 method, you might have no idea that there is a ListObjectsV2Paginator which makes it much easier to use, because nowhere in the method docs is it mentioned. Indeed, most operations that paginate have more ergonomic paginators, but none of them tell you this. But that isn't even the worst of it. Say you want to download or upload a file to S3. If you haven't worked with AWS for other languages, you might think that you just do GetObject and PutObject. And yes, for small files, that's generally fine. But for large files you want to use resumable downloads and multipart uploads. So you look and lo, there is no simple way to do this in the AWS SDK for Go. But actually, there is! It's in a totally unrelated and unlinked package, called github.com/aws/aws-sdk-go-v2/feature/s3/manager. Now you're getting some religion, so you ask "what are the other so-called 'feature' packages?" and you try to browse pkg.go.dev at the github.com/aws/aws-sdk-go-v2/feature level but nope, that's not a Go module so there's nothing to see there. In fact, you can't even browse the other s3 features, never mind find out what other services have features. Fortunately, you can browse their GitHub repo at least: https://github.com/aws/aws-sdk-go-v2/tree/main/feature https://github.com/aws/aws-sdk-go-v2/tree/main/feature It's quite clear that they use poorly thought-out cross-language codegen for this, which partly explains the lack of ergonomics, but also shows that they don't much care whether you use their stuff properly.
- djha-skin 9mo ago> Instead, just look at go.mod. It lists the precise version at which all dependencies are built. No, it does not. Minimum version selection means that the libraries will at least be that version, but it could be substituted for a later version if a transient dependency asks for such. That I'm reading this blog post at all suggests there is a "market" for a single checksum/version manifest, which data is currently housed in go.sum . This is sad, but, Hyrum's Law and all that.
- deleted 9mo ago[deleted]
- LukeShu 9mo agoThe correct answer is `go mod vendor && cat vendor/modules.txt`
- saghm 9mo agoJust to clarify, this will download the entirety of all of the dependencies in order to find out what versions are resolved? And then those versions aren't actually locked unless you keep the vendored dependencies around indefinitely? My understanding is that the point of a lockflle is that you don't need to do that.
- deleted 9mo ago[deleted]
- ncruces 9mo ago> No, it does not. Minimum version selection means that the libraries will at least be that version, but it could be substituted for a later version if a transient dependency asks for such. No? All dependencies - direct and indirect - are listed in your go.mod. Your module - as is - depends on nothing else. And those exact versions will be used to build it, if yours is the main module. If your module is used as a dependency of another module, then yes, your module may be built with a newer version of those dependencies. But that version will be listed in that module's go.mod. There's no way to use different versions without them being listed in some go.mod. go.sum to only maps between versions and hashes, and may contain hashes for multiple versions of modules.
- wereHamster 9mo agoA lock file, in my world, contains a cryptographic hash of dependencies. go.mod does not, it only lists tags, which are (in git) movable references. If go.sum has "no observable effect on builds", you don't know what you're building and go can download and run unverified code. I'm not a go developer and must be misunderstanding something...
- PunchyHamster 9mo ago> I'm not a go developer and must be misunderstanding something... I think it's coz not EVERY language's lockfile comes with checksum So, Go's go.mod is functionally equivalent Ruby Gem lockfile (that doesn't have checksum) but need to get go.sum to be equivalent to npm's (that does come with checksum) Author just compared it to languages where lockfile means just version lock
- wereHamster 9mo agoNow I understand :) thanks for clarifying
- inglor 9mo agoYou are not misunderstanding anything, I use Go and Rust/TypeScript in my daily work and you are correct - it is the OP that does not understand why people use lockfiles in CI (to prevent minor updates and changes in upstream through verifying a hash signature).
- alias_neo 9mo agoI would hazard a guess that the (former) head of the Go security team at Google (OP) _does_ in fact understand.
- kibwen 9mo agoThey may be an expert in Go, but from their writing they appear to be misunderstanding (or at least misrepresenting) how things work in other languages. See the previous discussion here: https://lobste.rs/s/exv2eq/go_sum_is_not_lockfile https://lobste.rs/s/exv2eq/go_sum_is_not_lockfile
- markkitti 9mo agoThe pyproject.toml, package.json, and Cargo.toml are declarative project configuration files. While the Rust community refers to Cargo.toml as a manifest, it is not a comprehensive and detailed list of a build. That is the lock file. While go.mod does not allow for explicit version ranges, the versions given are the minimum versions. In other words, the versions given are the lower bound of the compatibility range. Go also strictly follows semantic versioning. Thus the implicit exclusive upper bound is the next major version. This assumes that all minor and patch releases are backwards compatibile and not breaking. Dependency resolution in Go uses minimum version selection. That means the minimum requirements of all dependencies are evaluated and highest minimums are selected. In principle, this minimum version selection should be time invariant since the oldest versions of the compatible dependencies are used While the minimum versions specified in go.mod are not necessarily the version of the dependencies used, they can be resolved to the versions used irrespective of time or later versions of dependencies being released. Other languages do not use minimum version selection. Their package resolution often tries to retrieve the latest compatible dependency. Thus a lock file is needed. Python packages in particular do not follow semantic versioning. Thus ranges are critical in a pyproject.toml. In summary, the "manifests" files that the original author describes are configuration files. In some languages, or more accurately their package management schemes, they can also be lock files, true manifests, due to version semantics. If those semantics are absent, then lock files are necessary for compatibility.
- brabel 9mo agoThat’s very interesting. Most systems I know would pick the highest versions allowed by the ranges. In maven and gradle, for example, at least by default they choose the highest versions allowed. Even if no version range is used, it picks the highest choice even across major versions, which I always thought was completely broken. What does go do if you have two transitive dependency versions whose allowed major is different?
- Athas 9mo agoIn some sense, Go does not allow you to change the major version. Packages with the same name but different major versions are treated as different packages.
- virajk_31 9mo agoI am today years old to find this...
- dmarwicke 9mo agonpm's package.json and package-lock.json get out of sync constantly on my team. at least go only has one file to mess up
- abejfehr 9mo agohow is this possible? isn't everyone using the same node version? and checking in lockfile changes
- pie_flavor 9mo agoTwo very similar things are presented as though they are different (go.mod and lockfiles, not go.sum) for the purpose of sneering at one of them, when both are essentially the same. 'Ignored by downstream dependents' is not any less true of go.mod than of lockfiles. In both cases a later version can be demanded, overriding the earlier version, potentially breaking your code.