如何使用csproj构建C#源代码组件NuGet包?

张裕  高级会员 | 2024-6-20 11:14:14 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 213|帖子 213|积分 639

一般我们构建传统的NuGet包,都是打包和分发dll程序集文件。
至于打包和分发C#源代码文件的做法,比较少见。
那么这种打包源代码文件的做法,有什么长处和缺点呢?
长处:

  • 方便阅读源代码。
  • 方便断点调试。
  • 淘汰 Assembly 程序集模块加载个数。
  • 更利于发布期间的剪裁(PublishTrimmed 选项)。
  • 更利于混淆和保护代码(Internal 级别的源代码)。
缺点:

  • 容易外泄原始的源代码文件。
  • 随着引入源代码组件越多,越容易引发命名空间和范例名称重复辩论。
履历:

  • 不发起也不推荐分发 public 级别的源代码。
  • 尽大概严格规范命名范例名称。
  • 向目的项目写入源代码组件 version 和 git commit sha-1,方便出问题时排查版本问题。
  • 每次改动源代码文件时,尽大概做到向下兼容。
正文:
接下来,我们一起看看如何制作仅打包C#源代码文件,不打包dll程序集文件的C#源代码组件NuGet包。
起首是创建 AllenCai.BuildingBlocks 项目,目录结构如下:
  1. .
  2. ├── build
  3. └── src
  4.     ├── AllenCai.BuildingBlocks
  5.     │   ├── AllenCai.BuildingBlocks.csproj
  6.     │   ├── Properties
  7.     │   │   ├── PackageInfo.cs
  8.     │   ├── Assets
  9.     │   │   ├── build
  10.     │   │   │   └── AllenCai.BuildingBlocks.targets
  11.     │   │   └── buildMultiTargeting
  12.     │   │       └── AllenCai.BuildingBlocks.targets
  13.     │   ├── Collections
  14.     │   │   ├── ArrayBuilder.cs
  15.     │   │   ├── other...
  16.     │   ├── Functional
  17.     │   │   ├── Result.cs
  18.     │   │   ├── other...
  19.     │   ├── ObjectPooling
  20.     │   │   ├── DictionaryPool.cs
  21.     │   │   ├── other...
  22.     │   ├── Text
  23.     │   │   ├── StringBuffer.cs
  24.     │   │   ├── other...
  25.     │   ├── Threading
  26.     │   │   ├── ValueTaskEx.cs
  27.     │   │   ├── other...
  28.     │   ├── bin
  29.     │   │   ├── Release
  30.     │   │   │   └── other...
  31.     │   │   ├── Debug
  32.     │   │   │   └── other...
  33.     │   └── obj
  34.     │   │   ├── other...
  35.     │   ├── icon.png
  36.     │   ├── other...
  37.     ├── AllenCai.BuildingBlocks.sln
  38.     └── Directory.Build.targets
  39. ├── .gitattributes
  40. ├── .gitignore
  41. ├── README.md
复制代码
其中 Directory.Build.targets 文件,用来生成描述源代码组件包版本信息的C#源代码文件,输出文件路径为:Properties\PackageInfo.cs。
之所以输出到 Properties 目录,是因为 PackageInfo.cs 的作用其实和以前 .NET Framework 时代每个项目都会包含的 AssemblyInfo.cs 相同。
那么,为什么需要生成这个 PackageInfo.cs 文件呢?

  • 因为不再是编译和发布dll,而是直接打包和提供源代码文件,原本被内嵌到dll程序集的版本信息是丢失的。
  • 懒,也不盼望每次手工维护写入 Version 和 git commit sha-1。
Directory.Build.targets 文件代码如下所示:
  1. <Project>
  2.   
  3.   <Target Name="GeneratePackageInfoToFile" BeforeTargets="PreBuildEvent" Condition="'$(Configuration)' == 'Release'">
  4.     <PropertyGroup>
  5.       <SharedPackageInfoFile>$(ProjectDir)Properties\PackageInfo.cs</SharedPackageInfoFile>
  6.     </PropertyGroup>
  7.     <ItemGroup>
  8.       <AssemblyAttributes Include="AssemblyMetadata">
  9.         <_Parameter1>PackageVersion</_Parameter1>
  10.         <_Parameter2>$(Version)</_Parameter2>
  11.       </AssemblyAttributes>
  12.       <AssemblyAttributes Include="AssemblyMetadata">
  13.         <_Parameter1>PackageBuildDate</_Parameter1>
  14.         <_Parameter2>$([System.DateTime]::Now.ToString("yyyy-MM-dd HH:mm:ss"))</_Parameter2>
  15.       </AssemblyAttributes>
  16.       <AssemblyAttributes Include="AssemblyMetadata" Condition="'$(SourceRevisionId)' != ''">
  17.         <_Parameter1>PackageSourceRevisionId</_Parameter1>
  18.         <_Parameter2>$(SourceRevisionId)</_Parameter2>
  19.       </AssemblyAttributes>
  20.     </ItemGroup>
  21.     <MakeDir Directories="$(ProjectDir)Properties"/>
  22.     <WriteCodeFragment Language="C#" OutputFile="$(SharedPackageInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" />
  23.     <Message Importance="high" Text="SharedPackageInfoFile --> $(SharedPackageInfoFile)" />
  24.     <ItemGroup>
  25.       <Compile Include="$(SharedPackageInfoFile)" Pack="true" BuildAction="Compile" />
  26.     </ItemGroup>
  27.   </Target>
  28. </Project>
