保证NuGet包的版本相同

19

我们有一个框架,将其分成了许多单独的项目,放在一个解决方案中。现在我想为每个单独的项目创建 NuGet 包,但是要保证一个解决方案(可能跨越多个项目)只使用一个框架版本。

例如,假设该框架由两个项目组成:

Framework
   Framework_1
   Framework_2

使用这个框架时,一个项目可能引用Framework_1,而另一个项目则引用Framework_2。我希望确保两个包具有相同的版本(如果有一种简单的一步升级到新版本的过程,那就更好了)。

我认为我只需要定义一个解决方案级别的框架包,所有其他包都严格依赖于它即可。但问题是,NuGet可以轻松安装解决方案级别包的多个版本。

基本上我尝试了以下操作:

解决方案级别的nuspec文件:

<?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>

每个部分都需要一个nuspec包:

<?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>
问题现在是,如果我尝试安装另一个版本为1.0.1的My.Framework.EF包,并明确依赖于My.Framework 1.0.1,Visual Studio会重复安装My.Framework - 分别是版本1.0.0和1.0.1。

@Pseudonym 因为这样可以避免有人意外地更新了框架的某个部分而忘记更新其他部分。这个想法是在一个位置指定版本号或者至少提供某种保证来捕捉这种错误。 - Voo
你在使用构建服务器吗?如果是的话,使用的是哪一种? - Iain Galloway
@Iain 团队基础构建服务器,但我希望在本地编译时也可以像普通的 Visual Studio 2013 一样正常工作。 - Voo
@David 是的,基本上我想在每个解决方案中只有一个框架版本,而解决方案中的每个项目都可以使用框架的不同部分(例如数据层访问Entity Framework包,而通信层需要一些WCF助手)。如果我了解你误解了什么,我会尝试让问题更清晰。 - Voo
@Pseudonym 问题在于,即使我的子包明确指定了对解决方案级别包(元包)的依赖关系,Nuget和Visual Studio也可以轻松安装两个不同版本的解决方案级别包。 - Voo
显示剩余9条评论
4个回答

5

您可以通过在packages.config中使用以下语法来限制您的包的版本:

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

从NuGet的官方文档中可以得知: 在创建NuGet包时,你可以在.nuspec文件中指定包的依赖关系。

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

在这个例子中,版本1和版本2.9是可接受的,但版本0.9和3.0不可接受。我认为你可以将其限制在一个单一的或特定版本范围内。这里可以了解更多相关信息。

1
我无法理解为什么这篇文章会得到6个赞,因为它描述的语法已经在原始帖子中展示过了,并且根本没有解决所描述的问题。我最终仍然会在解决方案文件夹中得到一个package.config,其中包含<package id="My.Framework" version="1.0.0" allowedVersions="[1.0.0]" /><package id="My.Framework" version="1.0.1" />,因此可以安装任何版本。 - Voo
这个 <package id="My.Framework" version="1.0.0" allowedVersions="[1.0.0]" /><package id="My.Framework" version="1.0.1" /> 对你来说真的有意义吗? - Mladen Oršolić
不,它没有问题(看起来确实像是一个bug),但如果你真的尝试它,这就是发生的事情。 - Voo

1

您可以在解决方案中创建一个简单的单元测试,以在出现问题时发出警告。以下是代码:

为使以下代码正常工作,您需要在单元测试项目中安装 NuGet.Core

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;
    }
}

0
我会删除“解决方案级NuGet包”,将您的框架分成组件,并为每个组件创建一个NuGet包。没有人会有一个只引用您的“框架包装器”NuGet包的单一项目,并在该单一项目中包含业务逻辑、数据访问和WCF的代码。
然后,您需要梳理出您的依赖逻辑真正是什么,以及想要严格执行同一版本策略的原因是什么。
例如,假设My.Framework.BL依赖于My.Framework.DAL。所以此时您只有2个Nuspec文件和2个NuGet包,其中My.Framework.BL的.nuspec看起来像这样:
<dependencies>
  <dependency id="My.Framework.DAL" version="1.0.0" />
</dependencies>

同时,你的 My.Framework.DAL 不包含任何 My.Framework 特定的依赖项。

这很好,但是你想要紧密耦合与版本相关联的数字的解决方案有一些问题。首先,最重要的是,如果你更新了 My.Framework.DAL,但它没有任何更改,而你不得不更新它,因为你改变了 My.Framework.BL,那么会让你的框架使用者感到困惑。

根据你的框架的抽象级别和低级编程的程度,你可能会一个月甚至更长时间不需要更新 My.Framework 依赖项。在我看来,当核心框架 dll 没有实际新变化时,不得不更新核心框架 dll 版本是一个比所有 My.Framework dll 版本相同更大的问题。祝好!:)

这里是 nuspec 引用文档。


虽然对于外部框架来说这是一个合理的想法,但对于公司内部框架来说,考虑到额外的支持和测试成本以及非常有限的好处,这并没有任何益处。 - Voo

0
原来你可以在 Install.ps1 中调用 Install-Package $package.Id -version <someVersion>,这将导致最初安装的版本被卸载并安装指定的版本。
稍微简化一下,如下所示:
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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接