如何使用本地构建的 NuGet 包进行调试?

4
我们有几个项目使用共享的通用库(内部使用,不对外公开)。这些项目是使用 Visual Studio 的 C# .net 语言编写的,被设置为解决方案。
Solution A ---| 
              |----> Common Library (Nuget Package)
Solution B ---|

总的来说,这种方法非常有效。通用库发布到内部的Nuget源中,两个项目都可以使用它,无论是在本地运行还是在构建服务器上。
但问题在于当我们想要修改通用库时,似乎唯一的测试方式是重新构建通用库、将更新的包发布到源中、升级Project A中的包(由于解决方案中有很多项目,这需要一段时间),然后进行测试。这个周期非常漫长。
理想情况下,我想找到一种方法,能够将通用库项目包含在Project A(或B)相同的解决方案中,并对库进行更改,然后作为一个解决方案运行和调试代码。但这样做需要花费很长时间。它需要逐个查看Project A中的所有csproj文件,将所有的引用从包引用更改为项目引用。尽管我已经尝试在csproj文件中使用条件语句,以便轻松切换使用本地项目或构建时使用Nuget包,但这似乎并不有效。Visual Studio似乎会因为引用而混淆,导致构建失败。
有其他的替代方法吗?我觉得这应该是一个比较常见的情况,那么有没有一种被广泛接受的典型方法来设置它,以便既可以使用Nuget包作为通用库,又可以连接它们并轻松进行调试呢?
针对@vernou的回答,这是我们尝试在csproj文件中使用条件属性来设置的。我们设置了一种配置类型来控制使用哪个引用。
  <ItemGroup Condition=" '$(Configuration)' == 'DebugWithLocalCommon' ">
    <ProjectReference Include="$(CommonPath)Common.Project.Name\Common.Project.Name.csproj">
      <Project>{11111111-1111-1111-1111-111111111111}</Project>
      <Name>Common.Project.Name</Name>
    </ProjectReference>
    ...
    Multiple project references continue here
    ...
  </ItemGroup>

  <ItemGroup Condition=" '$(Configuration)' != 'DebugWithLocalCommon' ">
    <Reference Include="Common.Project.Name, Version=4.0.26.0, Culture=neutral, PublicKeyToken=1111111111111111, processorArchitecture=MSIL">
      <HintPath>..\packages\Common.Project.Name.4.0.26\lib\netstandard2.0\Common.Project.Name.dll</HintPath>
    </Reference>
    ...
    Multiple package references continue here
    ...
  </ItemGroup>

实际上我们有一些项目使用经典的csproj引用格式(如上所示),还有一些使用较新的'包引用'格式来引用NuGet包。在这些情况下,第二个代码块可能会更像这样:
  <ItemGroup Condition=" '$(Configuration)' != 'DebugWithLocalCommon' ">
    <PackageReference Include="Common.Project.Name">
      <Version>4.0.26</Version>
    </PackageReference>
    ...
    Multiple package references continue here
    ...
  </ItemGroup>

我们遇到的问题是,这似乎会让Visual Studio混淆。有时候在UI中会出现多个引用(这可能只是令人烦恼而非彻底破坏),但有时它似乎完全无法理解引用并拒绝构建或识别正在被引用的包,只会给出“可能缺少引用”的错误。
我们也尝试了在csproj内使用,但结果行为完全相同。

问题是“在发布之前使用其他解决方案测试包的修改”。这正确吗? - vernou
你如何进行测试?你是从VS的调试模式开始吗? - vernou
@vernou,是的,理想情况下我们应该能够通过从Visual Studio开始调试来测试。 - Simon P Stevens
好的...你使用了旧的令人噩梦的csproj风格。这是什么类型的项目?Winforms?WPF? - vernou
激活 sourcelink 以便在调试期间能够进入 nuget 包中的代码。请参考以下链接:https://github.com/dotnet/sourcelink#using-source-link-in-net-projects - magicandre1981
显示剩余7条评论
2个回答

2
如果您使用Git,也许可以使用 git-submodule
我遇到了类似的情况。我正在为Entity Framework Core开发自定义提供程序,并为了帮助开发,我调试EF Core代码源。
csproj看起来像:
<Project Sdk="Microsoft.NET.Sdk">

  ...

  <ItemGroup Condition=" '$(Configuration)'!='Debug' ">
    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="[3.1,4)" />
  </ItemGroup>

  <ItemGroup Condition=" '$(Configuration)'=='Debug' ">
    <ProjectReference Include="..\efcore\src\EFCore.Relational\EFCore.Relational.csproj" PrivateAssets="contentfiles;build" />
  </ItemGroup>

</Project>

当我在Debug模式下启动我的项目时,我可以调试EF Core的源代码,但是当我在Release模式下启动时,会使用NuGet包,无法访问EF Core的源代码。
额外说明git:
我们使用git来版本控制我们的源代码。如果我的同事获取了源代码并尝试进行调试,则会失败,因为缺少EF Core。
因此,我将EF Core作为子模块添加到我们的git存储库中:
git submodule add https://github.com/dotnet/efcore.git
# Load a specific version
cd efcore
git checkout v3.1.18
cd ../
git commit -m "Add the submodule efcore"

