首页 > 编程语言 >如何使用csproj构建C#源代码组件NuGet包?

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

时间:2024-06-19 09:43:58浏览次数:26  
标签:文件 C# csproj AllenCai BuildingBlocks cs 源代码 targets

一般我们构建传统的NuGet包,都是打包和分发dll程序集文件。

至于打包和分发C#源代码文件的做法,比较少见。

那么这种打包源代码文件的做法,有什么优点和缺点呢?

优点:

  1. 方便阅读源代码。
  2. 方便断点调试。
  3. 减少 Assembly 程序集模块加载个数。
  4. 更利于发布期间的剪裁(PublishTrimmed 选项)。
  5. 更利于混淆和保护代码(Internal 级别的源代码)。

缺点:

  1. 容易外泄原始的源代码文件。
  2. 随着引入源代码组件越多,越容易引发命名空间和类型名称重复冲突。

经验:

  1. 不建议也不推荐分发 public 级别的源代码。
  2. 尽可能严格规范命名类型名称。
  3. 向目标项目写入源代码组件 version 和 git commit sha-1,方便出问题时排查版本问题。
  4. 每次改动源代码文件时,尽可能做到向下兼容。

正文:

接下来,我们一起看看如何制作仅打包C#源代码文件,不打包dll程序集文件的C#源代码组件NuGet包

首先是创建 AllenCai.BuildingBlocks 项目,目录结构如下:

.
├── build
└── src
    ├── AllenCai.BuildingBlocks
    │   ├── AllenCai.BuildingBlocks.csproj
    │   ├── Properties
    │   │   ├── PackageInfo.cs
    │   ├── Assets
    │   │   ├── build
    │   │   │   └── AllenCai.BuildingBlocks.targets
    │   │   └── buildMultiTargeting
    │   │       └── AllenCai.BuildingBlocks.targets
    │   ├── Collections
    │   │   ├── ArrayBuilder.cs
    │   │   ├── other...
    │   ├── Functional
    │   │   ├── Result.cs
    │   │   ├── other...
    │   ├── ObjectPooling
    │   │   ├── DictionaryPool.cs
    │   │   ├── other...
    │   ├── Text
    │   │   ├── StringBuffer.cs
    │   │   ├── other...
    │   ├── Threading
    │   │   ├── ValueTaskEx.cs
    │   │   ├── other...
    │   ├── bin
    │   │   ├── Release
    │   │   │   └── other...
    │   │   ├── Debug
    │   │   │   └── other...
    │   └── obj
    │   │   ├── other...
    │   ├── icon.png
    │   ├── other...
    ├── AllenCai.BuildingBlocks.sln
    └── Directory.Build.targets
├── .gitattributes
├── .gitignore
├── README.md

其中 Directory.Build.targets 文件,用来生成描述源代码组件包版本信息的C#源代码文件,输出文件路径为:Properties\PackageInfo.cs

之所以输出到 Properties 目录,是因为 PackageInfo.cs 的作用其实和以前 .NET Framework 时代每个项目都会包含的 AssemblyInfo.cs 相同。

那么,为什么需要生成这个 PackageInfo.cs 文件呢?

  1. 因为不再是编译和发布dll,而是直接打包和提供源代码文件,原本被内嵌到dll程序集的版本信息是丢失的。
  2. 懒,也不希望每次手工维护写入 Versiongit commit sha-1

Directory.Build.targets 文件代码如下所示:

<Project>
  <!--
    将代码版本信息输出到C#文件中,使用者在项目中引入本组件源码,能够看到版本信息。
    且在使用者项目编译为程序集文件后,也能够保留本组件版本信息。
  -->
  <Target Name="GeneratePackageInfoToFile" BeforeTargets="PreBuildEvent" Condition="'$(Configuration)' == 'Release'">
    <PropertyGroup>
      <SharedPackageInfoFile>$(ProjectDir)Properties\PackageInfo.cs</SharedPackageInfoFile>
    </PropertyGroup>
​
    <ItemGroup>
      <AssemblyAttributes Include="AssemblyMetadata">
        <_Parameter1>PackageVersion</_Parameter1>
        <_Parameter2>$(Version)</_Parameter2>
      </AssemblyAttributes>
      <AssemblyAttributes Include="AssemblyMetadata">
        <_Parameter1>PackageBuildDate</_Parameter1>
        <_Parameter2>$([System.DateTime]::Now.ToString("yyyy-MM-dd HH:mm:ss"))</_Parameter2>
      </AssemblyAttributes>
      <AssemblyAttributes Include="AssemblyMetadata" Condition="'$(SourceRevisionId)' != ''">
        <_Parameter1>PackageSourceRevisionId</_Parameter1>
        <_Parameter2>$(SourceRevisionId)</_Parameter2>
      </AssemblyAttributes>
    </ItemGroup>
​
    <MakeDir Directories="$(ProjectDir)Properties"/>
    <WriteCodeFragment Language="C#" OutputFile="$(SharedPackageInfoFile)" AssemblyAttributes="@(AssemblyAttributes)" />
    <Message Importance="high" Text="SharedPackageInfoFile --> $(SharedPackageInfoFile)" />
