Nuget最佳实践:Debug或Release?

104

目前,我使用Nuget打包发布版本并发布到nuget.org官方平台上,但我会使用Nuget打包调试版本并推送到symbolsource.org等符号源服务器。

编辑:(Jon Skeet,带有Noda Time开发的一些偏见)

现在,NuGet支持同时将包推送到NuGet gallery和symbolsource.org(或类似的服务器),如文档所述。不幸的是,这里存在两个矛盾的要求:

  • 当仅使用库而不需要进行调试时,您确实希望使用发布版本。毕竟,发布版本就是为此而存在的。
  • 当为诊断目的调试到库时,您确实希望使用具有所有适当优化禁用的调试版本。毕竟,调试版本就是为此而存在的。

这本来没问题,但是(就我所知)NuGet不允许以有用的方式发布既有发布版本又有调试版本的同一个包。

因此,选择如下:

  • 向所有人分发调试版本(如文档中的示例)并承受任何大小和性能损失。
  • 向所有人分发发布版本并承受略微受损的调试体验。
  • 采用非常复杂的分发策略,可能会提供单独的发布版本和调试版本包。

前两种选择的本质在于调试版本和发布版本之间的差异......尽管值得注意的是,想要步入库的代码以检查某些行为与想要调试库的代码,因为您认为已经发现了一个错误,这两种情况之间也存在很大的区别。在第二种情况下,最好将库的代码作为“Visual Studio解决方案”获取,并以那种方式进行调试,因此我不会过多地考虑那种情况。

我的诱惑是仅使用发布版本,因为相对较少的人需要调试,并且那些需要调试的人也不会受到发布版本中优化的影响(JIT编译器主要负责优化)。

那么,我们是否考虑过其他选项?是否有其他考虑因素会倾斜平衡?将NuGet包推送到SymbolSource是否足够新,以至于“最佳实践”尚未建立?


2
我正想问同样的问题 - 尽管目前我也正在将发布配置推送到 symbolsource,因为我只是使用 nuget pack ... -Symbol 并推送生成的程序包 ... - Jon Skeet
1
我觉得我应该将这个问答会话提交给NuGet背后的人,看看他们是否能够参与其中。 - gzak
将日志记录集成到您的软件包中,然后仅发布发布版本构建。您可以允许日志记录注入,从而使消费者能够根据自己的喜好设置日志记录。 - CShark
4个回答

32

就SymbolSource而言,我认为最佳实践是:

  1. 只将发布的二进制+内容包推送到nuget.org(或任何其他生产Feed)
  2. 将调试的二进制+内容包推送到开发Feed上:
    • 内部部署
    • 在myget.org上
    • 作为预发布包在nuget.org上
  3. 将发布和调试的二进制+符号包都推送到symbolsource.org或任何其他符号存储库中。

顺便说一句,有一种常见误解,即.NET中的发布版和调试版实际上没有太大区别,但我假设这里的区别是因为可能包含或不包含各种代码,例如Debug.Asserts。

话虽如此,将两个配置都推送到SymbolSource真的很值得,因为你永远不知道何时需要调试生产代码。在生产环境中远程调试会更加困难,当这种情况发生时,你需要从工具中获取尽可能多的帮助。当然,我并不希望任何人遇到这种情况。

仍有一个问题需要考虑:是否将在调试和发布模式下构建的2个不同包共享1个版本号是正确的?如果NuGet允许相应地标记包,SymbolSource将接受这种做法,因为它会提取包并在单独的构建模式分支中存储二进制文件。目前没有办法确定包是调试版本还是发布版本。


