При выпуске нового релиза сборки её версию обычно меняют. Это особенно актуально, если разрабатывается библиотека, от которой зависят другие проекты. Но что, если этого не делать? Предлагаю вашему вниманию небольшую историю о проблемах с библиотеками от Microsoft.
Я активно участвую в разработке C# анализатора PVS-Studio. Помимо прочего, при работе он использует сборки 'Microsoft.Build.dll', 'Microsoft.Build.Framework.dll' и т. д. Эти библиотеки помогают получать различную информацию из проектного файла, что позволяет выполнять более глубокий анализ.
В какой-то момент разработчики из Microsoft решили, что версией всех подобных сборок всегда будет 15.1. Неважно, меняется ли публичный интерфейс, появляются ли новые типы, меняются ли старые — версия всегда одна и та же. У соответствующих NuGet-пакетов версии меняются, а вот у сборок — нет. На вопрос "почему так?" мне ответил maintainer репозитория msbuild:
This is intentional, and allows API client applications to work with multiple versions of MSBuild.
Ну вроде бы и ладно — это их дело, и никому такой подход не мешает.
Или мешает?
Примерно с таким незамысловатым сообщением о падении анализатора к нам обратился клиент. Мы обратились к переводчику и узнали, что сообщение говорит об отсутствии необходимого типа Microsoft.Build.Framework.Traits в одной из наших зависимостей — 'Microsoft.Build.Framework.dll'.
Очевидно, у нас это не воспроизводилось. Нужная сборка всегда устанавливается в папку с исполняемым файлом анализатора, и нужный тип в этой сборке присутствует. В чём же дело?
Не так давно мы как раз обновляли все 'Microsoft.Build.*' зависимости ради поддержки анализа проектов под .NET 7. Взглянув на предыдущие версии анализатора, мы увидели, что в используемой им библиотеке 'Microsoft.Build.Framework.dll' действительно нет нужного типа. Возможно, у клиента как-то некорректно обновился анализатор?
Ничего подобного! По нашей просьбе клиент скинул нам нужный dll-файл, и он оказался вполне корректным — нужный тип там присутствовал. Следующим шагом мы скинули пользователю специальную версию анализатора, которая бы логировала пути, по которым на этапе исполнения подгружаются сборки. И оказалось, что на этапе выполнения подгружалась не библиотека, которая лежала рядом с исполняемым файлом, а её 'аналог' из глобального кеша сборок (global assembly cache, GAC).
Тут-то пазл и собрался!
Итак, имеем:
Проверить данную гипотезу оказалось легко: достаточно было просто добавить в GAC сборку старого выпуска, и падение воспроизвелось. Но что было ещё интереснее — перестала работать Visual Studio 2022. При открытии появляется весёлое сообщение про то, что всё плохо:
Как серьёзный и опытный программист, я решил его проигнорировать и просто нажал "да". Сообщение пропало, ну и хорошо. Затем я открыл простейший консольный проект и обнаружил его в печальном состоянии:
Ну опять же, я человек простой — вижу "unloaded", значит нажимаю "Reload":
И тут же получаю знакомое сообщение (хотя уже не на немецком):
Да-да, это то же самое исключение, что возникало у нашего клиента.
Воспроизводится это всё легко:
Конечно, у нашего клиента не было цели сломать себе Visual Studio 2022. У него её попросту не было, а потому и заметить её падения он не мог. Вместо этого весь удар на себя принял анализатор, завязанный на новые MSBuild-библиотеки и потому таскающий их с собой.
Судя по всему, наличие данной сборки в GAC было необходимо для работы некоторого окружения, поэтому просто удалить её было нельзя. Вместо этого клиент обновил сборку, лежащую в GAC, и вроде бы ничего не сломалось :).
Сначала я писал на эту тему в Twitter и на Stack Overflow, но никакой реакции не получил. Куда более действенным оказалось создание issue в репозитории MSBuild.
В этом issue сам maintainer MSBuild, Rainer Sigwald, уделил моим вопросам немного времени, за что ему большое спасибо. Если обобщить, то он сказал мне 2 вещи:
Никакой возможности обойти GAC при подгрузке сборок на этапе исполнения найти не удалось :(.
Во-первых, повторю — не стоит добавлять сборки Microsoft.Build.* версии 15.1 в GAC, так как это может сломать MSBuild (ну и что не менее важно — это может сломать PVS-Studio).
Во-вторых, по возможности лучше всё-таки обновлять версию сборки, если в неё вносятся изменения. Таким образом можно уберечь своих пользователей от потенциальных проблем, подобных описанным в статье.
В-третьих, если вы ищете ошибки с помощью PVS-Studio и столкнулись с подобной проблемой, то уже знаете, в чём дело и как быть.
А у меня на этом всё, желаю удачи!
0