首页 > 解决方案 > 在具有大量 nuget 引用的大型 Xamarin.Forms 项目中,我在执行 UWP 的 .NET Native 版本构建时遇到 rhbind RHB0002 错误

问题描述

我认为这首先发生在我们更新 nuget 引用时,因为我可以返回并检查这些提交,并且仍然构建我们的 UWP 应用程序的 .NET Native 版本。

在 rhbind 步骤失败之前,构建本身大约需要 20-30 分钟(在 Azure DevOps 管道上将近一个小时)。

  Microsoft.NetNative.targets(805, 5): RHBIND : error RHB0002: Failed to write PDB.
  Microsoft.NetNative.targets(805, 5): ILT0005: 'C:\Users\chris.palmer\.nuget\packages\runtime.win10-x64.microsoft.net.native.compiler\2.2.10-rel-29722-00\tools\x64\ilc\Tools\rhbind.exe @"C:\Projects\FGX\FGX.UWP\obj\x64\Release.NetNative\ilc\intermediate\rhbindargs.FGX.UWP.rsp"' returned exit code 2

我试过注释掉

<Assembly Name="*Application*" Dynamic="Required All" />

default.rd.xml 文件中的行,它对我看到的错误没有影响。

我还尝试了几个建议的项目文件构建选项,例如<ShortcutGenericAnalysis>true</ShortcutGenericAnalysis><UseDotNetNativeSharedAssemblyFrameworkPackage>false</UseDotNetNativeSharedAssemblyFrameworkPackage>.

我从最后一个好的构建分支出来并一个一个地更新了引用,并且能够更新所有的包而不会失败,但是当我将那些 .csproj 文件从分支放回我们的开发分支时,错误重新 -出现了(那里有一些新的引用和一些代码更改)。所以,我相对确定它是其中之一,但有什么办法知道吗?是否有可能导致这种情况的代码更改?

我已经没有地方可以查看了,每次构建需要 30 分钟来测试它,它已经过时了。欢迎任何提示、技巧、建议或资源。不幸的是,需要 .NET Native 编译才能将我们的下一个版本发布到 Windows 应用商店。

编辑:我去了最后一次已知的良好构建,更新了 nuget 包,构建没有问题。将这些 .csproj 文件移至最新提交,并且在不更改项目文件的情况下,我得到了同样的错误。很确定它本身不是包裹。有人知道可能导致这种情况的代码更改吗?

标签: xamarin.formsuwpmsbuildxamarin.uwp.net-native

解决方案


这是一个复杂的问题,即使我以前从未见过它,我也可以帮助调试 msbuild 管道来解决它。首先,我们应该有一个计划。我建议:

  1. 建立理论
  2. 通过一些修改(即诊断手术)执行一系列测试运行
  3. 分析结果以确定哪种理论更适合
  4. 找到合适的解决方案

由于您的艺术状态的大小,它可能需要几天的时间。“诊断手术”和“找到合适的解决方案”之间的区别在于,在手术期间,我们可以自由地做任何事情,甚至是不适用或不能被生产构建接受的事情,比如禁用并行性。

建立理论

好的,从 msbuild 的角度来看,我看到的是“无法写入 PDB”。错误。很少有理论可以立即提出来。文件访问并发。MSBuild 在 IDE 和 prod 管道中并行构建项目,因此在编写文件时很容易面临竞争条件,尤其是大文件或大型解决方案。但是有一组内置保护,例如最广泛使用的“复制”任务消耗来自 $(CopyRetryDelayMilliseconds) 变量的参数 RetryDelayMilliseconds 和来自 $(CopyRetryCount) 的重试。这样,例如当一个大解决方案有一个大共享的 bin 文件夹时,一些节点可以同时写入相同的文件,这可以通过这个变量轻松解决。并且这个变量被其他任务以及来自 SDK 目标的 CreateAppHost 等使用。对于这么大的解决方案,我绝对建议无论如何都要设置它,它不会受到伤害,但可以在某处发生轻微并发的情况下节省大量机器时间。但在应用到这个特定的解决方案之前,我们必须分析Microsoft.NetNative.targets line 805.

