如何在 MSBuild 中排除(禁用)PackageReference 的(传递)依赖性?

how to exclude(disable) PackageReference's (transitive)dependency in MSBuild?

我正在使用一个包 Xamanimation,它具有 Xamarin.Forms 4.1.0 的依赖项(写在它的 nuspec 文件中):

    <dependencies>
      <group targetFramework=".NETStandard2.0">
        <dependency id="Xamarin.Forms" version="4.1.0.581479" exclude="Build,Analyzers" />
      </group>
    </dependencies>

但我已经为自己构建了 Xamarin.Froms 并将输出 dll 文件添加到我的项目参考中:

  <Reference Include="Xamarin.Forms.Xaml">
      <HintPath>..\thirdparty\xforms\Xamarin.Forms.Xaml.dll</HintPath>
    </Reference>

根据 nuget's doc,我将 ExcludeAssets 属性(和其他测试)添加到 PackageReference of Xamanimation 的部分:

    <PackageReference Include="Xamanimation">
      <IncludeAssets>compile</IncludeAssets>
      <!-- <ExcludeAssets>compile</ExcludeAssets> -->
      <!-- <PrivateAssets>all</PrivateAssets> -->
      <!-- <ExcludeAssets>buildtransitive</ExcludeAssets> -->
      <Version>1.3.0</Version>
    </PackageReference>

但其中 none 有效!

MSBuild 将始终使用传递依赖项 Xamarin.Forms.4.1.0 并忽略我自己构建的(我在其中添加了新的 类 并在主项目中使用它们所以链接失败表明选择的是旧的)。

那么排除传递依赖的正确方法是什么?

我花了一整天的时间研究这个问题,终于得到了一个合理的答案。

所以我想 post 我在 Whosebug 中的第一个长答案是我糟糕的英语。

TL'DR:

MSBuildnuget pluginResolveNuGetPackageAssets目标做坏事,制作一个自定义目标来恢复它,到任务代码的底部。


学习故事

首先,我把这个问题做了一个类似但更简单的副本来学习。

毕竟建一个xamarin项目太慢了,

演示源在github, 它有四个项目:

  • ConflictLib:另一个库和主应用程序都使用的库
  • DirectLib:主应用程序使用的库,正在使用 ConflictLib
  • MyAppDev: 主程序,以上两个库为ProjectReference
  • MyAppConsumer:另一个应用程序,PackageReference 使用 DirectLibProjectReference 使用 ConflictLib。为了测试这种情况,我将 ConflictLib 和 DirectLib 推送到 nuget.org,然后修改了本地版本的 ConflictLib,这样我就可以验证正在使用哪个。

这些项目及其关系与我的原始问题非常相似,关键点是:当应用程序同时使用具有两个不同版本的库时,本地版本(ProjectReference 或 HintPath)是否会(应该)获胜?

我的原生案例,xamarin项目,No,所以来研究一下

测试用例,一个dotnet core console项目,是Yes,所以在构建过程中一定有玄机:MSBuild,这是一个庞大的系统,但是现在我'我要深入研究它。

然后,我需要一个检查工具来找出 MSBuild 在构建项目时做了什么。

这个简单的工具只是在命令行中调用它,它会显示所有执行的targetsmsbuild target 类似于 a target in makefile,目标中的 tasks 类似于 commands in makefile,这个概念在许多其他系统中存在,但术语略有不同,例如 gradle,所以很容易理解。

但是,有这么多的目标和任务,而且它们都依赖于其他人并且通过 PropertyItems 进行交互,很难从文本日志中了解哪个目标打破了我的需要。

幸运的是,有一个高级工具可以检查 MSBuild 中的所有内容:它叫做 MSBuild structured log viewer, I learn it from here

现在使用 /bl 选项构建项目,它将生成一个包含完整信息的二进制日志文件,用上面提到的查看器打开它:(我的原始 xamarin 项目的构建日志)

很明显,ResolveNuGetPackageAssets 目标更改了 Reference 项,这决定了最终的链接库程序集。

但是为什么它在测试用例中没有做出错误的决定呢?让我们查看它的日志:

有区别吗? -- 没有 ResolveNuGetPackageAssets 目标!

ResolveReferencesResolveAssemblyReferences 是一样的,但是在 nuget 部分不同。

双击 ResolveAssemblyReferences,查看器将打开 targets file,其中定义了目标。

C:\Program Files (x86)\Microsoft Visual Studio19\Community\MSBuild\Current\Bin\Microsoft.Common.CurrentVersion.targets

两种情况仍然相同:

ResolveAssemblyReferences不依赖于ResolveNuGetPackageAssets,那么后者从何而来?只需单击它,文件就会打开:

C:\Program Files (x86)\Microsoft Visual Studio19\Community\MSBuild\Microsoft\NuGet.0\Microsoft.NuGet.targets

它覆盖 ResolveAssemblyReferencesDependsOn 并将 ResolveNuGetPackageAssets 添加到 ResolveAssemblyReferences 的依赖项。

最后一个问题:为什么上面的NuGet.targets文件没有出现在测试用例中?它仍然可以由观众的 Evaluation 部分回答:

很明显,此文件未导入,因为 属性 SkipImportNuGetBuildTargets 设置为 true。经过简单的搜索,我确认这是测试用例中的默​​认值:它设置在Microsoft.NET.Sdk.targets中。但在 xamarin 的情况下,它没有设置并且意味着 false,所以所有的事情都发生了。

最后,我得想办法解决这个问题了。

首先,我不会把SkipImportNuGetBuildTargets 属性加入到xamarin项目中,因为我觉得这是一个框架设计,可能对其他人影响很大,我只是想修复一个具体的小问题。

我决定在 ResolveAssemblyReferences 之后立即添加一个自定义目标,删除 Nuget's Xamarin.Forms 并添加我自己的 - 只需恢复 ResolveNuGetPackageAssets 所做的。

任务代码很简单(刚写完,其实花了我很多时间搜索grammar/builtinfunction/etc测试):

注意 Remove Item(参见 msbuild doc)是如何工作的(而不是工作:注释行),我仍然不完全理解它,但它确实工作了!