现在,当我的同事克隆我们的提供者存储库时,EF Core代码源会自动加载(只需要在从Visual Studio克隆时勾选“加载子模块”选项)。

1
这是一个挑战,解决方案相当复杂,也取决于您的源代码控制和开发环境设置。但是有一个潜在的解决方案,我可以解释一下。
简而言之,您需要一个自定义工具,它可以只是一个命令行exe项目,可以在不需要进行所有手动更改的情况下更新csproj文件中的引用。
开发环境设置:
您会发现,作为个人的开发人员可能会将源代码克隆到计算机上的不同位置。因此,每个人都在不同的地方拥有自己的代码。只要每个人在主文件夹中具有相同的结构,以便可以使用相对位置添加引用,这就可以了。
例如,如果每个人都使用: C:\ Source \ Common
C:\ Source \ App1 \
或者: C:\ Code \ Common
C:\ Code \ App1 \
那就没问题了,但是如果Common文件夹在某个人的计算机上被称为“cmn”,那么对他们来说就会有问题。
所以现在,在VS中,您可以使用相对位置(例如:..\Common\Common.csproj)在App1中将项目引用到您的Common项目。
项目类型问题:
您需要考虑您的项目类型。例如,在Visual Studio中创建一个新项目并使用.Net Framework 4.xx,您将拥有现在所称的“旧”项目类型。如果使用.Net Standard或.Net Core创建,则会获得新的项目类型(这对于执行所有这些nuget操作特别好)。
旧的项目类型中,每个项目都有一个packages.config引用所有nuget包。它还在csproj中引用了实际项目,包括确切的路径和版本。这使得整个过程更加繁琐。
新的项目类型只在csproj文件中引用nuget包(非常棒)。
但是,如果您仍然拥有旧的项目类型,则仍然可以使用自定义工具来处理此问题。
工具:
这只是基础,不是完整的解决方案,但希望您可以从中获得足够的信息以实施。
请确保您在副本上测试此功能,或首先启动新分支以确保没有永久性故障。这仅适用于我假设您正在使用的旧样式项目。
创建一个新的控制台应用程序项目,并添加一些基本类来处理文件:

PackageConfig.cs

[Serializable]
[DesignerCategory("code")]
[XmlType(AnonymousType = true, IncludeInSchema = true)]
[XmlRoot(ElementName = "packages", IsNullable = false)]
public class PackageConfig
{
    [XmlElement("package")]
    public Package[] Packages { get; set; }
}

[Serializable]
[DesignerCategory("code")]
public class Package
{
    [XmlAttribute("id")]
    public string Id { get; set; }

    [XmlAttribute("version")]
    public string Version { get; set; }

    [XmlAttribute("targetFramework")]
    public string TargetFramework { get; set; }
}

XmlSerializer.cx

class XmlSerializer
{
    private System.Xml.Serialization.XmlSerializer GetSerializer<T>()
    {
        return new System.Xml.Serialization.XmlSerializer(typeof(T));
    }

    public string Serialize<T>(T instance, bool omitXmlDeclaration = false)
    {
        System.Xml.Serialization.XmlSerializer xmlSerializer = GetSerializer<T>();
        StringBuilder stringBuilder = new StringBuilder();

        XmlWriterSettings settings = new XmlWriterSettings
        {
            OmitXmlDeclaration = omitXmlDeclaration,
            Encoding = Encoding.UTF8
        };

        using (XmlWriter writer = XmlWriter.Create(new StringWriter(stringBuilder), settings))
        {
            xmlSerializer.Serialize(writer, instance);
        }

        return stringBuilder.ToString();
    }

    public T Deserialize<T>(string xml)
    {
        System.Xml.Serialization.XmlSerializer xmlSerializer = GetSerializer<T>();
        using (XmlTextReader reader = new XmlTextReader(new StringReader(xml)))
            return (T)xmlSerializer.Deserialize(reader);
    }
}

将你的program.cs文件更新为以下内容:
class Program
{
    static void Main(string[] args)
    {
        try
        {
            string packageVersion = null;
            if (args != null && args.length == 1)
                packageVersion = args[0];
            UpdateProjects(packageVersion);
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine(ex.ToString());
        }
    }

    const string _packageId = "Common";
    delegate string UpdateContent(string content);

    static void UpdateProjects(string packageVersion)
    {
        if (packageVersion == null)
            UseLocalReferences();
        else
            UsePackageReferences(packageVersion);
    }

    static void UseLocalReferences()
    {
        RemoveNugetPackage();
        ProcessProjects(content =>
        {
            content = RemoveReference(content);
            content = AddReference(content, null);
            return content;
        });
    }

    static void UsePackageReferences(string packageVersion)
    {
        AddNugetPackage(packageVersion);
        ProcessProjects(content =>
        {
            content = RemoveReference(content);
            content = AddReference(content, packageVersion);
            return content;
        });
    }

