دعم Visual Studio 2019 في PVS-Studio


أثر دعم Visual Studio 2019 في PVS-Studio على الفور على العديد من المكونات المختلفة: المكون الإضافي IDE ، وتطبيق تحليل سطر الأوامر ، ومحللات C ++ و C # ، بالإضافة إلى العديد من الأدوات المساعدة. سأتحدث باختصار عن المشكلات التي واجهناها في دعم الإصدار الجديد من IDE وكيفية حلها.

قبل أن تبدأ ، أريد أن أنظر إلى الوراء قليلاً لتتبع محفوظات الدعم للإصدارات السابقة من Visual Studio ، والتي ستوفر فهمًا أفضل لرؤيتنا للمهمة والقرارات المتخذة في مواقف معينة.

بدايةً من الإصدار الأول من محلل PVS-Studio ، الذي ظهر فيه المكون الإضافي لبيئة Visual Studio (ثم كان إصدار Visual Studio 2005) ، كان دعم الإصدارات الجديدة من Visual Studio مهمة بسيطة إلى حد ما بالنسبة لنا - لقد تلاشت بشكل أساسي لتحديث ملف مشروع المكون الإضافي و تبعيات مختلف واجهات برمجة التطبيقات ملحق Visual Studio. في بعض الأحيان ، كان من الضروري دعم الميزات الجديدة للغة C ++ ، والتي تعلمها برنامج التحويل البرمجي Visual C ++ تدريجياً ، ولكن هذا أيضًا لم يتسبب في حدوث مشكلات مباشرةً قبل إصدار الإصدار التالي من Visual Studio. وكان هناك محلل واحد فقط في PVS-Studio بعد ذلك - للغات C و C ++.

لقد تغير كل شيء لإصدار Visual Studio 2017. بالإضافة إلى حقيقة أن العديد من واجهات برمجة التطبيقات للإضافات لهذا IDE قد تغيرت بشكل كبير في هذا الإصدار ، بعد التحديث واجهنا مشاكل في ضمان التوافق مع عمل محلل C # الجديد الذي ظهر في ذلك الوقت (بالإضافة إلى طبقة C ++ الجديدة لدينا محلل لمشاريع MSBuild) مع الإصدارات القديمة من MSBuild \ Visual Studio.

لذلك ، قبل قراءة هذه المقالة ، أوصي بشدة أن تقرأ المقالة ذات الصلة حول دعم Visual Studio 2017: "دعم Visual Studio 2017 و Roslyn 2.0 في PVS-Studio: أحيانًا يكون استخدام الحلول الجاهزة ليس سهلاً كما يبدو للوهلة الأولى ." توضح المقالة المذكورة أعلاه المشكلات التي واجهناها في المرة الأخيرة ، بالإضافة إلى مخططات التفاعل بين المكونات المختلفة (على سبيل المثال ، PVS-Studio و MSBuild و Roslyn). فهم هذا التفاعل سيكون مفيدًا عند قراءة هذا المقال.

في النهاية ، جلب حل هذه المشكلات تغييرات كبيرة على محللنا ، وكما كنا نأمل ، فإن الأساليب الجديدة التي طبقناها ستجعل من الممكن دعم الإصدارات المحدثة من Visual Studio \ MSBuild أسهل وأسرع في المستقبل. جزئيًا ، تم تأكيد هذا الافتراض بالفعل من خلال إصدار العديد من التحديثات لبرنامج Visual Studio 2017. هل ساعد هذا النهج الجديد في دعم Visual Studio 2019؟ عن ذلك أدناه.

PVS-Studio Plugin لـ Visual Studio 2019


بدأ كل شيء ، على ما يبدو ، ليس سيئًا. لقد كان من السهل بدرجة كافية توصيل المكوّن الإضافي بـ Visual Studio 2019 ، حيث بدأ العمل بشكل جيد. على الرغم من هذا ، تم الكشف عن مشكلتين على الفور ، والتي وعدت مشاكل في المستقبل.

الأول هو واجهة IVsSolutionWorkspaceService ، والتي تُستخدم لدعم وضع تحميل حلول Lightweight ، والتي ، بالمناسبة ، تم تعطيلها في أحد التحديثات السابقة في Visual Studio 2017 ، وقد تم تزيينها بالسمة Deprecated ، والتي كانت مجرد تحذير أثناء التجميع ، ولكنها وعدت بالمزيد في المستقبل مشاكل. قامت Microsoft بتقديم هذا الوضع بسرعة والتخلي عنه ... لقد تعاملنا مع هذه المشكلة بكل بساطة - رفض استخدام الواجهة المناسبة.

الثانية - عند تحميل Visual Studio مع المكون الإضافي ، ظهرت الرسالة التالية: اكتشف Visual Studio واحدًا أو أكثر من الإضافات المعرضة للخطر أو لا تعمل في تحديث ميزة VS.

عرض سجلات بدء تشغيل Visual Studio (ملف ActivityLog) أخيرًا منقط "i":

تحذير: تستخدم الإضافة "PVS-Studio" ميزة "التحميل التلقائي المتزامن" في Visual Studio. لن يتم دعم هذه الميزة في تحديث Visual Studio 2019 في المستقبل ، وعندها لن يعمل هذا الامتداد. يرجى الاتصال ببائع التمديد للحصول على تحديث.

