The Case of the Old NuGet Dependency Installation

Detective Fritz

I recently got an email from a friend looking for some help with a NuGet package installation issue. It seems that with the Visual Studio 2015 version of NuGet, the wrong version of dependencies the package author is getting installed.  This is related to a policy decision about NuGet to make it’s default behavior more conservative about which versions of packages it would install.  In this post, we’ll walk through troubleshooting this package and discuss what the author can do to improve their consumers experience as well as how the package consumer can use NuGet to install the dependency versions they want.

The Problem

Cool Packages Hierarchy

Hierarchy of the Cool Packages to be installed

For this post, I’m going to change the names of the packages that were referenced in order to make things a little easier to follow and to protect the innocent.  My friend was trying to install CoolPackage.ASP from nuget.org which depends on CoolPackage.Base and that package depends on CoolPackageUtilities.  The current version of CoolPackage.ASP is v3.0 and it references CoolPackage.Base version 3.0   CoolPackage.Base references CoolPackage.Utilities, which has released versions 1.0 through 3.2.1

With the default options configured in NuGet in Visual Studio 2015, when my friend installed CoolPackage.ASP NuGet acquired and installed the following versions:

CoolPackage.ASP v3.0
CoolPackage.Base v3.0
CoolPackage.Utilities v1.0

Huh?  Why did version 1.0 of CoolPackage.Utilities get installed?  There is a version 3.2.1 available, why didn’t NuGet install that one?

Investigation

Let’s take a look at the nuspec that is bundled inside of the CoolPackage.Base package to learn more:

 
 <dependencies>
   <dependency id="CoolPackage.Utilities" />
 </dependencies>

Starting with v2.8, NuGet is more conservative by default and will install the lowest version of packages that the package author depends on.  In this case, CoolPackage.Base did not specify a version attribute for the CoolPackage.Utilities package.  NuGet reverted to its default behavior and located the lowest version of CoolPackage.Utilities package.

Remedies

In general, it’s not a good idea to deliver a package with dependencies that don’t have a version or version range specified.  Version ranges in NuGet can be specified using some nifty parenthesis and square bracket notation. If you only specify a single version number, then the NuGet clients will install AT LEAST that version of the dependency.

Additionally, if you as a package consumer would like to make NuGet more aggressive about which versions of dependent packages you would like it to install, you can set an option on the right-side of the user-interface to instruct NuGet to install different versions:

dependencyVersionChooserBy default, this combobox has ‘Lowest’ selected.  If you want to change the default for your project, solution, or even across your entire machine you can add an entry to your NuGet.config file that forces this setting:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <config>
    <add key="dependencyVersion" value="Highest"/>
  </config>
</configuration>

You can save this simple NuGet.config file into your solution, a project, or add this setting to your machine-wide NuGet.config file residing at %AppData%\NuGet  More information about the various settings for NuGet.config are available in the online docs.

You can also override the dependencyVersion when installing or updating from Powershell by adding a -dependencyVersion switch with the value you want to use.

Summary

You can configure NuGet to be as conservative or aggressive as you would like in determining which packages to install and maintain in your project.  If you want the earliest versions, latest patch versions, or some combination of those you can specify that when you add packages to your project.  Package authors can control what versions of packages are recommended to be installed by adding and controlling the ‘version’ attribute of the dependencies in their package’s nuspec files.  These choices are up to all of you, and ultimately the package consumer can install and maintain whatever versions they wish.

  • Pingback: Dew Drop – December 3, 2015 (#2144) | Morning Dew()

  • Ouch. Introducing a breaking change without a semver major bump is never a good idea IMHO.

    2.x could have (should have?) introduced this with an additional option for no version specified uses previous behavior. And for 3.x they could have removed the additional option.

    • Jeffrey Fritz [jefritz@MSFT]

      How was it a breaking change? For some, that was the desired effect. The additional configuration options and decision for a default behavior would not have been correct for 50% of the users. The UI with that option was not introduced until 3.0

      • Edit – If the change was made this way in 2.8.x, the following statement applies:

        By definition. The behavior of an API was changed. It doesn’t matter if you considered it a bug or thought the behavior was incorrect, it is by definition a breaking change when the behavior is changed.

        I don’t make the rules – on https://semver.org:
        “8. Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API. It MAY include minor and patch level changes. Patch and minor version MUST be reset to 0 when major version is incremented.”

        If however this was changed in 3.0.x, then this statement very much does not apply and SemVer was followed. 🙂

        • If it was a backwards compatible bug fix – it could have been introduced earlier. However this doesn’t appear to be backwards compatible. Which makes it breaking.

          Unless I’m confused here.

      • Ha, Am I confused then? I may have misread. If you introduced this change in 3.x, then nevermind. 🙂

        This is what threw me off:
        “Starting with v2.8, NuGet is more conservative by default and will install the lowest version of packages that the package author depends on. In this case, CoolPackage.Base did not specify a version attribute for the CoolPackage.Utilities package. NuGet reverted to its default behavior and located the lowest version of CoolPackage.Utilities package.”

        Does this mean that with 2.8 – if I don’t have a version specified, that I’m going to get the lowest version available installed? This is what I keyed off of for my original comment.

        • It may help to edit that statement specifically to state that the default behavior of version-less dependencies was changed at 3.0.x to grab the lowest version instead of the highest. It certainly threw me off.