Bagikan melalui


Memecahkan masalah referensi perakitan

Salah satu tugas terpenting dalam MSBuild dan proses build .NET adalah menyelesaikan referensi perakitan, yang terjadi dalam ResolveAssemblyReference tugas. Artikel ini menjelaskan beberapa detail cara ResolveAssemblyReference kerjanya, dan cara memecahkan masalah kegagalan build yang dapat terjadi ketika ResolveAssemblyReference tidak dapat menyelesaikan referensi. Untuk menyelidiki kegagalan referensi perakitan, Anda mungkin ingin menginstal Penampil Log Terstruktur untuk melihat log MSBuild. Cuplikan layar dalam artikel ini diambil dari Penampil Log Terstruktur.

Tujuannya ResolveAssemblyReference adalah untuk mengambil semua referensi yang ditentukan dalam .csproj file (atau di tempat lain) melalui <Reference> item dan memetakannya ke jalur ke file rakitan di sistem file.

Pengkompilasi hanya dapat menerima .dll jalur pada sistem file sebagai referensi, jadi ResolveAssemblyReference mengonversi string seperti mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 yang muncul dalam file proyek ke jalur seperti C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll, yang kemudian diteruskan ke pengkompilasi melalui sakelar /r .

ResolveAssemblyReference Selain itu menentukan set lengkap (sebenarnya penutupan transitif dalam istilah teori grafik) dari semua .dll dan .exe referensi secara rekursif, dan untuk masing-masing menentukan apakah itu harus disalin ke direktori output build atau tidak. Ini tidak melakukan penyalinan aktual (yang ditangani nanti, setelah langkah kompilasi aktual), tetapi menyiapkan daftar item file untuk disalin.

ResolveAssemblyReference dipanggil dari ResolveAssemblyReferences target:

Cuplikan layar penampil log memperlihatkan saat ResolveAssemblyReferences dipanggil dalam proses build.

Jika Anda melihat pemesanan, ResolveAssemblyReferences terjadi sebelum Compile, dan tentu saja, CopyFilesToOutputDirectory terjadi setelah Compile.

Catatan

ResolveAssemblyReferencetugas dipanggil dalam file Microsoft.Common.CurrentVersion.targets standar .targets di folder penginstalan MSBuild. Anda juga dapat menelusuri target .NET SDK MSBuild secara online di https://github.com/dotnet/msbuild/blob/a936b97e30679dcea4d99c362efa6f732c9d3587/src/Tasks/Microsoft.Common.CurrentVersion.targets#L1991-L2140. Tautan ini memperlihatkan dengan tepat di mana ResolveAssemblyReference tugas dipanggil dalam .targets file.

Input ResolveAssemblyReference

ResolveAssemblyReference komprehensif tentang pengelogan inputnya:

Cuplikan layar memperlihatkan parameter input untuk tugas ResolveAssemblyReference.

Simpul Parameters adalah standar untuk semua tugas, tetapi juga ResolveAssemblyReference mencatat sekumpulan informasinya sendiri di bawah Input (yang pada dasarnya sama seperti di bawah Parameters, tetapi disusun secara berbeda).

Input yang paling penting adalah Assemblies dan AssemblyFiles:

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

Assemblies menggunakan konten Reference item MSBuild saat ini ketika ResolveAssemblyReference dipanggil untuk proyek. Semua referensi metadata dan rakitan, termasuk referensi NuGet Anda, harus dimuat dalam item ini. Setiap referensi memiliki sekumpulan metadata yang kaya yang melekat padanya:

Cuplikan layar memperlihatkan metadata pada referensi perakitan.

AssemblyFiles berasal dari ResolveProjectReference item output target yang disebut _ResolvedProjectReferencePaths. ResolveProjectReference berjalan sebelum ResolveAssemblyReference dan mengonversi <ProjectReference> item ke jalur rakitan bawaan pada disk. AssemblyFiles Jadi akan berisi rakitan yang dibangun oleh semua proyek yang dirujuk dari proyek saat ini:

Cuplikan layar memperlihatkan AssemblyFiles.

Input lain yang berguna adalah parameter boolean FindDependencies , yang mengambil nilainya dari _FindDependencies properti:

FindDependencies="$(_FindDependencies)"

Anda dapat mengatur properti ini ke false dalam build Anda untuk menonaktifkan analisis rakitan dependensi transitif.

Algoritma ResolveAssemblyReference

Algoritma yang disederhanakan ResolveAssemblyReference untuk tugas adalah sebagai berikut:

  1. Input log.
  2. MSBUILDLOGVERBOSERARSEARCHRESULTS Periksa variabel lingkungan. Atur variabel ini ke nilai apa pun untuk mendapatkan log yang lebih rinci.
  3. Menginisialisasi objek tabel referensi.
  4. Baca file cache dari obj direktori (jika ada).
  5. Menghitung penutupan dependensi.
  6. Buat tabel output.
  7. Tulis file cache ke obj direktori.
  8. Catat hasilnya.