​
    <ItemGroup>
      <Compile Include="$(SharedPackageInfoFile)" Pack="true" BuildAction="Compile" />
    </ItemGroup>
  </Target>
</Project>

AllenCai.BuildingBlocks.targets 文件,将会被打包到NuGet包。

当这个包被添加引用到目标项目中,MsBuild 将会自动调用它,执行一系列由你定义的动作。

那么,又为什么需要这个 AllenCai.BuildingBlocks.targets 文件呢?

它其实是非必须的,根据项目实际情况而定,没有这个 targets 文件也是可以的。

但这样的话,可能引用这个源代码组件包的开发者会在刚引入时遇到一系列问题,导致这个源代码组件包对开发者不友好。

比如源代码文件中使用了不安全代码,而目标项目的<AllowUnsafeBlocks>属性值是 false,那么目标项目在编译时就会报错。

因此需要这个 targets 文件来检查和自动设置为 true

如以下示例代码(build\AllenCai.BuildingBlocks.targets):

<Project>
  <Target Name="UpdateLangVersionAndAllowUnsafeBlocks" BeforeTargets="BeforeCompile">
    <PropertyGroup>
      <OldAllowUnsafeBlocks>$(AllowUnsafeBlocks)</OldAllowUnsafeBlocks>
      <AllowUnsafeBlocks Condition=" '$(AllowUnsafeBlocks)' == '' or $([System.String]::Equals('$(AllowUnsafeBlocks)','false','StringComparison.InvariantCultureIgnoreCase')) ">True</AllowUnsafeBlocks>
    </PropertyGroup>
​
    <!--当属性项被修改时,在Build控制台输出提示-->
    <Message Importance="high" Condition=" '$(AllowUnsafeBlocks)' != '$(OldAllowUnsafeBlocks)' " Text="Update AllowUnsafeBlocks to $(AllowUnsafeBlocks)" />
  </Target>
</Project>
以及 buildMultiTargeting\AllenCai.BuildingBlocks.targets 文件代码如下所示:
<Project>
  <Import Project="..\build\AllenCai.BuildingBlocks.targets" />
</Project>

需要注意的是,这个 targets 文件需要与 ProjectName 或 PackageId 保持一致。

最后 AllenCai.BuildingBlocks.csproj 文件代码如下所示:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
    <LangVersion>default</LangVersion>
    <Nullable>enable</Nullable>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    <ImplicitUsings>disable</ImplicitUsings>
    <ProduceReferenceAssembly>false</ProduceReferenceAssembly>
    <GenerateDocumentationFile>false</GenerateDocumentationFile>
    <Version>0.0.1</Version>
  </PropertyGroup>
​
  <!--一些与NuGet包相关的属性项-->
  <PropertyGroup>
    <Title>AllenCai BuildingBlocks</Title>
    <Description>提供一组最常用的通用软件模块,以文件链接的方式被包含到引用项目中。</Description>
    <Authors>Allen.Cai</Authors>
    <Copyright>Copyright © Allen.Cai 2015-$([System.DateTime]::Now.Year) All Rights Reserved</Copyright>
    <ContentTargetFolders>contentFiles\cs\any\AllenCai.BuildingBlocks;content\cs\any\AllenCai.BuildingBlocks</ContentTargetFolders>
    <!--该属性项声明了仅在开发期间依赖,并且不传递其自身的依赖项,这将导致目标项目需要主动引入间接依赖项-->
    <DevelopmentDependency>true</DevelopmentDependency>
    <!--打包时不包含编译输出的文件-->
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <!--该属性项仅用于源生成器(SourceGenerator)项目,从 Visual Studio 2022 v16.10及以上版本开始支持-->
    <!--<IsRoslynComponent>true</IsRoslynComponent>-->
    <!--跳过包分析-->
    <NoPackageAnalysis>true</NoPackageAnalysis>
    <PackageProjectUrl>http://192.168.1.88:5555/allen/allencai.buildingblocks/</PackageProjectUrl>
    <PackageReadmeFile>README.md</PackageReadmeFile>
    <RepositoryUrl>http://192.168.1.88:5555/allen/allencai.buildingblocks.git</RepositoryUrl>
    <RepositoryType>git</RepositoryType>
    <PackageIcon>icon.png</PackageIcon>
  </PropertyGroup>
​
  <ItemGroup>
    <!-- <PackageReference Include="System.Reactive" Version="5.0.0" /> -->
    <None Include="icon.png" Pack="true" PackagePath="\" />
    <None Include="..\..\README.md" Link="README.md" Pack="true" PackagePath="\" />
    <Content Include="**\*.cs" Exclude="obj\**\*.cs" Pack="true" BuildAction="Compile" />
  </ItemGroup>
</Project>

