Visual Studioが何度も無関係のプロジェクトをビルドするのは、奴のせいかもしれない – NuGet編

NuGetまるで続きがあるかのようなラノベタイトルですが、一話完結です (;´Д`)

ものすごーく長い間、頭を悩ませてきた問題の一つが、先日ようやく判明したので共有しようと思います。
Visual Studio 2013(より前のバージョンも多分)で、比較的中規模~大規模なソリューション(沢山プロジェクトが含まれている)において、クリーンビルド後にも再びビルドが走って「イラっと」する問題です。

何度やっても発生する

GitなどのSCMからソースコードだけを取得して、完全なクリーンビルドを行っても起きます。これが起きると、以後のビルドは「リビルド」の如く、依存関係上ビルド不要なものを次々とビルドしてしまうので、開発効率が極めて悪くなります。

「お前はバッチファイルでビルドしてるだろ」

と言いたくなります。

何故か、自分一人で開発している場合には発生しません。また、使用しているPCによって、起きたり起きなかったりします。PCによって挙動が大体決まるものの、同じPCで試行しても現象が変わる事があります。規模の問題なのか、プロジェクトに関わっている誰かが何かをしたのか、PCが腐っているのか、Visual Studioの問題なのか、ずっと謎のままでした。

原因

謎な要因は、得てして複雑な背景があるものですが、今回もそうでした。

  • プロジェクトには「NuGet」でパッケージ導入しているものが含まれます。とあるパッケージ「ABC」のバージョンが「1.0.0」とします。
  • ビルドされたバイナリは、共通のフォルダに格納されます。例えば、「$(SolutionDir)$(PlatformName)\$(ConfigurationName)」です。今回のシステムでは、アセンブリを動的にロードする要件があるので、プロジェクトのサブフォルダに出力されるとデバッグがやりにくいのです(というか、デフォルトでこうして欲しいんだけどなぁ)。

チーム内の誰かが、新たにプロジェクトを追加する場合に、自分が関わったプロジェクトの構成をひな形に、新しいプロジェクトを追加します。この時、手動でアセンブリ参照を追加します。その中には「ABC.1.0.0.dll」が含まれます。手動で追加しているので、当然ファイルダイアログから「packages」フォルダ内を参照して追加するわけですが、これによって「NuGetとは何の関係もない事になっているアセンブリを追加した」事になります。

さて、コアメンバーが新しいNuGetパッケージ群に対応させるため、NuGetのパッケージを更新したとします。ここで、SafeUpdateをしないと、「ABC」パッケージには「1.0.0」と「1.0.3」のような、二つのバージョンが混在する可能性があります(SafeUpdateは、Update-Packageに-Safeを付ける)。packagesフォルダ内には、この二種類のパッケージが配置され、プロジェクトファイルも新しいバージョンを使うように修正されます、

プロジェクトがNuGetで構成されていれば。

手動でアセンブリ参照を追加したプロジェクトは、当然packages.configファイルを持っていません。そのため、NuGetのUpdateで自動的に参照が更新されません。しかし、SafeUpdateしなかったためにpackagesフォルダには「1.0.0」のパッケージが残っており、ビルド自体には成功します。ここに問題があります。

複数のプロジェクトがビルドされると、これらの混在したバージョンのアセンブリが、何度も「共通の」出力先フォルダにコピーされます。その結果、1.0.0と1.0.3のアセンブリが、まるでロシアンルーレットのように上書きコピーされ、ビルド完了時にはどちらのバージョンが配置されたのか分かりません。すると、次回ビルド時に、何の変更をしていなくても、Visual Studioが「あ、ビルドしなきゃ」と思っていそいそとビルドを始めます。

この問題は、マルチコアによる平行ビルドを実行すると顕著に表われます。環境によって発生したりしなかったりし、再現性が無いのでかなりイライラします orz

解決方法

  • NuGetで複数のバージョンを同時に使わない事。確認するには、ビルド後にpackagesフォルダ内を見て、複数のバージョンのパッケージが配置されていない事を確認する。もし、複数のバージョンが含まれていたら、どのプロジェクトが犯人かをチェックして修正する。csproj内のパスを修正し、packages.config内のバージョンも修正する。一旦、古いバージョンに合わせた後、SafeUpdateで一括更新すると楽かもしれない。
  • プロジェクトに、直接アセンブリ参照している構成が無い事を確認する。これはプロジェクトが多いと地味に苦痛かもしれない。
  • NuGet絡みで書きましたが、「共通の出力先フォルダ」を使い、異なるバージョンの同一のアセンブリ名のファイルを配置していると、同様の問題が発生する可能性があります。心当たりある場合は、確認してみましょう。

感想

久しぶりに一発ビルド完了した状態を見た。感慨深かった。
NuGet、ALMの一部としてみると脆弱で辛い。PSで構成とか、悪夢だ…