默认的 PackageReference 可以实现传递依赖,传递依赖的含义是是假定 B 项目安装了 A 库,而 C 项目依赖 B 项目,那么 C 项目将会自然拿到 A 库的 DLL 引用。但默认的 NuGet 包的构建指导文件 targets 命令是不会在传递执行的,也就是如上的 C 项目将不会执行 B 项目安装的 A 库里面的 target 内容 有一些项目需要拷贝自定义文件,例如拷贝图片或者一些 Native 的 DLL 等资源。如 WPF 框架需要拷贝 PenIME 等资源。如果只是在最底层的项目安装了库,那为了让可执行文件项目也输出库的资源,就需要在可执行项目上也安装库。以上的方法的不足在于安装复杂,也许会忘记安装 本文告诉大家一个解决方法是通过在制作库的时候,加上 BuildTransitive 文件夹,在此文件夹内添加构建指导文件,此时这个构建指导文件 targets 文件里面的命令将会在传递中执行,也就是说只需要在底层的项目安装即可,不需要在可执行项目上也安装库

我写了很多 Rolsyn 的关于打包 NuGet 包相关的博客,如 Roslyn 打包自定义的文件到 NuGet 包 这一篇。在这个博客告诉大家如何打包自定义的文件到 NuGet 包,和将自定义的文件放在输出文件夹里面

但是以上方法存在的问题如上文,只有制定了 PackageReference 安装此库的项目,才能将自定义的文件输出。其他的项目,如果只是做传递引用,没有直接在 csproj 上写 PackageReference 安装此库,那么不会拿到自定义输出文件

如果此时自定义的文件是 Native 的 DLL 资源,而只有在底层的库安装了库,那就会让可执行项目输出文件夹没有这些 Native 的 DLL 内容,在运行的时候找不到 Native 的 DLL 文件

为了解决此问题,咱可以将原先写在 Build 文件夹下的 targets 等文件,修改放在 NuGet 包的 BuildTransitive 文件夹。除此之外,其他逻辑都和之前相同

也就是说如果期望在引用传递中,每个引用的项目都会执行到 NuGet 里面的 targets 和 props 文件的命令,只需要将 targets 和 props 文件放入到 BuildTransitive 文件夹即可。其他逻辑都和之前相同

敲黑板,只需要将原本放在 NuGet 里面的 Build 文件夹,重命名为 BuildTransitive 即可

以下是告诉大家如何制作的方法,以及更多细节。本文的代码可以在本文的最后拿到,建议大家试一下本文的测试代码

回顾一下通过 csproj 创建 NuGet 包的方法,如 Roslyn 打包自定义的文件到 NuGet 包 的方法,咱创建一个叫 BuildTransitivePackage 的控制台项目,为了加上指导构建的 targets 等文件,咱需要新建一个 Build 文件夹,在里面放两个文件,文件名如下

  • Package.props
  • Package.targets

为了方便演示,咱来存放一张 Xamarin 的启动图片作为资源文件。期望是在软件的输出文件夹里面可以找到这张图片,换句话说就是这张图片需要放入到 NuGet 包中,接着输出到安装了此库的项目的输出文件夹

此图片文件放在 Assets 文件夹下,默认的启动图片是 SplashScreen.scale-400.png 文件

  • Assets\SplashScreen.scale-400.png

接下来是本文的重点,咱来新建 BuildTransitive 文件夹来测试此功能,同样放入 targets 和 props 文件

  • BuildTransitive\Package.props
  • BuildTransitive\Package.targets

此时的项目文件大概如下

│  BuildTransitivePackage.csproj
│  BuildTransitivePackage.sln
│  Program.cs
│
├─Assets
│      SplashScreen.scale-400.png
│
├─Build
│      Package.props
│      Package.targets
│
└─BuildTransitive
        Package.props
        Package.targets

根据 NuGet 的规则,需要让放在 Build 文件夹的 targets 和 props 文件的命名和 NuGet 包的命名相同,咱可以在 csproj 上做一些更改,让以上的 targets 和 props 文件放入到 NuGet 包

  <ItemGroup>
    <None Include="Build\Package.targets" Pack="True" PackagePath="\build\$(PackageId).targets" />
    <None Include="Build\Package.props" Pack="True" PackagePath="\build\$(PackageId).props" />
  </ItemGroup>

同理对 BuildTransitive 文件夹的 targets 和 props 文件也输出到 NuGet 包

  <ItemGroup>
     <None Include="BuildTransitive\Package.targets" Pack="True" PackagePath="\buildTransitive\$(PackageId).targets" /> 
     <None Include="BuildTransitive\Package.props" Pack="True" PackagePath="\buildTransitive\$(PackageId).props" /> 
  </ItemGroup>

接下来将图片文件也打包放入到 NuGet 文件里面,为了方便测试,咱将图片复制两次,作为 Image1.png 和 Image2.png 两个文件。此时就可以测试两个不同的方法,分别是在 Build 文件夹和在 BuildTransitive 文件夹里面的 Package.targets 拷贝这两个文件。此时可以通过输出文件夹里面能找到哪些 png 文件来了解,放在 Build 文件夹和在 BuildTransitive 文件夹里面的构建命名的执行

    <None Include="Assets\SplashScreen.scale-400.png" Pack="True" PackagePath="\content\Image1.png" />
    <None Include="Assets\SplashScreen.scale-400.png" Pack="True" PackagePath="\content\Image2.png" />

