Recently, I encountered a problem in creating an app bundle using Qt Creator with Qt 5.6, so I posted my question with detail on StackOverflow here.
In this post, I am going to point out the places I got wrong, and some studies.
Scott
Scott is a friend of mine for years, and he is best programmer I’ve ever met in Taiwan. He helped me on this question, and I would like to quote his words here:
Do try to figure out what you did wrong before. Look at the RPATH, install names etc in your executable and update your StackOverflow question with those findings.
Finding out what you did wrong is an important step in understanding a system. This makes your exercise of publishing apps on multiple platforms more meaningful.
@executable_path, @loader_path, @rpath
The first reason I couldn’t build the app build is that I didn’t fully understand the path names used on Mac, and here is my study of @executable_path, @loader_path, and @rpath.
@executable_path
: the folder path of application’s executable
- ex.
/Applications/Foo.app/Contents/MacOS
- useful for frameworks embedded inside the applications
@loader_path
: the folder path of the related plug-in’s code
- ex.
/Library/Application Support/Foo/Plug-Ins/Bar.bundle/Contents/MacOS
- useful for frameworks embedded inside plug-ins
- availabe from Mac OS X 10.4
@rpath
: instructs the dynamic linker to search a list of paths in order to locate the framework
- no need to specify the “install path” using either
@executable_path
or @loader_pat
h, but pass additional flags when building the host application (ex. -rpath @excutable/…/Frameworks or /Library/Frameworks)
- availabe from Mac OS X 10.5
The second reason I was stuck is that otool
didn’t resolve @rpath
names, so I was confused when it always returned me the same thing.
However, Scott wrote another version of otool that resolves the rpaths here. Here are the steps that demostrate the difference:
> otool -L bibi.app/Contents/MacOS/bibi
bibi.app/Contents/MacOS/bibi:
@rpath/QtWidgets.framework/Versions/5/QtWidgets (compatibility version 5.6.0, current version 5.6.0)
@rpath/QtGui.framework/Versions/5/QtGui (compatibility version 5.6.0, current version 5.6.0)
@rpath/QtCore.framework/Versions/5/QtCore (compatibility version 5.6.0, current version 5.6.0)
/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/AGL.framework/Versions/A/AGL (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 120.1.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1226.10.1)
> otool-rpath bibi.app/Contents/MacOS/bibi
/Users/heron/Qt/5.6/clang_64/lib
> macdeployqt ./*.app -verbose=3 -always-overwrite -appstore-compliant
> otool -L bibi.app/Contents/MacOS/bibi
bibi.app/Contents/MacOS/bibi:
@rpath/QtWidgets.framework/Versions/5/QtWidgets (compatibility version 5.6.0, current version 5.6.0)
@rpath/QtGui.framework/Versions/5/QtGui (compatibility version 5.6.0, current version 5.6.0)
@rpath/QtCore.framework/Versions/5/QtCore (compatibility version 5.6.0, current version 5.6.0)
/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/AGL.framework/Versions/A/AGL (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 120.1.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1226.10.1)
> otool-rpath bibi.app/Contents/MacOS/bibi
@executable_path/../Frameworks
macdeployqt
The last reason I failed to understand what’s going on is the output of macdeployqt
, which confused me.
> macdeployqt bibi.app
File exists, skip copy: "bibi.app/Contents/PlugIns/platforms/libqcocoa.dylib"
File exists, skip copy: "bibi.app/Contents/PlugIns/printsupport/libcocoaprintersupport.dylib"
File exists, skip copy: "bibi.app/Contents/PlugIns/imageformats/libqdds.dylib"
File exists, skip copy: "bibi.app/Contents/PlugIns/imageformats/libqgif.dylib"
File exists, skip copy: "bibi.app/Contents/PlugIns/imageformats/libqicns.dylib"
File exists, skip copy: "bibi.app/Contents/PlugIns/imageformats/libqico.dylib"
File exists, skip copy: "bibi.app/Contents/PlugIns/imageformats/libqjpeg.dylib"
File exists, skip copy: "bibi.app/Contents/PlugIns/imageformats/libqtga.dylib"
File exists, skip copy: "bibi.app/Contents/PlugIns/imageformats/libqtiff.dylib"
File exists, skip copy: "bibi.app/Contents/PlugIns/imageformats/libqwbmp.dylib"
File exists, skip copy: "bibi.app/Contents/PlugIns/imageformats/libqwebp.dylib"
WARNING:
WARNING: "bibi.app/Contents/Resources/qt.conf" already exists, will not overwrite.
WARNING: To make sure the plugins are loaded from the correct location,
WARNING: please make sure qt.conf contains the following lines:
WARNING: [Paths]
WARNING: Plugins = PlugIns
However, in Scott’s solution, he gave following additional arguments:
- -verbose=3: see how the rpaths are updated in details (Scott’s log)
- always-overwrite: copy files even if the target file exists, so the first (Scott: I used “always-overwrite” to get predictable results after repeated testing, since the Qt frameworks would be copied into the app bundle.)
- appstore-compliant: skip deployment of components that use private API (Scott: appstore-compliant was just for your convenience)
Test
Testing is one additional thing the made the original question harder to be solved: there’s no easy way to see if my app bundle works on the other machine without Qt installed.
Instead of asking friends to run the app, Scott mentioned that we can use `lsof
at run-time.
> ps aux|grep bibi
heron 21610 0.0 0.5 2632680 40272 ?? S Tue09PM 5:32.80 /Users/heron/Project/bibi/bibi/build-bibi-Desktop_Qt_5_6_0_clang_64bit-Release/bibi.app/Contents/MacOS/bibi
heron 39245 0.0 0.0 2434840 664 s003 R+ 9:31AM 0:00.00 grep --color=auto bibi
> lsof -p 39183 | grep QtCore
bibi 21610 heron txt REG 1,4 6441676 168354669 /Users/heron/Qt-free/5.6/clang_64/lib/QtCore.framework/Versions/5/QtCore
After macdeployqt
, the app bundle no longer needs to link to frameworks outside the bundle:
> ps aux|grep bibi
heron 39352 0.0 0.0 2435864 788 s003 S+ 9:32AM 0:00.00 grep --color=auto bibi
heron 39315 0.0 0.8 2611176 63000 ?? S 9:32AM 0:00.68 /Users/heron/Project/bibi/bibi/bibi/bibi.app/Contents/MacOS/bibi
> lsof -p 39315 | grep QtCore
bibi 39315 heron txt REG 1,4 6017532 171823963 /Users/heron/Project/bibi/bibi/bibi/bibi.app/Contents/Frameworks/QtCore.framework/Versions/5/QtCore
Summary
I would say the biggest problem is that I didn’t know how to read @rpath
, so Scott’s otool-rpath
or lsof
helps eventually.
Reference