【问题标题】:How to enforce same nuget package version across multiple c# projects?如何在多个 c# 项目中强制执行相同的 nuget 包版本?
【发布时间】:2023-03-21 00:43:01
【问题描述】:

我有一堆使用几个 NuGet 包的小型 C# 项目。我希望能够自动更新给定包的版本。不仅如此:如果项目使用与其他项目不同的版本,我会收到警告。

如何在多个 C# 项目中强制执行相同的版本依赖关系?

【问题讨论】:

  • 一个很好的起点是 Visual Studio 中的 Manage Nuget Packages for Solution 对话框。它为每个版本列出一次包,因此很容易发现多个版本。然而,这并没有提供强制执行它的机制。
  • 您是否考虑过使用 Paket (fsprojects.github.io/Paket) 作为您的 nuget 客户端?您仍然可以使用相同的旧 nuget 服务器,但是您将获得一个现代的精心设计的客户端,而不是默认情况下在您的解决方案中强制执行相同的版本依赖(以及为您提供默认 nuget 客户端不会提供的大量其他强大功能)给你)。

标签: c# nuget


【解决方案1】:

由于我还没有找到其他方法来强制执行此操作,因此我编写了一个单元测试,如果在任何子文件夹中的任何 packages.config 中找到不同的包版本,该测试将失败。 由于这可能对其他人有用,您将在下面找到代码。您必须在 GetBackendDirectoryPath() 中调整根文件夹的分辨率。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;

using NUnit.Framework;

namespace Base.Test.Unit
{
    [TestFixture]
    public class NugetTest
    {
        private const string PACKAGES_CONFIG_FILE_NAME = "packages.config";
        private const string BACKEND_DIRECTORY_NAME = "DeviceCloud/";

        private const string PACKAGES_NODE_NAME = "packages";
        private const string PACKAGE_ID_ATTRIBUTE_NAME = "id";
        private const string PACKAGE_VERSION_ATTRIBUTE_NAME = "version";

        /// <summary>
        /// Tests that all referenced nuget packages have the same version by doing:
        /// - Get all packages.config files contained in the backend
        /// - Retrieve the id and version of all packages
        /// - Fail this test if any referenced package has referenced to more than one version accross projects
        /// - Output a message mentioning the different versions for each package 
        /// </summary>
        [Test]
        public void EnforceCoherentReferences()
        {
            // Act
            IDictionary<string, ICollection<PackageVersionItem>> packageVersionsById = new Dictionary<string, ICollection<PackageVersionItem>>();
            foreach (string packagesConfigFilePath in GetAllPackagesConfigFilePaths())
            {
                var doc = new XmlDocument();
                doc.Load(packagesConfigFilePath);

                XmlNode packagesNode = doc.SelectSingleNode(PACKAGES_NODE_NAME);
                if (packagesNode != null && packagesNode.HasChildNodes)
                {
                    foreach (var packageNode in packagesNode.ChildNodes.Cast<XmlNode>())
                    {
                        if (packageNode.Attributes == null)
                        {
                            continue;
                        }

                        string packageId = packageNode.Attributes[PACKAGE_ID_ATTRIBUTE_NAME].Value;
                        string packageVersion = packageNode.Attributes[PACKAGE_VERSION_ATTRIBUTE_NAME].Value;

                        if (!packageVersionsById.TryGetValue(packageId, out ICollection<PackageVersionItem> packageVersions))
                        {
                            packageVersions = new List<PackageVersionItem>();
                            packageVersionsById.Add(packageId, packageVersions);
                        }

                        //if (!packageVersions.Contains(packageVersion))
                        if(!packageVersions.Any(o=>o.Version.Equals(packageVersion)))
                        {
                            packageVersions.Add(new PackageVersionItem()
                            {
                                SourceFile = packagesConfigFilePath,
                                Version = packageVersion
                            });
                        }

                        if (packageVersions.Count > 1)
                        {
                            //breakpoint to examine package source
                        }
                    }
                }
            }

            List<KeyValuePair<string, ICollection<PackageVersionItem>>> packagesWithIncoherentVersions = packageVersionsById.Where(kv => kv.Value.Count > 1).ToList();

            // Assert
            string errorMessage = string.Empty;
            if (packagesWithIncoherentVersions.Any())
            {
                errorMessage = $"Some referenced packages have incoherent versions. Please fix them by adapting the nuget reference:{Environment.NewLine}";
                foreach (var packagesWithIncoherentVersion in packagesWithIncoherentVersions)
                {
                    string packageName = packagesWithIncoherentVersion.Key;
                    string packageVersions = string.Join("\n  ", packagesWithIncoherentVersion.Value);
                    errorMessage += $"{packageName}:\n  {packageVersions}\n\n";
                }
            }

            Assert.IsTrue(packagesWithIncoherentVersions.Count == 0,errorMessage);
            //Assert.IsEmpty(packagesWithIncoherentVersions, errorMessage);
        }

        private static IEnumerable<string> GetAllPackagesConfigFilePaths()
        {
            return Directory.GetFiles(GetBackendDirectoryPath(), PACKAGES_CONFIG_FILE_NAME, SearchOption.AllDirectories)
                .Where(o=>!o.Contains(".nuget"));
        }