بالنسبة لنا ، كان هذا يعني شيئًا واحدًا - تغيير الطريقة التي يتم بها تحميل المكون الإضافي في وضع غير متزامن. أرجو ألا تشعر بالضيق إذا لم أقم بإفراط في تفاصيلك حول التفاعل مع واجهات COM في Visual Studio ، وسأتابع التغييرات لفترة وجيزة بما فيه الكفاية.

لدى Microsoft مقالة حول إنشاء مكونات إضافية محملة بشكل غير متزامن: " كيفية: استخدام AsyncPackage لتحميل VSPackages في الخلفية ". في الوقت نفسه ، كان من الواضح للجميع أن الأمر لن يقتصر على هذه التغييرات.

أحد التغييرات الرئيسية هي طريقة التحميل ، أو بالأحرى ، التهيئة. في السابق ، تمت التهيئة اللازمة بطريقتين - طريقة التهيئة المتجاوزة لفئة توارث الحزمة ، وطريقة OnShellPropertyChange . ترجع الحاجة إلى نقل جزء من المنطق إلى طريقة OnShellPropertyChange إلى حقيقة أنه عندما يتم تحميل المكون الإضافي بشكل متزامن ، قد لا يتم تحميل وتهيئة Visual Studio بالكامل ، ونتيجة لذلك ، لا يمكن تنفيذ جميع الإجراءات اللازمة في مرحلة تهيئة المكون الإضافي. أحد الخيارات لحل هذه المشكلة هو انتظار Visual Studio للخروج من حالة "zombie" وتأخير هذه الإجراءات. هذا هو المنطق وقد تم تقديمه في OnShellPropertyChange مع التحقق من حالة "الزومبي".

في الفئة التجريدية AsyncPackage ، التي يتم من خلالها توارث الإضافات التي تم تحميلها بشكل غير متزامن ، تشتمل طريقة التهيئة على معدل مختوم ، لذلك يجب أن تتم التهيئة بالطريقة الأولية المتجاوزة ، والتي تم تنفيذها. كان علينا أيضًا تغيير المنطق من خلال تتبع حالة "zombie" في Visual Studio ، لأننا توقفنا عن تلقي هذه المعلومات في البرنامج المساعد. ومع ذلك ، لم يختف عددًا من الإجراءات التي يلزم تنفيذها بعد تهيئة البرنامج المساعد. كان الحل هو استخدام أسلوب OnPackageLoaded لواجهة IVsPackageLoadEvents ، حيث تم تنفيذ الإجراءات التي تتطلب التنفيذ المؤجل.

هناك مشكلة أخرى تنشأ بشكل منطقي من حقيقة التحميل غير المتزامن للمكون الإضافي وهي عدم وجود أوامر البرنامج المساعد PVS-Studio في وقت بدء تشغيل Visual Studio. عند فتح سجل المحلل بالنقر المزدوج من مدير الملفات (إذا كنت بحاجة إلى فتحه من خلال Visual Studio) ، تم إطلاق الإصدار الضروري من devenv.exe باستخدام الأمر لفتح تقرير المحلل. بدا الأمر التشغيل مثل هذا:

"C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\Common7\IDE\devenv.exe" /command "PVSStudio.OpenAnalysisReport C:\Users\vasiliev\source\repos\ConsoleApp\ConsoleApp.plog" 

يتم استخدام علامة "/ أمر" هنا لاستدعاء أمر مسجل في Visual Studio. الآن لم ينجح هذا النهج ، لأن الأوامر لم تكن متوفرة حتى تم تنزيل المكون الإضافي. نتيجةً لذلك ، اضطررت إلى التوقف عند "عكاز" مع تحليل خط الإطلاق لـ devenv.exe بعد تحميل المكون الإضافي ، وفي حالة تمثيل سلسلة للأمر لفتح السجل - فعلاً تحميل السجل. وبالتالي ، في هذه الحالة ، بعد رفض استخدام الواجهة "الصحيحة" للعمل مع الأوامر ، كان من الممكن الحفاظ على الوظيفة الضرورية عن طريق تأخير تحميل السجل حتى يتم تحميل المكون الإضافي بالكامل.

فوه ، يبدو أنه تم حلها ويعمل كل شيء - كل شيء يتم تحميله ويفتح بشكل صحيح ، لا توجد تحذيرات - أخيرًا.

ثم يحدث ما هو غير متوقع - يقوم Pavel (مرحبًا!) بتثبيت مكون إضافي ، وبعد ذلك يسأل عن سبب عدم قيامنا بالتحميل غير المتزامن؟

لقول أننا فوجئنا - كي لا نقول شيئا - كيف ذلك؟ لا ، حقا - هنا هو الإصدار الجديد من البرنامج المساعد المثبت ، وهنا هي الرسالة أن الحزمة قابلة للتحميل بشكل متزامن. نحن نثبّت مع ألكساندر (ونرحب لك أيضًا) نفس إصدار المكوّن الإضافي على أجهزتنا - كل شيء على ما يرام. ليس هناك ما هو واضح - قررنا معرفة أي إصدارات مكتبات PVS-Studio تم تحميلها في Visual Studio. وفجأة اتضح أن إصدارات مكتبات PVS-Studio لـ Visual Studio 2017 مستخدمة ، على الرغم من أن الإصدار الصحيح من المكتبات في حزمة VSIX - لـ Visual Studio 2019.

