しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

NuGet 6.4 の Central Package Management を使って複数プロジェクト間のバージョン管理を改善する

Visual Studio 2022 17.4 以降に含まれている NuGet 6.4 では、複数プロジェクトでのバージョン管理を一元化出来る Central Package Management が実運用可能なレベルとして提供されています。

NuGet などのパッケージマネージャを利用する際の共通の悩みが、プロジェクト間でバージョンがバラバラになってしまいやすく、それぞれが参照しているパッケージのバージョンを上げること自体に手間がかかることですが、NuGet 6.4 以降では CPM を有効化すると解消可能です。

最新の Visual Studio 2022 か .NET SDK をインストールしていれば CPM が利用可能なので、GitHub Actions などで利用する際には最新バージョンを使うように指定するのが安全です。

SDK スタイル以前の古い csproj でも利用可能ですが、PackageReference の利用が最低要件なので ASP.NET プロジェクトでは利用出来ません。その場合は ASP.NET Core への移行を検討する形になります。

実際に Central Package Management を有効にする方法ですが、シンプルに以下のような内容で Directory.Packages.props という名前のファイルをリポジトリやソリューションのルートに作成するだけです。このファイルでは ManagePackageVersionsCentrallytrue になっていることが重要になります。

<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.31.2" />
    <PackageVersion Include="Microsoft.Extensions.Http" Version="6.0.0" />
    <PackageVersion Include="Microsoft.NET.Sdk.Functions" Version="4.1.3" />
  </ItemGroup>
</Project>

実際のパッケージ情報は ItemGroup 内で PackageVersion を使って NuGet のパッケージ ID とバージョンを記述します。このファイルを作成した時点で既存の csproj は NU1008NU1010 が出てビルドが出来なくなるため、同時に csproj 側も対応する必要があります。

csproj 側での対応は PackageReference から Version を削除するだけなので簡単に終わります。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.Cosmos" />
    <PackageReference Include="Microsoft.NET.Sdk.Functions" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
  </ItemGroup>
</Project>

この状態になると Visual Studio や .NET SDK を使ったビルドが正しく通るようになります。

正直なところ CPM が必要な規模のソリューションでは手動での対応は無理なはずなので、以下のようなツールを使って機械的に対応するのが良いです。

CPM を有効化した後の NuGet パッケージの追加や更新ですが、Visual Studio の NuGet パッケージマネージャーを使うと自動的に Directory.Packages.props に対して更新を行ってくれるので、特別な対応は必要ありません。追加した場合でも以下のように csproj にバージョン無しで追加してくれるので、パッケージマネージャーでの対応は実運用で問題ないレベルです。

自分が確認している範囲では dotnet-outdated ツールが CPM に完全に対応できていないようでした。最低限の実装として Directory.Packages.props ファイルは認識してくれますが、一部のパッケージしか表示されないなど挙動が怪しかったです。

後は GitHub の Dependency Graph 上はバージョンが正しく認識されていませんでした。今後の対応に期待したいですが、現時点ではバージョンが >= 0 という扱いになります。

Dependabot を利用する際の注意点

GitHub とパッケージマネージャーと言えば Dependabot の存在は外せないので、CPM を有効にした状態で複数プロジェクトを追加して動作を確認しました。

結論としては問題なく動作しますが、最初は Directory.Packages.props をリポジトリのルートに置いておくと読み取ってくれませんでした。以下のようにソリューションと同じディレクトリに置くと正しく認識されたので、親ディレクトリを辿る部分に問題がありそうでした。

あえて古いバージョンを参照した状態で Dependabot を動かすと、以下のように Directory.Packages.props に対する Pull Request が作成されました。これは完全に意図した動作になるのと、複数の csproj があっても更新ファイルは 1 つだけなので変更点もシンプルです。

公式からの Production Ready との宣言通りに、CPM は実運用可能なレベルに達していると感じました。プロジェクト数が少ない場合はメリットを感じにくそうですが、大規模になるとパッケージ管理にもガバナンスが必要になるため CPM は良いツールとなるはずです。