Guarantee same version of nuget packages

3.1k views Asked by At

We have a framework that is split up into lots of separate projects in one solution. I now want to create NuGet packages for each separate project, but guarantee that only one version of the framework can be used in one solution (possibly across several projects).

For example, say the framework is made up of two projects:

Framework
   Framework_1
   Framework_2

Now when using this framework one project might reference Framework_1, while another project references Framework_2. I want to make sure that both packages have the same version (bonus points if there's an easy single-step process to upgrade to a newer version)

I thought I would just define one solution level Framework package that all other packages depend on strictly. The problem is that NuGet has no problems simply installing several versions of the solution level package.

Basically I tried the following:

Solution-level nuspec file:

<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
  <metadata>
    <id>My.Framework</id>
    <version>1.0.0</version>
    <title>My.Framework</title>
    <authors>voo</authors>
    <owners>voo</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Some Framework Solution Package</description>
    <copyright>Copyright ©  2015</copyright>
  </metadata>
</package>

And one nuspec package for one part:

<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
  <metadata>
    <id>My.Framework.BL</id>
    <version>1.0.0</version>
    <title>My.Framework.BL</title>
    <authors>voo</authors>
    <owners>voo</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Business Layer</description>
    <copyright>Copyright ©  2015</copyright>
    <dependencies> 
        <dependency id="My.Framework" version="[1.0.0]"/>
    </dependencies>
  </metadata>
</package>

The problem now is if I tried to install, say another My.Framework.EF package with version 1.0.1 and an explicit dependency on My.Framework 1.0.1 Visual Studio would just install My.Framework twice - once with version 1.0.0 and once with 1.0.1.

4

There are 4 answers

0
Voo On BEST ANSWER

It turns out that you can call Install-Package $package.Id -version <someVersion> inside Install.ps1 which will then cause the originally installed version to be uninstalled and the specified version to be installed.

A slightly simplified version is the following:

param($installPath, $toolsPath, $package, $project)

function GetInstallingVersion() {
    $package.Version
}

# Gets the current version of the used framework. 
# If no framework is yet installed, we set the framework version 
# to the one that's being installed right now.
function GetCurrentFrameworkVersion() {
    $solutionPath = Split-Path $dte.Solution.FileName
    $fwkVersionFile = "${solutionPath}\framework_version.txt"
    if (Test-Path $fwkVersionFile) {
        return Get-Content $fwkVersionFile
    } 
    else {
        $installingVersion = GetInstallingVersion
        $installingVersion > $fwkVersionFile
        return $installingVersion
    }
}

$currentFwkVersion = GetCurrentFrameworkVersion
$installingVersion = GetInstallingVersion

if ($currentFwkVersion -ne $installingVersion) {
    Install-Package $package.Id -version $currentFwkVersion
}
3
Mladen Oršolić On

you can constrain the version of your package by using the following syntax in your packages.config like :

<package id="jQuery" version="1.9.1" allowedVersions="[1.9.1]" />

Also from original nuget documentation : When you create a NuGet package, you can specify dependencies for the package in the .nuspec file.

<dependency id="ExamplePackage" version="[1,3)" />

In the example, version 1 and version 2.9 would be acceptable, but not 0.9 or 3.0.

I asume you can limit it this way to a single or certain range of versions. Here you can read more about it.

1
BeeTee2 On

I would rip out the "Solution level NuGet Package", and divide your framework up into components, and create a NuGet package per component. No one is going to have 1 single project that references you're "Framework Wrapper" NuGet package, and code for Business Logic, Data Access, and WCF inside that single project.

Then what you need to do is, iron out what your dependency logic truly is, and what is the reasoning behind wanting to strictly enforce a same-version policy.

For example, let's say My.Framework.BL has a dependency on My.Framework.DAL. So at this point you only have 2 Nuspec files, and 2 NuGet packages, with the .nuspec of your My.Framework.BL looking like this:

<dependencies>
  <dependency id="My.Framework.DAL" version="1.0.0" />
</dependencies>

And with your My.Framework.DAL containing no My.Framework specific dependencies.

This is fine, and your solution of wanting to tightly couple the number that is associated with the version is problematic for a few reasons. The first and most important is it that it will confuse your framework consumers if you updated My.Framework.DAL when it has 0 changes, but you had to update it because you changed My.Framework.BL.

You could go a month, or more possibly, and not have to update a My.Framework dependency, depending on the level of abstraction of your framework, and to what degree of low-level programming you're doing. In my opinion, having to update Core Framework dll versions when there aren't actually any new changes is a MUCH bigger problem than the version numbers of all My.Framework dlls being the same. Cheers. :)

Here are the nuspec references docs.

0
niaher On

You can create a simple unit test inside your solution to warn you when you have a problem. The code is below.

You will need to install-package NuGet.Core inside your unit-test project for the below code to work.

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NuGet;

[TestClass]
public class NugetPackagesTest
{
    /// <summary>
    /// This test method makes sure that we do not install different versions of the same nuget package
    /// across the solution. For example this test will fail if one project references EntityFramework
    /// version 6.1.3 and another project references version 6.2.0. Having different versions of the same
    /// package installed often results in unexpected and hard-to-understand errors.
    /// </summary>
    [TestMethod]
    public void PackagesAccrossProjectsAreOfSameVersion()
    {
        var dir = GetSolutionRoot();

        Debug.Assert(dir != null, nameof(dir) + " != null");

        var filePaths = Directory.GetFiles(dir.FullName, "*packages.config", SearchOption.AllDirectories);
        var installedPackages = filePaths
            .Select(t => new PackageReferenceFile(t))
            .SelectMany(t => t.GetPackageReferences().Select(x => new { File = t, Package = x }))
            .GroupBy(t => t.Package.Id)
            .ToList();

        foreach (var package in installedPackages)
        {
            var versions = package
                .Select(t => t.Package.Version.ToNormalizedString())
                .Distinct()
                .ToList();

            var report = package
                .Select(t => $"{t.Package.Version} @ {t.File.FullPath}")
                .OrderBy(t => t);

            Assert.IsTrue(
                versions.Count == 1,
                $"Multiple versions of package {package.Key} are installed: {string.Join(", ", versions)}.\n" +
                $"{string.Join("\n", report)}");
        }
    }

    private static DirectoryInfo GetSolutionRoot()
    {
        var current = AppDomain.CurrentDomain.BaseDirectory;
        var dir = Directory.GetParent(current);

        while (dir != null)
        {
            // TODO: replace with name your solution's folder.
            if (dir.Name == "MySolution")
            {
                dir = dir.Parent;
                break;
            }

            dir = dir.Parent;
        }

        return dir;
    }
}