بعد العبث بـ VSIXInstaller ، تمكنت من العثور على سبب المشكلة - ذاكرة التخزين المؤقت للحزمة. تم تأكيد النظرية أيضًا من خلال حقيقة أنه عند تقييد حقوق الوصول إلى الحزمة في ذاكرة التخزين المؤقت (C: \ ProgramData \ Microsoft \ VisualStudio \ Packages) ، كتب VSIXInstaller معلومات الخطأ إلى السجل. والمثير للدهشة أنه إذا لم يكن هناك خطأ ، فلن تتم كتابة أي معلومات حول حقيقة تثبيت الحزمة من ذاكرة التخزين المؤقت في السجل.

المذكرة. عند دراسة سلوك VSIXInstaller والمكتبات ذات الصلة ، أشار إلى أنه من اللطيف أن يكون لدى Roslyn و MSBuild شفرة مفتوحة المصدر تجعل من السهل قراءة منطق العمل وتصحيحه وتتبعه.

نتيجة لذلك ، حدث ما يلي - عند تثبيت المكون الإضافي ، رأى VSIXInstaller أن الحزمة المقابلة موجودة بالفعل في ذاكرة التخزين المؤقت (كانت هناك حزمة .vsix لـ Visual Studio 2017) ، واستخدمها بدلاً من الحزمة المثبتة الفعلية أثناء التثبيت. لماذا لا يأخذ ذلك في الاعتبار القيود / المتطلبات الموضحة في .vsixmanifest (على سبيل المثال ، إصدار Visual Studio الذي يمكنك تثبيت الملحق له) هو سؤال مفتوح. وبسبب هذا ، اتضح أنه على الرغم من احتواء .vsixmanifest على القيود اللازمة ، فقد تم تثبيت المكون الإضافي المصمم لـ Visual Studio 2017 على Visual Studio 2019.

أسوأ شيء هو أن مثل هذا التثبيت حطم الرسم البياني للاعتماد في Visual Studio ، وعلى الرغم من أنه من الخارج قد يبدو أن بيئة التطوير تعمل بشكل جيد ، في الواقع كان كل شيء سيئًا للغاية. كان من المستحيل تثبيت الامتدادات وإلغاء تثبيتها وإجراء تحديثات وما إلى ذلك. كانت عملية "الانتعاش" غير سارة إلى حد ما ، لأن كان من الضروري حذف الملحق (الملفات المقابلة) ، وكذلك تحرير ملفات التكوين التي تخزن معلومات حول الحزمة المثبتة يدويًا. بشكل عام - ليس لطيفًا بدرجة كافية.

لحل هذه المشكلة وتجنب المواقف المشابهة في المستقبل ، تقرر إنشاء GUID للحزمة الجديدة من أجل فصل حزم Visual Studio 2017 و Visual Studio 2019 تمامًا (لا توجد مشكلة من هذا القبيل مع الحزم الأقدم ، وكانوا يستخدمون دائمًا GUID شائعًا).

ونظرًا لأننا كنا نتحدث عن مفاجآت غير سارة ، فسنذكر شيئًا آخر - بعد التحديث إلى Preview 2 ، تم نقل عنصر القائمة ضمن علامة التبويب "الإضافات". يبدو أنه لا بأس به ، لكن الوصول إلى وظائف البرنامج المساعد أصبح أقل ملاءمة. في الإصدارات اللاحقة من Visual Studio 2019 ، بما في ذلك إصدار الإصدار ، تم الحفاظ على هذا السلوك. لم أجد أي ذكر لهذه "الميزة" في وقت صدوره في الوثائق أو في المدونة.

الآن ، يبدو أن كل شيء يعمل ، ومع دعم المكونات الإضافية لبرنامج Visual Studio 2019. في اليوم التالي لإصدار الإصدار 7.02 من PVS-Studio مع دعم Visual Studio 2019 ، تبين أن هذا لم يكن كذلك - تم العثور على مشكلة أخرى في المكون الإضافي غير المتزامن. بالنسبة إلى المستخدم ، قد يبدو هذا كما يلي: عند فتح نافذة تحتوي على نتائج التحليل (أو بدء التحليل) ، تظهر لنا النافذة أحيانًا "فارغة" - لا تحتوي على محتويات: أزرار ، جدول به تحذيرات المحللين ، إلخ.

في الواقع ، تكررت هذه المشكلة في بعض الأحيان أثناء سير العمل. ومع ذلك ، فقد تكررت فقط على جهاز واحد ، وبدأت تظهر فقط بعد تحديث Visual Studio في أحد الإصدارات الأولى من "Preview" - كانت هناك شكوك حول حدوث شيء ما أثناء التثبيت / التحديث. لكن مع مرور الوقت ، توقفت المشكلة عن تكرارها حتى على هذا الجهاز ، وقررنا "إصلاحها بنفسها". اتضح أن لا - فقط محظوظا جدا. بتعبير أدق ، لا حظ.

