Can't use fixup_bundle() to create a portable bundle with Qt

ⅰ亾dé卋堺 提交于 2021-02-07 02:48:28

问题


I already searched this issue on other posts, but nothing so far. So here I am.

I'd like to create a bundle that is portable. Portable as in "I can run it on any OS X machine, even if my required libs (Qt) are not installed". Unfortunately, I can't figure out how to use fixup_bundle() (which seems the right tool for it) to achieve this goal.

Here is my minimal CMake generated C++ project :

main.cpp

#include <QString>
#include <iostream>

int main()
{    
    QString s("Hello, world!");
    std::cout << s.toStdString() << std::endl;
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 2.8.11)

project(test)

# That part because I use a custom build of Qt. That's not the
# relevant part (I guess?)
FILE(GLOB QTROOTS path_to_qt/Qt-4.8.1/osx/bin/qmake)
find_program(QT_QMAKE_EXECUTABLE NAMES qmake PATHS ${QTROOTS})
find_package(Qt4 COMPONENTS QtCore REQUIRED)
include(${QT_USE_FILE})

add_executable(test MACOSX_BUNDLE main.cpp)

target_link_libraries(test ${QT_LIBRARIES})

install(SCRIPT bundle.cmake)

bundle.cmake

INCLUDE(BundleUtilities)
fixup_bundle(test.app "" "")

Here's the resulting test.app structure

test.app
 - Contents
    - Info.plist
    - Frameworks
       - QtCore.framework
          - Versions
             - 4
                - QtCore
    - MacOS
       - test

Everything that is needed seems to be in the bundle. Everything compiles smoothly, runs well, but when I invoke fixup_bundle, that's what I get :

vincent@hpcd0016-lion:tmp/test_bundle/ (0) > make install

[100%] Built target test
Install the project...
-- Install configuration: ""
-- fixup_bundle
--   app='test.app'
--   libs=''
--   dirs=''
-- fixup_bundle: preparing...
-- fixup_bundle: copying...
-- 1/4: *NOT* copying '/Users/vincent/tmp/test_bundle/test.app/Contents/MacOS/test'
-- 2/4: copying 'path_to_qt/Qt-4.8.1/osx/lib//QtCore.framework/Versions/4/QtCore'
-- fixup_bundle: fixing...
-- 3/4: fixing up '/Users/vincent/tmp/test_bundle/test.app/Contents/MacOS/test'
  exe_dotapp_dir/='test.app/Contents/MacOS/'
  item_substring='/Users/vincent/t'
  resolved_embedded_item='/Users/vincent/tmp/test_bundle/test.app/Contents/MacOS/test'

Install or copy the item into the bundle before calling fixup_bundle.
Or maybe there's a typo or incorrect path in one of the args to fixup_bundle?

CMake Error at /Applications/CMake 2.8-11.app/Contents/share/cmake-2.8/Modules/BundleUtilities.cmake:568 (message):
  cannot fixup an item that is not in the bundle...
Call Stack (most recent call first):
  /Applications/CMake 2.8-11.app/Contents/share/cmake-2.8/Modules/BundleUtilities.cmake:656 (fixup_bundle_item)
  bundle.cmake:2 (fixup_bundle)
  cmake_install.cmake:31 (INCLUDE)


make: *** [install] Error 1

There's the dependencies path (given by otool -L) :

vincent@hpcd0016-lion:test.app/Contents/ (0) > otool -L test.app/Contents/MacOS/test     
    test.app/Contents/MacOS/test:
        path_to_qt/Qt-4.8.1/osx/lib//QtCore.framework/Versions/4/QtCore (compatibility version 4.8.0, current version 4.8.1)
        /usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 56.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 169.3.0)

vincent@hpcd0016-lion:tmp/test_bundle/ (0) > otool -L test.app/Contents/Frameworks/QtCore.framework/Versions/4/QtCore
    test.app/Contents/Frameworks/QtCore.framework/Versions/4/QtCore:
        path_to_qt/Qt-4.8.1/osx/lib//QtCore.framework/Versions/4/QtCore (compatibility version 4.8.0, current version 4.8.1)
        /usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.5)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 159.1.0)
        /System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices (compatibility version 1.0.0, current version 41.0.0)
        /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 635.15.0)
        /System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 1.0.0, current version 55010.0.0)
        /usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 52.0.0)
        /usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1094.0.0)
        /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices (compatibility version 1.0.0, current version 53.0.0)

