次の方法で共有


アセンブリ参照のトラブルシューティング

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 が呼び出されたときを示す、ログ ビューアーのスクリーンショット。

順序を見ると、ResolveAssemblyReferencesCompile の前に処理され、CopyFilesToOutputDirectoryCompile の後に処理されます。

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 は、以下のように入力のログ記録を包括的に示します。

ResolveAssemblyReference タスクの入力パラメーターを示すスクリーンショット。

Parameters ノードはすべてのタスクに標準ですが、追加で ResolveAssemblyReference が [Inputs] の下の独自の情報セットをログに記録します (基本的には [Parameters] の下の情報と同じですが、構造が異なります)。

最も重要な入力は、以下の AssembliesAssemblyFiles です。

    <ResolveAssemblyReference
        Assemblies="@(Reference)"
        AssemblyFiles="@(_ResolvedProjectReferencePaths);@(_ExplicitReference)"

Assemblies は、プロジェクトに対して ResolveAssemblyReference が呼び出されたときに、 MSBuild 項目 Reference の内容を使用します。 NuGet 参照を含むすべてのメタデータとアセンブリ参照は、この項目に含まれています。 各参照には、豊富なメタデータ セットがアタッチされています。

アセンブリ参照のメタデータを示すスクリーンショット。

AssemblyFiles は、ResolveProjectReference ターゲットの出力項目、_ResolvedProjectReferencePaths から取得されます。 ResolveProjectReferenceResolveAssemblyReference の前に実行され、<ProjectReference> 項目をディスク上のビルド済みアセンブリのパスに変換します。 そのため、AssemblyFiles には、現在のプロジェクトのすべての参照プロジェクトによってビルドされたアセンブリが含まれます。

AssemblyFiles を示すスクリーンショット。

もう 1 つの有益な入力は、_FindDependencies プロパティからその値を受け取るブール値の FindDependencies パラメーターです。

FindDependencies="$(_FindDependencies)"

ビルドでこのプロパティを false に設定して、推移的な依存関係アセンブリの分析をオフにすることができます。

ResolveAssemblyReference のアルゴリズム

ResolveAssemblyReference タスクの簡略化されたアルゴリズムは次のとおりです。

  1. 入力をログ記録します。
  2. MSBUILDLOGVERBOSERARSEARCHRESULTS 環境変数をチェックします。 より詳細なログを記録するには、この変数を任意の値に設定します。
  3. 参照オブジェクトのテーブルを初期化します。
  4. obj ディレクトリからキャッシュ ファイルを読み取ります (存在する場合)。
  5. 依存関係のクロージャを計算します。
  6. 出力テーブルを構築します。
  7. キャッシュ ファイルを obj ディレクトリに書き込みます。
  8. 結果をログ記録します。

このアルゴリズムでは、アセンブリの入力リスト (メタデータとプロジェクト参照の両方から) を取得し、処理するアセンブリごとに参照の一覧を (メタデータを読み取って) 取得し、すべての参照アセンブリの完全なセット (推移的なクロージャ) を構築し、それらをさまざまな場所 (GAC、AssemblyFoldersEx など) から解決します。

参照アセンブリは、新しい参照が追加されない限り、繰り返しリストに追加されます。 その後、アルゴリズムが停止します。

タスクに提供した直接参照は、Primary reference (プライマリ参照) と呼ばれます。 推移的な参照のためにセットに追加された間接アセンブリは、Dependency (依存関係) と呼ばれます。 間接アセンブリごとのレコードは、そのインクルージョンとそれらに対応するメタデータに導かれたすべてのプライマリ ("ルート") 項目を追跡します。

ResolveAssemblyReference タスクの結果

ResolveAssemblyReference は、以下のように結果の詳細なログ記録を提供します。

構造化ログ ビューアーに ResolveAssemblyReference の結果を示すスクリーンショット。

解決されたアセンブリは、Primary reference (プライマリ参照) と Dependency (依存関係) の 2 つのカテゴリに分類されます。 プライマリ参照は、ビルド中のプロジェクトの参照として明示的に指定されました。 依存関係は、参照の参照から推移的に推論されました。

重要

ResolveAssemblyReference は、アセンブリ メタデータを読み取って、特定のアセンブリの参照を決定します。 C# コンパイラはアセンブリを生成するとき、実際に必要なアセンブリへの参照のみを追加します。 そのため、特定のプロジェクトをコンパイルするときに、アセンブリに組み込まれない不要な参照が指定される場合があります。 プロジェクトへの不要な参照を追加してもかまいません。それらは無視されます。

CopyLocal 項目メタデータ

参照には、CopyLocal メタデータがあることもないこともあります。 参照に CopyLocal = trueがある場合は、後で CopyFilesToOutputDirectory ターゲットによって出力ディレクトリにコピーされます。 この例では、DataFlow には true に設定された CopyLocal がありますが、Immutable にはありません。

一部の参照の CopyLocal の設定を示すスクリーンショット。

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 メタデータを指定していない場合、CopyLocalTrue に設定されます (または設定されません。既定値は True です)
  • いずれかのソース項目が Private=true を指定している場合、CopyLocalTrue に設定されます
  • どのソース アセンブリも Private=true を指定していなくて、少なくとも 1 つが Private=false を指定している場合、CopyLocalFalse に設定されます

どの参照が 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 メタデータが追加されます。

構造化ログ ビューアで 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" を検索することです。 これにより、どの参照が問題のアセンブリのどのバージョンが必要であったかに関する詳細情報が表示されます。