<PackageReference Include>は使うけど<PackageReference Update>は使わない、そう思っていた時期が私にもありました。今回は、<PackageReference Update>を使うと便利な例です。忘れそうなのでメモです。
CPMとDirectory.Build.propsで全プロジェクトにパッケージを追加
Central Package Managementを使用しているプロジェクトでDirectory.Packages.propsにてパッケージバージョンを指定し、Directory.Build.propsを使って全プロジェクトに暗黙的にパッケージ追加できます。個別のcsprojへパッケージを追加しなくていいのでかなり便利なやり方です。例えば、Microsoft.SourceLink.GitHubのバージョン8.0.0を全プロジェクトで利用したいときは、以下のようにします。
<!-- Directory.Packages.props --> <Project> <PropertyGroup> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled> </PropertyGroup> <ItemGroup> <PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" /> </ItemGroup> </Project>
<!-- Directory.Build.props --> <Project> <ItemGroup> <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="all"/> </ItemGroup> </Project>
プロジェクトファイルでは、Microsoft.SourceLink.GitHubを指定しなくてもビルド時にパッケージが入ります。
<!-- Normal.csproj --> <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net10.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> </Project>
// Program.cs Console.WriteLine("Hello, World!");
$ dotnet build Restore complete (0.6s) Normal net10.0 succeeded (1.1s) → bin/Debug/net10.0/Normal.dll Build succeeded in 2.6s
どのような時に困るのか
ただ、時々プロジェクトファイルでCPMを無効にしたくなる時があります。例えば、ライブラリ開発しているとプロジェクト参照ではなく、過去リリースしたNuGetパッケージで動作テストしたくなることがあります。この場合、プロジェクトファイルでCPMを無効にして、個別のプロジェクトファイルでPackageReferenceを追加します。
検証してみましょう。csprojをいじって、ビルド時にUseNuGetを指定するとCPMを無効にして任意のNuGetパッケージを追加するようにします。
<!-- Normal.csproj --> <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net10.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <!-- ここから追加 --> <PropertyGroup Condition="'$(UseNuGet)' != ''"> <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally> </PropertyGroup> <ItemGroup Condition="'$(UseNuGet)' != ''"> <PackageReference Include="MagicOnion.Client" Version="$(UseNuGet)"/> </ItemGroup> <!-- ここまで --> </Project>
これをビルドするとビルドエラーが発生します。原因は、Directory.Build.propsで指定されているMicrosoft.SourceLink.GitHubが追加されますが、Directory.Build.propsではバージョンを指定していないためです。それはそう。
$ dotnet build -p:UseNuGet=6.1.4
./Override/Normal.csproj : error NU1015: The following PackageReference item(s) do not have a version specified: Microsoft.SourceLink.GitHub
Restore failed with 1 error(s) in 1.2s
具体的な状況はdepsファイルで確認できます。Microsoft.SourceLink.GitHubのバージョンが"version": "(, )"になっており、指定されていないことがわかります。
# プロジェクト名がNormal.csprojなので、Normal.csproj.nuget.dgspec.jsonを見ます。
$ cat obj/Normal.csproj.nuget.dgspec.json
...省略
"frameworks": {
"net10.0": {
"targetAlias": "net10.0",
"dependencies": {
"MagicOnion.Client": {
"target": "Package",
"version": "[6.1.4, )"
},
"Microsoft.SourceLink.GitHub": {
"suppressParent": "All",
"target": "Package",
"version": "(, )"
}
},
...省略
一見するとNuGetパッケージを指定するときと同様に、.csprojに<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0"/> を追加すればよさそうです。が、これはDirectory.Build.propsと定義が重複するのでエラーになります。
<!-- Normal.csproj --> <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net10.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <!-- ここから追加 --> <PropertyGroup Condition="'$(UseNuGet)' != ''"> <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally> </PropertyGroup> <ItemGroup Condition="'$(UseNuGet)' != ''"> <PackageReference Include="MagicOnion.Client" Version="$(UseNuGet)"/> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0"/> <!-- ← これを追加 --> </ItemGroup> <!-- ここまで --> </Project>
ビルドエラーからパッケージ定義が重複していることがわかります。
$ dotnet build -p:UseNuGet=6.1.4
./Bad/Normal.csproj : warning NU1504: Duplicate 'PackageReference' items found. Remove the duplicate items or use the Update functionality to ensure a consistent restore behavior. The duplicate 'PackageReference' items are: Microsoft.SourceLink.GitHub , Microsoft.SourceLink.GitHub 8.0.0.
./Bad/Normal.csproj : error NU1015: The following PackageReference item(s) do not have a version specified: Microsoft.SourceLink.GitHub
Restore failed with 1 error(s) and 1 warning(s) in 1.2s
Includeだと既存の定義があっても新しい定義を追加しようとするんですね。Directory.Build.propsで指定したPackageReferenceをcsprojで上書きしたいときは、<PackageReference Update="..." Version="上書きしたいバージョン" />を使います。
<!-- Normal.csproj --> <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net10.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <!-- ここから追加 --> <PropertyGroup Condition="'$(UseNuGet)' != ''"> <ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally> </PropertyGroup> <ItemGroup Condition="'$(UseNuGet)' != ''"> <PackageReference Include="MagicOnion.Client" Version="$(UseNuGet)"/> <PackageReference Update="Microsoft.SourceLink.GitHub" Version="8.0.0"/> <!-- ← Updateを使う --> </ItemGroup> <!-- ここまで --> </Project>
ビルドが成功していますね。
dotnet build -p:UseNuGet=6.1.4 Restore succeeded with 1 warning(s) in 2.4s Build succeeded with 2 warning(s) in 7.5s
まとめ
もし、どこかですでに<PackageReference Include="..."/>と定義したパッケージをプロジェクトファイルで上書きしたいときは、<PackageReference Update="..." Version="X.Y.Z"/>を使うと上書きできます。
状況的に、Directory.Build.propsやカスタムprops読み込みを使っていない限りは遭遇しないでしょう。ただ、csproj管理の効率化のためにDirectory.Build.propsを使っているときは、思いがけず遭遇する可能性があります。
今ならこういうのはLLM聞けば? ってなるのですが、割とこういうファイルを探索する系はLLMもすんなり答えにたどり着かないことがあります。基礎知識ということで。なお、Microsoft Learnにはこの記載がないので、なかなか気づけないです。