        private static string GetBackendDirectoryPath()
        {
            string codeBase = Assembly.GetExecutingAssembly().CodeBase;
            var uri = new UriBuilder(codeBase);
            string path = Uri.UnescapeDataString(uri.Path);
            return Path.GetDirectoryName(path.Substring(0, path.IndexOf(BACKEND_DIRECTORY_NAME, StringComparison.Ordinal) + BACKEND_DIRECTORY_NAME.Length));
        }

    }

    public class PackageVersionItem
    {
        public string SourceFile { get; set; }
        public string Version { get; set; }

        public override string ToString()
        {
            return $"{Version} in {SourceFile}";
        }
    }
}

【讨论】:

  • 很好的解决方案。我需要将它添加到我自己的代码库中,因为版本似乎在不断变化......
  • 事实证明,该测试正在捕获在我的解决方案文件夹中闲逛但不再添加到解决方案中的旧项目。我制作了一个版本,其中存储了不匹配的包版本和它来自的配置文件,然后在错误消息中打印出来。这样,我很容易就能找到冲突的根源。如果你喜欢它,我可以将它作为编辑推荐给你的帖子。
  • 嗨@Slothario,是的,请这样做。正如我在一个非常年轻的项目中引入的那样,我们没有任何“死”文件,我们能够解决 nuget 包管理器中的冲突
  • 这很好用,在我意识到我必须更改 BACKEND_DIRECTORY_NAME 之后。谢谢!
【解决方案2】:

我相信我已经找到了可以解决这个(以及许多其他)问题的设置。

我刚刚意识到可以将文件夹用作 nuget 源。这是我所做的:

root
  + localnuget
      + Newtonsoft.Json.6.0.1.nupkg
  + nuget.config
  + packages
      + Newtonsoft.Json.6.0.1
  + src
      + project1

nuget.config 如下所示:

<configuration>
  <config>
    <add key="repositoryPath" value="packages" />
  </config>
  <packageSources>
    <add key="local source" value="localnuget">
  </packageSources>
</configuration>

您可以将 Nuget 服务器添加到 nuget.config 以在开发期间访问更新或新的依赖项:

<add key="nuget.org" value="https://www.nuget.org/api/v2/" /> 

完成后,您可以将 .nupkg from cache 复制到 localnuget 文件夹以签入。

我喜欢这个设置的 3 件事:

  1. 我现在可以使用 Nuget 功能,例如添加道具和目标。如果您有代码生成器(例如 protobuf 或 thrift),这将变得无价。

  2. 它(部分)解决了 Visual Studio 不复制 all DLLs 的问题,因为您需要在 .nuspec 文件中指定依赖项,并且 nuget 会自动加载间接依赖项。

  3. 我曾经为所有项目提供一个解决方案文件,因此更新 nuget 包更加容易。我还没有尝试过,但我想我也解决了这个问题。我可以为要从给定解决方案导出的项目提供 nuget 包。

【讨论】:

    【解决方案3】:

    感谢您提出这个问题 - 所以我并不孤单。我花了相当多的时间来确保我的解决方案中的所有项目都使用相同的包版本。 NuGet 用户界面(以及命令行界面)也有助于在解决方案中的项目之间具有不同的版本。特别是当一个新项目被添加到解决方案并且包 X 应该被添加到新项目时,NuGet 过于贪婪地从 nuget.org 下载最新版本而不是首先使用本地版本,这将是更好的默认处理.

    我完全同意你的观点,如果在解决方案中使用不同版本的包,NuGet 应该发出警告。它应该有助于避免这种情况并修复这样的版本迷宫。

    我现在发现的最好办法是枚举解决方案文件夹(您的项目根目录)中的所有 packages.config 文件,这些文件看起来像

    <?xml version="1.0" encoding="utf-8"?>
    <packages>
      <package id="Newtonsoft.Json" version="6.0.6" targetFramework="net451" />
      ...
    </packages>
    

    然后按 id 对 xml 节点进行排序并分析版本号。

    如果任何包的版本号不同,使它们都相等,然后运行 ​​NuGet 命令

    Update-Package -ProjectName 'acme.lab.project' -Reinstall
    

    应该修复错误的包版本。

    (由于 NuGet 是开源的,因此亲自动手并实现缺少的版本冲突避免实用程序肯定是一件很酷的事情。)

    【讨论】:

    • 谢谢。我通过从工作项目中复制 packages.config 以及程序集绑定并运行上述命令,修复了一个始终收到冲突警告的项目。
    【解决方案4】:

    我不知道如何执行它,但我发现“合并”选项卡可以提供帮助。 此选项卡向您显示在整个解决方案中具有不同版本的包。从那里您可以选择项目并使用安装按钮为它们安装相同的软件包版本。此选项卡位于“管理 NuGet 以获取解决方案”下。

    【讨论】:

    • 这对我真的很有帮助!对于任何好奇的人,您可以通过管理解决方案的 Nuget 包来访问整合它
    • 非常感谢!这是在一个大解决方案中解决了有关不同软件包版本的所有问题的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2013-10-13
    • 1970-01-01
    • 1970-01-01
    • 2021-07-01
    • 2015-09-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-07
    相关资源
    最近更新 更多