Does (or doesn't) the NuGet client respect Semantic Versioning when updating packages?

Topics: General
Jan 23, 2015 at 3:07 AM
Sorry in advance for the length of this thing, but I cannot find a more succinct way to convey the details of an issue which has been bedeviling me for nearly two weeks now :-/

I am one of the maintainers of the Common.Logging project ( Common.Logging is distributed as a suite of packages upon which significant numbers of other packages depend; the main package ( reports > 680K downloads in the aggregate to-date.

Since version 1.2 of the project (up through version 2.3.1), we had been able to advance the code-base while maintaining 100% binary reverse-compatibility (enabling binding redirects to suffice for in-place updates of any arbitrary subet of the assemblies). We'd not (historically) followed Semantic Versioning for Common.Logging because its a project which pre-dates both NuGet and the concept of SemVer having become more broadly accepted in many circles.

However, we recently released a new version of the project, which introduced a breaking change (rendering binding redirects insufficient on their face to resolve references to Common.Logging and it suite of assemblies).

In keeping with the rules of Semantic Versioning, when introducing this breaking change we rev-ed the version from its prior 2.3.1 to 3.0.0 to signal to consumers of Common.Logging (both human and machine) that a breaking change had been introduced.

We quickly began to receive reports from projects like Quartz.NET ( and JSNLog ( which depend upon Common.Logging that users of their projects were experiencing a mix of compile-time and run-time errors after the NuGet client updated their dependency on Common.Logging 2.3.1 to the incompatible Common.Logging 3.0.0.

We'd not expected this outcome because we'd followed the rules of Semantic Versioning precisely to avoid introducing this 'silent update breaks my project' problem.

(Invalid?) Assumption: NuGet Supports Semantic Versioning
Based on references to Semantic Versioning on this doc page I'd always assumed that NuGet was stating there that it supported Semantic Versioning of packages. However, as we've begun to discover from the several threads mentioned above, that seems empirically to not be the case.

Instead, I'm guessing that I misunderstood the section of the docs referencing Semantic Versioning as meaning support for the entirety of Semantic Versioning when in fact I now suspect the intent was to state a more narrow adoption of only the part of Semantic Versioning that describes that the arbitrary text suffix on a version number connotes a pre-release state for the package. [SIDE NOTE: would be great if the docs could be clearer on this -- the URL on that page to the entirety of the SemVer 2.0 spec is at best misleading IMO]

Investigations and Conclusions
This started me down a further rabbit hole of trying to understand possible corrective actions that could be taken to try to avoid this in the future. You can see most of my exploratory thought process in this issue but I want to try to summarize it here because I'd like to get the following in a (semi-) official capacity if at all possible:
  1. Is my impression that NuGet does not support SemVer correct?
  2. Are my suggestions as to corrective actions package authors should consider taking when dealing with SemVer-versioned packages a recommended way to address this?
  3. Could we begin thinking about a 'smarter' way for NuGet to deal with packages that support SemVer so as to avoid a repeat of this experience for consumers of Common.Logging if/when it needs to rev to version 4.0?
In the 'Guidance' section of the recommendation is offered...
Generally, the guidance in most cases is to only specify a lower bound, and leave the upper bound open.
Setting aside for a moment the prudence of stating a dependency on something that might introduce an unpredictable breaking change at any moment :) let's assume this is reasonable guidance for packages that do not adhere to SemVer. But as our own experiences have demonstrated, this recommendation seems entirely incorrect on its face when referencing packages that do follow SemVer rules.

Projects like Quartz.NET got into trouble with our 3.0.0 release because they had stated their dependency on us as...
<dependency id="Common.Logging" version="2.3.1" />
...which, using the Maven-based interpretation of "=" employed by NuGet of course translates to "any package version 2.3.1 or greater". This is a fundamentally incorrect mechanism for referencing a package that supports SemVer because it permits an update to 3.0, 4.0, and so on which by definition will contain breaking/incompatible changes (e.g., its not a case of if, its a given that an incremented MAJOR version number will contain breaking changes).

Had e.g., Quartz.NET stated their dependency as ...
<dependency id="Common.Logging" version="2.3.1,3)" />
...they (and others) would have been unaffected by our 3.0.0 release because this is interpreted as "version 2.3.1 or greater but not version 3.0 or later".

As you can see from the threads referenced elsewhere in this posting, this is in fact what we eventually ended up recommending to package authors that consume Common.Logging -- that they place an upper-bound on the version number of their dependency on our packages to 'protect' themselves from NuGet updating to future versions that would introduce still more breaking changes (e.g., 4.x, 5.x).

Responsibility seems in the wrong place
Unfortunately, this corrective action has to be initiated by consumers of our packages rather than ourselves. This creates a rather unwieldy downstream set of required actions on the part of other package authors to 'protect' their own packages from our breaking changes.

Even if my experimentation and the reports of others referenced here are correct that NuGet does not presently respect SemVer during package dependency resolution, I'd like to propose that consideration be given to making it do so in the future in a way that package authors that desire to support SemVer for their own packages be able to do so (rather than asking downstream consumers to take the action).

I'm proposing adding an additional node to the .nuspec file that would permit package authors to effectively signal "this package is Semantically Versioned". When the NuGet client inspects the .nuspec file to determine acceptable updates for dependencies on such packages, it could properly apply SemVer rules and not update to what it would then know would be a breaking change.

If we were to add the following to the .nuspec file of Common.Logging packages...
<packageSemanticallyVersioned="true" />
...then the NuGet client could properly interpret a dependency such as this in the consuming pacakage (e.g., Quartz.NET)...
<dependency id="Common.Logging" version="2.3.1" /> if it had been written instead as ...
<dependency id="Common.Logging" version="2.3.1,3)" />
...which was the intent of the package author (e.g., Common.Logging) when following SemVer for their package.

In this manner, having your package respect SemVer could be an 'opt-in' for package authors and the NuGet client would be able to be significantly more intelligent in re: helping to ensure that an errant package update doesn't result in broken code for the consumers of packages.

Thoughts on this situation, my proposed manner of dealing with it for the time being, and my proposed way to think about enhancing NuGet's dependency-resolution behavior would be very much appreciated--!

-Steve B.
Jan 31, 2015 at 3:34 PM
Hi Steve!

Disclaimer: I hope I haven't missed anything you stated before coming up with my answer.

Back in 2011, David Ebbo stated:
In most cases, simply specifying a minimum version is the way to go, [...]. This does not imply that upper bounds shouldn't be specified in some cases. In fact, an upper bound should be specified whenever a component is known not to work past a certain version of a dependency.
From this and what I have seen in various blog posts about NuGet as well as discussions, the main guideline is that you should never automatically upgrade a version, i.e. a version upgrade should happen inside your dev environment and you run your tests against upgraded version before committing anything.

The version ranges are mainly intended to guard against known incompatibility, i.e. you release a new version of the consuming package with a version constraint once you know it won't work with the newer dependency. Personally, I'm not wholly convinced of this after-the-fact system, because it leaves the package consumers open for just such an occurance as you have gotten now. On the other hand, if you do lock your package down, you will need to release new versions with a revised boundary if you are actually compatible with the next major version as well.

Anyhow, am I reading your post correctly, that Maven is automatically updating dependencies?

Best regards, Michael
Jan 31, 2015 at 8:39 PM
Edited Jan 31, 2015 at 8:41 PM

As you mentioned, a package which uses version="2.3.1" on a dependency specifies they are are allowing versions 2.3.1 or higher of that dependency.
Yet, this does not mean NuGet will get the latest version of that dependency when installing the package.

The Package-Conventions page links us to Version Selection Algorithm of NuGet.
That other page notes that "(NuGet) always pick the lowest version of a dependency that fits in the range", and shows an example of that.

I have tested this by creating a dummy project which uses version 2.3.1 of Common.Logging.
The dependency in the nuspec file of my dummy package looks like that:
<dependency id="Common.Logging" version="2.3.1" />
The result when installing my dummy package was that Common.Logging 2.3.1 was installed, not 3.0.0.

As far as I can see it, if Quartz.NET and others used version="2.3.1" and they are suffering from this problem, one of the following is the cause:
(a) They have an other project / dependency which requires Common.Logging v3.0.0
(b) They are using an old NuGet (older than 2.5).
(c) They used the Update command
Feb 1, 2015 at 8:52 PM

Thanks for the input. I've concluded in this update to the thread that started all this that as you suggest NuGet is actually behaving "as designed". However, unfortunately in this case its design is such that it leaves package adopters entirely open to introducing breaking changes during almost any 'update' operations :(

During installation of the packages, the .nuspec files are interrogated to determine both the necessary dependencies for each package and their acceptable version ranges (both upper and lower bounds). The NuGet client uses this info to perform the install/add action for each package (much as you'd expect it to). During this process, constraints specified in the .nuspec file(s) are properly respected as you'd expect.

However, after each package is initially installed the NuGet client effectively 'collapses' all package dependency hierarchies into a single 'flat' collection of dependencies and records this in the packages.config file.

When a user proceeds to update their packages, NuGet appears to be reading only the packages.config contents to determine which packages to update. Because packages.config only contains a list of each package and its current version (e.g., not any info re: upper-bounds of packages, any of the dependency graph between packages, etc.), the NuGet client performs what I can only describe as a 'blind' update -- despite the constraints it needs to update 'smartly' being in the .nuspec files inside the packages that it already has locally on disk, NuGet apparently disregards this additional meta-data and instead updates all packages, regardless of their version constraints specified by the package author.

This means that as a package author, I can control/influence the initial installation of my package (and its dependencies) but after that "all bets are off" and users are "on their own" to try to puzzle-out reconciling conflicting versions of assemblies/packages. Even were I to recommend that consumers of Common.Logging specify something like this...
<dependency id="Common.Logging" version="2.3.1,3)" />
...which would indicate a clear "don't accept a version of this >= 3.0", NuGet would only respect those bounds during an installation, not any subsequent update operations.

Disappointingly, this means that any update operation a user invokes is entirely likely to introduce breaking changes even if package authors bother to express version ranges for their dependencies. This choice to respect dependency version ranges on install but not update (IMO) makes NuGet valuable as a package-fetcher but unnecessarily hinders its value as a package-manager (the manager aspect being something that helps me during the entire life of my project rather than just one-and-done as is essentially the only safe use-case today).

Even though NuGet could protect users from this dependency-update-version-conflict-hell, it presently offers no such affordance, so my conclusion is that the answer to users who bump into this probably has to be...
"I recommend against using the NuGet udpate process to manage your packages. Given the constraints on how the NuGet client behaves today, you should really perform a two step remove-and-re-add dance in order to rev your dependencies".
Unless, of course, I'm missing something in my analysis and my conclusions here based on what I'm seeing in my testing and users are reporting to me are wrong...!
Feb 1, 2015 at 8:56 PM

Yes, as you can see from my other reply to @MichaelKetting, its the UPDATE scenario that's the source of this set of problem behaviors (or lack of behaviors!) in the NuGet client experience.

TBH, based on the number of consumers of Common.Logging who reported "unexpected" breaking changes from their applying the update to that package, I don't think its the case that the vast majority of NuGet users have any idea that UPDATE processes only the packages.config and disregards version constraints as specified by package authors...I know that as a package author I had no idea until these past few weeks forced me to become better-aware of this limitation :-/
Feb 3, 2015 at 6:15 PM
@sbohlen: That's...weird. I could have sworn that I read somewhere in the NuGet docs that an update would consider dependencies. But it is possible, that this is only in regards to indirect dependencies, i.e. if you update Quartz and it requires Common.Logging >= 3.0 and you have a second component that requires Common.Logging < 3.0, there would be an error.

But yes, if a user decides to just update Common.Logging, then this could possibly overrule package safety. Up till now, I thought it would require an explicit consent to do this unsafe operation, but NuGet would allow it.

I guess, I'll have to go back to the documentation and set up a nice playground to actually try this stuff for myself to get a similarly deep understanding of the pitfalls as you have gotten now.

Erm...decided to do that a little bit right now and found that transitive updates should be safe according to this blogpost:

Just to make sure: Are you using an up-to-date version of NuGet or something older, e.g. pre-2.5 as mentioned by fourwheels4fun?

Best regards, Michael
Feb 3, 2015 at 6:31 PM
Per that thread mentioned on the Common.Logging issues list in my initial post, the user reporting the issue was was on NuGet 2.8 AFAIK.

Please do let me know what you find; its (obviously) important to properly understand both the intended and the actual behavior (assuming they in fact diverge).

FWIW, part of the issue may be that half the "docs" for NuGet are on the codeplex site and half of them are scattered over blog posts, etc. that aren't (seemingly) linked to from the docs....

Thanks, let me know what you find/confirm!

-Steve B.