Ежегодно Microsoft выпускает новую версию .NET. Это большое событие, к которому мы выпускаем версию PVS-Studio с поддержкой нововведений. Сегодня речь пойдёт про боль PVS-Studio при обновлении Roslyn — неотъемлемой части .NET.

Для удобства я поделил статью на две части: краткую (для тех, кто хочет быстро найти обоснование изменений, произошедших в PVS-Studio 7.34) и длинную (для тех, кто хочет узнать ещё и историю).

Краткая версия

В релизе PVS-Studio 7.34 была добавлена поддержка .NET 9 и всех новых функций языка C#. Для этого нам требовалось обновить платформу Roslyn, которая используется PVS-Studio для синтаксического анализа языка C#, а также обновить взаимозависимые библиотеки MSBuild, используемые для построения модели и парсинга проектов на C# и C++ (.csproj, .vcxproj).

Начиная с версии PVS-Studio 7.34, для анализа проектов C# .NET, .NET Standard и .NET Framework в стиле SDK требуется дополнительная установка .NET 9 SDK в системе, выполняющей анализ.

Классические проекты .NET Framework остаются без изменений, если в системе, выполняющей анализ, установлен экземпляр MSBuild или Visual Studio версий 2017 и выше. Если в системе установлены только MSBuild или Visual Studio версий 2015 и ниже, то для анализа классических проектов .NET Framework также потребуется наличие .NET 9 SDK.

Причиной этого изменения стала потеря обратной совместимости библиотек Roslyn, используемых для анализа проектов из Visual Studio\MSBuild версий 2015 и ниже. Теперь для этого Roslyn использует собственный серверный компонент на базе .NET SDK, который, в свою очередь, требует наличия в системе .NET SDK.

Краткая версия — всё.

Длинная версия

Обновление PVS-Studio C#

В релизе PVS-Studio 7.34 была добавлена поддержка .NET 9 и всех новых функций языка C#.

Кстати, предлагаю вам прочитать две наши статьи про нововведения:

Для поддержки .NET 9 нам потребовалось обновить библиотеки Roslyn и MSBuild. PVS-Studio и Roslyn используют MSBuild для построения модели проекта (Design-Time Build). Это достаточно сложный процесс, который требует определённых знаний от сотрудника (или его ментора). Вы могли бы подумать: а что сложного нажать update на соответствующих NuGet пакетах из IDE? До поддержки нами Visual Studio 2017 так и было. Я бы мог начать рассказывать, что изменилось, и почему недостаточно обновить пакеты, но лучше поделюсь ссылкой на статью, где об этом написано.

Но краткую вводную всё равно придётся рассказать. Что приходится делать для поддержки нового .NET и почему? MSBuild, начиная с версии 15 (VS 2017), не умеет находить собственную установочную директорию. Предполагаю, это связано с тем, что MSBuild с версии 15 и выше не прописывается в реестре. Чтобы обойти эту проблему, было предложено решение:

  1. Мы создаём собственный кастомный toolset. Для этого нам нужно тащить с собой изменённый BuildTools, который содержит большое количество ИЗМЕНЁННЫХ .target и .props файлов;

  2. Ищем установочные директории MSBuild и на основе этой информации изменяем значение свойств в кастомном toolset;

  3. Подсовываем MSBuild путь до нашего BuildTools, чтобы он считал кастомный toolset;

  4. Profit!

Исходя из этого, для поддержки нового .NET нужно:

  1. Обновить библиотеки Roslyn, MSBuild и необходимые им;

  2. Справиться с некоторым Dependency hell;

  3. Обновить наш BuildTools до последней версии, сохраняя обратную совместимость с прошлыми версиями в некоторых местах;

  4. Исправить проблемы, которые возникнут при обновлении (а они точно возникнут, иначе не было бы этой статьи).

Это очень упрощённая схема, и лучше бы вам прочитать статью по ссылке выше. Вдобавок есть ещё статьи про поддержку Visual Studio 2019 и Visual Studio 2022. В них также описываются возникающие проблемы. Прочитав всё это вы могли подумать: мммм, вкусное legacy.

Да, legacy... Теперь уже есть решение для поиска MSBuild — MSBuildLocator. Но у него тоже есть свои ограничения: он разделён на сборки для .NET Framework и .NET Core. Версия для .NET Framework может искать и регистрировать MSBuild только для .NET Framework, версия .NET Core —только версию MSBuild для .NET Core. Предположу, что это связано с тем, что MSBuild также разделён на MSBuild для .NET Framework и на MSBuild для .NET Core.

Проблемы и решения

Теперь, когда вы немного лучше понимаете, с чем приходится иметь дело, начнём.

Вышел .NET 9 RC 1, и мы приступили к поддержке. Первым делом обновили библиотеки и наш BuildTools. Во время обновления мы заметили, что в пакете Microsoft.CodeAnalysis (Roslyn) появились две новые папки: BuildHost-net472 и BuildHost-netcore. Сначала я даже обрадовался и подумал, что теперь у нас будет меньше проблем. Но, как оказалось, это не так.

Обновление библиотек и BuildTools прошло довольно быстро, поскольку у нас уже был такой опыт. После этого этапа разработчик запускает локальный набор тестов, чтобы убедиться в корректности анализа. Эти тесты содержат большое количество Open Source проектов, на которых запускается анализатор. После их запуска возникли проблемы: в некоторых фрагментах кода анализатор начал сообщать (с помощью внутренних логов) об ошибках компиляции в полностью компилируемом коде, где-то были выброшены исключения. Мы приступили к правкам и успешно довели локальные тесты до полного прохождения.

