繁体   English   中英

如何在多个 c# 项目中强制执行相同的 nuget 包版本?

[英]How to enforce same nuget package version across multiple c# projects?

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

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

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

我刚刚意识到可以使用文件夹作为 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 从缓存复制到localnuget文件夹以将其签入。

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

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

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

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

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

在此处输入图像描述

请参阅 Microsoft 文档中的合并选项卡

由于我还没有找到另一种方法来强制执行此操作,因此我编写了一个单元测试,如果在任何子文件夹中的任何 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}";
        }
    }
}

谢谢你问这个 - 所以我并不孤单。 我花了相当多的时间来确保我的解决方案中的所有项目都使用相同的包版本。 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 是开源的,弄脏我们的手并实现缺少的版本冲突避免实用程序肯定是一件很酷的事情。)

除了 VS 中的“合并”选项卡外,您还可以使用 powershell Sync-Package https://docs.microsoft.com/en-us/nuget/reference/ps-reference/ps-ref-sync-package

例子:

# Sync the Elmah package installed in the default project into the other projects in the solution
Sync-Package Elmah

# Sync the Elmah package installed in the ClassLibrary1 project into other projects in the solution
Sync-Package Elmah -ProjectName ClassLibrary1

# Sync Microsoft.Aspnet.package but not its dependencies into the other projects in the solution
Sync-Package Microsoft.Aspnet.Mvc -IgnoreDependencies

# Sync jQuery.Validation and install the highest version of jQuery (a dependency) from the package source    
Sync-Package jQuery.Validation -DependencyVersion highest

您可以使用 nuget 的新“中央包管理”功能。

示例问题:

假设您有多个项目的 monorepo(即 VS“解决方案”或 VSCode“工作区”)。

ProjectA.csproj

<ItemGroup>
  <PackageReference Include="Foo.Bar.Baz" Version="1.0.0" />
  <PackageReference Include="Spam.Ham.Eggs" Version="4.0.0" />
</ItemGroup>

ProjectB.csproj

<ItemGroup>
  <PackageReference Include="Foo.Bar.Qux" Version="1.2.3" />
  <PackageReference Include="Spam.Ham.Eggs" Version="4.5.6" />
</ItemGroup>

有些项目相同,而有些项目不同。 而且您需要记住保持版本同步 - 该示例表明您忘记了这样做!

第 1 步:删除版本

ProjectA.csproj

<ItemGroup>
  <PackageReference Include="Foo.Bar.Baz" />
  <PackageReference Include="Spam.Ham.Eggs" />
</ItemGroup>

ProjectB.csproj

<ItemGroup>
  <PackageReference Include="Foo.Bar.Qux" />
  <PackageReference Include="Spam.Ham.Eggs" Version="4.0.0" />  <!-- note version override for this project -->
</ItemGroup>

第 2 步:将名为Directory.Packages.props的文件添加到您的存储库的根目录

<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
  <ItemGroup>
    <!-- use 'PackageVersion' rather than 'PackageReference' -->
    <PackageVersion Include="Foo.Bar.Baz" Version="1.2.3" />
    <PackageVersion Include="Foo.Bar.Qux" Version="1.2.3" />
    <PackageVersion Include="Spam.Ham.Eggs" Version="4.5.6" />
  </ItemGroup>
</Project>

第三步:恢复

对于每个项目:

  • 清除构建输出: dotnet clean
  • 恢复包: dotnet restore

您的所有项目现在都将使用您在配置文件中指定的版本。

还有更多选项,例如版本覆盖和传递依赖固定 - 阅读文档了解更多信息。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM