mcpField
D
dotnet-nuget-authoring

dotnet-nuget-authoring

github.com/wshaddix开发8671 次安装35 周前更新
代码仓库

关于 dotnet-nuget-authoring

面向 .NET 库作者的技能,涵盖 NuGet 包元数据、布局、多目标框架、签名、验证和版本管理。重点是包的制作,不涉及发布工作流或通用项目配置。

主要功能

  1. 01SDK 风格包元数据
  2. 02源生成器包布局
  3. 03多 TFM 打包
  4. 04符号包与签名
  5. 05包与 API 验证
  6. 06NuGet SemVer 版本管理

适用场景

  1. 01准备库的 NuGet 打包
  2. 02打包分析器和源生成器
  3. 03发布前检查包兼容性
  4. 04使用本地源测试包

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-nuget-authoring

安装后在 Claude Code 中输入/dotnet-nuget-authoring确认已加载。

详细介绍

dotnet-nuget-authoring

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

**版本假设:**以 .NET 8.0+ 为基准。使用 .NET 8+ SDK 附带的 NuGet 客户端。用于 API 兼容性验证的 Microsoft.DotNet.ApiCompat.Task 8.0+。

**范围界定:**此技能负责面向库使用者的 NuGet 包创作——包括属性、元数据、打包布局、签名和验证。项目级 NuGet 配置(Central Package Management、SourceLink、nuget.config、NuGet Audit、锁定文件)由 [skill:dotnet-project-structure] 负责。CI/CD 发布工作流(推送 NuGet 包到源、推送容器镜像)由 [skill:dotnet-gha-publish] 和 [skill:dotnet-ado-publish] 负责。CLI 工具打包(Homebrew、apt、winget、Scoop、dotnet tool)由 [skill:dotnet-cli-packaging] 负责。

**范围外:**Central Package Management、SourceLink、nuget.config、NuGet Audit——参见 [skill:dotnet-project-structure]。CI/CD NuGet 推送工作流——参见 [skill:dotnet-gha-publish] 和 [skill:dotnet-ado-publish]。CLI 工具打包和分发——参见 [skill:dotnet-cli-packaging]。Roslyn 分析器创作(Roslyn API、诊断描述符)——参见 [skill:dotnet-roslyn-analyzers]。发布生命周期和 NBGV 设置——参见 [skill:dotnet-release-management]。