Следующий этап — запуск обновлённой версии на тестах, запускающих анализатор на шаблонных проектах под разные версии Visual Studio и .NET. Эти тесты запускаются в контейнерах с разным окружением. И тут мы видим, что у нас сломался анализ на машинах, где установлена только VS 2010, 2012, 2013, 2015, и сломался анализ .NET Framework проектов SDK стиля c VS 2017, 2019, 2022, но без .NET SDK.

Почему предыдущие тесты прошли и не выявили проблем? Они запускались локально на машине разработчика, где установлены, по сути, все версии Visual Studio и множество версий .NET SDK.

Мы начали разбираться и нашли проблему: обновлённый Roslyn сделал не лучше, а хуже.

Раньше Roslyn ожидал, что пользователи будут использовать MSBuildLocator или что-то похожее. У нас же был свой BuildTools и свои toolset-ы, которые Roslyn использовал. Это позволяло нам поддерживать C# проекты даже для Visual Studio 2010. И в целом оставалась только сложность с поддержкой нового .NET. Ранее я писал, что Microsoft.CodeAnalysis теперь включает две дополнительные папки. Roslyn перешёл на новую архитектуру с Build серверами для самостоятельного поиска и Design-Time сборки проектов. Теперь он тянет с собой два BuildHost: для .NET Framework и для .NET Core. В зависимости от типа проекта Roslyn вызывает соответствующий процесс BuildHost для сборки проекта. Для поиска MSBuild используется MSBuildLocator. И здесь есть несколько проблемных моментов.

Первая проблема

Для работы BuildHost для .NET Core требуется минимум .NET 6 Runtime. И так как PVS-Studio использует Roslyn, пользователь должен иметь установленный .NET 6+ Runtime. В целом это можно понять и принять. Вы можете сказать: а почему бы не публиковать BuildHost для .NET Core как self-contained приложение или даже использовать NativeAOT? К сожалению, сильно вырастет вес, который будет занимать .exe файл. Но основное, это что для регистрации MSBuild под капотом используется Assembly.Load, который просто не работает в подобных приложениях.

Вторая проблема

MSBuildLocator для .NET Framework может искать только MSBuild 15, 16, 17 (Visual Studio 2017, 2019, 2022). Соответственно, если у пользователя достаточно старый проект и он использует Visual Studio 2015, то Roslyn не может найти подходящий MSBuild и просто не будет работать. И это учитывая тот факт, что проект полностью собирается на машине. Мы создали issue на GitHub об этом. Если кратко, ответ состоит в том, что для них это не приоритет, так как VS 2015 и прошлые версии слишком старые. Но оказалось, что если у пользователя имеется .NET SDK, то Roslyn начинает использовать запасной план. Если у вас старый .NET Framework проект, то Roslyn попробует использовать BuildHost для .NET Core. Чаще всего это работает нормально, но возможны проблемы, если встречается что-то, что не поддерживается MSBuild для .NET Core. Про это вы узнаете в описании третьей проблемы.

Из всего этого мы приходим к тому, что, если мы хотим оставить поддержку старых проектов, нам нужно поставлять .NET SDK. Для целостности продукта мы решили поставлять с собой на Windows .NET 9 SDK, как уже делаем на Linux и macOS. На этих операционных системах анализатору для работы нужен .NET SDK той версии, под которую он был собран. Зачем нам .NET 9 SDK на Linux и macOS — это отдельная тема для разговора, но поверьте, он нужен.

Третья проблема

Roslyn для любых проектов SDK стиля начинает использовать BuildHost для .NET Core, и неважно, может ли MSBuild для .NET Core собрать его. MSBuild для .NET Core поддерживает не все вещи, которые поддерживает MSBuild для .NET Framework. Например, COMReference не поддерживается в MSBuild для .NET Core. По этой проблеме тоже было создано issue. Как это решается с нашей стороны? Да в общем и целом никак. Подобные точечные ошибки выливаются в мелкие недостатки полученной от Roslyn семантической модели. И это никак не влияет на качество анализа.

Это только самые запомнившиеся проблемы. По ходу обновления мы сталкивались ещё с ворохом маленьких проблем.

Мысли

Что же мы имеем по итогу улучшений в Roslyn от Microsoft? Усложнение дистрибуции анализатора, необходимость для клиента ставить ненужные ему .NET SDK и десятки дней, потраченных на то, чтобы всё заработало.

Зачем же эта статья? Хотелось поделиться небольшой историей о том, как обновление и улучшение инструмента делает жизнь не лучше, а хуже. И на сам деле многое можно было избежать, если бы Roslyn более правильно выбирал BuildHost. А ещё хотелось иметь быстрый ответ на клиентский вопрос: "Почему так?" Для этого была написана краткая версия.

Во время чтения вам могло показаться, что я достаточно негативно высказываюсь о Roslyn. Может, так и было, но только чуть-чуть и из-за того, что мне сильно прибавили работы :) Я всё ещё считаю Roslyn отличным инструментом, который во многом облегчает разбор и анализ кода.

Также скажу, что в будущем мы бы хотели избавиться от наших BuildTools и перейти к похожей на Roslyn архитектуре. Как обычно и бывает, это достаточно сложная задача, так как много старого кода нужно переписать или выкинуть и написать заново. И при этом нужно сохранить совместимости со старыми проектами. Короче, та ещё работа.

А вам большое спасибо за прочтение :)

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Artem Rovenskii. How to update library and get swamped with this task. Roslyn and PVS-Studio 7.34 update.

Комментарии (1)