“发布两个构建到NuGet”部分是棘手的。我一直发布Noda Time的Release构建,因为我必须在两者之间选择。(我有一个nuspec文件,而不是仅从C#项目中进行操作。)你提到调试生产代码好像只有Release构建会更加困难 - 尽管之前提到它们并没有太大的区别。我知道调试构建中的NOPs,以及显然的Debug.Assert等等 - 但你能否在你的回答中详细说明调试时的影响?使用Release构建有多糟糕? - Jon Skeet
我的意思只是你想要为所有正在使用的二进制文件提供符号:如果有调试和发布版本在使用中,你需要为两者都提供符号。它们在代码方面不需要有太大的区别,但它们将在校验和方面有所不同,这使得在没有一些黑客技巧的情况下加载符号变得不可能。 - TripleEmcoder
1
好的。我的可能解决方案是仅仅发布Release二进制文件,但我担心在调试方面会受到多大影响。例如,缺少NOP是否意味着你不能添加断点? - Jon Skeet
问题主要是在JIT优化而不是编译时优化。所以,如果您推送发布模式包并建议在需要调试时使用COMPLUS_ZapDisable = 1,那对我来说就足够了。但是,NuGet应该以某种方式允许同时使用调试和发布。 - TripleEmcoder
为了Nuget和发布您的软件包的两种配置,只需创建一个.tt(T4模板)文件,在预/后构建步骤中处理它并生成您的NuSpec文件。然后可以将其提供给构建系统。为了帮助您生成正确的NuSpec文件,您可能需要向.tt处理传递一些参数,例如您的配置和更改列表编号。享受吧。 - christ.s
显示剩余3条评论

6

自 OP 发布帖子以来已经过去了8年(上一个回答仍然是下面的最佳答案),所以我将介绍现在使用的方法。

有两种“步入”NuGet包的方式:

1. 分发PDBs

.symbols.nupkg 符号包被认为是遗留问题,已被 .snupkg 包取代,并发布到 Symbol Server。大多数供应商都支持它,Azure DevOps 是最大的例外,其中 feature request 仍在“审核中”(感谢@alv提供的链接)。

这里是官方说明。只需将以下两行添加到csproj文件中即可:
<PropertyGroup>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

在消费者端,确保你的IDE已配置为NuGet.org(或Azure等)符号服务器,以允许在调试时进入包代码
2. Source Link. 链接实际代码
在某些情况下,由于MSIL / JIT优化,PDB可能会隐藏源代码的某些具体信息。因此,在调试时有一种“Step Into” NuGet实际源代码的方法。它称为.NET基金会的Source Link - “提供二进制源代码调试体验的语言和源代码控制不可知系统”。
它受到Visual Studio 15.3+和所有主要供应商的支持(还支持私有存储库)。
它很容易设置(官方文档)-只需将开发依赖项添加到项目文件中(取决于您的存储库类型),并添加一些标志:
<PropertyGroup>
    <PublishRepositoryUrl>true</PublishRepositoryUrl>
    <EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
<ItemGroup>
  <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>

请在 "5个提高NuGet包的步骤" 中查看更多相关内容。


1
snupkg不受Azure DevOps支持,请参见https://developercommunity.visualstudio.com/idea/657354/add-snupkg-support-to-azure-devops-artifacts.html。 - alv
3
问题是:调试版还是发布版? - Pieterjan

4
我完全同意您的结论。NuGet包使用RELEASE版本,而SymbolSource则使用debug版本。直接进入包中似乎相当罕见,启用优化时偶尔出现调试失误可能是可以接受的。
如果真的存在问题,我认为理想的解决方案是让NuGet支持它。例如,想象一下,当进行调试时,它可以用SymbolSource包中包含的那个release DLL文件替换它。
理想情况下,nuget pack SomePackage -Symbols会生成一个发布的NuGet包和一个debug符号包。VS插件将被更新得足够智能,以查看关联并在调试器中运行时拉取Debug程序集并加载这些程序集。有点疯狂,但很有趣。
然而,我目前还没有看到足够多的人抱怨这个问题,所以不值得这么做。
NuGet团队接受pull请求。 :)

