複数のプロジェクトをひとつのリポジトリで管理するとき、プロジェクト間の参照関係は csproj に <ProjectReference>
を書くわけですが、ここで、このプロジェクトを NuGet パッケージ化するときのことを考えます。例えば、 A と B というプロジェクトがあり、 B が A に依存しているとします。このとき B を dotnet pack
してできあがるパッケージの A への依存はどのようになるでしょうか? 実際にやってみると、現在の A のバージョン以上という依存関係になります。
ここで、 A の現在のバージョンを 1.0.0 とします。 Semantic Versioning に従っていると考えると、もし 2.0.0 がリリースされたら、破壊的な変更が入っているかもしれません。それでも B から A への依存は 1.0.0 以上で良いのでしょうか? と考えると、「以上」以外の柔軟な依存関係を指定したくなりませんか? というわけで、 <ProjectReference>
を使ったプロジェクト間参照で、柔軟なバージョン範囲指定をしたいというのが今回のお話です。
サンプルプロジェクト
文章でだらだらと説明されても読みたくないのはわかります。ので、実際の csproj を示しておきます。
ここで、 B に対して dotnet pack
を実行したときの nuspec の <dependencies>
はこのようになります。
<dependencies>
<group targetFramework=".NETStandard2.0">
<dependency id="A" version="1.0.0" exclude="Build,Analyzers" />
</group>
</dependencies>
version="1.0.0"
という指定は、「1.0.0 以上」と解釈されます。
目標設定
ここでは、 Semantic Versioning という前提で、 B が依存するのは A v1.0.0 以上 2.0.0 未満、としましょう。こうすれば、 B が A の Public API のみに依存しているならば、 B はこの依存関係が解決できる限り、必ず動作するといえます。
一筋縄で実現できたらブログ書いてない
はい。これは NuGet の Issue (NuGet/Home#5556) に挙がっており、現在も実現されていません。しかし頑張ればできないこともない状況になっています。
必要なものは .NET 5.0 Preview SDK (執筆時点で 5.0.100-preview.3.20216.6) です。最新の NuGet を搭載している SDK を使うと、 csproj に少し手を入れるだけで、 <ProjectReference>
に対する依存関係に介入できるようになります。
目標をクリアする csproj
仕組みとかいいからとりあえず使いたいって人は、これをコピペしてください。バージョンの指定方法は、 NuGet のドキュメント (Version ranges) を確認してください。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\A\A.csproj" />
</ItemGroup>
<!-- 以下を追加 -->
<Target Name="SetDependencyVersion" AfterTargets="_GetProjectReferenceVersions">
<ItemGroup>
<_ProjectReferencesWithVersions Update="..\A\A.csproj" ProjectVersion="[1.0.0,2.0.0)" />
</ItemGroup>
</Target>
</Project>
出力される nuspec の <dependencies>
はこんな感じになります。
<dependencies>
<group targetFramework=".NETStandard2.0">
<dependency id="A" version="[1.0.0, 2.0.0)" exclude="Build,Analyzers" />
</group>
</dependencies>
仕組み
dotnet pack
(MSBuild で Pack
ターゲットを実行する) では、 <ProjectReference>
Item があったら、そのプロジェクトのバージョンを読み込み、 <_ProjectReferencesWithVersions>
という Item を作成します。そこで、その処理が行われる _GetProjectReferenceVersions
ターゲットの後に、読み込まれたバージョンを上書きするようなターゲットを作成することで、好きなバージョンに書き換えることができます。
ここまでは古い SDK でもできたのですが、古い SDK では ProjectVersion
属性にバージョンの範囲が指定されることを想定していませんでした。つまり 1.0.0
は受け付けるけど、 [1.0.0,2.0.0)
は受け付けてくれなかったわけです。新しい SDK では、範囲を指定してもエラーにならないようになったので、このようなハックでお茶を濁せるようになりました。
今後もっと簡単になるか?
NuGet/Home#5556 を監視していきましょう。
NuGet に対するぼやき
依存関係解決の戦略がデフォルトで「条件を満たす最小バージョン」な所為で、依存バージョンをすぐ「以上」にしてしまうのは NuGet の悪いところだなぁと思っています。そのおかげで lock ファイルを使わなくても、あまり崩壊しないという利点はありますが、少なくともリビジョンリリースは自動で最新にしてほしくない? という思いがあります。