Obviously, fixup_bundle did not fix up the bundle for the binaries still have their ids set to my machine's paths.

What am I doing wrong on this simple example?


回答1:


I added this line at the top of my CMakeLists.txt

set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR})

And that was it.

By default, apparently, CMAKE_INSTALL_PREFIX is set to /usr/local on my machine. If changing it to my current working directory solved the issue, that means CMake was trying to perform some operations on /usr/local (which it is not allowed to do). So why the error message does not mention such a right access error?

I don't know if I haven't read enough documentation, or if the documentation needs some precisions...




回答2:


In addition, I actually had to be even more explicit about the install path (i.e. within the .app).

Like this:

set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR})
install(CODE "
    include(BundleUtilities)
    fixup_bundle(${CMAKE_INSTALL_PREFIX}/MyApp.app \"\" \"\")
" COMPONENT Runtime)

(N.B. no separate SCRIPT but rather embedded CODE - shouldn't make a difference).




回答3:


To respond to timlukins' answer, if you're going to invoke any CMake variables in the install code then you have to be very careful to escape them against early evaluation.

The code you posted will resolve CMAKE_INSTALL_PREFIX immediately, before it embeds your code into the install script. Which means the cmake_install.cmake file will contain:

include(BundleUtilities)
fixup_bundle(/usr/local/MyApp.app "" "")

(Or whatever path you had CMAKE_INSTALL_PREFIX set to when generating the build tree.) That's not what you want, since it makes your build unrelocatable even before it's installed.

You need to protect the CMAKE_INSTALL_PREFIX invocation so it makes it into the install script intact, and will use the install-time value. There's two ways you can do that.

  1. Furious escaping within the quotes...
    install(CODE "
      include(BundleUtilities)
      fixup_bundle(\"\$\{CMAKE_INSTALL_PREFIX\}/MyApp.app\" \"\" \"\")
    " COMPONENT Runtime)
    
  2. (Much easier:) Use a bracket argument...
    install(CODE [[
      include(BundleUtilities)
      fixup_bundle("${CMAKE_INSTALL_PREFIX}/MyApp.app" "" "")
    ]] COMPONENT Runtime)
    

No variable names will be recognized or references replaced inside a bracket argument, so it's safe to use them unescaped. (Generator expressions will still be processed, which can sometimes be quite handy. Not for this, exactly, but I did just think of one way...)

To oarfish's comment question:

How did you solve the problem that qt platform plugin (libqcocoa.dylib) is not bundled with the rest?

Here's how I might handle that. It's lightly tested, seems to at least be on the right track. Though I can't help feeling like there must be an easier way to handle the output paths.

install(CODE [[
  include(BundleUtilities)

  # You could also do this part before the CODE using
  #   install(TARGETS MyApp BUNDLE DESTINATION foo)
  #
  file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/foo"
    TYPE DIRECTORY FILES "$<TARGET_BUNDLE_DIR:MyApp>")

  # This string is crazy-long enough that it's worth folding into a var...
  set (_plugin_file "$<TARGET_FILE_NAME:Qt5::QCocoaIntegrationPlugin>")

  # Ditto the output paths for our installation
  set (_appdir "${CMAKE_INSTALL_PREFIX}/foo/MyApp.app")
  set (_outdir "${_appdir}/Contents/plugins/platforms")

  file(INSTALL DESTINATION "${_outdir}"
    TYPE FILE FILES "$<TARGET_FILE:Qt5::QCocoaIntegrationPlugin>")

  fixup_bundle("${_appdir}" "${_outdir}/${_plugin_file}" "")
]] COMPONENT Runtime)

As soon as you generate the build system, you can read the cmake_install.cmake file that's output into the binary directory for this CMakeLists.txt file, to see if the install(CODE...) produced what you intended.

Destination path

If you're wondering where that ${_outdir} path came from, that's Word of God.

From Qt's "Using qt.conf":

Absolute paths are used as specified in the qt.conf file. All paths are relative to the Prefix. [...] On macOS, the Prefix is relative to the Contents in the application bundle. For example, application.app/Contents/plugins/ is the default location for loading Qt plugins. Note that the plugins need to be placed in specific sub-directories under the plugins directory (see How to Create Qt Plugins for details).



来源:https://stackoverflow.com/questions/17974439/cant-use-fixup-bundle-to-create-a-portable-bundle-with-qt

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!