I think one thing that’s being missed in this whole conversation is that there are only two stable states for a package ecosystem:
- No versions have upper bounds
- Almost all versions have upper bounds
Specifically, if older versions of packages have higher upper bounds—or no upper bounds—then things are going to be very painful. That’s precisely what is causing problems right now. That’s because we’re in the process of transitioning from the former stable state to the latter stable state.
Why is being half way so problematic? When older versions of some packages have no upper bounds but the latest versions have upper bounds, it causes the resolver to go nuts and pick old broken versions that only appear viable because someone lied in the past by claiming that that version would work forever. It’s not the upper bounds that cause the problem, it’s the old lies about being compatible with everything forever that cause issues.
Different ecosystems have chosen between these two stable states in different ways. For example, Go chooses the former approach: upper bounds are not allowed except for new major releases, which are treated like entirely new packages. However that comes along with some serious discipline: Go packages are not allowed to make incompatible changes at all, except in major releases. Rust, on the other hand, takes the latter approach: all registered versions must have semantically plausible upper bounds.
So the question is, which model do we want for Julia? Frankly, I don’t think that flat out disallowing breaking changes is going to fly in our community, although I’d be willing to try. That means if you make a release that breaks your dependers, we won’t let you register it except as a new major release. Doesn’t seem pleasant or feasible to me. I just don’t think the Julia community would accept that kind of strict policy.
Assuming that we allow people to make releases as they wish and react when things break by putting upper bounds even if they aren’t always at major release numbers (rather than forcing people to retract those releases), that implies that we’re going to have some upper bounds. Which in turn implies that we’re in the latter camp, with Rust (excellent company, btw) and we need upper bounds on most versions.
The most straightforward way to make sure that older versions have upper bounds is to require plausible upper bounds at registration time and adjust as appropriate. Why default to capping at the next semantically breaking version? Because that’s the best guess we can make in advance. If all I know is that I’m fine with using version 1.2.3 of a dependency and that 1.2.4 has just been released, I’d be surprised (and annoyed) if I can’t use that too. I would also expect 1.3 to probably work with no or minimal changes (knowing nothing else). If someone releases 2.0 on the other hand, I’d be surprised if something didn’t break. Pleasantly surprised, but surprised nonetheless. And then my next question would be “if nothing broke, was it really necessary to make a major release and cause all this extra work?” And then I’d go check out the release notes and see what changed. And if it was one or two obscure but technically breaking changes, I’d be irritated.
Will using semantic breaking as a guess be perfect? Of course not. Some point releases will actually break things and we’ll have to add caps after the fact (or yank them if it’s bad enough). Some minor releases will introduce new exports which collide with some other export in a package that uses using
instead of explicit imports and that will need a cap even though the change is allowable. Some major versions will get released and not break as much as expected. Lucky us! We can bump the bounds. I don’t really get where the panic is coming from. We’ve been dealing with this kind of thing for years and it’s been fine. As Tamas has said, if there are problems, we’ll do what needs to be done to make things work well. God knowns we go to serious lengths to make things work.
I also think it’s interesting and worth noting that the people who have been thinking about and working on package management, registries, versioning and CI for the past however many years (about seven in my case) are all roughly in agreement about how this needs to work. Meanwhile those who are arguing against that approach seem to have just become aware of the issue this week and be extrapolating doom based on problems that we’ve known we were going to have to deal with at some point for over a year now when we transition from the “no bounds yolo” model to the more mature “plausible bounds” model.