dotnet-nuget-包创作 dotnet-nuget-authoring

这个技能专注于在.NET生态系统中创建和管理NuGet包,提供从SDK-style项目属性设置到高级打包技术的全面指南。它涵盖源生成器打包、多目标框架支持、符号包生成、包签名和验证,以及版本策略管理,帮助开发者高效构建和分发高质量的库包。关键词:.NET, NuGet, 包创作, 打包, 源生成器, 多目标框架, 符号包, 包签名, 包验证, 版本控制, 软件开发。

后端开发 0 次安装 0 次浏览 更新于 3/6/2026

名称: dotnet-nuget-包创作 描述: “创建NuGet包。SDK风格的csproj,源生成器,多目标框架,符号包,签名。” 用户可调用: false

dotnet-nuget-包创作

面向.NET库作者的NuGet包创作:SDK风格的.csproj包属性(PackageIdPackageTagsPackageReadmeFilePackageLicenseExpression),使用analyzers/dotnet/cs/文件夹布局和buildTransitive目标的源生成器NuGet打包,多目标框架包,具有确定性构建的符号包(snupkg),包签名(使用证书的作者签名、仓库签名),包验证(EnablePackageValidation、用于API兼容性的Microsoft.DotNet.ApiCompat.Task),以及NuGet版本策略(SemVer 2.0、预发布后缀、NBGV集成)。

版本假设:.NET 8.0+ 基线。NuGet客户端随.NET 8+ SDK捆绑。Microsoft.DotNet.ApiCompat.Task 8.0+ 用于API兼容性验证。

范围

  • SDK风格的csproj包属性和元数据
  • 使用分析器文件夹布局的源生成器NuGet打包
  • 多目标框架包和符号包(snupkg)
  • 包签名(作者和仓库签名)
  • 包验证(EnablePackageValidation、API兼容性)
  • NuGet版本策略(SemVer 2.0、NBGV)

超出范围

  • 中央包管理、SourceLink、nuget.config —— 参见[技能:dotnet-project-structure]
  • CI/CD NuGet推送工作流 —— 参见[技能:dotnet-gha-publish]和[技能:dotnet-ado-publish]
  • CLI工具打包和分发 —— 参见[技能:dotnet-cli-packaging]
  • Roslyn分析器创作 —— 参见[技能:dotnet-roslyn-analyzers]
  • 发布生命周期和NBGV设置 —— 参见[技能:dotnet-release-management]

交叉参考:[技能:dotnet-project-structure]用于CPM、SourceLink、nuget.config,[技能:dotnet-gha-publish]用于CI NuGet推送工作流,[技能:dotnet-ado-publish]用于ADO NuGet推送工作流,[技能:dotnet-cli-packaging]用于CLI工具分发格式,[技能:dotnet-csharp-source-generators]用于Roslyn源生成器创作,[技能:dotnet-release-management]用于发布生命周期和NBGV设置,[技能:dotnet-roslyn-analyzers]用于Roslyn分析器创作。


SDK风格包属性

每个NuGet包都从.csproj中的MSBuild属性开始。SDK风格的项目使用dotnet pack生成NuGet包——无需.nuspec文件。

基本包元数据

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <PackageId>MyCompany.Widgets</PackageId>
    <Version>1.0.0</Version>
    <Authors>My Company</Authors>
    <Description>用于管理小部件的库,支持流畅API。</Description>
    <PackageTags>widgets;fluent;dotnet</PackageTags>
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
    <PackageProjectUrl>https://github.com/mycompany/widgets</PackageProjectUrl>
    <RepositoryUrl>https://github.com/mycompany/widgets</RepositoryUrl>
    <RepositoryType>git</RepositoryType>

    <!-- 在nuget.org包页面上显示的README -->
    <PackageReadmeFile>README.md</PackageReadmeFile>

    <!-- 包图标(推荐128x128 PNG) -->
    <PackageIcon>icon.png</PackageIcon>

    <!-- 为IntelliSense生成XML文档 -->
    <GenerateDocumentationFile>true</GenerateDocumentationFile>

    <!-- 用于可重现性的确定性构建 -->
    <ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
  </PropertyGroup>

  <!-- 在包中包含README和图标 -->
  <ItemGroup>
    <None Include="README.md" Pack="true" PackagePath="\" />
    <None Include="icon.png" Pack="true" PackagePath="\" />
  </ItemGroup>