تحول الأمر إلى أن يكون بترتيب تهيئة إطار البيئة نفسه (سليل فئة ToolWindowPane ) ومحتوياته (في الواقع ، سيطرتنا على الشبكة والأزرار). في ظل ظروف معينة ، تمت تهيئة عنصر التحكم قبل تهيئة الجزء ، وعلى الرغم من حقيقة أن كل شيء يعمل دون أخطاء ، فإن طريقة FindToolWindowAsync (إنشاء نافذة على المكالمة الأولى) عملت بشكل صحيح ، ولكن ظل عنصر التحكم غير مرئي. لقد قمنا بإصلاح هذا بإضافة التهيئة البطيئة لسيطرتنا إلى رمز ملء الجزء.

دعم C # 8.0


استخدام Roslyn كأساس للمحلل له ميزة كبيرة - ليست هناك حاجة للحفاظ على بنيات لغوية جديدة يدويًا. كل هذا يتم دعمه وتنفيذه في إطار مكتبات Microsoft.CodeAnalysis - نستخدم نتائج جاهزة. وبالتالي ، يتم تطبيق دعم بناء الجملة الجديد عن طريق تحديث المكتبات.

بطبيعة الحال ، فيما يتعلق بالتحليل الساكن ، هنا يجب عليك فعل كل شيء بنفسك ، على وجه الخصوص ، لمعالجة التراكيب اللغوية الجديدة. نعم ، نحصل على شجرة بناء الجملة الجديدة تلقائيًا باستخدام الإصدار الأحدث من Roslyn ، لكننا بحاجة إلى تعليم المحلل كيفية إدراك ومعالجة العقد الجديدة / المتغيرة للشجرة.

أعتقد أن أكثر الابتكارات التي تحدثنا عنها في C # 8 هي أنواع المراجع الفارغة. لن أكتب عنها هنا - هذا موضوع كبير إلى حد ما يستحق مقالاً منفصلاً (وهو بالفعل في مرحلة الكتابة). بشكل عام ، لقد استقرنا حتى الآن على تجاهل التعليقات التوضيحية الفارغة في آلية تدفق البيانات لدينا (أي أننا نفهمها ونحللها ونتخطاها). الحقيقة هي أنه على الرغم من نوع المرجع غير القابل للإلغاء للمتغير ، إلا أنه لا يزال بإمكانك أن تكتب إليه بكل بساطة (أو عن طريق الخطأ) ، مما قد يؤدي إلى NRE عند إلغاء ربط الرابط المقابل. في هذه الحالة ، يمكن أن يرى المحلل الخاص بنا خطأً مشابهاً ويحذر من استخدام مرجع محتمل لاغية (بالطبع ، إذا رأى هذا التخصيص في الكود) على الرغم من نوع المرجع غير القابل للإلغاء للمتغير.

أريد أن أشير إلى أن استخدام أنواع المراجع الفارغة وصيغة الجملة المصاحبة يفتح إمكانية كتابة تعليمات برمجية ممتعة للغاية. لأنفسنا ، أطلقنا على هذا "بناء الجملة العاطفي". التعليمات البرمجية أدناه برمجيًا جيدًا:

 obj.Calculate(); obj?.Calculate(); obj.Calculate(); obj!?.Calculate(); obj!!!.Calculate(); 

بالمناسبة ، أثناء عملي ، وجدت عدة طرق "لملء" Visual Studio باستخدام بناء الجملة الجديد. الحقيقة هي أنه لا يمكنك تحديد عدد الأحرف بحرف واحد عند وضع "!". وهذا يعني أنه يمكنك كتابة ليس فقط رمز النموذج:

 object temp = null! 

ولكن أيضا:

 object temp = null!!!; 

يمكنك الانحراف والمضي قدمًا وكتابة مثل هذا:

 object temp = null!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!; 

هذا الرمز يجمع بنجاح. ولكن إذا طلبت معلومات حول شجرة بناء الجملة باستخدام Syntax Visualizer من .NET Compiler Platform SDK ، فسوف يتعطل Visual Studio.

يمكنك الحصول على معلومات حول المشكلة من "عارض الأحداث":

 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: 

إذا ذهبت إلى أبعد من ذلك وزادت عدد علامات التعجب عدة مرات ، فسوف يقع Visual Studio بنفسه - لم تعد هناك حاجة إلى مساعدة Syntax Visualizer. مكتبات Microsoft.CodeAnalysis و برنامج التحويل البرمجي csc.exe أيضًا لا تستوعب هذا الرمز.

بالطبع ، هذه أمثلة اصطناعية ، لكن هذه الحقيقة بدت مضحكة بالنسبة لي.

مجموعة أدوات


المذكرة. مرة أخرى ، أواجه مشكلة ترجمة كلمة "تقييم" في سياق محادثة حول مشاريع MSBuild. كانت الترجمة ، التي بدت الأقرب في المعنى وفي نفس الوقت تبدو طبيعية ، "بناء نموذج للمشروع". إذا كان لديك خيارات ترجمة بديلة - يمكنك الكتابة إلي ، سيكون من الممتع قراءتها.

كان من الواضح أن تحديث مجموعة الأدوات سيكون المهمة الأكثر استهلاكا للوقت. بتعبير أدق ، بدا الأمر كذلك من البداية ، لكنني الآن أميل إلى الاعتقاد بأن أكثر المشاكل صعوبة هي دعم البرنامج المساعد. على وجه الخصوص ، كان هذا بسبب مجموعة الأدوات الموجودة بالفعل وآلية إنشاء نموذج مشروع MSBuild ، والذي نجح الآن ، على الرغم من أنه يتطلب التوسع. لا حاجة لكتابة خوارزميات من الصفر تبسيط المهمة إلى حد كبير. لقد تم تبرير رهاننا على مجموعة أدوات "our" ، التي تم تصنيعها في مرحلة دعم Visual Studio 2017 ، مرة أخرى.