    static void ProcessProjects(UpdateContent update)
    {
        string folder = Environment.CurrentDirectory;
        string[] files = Directory.GetFiles(folder, "*.csproj", SearchOption.AllDirectories);

        foreach (string fileName in files)
        {
            try
            {
                string content = File.ReadAllText(fileName);

                content = update(content);

                using (StreamWriter writer = new StreamWriter(fileName))
                {
                    writer.Write(content);
                }

                Console.WriteLine("Updated: " + fileName);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"Error updating {fileName}:");
                Console.Error.WriteLine(ex.ToString());
            }
        }
    }

    static void RemoveNugetPackage()
    {
        string folder = Environment.CurrentDirectory;
        Console.WriteLine($"Updating all packages.config files under {folder}");

        XmlSerializer xmlSerializer = new XmlSerializer();

        string[] packageConfigFiles = Directory.GetFiles(folder, "packages.config", SearchOption.AllDirectories);
        foreach (string packageConfigFile in packageConfigFiles)
        {
            PackageConfig packageConfig = xmlSerializer.Deserialize<PackageConfig>(File.ReadAllText(packageConfigFile));
            List<Package> packages = new List<Package>(packageConfig.Packages);

            if (packages.Any(x => x.Id == _packageId))
                packages.First(x => x.Id == _packageId).Version = packageVersion;
            else
                packages.Add(new Package { Id = _packageId, Version = packageVersion, TargetFramework = "net462" }); // may need to change the framework here

            packages.Sort((x, y) => string.Compare(x.Id, y.Id));
            packageConfig.Packages = packages.ToArray();
            File.WriteAllText(packageConfigFile, xmlSerializer.Serialize(packageConfig));
            Console.WriteLine($"{packageConfigFile} updated");
        }

        Console.WriteLine("Update of packages.config files complete");
    }

    static void AddNugetPackage(string packageVersion)
    {
        string folder = Environment.CurrentDirectory;
        Console.WriteLine($"Updating all packages.config files under {folder}");

        XmlSerializer xmlSerializer = new XmlSerializer();

        string[] packageConfigFiles = Directory.GetFiles(folder, "packages.config", SearchOption.AllDirectories);
        foreach (string packageConfigFile in packageConfigFiles)
        {
            PackageConfig packageConfig = xmlSerializer.Deserialize<PackageConfig>(File.ReadAllText(packageConfigFile));
            List<Package> packages = new List<Package>(packageConfig.Packages);

            Package package = packages.FirstOrDefault(x => x.Id == _packageId);
            if (package != null)
                packages.Remove(package);

            packageConfig.Packages = packages.ToArray();
            File.WriteAllText(packageConfigFile, xmlSerializer.Serialize(packageConfig));
            Console.WriteLine($"{packageConfigFile} updated");
        }

        Console.WriteLine("Update of packages.config files complete");
    }

    static string RemoveReference(string content)
    {
        string[] lines = content.Split(new string[] { "\r\n" }, StringSplitOptions.None);

        StringBuilder sb = new StringBuilder();
        bool removing = false;

        foreach (string line in lines)
        {
            if (!removing && line.Trim().StartsWith($"<Reference Include=\"{_packageId}\""))
                removing = true;
            else if (removing)
            {
                if (line.Trim() == "</Reference>")
                    removing = false;
            }
            else
                sb.AppendLine(line);
        }

        return sb.ToString();
    }

    static string AddReference(string content, string packageVersion)
    {
        string[] lines = content.Split(new string[] { "\r\n" }, StringSplitOptions.None);

        StringBuilder sb = new StringBuilder();

        foreach (string line in lines)
        {
            if (line.Trim() == "<Reference Include=\"System\" />") // find where the references need to be inserted
            {
                if (packageVersion == null)
                {
                    sb.AppendLine($"    <Reference Include=\"{_packageId}\">");
                    sb.AppendLine($"      <SpecificVersion>False</SpecificVersion>");
                    sb.AppendLine($"      <HintPath>..\\Common\\bin\\$(Configuration)\\{_packageId}.dll</HintPath>");
                    sb.AppendLine($"    </Reference>");
                }
                else
                {
                    sb.AppendLine($"    <Reference Include=\"{_packageId}, Version = {packageVersion}, Culture = neutral, processorArchitecture = MSIL\">");
                    sb.AppendLine($"      <HintPath>..\\packages\\{_packageId}.{packageVersion}\\lib\\net462\\{_packageId}.dll</HintPath>"); // again, framework may need updating
                    sb.AppendLine($"    </Reference>");
                }
            }

            sb.AppendLine(line);
        }

        return sb.ToString();
    }
}

使用该工具:

当您编译该工具时,请将exe文件复制到您的主项目文件夹中,例如C:\Source\App1 - 然后您可以使用以下命令运行它:

updatetool
(to set the local references)

或者

updatetool 1.0.1
(to set to the nuget package and references)

这全部都没有经过测试,但基于我用于相同问题的解决方案!

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