Algoritma mengambil daftar input rakitan (baik dari metadata maupun referensi proyek), mengambil daftar referensi untuk setiap rakitan yang diproses (dengan membaca metadata) dan membangun set lengkap (penutupan transitif) dari semua rakitan yang direferensikan, dan menyelesaikannya dari berbagai lokasi (termasuk GAC, AssemblyFoldersEx, dan sebagainya).

Rakitan yang dirujuk ditambahkan ke daftar secara berulang sampai tidak ada lagi referensi baru yang ditambahkan. Kemudian algoritma berhenti.

Referensi langsung yang Anda berikan ke tugas disebut Referensi utama. Rakitan tidak langsung yang ditambahkan ke set karena referensi transitif disebut Dependensi. Catatan untuk setiap rakitan tidak langsung melacak semua item utama ("root") yang menyebabkan penyertaannya dan metadata yang sesuai.

Hasil tugas ResolveAssemblyReference

ResolveAssemblyReference menyediakan pengelogan terperinci dari hasil:

Cuplikan layar memperlihatkan hasil ResolveAssemblyReference di penampil log terstruktur.

Rakitan yang diselesaikan dibagi menjadi dua kategori: Referensi utama dan Dependensi. Referensi utama ditentukan secara eksplisit sebagai referensi proyek yang sedang dibangun. Dependensi disimpulkan dari referensi referensi secara transitif.

Penting

ResolveAssemblyReference membaca metadata rakitan untuk menentukan referensi rakitan tertentu. Ketika pengkompilasi C# memancarkan rakitan, itu hanya menambahkan referensi ke rakitan yang benar-benar diperlukan. Jadi, mungkin terjadi bahwa ketika Anda mengkompilasi proyek tertentu, proyek dapat menentukan referensi yang tidak diperlukan yang tidak akan dipanggang ke dalam rakitan. Tidak apa-apa untuk menambahkan referensi ke proyek yang tidak diperlukan; mereka diabaikan.

Metadata item CopyLocal

Referensi juga dapat memiliki CopyLocal metadata atau tidak. Jika referensi memiliki CopyLocal = true, referensi tersebut nantinya akan disalin ke direktori output oleh CopyFilesToOutputDirectory target. Dalam contoh ini, DataFlow telah CopyLocal diatur ke true, sementara Immutable tidak:

Cuplikan layar memperlihatkan pengaturan CopyLocal untuk beberapa referensi.

CopyLocal Jika metadata hilang sepenuhnya, metadata diasumsikan benar secara default. Jadi ResolveAssemblyReference secara default mencoba menyalin dependensi ke output kecuali menemukan alasan untuk tidak. ResolveAssemblyReference mencatat alasan mengapa ia memilih referensi tertentu menjadi CopyLocal atau tidak.

Semua kemungkinan alasan keputusan CopyLocal dijumlahkan dalam tabel berikut. Sangat berguna untuk mengetahui string ini untuk dapat mencarinya di log build.

Status CopyLocal Deskripsi
Undecided Status lokal salinan tidak terdeteksi sekarang.
YesBecauseOfHeuristic Referensi harus memiliki CopyLocal='true' karena itu bukan 'tidak' karena alasan apa pun.
YesBecauseReferenceItemHadMetadata Referensi harus memiliki CopyLocal='true' karena item sumbernya memiliki Private='true'
NoBecauseFrameworkFile Referensi harus memiliki CopyLocal='false' karena ini adalah file kerangka kerja.
NoBecausePrerequisite Referensi harus memiliki CopyLocal='false' karena ini adalah file prasyarat.
NoBecauseReferenceItemHadMetadata Referensi harus memiliki CopyLocal='false' karena Private atribut diatur ke 'false' dalam proyek.
NoBecauseReferenceResolvedFromGAC Referensi harus memiliki CopyLocal='false' karena diselesaikan dari GAC.
NoBecauseReferenceFoundInGAC Perilaku warisan, CopyLocal='false' ketika rakitan ditemukan di GAC (bahkan ketika diselesaikan di tempat lain).
NoBecauseConflictVictim Referensi harus memiliki CopyLocal='false' karena kehilangan konflik antara file rakitan bernama yang sama.
NoBecauseUnresolved Referensi belum terselesaikan. Ini tidak dapat disalin ke direktori bin karena tidak ditemukan.
NoBecauseEmbedded Referensi disematkan. Ini tidak boleh disalin ke direktori bin karena tidak akan dimuat saat runtime.
NoBecauseParentReferencesFoundInGAC Properti copyLocalDependenciesWhenParentReferenceInGac diatur ke false dan semua item sumber induk ditemukan di GAC.
NoBecauseBadImage File rakitan yang disediakan tidak boleh disalin karena merupakan gambar yang buruk, mungkin tidak dikelola, mungkin bukan rakitan sama sekali.

