Сборка Revit API C# решения под разные версии Revit

Вышла новая версия Revit — время пересобирать плагины для поддержки новой версии!

Revit API от версии к версии меняется, и что делать, если вы написали какой-то плагин и хотите, чтобы он работал под разными версиями Revit? Конечно, можно просто сделать копию всего решения и подправить под новую версию, но как тогда вносить изменения — в каждую копию отдельно? Ну уж нет!

Решить задачу можно разными способами, опишу решение, к которому сам пришел.

К сожалению, в стандартном интерфейсе Visual Studio задачу не решить: там можно вводить несколько «конфигураций», но нам этого мало — ведь ещё и нужно подключать разные версии библиотек Revit API. К счастью, возможность всё-таки есть, но нужно будет подредактировать файлы вручную! Я буду использовать Notepad++ и собирать под версии Revit 2017-2022.

Для начала, конечно, надо закрыть Visual Studio. Далее открываем sln-файл в блокноте (предварительно сделайте резервную копию!). Нас интересуют вот эти две секции:

Здесь мы как раз вводим «Конфигурации сборки». В первой секции меняем текст на следующий:

R2017|Any CPU = R2017|Any CPU
R2018|Any CPU = R2018|Any CPU
R2019|Any CPU = R2019|Any CPU
R2020|Any CPU = R2020|Any CPU
R2021|Any CPU = R2021|Any CPU
R2022|Any CPU = R2022|Any CPU

Во второй секции обратите внимание на GUID в фигурных скобках. Сохраните его где-нибудь, он сейчас пригодится. Вставляем этот текст, но заменяем guid на свой:

{замените здесь на свой guid}.R2017|Any CPU.ActiveCfg = R2017|Any CPU
{замените здесь на свой guid}.R2017|Any CPU.Build.0 = R2017|Any CPU
{замените здесь на свой guid}.R2018|Any CPU.ActiveCfg = R2018|Any CPU
{замените здесь на свой guid}.R2018|Any CPU.Build.0 = R2018|Any CPU
{замените здесь на свой guid}.R2019|Any CPU.ActiveCfg = R2019|Any CPU
{замените здесь на свой guid}.R2019|Any CPU.Build.0 = R2019|Any CPU
{замените здесь на свой guid}.R2020|Any CPU.ActiveCfg = R2020|Any CPU
{замените здесь на свой guid}.R2020|Any CPU.Build.0 = R2020|Any CPU
{замените здесь на свой guid}.R2021|Any CPU.ActiveCfg = R2021|Any CPU
{замените здесь на свой guid}.R2021|Any CPU.Build.0 = R2021|Any CPU
{замените здесь на свой guid}.R2022|Any CPU.ActiveCfg = R2022|Any CPU
{замените здесь на свой guid}.R2022|Any CPU.Build.0 = R2022|Any CPU

Результат будет примерно такой:

Готово, сохраняем и закрываем этот файл.

Далее будет посложнее. Заходим в папку с решением и открываем csproj-файл (для него тоже не забудьте сделать резервную копию!). В нём нам предстоит задать, что именно будет делать Студия в зависимости от выбранной конфигурации. Этот файл имеет xml-синтаксис: <Свойство> Содержимое </Свойство>. Надо не забывать, что у каждого открывающего тэга должен быть закрывающий с таким же именем, помеченный значком слэш /. В Notepad++ есть подсветка синтаксиса и работать достаточно удобно.

Структура файла будет примерно следующая:

<Project>
//начальные настройки не трогаем
  <PropertyGroup>
    Общие настройки не трогаем
  </PropertyGroup>
  <PropertyGroup>
    Добавляем особые настройки под R2017
  </PropertyGroup>
  <PropertyGroup>
    Особые настройки под R2018 и т.д.
  </PropertyGroup>
  <ItemGroup>
    Общие подключаемые библиотеки
  </ItemGroup>
  <Choose>
    <When Condition = 2017>
      Подключаемые библиотеки для R2017
    </When>
    <When Condition = 2017>
      Подключаемые библиотеки для R2018, и т.д.
     </When>
  </Choose>
//остальные настройки не трогаем
</Project>

Главная магия будет происходить в блоке Choose/When: как раз здесь мы можем указать, что в зависимости от конфигурации будут подключаться разные библиотеки. Намечаем план корректировок:

Смотрим блок конфигурации под конкретную версию. Содержимое его будет примерно такое:

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'R2017|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\R2017\</OutputPath>
<DefineConstants>DEBUG;R2017</DefineConstants>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<AssemblyName>$(AssemblyName)_2017</AssemblyName>
</PropertyGroup>

Опции DebugSymbols, Optimise и DebugType нужны, чтобы работала отладка и присоединение к процессу Revit в VisualStudio.

OutputPath — папка, куда будет складироваться dll-ка. У меня сделано, что каждая версия будет складываться в папку с номером версии. AssemblyName, соответственно, — шаблон имени dll-файла, у меня будет как ИмяРешения_НомерВерсии.dll.

DefineConstants — важная вещь, это «ключи для компиляции», которые можно будет использовать в коде (далее покажу).

TargetFramework — какую версию .Net Framework использовать при сборке:

  • Revit 2017: v4.5.2
  • Revit 2018: v4.6
  • Revit 2019, 2020: v4.7
  • Revit 2021, 2022: v4.8

.NET Framework устанавливается вместе с Ревитом, но только «облегченная» версия — для запуска готовых приложений, не для их сборки. Нужно установить именно версии Developer Pack:

Итак, копируем PropertyGroup столько раз, сколько у нас конфигураций, и внимательно меняем значения на нужные нам:

Далее в секции ItemGroups удаляем блоки Reference со ссылками на библиотеки Revit и после окончания этой ItemGroup добавляем секцию Choose/When:

«When», соответственно, дублируем столько же раз, сколько у нас конфигураций.

Готово! Сохраняем файл и открываем решение в VisualStudio. В списке сверху должны появиться все заданные конфигурации, и если их переключать — будет видно, что меняется путь в свойствах подключенных RevitAPI и RevitAPIUI:

Но для чего мы всем этим занимались? Для того, чтобы можно было использовать символы условной компиляции! Изменения в Revit API обычно вносятся небольшие, и весь код остается неизменный, но где-то в одном месте выдает ошибку из-за изменений в API. Например, свойство Rebar.Normal работает в Revit 2017, но в 2018 уже помечено как «Устаревшее», а в 2019 просто будет выдавать ошибку:

Мы можем просто «обернуть» этот блок кода и сказать, чтобы он выполнялся только для одной конфигурации, а для другой — игнорировался! Это делается синтаксисом #if #endif. Обратите внимание, как при переключении «текущей конфигурации» код «отключается» и вообще не будет компилироваться.

R2017 после #if — это как раз то, что мы ранее указывали в DefineConstants. Можно вводить несколько через ||.

Если в тот момент, когда выбрана какая-то конфигурация, запустить «Пересобрать проект», то соберется dll именно с нужным кодом и подключенными библиотеками и сохранится в нужной папке.

Но можно сделать ещё круче — использовать Пакетную сборку! Зайдите Build — Batch Build, просто отметьте все флажки, нажмите «Пересобрать» и разом получите набор dll-ок под каждую версию Revit:

Что-то не получилось? Вот примеры файлов на Github: sln, csproj.

Успехов в изучении C# и Revit API!