Dukungan untuk Visual Studio 2019 dalam PVS-Studio segera mempengaruhi beberapa komponen yang berbeda: plug-in IDE itu sendiri, aplikasi analisis baris perintah, C ++ dan C # analisis, serta beberapa utilitas. Saya akan berbicara secara singkat tentang masalah apa yang kami temui dalam mendukung versi baru IDE dan bagaimana menyelesaikannya.
Sebelum Anda mulai, saya ingin melihat ke belakang sedikit untuk menelusuri sejarah dukungan untuk versi Visual Studio sebelumnya, yang akan memberikan pemahaman yang lebih baik tentang visi tugas dan keputusan yang dibuat dalam situasi tertentu.
Dimulai dengan versi pertama PVS-Studio analyzer, di mana plug-in untuk lingkungan Visual Studio muncul (kemudian itu juga versi Visual Studio 2005), mendukung versi baru Visual Studio adalah tugas yang cukup sederhana bagi kami - itu pada dasarnya bermula untuk memperbarui file proyek plug-in dan dependensi dari berbagai API ekstensi Visual Studio. Kadang-kadang perlu juga mendukung fitur-fitur baru dari bahasa C ++, yang secara bertahap dipelajari oleh kompiler Visual C ++, tetapi ini juga biasanya tidak menimbulkan masalah segera sebelum rilis Visual Studio edisi berikutnya. Dan hanya ada satu analisa di PVS-Studio saat itu - untuk bahasa C dan C ++.
Semuanya berubah untuk rilis Visual Studio 2017. Selain fakta bahwa banyak ekstensi API untuk IDE ini berubah sangat signifikan dalam versi ini, setelah pembaruan kami mengalami masalah memastikan kompatibilitas mundur pekerjaan penganalisis C # baru yang telah muncul pada waktu itu (serta lapisan C ++ baru kami) analyzer untuk proyek MSBuild) dengan versi yang lebih lama dari MSBuild \ Visual Studio.
Oleh karena itu, sebelum membaca artikel ini, saya sangat menyarankan Anda membaca artikel terkait tentang dukungan Visual Studio 2017: "dukungan
Visual Studio 2017 dan Roslyn 2.0 di PVS-Studio: kadang-kadang menggunakan solusi siap pakai tidak semudah kelihatannya pada pandangan pertama ." Artikel yang disebutkan di atas menjelaskan masalah yang kami temui terakhir kali, serta skema interaksi berbagai komponen (misalnya, PVS-Studio, MSBuild, Roslyn). Memahami interaksi ini akan sangat membantu saat membaca artikel ini.
Pada akhirnya, solusi untuk masalah ini membawa perubahan signifikan pada penganalisa kami, dan, seperti yang kami harapkan, pendekatan baru yang kami terapkan kemudian akan memungkinkan untuk mendukung versi terbaru dari Visual Studio \ MSBuild jauh lebih mudah dan lebih cepat di masa depan. Sebagian, asumsi ini telah dikonfirmasi oleh rilis berbagai pembaruan untuk Visual Studio 2017. Apakah pendekatan baru ini membantu dengan dukungan Visual Studio 2019? Tentang itu di bawah ini.
Plugin PVS-Studio untuk Visual Studio 2019
Semuanya dimulai, sepertinya, tidak buruk. Cukup mudah untuk port plug-in ke Visual Studio 2019, di mana ia mulai dan bekerja dengan baik. Meskipun demikian, 2 masalah segera terungkap, yang menjanjikan masalah di masa depan.
Yang pertama adalah antarmuka
IVsSolutionWorkspaceService , yang digunakan untuk mendukung mode Beban Solusi Ringan, yang, dengan cara, dinonaktifkan di salah satu pembaruan sebelumnya di Visual Studio 2017, dihiasi dengan atribut
Deprecated , yang hanya merupakan peringatan selama perakitan, tetapi menjanjikan lebih banyak di masa depan masalah. Microsoft dengan cepat memperkenalkan mode ini dan mengabaikannya ... Kami menangani masalah ini cukup sederhana - menolak untuk menggunakan antarmuka yang sesuai.
Yang kedua - saat memuat Visual Studio dengan plugin, pesan berikut muncul:
Visual Studio telah mendeteksi satu atau lebih ekstensi yang berisiko atau tidak berfungsi dalam pembaruan fitur VS.Melihat log startup Visual Studio (file ActivityLog) akhirnya bertitik 'i':
Peringatan: Ekstensi 'PVS-Studio' menggunakan fitur 'auto-load sinkron' dari Visual Studio. Fitur ini tidak lagi didukung dalam pembaruan Visual Studio 2019 di masa depan, di mana ekstensi ini tidak akan berfungsi. Silakan hubungi vendor ekstensi untuk mendapatkan pembaruan.Bagi kami, ini berarti satu hal - mengubah cara plug-in dimuat ke mode asinkron. Saya harap Anda tidak akan kecewa jika saya tidak membebani Anda dengan detail tentang berinteraksi dengan antarmuka COM dari Visual Studio, dan saya akan melalui perubahan cukup singkat.
Microsoft memiliki artikel tentang cara membuat plugin yang tidak sinkron: "
Cara: Gunakan AsyncPackage untuk memuat VSPackages di latar belakang ". Pada saat yang sama, jelas bagi semua orang bahwa masalah ini tidak akan terbatas pada perubahan ini.
Salah satu perubahan utama adalah metode memuat, atau lebih tepatnya, inisialisasi. Sebelumnya, inisialisasi yang diperlukan terjadi dalam dua metode - metode
inisialisasi inisialisasi kelas
inheritance Paket kami
, dan metode
OnShellPropertyChange . Kebutuhan untuk mentransfer bagian dari logika ke metode
OnShellPropertyChange disebabkan oleh kenyataan bahwa ketika plug-in dimuat secara sinkron, Visual Studio mungkin belum dimuat dan diinisialisasi penuh, dan sebagai akibatnya, tidak semua tindakan yang diperlukan dapat dilakukan pada tahap inisialisasi plug-in. Pilihan untuk menyelesaikan masalah ini adalah menunggu Visual Studio keluar dari status 'zombie' dan menunda tindakan ini. Ini adalah logikanya dan telah diterjemahkan di
OnShellPropertyChange dengan cek status 'zombie'.
Dalam kelas
AsyncPackage abstrak, dari mana plugin yang dimuat secara asinkron diwariskan, metode
Initialize memiliki pengubah
disegel , jadi inisialisasi harus dilakukan dalam metode
InitializeAsync yang ditimpa, yang telah dilakukan. Kami juga harus mengubah logika dengan melacak keadaan 'zombie' dari Visual Studio, karena kami berhenti menerima informasi ini di plugin. Namun, sejumlah tindakan yang perlu dilakukan setelah plugin diinisialisasi tidak hilang.
Solusinya adalah dengan menggunakan metode
OnPackageLoaded dari antarmuka
IVsPackageLoadEvents , di mana tindakan yang membutuhkan eksekusi ditangguhkan dilakukan.
Masalah lain yang secara logis muncul dari fakta asynchronous loading plugin adalah tidak adanya perintah plugin PVS-Studio pada saat memulai Visual Studio. Ketika Anda membuka log analyzer dengan mengklik dua kali dari manajer file (jika Anda perlu membukanya melalui Visual Studio), versi devenv.exe yang diperlukan diluncurkan dengan perintah untuk membuka laporan analyzer. Perintah peluncuran terlihat seperti ini:
"C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\Common7\IDE\devenv.exe" /command "PVSStudio.OpenAnalysisReport C:\Users\vasiliev\source\repos\ConsoleApp\ConsoleApp.plog"
Bendera "/ command" di sini digunakan untuk menjalankan perintah yang terdaftar di Visual Studio. Sekarang pendekatan ini tidak berfungsi, karena perintah tidak tersedia sampai plug-in diunduh. Akibatnya, saya harus berhenti di "kruk" dengan menguraikan garis peluncuran devenv.exe setelah memuat plugin, dan jika ada representasi string dari perintah untuk membuka log - pada kenyataannya, memuat log. Jadi, dalam hal ini, setelah menolak untuk menggunakan antarmuka "benar" untuk bekerja dengan perintah, dimungkinkan untuk mempertahankan fungsionalitas yang diperlukan dengan menunda pemuatan log hingga plug-in dimuat sepenuhnya.
Fuh, tampaknya akan beres dan semuanya berfungsi - semuanya memuat dan terbuka dengan benar, tidak ada peringatan - akhirnya.
Dan kemudian hal yang tidak terduga terjadi - Pavel (halo!) Menginstal plug-in, setelah itu dia bertanya mengapa kami tidak melakukan loading asinkron?
Untuk mengatakan bahwa kami terkejut - untuk tidak mengatakan apa-apa - bagaimana bisa begitu? Tidak, sungguh - ini adalah versi baru dari plugin yang diinstal, ini adalah pesan bahwa paket tersebut dapat diunduh secara sinkron. Kami memasang dengan Alexander (dan menyapa Anda juga) versi plugin yang sama pada mesin kami - semuanya baik-baik saja. Tidak ada yang jelas - kami memutuskan untuk melihat versi perpustakaan PVS-Studio mana yang dimuat dalam Visual Studio. Dan tiba-tiba ternyata versi pustaka PVS-Studio untuk Visual Studio 2017 digunakan, meskipun faktanya versi pustaka yang benar ada dalam paket VSIX - untuk Visual Studio 2019.
Setelah bermain-main dengan VSIXInstaller, saya berhasil menemukan penyebab masalah - cache paket. Teori ini juga dikonfirmasi oleh fakta bahwa ketika membatasi hak akses ke paket dalam cache (C: \ ProgramData \ Microsoft \ VisualStudio \ Packages) VSIXInstaller menulis informasi kesalahan ke log. Anehnya, jika tidak ada kesalahan, tidak ada informasi tentang fakta bahwa paket diinstal dari cache tidak ditulis ke log.
Catatan Mempelajari perilaku VSIXInstaller dan perpustakaan terkait, ia mencatat pada dirinya sendiri bahwa sangat keren bahwa Roslyn dan MSBuild memiliki kode sumber terbuka yang membuatnya mudah untuk membaca, men-debug dan melacak logika kerja.
Akibatnya, hal berikut terjadi - ketika menginstal plugin, VSIXInstaller melihat bahwa paket yang sesuai sudah ada dalam cache (ada paket .vsix untuk Visual Studio 2017), dan menggunakannya sebagai pengganti paket yang diinstal sebenarnya saat instalasi. Mengapa ini tidak memperhitungkan pembatasan / persyaratan yang dijelaskan dalam .vsixmanifest (misalnya, versi Visual Studio yang Anda dapat menginstal ekstensi) adalah pertanyaan terbuka. Karena itu, ternyata meskipun .vsixmanifest berisi pembatasan yang diperlukan, plugin yang dirancang untuk Visual Studio 2017 diinstal pada Visual Studio 2019.
Yang terburuk adalah instalasi seperti itu memecah grafik ketergantungan Visual Studio, dan meskipun secara lahiriah bahkan kelihatannya lingkungan pengembangan berfungsi dengan baik, pada kenyataannya semuanya sangat buruk. Mustahil untuk menginstal dan menghapus instalan ekstensi, membuat pembaruan, dan sebagainya. Proses 'pemulihan' juga agak tidak menyenangkan, karena itu perlu untuk menghapus ekstensi (file yang sesuai), serta secara manual mengedit file konfigurasi yang menyimpan informasi tentang paket yang diinstal. Secara umum - itu tidak cukup menyenangkan.
Untuk mengatasi masalah ini dan menghindari situasi serupa di masa mendatang, diputuskan untuk membuat GUID untuk paket baru untuk memisahkan paket Visual Studio 2017 dan Visual Studio 2019 dengan tepat (tidak ada masalah dengan paket yang lebih lama, dan mereka selalu menggunakan GUID yang sama).
Dan karena kami berbicara tentang kejutan yang tidak menyenangkan, saya akan menyebutkan satu hal lagi - setelah memperbarui ke Pratinjau 2, item menu 'dipindahkan' di bawah tab 'Ekstensi'. Tampaknya tidak apa-apa, tetapi akses ke fungsi plugin menjadi kurang nyaman. Pada versi Visual Studio 2019 berikutnya, termasuk versi rilis, perilaku ini telah dipertahankan. Saya tidak menemukan penyebutan 'fitur' ini pada saat rilis di dokumentasi atau blog.
Sekarang, tampaknya, semuanya berfungsi, dan dengan dukungan plug-in untuk Visual Studio 2019 selesai. Sehari setelah rilis PVS-Studio 7.02 dengan dukungan untuk Visual Studio 2019, ternyata tidak demikian - masalah lain dengan plug-in asinkron ditemukan. Untuk pengguna, ini bisa terlihat seperti ini: ketika membuka jendela dengan hasil analisis (atau memulai analisis), jendela kita kadang-kadang ditampilkan "kosong" - tidak berisi konten: tombol, meja dengan peringatan penganalisa, dll.
Bahkan, masalah ini terkadang terulang dalam perjalanan kerja. Namun, itu hanya diulang pada satu mesin, dan mulai muncul hanya setelah memperbarui Visual Studio di salah satu versi pertama 'Pratinjau' - ada kecurigaan bahwa ada sesuatu yang rusak selama instalasi / pembaruan. Namun, seiring waktu, masalahnya tidak lagi terulang lagi pada mesin ini, dan kami memutuskan bahwa "diperbaiki dengan sendirinya." Ternyata tidak - hanya sangat beruntung. Lebih tepatnya, tidak beruntung.
Masalahnya ternyata berada dalam urutan inisialisasi jendela lingkungan itu sendiri (turunan dari kelas
ToolWindowPane ) dan isinya (pada kenyataannya, kontrol kami dengan kisi-kisi dan tombol). Dalam kondisi tertentu, inisialisasi kontrol terjadi sebelum inisialisasi panel, dan terlepas dari kenyataan bahwa semuanya bekerja tanpa kesalahan, metode
FindToolWindowAsync (membuat jendela pada panggilan pertama) bekerja dengan benar, tetapi kontrol tetap tidak terlihat. Kami memperbaiki ini dengan menambahkan inisialisasi malas untuk kontrol kami ke kode isian panel.
Dukungan C # 8.0
Menggunakan Roslyn sebagai dasar untuk penganalisa memiliki keuntungan yang signifikan - tidak perlu secara manual mempertahankan konstruksi bahasa baru. Semua ini didukung dan diimplementasikan dalam kerangka kerja perpustakaan Microsoft.CodeAnalysis - kami menggunakan hasil yang sudah jadi. Dengan demikian, dukungan untuk sintaks baru diimplementasikan dengan memperbarui perpustakaan.
Tentu saja, sejauh menyangkut analisis statis, di sini Anda harus melakukan semuanya sendiri, khususnya, untuk memproses konstruksi bahasa baru. Ya, kami mendapatkan pohon sintaksis baru secara otomatis dengan menggunakan versi Roslyn yang lebih baru, tetapi kami perlu mengajari analis bagaimana memahami dan memproses simpul pohon yang baru / diubah.
Saya pikir inovasi yang paling banyak dibicarakan di C # 8 adalah jenis referensi nullable. Saya tidak akan menulis tentang mereka di sini - ini adalah topik yang agak besar yang layak untuk artikel terpisah (yang sudah dalam proses penulisan). Secara umum, kami sejauh ini memutuskan untuk mengabaikan anotasi yang tidak dapat dibatalkan dalam mekanisme aliran data kami (mis., Kami memahami, menguraikan, dan melewatkannya). Faktanya adalah bahwa meskipun jenis referensi variabel tidak dapat
dibatalkan , Anda masih dapat menulis
null secara sederhana (atau karena kesalahan), yang dapat menyebabkan NRE ketika mendereferensi tautan yang sesuai. Dalam hal ini, penganalisa kami dapat melihat kesalahan yang sama dan memberikan peringatan tentang penggunaan referensi yang berpotensi nol (tentu saja, jika ia melihat penugasan seperti itu dalam kode) meskipun jenis referensi variabel tidak dapat dibatalkan.
Saya ingin mencatat bahwa penggunaan tipe referensi yang dapat dibatalkan dan sintaks yang menyertainya membuka kemungkinan untuk menulis kode yang sangat menarik. Bagi kami sendiri, kami menyebutnya 'sintaksis emosional'. Kode di bawah ini mengkompilasi dengan cukup baik:
obj.Calculate(); obj?.Calculate(); obj.Calculate(); obj!?.Calculate(); obj!!!.Calculate();
By the way, selama pekerjaan saya, saya menemukan beberapa cara untuk 'mengisi' Visual Studio menggunakan sintaks baru. Faktanya adalah bahwa Anda tidak dapat membatasi jumlah karakter menjadi satu ketika Anda menempatkan '!'. Artinya, Anda tidak hanya dapat menulis kode formulir:
object temp = null!
tetapi juga:
object temp = null!!!;
Anda dapat memutarbalikkan, silakan dan menulis seperti ini:
object temp = null!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!;
Kode ini berhasil dikompilasi. Tetapi jika Anda meminta informasi tentang pohon sintaks menggunakan Syntax Visualizer dari .NET Compiler Platform SDK, Visual Studio akan macet.
Anda bisa mendapatkan informasi tentang masalah tersebut dari Peraga Peristiwa:
Faulting application name: devenv.exe, version: 16.0.28803.352, time stamp: 0x5cc37012 Faulting module name: WindowsBase.ni.dll, version: 4.8.3745.0, time stamp: 0x5c5bab63 Exception code: 0xc00000fd Fault offset: 0x000c9af4 Faulting process id: 0x3274 Faulting application start time: 0x01d5095e7259362e Faulting application path: C:\Program Files (x86)\ Microsoft Visual Studio\2019\Community\Common7\IDE\devenv.exe Faulting module path: C:\WINDOWS\assembly\NativeImages_v4.0.30319_32\ WindowsBase\4480dfedf0d7b4329838f4bbf953027d\WindowsBase.ni.dll Report Id: 66d41eb2-c658-486d-b417-02961d9c3e4f Faulting package full name: Faulting package-relative application ID:
Jika Anda melangkah lebih jauh dan meningkatkan jumlah tanda seru beberapa kali, Visual Studio akan jatuh dengan sendirinya - bantuan Syntax Visualizer tidak lagi diperlukan. Perpustakaan Microsoft.CodeAnalysis dan kompiler csc.exe juga tidak mencerna kode ini.
Tentu saja, ini adalah contoh sintetis, tetapi tetap saja fakta ini terasa lucu bagi saya.
Toolset
Catatan Sekali lagi saya dihadapkan dengan masalah menerjemahkan kata 'evaluasi' dalam konteks percakapan tentang proyek MSBuild. Terjemahan, yang tampaknya paling dekat artinya dan pada saat yang sama terdengar normal, adalah "membangun model proyek". Jika Anda memiliki opsi terjemahan alternatif - Anda dapat menulis kepada saya, akan menarik untuk dibaca.
Jelas bahwa memperbarui toolset akan menjadi tugas yang paling memakan waktu. Lebih tepatnya, sepertinya sudah dari awal, tapi sekarang saya cenderung percaya bahwa yang paling bermasalah adalah dukungan plugin. Secara khusus, ini adalah karena toolset yang sudah ada dan mekanisme untuk membangun model proyek MSBuild, yang bekerja dengan sukses sekarang, meskipun itu membutuhkan ekspansi. Tidak perlu menulis algoritma dari awal sangat menyederhanakan tugas. Taruhan kami pada toolset "kami", dibuat pada tahap mendukung Visual Studio 2017, dibenarkan sekali lagi.
Secara tradisional, semuanya dimulai dengan memperbarui paket NuGet. Pada tab manajemen paket NuGet untuk solusi, ada tombol 'Perbarui' ... yang tidak berfungsi. Ketika memperbarui semua paket, beberapa konflik versi muncul, dan menyelesaikannya semua tampaknya tidak terlalu benar. Cara yang lebih menyakitkan, tetapi, tampaknya, cara yang lebih dapat diandalkan adalah untuk 'sepotong demi sepotong' memperbarui target Microsoft.Build / Microsoft.CodeAnalysis.
Salah satu perbedaan segera diidentifikasi oleh tes aturan diagnostik - struktur pohon sintaks untuk node yang sudah ada telah berubah. Tidak apa-apa, diperbaiki dengan cepat.
Biarkan saya mengingatkan Anda bahwa selama pekerjaan kami menguji analisis (C #, C ++, Java) pada proyek open source. Ini memungkinkan Anda untuk menguji aturan diagnostik dengan baik - temukan, misalnya, positif palsu, atau mendapatkan gagasan tentang kasus lain yang belum dipertimbangkan (kurangi jumlah negatif palsu). Tes-tes ini juga membantu melacak kemungkinan kemunduran pada tahap awal memperbarui perpustakaan / toolset. Dan kali ini tidak terkecuali, karena sejumlah masalah muncul.
Satu masalah adalah penurunan perilaku dalam perpustakaan CodeAnalysis. Lebih khusus lagi, pada sejumlah proyek dalam kode perpustakaan, pengecualian terjadi selama berbagai operasi - memperoleh informasi semantik, membuka proyek, dll.
Perhatian pembaca artikel tentang dukungan Visual Studio 2017 ingat bahwa kit distribusi kami memiliki rintisan - file MSBuild.exe berukuran 0 byte.
Kali ini saya harus melangkah lebih jauh - sekarang kit distribusi juga berisi stub compiler kosong - csc.exe, vbc.exe, VBCSCompiler.exe. Mengapa Cara untuk ini dimulai dengan analisis salah satu proyek di basis tes, di mana perbedaan laporan muncul - sejumlah peringatan tidak ada ketika menggunakan versi baru dari penganalisa.
Masalahnya ternyata menjadi simbol kompilasi bersyarat - ketika menganalisis proyek menggunakan versi baru dari penganalisa, beberapa simbol diekstraksi secara tidak benar. Untuk lebih memahami apa yang menyebabkan masalah ini, saya harus menyelami perpustakaan Roslyn.
Untuk menguraikan karakter kompilasi bersyarat, gunakan metode
GetDefineConstantsSwitch dari kelas
Csc dari perpustakaan
Microsoft.Build.Tasks.CodeAnalysis . Parsing dilakukan menggunakan metode
String.Split pada sejumlah pembatas:
string[] allIdentifiers = originalDefineConstants.Split(new char[] { ',', ';', ' ' });
Metode parsing ini berfungsi dengan baik, semua simbol kompilasi bersyarat yang diperlukan berhasil diekstraksi. Menggali lebih jauh.
Titik kunci berikutnya adalah panggilan ke metode
ComputePathToTool dari kelas
ToolTask . Metode ini membangun jalur ke file yang dapat dieksekusi (
csc.exe ) dan memeriksa keberadaannya. Jika file ada, path ke sana dikembalikan, jika tidak
null dikembalikan.
Kode Penelepon:
.... string pathToTool = ComputePathToTool(); if (pathToTool == null) {
Karena tidak ada file
csc.exe (akan terlihat - mengapa kita membutuhkannya?),
PathToTool pada tahap ini adalah
nol , dan metode saat ini (
ToolTask.Execute ) menyelesaikan eksekusi dengan hasil
false . Akibatnya, hasil tugas, termasuk simbol kompilasi bersyarat yang dihasilkan, diabaikan.
Baiklah, mari kita lihat apa yang terjadi jika Anda meletakkan file
csc.exe di lokasi yang diharapkan.
Dalam hal ini,
pathToTool menunjukkan lokasi sebenarnya dari file yang ada dan eksekusi metode
ToolTask.Execute berlanjut. Poin kunci berikutnya adalah panggilan ke metode
ManagedCompiler.ExecuteTool . Dan itu dimulai sebagai berikut:
protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) { if (ProvideCommandLineArgs) { CommandLineArgs = GetArguments(commandLineCommands, responseFileCommands) .Select(arg => new TaskItem(arg)).ToArray(); } if (SkipCompilerExecution) { return 0; } .... }
Properti
SkipCompilerExecution benar (secara logis, kami sebenarnya tidak mengkompilasi). Akibatnya, metode panggilan (
ToolTask.Execute yang telah disebutkan) memeriksa bahwa kode kembali metode
ExecuteTool adalah 0, dan, jika demikian, menyelesaikan eksekusi dengan nilai
true . Apa yang Anda miliki di belakang
csc.exe ada di sana - kompiler nyata atau 'Perang dan Damai' Leo Tolstoy dalam bentuk tekstual tidak masalah.
Akibatnya, masalah utama berasal dari kenyataan bahwa urutan langkah-langkah didefinisikan dalam urutan berikut:
- periksa keberadaan kompiler;
- periksa apakah kompiler perlu dimulai;
bukan sebaliknya. Rintisan kompiler berhasil memecahkan masalah ini.
Nah, bagaimana karakter kompilasi yang berhasil muncul jika file csc.exe tidak terdeteksi (dan hasil tugas diabaikan)?
Ada metode untuk kasus ini -
CSharpCommandLineParser.ParseConditionalCompilationSymbols dari perpustakaan
Microsoft.CodeAnalysis.CSharp . Parsing juga dilakukan oleh metode
String.Split dengan sejumlah pembatas:
string[] values = value.Split(new char[] { ';', ',' } );
Perhatikan perbedaannya dengan set pembatas dari metode
Csc.GetDefineConstantsSwitch ? Dalam hal ini, spasi putih bukan pemisah. Dengan demikian, jika karakter kompilasi bersyarat ditulis dengan spasi, metode ini akan menguraikannya dengan tidak benar.
Situasi ini muncul pada proyek bermasalah - karakter kompilasi bersyarat ditulis di dalamnya dengan spasi, dan berhasil diurai menggunakan
GetDefineConstantsSwitch , tetapi tidak
ParseConditionalCompilationSymbols .
Masalah lain yang muncul setelah memperbarui perpustakaan adalah memburuknya perilaku dalam sejumlah kasus, khususnya, pada proyek-proyek yang tidak dikumpulkan. Masalah muncul di perpustakaan Microsoft.CodeAnalysis dan kembali kepada kami dalam bentuk berbagai pengecualian -
ArgumentNullException (beberapa logger internal tidak diinisialisasi),
NullReferenceException , dan lainnya.
Saya ingin berbicara tentang salah satu masalah di bawah ini - menurut saya cukup menarik.
Kami mengalami masalah ini ketika memeriksa versi terbaru dari proyek Roslyn -
NullReferenceException dilemparkan dari kode salah satu perpustakaan. Karena informasi yang cukup terperinci tentang lokasi masalah, kami dengan cepat menemukan kode masalah dan, demi kepentingan, memutuskan untuk mencoba melihat apakah masalah tersebut berulang ketika bekerja dari Visual Studio.
Yah - itu mungkin untuk mereproduksi di Visual Studio (percobaan dilakukan pada Visual Studio 16.0.3). Untuk melakukan ini, kita memerlukan definisi kelas dari formulir berikut:
class C1<T1, T2> { void foo() { T1 val = default; if (val is null) { } } }
Kami juga akan memerlukan Sintaks Visualizer (bagian dari .NET Compiler Platform SDK). Hal ini diperlukan untuk meminta
TypeSymbol (item menu "Lihat TypeSymbol (jika ada)") dari node pohon sintaks tipe
ConstantPatternSyntax (
null ). Setelah itu, Visual Studio akan memulai kembali, dan di Peraga Peristiwa Anda dapat melihat informasi tentang masalah, khususnya, menemukan jejak tumpukan:
Application: devenv.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.NullReferenceException at Microsoft.CodeAnalysis.CSharp.ConversionsBase. ClassifyImplicitBuiltInConversionSlow( Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, System.Collections.Generic.HashSet'1 <Microsoft.CodeAnalysis.DiagnosticInfo> ByRef) at Microsoft.CodeAnalysis.CSharp.ConversionsBase.ClassifyBuiltInConversion( Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, System.Collections.Generic.HashSet'1 <Microsoft.CodeAnalysis.DiagnosticInfo> ByRef) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfoForNode( Microsoft.CodeAnalysis.CSharp.BoundNode, Microsoft.CodeAnalysis.CSharp.BoundNode, Microsoft.CodeAnalysis.CSharp.BoundNode) at Microsoft.CodeAnalysis.CSharp.MemberSemanticModel.GetTypeInfoWorker( Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.SyntaxTreeSemanticModel.GetTypeInfoWorker( Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfo( Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfoFromNode( Microsoft.CodeAnalysis.SyntaxNode, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfoCore( Microsoft.CodeAnalysis.SyntaxNode, System.Threading.CancellationToken) ....
Seperti yang Anda lihat, penyebab masalahnya adalah referensi referensi nol.
Seperti yang saya sebutkan sebelumnya, kami mengalami masalah yang sama selama pengujian analisa. Jika Anda menggunakan perpustakaan debug Microsoft.CodeAnalysis untuk membangun analisa, Anda bisa datang ke tempat yang tepat dengan
men- debug dengan meminta
TypeSymbol dari node yang diinginkan di pohon sintaksis.
Akibatnya, kami tiba di metode
ClassifyImplicitBuiltInConversionSlow yang disebutkan dalam jejak tumpukan di
atas :
private Conversion ClassifyImplicitBuiltInConversionSlow( TypeSymbol source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { Debug.Assert((object)source != null); Debug.Assert((object)destination != null); if (source.SpecialType == SpecialType.System_Void || destination.SpecialType == SpecialType.System_Void) { return Conversion.NoConversion; } Conversion conversion = ClassifyStandardImplicitConversion(source, destination, ref useSiteDiagnostics); if (conversion.Exists) { return conversion; } return Conversion.NoConversion; }
Masalahnya adalah bahwa parameter
tujuan adalah
nol dalam kasus ini. Dengan demikian, saat memanggil
tujuan. Jenis Khusus, NullReferenceException dilemparkan .
Ya, Debug.Assert lebih tinggi daripada dereferencing , tetapi ini tidak cukup, karena pada kenyataannya itu tidak melindungi terhadap apa pun - itu hanya membantu untuk mengidentifikasi masalah dalam debug versi perpustakaan. Atau tidak membantu.Perubahan dalam membangun model proyek C ++
Tidak ada yang sangat menarik terjadi di sini - algoritma lama tidak memerlukan modifikasi yang signifikan, yang akan menarik untuk dibicarakan. Ada, mungkin, dua poin yang masuk akal untuk dipikirkan.Pertama, kami harus memodifikasi algoritma yang bergantung pada nilai ToolsVersion yang akan ditulis dalam format numerik. Tanpa merinci - ada beberapa kasus ketika Anda perlu membandingkan toolets dan memilih, misalnya, versi baru yang lebih terkini. Versi ini, masing-masing, memiliki nilai numerik yang lebih tinggi. Ada perhitungan bahwa ToolsVersion, yang sesuai dengan versi baru MSBuild / Visual Studio, akan sama dengan 16.0. Apa pun masalahnya ... Demi kepentingan, saya kutip tabel tentang bagaimana nilai berbagai properti berubah dalam versi Visual Studio yang berbeda:Lelucon itu, tentu saja, sudah ketinggalan zaman, tetapi Anda tidak dapat membantu tetapi ingat tentang mengubah versi Windows dan Xbox untuk memahami bahwa memprediksi nilai masa depan (tidak peduli apa nama dan versi), dalam kasus Microsoft, adalah hal yang goyah. :)
Solusinya cukup sederhana - memperkenalkan penentuan prioritas toolets (alokasi entitas prioritas terpisah).Poin kedua adalah masalah ketika bekerja di Visual Studio 2017 atau di lingkungan yang berdekatan (misalnya, kehadiran variabel lingkungan VisualStudioVersion ). Faktanya adalah bahwa menghitung parameter yang diperlukan untuk membangun model proyek C ++ jauh lebih rumit daripada membangun model proyek NET. Dalam kasus .NET, kami menggunakan alat kami sendiri dan nilai ToolsVersion yang sesuai. Dalam kasus C ++, kita dapat membangun baik pada toolet kita sendiri maupun yang sudah ada dalam sistem. Dimulai dengan Alat Bangun di Visual Studio 2017, alat terdaftar di file MSBuild.exe.config, tidak ada dalam registri. Karenanya, kami tidak dapat memperolehnya dari daftar umum toolet (misalnya, melalui Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.Toolsets ), tidak seperti toolets yang dicatat dalam registri (sesuai dengan <= Visual Studio 2015) .Sebagai konsekuensi dari hal tersebut di atas, tidak akan berhasil membangun model proyek menggunakan ToolsVersion 15.0 , karena sistem tidak akan melihat toolset yang diperlukan. Toolset Terkini - Sekarang- itu akan tersedia pada saat yang sama, karena ini adalah perangkat kami sendiri, oleh karena itu, tidak ada masalah untuk Visual Studio 2019. Solusinya ternyata sederhana dan diizinkan untuk menyelesaikan masalah tanpa mengubah algoritma yang ada untuk membangun model proyek - menambah daftar set alat Anda sendiri, selain Current, yang lain - 15,0 .Perubahan dalam membangun model proyek C # .NET Core
Dalam kerangka tugas ini, 2 masalah diselesaikan sekaligus, karena ternyata terkait:- setelah menambahkan toolset 'Lancar', analisis proyek .NET Core untuk Visual Studio 2017 berhenti bekerja;
- Analisis proyek .NET Core pada sistem di mana setidaknya satu versi Visual Studio tidak diinstal tidak berfungsi.
Masalah dalam kedua kasus itu sama - beberapa file .targets / .props dasar dicari dengan cara yang salah. Ini mengarah pada fakta bahwa tidak mungkin untuk membangun model proyek menggunakan perangkat kami.Dengan tidak adanya Visual Studio, Anda bisa melihat kesalahan seperti itu (dengan toolset'a versi sebelumnya - 15.0 ): The imported project "C:\Windows\Microsoft.NET\Framework64\ 15.0\Microsoft.Common.props" was not found.
Saat membangun C # .NET Core model proyek di Visual Studio 2017, Anda bisa melihat masalah berikut (dengan versi toolset saat ini , Lancar ): The imported project "C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\MSBuild\Current\Microsoft.Common.props" was not found. ....
Karena masalahnya mirip (tetapi terlihat seperti itu), Anda dapat mencoba membunuh dua burung dengan satu batu.Di bawah ini saya menjelaskan bagaimana masalah ini diselesaikan tanpa masuk ke rincian teknis. Ini sangat detail (tentang membangun model proyek C # .NET Core, serta mengubah konstruksi model di toolset'e kami) sedang menunggu di salah satu artikel kami di masa mendatang. Omong-omong, jika Anda hati-hati membaca teks di atas, Anda mungkin memperhatikan bahwa ini adalah referensi kedua untuk artikel mendatang. :)
Jadi bagaimana kami mengatasi masalah ini? Solusinya adalah memperluas toolset kami sendiri dengan mengorbankan file .targets / .props utama dari .NET Core SDK ( Sdk.props , Sdk.targets ). Ini memungkinkan kami untuk memiliki kontrol lebih besar terhadap situasi, lebih banyak fleksibilitas dalam mengelola impor, serta dalam membangun model proyek .NET Core secara umum. Ya, toolset kami telah tumbuh sedikit lagi, dan kami juga harus menambahkan logika untuk mengatur proyek lingkungan yang diperlukan untuk membangun model .NET Core, tetapi sepertinya itu sepadan.Sebelumnya, prinsip kerja ketika membangun model proyek .NET Core adalah sebagai berikut: kami hanya meminta konstruksi ini, dan kemudian semuanya bekerja dengan mengorbankan MSBuild.Sekarang, ketika kita telah mengambil kendali lebih banyak ke tangan kita sendiri, itu tampak sedikit berbeda:- persiapan lingkungan yang diperlukan untuk membangun model proyek .NET Core;
- model bangunan:
- mulai konstruksi menggunakan file .targets / .props dari toolset'a kami;
- konstruksi lanjutan menggunakan file eksternal.
Dari langkah-langkah yang dijelaskan di atas, jelas bahwa pengaturan lingkungan yang diperlukan memiliki dua tujuan utama:- memulai pembuatan model menggunakan file .targets / .props dari perangkat Anda sendiri;
- mengarahkan operasi lebih lanjut ke file .targets / .props eksternal.
Untuk mencari file .targets / .props yang diperlukan untuk membangun model proyek .NET Core, perpustakaan khusus digunakan - Microsoft.DotNet.MSBuildSdkResolver. Inisiasi membangun menggunakan file dari toolset kami diselesaikan dengan menggunakan variabel lingkungan khusus yang digunakan oleh perpustakaan ini - kami menyarankan tempat untuk mengimpor file yang diperlukan (dari toolset kami). Karena perpustakaan adalah bagian dari distribusi kami, tidak ada kekhawatiran bahwa logika akan tiba-tiba berubah dan berhenti berfungsi.Sekarang file Sdk pertama kali diimpor dari toolset kami, dan karena kita dapat dengan mudah mengubahnya, kontrol dari logika selanjutnya untuk membangun model diteruskan ke tangan kita. Oleh karena itu, kita dapat menentukan sendiri file mana yang perlu diimpor dan dari mana. Ini juga berlaku untuk Microsoft.Common.props yang disebutkan di atas. Kami mengimpor ini dan file-file dasar lainnya dari perangkat kami sendiri dengan keyakinan pada ketersediaan dan kontennya.Setelah itu, setelah menyelesaikan impor yang diperlukan dan menetapkan sejumlah properti, kami mentransfer kontrol lebih lanjut dari pembangunan model ke .NET Core SDK yang sebenarnya, tempat tindakan selanjutnya diperlukan.Kesimpulan
Secara umum, dukungan untuk Visual Studio 2019 berjalan lebih mudah daripada dukungan untuk Visual Studio 2017, yang, seperti yang saya lihat, adalah karena beberapa faktor. Pertama, Microsoft tidak mengubah banyak hal seperti antara Visual Studio 2015 dan Visual Studio 2017. Ya, kami mengubah perangkat utama, mulai mengarahkan plug-in untuk Visual Studio pada asynchrony, tetapi tetap saja. Yang kedua - kami sudah memiliki solusi yang siap dengan toolset kami sendiri dan membangun model proyek - tidak perlu menemukan semuanya lagi, itu sudah cukup hanya untuk memperluas solusi yang ada. Dukungan yang relatif sederhana untuk menganalisis .NET Core proyek untuk kondisi baru (serta untuk kasus analisis pada mesin di mana tidak ada contoh Visual Studio diinstal) karena perluasan sistem bangunan model proyek kami juga memberi harapan bahwa kami membuat pilihan yang tepat.Setelah memutuskan untuk mengendalikan diri sendiri.Tapi tetap saja, saya ingin mengulangi satu pemikiran yang ada di artikel sebelumnya lagi - terkadang menggunakan solusi yang sudah jadi tidak sesederhana seperti yang terlihat pada pandangan pertama.
Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Sergey Vasiliev. Dukungan Visual Studio 2019 di PVS-Studio