其中三个属性比较重要,DevelopmentDependencyIncludeBuildOutput 以及 ContentTargetFolders

  1. DevelopmentDependency 设置为 true,表示这个 NuGet 包仅在开发期间依赖​。
  2. IncludeBuildOutput 设置为 false,表示打包时不包含编译输出的 dll​ 文件。
  3. 重写 ContentTargetFolders,将会改变这些源代码文件在目标项目中的虚拟​文件系统布局。

如有不明白,欢迎留言,互相探讨。

截止本文,我刚搜到有 MVP大佬-吕毅 也写了类似教程,​大家也可以参考:从零开始制作 NuGet 源代码包(全面支持 .NET Core / .NET Framework / WPF 项目) - walterlv

标签:文件,C#,csproj,AllenCai,BuildingBlocks,cs,源代码,targets
From: https://www.cnblogs.com/VAllen/p/18255504/how-to-use-csproj-to-build-the-csharp-source-cod

相关文章

  • 如何使用csproj构建C#源代码组件NuGet包?
    一般我们构建传统的NuGet包,都是打包和分发dll程序集文件。至于打包和分发C#源代码文件的做法,比较少见。那么这种打包源代码文件的做法,有什么优点和缺点呢?优点:方便阅读源代码。方便断点调试。减少Assembly程序集模块加载个数。更利于发布期间的剪裁(PublishTrimmed选项)。......
  • IT66352: 2 IN to 1 OUT HDMI2.0 18Gb/s Re Timer Switch
    TheIT66352isaHDMI2.02INto1OUTre-timerswitchwhichsupportsmaximumsignalingrateupto6Gbps/channel. IT66352是一款HDMI2.02INto1OUT重定时器开关,支持最高6Gbps/通道的信令速率。ItiscomplianttothelatestHDMI2.0bspecification4Kx2K@50/6......
  • IT66341 : 4 IN to 1 OUT HDMI2.0 18Gb/s Switch with Audio In/Out
    TheIT66341isaHDMI2.04INto1OUTswitchwhichsupportsmaximumsignalingrateupto6Gbps/channel. IT66341是一款HDMI2.04INto1OUT交换机,支持高达6Gbps/通道的最大信令速率。ItiscomplianttothelatestHDMI2.0bspecificationandbackwardcompatib......
  • C语言实现三子棋游戏
    三子棋我们再熟悉不过了,但是如何用C语言制作三子棋游戏呢?首先我们用*号代表玩家下棋;我们用#号代表电脑下棋;接着我们可以通过输入坐标来控制棋子的输入,而电脑通过随机数的来下棋。用模块化编程,将一个游戏分成3个文件来制作,分别为主文件test.c来执行主函数main、其次是注册函数......
  • ADI的CCES软件,如何申请序列号(软件注册详解)
    作者的话这些资料都在我整理的ADIDSP资料全集里,链接如下:https://item.taobao.com/item.htm?spm=a1z10.5-c.w4002-5192690539.23.76f16a87mLzgkl&id=566262352508ADI公司的DSP开发软件,CCES软件可以提供测试序列号,给大家做软件评估用,这个序列号跟正版序列号,除了一个是花巨......
  • 如何使用csproj构建C#源代码组件NuGet包?
    一般我们构建传统的NuGet包,都是打包和分发dll程序集文件。至于打包和分发C#源代码文件的做法,比较少见。那么这种打包源代码文件的做法,有什么优点和缺点呢?优点:方便阅读源代码。方便断点调试。减少Assembly程序集模块加载个数。更利于发布期间的剪裁(PublishTrimmed选项)。......
  • Java与React轻松导出Excel/PDF数据
    前言在B/S架构中,服务端导出是一种高效的方式。它将导出的逻辑放在服务端,前端仅需发起请求即可。通过在服务端完成导出后,前端再下载文件完成整个导出过程。服务端导出具有许多优点,如数据安全、适用于大规模数据场景以及不受前端性能影响等。本文将使用前端框架React和服务端框架S......
  • 请编写函数fun,对长度位7个字符的字符串,除首尾字符外,将其余5个字符按ASCII码降序排列
    请编写函数fun,对长度位7个字符的字符串,除首尾字符外,将其余5个字符按ASCII码降序排列#include<stdio.h>#include<string.h>voidsortDescending(charstr[]){intlen=strlen(str);if(len!=7) {printf("字符串长度不为7,无法进行排序。\n");......
  • DHCP协议
    DHCP协议1.DHCP的作用:动态主机配置协议可以人工给主机配置参数,但是当网络中的主机太多时,一个个的人工配置代价未免太大了,所以就出现了DHCP协议实现动态主机配置配置那些参数呢:IP地址子网掩码默认网关域名DHCP客户端程序在主机中开机联网自启动,请求DHCP服务器得到......
  • 如何使用csproj构建C#源代码组件NuGet包?
    一般我们构建传统的NuGet包,都是打包和分发dll程序集文件。至于打包和分发C#源代码文件的做法,比较少见。那么这种打包源代码文件的做法,有什么优点和缺点呢?优点:方便阅读源代码。方便断点调试。减少Assembly程序集模块加载个数。更利于发布期间的剪裁(PublishTrimmed选项)。......