Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CMake MacOS X bundle with BundleUtiliies for Qt application

I am a CMake beginner and have an issue with creation of an Qt application bundle for MacOS X. Let's consider a simple widget "helloworld" app in only one main.cpp file.

// main.cpp
#include <QApplication>
#include <QLabel>

int main(int argc, char** argv)
{
    QApplication app(argc,argv);
    QLabel lbl("Hello");
    lbl.show();
    return app.exec();
}

The CMakeLists.txt file is also simple.

# CMakeLists.txt
cmake_minimum_required( VERSION 3.0 )
project( QtBundle )    
set( CMAKE_INCLUDE_CURRENT_DIR ON )
set( CMAKE_AUTOMOC ON )

set( SOURCES main.cpp )    
find_package( Qt5Widgets REQUIRED )

add_executable( ${PROJECT_NAME} MACOSX_BUNDLE ${SOURCES} )    
qt5_use_modules( ${PROJECT_NAME} Widgets )

I run cmake .. -DCMAKE_PREFIX_PATH=/path/to/Qt5.5.1/ and it generates Makefile in the build directory.

Then I run make and have QtBundle.app directory as I wanted and QtBundle.app/Contents/MacOS/QtBundle executable, OK.

But when I launch it I get:

This application failed to start because it could not find or load the Qt platform plugin "cocoa".

Reinstalling the application may fix this problem.
Abort trap: 6 

As far as I understand that error is occurred because application bundle doesn't have any Qt stuffs (Framework libs and plugins), so I run macdeployqt and it populates bundles directory with a lot of files in Framework and PlugIns folders and application is able to run and relocate to another system.

It partially solves the problem but I want to populate bundle with CMake and BundleUtilities and without macdeployqt tool.

Unfortunately I didn't find any good and simple example for Qt5 deployment with BundleUtilities.

Could someone help me to modify my 'helloworld' example in such way that CMake automatically creates ready-to-deploy bundle?

Thanks in advance.

Main question: how to use CMake BundleUtilities to get a relocatable application?

like image 742
Oleh Pomazan Avatar asked Feb 24 '16 20:02

Oleh Pomazan


1 Answers

Add the code below to CMakeLists.txt. The most challenging thing is to figure out, what plugins do you need, find their names and then properly specify paths for BundleUtilities' fixup_bundle().

install_qt5_plugin() macro locates plugin by name. It will only find plugin for Qt module already found. In this case Qt5::QCocoaIntegrationPlugin is plugin in Qt5Gui module, which is found as dependency for Qt5Widgets by find_package(Qt5 COMPONENTS Widgets REQUIRED). Macro generates install() command for plugin and calculates full path to installed plugin. The latter we'll pass (see QT_PLUGIN variable) to fixup_bundle().

Notes:

  1. We create and install qt.conf file, so plugin can be found, when application starts.
  2. APPS variable specifies path to bundle, not to executable inside it.
  3. Filling DIRS is very important. Note, how it uses CMAKE_PREFIX_PATH.
  4. Printing APPS, QT_PLUGINS and DIRS is optional yet very useful.
  5. One should manually copy/install only those dynamic libraries (including plugins), that aren't referenced from app. Qt platform plugin is such dynamic library.

Dependencies lookup and fixing happens on installation. To get relocatable bundle in necessary location one may configure with CMAKE_INSTALL_PREFIX pointing to that location and then build install target.

I prefer creating .dmg file with

mkdir build
cd build
cmake ..
cpack -G DragNDrop

Contents to add to CMakeLists.txt is from here:

set(prefix "${PROJECT_NAME}.app/Contents")
set(INSTALL_RUNTIME_DIR "${prefix}/MacOS")
set(INSTALL_CMAKE_DIR "${prefix}/Resources")

# based on code from CMakes QtDialog/CMakeLists.txt
macro(install_qt5_plugin _qt_plugin_name _qt_plugins_var _prefix)
    get_target_property(_qt_plugin_path "${_qt_plugin_name}" LOCATION)
    if(EXISTS "${_qt_plugin_path}")
        get_filename_component(_qt_plugin_file "${_qt_plugin_path}" NAME)
        get_filename_component(_qt_plugin_type "${_qt_plugin_path}" PATH)
        get_filename_component(_qt_plugin_type "${_qt_plugin_type}" NAME)
        set(_qt_plugin_dest "${_prefix}/PlugIns/${_qt_plugin_type}")
        install(FILES "${_qt_plugin_path}"
            DESTINATION "${_qt_plugin_dest}")
        set(${_qt_plugins_var}
            "${${_qt_plugins_var}};\$ENV{DEST_DIR}\${CMAKE_INSTALL_PREFIX}/${_qt_plugin_dest}/${_qt_plugin_file}")
    else()
        message(FATAL_ERROR "QT plugin ${_qt_plugin_name} not found")
    endif()
endmacro()

install_qt5_plugin("Qt5::QCocoaIntegrationPlugin" QT_PLUGINS ${prefix})
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/qt.conf"
    "[Paths]\nPlugins = ${_qt_plugin_dir}\n")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/qt.conf"
    DESTINATION "${INSTALL_CMAKE_DIR}")

# Destination paths below are relative to ${CMAKE_INSTALL_PREFIX}
install(TARGETS ${PROJECT_NAME}
    BUNDLE DESTINATION . COMPONENT Runtime
    RUNTIME DESTINATION ${INSTALL_RUNTIME_DIR} COMPONENT Runtime
    )

# Note Mac specific extension .app
set(APPS "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}.app")

# Directories to look for dependencies
set(DIRS "${CMAKE_BINARY_DIR}")

# Path used for searching by FIND_XXX(), with appropriate suffixes added
if(CMAKE_PREFIX_PATH)
    foreach(dir ${CMAKE_PREFIX_PATH})
        list(APPEND DIRS "${dir}/bin" "${dir}/lib")
    endforeach()
endif()

# Append Qt's lib folder which is two levels above Qt5Widgets_DIR
list(APPEND DIRS "${Qt5Widgets_DIR}/../..")

include(InstallRequiredSystemLibraries)

message(STATUS "APPS: ${APPS}")
message(STATUS "QT_PLUGINS: ${QT_PLUGINS}")
message(STATUS "DIRS: ${DIRS}")

install(CODE "include(BundleUtilities)
    fixup_bundle(\"${APPS}\" \"${QT_PLUGINS}\" \"${DIRS}\")")

set(CPACK_GENERATOR "DRAGNDROP")
include(CPack)
like image 194
ivan_onys Avatar answered Sep 16 '22 17:09

ivan_onys