Pull to refresh

Cmake — собираем переносимые приложения для Mac Os X и Windows

Reading time 4 min
Views 16K
image
Рано или поздно наступает тот момент, когда приложения из наших лабораторий, полных чудес, вкусных библиотек и красивых фреймворков начинают проситься в большой мир, на компьютеры простых людей, не искушённых в магии. На их компьютерах не то, что не стоят наши новомодные фреймворки и средства разработки, там даже простого компилятора днём с огнем не сыщешь. Не может наше приложение жить без библиотек, которые так редко встречаются в дикой природе, зачахнет оно без них, никогда не видать ему белого света…
Но не всё так печально.

Обычно в таких случаях разработчики создают так называемые бандлы, которые содержат в себе все необходимые библиотеки и плагины для запуска приложения. Чаще всего их создают или при помощи скриптов или зависимых от IDE инструментов или вовсе руками.
Если вы никогда не вылезали за пределы Visual Studio, то вам для счастья особенно многого и не надо, все рецепты создания инсталляторов уже давно известны и обкатаны. Но если вы приверженец кроссплатформенного ПО, то тут начинаются сложности. Зачастую это все приводит к появлению небольшой рощицы из костылей для создания готовых к распространению бандлов без каких-либо намеков на универсальность. Но есть способы этого избежать.
Уже много лет самым популярным способом сборки для кроссплатформенных приложений является cmake, который имеет поддержку кучи платформ и компиляторов, умеет Qt, умеет создавать инсталляторы и dmg'шки и многое другое. Как раз о кроссплатформенном создании бандлов я и хочу рассказать. Для этого в cmake 2.8 появилась прекрасная утилита fixup_bundle

Описание утилиты


Утилита fixup_bundle опрашивает приложение и дополнительный список библиотек(чаще всего плагинов) на предмет наличия внешних зависимостей, после чего она ищет их в списке директорий, который мы заранее приготовили и копирует библиотеки и устанавливает значение RUNPATH, где это необходимо, чтобы приложение и плагины смогли ими воспользоваться. На заключительном этапе утилита проверяет получившийся бандл на наличие внешних зависимостей, если всё плохо, то она сообщает об ошибке.

Подготовка проекта


Проще всего работать с результатом выполнения команды make install. В этом случае, при правильном создании целей установки, получится хорошо структурированный каталог, на который уже будет легко натравливать fixup_bundle.
Ниже приведен мой вариант путей установки для различных типов целей.
if(WIN32)
set(BUNDLE_NAME ${_name}.exe)
set(BINDIR bin)
set(BUNDLE_PATH "${CMAKE_INSTALL_PREFIX}/${BINDIR}/${BUNDLE_NAME}")
set(LIBDIR lib${LIB_SUFFIX})
set(SHAREDIR share)
set(PLUGINSDIR bin)
set(IMPORTSDIR ${BINDIR})
set(RLIBDIR ${BINDIR})
elseif(APPLE)
set(BUNDLE_NAME ${_name}.app)
set(BUNDLE_PATH "${CMAKE_INSTALL_PREFIX}/${BUNDLE_NAME}")
set(BINDIR ${BUNDLE_NAME}/Contents/MacOS)
set(LIBDIR ${BINDIR})
set(RLIBDIR ${BUNDLE_NAME}/Contents/Frameworks)
set(SHAREDIR ${BUNDLE_NAME}/Contents/Resources)
set(PLUGINSDIR ${BUNDLE_NAME}/Contents/PlugIns)
set(IMPORTSDIR ${BINDIR})
else()
set(BUNDLE_NAME ${_name})
set(BINDIR bin)
set(BUNDLE_PATH "${CMAKE_INSTALL_PREFIX}/${BINDIR}/${BUNDLE_NAME}")
set(LIBDIR lib${LIB_SUFFIX})
set(RLIBDIR ${LIBDIR})
set(SHAREDIR share/apps/${_name})
set(PLUGINSDIR ${LIBDIR}/plugins/)
set(IMPORTSDIR ${LIBDIR}/imports)
endif()


Пример использования

