19

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.

Randika Vishman
  • 7,983
  • 3
  • 57
  • 80
Voo
  • 29,040
  • 11
  • 82
  • 156
  • Why not explicitly state the version you want installed? Or am I missing something? – Pseudonym Jun 18 '15 at 12:46
  • Am I correct saying what you want is some kind of package bundle, where all sub-packages use the same version? I must admit I find your question a little bit fuzzy. – David Brabant Jun 18 '15 at 12:47
  • @Pseudonym Because that allows someone accidentally to update one part of the framework but not others. The idea is to have *one* location where the version is specified or at least some kind of guarantee that catches such errors. – Voo Jun 18 '15 at 12:47
  • Are you using a build server? If so, which one? – Iain Galloway Jun 18 '15 at 12:48
  • @Iain Team Foundation Build Server, but I do want this to work just as much for normal Visual Studio 2013 when compiling locally. – Voo Jun 18 '15 at 12:49
  • does this help? https://stackoverflow.com/questions/7772346/how-to-specify-specific-dependency-version-in-nuspec – Pseudonym Jun 18 '15 at 12:50
  • @David Yes basically I want to have only one framework version per solution and each project in the solution could use different parts of the framework (say the data layer accesses the Entity Framework package, while the communication layer needs some WCF helpers). If I understand what exactly you misunderstand I'll try to make the question clearer – Voo Jun 18 '15 at 12:50
  • @Pseudonym The problem is that even if my sub packages specify an exact dependency on the solution level package (meta package if you will), Nuget und Visual studio are perfectly fine with just installing two different versions of the solution level package. – Voo Jun 18 '15 at 12:52
  • Isn't it possible to do version checking in each install.ps1 installation script for sub-packages? They'd have to check they are using the same version of the root package's one. – David Brabant Jun 18 '15 at 12:53
  • @David Certainly acceptable. Just very light documentation on that point and I don't see how you could check the version of the root package and compare the two (and abort the install process if there's a mistake). – Voo Jun 18 '15 at 13:05
  • On a side note: Assume a user creates a library L using your package P. The user imports L into two Solutions S and T. S contains a project that uses P (ver 1.0.0) and T contains a project that uses P (ver 1.0.1). He would have no other choice but to create a new library branch each time you release a new package's version. Personally I would solve this somewhat different (ie. create a temp-file/ registry entry containing some version number that all instances check against. This data could then be updated once the use of a newer version has been detected) ... not elegant, but ... – Benj Jun 18 '15 at 13:34
  • @Benj Yes you generally do want to create a new version, when making backwards incompatible changes to your library such as switching a major component to a new, non-backwards compatible version. That's not a problem, that's a feature. – Voo Jun 18 '15 at 13:40
  • I figured, that it is possible to run powershell-scripts during the installation process ( https://docs.nuget.org/Create/creating-and-publishing-a-package#automatically-running-powershell-scripts-during-package-installation-and-removal ). Then I figured that it is possible to list all projects in a solution (ie http://stackoverflow.com/questions/3802027/how-do-i-programmatically-list-all-projects-in-a-solution ) which would eventually allow to abort the installation of NUGET once another version is detected ... is this heading into the right direction or way off ... ? – Benj Jun 19 '15 at 04:29
  • If you need to use the same nuget versions in multiple projects in the same repo, see my [answer here](https://stackoverflow.com/a/73088045/9971404). – lonix Jul 23 '22 at 04:42

4 Answers4

5

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.

Mladen Oršolić
  • 1,352
  • 3
  • 23
  • 43
  • 1
    I cannot fathom why this gets 6 upvotes considering that it describes the syntax shown already in the original post and doesn't solve the described problem at all. I still end up with a package.config in the solution folder that has `` and hence the ability to install whatever versions I want. – Voo Jun 24 '15 at 11:04
  • does this `` really make any sense to you? – Mladen Oršolić Jun 25 '15 at 00:03
  • No it doesn't (sure looks like a bug to me), but that's what happens if you actually try it. – Voo Jun 25 '15 at 05:34
1

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;
    }
}
niaher
  • 9,460
  • 7
  • 67
  • 86
0

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.

BeeTee2
  • 777
  • 4
  • 13
  • While certainly a reasonable idea for external frameworks, there's just no benefit for a company internal framework to do this considering the additional support and testing costs with a very limited benefit. – Voo Jun 22 '15 at 06:57
0

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
}
Voo
  • 29,040
  • 11
  • 82
  • 156