تقليديا ، كل شيء يبدأ بتحديث حزم NuGet. في علامة تبويب إدارة حزم NuGet للحلول ، يوجد زر "تحديث" ... لا يعمل. عند تحديث جميع الحزم ، ظهرت تعارضات في إصدارات متعددة ، ويبدو أن حلها غير صحيح. هناك طريقة أكثر إيلامًا ، ولكن يبدو أن الطريقة الأكثر موثوقية هي "تحديث قطعة" لتحديث حزم Microsoft.Build / Microsoft.CodeAnalysis المستهدفة.

تم تحديد أحد الاختلافات على الفور باختبارات قواعد التشخيص - تم تغيير بنية شجرة بناء الجملة لعقدة موجودة بالفعل. لا بأس ، تصحيح بسرعة.

اسمحوا لي أن أذكرك بأنه خلال العمل نقوم باختبار أجهزة التحليل (C # ، C ++ ، Java) في مشاريع مفتوحة المصدر. يسمح لك هذا باختبار قواعد التشخيص بشكل جيد - على سبيل المثال ، العثور على إيجابيات كاذبة ، أو التعرف على الحالات الأخرى التي لم يتم أخذها في الاعتبار (تقليل عدد السلبيات الخاطئة). تساعد هذه الاختبارات أيضًا على تتبع الانحدار المحتمل في المرحلة الأولى من تحديث المكتبات / مجموعة الأدوات. وهذه المرة لم تكن استثناء ، حيث ظهر عدد من المشكلات.

كانت إحدى المشكلات تدهور السلوك داخل مكتبات CodeAnalysis. وبشكل أكثر تحديدًا ، في عدد من المشاريع في رمز المكتبة ، حدثت استثناءات أثناء عمليات مختلفة - الحصول على معلومات دلالية ، وفتح مشاريع ، إلخ.

يتذكر القراء المهتمون حول دعم Visual Studio 2017 أن مجموعة التوزيع الخاصة بنا تحتوي على كعب رقيق - حجم ملف MSBuild.exe هو 0 بايت.

هذه المرة كان علي أن أذهب إلى أبعد من ذلك - الآن تحتوي مجموعة التوزيع أيضًا على أدوات ترجمة مترجمة فارغة - csc.exe ، vbc.exe ، VBCSCompiler.exe. لماذا؟ بدأ الطريق إلى ذلك بتحليل أحد المشاريع في قاعدة الاختبار ، والتي ظهرت عليها "فرق" التقارير - كان هناك عدد من التحذيرات غائبة عند استخدام الإصدار الجديد من المحلل.

تحولت المشكلة إلى رموز تجميع مشروطة - عند تحليل مشروع باستخدام الإصدار الجديد من المحلل ، تم استخراج بعض الرموز بشكل غير صحيح. لفهم سبب هذه المشكلة بشكل أفضل ، اضطررت إلى الغوص في مكتبات Roslyn.

لتحليل أحرف الترجمة الشرطية ، استخدم الأسلوب GetDefineConstantsSwitch لفئة Csc من مكتبة Microsoft.Build.Tasks.CodeAnalysis . يتم تنفيذ التحليل باستخدام طريقة String.Split على عدد من المحددات:

 string[] allIdentifiers = originalDefineConstants.Split(new char[] { ',', ';', ' ' }); 

طريقة التحليل هذه تعمل بشكل جيد ، يتم استخراج جميع رموز الترجمة الشرطية اللازمة بنجاح. مزيد من الحفر.

النقطة الرئيسية التالية هي استدعاء الأسلوب ComputePathToTool للفئة ToolTask . تقوم هذه الطريقة بإنشاء المسار إلى الملف القابل للتنفيذ ( csc.exe ) والتحقق من وجوده. في حالة وجود الملف ، يتم إرجاع المسار إليه ، وإلا يتم إرجاع فارغة .

رمز المتصل:

 .... string pathToTool = ComputePathToTool(); if (pathToTool == null) { // An appropriate error should have been logged already. return false; } .... 

نظرًا لعدم وجود ملف csc.exe (يبدو - لماذا نحتاج إليه؟) ، يعد PathToTool في هذه المرحلة لاغياً ، وتنتهي الطريقة الحالية ( ToolTask.Execute ) من تنفيذها بالنتيجة الخاطئة . نتيجة لذلك ، يتم تجاهل نتائج المهمة ، بما في ذلك رموز الترجمة الشرطية الناتجة.

حسنًا ، دعنا نرى ما يحدث إذا وضعت ملف csc.exe في الموقع المتوقع.

في هذه الحالة ، يشير pathToTool إلى الموقع الفعلي للملف الموجود ويستمر تنفيذ أسلوب ToolTask . النقطة الرئيسية التالية هي استدعاء الأسلوب ManagedCompiler.ExecuteTool . ويبدأ على النحو التالي:

 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; } .... } 

