Портирование КОМПАС под Linux: особенности реализации и результаты
Мы много рассказывали на Хабре, как запустить САПР КОМПАС на Linux с разными вариантами Wine (ссылка 1, ссылка 2). Сегодня впервые публикуем пост о разработке нативной Linux-версии. На связи Андрей Пилюгин, ведущий инженер-программист.
Наша команда отвечает за портирование бизнес-логики, а работы по интерфейсу и по API делают другие команды. Эти процессы идут параллельно.
Работы по портированию КОМПАСа велись не один год, над этим начинала работать совсем небольшая группа программистов, и за этот период вышло две версии под Windows. По этой причине мы не могли разломать весь каркас приложения и написать его заново, приходилось аккуратно трансформировать приложение, постепенно заменяя его составляющие на кроссплатформенные и поддерживая в работоспособном состоянии все остальные. В этом нас сильно выручала система автотестов.
Мы решили не пытаться переписать всё сразу, а разделили портирование на этапы. На первом этапе хотели получить некую заготовку, в которой будет только сильно урезанное 2D с возможностью открытия нескольких документов, их масштабирования и переключения вкладок: по сути viewer.
Для запуска заготовки КОМПАСа под Linux мы:
убрали MFC из каркаса приложения
переписали большое количество Windows-специфичного кода
провели глубокий рефакторинг кода и структуры проекта
отрезали второстепенные механизмы (API, диалоги, печать и т.д.)
выделили модули, составляющие базовую часть приложения
реализовали новый маршрутизатор событий
переработали ресурсы, работу со шрифтами, с файловой системой
добились компиляции заготовки под Linux
Эта заготовка постепенно начала (и продолжает) обрастать функционалом, превращаясь в полноценный КОМПАС-3D.
Прочие модули, составляющие приложение, мы тоже не пытаемся портировать «в лоб», а используем многопроходный подход и постепенно, механизм за механизмом переводим на кроссплатформенные рельсы.
А теперь подробности.
Проблемы и задачи в портировании
К моменту перехода на Linux у нас был большой запас неиспользуемого старого кода, который остался после перевода большей части интерфейса на WPF в 17-й версии КОМПАС-3D. Это Windows-специфичный код, и от него нужно было избавляться.
Каркас приложения (окна, рабочие области, маршрутизация событий) был реализован на MFC. Сохранение в растровый формат, печать, текстовый документ — на GDI. Ресурсы (строки, меню, иконки, диалоги) мы хранили как стандартные ресурсы MFC. А работу с файловой системой, строками, таймерами и прочим очень часто строили, используя методы Windows API. Также, использовали много MFC/Windows API-специфичных типов данных и классов (VARIANT, CString, Cfont). Структура проекта не была оптимальной. И были десятки прочих проблем различных уровней сложности.
Переработка кода
Что касается структуры кода, необходимо было определиться, как мы будем работать с тем кодом, который нужно менять. По-крупному, код, подлежащий переработке, можно разделить на три вида:
Код, который можно сделать кроссплатформенным сразу. К примеру, то, что можно переложить на стандартную библиотеку или boost. С этим всё просто - берём и делаем.
Код, который будет кроссплатформенным, но потом. «Потом» может быть по разным причинам. Где-то нужно провести исследование и выбрать библиотеку, но на данный момент мы не готовы это сделать. Где-то, нужно дождаться реализации другого функционала. Какой-то функционал не является первостепенным, реализовывать его долго, и мы не хотим сейчас тратить на него время. Для того, чтобы продолжить работать над модулем мы «заглушаем» подобный код.
Платформозависимый код. Этот код в любом случае останется, и каким-то образом придётся его разделять. Подход с разделением кода макросами мы решили не использовать, т.к. с ними код становится перегруженным и не читаемым, поэтому разделяем подобный код на различные ОС-специфичные реализации.
Подходы к переработке
Разделение платформозависимого кода: в целевой папке исходников создаём папку Impl, а в ней папки Windows и Linux. В папке Impl создаём заголовочный файл, в нём под namespace impl декларируем метод и реализуем его под Windows и Linux. Обращение к нему выглядит примерно так: impl::SetWaitCursor(). И программист сразу понимает, что работает с платформоспецифичной реализацией. Исходник в Cmake мы подключаем через переменную SYSDIR, которую, соответственно, меняем под целевую платформу в CompileOptions.cmake. В итоге всё выглядит очень просто и понятно:
CompileOptions.cmake
# System specific dir for implementation
if(WIN32)
set(SYSDIR "Windows")
else()
set(SYSDIR "Linux")
endif()
CmakeLists.txt
set(${this_target}_SRC_GROUP_IMPL
"../Sys/Impl/${SYSDIR}/FileUtils.cpp"
"../Sys/Impl/FileUtils.h"
)
Допустим, по каким-то причинам нам не требуется реализовывать метод под Linux прямо сейчас, или решение ещё не готово, но продолжать работу нужно. Тогда пишем заглушку с пустым методом, в который добавляем типизированный комментарий и ASSERT. Типизированный комментарий позволяет не потерять эту заглушку и реализовать её, когда необходимо. ASSERT под Linux у нас выводит стек вызовов.
namespace impl
{
//------------------------------------------------------------------------------
/**
*/
//---
void WaitCursor()
{
//TODO Linux : реализовать WaitCursor (курсор ожидания)
K3D_ASSERT(false);
}
}
Когда необходимо «глушить» не один метод, а крупный функционал, реализованный во множестве файлов, в том числе заголовочных, приходится поступать иначе. В структуре проекта создаём папку Stubs, в ней создаём папку для конкретного механизма, который нам сейчас мешает. Имя папки даём осмысленное, чтобы потом не запутаться. В неё выносим исходники с минимальной реализацией и пустыми методами — главное, чтобы компилировалось. Заводим переменную в CМake c таким же названием, как папка, и под неё ветвим исходники. Таким образом, все заглушки лежат в одном месте, мы их никогда не потеряем, всегда сможем вернуться к ним и реализовать. И при этом не упираться в определённый функционал, а продолжать компилировать то, что можем на данный момент.
if (WITH_WIDGETS)
set(${this_target}_SRC_GROUP_D3WINDOWS
${${this_target}_SRC_GROUP_D3WINDOWS}
"../K3dDoc/ManipulatorsForWindow3D.cpp"
…
…
)
else()
set(${this_target}_SRC_GROUP_D3WINDOWS
${${this_target}_SRC_GROUP_D3WINDOWS}
"${WIDGETS_STUBS_DIR}/ManipulatorsForWindow3D.cpp"
…
…
)
endif()
Всё это даёт возможность осуществить наш многопроходный подход, который, в свою очередь, позволяет не упираться в частности, а прогнозируемо получать готовый функционал в соответствии с теми приоритетами, которые нам важны.
Мы можем увидеть результат раньше, запуститься и начать отлаживать код под Linux’ом. Можно увидеть и избавиться от избыточных зависимостей и дублирования кода.
Этапы портирования
Какие этапы портирования мы прошли на данный момент и что делаем сейчас.
Первый этап — это выпиливание старого кода, который остался после 17-й версии. С неиспользуемым кодом всё просто - есть группа классов и методов, они нигде не создаются и нигде не вызываются, мы их находим и удаляем. Но была более сложная проблема — это «холостой» код, который по разным причинам, остался после перевода интерфейса на WPF. Например, старая панель параметров, которая, как выяснилось, создавалась, принимала события, как-то их обрабатывала, но на результат работы у пользователей это не влияло. Соответственно, нужно было найти такой код, убедиться, что он действительно холостой, и аккуратно удалить его. В итоге нам удалось выпилить несколько сотен тысяч строк Windows-специфичного кода.
Все внешние зависимости были перемещены в пакетный менеджер — Conan.
Далее нам было необходимо сформировать заготовку. Чтобы её получить, нам нужно было аккуратно, слой за слоем, срезать всё второстепенное, что на данный момент мешало и не интересовало. Сначала отрезали API. Раньше КОМПАС без API скомпилироваться не мог. Мы отрезали все диалоги, убрали второстепенный функционал вроде автосохранения, слежения за файлами, печати. В результате обнажилась базовая часть, составляющая ядро КОМПАСа. Из неё мы начали вытаскивать атомарные единицы, с которыми удобно работать. Для этого пришлось развязать очень много зависимостей, накопившихся за 30 лет. Удалось сформировать набор функциональных модулей, составляющих базовую часть. Так появился модуль, отвечающий за работу с потоком ввода/вывода, модуль настроек, отрисовщик и так далее. В это же время другая часть команды удаляла MFC из каркаса приложения. Убрали зависимость рабочих областей от MFC, реализовали шину данных.
Всё это время мы по сути набирали детали пазла. Когда их набралось достаточно, мы из этого пазла сложили заготовку, которая сперва запустилась только под Windows. Следующим шагом направили усилия на компиляцию этих модулей и получили заготовку под Linux.
Изначально это был простой 2D-viewer. Мы компилировали, срезая по максимуму все углы.
Запустили и начали потихоньку наращивать функционал: добавили работу с hot-точками, работу процессов редактирования и многое другое. Начали работы по текстовому редактору и 3D, также начиная с открытия файла и далее постепенно наращивая функционал и привлекая к этой работе большее количество программистов.
Сейчас мы находимся на этапе портирования текстового редактора и спецификации, занимаемся печатью, пишем новый отрисовщик для вторичных представлений. Когда решим эти задачи, сможем вернуться к 2D и 3D и добавить то, чего там не хватает. Постепенно КОМПАС под Linux из заготовки превратится в полноценную САПР.
Андрей Пилюгин, ведущий инженер-программист.