install(TARGETS client
RUNTIME DESTINATION ${BINDIR}
LIBRARY DESTINATION ${LIBDIR}
ARCHIVE DESTINATION ${LIBDIR}
BUNDLE DESTINATION .
)


Аналогичным образом нужно завернуть все устанавливаемые файлы и цели, дабы можно было менять пути для новых платформ простым образом. Это очень облегчает создание бандлов, инкапсулируя все особенности платформ.

Установка плагинов


Все зависимости для библиотек и бинарников будут найдены и прописаны автоматически, при условии, что они есть в путях поиска. Но раз нам удалось собрать приложение, то логично предположить, что все эти пути у нас на руках есть! Сложнее с плагинами и библиотеками, которые подгружаются динамически. Пока не запустишь программу никогда не будешь уверен, что же ей ещё нужно для корректной работы. К сожалению, это не самый лучший вариант. Но мы же разработчики приложения и знаем какие же плагины использует наше пока ещё не вышедшее в свет приложение, поэтому мы можем написать код для их поиска и добавления в цели установки примерно таким образом:
macro(DEPLOY_QT_PLUGIN _path)
get_filename_component(_dir ${_path} PATH)
get_filename_component(name ${_path} NAME_WE)
string(TOUPPER ${CMAKE_BUILD_TYPE} _type)
if(${_type} STREQUAL "DEBUG")
set(name "${name}${CMAKE_DEBUG_POSTFIX}")
endif()

set(name "${CMAKE_SHARED_LIBRARY_PREFIX}${name}")
set(PLUGIN "${QT_PLUGINS_DIR}/${_dir}/${name}${CMAKE_SHARED_LIBRARY_SUFFIX}")
#trying to search lib with suffix 4
if(NOT EXISTS ${PLUGIN})
set(name "${name}4")
set(PLUGIN "${QT_PLUGINS_DIR}/${_dir}/${name}${CMAKE_SHARED_LIBRARY_SUFFIX}")
endif()

#message(${PLUGIN})
if(EXISTS ${PLUGIN})
message(STATUS "Deployng ${_path} plugin")
install(FILES ${PLUGIN} DESTINATION "${PLUGINSDIR}/${_dir}" COMPONENT Runtime)
else()
message(STATUS "Could not deploy ${_path} plugin")
endif()
endmacro()


После чего можно будет просто перечислить те Qt плагины, которые реально нужны. И по make install'у они окажутся все в нужном нам месте.

list(APPEND QT_PLUGINS
bearer/qgenericbearer
...
imageformats/qtiff
iconengines/qsvgicon
)


По хорошему, для распространения софта через менеджеры пакетов нужно, чтобы при обычной работе make install'а все внешние библиотеки не трогались, поэтому лучше всего включать режим сборки бандла отдельной опцией в конфигураторе.

option(CREATE_BUNDLE "Create application bundle then install" ON)


Запуск fixup_bundle

if(CREATE_BUNDLE)
set(APPS ${BUNDLE_PATH})
list(APPEND DIRS
${QT_LIBRARY_DIR}
${CMAKE_INSTALL_PREFIX}/${LIBDIR}
)
...
deploy_qml_modules(${QML_MODULES})
deploy_qt_plugins(${QT_PLUGINS})

INSTALL(CODE "
file(GLOB_RECURSE QTPLUGINS
\"\${CMAKE_INSTALL_PREFIX}/*${CMAKE_SHARED_LIBRARY_SUFFIX}\")
include(BundleUtilities)
fixup_bundle(\"${APPS}\" \"\${QTPLUGINS}\" \"${DIRS}\")
" COMPONENT Runtime)
endif()


Из приведенного куска кода вполне очевидно, как работает fixup_bundle. В конечном итоге для получения бандла достаточно команды make install. А инсталляторы на основе cpack'а уже станут делом техники, но это тема отдельного разговора.

Ну и небольшой пример в виде индикатора прогресса для qml. В приложении используются QtComponents Desktop. Кому интересно, можете попробовать его запустить на Windows или Mac Os X Lion.
image
image

Код примера на github'е, там же набор макросов для cmake'а, который делает создание бандлов крайне простым и удобным.

Win build
Mac build(Lion only)
Tags:
Hubs:
+30
Comments 15
Comments Comments 15

Articles