好的,在查看您的错误的下一行时,我可以看到您正在使用 .NetNative 来自 nugget 包,并且有一个版本“2.2.10-”,它是一个预发布版本,让我下载并查看。我绝对不建议使用预发布版本运行 prod 构建,除非这是一个确切的目标——预先测试集成。这可能是 2 月 8 日出现的错误版本。现在我可以看到它是“LoggerBasedExecTask”被执行,并且上面提到的变量没有传递给这个任务。现在我也知道执行的目标是“BuildNativePackage”。我们可以隔离所有“BuildNativePackage”阶段,在这里禁用并行以避免并发。事实上,如果应用到适当的阶段,这甚至可以加快构建速度,因为这个阶段可能会在相同的工件上多次执行。但是这个阶段没有需要优化的输入和输出,因此您可以稍后获取诊断日志以构建更好的图像,在哪个阶段应用挂钩以禁用并行性,这些阶段应该具有输入和输出参数以利用这一点。整个文件是 1000 行 msbuild 代码,所以对我来说看起来不错,不是世界末日,可以分析任何问题甚至优化它。此外,该文件根本没有使用任何重试机制,没有特定于并行的属性,没有“输入”/“输出”,因此看起来没有得到很好的优化。我们必须尽量避免并行运行 BuildNativePackage 目标。整个文件是 1000 行 msbuild 代码,所以对我来说看起来不错,不是世界末日,可以分析任何问题甚至优化它。此外,该文件根本没有使用任何重试机制,没有特定于并行的属性,没有“输入”/“输出”,因此看起来没有得到很好的优化。我们必须尽量避免并行运行 BuildNativePackage 目标。整个文件是 1000 行 msbuild 代码,所以对我来说看起来不错,不是世界末日,可以分析任何问题甚至优化它。此外,该文件根本没有使用任何重试机制,没有特定于并行的属性,没有“输入”/“输出”,因此看起来没有得到很好的优化。我们必须尽量避免并行运行 BuildNativePackage 目标。

诊断手术

好的,最简单的开始方法就是完全禁用并行构建,看看它是否有助于证明我们的方向是正确的。为此,您应该更改您的 Azure 管道(创建 DRAFT)。如果您使用 msbuild 构建,我们只需从中删除“-m”。如果您使用“donnet build”进行构建,则有 --disable-parallel。这将增加构建时间。

分析结果

到目前为止,这是唯一的理论,所以,让我们看看您的评论,希望这不会过多地增加构建时间(我通常的预期是〜30%,因为即使是大型解决方案也经常由于许多小型独立项目和很少数量的非常大的难以构建并且需要大部分时间的)。

找到合适的解决方案

不确定您要升级的 nugget 包是否是 Microsoft.Net.Native.Compiler 本身以预发布 2.2.10-rel,如果是这样,我认为这是一个坏主意。无论如何,我们可以尝试添加一些钩子来确保我们想要的目标不会并行运行。事实上,现在描述适当的解决方案还为时过早,您可以将其视为手术的另一个阶段,我们将拭目以待。尝试将此 Directory.Build.targets 放在您的解决方案附近。我已经在我的人工案例中测试了这个,并发文件写入解决了这个问题。

<Project>
  <Target Name="SyncBuildStart" BeforeTargets="BuildNativePackage">
    <Message Text="SyncBuildStart" Importance="high" />
    <SyncBuildStart Identity="$(MSBuildProjectName)" />
    <Message Text="SyncBuildStartDone" Importance="high" />
  </Target>

  <UsingTask TaskName="SyncBuildStart" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
    <ParameterGroup>
        <Identity ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Using Namespace="System.Threading" />
      <Code Type="Fragment" Language="cs">
        <![CDATA[       
            var ev = new ManualResetEvent(false);
            ThreadPool.QueueUserWorkItem(delegate
            {
                // Mutex holder
                using var evStop = new EventWaitHandle(false, EventResetMode.AutoReset , "BuildLockStop_" + Identity);
                try
                {
                    using var mx = new Mutex(false, "BuildNativePackage_BuildLock");
                    mx.WaitOne();
                    ev.Set(); // notify main thread
                    evStop.WaitOne();
                    mx.ReleaseMutex();
                }
                catch (Exception ex)
                {
                    // Add Log to file here if needed
                }
            });
            ev.WaitOne();
]]>
      </Code>
    </Task>
  </UsingTask>

  <Target Name="SyncBuildEnd" AfterTargets="BuildNativePackage">
    <Message Text="SyncBuildEnd" Importance="high" />
    <SyncBuildEnd Identity="$(MSBuildProjectName)"/>
    <Message Text="SyncBuildEndDone" Importance="high" />
  </Target>

  <UsingTask TaskName="SyncBuildEnd" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
    <ParameterGroup>
        <Identity ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Using Namespace="System.Threading" />
      <Code Type="Fragment" Language="cs">
        <![CDATA[
        using var evStop = EventWaitHandle.OpenExisting("BuildLockStop_" + Identity);
        evStop.Set();
        ]]>
      </Code>
    </Task>
  </UsingTask>
</Project>

在这里您可以进行微调,例如锁定此目标的任何入口或锁定每个项目,锁定另一个上层目标等。

祝你好运,我希望如果这不是结束,至少它是体面的潜水更深!


推荐阅读