微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在包含大量 nuget 引用的大型 Xamarin.Forms 项目中,我在执行 UWP 的 .NET Native 版本构建时遇到 rhbind RHB0002 错误

如何解决在包含大量 nuget 引用的大型 Xamarin.Forms 项目中,我在执行 UWP 的 .NET Native 版本构建时遇到 rhbind RHB0002 错误

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

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

  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 文件移动到最新提交,并且在没有更改项目文件的情况下,我得到了同样的错误。很确定这不是包裹本身。有人知道可能导致这种情况的代码更改吗?

解决方法

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

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

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

建立理论

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

好的,从您的错误中查看下一行时,我可以看到您正在从 nugget 包中使用 .NetNative 并且有一个版本“2.2.10-”,这是一个预发布版本,让我下载并看。我绝对不建议在预发布时运行 prod 构建,除非这是这里的确切目标——预先测试集成。这可能是 2 月 8 日出现的错误版本。现在我可以看到它是“LoggerBasedExecTask”被执行并且上面提到的变量没有传递给这个任务。现在我也知道执行的目标是“BuildNativePackage”。我们可以隔离所有“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>

在这里你可以微调一些东西,例如锁定此目标的任何入口或锁定每个项目,锁定另一个上级目标等。

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。