
Cmake - building portable applications for Mac Os X and Windows

Sooner or later, the moment comes when applications from our laboratories, full of wonders, delicious libraries and beautiful frameworks begin to ask in the big world, on the computers of ordinary people who are not sophisticated in magic. On their computers, it’s not just that our newfangled frameworks and development tools are not standing there, you won’t even find a simple compiler during the day with fire. Our application cannot live without libraries that are so rare in the wild, it will wither away without them, never see a white light for it ...
But not everything is so sad.
Usually in such cases, developers create the so-called bundles, which contain all the necessary libraries and plugins to run the application. Most often they are created either using scripts or IDE-dependent tools, or even with your hands.
If you never crawled out of the bounds of Visual Studio, then for happiness you don’t need much, all the recipes for creating installers have long been known and tested. But if you are a fan of cross-platform software, then the difficulties begin. Often this all leads to the appearance of a small grove of crutches to create bundles ready for distribution without any hint of universality. But there are ways to avoid this.
For many years, the most popular build method for cross-platform applications is cmake, which has support for a bunch of platforms and compilers, Qt, can create installers and dmg's, and much more. Just about cross-platform creation of bundles I want to tell. To do this, cmake 2.8 introduced the excellent fixup_bundle utility
Utility Description
The fixup_bundle utility polls the application and an additional list of libraries (most often plug-ins) for external dependencies, after which it looks for them in the directory list that we prepared and copies the libraries in advance and sets the RUNPATH value, where necessary, so that the application and plugins can use them take advantage of. At the final stage, the utility checks the resulting bundle for external dependencies, if everything is bad, then it reports an error.
Project preparation
The easiest way to work is with the result of the make install command. In this case, with the correct creation of installation goals, you get a well-structured directory, which fixup_bundle will be easy to set against.
Below is my version of the installation paths for various types of purposes.
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()
Usage example
install(TARGETS client
RUNTIME DESTINATION ${BINDIR}
LIBRARY DESTINATION ${LIBDIR}
ARCHIVE DESTINATION ${LIBDIR}
BUNDLE DESTINATION .
)
Similarly, you need to wrap all installed files and goals so that you can change the paths for new platforms in a simple way. This greatly facilitates the creation of bundles by encapsulating all the features of the platforms.
Plugin Installation
All dependencies for libraries and binaries will be found and registered automatically, provided that they are in the search paths. But since we managed to build the application, it is logical to assume that we have all these ways on hand! More complicated with plugins and libraries that are loaded dynamically. Until you start the program, you will never be sure what else it needs for correct operation. Unfortunately, this is not the best option. But we are application developers, and we know what plug-ins are used by our application that has not yet been published, so we can write code to find them and add them to the installation in approximately the following way: After that, we can simply list those Qt plug-ins that are really needed . And by make install, they will be all in the right place for us.
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()
list(APPEND QT_PLUGINS
bearer/qgenericbearer
...
imageformats/qtiff
iconengines/qsvgicon
)
In a good way, for distributing software through package managers it is necessary that during normal work of make install all external libraries should not touch, therefore it is best to enable the bundle assembly mode as a separate option in the configurator.
option(CREATE_BUNDLE "Create application bundle then install" ON)
Running fixup_bundle
if (CREATE_BUNDLE) From the given piece of code, it is quite obvious how fixup_bundle works. Ultimately, the make install command is enough to get the bundle. And cpack-based installers will already become a matter of technology, but this is a topic for another discussion. Well, a small example in the form of a progress indicator for qml. The application uses QtComponents Desktop. If you are interested, you can try to run it on Windows or Mac Os X Lion. The sample code is on github , there is also a set of macros for cmake, which makes creating bundles extremely simple and convenient. Win build Mac build (Lion only)
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()