Metadata item privat

Bagian penting dari penentuan CopyLocal adalah Private metadata pada semua referensi utama. Setiap referensi (utama atau dependensi) memiliki daftar semua referensi utama (item sumber) yang telah berkontribusi pada referensi tersebut yang ditambahkan ke penutupan.

  • Jika tidak ada item sumber yang menentukan Private metadata, CopyLocal diatur ke True (atau tidak diatur, yang defaultnya )True
  • Jika salah satu item sumber menentukan Private=true, CopyLocal diatur ke True
  • Jika tidak ada rakitan sumber yang menentukan Private=true dan setidaknya satu menentukan Private=false, CopyLocal diatur ke False

Referensi mana yang mengatur Privat ke false?

Poin terakhir adalah alasan yang sering digunakan untuk CopyLocal diatur ke 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 tidak memberi tahu kami referensi mana yang telah diatur Private ke false, tetapi penampil log terstruktur menambahkan Private metadata ke item yang ditentukan di atas:

Cuplikan layar memperlihatkan Kumpulan privat ke false di penampil log terstruktur.

Ini menyederhanakan penyelidikan dan memberi tahu Anda referensi mana yang menyebabkan dependensi yang bersangkutan diatur dengan CopyLocal=false.

Singgahan Perakitan Global

Global Assembly Cache (GAC) memainkan peran penting dalam menentukan apakah akan menyalin referensi ke output. Ini sangat disayangkan karena konten GAC khusus mesin dan ini mengakibatkan masalah untuk build yang dapat direproduksi (di mana perilaku berbeda pada komputer yang berbeda tergantung pada status komputer, seperti GAC).

Ada perbaikan terbaru yang dibuat untuk ResolveAssemblyReference meringankan situasi. Anda dapat mengontrol perilaku dengan dua input baru ini ke ResolveAssemblyReference:

    CopyLocalDependenciesWhenParentReferenceInGac="$(CopyLocalDependenciesWhenParentReferenceInGac)"
    DoNotCopyLocalIfInGac="$(DoNotCopyLocalIfInGac)"

AssemblySearchPaths

Ada dua cara untuk mengkustomisasi ResolveAssemblyReference daftar pencarian jalur dalam mencoba menemukan rakitan. Untuk sepenuhnya menyesuaikan daftar, properti AssemblySearchPaths dapat diatur sebelumnya. Pesanan penting; jika rakitan berada di dua lokasi, ResolveAssemblyReference berhenti setelah menemukannya di lokasi pertama.

Secara default, ada sepuluh pencarian lokasi ResolveAssemblyReference (empat jika Anda menggunakan .NET SDK), dan masing-masing dapat dinonaktifkan dengan mengatur bendera yang relevan ke false:

  • Mencari file dari proyek saat ini dinonaktifkan dengan mengatur properti ke AssemblySearchPath_UseCandidateAssemblyFiles false.
  • Mencari properti jalur referensi (dari .user file) dinonaktifkan dengan mengatur properti ke AssemblySearchPath_UseReferencePath false.
  • Menggunakan jalur petunjuk dari item dinonaktifkan dengan mengatur properti ke AssemblySearchPath_UseHintPathFromItem false.
  • Menggunakan direktori dengan runtime target MSBuild dinonaktifkan dengan mengatur AssemblySearchPath_UseTargetFrameworkDirectory properti ke false.
  • Mencari folder rakitan dari AssemblyFolders.config dinonaktifkan dengan mengatur AssemblySearchPath_UseAssemblyFoldersConfigFileSearchPath properti ke false.
  • Mencari registri dinonaktifkan dengan mengatur properti ke AssemblySearchPath_UseRegistry false.
  • Mencari folder rakitan terdaftar warisan dinonaktifkan dengan mengatur AssemblySearchPath_UseAssemblyFolders properti ke false.
  • Mencari di GAC dinonaktifkan dengan mengatur properti ke AssemblySearchPath_UseGAC false.
  • Memperlakukan Include referensi sebagai nama file nyata dinonaktifkan dengan mengatur AssemblySearchPath_UseRawFileName properti ke false.
  • Memeriksa folder output aplikasi dinonaktifkan dengan mengatur properti ke AssemblySearchPath_UseOutDir false.

Terjadi konflik

Situasi umum adalah MSBuild memberikan peringatan tentang versi yang berbeda dari rakitan yang sama yang digunakan oleh referensi yang berbeda. Solusi ini sering melibatkan penambahan pengalihan pengikatan ke file app.config.

Cara yang berguna untuk menyelidiki konflik ini adalah dengan mencari di MSBuild Structured Log Viewer untuk "Ada konflik". Ini menunjukkan kepada Anda informasi terperinci tentang referensi mana yang diperlukan versi rakitan mana yang dimaksud.