我不确定我理解你的第二句话 - 我怀疑在我编辑之前,OP正在手动推送调试版本到SymbolSource。如果发布版本的PDB最终出现在SymbolSource而不是调试版本中,您是否预见到重大问题?还是这就是您提倡的,只是我误解了? - Jon Skeet
3
也许他们没有抱怨,但如果你问一些用户他们对这件事的看法,我敢打赌大多数人会承认在这一步(将什么发布到NuGet.org和SymbolSource.org)有些困惑。如果你问他们最终选择了什么,你可能会发现没有一个统一的做法,每个人都只是随心所欲地做自己的事情。然而,我只是觉得目前还没有足够多的人抱怨这个问题值得去解决它。 - gzak
8
我想投诉这件事,我应该在哪里登记? - Alex
1
一旦您拥有内部NuGets并开始将它们部署到符号服务器上...您不可避免地会想要对它们进行调试...尽管您可以逐步执行代码,但最终您会得到“无法获取本地变量或参数的值...它已被优化掉”。正如Phil所提到的那样...如果Nuget有一种方式在调试模式下加载调试dll,并在发布模式下加载发布dll(从Nuget包中),那将是最好的。在那之前,我们只能使用Nuget包切换器 - felickz
1
我同意,这将是一个不错的产品增强。 - htm11h

1

这个创建和发布符号包的例子中,引用Debug目录中的文件作为dll和pdb文件的源。

指定符号包内容

符号包可以按照约定从一个按照前一节所述方式结构化的文件夹中构建,也可以使用文件部分来指定其内容。如果您想构建先前描述的示例包,您可以将以下内容放入nuspec文件中:

<files>
  <file src="Full\bin\Debug\*.dll" target="lib\net40" /> 
  <file src="Full\bin\Debug\*.pdb" target="lib\net40" /> 
  <file src="Silverlight\bin\Debug\*.dll" target="lib\sl40" /> 
  <file src="Silverlight\bin\Debug\*.pdb" target="lib\sl40" /> 
  <file src="**\*.cs" target="src" />
</files>

发布符号的目的是允许其他人在调试时通过代码,因此最明智的做法是发布一个针对调试而不包含可能影响代码步进的优化的代码版本。


是的,这就是示例的作用 - 但我宁愿不要以调试能力为代价来满足大多数开发人员运行发布版本的需求。问题在于,据我所知,NuGet没有一种同时发布发布版和调试版的方法。(对我来说,同时发布两个版本也会有些麻烦,因为这意味着需要创建另一组配置,用于签名调试构建...) - Jon Skeet
在这里我们涉及到一些主观性。通常,我的“最佳实践”选择将是作者明确或隐含地推荐的,因为我认为他们比我更有深入(或在例子中最终出现的假设的监督)的见解。就我个人而言,我认为符号应该与我使用的版本匹配。我通常不会使用我认为可能需要调试的软件包,除非源代码是公开的,并且如果需要,我可以编译。因此,缺少打包的调试版本并不特别重要。 - tvanfosson
在我的情况下,如果有人需要调试Noda Time,因为他们认为Noda Time存在错误,最好下载源代码(当然他们可以这样做)。然而,能够进入Noda Time源代码以查看正在发生的事情仍然是有用的。基本上,我认为有(至少)两种不同的调试方案,具有不同的解决方案... - Jon Skeet
我喜欢思考的一件事是.NET本身:我认为可以肯定地说,微软发布的是.NET的发行版本而不是调试版本。发行版本意味着一定程度的质量和稳定性,因此想法是你很少需要进入代码来诊断问题。 - gzak
3
然而,这也是一种相当“封闭源代码”的心态。我发现在开源世界中,人们更希望能够进入各种库的“稳定版本”代码,不是因为有什么问题,而是因为有时候最好的文档就是看看库在做什么。 - gzak
也许发布版和调试版之间的整个区别是有问题的?换句话说,也许微软应该考虑删除这种区别,只发布一个库的构建版本(并让版本号+文档指定什么是稳定的,什么不是)。 - gzak

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