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)、analyzers/dotnet/cs/ フォルダー構成と buildTransitive ターゲットを使用したソースジェネレーターの NuGet パッケージ化、複数 TFM パッケージ、決定論的ビルドによるシンボルパッケージ(snupkg)、パッケージ署名(証明書による作成者署名、リポジトリ署名)、パッケージ検証(API 互換性のための EnablePackageValidation、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 push、コンテナーイメージの push)は [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 push ワークフローについては、[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 push ワークフロー)、[skill:dotnet-ado-publish](ADO の NuGet push ワークフロー)、[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 スタイルのプロジェクトでは、.nuspec ファイルを使わずに dotnet pack で NuGet パッケージを生成できます。
必須のパッケージメタデータ
<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 | 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 は lib/ フォルダーではなく、analyzers/dotnet/cs/ フォルダーに配置する必要があります。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)
マルチ TFM パッケージ
マルチターゲットにすると、各ターゲットフレームワーク用のアセンブリを含む単一の NuGet パッケージが生成されます。利用側は最適なアセンブリを自動的に取得します。
マルチターゲットを使用する場合
| シナリオ | 方法 |
|---|---|
| ライブラリが net8.0 のみで動作する | 単一 TFM:<TargetFramework>net8.0</TargetFramework> |
| ライブラリに netstandard2.0 と net8.0 の API が必要 | マルチ TFM:<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks> |
ライブラリが net9.0 固有の API(例:SearchValues)を使用する | ポリフィルまたは条件付きコードを使用してマルチターゲットにする |
| ライブラリが .NET Framework の利用側を対象とする | net472 または netstandard2.0 の TFM を含める |
マルチ 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 パッケージのフォルダー構成(マルチ 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>
<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 | パッチ(バグ修正、API 変更なし) |
1.1.0 | マイナー(新機能、後方互換性あり) |
2.0.0 | メジャー(破壊的変更) |
1.0.0-alpha.1 | プレリリースのアルファ版 |
1.0.0-beta.1 | プレリリースのベータ版 |
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 |
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/
エージェントが注意すべき点
-
PackageLicenseExpressionとPackageLicenseFileの両方を設定しないでください。これらは相互に排他的です。標準の SPDX 識別子にはPackageLicenseExpressionを、カスタムライセンスにはPackageLicenseFileのみを使用します。 -
ソースジェネレーターは必ず
netstandard2.0をターゲットにしてください。Roslyn ホストがこれを必要とします。ジェネレーター自体をマルチターゲットにしないでください。ジェネレータープロジェクトを参照するランタイムライブラリをマルチターゲットにします。 -
ライブラリプロジェクトで
IncludeBuildOutputをfalseに設定しないでください。ランタイムアセンブリを含めない純粋なアナライザー/ジェネレータープロジェクトでのみ設定します。 -
buildTransitiveとbuildフォルダーの違い。推移的なPackageReference依存関係にも適用する props/targets にはbuildTransitiveを使用します。buildフォルダーは直接のコンシューマーにのみ影響します。 -
パッケージ検証の抑制には、
PackageValidationSuppressionMSBuild 項目ではなく、CompatibilitySuppressions.xmlとともにApiCompatSuppressionFileを使用します。/p:GenerateCompatibilitySuppressionFile=trueを指定してファイルを生成します。 -
SDK スタイルのプロジェクトは、すべての
*.csファイルを自動的に含めます。先にCompile Removeを指定せずに TFM 条件付きのCompile Includeを追加すると、重複項目が原因で NETSDK1022 が発生します。 -
CLI の例に API キーをハードコードしないでください。必ず環境変数のプレースホルダー(
$NUGET_API_KEY)を使用し、CI のシークレットストレージについて注記してください。 -
ContinuousIntegrationBuildは CI 実行時にのみ設定してください。無条件に設定すると、PDB にローカルのファイルパスが含まれて再現性が失われ、ローカルでのデバッグに支障が出ます。