9

Some context: I'm working on a team which produces 6 different NuGet packages, which depend directly, and sometimes indirectly, on each other. Simply, we could have a situation like p0 < p1 < p2, where package p0 depends on p1, which in turn depends on p2. We're trying to follow Semantic Versioning for these packages, but aren't quite sure what to do with p0's version when p1 or p2 changes.

Here are some concrete examples:

  • p1 makes a breaking change (say 0.0.01.0.0). We want to publish a version of p0 which depends on 1.0.0. Should this be a major or minor version bump for p0?
  • p1 makes a minor changes (0.0.00.1.0). Should this make a minor / patch version bump for p0?

More importantly,

Is there any standard / consensus on how dependency version changes should affect package version changes?

Daniel Miller
  • 287
  • 4
  • 12
  • If all the packages are dependent on each-other, why not roll them all into a single package? What's the case for maintaining them all as separate libraries? – Scuba Steve Nov 27 '18 at 18:36
  • 1
    There are many reasons for decoupling logic into separate libraries: 1) You can consume them independently (e.g. if some are .NET Standard 1.3 and some are .NET Standard 2.0, this is useful), 2) the underlying logic is clearly separated. – Daniel Miller Nov 27 '18 at 22:36
  • 1
    Semantic Versioning FAQ [What should I do if I update my own dependencies without changing the public API?](https://semver.org/#what-should-i-do-if-i-update-my-own-dependencies-without-changing-the-public-api) – Randall Whitman Dec 29 '22 at 17:28

2 Answers2

8

Semantic versioning is all about what the change means to the users of that library. So if the change in p1 doesn't cause a breaking change in p0 then I don't see why it would require a major version bump in p0.

Basically, use whatever versions of dependent libraries work (remember that your users could be overriding them with different versions too, based on your dependency rules!), your versioning should only reflect what will affect your users.

Warning, opinion based content ahead

In my opinion, there's a fair bit of "caveat emptor" if you are directly using a transitive dependency (ie, using p1 but only listing p0 as a dependency) as a client of a library. This goes double when you have a lot of dependencies (as in most NPM packages) that go many, many layers down. I don't expect the authors of those libraries to keep track of all version changes of all transitive dependencies so I know to check.

BradleyDotNET
  • 60,462
  • 10
  • 96
  • 117
  • 1
    What if the user is using both `p0` and `p1`, but only list `p0` as a dependency? They have the expectation that a upgrading to a new minor version of `p0` will not break them, but it does because it brings in a breaking change of `p1` that does affect them. Is the answer to this that everyone should only write code that uses packages they list as dependencies in their project and not use transitive dependencies directly in their own code? That sounds like a good idea to me, but I've never seen a static code analyser that warns when this happens, which makes me think it's not common. – zivkan Nov 27 '18 at 23:18
  • @Ziv Using transitive dependencies directly is a pretty risky thing to do (though we certainly do it, especially with framework code, all the time). NPM will even warn you of such a thing (through the "peer dependency" concept). I'm just of the opinion that a breaking change in someone else's code does not mean a breaking change in yours. – BradleyDotNET Nov 27 '18 at 23:44
  • In principal I agree, and I wish there was a FXCop rule for it for the .NET ecosystem (maybe there is and I don't know about it). However, in practise it doesn't work that way in .NET (original question tagged C# and NuGet). If I have a reference on xunit, it brings in several packages, one has the assert methods, another has attributes, I can't remember where the output helper is defined. Another example is Microsoft.AspNetCore.App. A web app would use types from dependency injection, settings/options, kestrel, MVC and other packages, without directly referencing those packages. – zivkan Nov 27 '18 at 23:55
  • 1
    By the way, I hope my messages don't come off as arguing. I think it's a very interesting topic worth discussing. My interpretation of the original question included the scenarios from my first comment, so I think your answer would be improved by addressing it. – zivkan Nov 27 '18 at 23:57
  • 1
    It's fair to note that Microsoft.AspNetCore.App is a "metapackage" that's explicitly designed to be "all these other packages, as one reference". I didn't realize you thought I should expound on this in the answer so I'll add it, and I didn't think you were being argumentative :) @Ziv – BradleyDotNET Nov 28 '18 at 00:25
4

In no way suggesting that @BradleyDotNET's excellent answer is insufficient. I just want to add my take on this.

p1 makes a breaking change (say 0.0.0 → 1.0.0). We want to publish a version of p0 which depends on 1.0.0. Should this be a major or minor version bump for p0?

Yes, you are introducing a breaking change to users of p0.

p1 makes a minor changes (0.0.0 → 0.1.0). Should this make a minor / patch version bump for p0?

If the additional interface from p1 is exposed in the p0 interface, then minor. If the dependency is at the implementation layer, then patch. The real question here is why are you taking this dependency change? If it's to add an interface to the p0 public surface, then it's a minor bump for p0, otherwise it's a patch level bump.

One more thing to consider here is what exactly you are applying version numbers to. Packages, libraries and interfaces are all different things and they can all be version independently. As BradleyDotNET points out, some packages contain collections of independently versioned artifacts. You may want to consider modifying your strategy such that your interfaces can have stable versions, despite frequent repackaging.

jwdonahue
  • 6,199
  • 2
  • 21
  • 43