修改之后的 csproj 文件内容如下

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <Version>1.0.19</Version>
  </PropertyGroup>

  <ItemGroup>
    <None Include="Assets\SplashScreen.scale-400.png" Pack="True" PackagePath="\content\Image1.png" />
    <None Include="Assets\SplashScreen.scale-400.png" Pack="True" PackagePath="\content\Image2.png" />
    <None Include="Build\Package.targets" Pack="True" PackagePath="\build\$(PackageId).targets" />
    <None Include="Build\Package.props" Pack="True" PackagePath="\build\$(PackageId).props" />

     <None Include="BuildTransitive\Package.targets" Pack="True" PackagePath="\buildTransitive\$(PackageId).targets" /> 
     <None Include="BuildTransitive\Package.props" Pack="True" PackagePath="\buildTransitive\$(PackageId).props" /> 
  </ItemGroup>
</Project>

接下来在 Build\Package.targets 添加如下代码,用于将 Image1.png 文件输出到安装了库的项目的输出文件夹

<Project>
  <Target Name="CopyImage1File" BeforeTargets="AfterCompile">
     <Copy SourceFiles="$(MSBuildThisFileDirectory)..\content\Image1.png" 
           DestinationFolder="$(OutputPath)" SkipUnchangedFiles="True" />
  </Target>
</Project>

同理在 BuildTransitive\Package.targets 添加如下代码,用于将 Image2.png 拷贝到输出文件夹

<Project>
  <Target Name="CopyImage2File" BeforeTargets="AfterCompile">
    <Copy SourceFiles="$(MSBuildThisFileDirectory)..\content\Image2.png"
          DestinationFolder="$(OutputPath)" SkipUnchangedFiles="True" />
  </Target>
</Project>

打包 BuildTransitivePackage 项目,可以看到输出的 NuGet 包里面的文件结构如下

│  BuildTransitivePackage.nuspec
│  [Content_Types].xml
│
├─build
│      BuildTransitivePackage.props
│      BuildTransitivePackage.targets
│
├─buildTransitive
│      BuildTransitivePackage.props
│      BuildTransitivePackage.targets
│
├─content
│      Image1.png
│      Image2.png
│
├─lib
│  └─net5.0
│          BuildTransitivePackage.dll
│          BuildTransitivePackage.runtimeconfig.json
│
├─package
│  └─services
│      └─metadata
│          └─core-properties
│                  3dbfc6f3466b4a8a9662add17409b308.psmdcp
│
└─_rels
        .rels

接着新建两个测试使用的项目,分别是 BuildTransitivePackage.Foo1BuildTransitivePackage.Foo2 项目

BuildTransitivePackage.Foo1 项目安装 BuildTransitivePackage 的 NuGet 库,让 BuildTransitivePackage.Foo2 项目引用 BuildTransitivePackage.Foo1 项目

可以看到 BuildTransitivePackage.Foo1 项目的 csproj 文件内容大概如下

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="BuildTransitivePackage" Version="1.0.19" />
  </ItemGroup>

</Project>

BuildTransitivePackage.Foo2 项目的 csproj 文件内容大概如下

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\BuildTransitivePackage.Foo1\BuildTransitivePackage.Foo1.csproj" />
  </ItemGroup>

</Project>

以上的 BuildTransitivePackage.Foo2 没有通过 PackageReference 安装 BuildTransitivePackage 库。但是通过传递依赖,依然可以访问到 BuildTransitivePackage 库里面的 DLL 定义类型

此时尝试生成一下 BuildTransitivePackage.Foo2 然后去到输出文件夹

可以在输出文件夹找到 Image2.png 文件,证明放在 BuildTransitive 文件夹的 Package.targets 文件有在执行

但是没有找到 Image1.png 文件,证明放在 Build 文件夹的 Package.targets 文件是没有被执行,符合预期

本文所有代码放在 githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 1658ceb689b8285242e1411e2104643409facbc7

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

获取代码之后,进入 BuildTransitivePackage 文件夹

更多关于 Roslyn 请看 手把手教你写 Roslyn 修改编译

官方文档 Allow package authors to define build assets transitive behavior · NuGet/Home Wiki


本文会经常更新,请阅读原文: https://blog.lindexi.com/post/Roslyn-%E6%89%93%E5%8C%85-NuGet-%E5%8C%85-BuildTransitive-%E6%96%87%E4%BB%B6%E5%A4%B9%E7%94%A8%E4%BA%8E%E7%A9%BF%E9%80%8F%E4%BE%9D%E8%B5%96%E4%BC%A0%E9%80%92%E6%8B%B7%E8%B4%9D%E6%96%87%E4%BB%B6.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者收藏我的博客导航

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系

微软最具价值专家


无盈利,不卖课,做纯粹的技术博客

以下是广告时间

推荐关注 Edi.Wang 的公众号

欢迎进入 Eleven 老师组建的 .NET 社区

以上广告全是友情推广,无盈利