关于 dotnet-nuget-authoring
面向 .NET 库作者的技能,涵盖 NuGet 包元数据、布局、多目标框架、签名、验证和版本管理。重点是包的制作,不涉及发布工作流或通用项目配置。
主要功能
- 01SDK 风格包元数据
- 02源生成器包布局
- 03多 TFM 打包
- 04符号包与签名
- 05包与 API 验证
- 06NuGet SemVer 版本管理
适用场景
- 01准备库的 NuGet 打包
- 02打包分析器和源生成器
- 03发布前检查包兼容性
- 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>
属性参考
| 属性 | 用途 | 示例 |
|---|---|---|
PackageId | nuget.org 上的唯一包标识符 | MyCompany.Widgets |
Version | SemVer 2.0 版本 | 1.2.3-beta.1 |
Authors | 以逗号分隔的作者姓名 | Jane Doe, My Company |
Description | nuget.org 上显示的包描述 | Fluent widget management library |
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 | 此版本的发行说明 | Added widget caching support |
Copyright | 版权声明 | Copyright 2024 My Company |
RepositoryUrl | 源代码存储库 URL | https://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.1 | Alpha 预发行版 |
1.0.0-beta.1 | Beta 预发行版 |
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.json42是 Git 提交高度-beta是version.json中的预发行版后缀+abcdef是 Git 提交哈希(构建元数据,NuGet 解析时会忽略)
版本属性参考
| 属性 | 用途 | 设置方式 |
|---|---|---|
Version | 完整的 SemVer 版本(用于设置 PackageVersion) | 手动或由 NBGV 设置 |
PackageVersion | NuGet 包版本(默认为 Version) | 手动或由 NBGV 设置 |
AssemblyVersion | CLR 程序集版本 | 手动或由 NBGV 设置 |
FileVersion | Windows 文件版本 | 手动或由 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 注意事项
-
不要同时设置
PackageLicenseExpression和PackageLicenseFile——二者互斥。对于标准 SPDX 标识符,使用PackageLicenseExpression;仅对自定义许可证使用PackageLicenseFile。 -
源生成器必须以
netstandard2.0为目标框架——Roslyn 宿主有此要求。不要对生成器本身进行多目标构建;应对引用生成器项目的运行时库进行多目标构建。 -
不要在库项目中将
IncludeBuildOutput设为false——仅对不应包含运行时程序集的纯分析器/生成器项目设置此项。 -
buildTransitive与build文件夹——对于应通过传递性PackageReference依赖项传播的 props/targets,请使用buildTransitive。build文件夹仅影响直接使用方。 -
包验证抑制使用
ApiCompatSuppressionFile和CompatibilitySuppressions.xml——而非PackageValidationSuppressionMSBuild 项。使用/p:GenerateCompatibilitySuppressionFile=true生成该文件。 -
SDK 风格的项目会自动包含所有
*.cs文件——添加 TFM 条件的Compile Include前若未先添加Compile Remove,会导致重复项错误 NETSDK1022。 -
切勿在 CLI 示例中硬编码 API 密钥——始终使用环境变量占位符(
$NUGET_API_KEY),并注明 CI 密钥的存储方式。 -
ContinuousIntegrationBuild必须仅在 CI 环境下设置——无条件设置会导致 PDB 无法复现,因为其中会包含本地文件路径。