Qt 5.0 and 'failed to load platform plugin "cocoa"'



  • I'm on Mac OS 10.8.3. After porting my Qt app from Qt 4.4 to Qt 5.0, I was getting the following fatal error immediately when I would create a Mac bundle and then run the bundle app:

    Failed to load platform plugin "cocoa"

    To debug this, I tried reading http://qt-project.org/doc/qt-5.0/qtdoc/deployment-mac.html and related pages, but found them only partially helpful. I also tried macdeployqt but couldn't get it to work. All of this needs much better documentation.

    Here is what I did to get my app to work. Using

    @
    export DYLD_PRINT_LIBRARIES=1
    @

    before running my app, I found that the library causing the above error was libqcocoa.dylib. Typically one is able to find all dylib and framework dependencies of an executable using the "otool -L" command, recursively following the graph of dependencies, but libqcocoa is not found this way (perhaps because it's a plugin?). But if you run "strings" on QtCore, you'll find references to libqcocoa there. Note that simple Qt apps don't need libqcocoa, but mine did. So I need to add this library to my bundle.

    If qtdir is the dir where Qt 5.0 is installed (in my case it was /Users/paul/more_stuff/qt5.0.0/5.0.0/clang_64) and appdir is the dir for the app bundle being put together (in my case it was name.app) then the cause of the above error was that Qt's runtime library was looking for qtdir/plugins/platforms/libqcocoa.dylib but I want to eliminate all dependencies on qtdir, which exists only on my build machine. I need to make my app more self-contained.

    First, I changed my C++ code to replace the line

    @
    QApplication app(argc, argv);
    @

    with

    @
    QDir dir(argv[0]); // e.g. appdir/Contents/MacOS/appname
    assert(dir.cdUp());
    assert(dir.cdUp());
    assert(dir.cd("PlugIns")); // e.g. appdir/Contents/PlugIns
    QCoreApplication::setLibraryPaths(QStringList(dir.absolutePath()));
    printf("after change, libraryPaths=(%s)\n", QCoreApplication::libraryPaths().join(",").toUtf8().data());
    QApplication app(argc, argv);
    @

    This changes the plugin directory to be relative to appdir (no longer relative to qtdir). Note that it must be done before the QApplication constructor, because that is where the link error above was occuring.

    I also needed to copy libqcocoa and its extra dependencies into my bundle and patch up the identities and reference paths in the dylibs. otool -L is handy for figuring out these dependencies. In my case, there was only two dependencies due to plugins: libqcocoa.dylib and QtPrintSupport.framework. The commands came down to the following:

    @

    install libqcocoa library

    mkdir -p $appdir/Contents/PlugIns/platforms
    cp $qtdir/plugins/platforms/libqcocoa.dylib $appdir/Contents/PlugIns/platforms

    fix its identity and references to others

    install_name_tool -id @executable_path/../PlugIns/platforms/libqcocoa.dylib $appdir/Contents/PlugIns/platforms/libqcocoa.dylib
    install_name_tool -change $qtdir/lib/QtPrintSupport.framework/Versions/5/QtPrintSupport @executable_path/../Frameworks/QtPrintSupport.framework/Versions/5/QtPrintSupport $appdir/Contents/PlugIns/platforms/libqcocoa.dylib
    install_name_tool -change $qtdir/lib/QtWidgets.framework/Versions/5/QtWidgets @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets $appdir/Contents/PlugIns/platforms/libqcocoa.dylib
    install_name_tool -change $qtdir/lib/QtGui.framework/Versions/5/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui $appdir/Contents/PlugIns/platforms/libqcocoa.dylib
    install_name_tool -change $qtdir/lib/QtCore.framework/Versions/5/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore $appdir/Contents/PlugIns/platforms/libqcocoa.dylib

    install QtPrintSupport framework

    cp -r $qtdir/lib/QtPrintSupport.framework $appdir/Contents/Frameworks

    fix its identity and references to others

    install_name_tool -id @executable_path/../Frameworks/QtPrintSupport.framework/Versions/5/QtPrintSupport $appdir/Contents/Frameworks/QtPrintSupport.framework/Versions/5/QtPrintSupport
    install_name_tool -change $qtdir/lib/QtWidgets.framework/Versions/5/QtWidgets @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets $appdir/Contents/Frameworks/QtPrintSupport.framework/Versions/5/QtPrintSupport
    install_name_tool -change $qtdir/lib/QtGui.framework/Versions/5/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui $appdir/Contents/Frameworks/QtPrintSupport.framework/Versions/5/QtPrintSupport
    install_name_tool -change $qtdir/lib/QtCore.framework/Versions/5/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore $appdir/Contents/Frameworks/QtPrintSupport.framework/Versions/5/QtPrintSupport
    @

    Piece of cake, right? :-)

    This worked for me.

    Small remaining puzzles:

    1. why does Qt load libqcocoa as a plugin?
    2. is there a way, without running the app, to get a list of the paths to the plugins it will load?

  • Lifetime Qt Champion

    Hi,

    For number 1, it's because it's the implementation of Qt Abstract Platform for OS X (there is one such plugin for each support system)

    For number 2, I don't know, since plugins are loaded at run time



  • Hi paulheckbert,
    Thanks.

    I can confirm "PyQt5 + Qt5.0.2 + Python3.3 + py2app" works fine on your way.
    They were showing same trouble.
    Failed to load platform plugin “cocoa”

    Py2app doesn't include plugins automatically.
    So I copied it to .app manually.
    However, the plugins cause trouble because it doesn't have correct reference to the object which included to .app.

    I wrote these scripts in my setup.py to configure reference.
    Now I can deploy my pyqt app.
    I hope my post help someone who have same trouble :)

    @
    # (1) remove build files

    # (2)  issue setup command
    
    # (3) copy plugin 
    #plugin
    print("copy plugins")
    os.system("cp -r plugins dist/test.app/Contents/PlugIns")
    os.system("cp qt.conf dist/test.app/Contents/Resources/qt.conf") 
    
    # (4) correct dylib references
    
    appPath = "PATH_TO_YOUR_APP"
    qtPath = "PATH_TO_QT_LIB"
    pythonPath = "PATH_TO_PYTHON"
    
    def iid(dylib):
        command = "install_name_tool -id @executable_path/../PlugIns/{dylib} {appPath}/Contents/PlugIns/{dylib}".format(dylib=dylib, appPath=appPath)
        os.system(command)
    
    def icPython(dylib):
        command = "install_name_tool -change {pythonPath}/Python.framework/Versions/3.3/Python @executable_path/../Frameworks/Python.framework/Versions/3.3/Python {appPath}/Contents/PlugIns/{dylib}".format(dylib=dylib, appPath=appPath, pythonPath=pythonPath)
        os.system(command)
        
    def icCore(dylib):
        command = "install_name_tool -change {qtPath}/lib/QtCore.framework/Versions/5/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore {appPath}/Contents/PlugIns/{dylib}".format(dylib=dylib, appPath=appPath, qtPath=qtPath)
        os.system(command)
    
    ....
    
    def update(dylib):
        iid(dylib)
        icPython(dylib)
        icCore(dylib)
        ....
    
    #accessible
    print("# update accessible")
    update("accessible/libqtaccessiblequick.dylib")
    update("accessible/libqtaccessiblewidgets.dylib")
    
    #bearer
    print("# update bearer")
    update("bearer/libqcorewlanbearer.dylib")
    ....
    
    #platform
    print("# update platforms")
    update("platforms/libqcocoa.dylib")   ## <-- you need it
    update("platforms/libqminimal.dylib")
    
    ......
    

    @

    I think you can use same method to PyQt4 trouble.



  • I forgot to write a important thing.
    you also need to configure qt library path before creating QApplication Instance.
    It is almost same to pure Qt5's way.

    @if deploy:
    print("DEPLOY")
    for path in QApplication.libraryPaths():
    QApplication.removeLibraryPath(path)

    filePath = os.path.dirname(os.path.abspath( __file__ )) 
    # abspath returns "APP_DIR/Contents/Resouces". This isn't same to C++ app.
    fileDir = QDir(filePath)
    fileDir.cdUp()
    fileDir.cd("PlugIns")
    appPath = fileDir.absolutePath()
    QApplication.addLibraryPath(str(appPath))
    

    print("INIT: " + str(QApplication.libraryPaths()))
    app = QApplication(sys.argv)
    @

    If you didn't do it, your app will crash.
    Because, it can't find "cocoa" plugin.



  • Thank you so much paulheckbert :D



  • I also experienced this, on my dev machine (where Qt is installed), when in Xcode I turned on sandbox for my app. Sandbox means the OS restricts what the app can read and write. In this case, it denies the app reading the installed Qt libraries, only allowing the app to read libraries (frameworks) from the app's bundle (package.) It would happen later, on a user's deployed machine.

    That almost explains why Qt doesn't already include the apps bundle in the library search path: the sandbox concept is relatively new and unique to the OSX platform. So it's one platform difference that Qt doesn't yet address? On other platforms, at installation time the app's packaged libraries (dependencies) are moved from the app's package to standard directories, or on Linux, downloaded and installed by the package manager.

    (For me, Qt5.3, Python3.4, PyQt5, pyqtdeploy, macdeployqt, and 'qmake -spec macx-xcode foo.pro' to move a Qt project to Xcode5.1, all reasonably work.)



  • yuichi: thanks for the Python snippet.

    How does your app know it is deployed? If 'deploy' is a compile-time flag, I think it makes more sense to not remove the existing path. Then addLibraryPath() prepends. Then the same code base works whether deployed or not.



  • Also,

    @filePath = os.path.dirname(os.path.abspath( file ))@

    does NOT always return something like "APP_DIR/Contents/Resouces". At best it might return something like ..../Contents/MacOS, in other words, the path to the directory which contains the executable in the app bundle.

    Worse, on a sandboxed app, it seems to return:

    @/Users/bootch/Library/Containers/org.Pensool.pensool/Data/:/pensool@

    in other words, to a copy of the app that was placed in a sandbox. I don't understand that path, and your code doesn't seem to work in this case.

    I have yet to try a Python equivalent to Paul's solution:

    @QDir dir(argv[0])@

    And I have seen conflicting advice about whether QCoreApplication.applicationDirPath() will work before an application instance is created. It is a class method, so I don't see why it wouldn't.



  • It seems that argv[0] on a sandboxed app returns just the app name, not a path, e.g. 'myApp' instead of .../Contents/MacOS/myApp. So Paul's solution above doesn't work on a sandboxed app.

    Also, indeed applicationDirPath() prints a warning if it is invoked before a QApplication is instantiated.

    Re the path returned by os.path.dirname(os.path.abspath( file )) , which has a colon in it. That is strange to me. Only Windows paths typically have colons in them. Maybe it is part of Apple's sandboxing technology, and is a form of network path? If so, I'm not sure whether either Python's os.path module or Qt's QDir class deal with it correctly yet.



  • [quote author="bootchk" date="1401976977"]It seems that argv[0] on a sandboxed app returns just the app name, not a path, e.g. 'myApp' instead of .../Contents/MacOS/myApp. So Paul's solution above doesn't work on a sandboxed app.[/quote]

    What version of Qt? I am using 5.3 and I am getting the entire path from argv[0]. However, one interesting thing is that the line:

    QDir dir(argv[0]);

    Will initialize 'dir' with the appname as another folder. Hence, if argv is: /Applications/MyApp/Contents/MacOS/MyApp, QDir will treat that as a folder and you essentially have to cdUp 3 times.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.