复制代码
而 AllenCai.BuildingBlocks.targets 文件,将会被打包到NuGet包。
当这个包被添加引用到目的项目中,MsBuild 将会自动调用它,实行一系列由你定义的动作。
那么,又为什么需要这个 AllenCai.BuildingBlocks.targets 文件呢?
它其实黑白必须的,根据项目现真相况而定,没有这个 targets 文件也是可以的。
但这样的话,大概引用这个源代码组件包的开发者会在刚引入时碰到一系列问题,导致这个源代码组件包对开发者不友好。
比如源代码文件中使用了不安全代码,而目的项目的属性值是 false,那么目的项目在编译时就会报错。
因此需要这个 targets 文件来查抄和自动设置为 true。
如以下示例代码(build\AllenCai.BuildingBlocks.targets):
  1. <Project>
  2.   <Target Name="UpdateLangVersionAndAllowUnsafeBlocks" BeforeTargets="BeforeCompile">
  3.     <PropertyGroup>
  4.       <OldAllowUnsafeBlocks>$(AllowUnsafeBlocks)</OldAllowUnsafeBlocks>
  5.       <AllowUnsafeBlocks Condition=" '$(AllowUnsafeBlocks)' == '' or $([System.String]::Equals('$(AllowUnsafeBlocks)','false','StringComparison.InvariantCultureIgnoreCase')) ">True</AllowUnsafeBlocks>
  6.     </PropertyGroup>
  7.    
  8.     <Message Importance="high" Condition=" '$(AllowUnsafeBlocks)' != '$(OldAllowUnsafeBlocks)' " Text="Update AllowUnsafeBlocks to $(AllowUnsafeBlocks)" />
  9.   </Target>
  10. </Project>
  11. 以及 buildMultiTargeting\AllenCai.BuildingBlocks.targets 文件代码如下所示:
  12. <Project>
  13.   <Import Project="..\build\AllenCai.BuildingBlocks.targets" />
  14. </Project>
复制代码
需要注意的是,这个 targets 文件需要与 ProjectName 或 PackageId 保持一致。
最后 AllenCai.BuildingBlocks.csproj 文件代码如下所示:
  1. <Project Sdk="Microsoft.NET.Sdk">
  2.   <PropertyGroup>
  3.     <TargetFrameworks>net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
  4.     <LangVersion>default</LangVersion>
  5.     <Nullable>enable</Nullable>
  6.     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  7.     <ImplicitUsings>disable</ImplicitUsings>
  8.     <ProduceReferenceAssembly>false</ProduceReferenceAssembly>
  9.     <GenerateDocumentationFile>false</GenerateDocumentationFile>
  10.     <Version>0.0.1</Version>
  11.   </PropertyGroup>
  12.   
  13.   <PropertyGroup>
  14.     <Title>AllenCai BuildingBlocks</Title>
  15.     <Description>提供一组最常用的通用软件模块,以文件链接的方式被包含到引用项目中。</Description>
  16.     <Authors>Allen.Cai</Authors>
  17.     <Copyright>Copyright © Allen.Cai 2015-$([System.DateTime]::Now.Year) All Rights Reserved</Copyright>
  18.     <ContentTargetFolders>contentFiles\cs\any\AllenCai.BuildingBlocks;content\cs\any\AllenCai.BuildingBlocks</ContentTargetFolders>
  19.    
  20.     <DevelopmentDependency>true</DevelopmentDependency>
  21.    
  22.     <IncludeBuildOutput>false</IncludeBuildOutput>
  23.    
  24.    
  25.    
  26.     <NoPackageAnalysis>true</NoPackageAnalysis>
  27.     <PackageProjectUrl>http://192.168.1.88:5555/allen/allencai.buildingblocks/</PackageProjectUrl>
  28.     <PackageReadmeFile>README.md</PackageReadmeFile>
  29.     <RepositoryUrl>http://192.168.1.88:5555/allen/allencai.buildingblocks.git</RepositoryUrl>
  30.     <RepositoryType>git</RepositoryType>
  31.     <PackageIcon>icon.png</PackageIcon>
  32.   </PropertyGroup>
  33.   <ItemGroup>
  34.    
  35.     <None Include="icon.png" Pack="true" PackagePath="" />
  36.     <None Include="..\..\README.md" Link="README.md" Pack="true" PackagePath="" />
  37.     <Content Include="**\*.cs" Exclude="obj\**\*.cs" Pack="true" BuildAction="Compile" />
  38.   </ItemGroup>
  39. </Project>
复制代码
其中三个属性比较重要,DevelopmentDependency 和 IncludeBuildOutput 以及 ContentTargetFolders。

  • 将 DevelopmentDependency 设置为 true,表示这个 NuGet 包仅在开发期间依赖​。
  • 将 IncludeBuildOutput 设置为 false,表示打包时不包含编译输出的 dll​ 文件。
  • 重写 ContentTargetFolders,将会改变这些源代码文件在目的项目中的假造​文件体系布局。
如有不明白,欢迎留言,互相探讨。
停止本文,我刚搜到有 MVP大佬-吕毅 也写了类似教程,​大家也可以参考:从零开始制作 NuGet 源代码包(全面支持 .NET Core / .NET Framework / WPF 项目) - walterlv

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

张裕

高级会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表