خاصية SkipCompilerExecution صحيحة (منطقيا ، نحن لسنا في الواقع ترجمة). نتيجة لذلك ، تتحقق طريقة الاستدعاء ( ToolTask.Execute المذكورة سابقًا ) من أن رمز الإرجاع للأسلوب ExecuteTool هو 0 ، وإذا كان الأمر كذلك ، فإنه يكمل تنفيذه بالقيمة true . ما كان لديك وراء csc.exe كان هناك - المترجم الحقيقي أو "الحرب والسلام" من ليو تولستوي في شكل نصي لا يهم.

نتيجة لذلك ، تنبع المشكلة الرئيسية من حقيقة أن تسلسل الخطوات محدد بالترتيب التالي:

  • تحقق من وجود المترجم ؛
  • تحقق مما إذا كان المترجم يحتاج إلى بدء التشغيل ؛

ليس العكس. كعب الروتين المحول بنجاح حل هذه المشكلة.

حسنًا ، كيف أتت شخصيات الترجمة الناجحة إذا لم يتم اكتشاف ملف csc.exe (وتم تجاهل نتيجة المهمة)؟

هناك طريقة لهذه الحالة - CSharpCommandLineParser.ParseConditionalCompilationSymbols من مكتبة Microsoft.CodeAnalysis.CSharp . يتم تنفيذ تحليل بواسطة الأسلوب String.Split مع عدد من المحددات:

 string[] values = value.Split(new char[] { ';', ',' } /*, StringSplitOptions.RemoveEmptyEntries*/); 

لاحظ الفرق مع مجموعة المحددات من طريقة Csc.GetDefineConstantsSwitch ؟ في هذه الحالة ، المسافة البيضاء ليست فاصل. وبالتالي ، إذا تمت كتابة أحرف الترجمة الشرطية بمسافة ، فسوف تقوم هذه الطريقة بتحليلها بطريقة غير صحيحة.

نشأ هذا الموقف على المشروعات ذات المشكلات - حيث تمت كتابة أحرف الترجمة الشرطية فيها بمسافة ، وتم تحليلها بنجاح باستخدام GetDefineConstantsSwitch ، ولكن ليس ParseConditionalCompilationSymbols .

هناك مشكلة أخرى كشفت عن نفسها بعد تحديث المكتبات وهي تدهور السلوك في عدد من الحالات ، على وجه الخصوص ، في المشروعات التي لم يتم جمعها. نشأت مشاكل في مكتبات Microsoft.CodeAnalysis وإعادتها إلينا في شكل استثناءات مختلفة - ArgumentNullException (لم تتم تهيئة بعض المسجل الداخلي) و NullReferenceException وغيرها.

أريد أن أتحدث عن واحدة من هذه المشاكل أدناه - بدا لي مثيراً للاهتمام.

واجهنا هذه المشكلة عند التحقق من أحدث إصدار من مشروع Roslyn - تم طرح NullReferenceException من رمز إحدى المكتبات. نظرًا لوجود معلومات مفصلة كافية حول موقع المشكلة ، وجدنا بسرعة رمز المشكلة ، ومن أجل الاهتمام ، قررنا محاولة معرفة ما إذا كانت المشكلة تتكرر عند العمل من Visual Studio.

حسنًا - كان من الممكن إعادة إنتاجه في Visual Studio (أجريت التجربة على Visual Studio 16.0.3). للقيام بذلك ، نحتاج إلى تعريف فئة للنموذج التالي:

 class C1<T1, T2> { void foo() { T1 val = default; if (val is null) { } } } 

سنحتاج أيضًا إلى Syntax Visualizer (جزء من .NET Compiler Platform SDK). من الضروري طلب TypeSymbol (عنصر القائمة "View TypeSymbol (إن وجد)") من عقدة شجرة بناء الجملة من النوع ConstantPatternSyntax ( خالية ). بعد ذلك ، سيتم إعادة تشغيل Visual Studio ، ويمكنك في "عارض الأحداث" رؤية معلومات حول المشكلة ، على وجه الخصوص ، العثور على تتبع المكدس:

 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) .... 

كما ترون ، سبب المشكلة هو إلغاء مرجع المرجع الفارغ.

كما ذكرت سابقا ، واجهنا نفس المشكلة أثناء اختبار المحلل. إذا كنت تستخدم مكتبات تصحيح Microsoft.CodeAnalysis لإنشاء محلل ، فيمكنك الوصول إلى المكان المحدد عن طريق تصحيح الأخطاء عن طريق طلب TypeSymbol من العقدة المطلوبة في شجرة بناء الجملة.

نتيجة لذلك ، وصلنا إلى أسلوب ClassifyImplicitBuiltInConversionSlow المذكور في تتبع المكدس أعلاه :

 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; } 

المشكلة هي أن المعلمة الوجهة فارغة في هذه الحالة. وفقًا لذلك ، عند استدعاء الوجهة .SpecialType ، يتم طرح NullReferenceException .نعم ، Debug.Assert أعلى من إلغاء التسجيل ، لكن هذا لا يكفي ، لأنه في الواقع لا يحمي من أي شيء - إنه يساعد فقط في تحديد المشكلة في إصدارات تصحيح الأخطاء من المكتبات. أو لا يساعد.

التغييرات في بناء نموذج لمشاريع C ++