</Project>

属性参考

属性 用途 示例
PackageId nuget.org上的唯一包标识符 MyCompany.Widgets
Version SemVer 2.0版本 1.2.3-beta.1
Authors 逗号分隔的作者名称 Jane Doe, My Company
Description nuget.org上的包描述 流畅小部件管理库
PackageTags 分号分隔的搜索标签 widgets;fluent;dotnet
PackageLicenseExpression SPDX许可证标识符 MIT, Apache-2.0
PackageLicenseFile 许可证文件(替代表达式) LICENSE.txt
PackageReadmeFile 在nuget.org上显示的Markdown自述文件 README.md
PackageIcon 包图标文件名 icon.png
PackageProjectUrl 项目主页URL https://github.com/mycompany/widgets
PackageReleaseNotes 此版本的发布说明 添加了小部件缓存支持
Copyright 版权声明 Copyright 2024 My Company
RepositoryUrl 源代码仓库URL https://github.com/mycompany/widgets
RepositoryType 仓库类型 git

Directory.Build.props用于共享元数据

对于多项目仓库,在Directory.Build.props中设置公共属性:

<!-- Directory.Build.props(仓库根目录) -->
<Project>
  <PropertyGroup>
    <Authors>My Company</Authors>
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
    <PackageProjectUrl>https://github.com/mycompany/widgets</PackageProjectUrl>
    <RepositoryUrl>https://github.com/mycompany/widgets</RepositoryUrl>
    <RepositoryType>git</RepositoryType>
    <Copyright>Copyright 2024 My Company</Copyright>
  </PropertyGroup>
</Project>

各个.csproj文件然后只设置包特定属性(PackageIdDescriptionPackageTags)。


源生成器NuGet打包

源生成器和分析器需要特定的NuGet包布局。生成器DLL必须放置在analyzers/dotnet/cs/文件夹中,而不是lib/文件夹。对于Roslyn源生成器创作(IIncrementalGenerator、语法/语义分析),参见[技能:dotnet-csharp-source-generators]。本节仅涵盖生成器的NuGet打包

源生成器包的项目设置

<!-- MyCompany.Generators.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
    <IsRoslynComponent>true</IsRoslynComponent>

    <!-- 包元数据 -->
    <PackageId>MyCompany.Generators</PackageId>
    <Description>用于小部件自动注册的源生成器。</Description>

    <!-- 不要将生成器DLL包含在lib/文件夹中 -->
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>

    <!-- 生成器必须针对netstandard2.0以兼容Roslyn主机 -->
    <IsPackable>true</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
  </ItemGroup>

  <!-- 将生成器DLL放置在分析器文件夹中 -->
  <ItemGroup>
    <None Include="$(OutputPath)$(AssemblyName).dll"
          Pack="true"
          PackagePath="analyzers/dotnet/cs"
          Visible="false" />
  </ItemGroup>
</Project>

添加构建Props/Targets

当源生成器需要在消费项目中设置MSBuild属性时,使用buildTransitive文件夹:

<!-- build/MyCompany.Generators.props -->
<Project>
  <PropertyGroup>
    <MyCompanyGeneratorsEnabled>true</MyCompanyGeneratorsEnabled>
  </PropertyGroup>
  <ItemGroup>
    <!-- 示例:为生成器消费添加额外文件 -->
    <CompilerVisibleProperty Include="MyCompanyGeneratorsEnabled" />
  </ItemGroup>
</Project>

在包中包含buildTransitive内容:

<!-- 在.csproj中 -->
<ItemGroup>
  <!-- buildTransitive确保props/targets通过传递依赖流动 -->
  <None Include="build\MyCompany.Generators.props"
        Pack="true"
        PackagePath="buildTransitive\MyCompany.Generators.props" />
  <None Include="build\MyCompany.Generators.targets"
        Pack="true"
        PackagePath="buildTransitive\MyCompany.Generators.targets" />
</ItemGroup>

多目标分析器包(分析器 + 库)

当在同一包中同时发布分析器和运行时库时:

<!-- MyCompany.Widgets.csproj(发布运行时库 + 分析器) -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net8.0;netstandard2.0</TargetFrameworks>
    <PackageId>MyCompany.Widgets</PackageId>
  </PropertyGroup>

  <!-- 引用生成器项目,但抑制其输出到lib/ -->
  <ItemGroup>
    <ProjectReference Include="..\MyCompany.Widgets.Generators\MyCompany.Widgets.Generators.csproj"
                      OutputItemType="Analyzer"
                      ReferenceOutputAssembly="false" />
  </ItemGroup>
</Project>

NuGet包文件夹布局

MyCompany.Generators.1.0.0.nupkg
  analyzers/
    dotnet/
      cs/
        MyCompany.Generators.dll          <-- 生成器/分析器程序集
  buildTransitive/
    MyCompany.Generators.props            <-- 自动导入的MSBuild props
    MyCompany.Generators.targets          <-- 自动导入的MSBuild targets
  lib/
    netstandard2.0/
      _._                                <-- 空标记(无运行时库)

多目标框架包

多目标生成一个包含每个目标框架程序集的单一NuGet包。消费者自动获得最佳匹配的程序集。

何时使用多目标

