Remix.run Logo
aidenn0 4 days ago

I think you missed the point of the article. Consider Application A, that depends on Library L1. Library L1 in turn depends on Library L2:

A -> L1 -> L2

They are saying that A should not need a lockfile because it should specify a single version of L1 in its dependencies (i.e. using an == version check in Python), which in turn should specify a single version of L2 (again with an == version check).

Obviously if everybody did this, then we wouldn't need lockfiles (which is what TFA says). The main downsides (which many comments here point out) are:

1. Transitive dependency conflicts would abound

2. Security updates are no longer in the hands of the app developers (in my above example, the developer of A1 is dependent on the developer of L1 whenever a security bug happens in L2).

3. When you update a direct dependency, your transitive dependencies may all change, making what you that was a small change into a big change.

(FWIW, I put these in order of importance to me; I find #3 to be a nothingburger, since I've hardly ever updated a direct dependency without it increasing the minimum dependency of at least one of its dependencies).

yawaramin 4 days ago | parent | next [-]

> Transitive dependency conflicts would abound

They would be resolved by just picking the version 'closest to root', as explained in the article.

> Security updates are no longer in the hands of the app developers

It is, the app developers can just put in a direct dependency on the fixed version of L2. As mentioned earlier, this is the version that will be resolved for the project.

> When you update a direct dependency, your transitive dependencies may all change, making what you that was a small change into a big change.

This is the same even if you use a lockfile system. When you update dependencies you are explicitly updating the lockfile as well, so a bunch of transitive dependencies can change.

crote 4 days ago | parent [-]

> They would be resolved by just picking the version 'closest to root', as explained in the article.

Which is going to lead to horrible issues when that library isn't compatible with all your other dependencies. What if your app directly depends on both L1 and L2, but L1 is compatible with L3 1.2 ... 1.5 while L2 is compatible with L3 1.4 ... 1.7? A general "stick to latest" policy would have L1: "L3==1.5", L2: "L3==1.7" (which breaks L1 if L2 wins). A general "stick to oldest compatible" policy would have L1: "L3==1.2", L2: "L3==1.4" (which breaks L2 if L1 wins).

The obvious solution would be to use L3 1.4 ... 1.5 - but that will never happen without the app developer manually inspecting the transitive dependencies and hardcoding the solution - in essence reinventing the lock file.

> It is, the app developers can just put in a direct dependency on the fixed version of L2. As mentioned earlier, this is the version that will be resolved for the project.

And how is that going to work out in practice? Is that direct dependency supposed to sit in your root-level spec file forever? Will there be a special section for all the "I don't really care about this, but we need to manually override it for now" dependencies? Are you going to have to manually specify and bump it until the end of time because you are at risk of your tooling pulling in the vulnerable version? Is there going to be tooling which automatically inspects your dependencies and tells you when it is safe to drop?

> This is the same even if you use a lockfile system. When you update dependencies you are explicitly updating the lockfile as well, so a bunch of transitive dependencies can change.

The difference is that in the lockfile world any changes to transitive dependencies are well-reasoned. If every package specifies a compatibility range for its dependencies, the dependency management system can be reasonably sure that any successful resolution will not lead to issues and that you are getting the newest package versions possible.

With a "closest-to-root" approach, all bets are off. A seemingly-trivial change in your direct dependencies can lead to a transitive dependency completely breaking your entire application, or to a horribly outdated library getting pulled in. Moreover, you might not even be aware that this is happening. After all, if you were keeping track of the specific versions of every single transitive dependency, you'd essentially be storing a lockfile - and that's what you were trying to avoid...

hosh 4 days ago | parent | prev [-]

Is the article also suggesting that if there are version conflicts, it goes with the top level library? For example, if we want to use a secure version of L2, it would be specified at A, ignoring the version specified by L1?

Or maybe I misread the article and it did not say that.

aidenn0 4 days ago | parent [-]

It's maybe implied since Maven lets you do that (actually it uses the shallowest dependency, with the one listed first winning ties), but the thrust of the article seems to be roughly: "OMGWTFBBQ we can't use L2 with 0.7.9 if L1 was only tested with 0.7.9!" so I don't know how the author feels about that.

[edit]

The author confirmed that they are assuming Maven's rules and added it to the bottom of their post.