アセンブリ参照のトラブルシューティング
MSBuild および .NET ビルド プロセスで最も重要なタスクの 1 つは、ResolveAssemblyReference
タスクで発生するアセンブリ参照の解決です。 この記事では、ResolveAssemblyReference
の動作の詳細と、ResolveAssemblyReference
が参照を解決できないときに発生する可能性があるビルド エラーのトラブルシューティングの方法について説明します。 アセンブリ参照エラーを調査するために、構造化ログ ビューアーをインストールして MSBuild ログを表示できます。 この記事のスクリーンショットは、構造化ログ ビューアーから取得されています。
ResolveAssemblyReference
の目的は、<Reference>
項目を介して .csproj
ファイル (または他の場所) で指定されたすべての参照を取得し、それらをファイルシステム内のアセンブリ ファイルへのパスにマップすることです。
コンパイラはファイルシステム上の .dll
パスのみを参照として受け入れることができるため、ResolveAssemblyReference
は、プロジェクト ファイルに現れる mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
などの文字列を C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll
などのパスに変換します。これらは、/r
スイッチを介してコンパイラに渡されます。
さらに、ResolveAssemblyReference
は、すべての .dll
および .exe
参照の完全なセット (実際には、グラフ理論の用語では推移閉包) を決定し、それぞれに対してビルド出力ディレクトリにコピーするかどうかを決定します。 実際のコピーは行いませんが (コピーは実際のコンパイル手順の後で処理されます)、コピーするファイルの項目リストを準備します。
ResolveAssemblyReference
は、以下のように ResolveAssemblyReferences
ターゲットから呼び出されます。
順序を見ると、ResolveAssemblyReferences
は Compile
の前に処理され、CopyFilesToOutputDirectory
は Compile
の後に処理されます。
Note
ResolveAssemblyReference
タスクは、MSBuild インストール フォルダーにある標準の .targets
ファイル、Microsoft.Common.CurrentVersion.targets
で呼び出されます。 https://github.com/dotnet/msbuild/blob/a936b97e30679dcea4d99c362efa6f732c9d3587/src/Tasks/Microsoft.Common.CurrentVersion.targets#L1991-L2140 で、.NET SDK MSBuild ターゲットをオンラインで参照することもできます。 このリンクは、.targets
ファイルで ResolveAssemblyReference
タスクが呼び出される正確な場所を示しています。
ResolveAssemblyReference の入力
ResolveAssemblyReference
は、以下のように入力のログ記録を包括的に示します。
Parameters
ノードはすべてのタスクに標準ですが、追加で ResolveAssemblyReference
が [Inputs] の下の独自の情報セットをログに記録します (基本的には [Parameters
] の下の情報と同じですが、構造が異なります)。
最も重要な入力は、以下の Assemblies
と AssemblyFiles
です。
<ResolveAssemblyReference
Assemblies="@(Reference)"
AssemblyFiles="@(_ResolvedProjectReferencePaths);@(_ExplicitReference)"
Assemblies
は、プロジェクトに対して ResolveAssemblyReference
が呼び出されたときに、 MSBuild 項目 Reference
の内容を使用します。 NuGet 参照を含むすべてのメタデータとアセンブリ参照は、この項目に含まれています。 各参照には、豊富なメタデータ セットがアタッチされています。
AssemblyFiles
は、ResolveProjectReference
ターゲットの出力項目、_ResolvedProjectReferencePaths
から取得されます。 ResolveProjectReference
は ResolveAssemblyReference
の前に実行され、<ProjectReference>
項目をディスク上のビルド済みアセンブリのパスに変換します。 そのため、AssemblyFiles
には、現在のプロジェクトのすべての参照プロジェクトによってビルドされたアセンブリが含まれます。
もう 1 つの有益な入力は、_FindDependencies
プロパティからその値を受け取るブール値の FindDependencies
パラメーターです。
FindDependencies="$(_FindDependencies)"
ビルドでこのプロパティを false
に設定して、推移的な依存関係アセンブリの分析をオフにすることができます。
ResolveAssemblyReference のアルゴリズム
ResolveAssemblyReference
タスクの簡略化されたアルゴリズムは次のとおりです。
- 入力をログ記録します。
MSBUILDLOGVERBOSERARSEARCHRESULTS
環境変数をチェックします。 より詳細なログを記録するには、この変数を任意の値に設定します。- 参照オブジェクトのテーブルを初期化します。
obj
ディレクトリからキャッシュ ファイルを読み取ります (存在する場合)。- 依存関係のクロージャを計算します。
- 出力テーブルを構築します。
- キャッシュ ファイルを
obj
ディレクトリに書き込みます。 - 結果をログ記録します。
このアルゴリズムでは、アセンブリの入力リスト (メタデータとプロジェクト参照の両方から) を取得し、処理するアセンブリごとに参照の一覧を (メタデータを読み取って) 取得し、すべての参照アセンブリの完全なセット (推移的なクロージャ) を構築し、それらをさまざまな場所 (GAC、AssemblyFoldersEx など) から解決します。
参照アセンブリは、新しい参照が追加されない限り、繰り返しリストに追加されます。 その後、アルゴリズムが停止します。
タスクに提供した直接参照は、Primary reference (プライマリ参照) と呼ばれます。 推移的な参照のためにセットに追加された間接アセンブリは、Dependency (依存関係) と呼ばれます。 間接アセンブリごとのレコードは、そのインクルージョンとそれらに対応するメタデータに導かれたすべてのプライマリ ("ルート") 項目を追跡します。
ResolveAssemblyReference タスクの結果
ResolveAssemblyReference
は、以下のように結果の詳細なログ記録を提供します。
解決されたアセンブリは、Primary reference (プライマリ参照) と Dependency (依存関係) の 2 つのカテゴリに分類されます。 プライマリ参照は、ビルド中のプロジェクトの参照として明示的に指定されました。 依存関係は、参照の参照から推移的に推論されました。
重要
ResolveAssemblyReference
は、アセンブリ メタデータを読み取って、特定のアセンブリの参照を決定します。 C# コンパイラはアセンブリを生成するとき、実際に必要なアセンブリへの参照のみを追加します。 そのため、特定のプロジェクトをコンパイルするときに、アセンブリに組み込まれない不要な参照が指定される場合があります。 プロジェクトへの不要な参照を追加してもかまいません。それらは無視されます。
CopyLocal 項目メタデータ
参照には、CopyLocal
メタデータがあることもないこともあります。 参照に CopyLocal = true
がある場合は、後で CopyFilesToOutputDirectory
ターゲットによって出力ディレクトリにコピーされます。 この例では、DataFlow
には true に設定された CopyLocal
がありますが、Immutable
にはありません。
CopyLocal
メタデータがまったくない場合、既定では true であると見なされます。 そのため、既定では、ResolveAssemblyReference
はコピーしない理由が見つからない限り、依存関係を出力にコピーしようとします。 ResolveAssemblyReference
は、特定の参照が CopyLocal
であるかどうかを選択した理由を記録します。
CopyLocal
の決定に関して、考えられるすべての理由を次の表に列挙します。 これらの文字列をビルド ログで検索できることを知っていると便利です。
CopyLocal の状態 | 説明 |
---|---|
Undecided |
現在、CopyLocal の状態は未決定です。 |
YesBecauseOfHeuristic |
何らかの理由で 'no' ではなかったため、参照には CopyLocal='true' があります。 |
YesBecauseReferenceItemHadMetadata |
ソース項目に Private='true' があるため、参照には CopyLocal='true' があります。 |
NoBecauseFrameworkFile |
参照はフレームワーク ファイルであるため、参照には CopyLocal='false' があります。 |
NoBecausePrerequisite |
参照は前提条件ファイルであるため、参照には CopyLocal='false' があります。 |
NoBecauseReferenceItemHadMetadata |
プロジェクトで Private 属性が 'false' に設定されているため、参照には CopyLocal='false' があります。 |
NoBecauseReferenceResolvedFromGAC |
参照は GAC から解決されたため、参照には CopyLocal='false' があります。 |
NoBecauseReferenceFoundInGAC |
従来の動作、CopyLocal='false' 、アセンブリが GAC で見つかったとき (別の場所で解決されたときでも)。 |
NoBecauseConflictVictim |
参照は同じ名前のアセンブリ ファイル間の競合を失ったため、参照には CopyLocal='false' があります。 |
NoBecauseUnresolved |
参照は未解決でした。 参照は見つからなかったため、bin ディレクトリにコピーできません。 |
NoBecauseEmbedded |
参照は埋め込まれました。 参照は実行時に読み込まれないため、bin ディレクトリにコピーしてはいけません。 |
NoBecauseParentReferencesFoundInGAC |
copyLocalDependenciesWhenParentReferenceInGac プロパティは false に設定され、すべての親ソース項目は GAC で見つかりました。 |
NoBecauseBadImage |
提供されたアセンブリ ファイルは、不適切なイメージであり、管理されていない可能性があり、アセンブリではない可能性があるため、コピーしてはいけません。 |
Private 項目メタデータ
CopyLocal
の決定に重要な部分は、すべてのプライマリ参照の Private
メタデータです。 各参照 (プライマリまたは依存関係) には、クロージャに追加される、その参照に寄与したすべてのプライマリ参照 (ソース項目) の一覧があります。
- どのソース項目も
Private
メタデータを指定していない場合、CopyLocal
はTrue
に設定されます (または設定されません。既定値はTrue
です) - いずれかのソース項目が
Private=true
を指定している場合、CopyLocal
はTrue
に設定されます - どのソース アセンブリも
Private=true
を指定していなくて、少なくとも 1 つがPrivate=false
を指定している場合、CopyLocal
はFalse
に設定されます
どの参照が Private を false に設定するか?
最後のポイントは、CopyLocal
を false に設定するためによく使用される理由です。This reference is not "CopyLocal" because at least one source item had "Private" set to "false" and no source items had "Private" set to "true".
MSBuild からは、どの参照が Private
を false に設定したかは知らされませんが、構造化されたログ ビューアーによって、上で指定された項目に Private
メタデータが追加されます。
これにより調査が簡略化され、どの参照が問題の依存関係を CopyLocal=false
で設定させたかが正確に示されます。
グローバル アセンブリ キャッシュ
グローバル アセンブリ キャッシュ (GAC) は、参照を出力にコピーするかどうかを判断する上で重要な役割を果たします。 残念ながら、GAC の内容はマシン固有であり、再現可能なビルドの問題が発生します (GAC など、マシンの状態によって各マシンの動作が異なります)。
最近、状況を軽減するために、ResolveAssemblyReference
への修正が行われました。 ResolveAssemblyReference
への以下の 2 つの新しい入力によって、動作を制御できます。
CopyLocalDependenciesWhenParentReferenceInGac="$(CopyLocalDependenciesWhenParentReferenceInGac)"
DoNotCopyLocalIfInGac="$(DoNotCopyLocalIfInGac)"
AssemblySearchPaths
アセンブリを見つけようとする際に ResolveAssemblyReference
が検索するパスの一覧をカスタマイズするには、2 つの方法があります。 一覧を完全にカスタマイズするために、AssemblySearchPaths
プロパティを事前に設定できます。 順序が重要です。アセンブリが 2 つの場所にある場合、ResolveAssemblyReference
は最初の場所でアセンブリを見つけた後に停止します。
既定では、ResolveAssemblyReference
が検索する場所は 10 か所あり (.NET SDK を使用する場合は 4 か所)、関連フラグを false に設定することでそれぞれを無効にできます。
- 現在のプロジェクトからのファイルの検索を無効にするには、
AssemblySearchPath_UseCandidateAssemblyFiles
プロパティを false に設定します。 - 参照パス プロパティを検索するには (
.user
ファイルから)、AssemblySearchPath_UseReferencePath
プロパティを false に設定します。 - 項目からのヒント パスの使用を無効にするには、
AssemblySearchPath_UseHintPathFromItem
プロパティを false に設定します。 - MSBuild のターゲット ランタイムによるディレクトリの使用を無効にするには、
AssemblySearchPath_UseTargetFrameworkDirectory
プロパティを false に設定します。 - AssemblyFolders.config からのアセンブリ フォルダーの検索を無効にするには、
AssemblySearchPath_UseAssemblyFoldersConfigFileSearchPath
プロパティを false に設定します。 - レジストリの検索を無効にするには、
AssemblySearchPath_UseRegistry
プロパティを false に設定します。 - 従来の登録済みアセンブリ フォルダーの検索を無効にするには、
AssemblySearchPath_UseAssemblyFolders
プロパティを false に設定します。 - GAC の調査を無効にするには、
AssemblySearchPath_UseGAC
プロパティを false に設定します。 - 参照の Include を実際のファイル名として扱うことを無効にするには、
AssemblySearchPath_UseRawFileName
プロパティを false に設定します。 - アプリケーションの出力フォルダーの確認を無効にするには、
AssemblySearchPath_UseOutDir
プロパティを false に設定します。
There was a conflict (競合がありました)
一般的な状況として、MSBuild により、異なる参照で使用されている同じアセンブリの異なるバージョンに関する警告が表示されます。 多くの場合、これを解決するには、app.config ファイルへのバインド リダイレクトの追加が必要です。
これらの競合を調査する便利な方法は、MSBuild 構造化ログ ビューアーで "There was a conflict" を検索することです。 これにより、どの参照が問題のアセンブリのどのバージョンが必要であったかに関する詳細情報が表示されます。