📜 ⬆️ ⬇️

Cmake - we collect portable applications for Mac Os X and Windows

image
Sooner or later, the moment comes when applications from our laboratories full of wonders, tasty libraries and beautiful frameworks begin to ask in the big world, on the computers of ordinary people not versed in magic. On their computers, it’s not that our new-fangled frameworks and development tools aren’t worth it, you can’t even find a simple compiler there in the afternoon. Our application cannot live without libraries, which are so rare in the wild, it will wither without them, never see the white light ...
But not everything is so sad.

Usually in such cases, the developers create the so-called bundles, which contain all the necessary libraries and plug-ins to run the application. Most often they are created either with the help of scripts or IDE-dependent tools, or even with their hands.
If you have never climbed out of Visual Studio, then you don’t need much to be happy, all the recipes for creating installers have long been known and tested. But if you are an adherent of cross-platform software, then 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 way to build cross-platform applications is cmake, which has support for heaps of platforms and compilers, can Qt, can create installers and dmg'ski and much more. Just about cross-platform creating bundles I want to tell. To do this, cmake 2.8 has a wonderful 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 searches for them in the list of directories that we prepared in advance and copies the libraries and sets the RUNPATH value where it is necessary for the application and the plugins to 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 with the result of the command run install. In this case, with the proper creation of the goals of the installation, you get a well-structured directory, which will already be easy to set fixup_bundle.
Below is my version of 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 targets, so that you can change the paths for new platforms in a simple way. This makes it very easy to create bundles by encapsulating all the features of the platforms.

Installing plugins


All dependencies for libraries and binaries will be found and spelled automatically, provided that they are in the search paths. But once we managed to build the application, it is logical to assume that we have all these ways in our hands! More difficult with plugins and libraries that are dynamically loaded. Until you start the program you will never be sure what else is needed for it to work correctly. Unfortunately, this is not the best option. But we are application developers and we know what kind of plugins our application has not yet released, so we can write code to search for them and add them to the installation goals in the following way:
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()


After that, you can simply list those Qt plugins that are really needed. And according to make install, they will be all in the right place for us.

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


On good, for distribution of software through package managers, it is necessary that during normal work of install install, all external libraries do not touch, therefore it is best to enable the bundle build mode with a separate option in the configurator.

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


Running 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()


From the above piece of code it is quite obvious how fixup_bundle works. In the end, the make install command is enough to get the bundle. And cpack-based installers will already be 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 running it on Windows or Mac Os X Lion.
image
image

The sample code on github is , there is also a set of macros for cmake, which makes creating bundles extremely simple and convenient.

Win build
Mac build (Lion only)

Source: https://habr.com/ru/post/132313/


All Articles