لم يحدث شيء مثير للاهتمام بشكل خاص هنا - الخوارزميات القديمة لم تتطلب أي تعديلات مهمة ، والتي ستكون مثيرة للاهتمام للحديث عنها. ربما كان هناك نقطتان من المنطقي أن أسهب فيهما.

أولاً ، كان علينا تعديل الخوارزميات التي تعتمد على قيمة ToolsVersion لتتم كتابتها بتنسيق رقمي. دون الخوض في التفاصيل - هناك العديد من الحالات التي تحتاج فيها إلى مقارنة مجموعات الأدوات واختيار ، على سبيل المثال ، نسخة جديدة أكثر حداثة. هذا الإصدار ، على التوالي ، كان له قيمة عددية أعلى. كان هناك حساب أن ToolsVersion ، المقابلة للنسخة الجديدة من MSBuild / Visual Studio ، سوف تساوي 16.0. مهما كانت الحالة ... من أجل الاهتمام ، أقتبس جدولًا حول كيفية تغيير قيم الخصائص المختلفة في إصدارات مختلفة من Visual Studio:
اسم المنتج الاستوديو البصري
رقم إصدار الاستوديو البصري
أدوات الإصدار
إصدار PlatformToolset
مرئي ستوديو 2010
10.0
4.0
100
استوديو مرئي 2012
11.0
4.0
110
استوديو مرئي 2013
12.0
12.0
120
استوديو مرئي 2015
14.0
14.0
140
استوديو مرئي 2017
15.0
15.0
141
ستوديو بصري 2019
16.0
تيار
142

بطبيعة الحال ، تعد هذه النكات قديمة ، لكن لا يمكنك المساعدة في تذكر إصدارات Windows و Xbox من أجل فهم أن التنبؤ بالقيم المستقبلية (بغض النظر عن الاسم والإصدار) غير مستقر في حالة Microsoft. :)

كان الحل بسيطًا بما فيه الكفاية - تقديم تحديد أولويات مجموعات الأدوات (تخصيص كيان أولوية منفصل).

النقطة الثانية هي مشاكل عند العمل في Visual Studio 2017 أو في بيئة مجاورة (على سبيل المثال ، وجود متغير البيئة VisualStudioVersion ). الحقيقة هي أن حساب المعلمات الضرورية لإنشاء نموذج لمشروع C ++ هو أكثر تعقيدًا بكثير من بناء نموذج لمشروع .NET. في حالة .NET ، نستخدم مجموعة أدواتنا الخاصة والقيمة المقابلة لـ ToolsVersion. في حالة الإصدار C ++ ، يمكننا أن نبني على أدواتنا الحالية والقائمة في النظام. بدءًا من Build Tools في Visual Studio 2017 ، يتم تسجيل مجموعات الأدوات في ملف MSBuild.exe.config، ليس في التسجيل. وفقًا لذلك ، لا يمكننا الحصول عليها من القائمة العامة لمجموعات الأدوات (على سبيل المثال ، من خلال Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.Toolsets ) ، على عكس تلك الأدوات التي تم تسجيلها في السجل (المقابلة لـ <= Visual Studio 2015) .

نتيجة لما تقدم ، لن يعمل إنشاء نموذج مشروع باستخدام ToolsVersion 15.0 ، نظرًا لأن النظام لن يرى مجموعة الأدوات اللازمة. معظم الأدوات الحالية - الحالية- سيكون متاحًا في نفس الوقت ، نظرًا لأن هذا هو مجموعة أدواتنا الخاصة ، وبالتالي ، لا توجد مشكلة من هذا القبيل لبرنامج Visual Studio 2019. تبين أن الحل بسيط وسُمح له بحل المشكلة دون تغيير الخوارزميات الموجودة لإنشاء نموذج المشروع - إضافة واحد آخر إلى قائمة مجموعات الأدوات الخاصة بك ، الحالية ، 15.0 .

التغييرات في بناء نموذج لمشاريع C # .NET Core


في إطار هذه المهمة ، تم حل مشكلتين في وقت واحد ، حيث تبين أنهما متصلان:

  • بعد إضافة مجموعة الأدوات "الحالية" ، توقف تحليل مشاريع .NET Core لبرنامج Visual Studio 2017 عن العمل ؛
  • لم ينجح تحليل مشاريع .NET Core على نظام حيث لم يتم تثبيت إصدار واحد على الأقل من Visual Studio.

كانت المشكلة في كلتا الحالتين هي نفسها - تم البحث في بعض ملفات .targets / .props الأساسية بطرق خاطئة. أدى ذلك إلى حقيقة أنه لم يكن من الممكن بناء نموذج مشروع باستخدام مجموعة أدواتنا.

