Hacker News new | ask | show | jobs
by stouset 665 days ago
> dependency graph spaghetti

The worst spaghetti comes from hard dependencies on minor versions and revisions.

I will die on the hill that you should only ever specify dependencies on “at least this major-minor (and optionally and rarely revision for a bugfix)” in whatever the syntax is for your preferred language. Excepting of course a known incompatibility with a specific version or range of versions, and/or developers who refuse to get on the semver bandwagon who should collectively be rounded up and yelled at.

In Rust, Cargo makes this super easy: “x.y.z” means “>= x.y.z, < (x+1).0.0”.

It’s fine to ship a generated lock file that locks everything to a fixed, known-good version of all your dependencies. But you should be able to trivially run an update that will bring everything to the latest minor and revision (and alert on newer major versions).

3 comments

There's a subtle point there though. When you rely on something that was introduced in x.y.z, stating that your version requirement is x.y.0 is an error that can easily cause downstream breakage.
I’m confused. If you rely on a feature introduced in X.y.z why would you specify X.y.0 to begin with (and not just X.y.z)?

In practice, usual rust projects that have not put a ton of work into their dependencies encode X.y.z in Cargo.toml matching the current release at the time they developed the system. So you get at worst an unnecessarily higher version requirement but never a lower one.

Moreover, rust semver would normally imply that new features should only be introduced in X.y releases, so this doesn’t really happen in practice!

It's easy to accidentally ship a minimum version requirement that is out of date when you also consistently use lock files pinned to newer versions. The code may silently depend on something introduced in a newer version pulled in by the lock file.
You can have a CI builder using direct-minimal-versions to check this.
Point releases are often bugfix releases, i.e. not api changes but runtime changes. CI won’t help without very specific accompanying tests.
I have literally never run into this being a problem in practice. If someone downstream ever did notice, they can just specify a higher minimum version constraint.
Just have CI build with the minimum and the maximum.
My point was not about "x.y.0" (I mispoke), but "x.y" (or "0.x") that causes this problem.

Take a look at any random crates's cargo config, and you'll regularly see dependencies specified as "1" or "0.3" instead of "1.0.119" or "0.3.39". If another crate depends on this crate, and has a more precise version needed (say "=1.0.100" (perhaps due to a bug introduced in "1.0.101", but the included library relies on features introduced in some version after "1.0.100", then your library won't compile. Stating your dependency on "1" instead of "1.0.119" is what caused this problem.

This is not hypothetical - I've run into this quite a few times with crates that over-specify their dependencies interacting with crates that under-specify theirs.

Yes, the solution to this is pretty simple - check minimal versions in CI. That's something I do for most of my stuff, but it's not universal.

> In Rust, Cargo makes this super easy: “x.y.z” means “>= x.y.z, < (x+1).0.0”.

With the added special case of `0.x.y` meaning `>= 0.x.y, < 0.(x+1).0`, going beyond what semver specifies.

Building against recent and old version and publishing ranged lockfiles should be mandatory.