Question

Background

In a software ecosystem where different packages depend on different versions of other packages, sometimes dependency resolution ends in a version conflict.

Example:

  • Root package A depends on B 1.* and C 1.*.
  • Package B depends on E 1.*.
  • Package C depends on E 2.*. -> conflict.

Maybe the developers of library C had a discussion about whether it is ok to depend on E 2.* instead of E 1.*, and how many people this would make angry.

Maybe the developers of library E also had a discussion about whether anyone would switch to their new branch E 2., which could break compatibility with older packages which still use E 1..

I have seen discussions around this kind of problem in the PHP community, for the Composer / Packagist ecosystem. But I assume similar discussions happen elsewhere.

Question

I have been wondering, why not release a new version under its own package name and namespace? This would allow different versions to coexist without conflict.

E.g. instead of E 1.* and E 2., there would be E1 1. and E2 1.*. The package name would not be "E" but "E1" / "E2". The namespace for classes would be ACME::E1::* or ACME::E::v1::*, instead of just ACME::E::*.

Or in PHP / Composer / Packagist ecosystem:

Instead of just symfony/symfony, there would be symfony/symfony1, symfony/symfony2 etc. And the namespace would be Symfony2\Component\Asset etc.

Or maybe the version number would apply on a lower level, e.g. Symfony\Component2\Asset.

So my question is: Is there any package ecosystem where this is common practice? Is there a good reason not to do this?

Was it helpful?

Solution

This depends a lot on the ecosystem, specifically:

  • Do library developers take backwards-compatibility seriously?
  • Can I install different versions of the same package side by side?

If the answer to either question is “yes”, there is no problem.

When backwards-compatibility is ensured, there is no conflict between the dependency on E v1.X and E v2.X. As E v2.X is backwards compatible to E v1.X, you can resolve the dependency to E v2.X and everything should work. This means that packages should not specify exact dependency versions or ranges of allowed versions, but only minimum required versions. This strategy is sufficient in stable, well-tested ecosystems like Debian/APT or CPAN.

When I can install and load multiple versions of the same package, two different versions do not conflict. The advantage is that all dependencies are isolated, and that precise versions can be pinned. The disadvantage is that dependencies cannot be shared between libraries which leads to duplication and bloat. Languages that assume a global package namespace generally have difficulty with this strategy. This strategy can also cope with unstable ecosystems where breaking changes are common (e.g. Ruby or JavaScript). Notable users of this strategy are NPM and Java/OSGi.

So what happens if there are breaking changes, but I can't install two versions for the same package?

The naming scheme you have suggested is not uncommon. Assuming that each project uses a SemVer-compatible version number like “Foo v1.X.0” and “Foo v2.Y.0”, the major version number that indicates breaking changes would become part of the package name, leading to packages like foo X.0 and foo2 Y.0. (If the major version is 1 that is essentially meaningless for the purpose of a package name, so it can be omitted.) Within each of foo and foo2 there would only be backwards-compatible changes, so the minimum-version strategy can be used.

In the Ubuntu repositories, there are multiple APT packages with such names, for example python vs. python3, or openjdk-8 vs. openjdk-9, or libgtk2.0-0 vs. libgtk-3-0. Most projects do not see changes on a scale that would make versioned package names necessary.

OTHER TIPS

One important question is whether packages B and C export objects with types from E. Then there may be a real conflict. If they use E only internally then the problem is only that they need different namespaces at compile time, so the build system must be able to handle this.

In the Java world, OSGi can cope with this. On the downside, the specification for OSGi Core alone is 80 pages long, more than twice as long as the definition of LISP (McCarthy, 1960). Whether you really want a dependency manager more complex than a programming language is up to you.

Licensed under: CC-BY-SA with attribution
scroll top