场景 方法
库仅适用于net8.0 单一TFM:<TargetFramework>net8.0</TargetFramework>
库需要netstandard2.0 + net8.0 API 多TFM:<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
库使用net9.0特定API(例如,SearchValues 使用polyfills或条件代码的多TFM
库目标为.NET Framework消费者 包含net472netstandard2.0 TFM

多目标配置

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netstandard2.0;net8.0;net9.0</TargetFrameworks>
  </PropertyGroup>

  <!-- 每个TFM的API差异 -->
  <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
    <PackageReference Include="System.Memory" Version="4.6.0" />
    <PackageReference Include="System.Text.Json" Version="8.0.5" />
  </ItemGroup>
</Project>

条件编译

public static class StringExtensions
{
    public static bool ContainsIgnoreCase(this string source, string value)
    {
#if NET8_0_OR_GREATER
        return source.Contains(value, StringComparison.OrdinalIgnoreCase);
#else
        return source.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0;
#endif
    }
}

NuGet包文件夹布局(多TFM)

MyCompany.Widgets.1.0.0.nupkg
  lib/
    netstandard2.0/
      MyCompany.Widgets.dll
    net8.0/
      MyCompany.Widgets.dll
    net9.0/
      MyCompany.Widgets.dll

符号包和确定性构建

符号包(.snupkg)通过NuGet符号服务器为包消费者启用源码级调试。

启用符号包

<PropertyGroup>
  <!-- 生成.snupkg与.nupkg一起 -->
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>

  <!-- 确定性构建(用于可重现包) -->
  <Deterministic>true</Deterministic>
  <ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>

  <!-- 在PDB中嵌入源码以便无需源码服务器调试 -->
  <EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>

当使用dotnet nuget push时,snupkg会自动与nupkg一起推送:

# 将.nupkg和.snupkg推送到nuget.org
dotnet nuget push "bin/Release/*.nupkg" \
  --source https://api.nuget.org/v3/index.json \
  --api-key "$NUGET_API_KEY"

SourceLink集成: 对于链接到实际源码仓库的源码级调试,在项目中配置SourceLink。参见[技能:dotnet-project-structure]获取SourceLink设置——不要在此重复配置。

嵌入式PDB替代方案

对于不希望使用单独符号包的包:

<PropertyGroup>
  <DebugType>embedded</DebugType>
</PropertyGroup>

这将PDB直接嵌入到程序集DLL中。权衡是包大小更大但分发更简单。


包签名

NuGet支持作者签名(证明包来源)和仓库签名(证明它来自特定源)。

使用证书进行作者签名

# 使用PFX证书签名包
dotnet nuget sign "MyCompany.Widgets.1.0.0.nupkg" \
  --certificate-path ./signing-cert.pfx \
  --certificate-password "$CERT_PASSWORD" \
  --timestamper http://timestamp.digicert.com

# 使用证书存储中的证书签名(Windows)
dotnet nuget sign "MyCompany.Widgets.1.0.0.nupkg" \
  --certificate-fingerprint "ABC123..." \
  --timestamper http://timestamp.digicert.com

证书要求

要求 详情
密钥用法 代码签名(1.3.6.1.5.5.7.3.3)
算法 RSA 2048位最小
时间戳 长期有效性所需
可信CA DigiCert、Sectigo或其他nuget.org可信CA
自签名 私有源接受;nuget.org拒绝

仓库签名

仓库签名由源操作员应用(例如,nuget.org对所有包签名)。包作者无需配置仓库签名——它由源基础设施自动应用。

验证包签名

# 验证签名包
dotnet nuget verify "MyCompany.Widgets.1.0.0.nupkg"

# 详细输出验证
dotnet nuget verify "MyCompany.Widgets.1.0.0.nupkg" --verbosity detailed

包验证

包验证在发布前捕获API中断、无效包布局和兼容性问题。

内置打包验证

<PropertyGroup>
  <!-- 在dotnet pack上启用包验证 -->
  <EnablePackageValidation>true</EnablePackageValidation>
</PropertyGroup>

这验证:

  • 所有TFM具有兼容的API表面
  • 包版本之间无意外API移除
  • 包布局遵循NuGet约定

与基线版本的API兼容性

比较当前包与先前发布的基线版本以检测破坏性更改:

<PropertyGroup>
  <EnablePackageValidation>true</EnablePackageValidation>
  <!-- 与最后发布版本比较 -->
  <PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion>
</PropertyGroup>

Microsoft.DotNet.ApiCompat.Task

用于跨程序集的高级API兼容性检查:

<ItemGroup>
  <PackageReference Include="Microsoft.DotNet.ApiCompat.Task" Version="8.0.0" PrivateAssets="all" />
</ItemGroup>

<PropertyGroup>
  <!-- 启用API兼容性分析 -->
  <ApiCompatEnableRuleAttributesMustMatch>true</ApiCompatEnableRuleAttributesMustMatch>
  <ApiCompatEnableRuleCannotChangeParameterName>true</ApiCompatEnableRuleCannotChangeParameterName>
</PropertyGroup>

抑制已知中断

当进行故意的API更改时,生成并提交抑制文件:

# 为已知中断生成抑制文件
dotnet pack /p:GenerateCompatibilitySuppressionFile=true

这将创建CompatibilitySuppressions.xml

<!-- CompatibilitySuppressions.xml(提交到源代码控制) -->
<?xml version="1.0" encoding="utf-8"?>
<Suppressions xmlns:ns="https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids">
  <Suppression>
    <DiagnosticId>CP0002</DiagnosticId>
    <Target>M:MyCompany.Widgets.Widget.OldMethod</Target>
    <Left>lib/net8.0/MyCompany.Widgets.dll</Left>
    <Right>lib/net8.0/MyCompany.Widgets.dll</Right>
  </Suppression>
</Suppressions>

引用抑制文件:

<ItemGroup>
  <ApiCompatSuppressionFile Include="CompatibilitySuppressions.xml" />
</ItemGroup>

NuGet版本策略

NuGet的SemVer 2.0

NuGet遵循语义化版本2.0:

版本 含义
1.0.0 稳定发布
1.0.1 补丁(错误修复,无API更改)
1.1.0 次要(新功能,向后兼容)
2.0.0 主要(破坏性更改)
1.0.0-alpha.1 预发布alpha
1.0.0-beta.1 预发布beta
1.0.0-rc.1 发布候选

预发布后缀

<!-- 稳定发布 -->
<Version>1.2.3</Version>

<!-- 带有SemVer 2.0点分隔后缀的预发布 -->
<Version>1.2.3-beta.1</Version>

<!-- 具有提交高度的CI构建(NBGV模式) -->
<!-- 产生:1.2.3-beta.42+abcdef -->

NBGV集成

Nerdbank.GitVersioning(NBGV)从git历史计算版本。对于NBGV设置和version.json配置,参见[技能:dotnet-release-management]。本技能涵盖NBGV生成的版本如何与NuGet打包交互:

<PropertyGroup>
  <!-- NBGV自动设置Version、PackageVersion、AssemblyVersion -->
  <!-- 使用NBGV时不要显式设置Version -->
</PropertyGroup>

NBGV生成像1.2.42-beta+abcdef这样的版本,其中:

  • 1.2来自version.json
  • 42是git提交高度
  • -beta是来自version.json的预发布后缀
  • +abcdef是git提交哈希(构建元数据,NuGet解析忽略)

版本属性参考

属性 用途 设置者
Version 完整SemVer版本(驱动PackageVersion) 手动或NBGV
PackageVersion NuGet包版本(默认为Version) 手动或NBGV
AssemblyVersion CLR程序集版本 手动或NBGV
FileVersion Windows文件版本 手动或NBGV
InformationalVersion 带有元数据的完整版本字符串 手动或NBGV

打包和本地测试

构建包

# 以Release配置打包
dotnet pack --configuration Release

# 以特定版本覆盖打包
dotnet pack --configuration Release /p:Version=1.2.3-beta.1

# 输出到特定目录
dotnet pack --configuration Release --output ./artifacts

本地源测试

发布前在本地测试包:

# 创建本地源目录
mkdir -p ~/local-nuget-feed

# 将包添加到本地源
dotnet nuget push "bin/Release/MyCompany.Widgets.1.0.0.nupkg" \
  --source ~/local-nuget-feed

# 在消费项目中,添加本地源
dotnet nuget add source ~/local-nuget-feed --name LocalFeed

包内容检查

# 列出包内容(nupkg是zip文件)
unzip -l MyCompany.Widgets.1.0.0.nupkg

# 验证分析器放置
unzip -l MyCompany.Generators.1.0.0.nupkg | grep analyzers/

代理陷阱

  1. 不要同时设置PackageLicenseExpressionPackageLicenseFile——它们是互斥的。对标准SPDX标识符使用PackageLicenseExpression,仅对自定义许可证使用PackageLicenseFile

  2. 源生成器必须针对netstandard2.0——Roslyn主机要求此。不要对生成器本身进行多目标;对引用生成器项目的运行时库进行多目标。

  3. 不要在库项目上将IncludeBuildOutput设置为false——仅对不应贡献运行时程序集的纯分析器/生成器项目。

  4. buildTransitivebuild文件夹——对应该通过传递PackageReference依赖流动的props/targets使用buildTransitivebuild文件夹仅影响直接消费者。

  5. 包验证抑制使用带有CompatibilitySuppressions.xmlApiCompatSuppressionFile——不是PackageValidationSuppression MSBuild项。使用/p:GenerateCompatibilitySuppressionFile=true生成文件。

  6. SDK风格项目自动包含所有*.cs文件——添加TFM条件Compile Include而不带前导Compile Remove会导致NETSDK1022重复项。

  7. 永远不要在CLI示例中硬编码API密钥——始终使用环境变量占位符($NUGET_API_KEY)并注明CI秘密存储。

  8. ContinuousIntegrationBuild必须基于CI条件——无条件设置会通过使PDB不可重现本地文件路径来破坏本地调试。