في غياب Visual Studio ، قد ترى مثل هذا الخطأ (مع الإصدار السابق من toolset'a - 15.0 ):

 The imported project "C:\Windows\Microsoft.NET\Framework64\ 15.0\Microsoft.Common.props" was not found. 

عند إنشاء نموذج C # .NET Core للمشروع في Visual Studio 2017 ، قد ترى المشكلة التالية (مع الإصدار الحالي من مجموعة الأدوات ، الحالي ):

 The imported project "C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\MSBuild\Current\Microsoft.Common.props" was not found. .... 

نظرًا لأن المشكلات متشابهة (لكنها تبدو كذلك) ، يمكنك محاولة قتل عصفورين بحجر واحد.

أصف أدناه كيف تم حل هذه المشكلة دون الدخول في التفاصيل الفنية. هذه التفاصيل بالذات (حول إنشاء نماذج لمشاريع C # .NET Core ، وكذلك تغيير إنشاء النماذج في مجموعة أدواتنا) تنتظر في أحد مقالاتنا المستقبلية. بالمناسبة ، إذا قرأت النص أعلاه بعناية ، فقد تلاحظ أن هذا هو المرجع الثاني للمقالات المستقبلية. :)

فكيف حلنا هذه المشكلة؟ كان الحل هو توسيع مجموعة أدواتنا الخاصة على حساب ملفات .targets / .props الرئيسية من .NET Core SDK ( Sdk.props ، Sdk.targets ). هذا سمح لنا بمزيد من التحكم في الموقف ، ومزيد من المرونة في إدارة الواردات ، وكذلك في بناء نموذج لمشاريع .NET Core بشكل عام. نعم ، نمت مجموعة أدواتنا قليلاً مرة أخرى ، وكان علينا أيضًا إضافة بعض المنطق لإعداد مشاريع البيئة اللازمة لبناء نموذج .NET Core ، لكن يبدو أن الأمر يستحق ذلك.

في السابق ، كان مبدأ العمل عند إنشاء نموذج لمشاريع .NET Core على النحو التالي: لقد طلبنا هذا البناء ببساطة ، ثم عمل كل شيء على حساب MSBuild.

الآن ، عندما سيطرنا أكثر على أيدينا ، يبدو الأمر مختلفًا بعض الشيء:

  • إعداد البيئة اللازمة لبناء نموذج لمشاريع .NET Core ؛
  • بناء نموذج:
    • بدء البناء باستخدام .targets / .props الملفات من أدواتنا ؛
    • استمرار البناء باستخدام الملفات الخارجية.

من الخطوات الموضحة أعلاه ، من الواضح أن تحديد البيئة اللازمة له هدفان رئيسيان:
  • بدء بناء النموذج باستخدام .targets / .props الملفات من مجموعة الأدوات الخاصة بك ؛
  • إعادة توجيه المزيد من العمليات إلى ملفات .targets / .props الخارجية.

للبحث عن ملفات .targets / .props اللازمة لإنشاء نموذج لمشاريع .NET Core ، يتم استخدام مكتبة خاصة - Microsoft.DotNet.MSBuildSdkResolver. تم حل بدء استخدام الملفات من مجموعة أدواتنا باستخدام متغير بيئة خاص تستخدمه هذه المكتبة - نقترح مكان استيراد الملفات الضرورية (من مجموعة أدواتنا). نظرًا لأن المكتبة جزء من التوزيع لدينا ، فلا توجد مخاوف من أن المنطق سيتغير فجأة ويتوقف عن العمل.

الآن يتم استيراد ملفات Sdk أولاً من مجموعة أدواتنا ، وبما أننا نستطيع تغييرها بسهولة ، فإن التحكم في المنطق الإضافي لبناء النموذج ينتقل إلى أيدينا. لذلك ، يمكننا أن نحدد بأنفسنا الملفات التي يجب استيرادها ومن أين. ينطبق هذا أيضًا على Microsoft.Common.props المذكورة أعلاه. نحن نستورد هذا والملفات الأساسية الأخرى من مجموعة أدواتنا الخاصة بثقة في توفرها ومحتواها.

بعد ذلك ، وبعد الانتهاء من عمليات الاستيراد الضرورية وتعيين عدد من الخصائص ، ننقل التحكم الإضافي في بناء النماذج إلى .NET Core SDK الفعلي ، حيث تتم بقية الإجراءات اللازمة.

استنتاج


بشكل عام ، أصبح دعم Visual Studio 2019 أسهل من دعم Visual Studio 2017 ، والذي ، كما أراه ، يرجع إلى عدة عوامل. أولاً ، لم تقم Microsoft بتغيير العديد من الأشياء بين Visual Studio 2015 و Visual Studio 2017. نعم ، لقد قمنا بتغيير مجموعة الأدوات الرئيسية ، وبدأنا في توجيه المكونات الإضافية لـ Visual Studio على عدم التزامن ، ولكن مع ذلك. والثاني - كان لدينا بالفعل حل جاهز باستخدام مجموعة أدواتنا وبناء نماذج مشاريع - لم تكن هناك حاجة لاختراع كل شيء من جديد ، لقد كان يكفي لتوسيع الحل الحالي. كما أن الدعم البسيط نسبيًا لتحليل مشاريع .NET Core للظروف الجديدة (وكذلك لحالات التحليل على جهاز لا توجد فيه مثيلات Visual Studio مثبتة) بسبب توسيع نظام بناء نموذج المشروع لدينا يمنحنا الأمل في أننا اتخذنا الاختيار الصحيح.بعد أن قررت السيطرة على نفسك.

لكن لا يزال ، أود أن أكرر فكرة واحدة كانت في المقال السابق مرة أخرى - في بعض الأحيان باستخدام حلول جاهزة ليست بسيطة كما يبدو للوهلة الأولى.



إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فالرجاء استخدام الرابط الخاص بترجمة: Sergey Vasiliev. دعم Visual Studio 2019 في PVS-Studio

Source: https://habr.com/ru/post/ar454738/


All Articles