交叉引用:[skill:dotnet-project-structure](CPM、SourceLink、nuget.config)、[skill:dotnet-gha-publish](CI NuGet 推送工作流)、[skill:dotnet-ado-publish](ADO NuGet 推送工作流)、[skill:dotnet-cli-packaging](CLI 工具分发格式)、[skill:dotnet-csharp-source-generators](Roslyn 源生成器创作)、[skill:dotnet-release-management](发布生命周期和 NBGV 设置)、[skill: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>A library for managing widgets with fluent API support.</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>

    
    <PackageReadmeFile>README.md</PackageReadmeFile>

    
    <PackageIcon>icon.png</PackageIcon>

    
    <GenerateDocumentationFile>true</GenerateDocumentationFile>

    
    <ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
  </PropertyGroup>

  
  <ItemGroup>
    <None Include="README.md" Pack="true" PackagePath="\" />
    <None Include="icon.png" Pack="true" PackagePath="\" />
  </ItemGroup>
</Project>
属性参考
属性用途示例
PackageIdnuget.org 上的唯一包标识符MyCompany.Widgets
VersionSemVer 2.0 版本1.2.3-beta.1
Authors以逗号分隔的作者姓名Jane Doe, My Company
Descriptionnuget.org 上显示的包描述Fluent widget management library
PackageTags以分号分隔的搜索标签widgets;fluent;dotnet
PackageLicenseExpressionSPDX 许可证标识符MIT、Apache-2.0
PackageLicenseFile许可证文件(表达式的替代方式)LICENSE.txt
PackageReadmeFile在 nuget.org 上显示的 Markdown 自述文件README.md
PackageIcon包图标文件名icon.png
PackageProjectUrl项目主页 URLhttps://github.com/mycompany/widgets
PackageReleaseNotes此版本的发行说明Added widget caching support
Copyright版权声明Copyright 2024 My Company
RepositoryUrl源代码存储库 URLhttps://github.com/mycompany/widgets
RepositoryType存储库类型git
用于共享元数据的 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 文件只需设置特定于包的属性(PackageId、Description、PackageTags)。


源代码生成器 NuGet 打包

源代码生成器和分析器需要采用特定的 NuGet 包布局。生成器 DLL 必须放在 analyzers/dotnet/cs/ 文件夹中,而不是 lib/ 文件夹中。有关 Roslyn 源代码生成器的编写(IIncrementalGenerator、语法/语义分析),请参阅 [skill:dotnet-csharp-source-generators]。本节仅介绍生成器的 NuGet 打包。

源代码生成器包的项目设置

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
    <IsRoslynComponent>true</IsRoslynComponent>

    
    <PackageId>MyCompany.Generators</PackageId>
    <Description>Source generators for widget auto-registration.</Description>

    
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>

    
    <IsPackable>true</IsPackable>
  </PropertyGroup>

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

  
  <ItemGroup>
    <None Include="$(OutputPath)$(AssemblyName).dll"
          Pack="true"
          PackagePath="analyzers/dotnet/cs"
          Visible="false" />
  </ItemGroup>
</Project>
添加 Build Props/Targets

当源代码生成器需要在使用它的项目中设置 MSBuild 属性时,请使用 buildTransitive 文件夹:


<Project>
  <PropertyGroup>
    <MyCompanyGeneratorsEnabled>true</MyCompanyGeneratorsEnabled>
  </PropertyGroup>
  <ItemGroup>
    
    <CompilerVisibleProperty Include="MyCompanyGeneratorsEnabled" />
  </ItemGroup>
</Project>

将 buildTransitive 内容包含在包中:


<ItemGroup>
  
  <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>
多目标框架分析器包(分析器 + 库)

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


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

  
  <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          <-- generator/analyzer assembly
  buildTransitive/
    MyCompany.Generators.props            <-- auto-imported MSBuild props
    MyCompany.Generators.targets          <-- auto-imported MSBuild targets
  lib/
    netstandard2.0/
      _._                                <-- empty marker (no runtime lib)

多目标框架包

多目标框架会生成一个 NuGet 包,其中包含面向每个目标框架的程序集。使用者会自动获得最匹配的程序集。

何时使用多目标框架
场景方法
库仅适用于 net8.0单一 TFM:<TargetFramework>net8.0</TargetFramework>
库需要 netstandard2.0 + net8.0 API多目标框架:<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
库使用 net9.0 专属 API(例如 SearchValues)使用多目标框架,并配合 polyfill 或条件代码
库面向 .NET Framework 使用者包含 net472 或 netstandard2.0 TFM
多目标框架配置
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netstandard2.0;net8.0;net9.0</TargetFrameworks>
  </PropertyGroup>

  
  <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 包文件夹布局(多目标框架)
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>
  
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>

  
  <Deterministic>true</Deterministic>
  <ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>

  
  <EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>

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

# Push both .nupkg and .snupkg to nuget.org
dotnet nuget push "bin/Release/*.nupkg" \
  --source https://api.nuget.org/v3/index.json \
  --api-key "$NUGET_API_KEY"

SourceLink 集成: 要通过指向实际源代码仓库的链接进行源代码级调试,请在项目中配置 SourceLink。有关 SourceLink 设置,请参阅 [skill:dotnet-project-structure],此处不再重复该配置。

嵌入 PDB 的替代方案

对于不适合使用单独符号包的包:

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

这会将 PDB 直接嵌入程序集 DLL 中。这样做的代价是包体积更大,但分发更简单。


包签名

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

使用证书进行作者签名
# Sign a package with a PFX certificate
dotnet nuget sign "MyCompany.Widgets.1.0.0.nupkg" \
  --certificate-path ./signing-cert.pfx \
  --certificate-password "$CERT_PASSWORD" \
  --timestamper http://timestamp.digicert.com

# Sign with a certificate from the certificate store (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对于 nuget.org,使用 DigiCert、Sectigo 或其他受信任的 CA
自签名私有源接受;nuget.org 拒绝
仓库签名

仓库签名由源运营方应用(例如,nuget.org 会为所有包签名)。包作者无需配置仓库签名——源基础设施会自动应用该签名。

验证包签名
# Verify a signed package
dotnet nuget verify "MyCompany.Widgets.1.0.0.nupkg"

# Verify with verbose output
dotnet nuget verify "MyCompany.Widgets.1.0.0.nupkg" --verbosity detailed

包验证

包验证可在发布前发现 API 破坏性变更、无效的包布局和兼容性问题。

内置包验证
<PropertyGroup>
  
  <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>
  
  <ApiCompatEnableRuleAttributesMustMatch>true</ApiCompatEnableRuleAttributesMustMatch>
  <ApiCompatEnableRuleCannotChangeParameterName>true</ApiCompatEnableRuleCannotChangeParameterName>
</PropertyGroup>
抑制已知的兼容性中断

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

# Generate suppression file for known breaks
dotnet pack /p:GenerateCompatibilitySuppressionFile=true

这会创建 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补丁版本(修复 Bug,不更改 API)
1.1.0次要版本(新增功能,向后兼容)
2.0.0主要版本(包含重大更改)
1.0.0-alpha.1Alpha 预发行版
1.0.0-beta.1Beta 预发行版
1.0.0-rc.1候选发布版
预发行版后缀

<Version>1.2.3</Version>


<Version>1.2.3-beta.1</Version>



NBGV 集成

Nerdbank.GitVersioning(NBGV)根据 Git 历史计算版本。有关 NBGV 设置和 version.json 配置,请参阅 [skill:dotnet-release-management]。本技能介绍 NBGV 生成的版本与 NuGet 打包的交互方式:

<PropertyGroup>
  
  
</PropertyGroup>

NBGV 生成的版本示例:1.2.42-beta+abcdef,其中:

  • 1.2 来自 version.json
  • 42 是 Git 提交高度
  • -beta 是 version.json 中的预发行版后缀
  • +abcdef 是 Git 提交哈希(构建元数据,NuGet 解析时会忽略)
版本属性参考
属性用途设置方式
Version完整的 SemVer 版本(用于设置 PackageVersion)手动或由 NBGV 设置
PackageVersionNuGet 包版本(默认为 Version)手动或由 NBGV 设置
AssemblyVersionCLR 程序集版本手动或由 NBGV 设置
FileVersionWindows 文件版本手动或由 NBGV 设置
InformationalVersion带元数据的完整版本字符串手动或由 NBGV 设置

打包和本地测试

构建包
# Pack in Release configuration
dotnet pack --configuration Release

# Pack with specific version override
dotnet pack --configuration Release /p:Version=1.2.3-beta.1

# Output to specific directory
dotnet pack --configuration Release --output ./artifacts
本地源测试

发布前在本地测试包:

# Create a local feed directory
mkdir -p ~/local-nuget-feed

# Add the package to the local feed
dotnet nuget push "bin/Release/MyCompany.Widgets.1.0.0.nupkg" \
  --source ~/local-nuget-feed

# In the consuming project, add the local feed
dotnet nuget add source ~/local-nuget-feed --name LocalFeed
检查包内容
# List package contents (nupkg is a zip file)
unzip -l MyCompany.Widgets.1.0.0.nupkg

# Verify analyzer placement
unzip -l MyCompany.Generators.1.0.0.nupkg | grep analyzers/

Agent 注意事项

  1. 不要同时设置 PackageLicenseExpression 和 PackageLicenseFile——二者互斥。对于标准 SPDX 标识符,使用 PackageLicenseExpression;仅对自定义许可证使用 PackageLicenseFile。

  2. 源生成器必须以 netstandard2.0 为目标框架——Roslyn 宿主有此要求。不要对生成器本身进行多目标构建;应对引用生成器项目的运行时库进行多目标构建。

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

  4. buildTransitive 与 build 文件夹——对于应通过传递性 PackageReference 依赖项传播的 props/targets,请使用 buildTransitive。build 文件夹仅影响直接使用方。

  5. 包验证抑制使用 ApiCompatSuppressionFile 和 CompatibilitySuppressions.xml——而非 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 无法复现,因为其中会包含本地文件路径。

评论

支持 Markdown