1. Anthony Tuininga
  2. cx_Freeze
  3. Issues
Issue #33 new

Handle Qt 5 platform plugins

Thomas Kluyver
created an issue

Qt 5 seems to introduce a new concept of platform plugins, dynamically loaded to integrate the program with the operating system. So on Windows it needs a platforms\qwindows.dll plugin for the application to work at all. The abstraction is briefly documented here.

This should be simple to handle automatically - we just need a hook that will check which platform it's on and copy the relevant plugin file to the target directory. We need to find out whether QtCore requires the platform plugin, or if it's only when QtGui is in use.

Prompted by this StackOverflow question.

Comments (23)

  1. Thomas Kluyver reporter

    I see we've gained a couple of watchers. To help get this fixed, could you provide some information:

    • What files are in your Qt plugins/platforms/ directory? (e.g. on Ubuntu, I see libqlinuxfb.so libqminimal.so libqxcb.so)
    • If you freeze a script that only tries to load QtCore (not QtGui), does it work without the platform plugin?
  2. Alex Rattray

    In response to questions asked and unasked, in case it helps:

    I'm on OS X 10.8, using Qt5. I added a qt_plugins dir to my include_files, which contains a dir named platforms, which contains libqcocoa.dylib and libqminimal.dylib. (I believe I didn't load the installed Qt's plugins folder, but rather one in my source tree, because the presence of libqcocoa_debug.dylib was causing problems... but those may have been imagined).

    Then, in macdist.py, I modified the code that opened and closed qt.conf to also write:

    ; Qt Configuration file
    [Paths]
    Plugins = MacOS/qt_plugins
    

    which solved the issue... most of the way. When I used brew'd Python and Qt, many dylibs were still being loaded twice, causing it to crash. I had to manually change things with, eg:

    subprocess.call('''install_name_tool -change /usr/local/Cellar/qt5/5.0.2/lib/QtPrintSupport.framework/Versions/5/QtPrintSupport @executable_path/QtPrintSupport build/MyApp.app/Contents/MacOS/qt_plugins/platforms/libqcocoa.dylib''', shell=True)
    

    for QtPrintSupport, QtWidgets, QtGui, and QtCore.

    I know this may not be directly related to the issue at hand but wanted to post in case it helps others.

    I no longer had to perform this step when using the binary-installed python and Qt installed from source. (I switched back for reasons I can't recall, I think unrelated).

  3. Thomas Kluyver reporter

    Thanks Alex.

    I think on Macs it's best tested with Python & Qt installed through Macports, though I imagine any work to improve the situation with other setups is welcome.

  4. Martin Fitzpatrick

    Thanks Thomas. I've tried this with Qt5.1 (Homebrew) on OSX and it doesn't seem to be working - none of the plugins are copied over to the app. But looking at it I'm also on PyQt5 so perhaps that is the problem?

  5. Thomas Kluyver reporter

    Oh drat, I didn't realise that they'd changed the package name. All of the hooks written for PyQt4 won't fire when you import PyQt5.

    Can you try doing a search/replace in cx_Freeze/hooks.py, PyQt4 -> PyQt5, then have another go at freezing your application?

  6. Martin Fitzpatrick

    Thanks Thomas that's great. I've reinstalled cx_Freeze from that branch and it is indeed pulling in the PyQt5 libraries now during the build process. However attempting to run the built application returns the slightly confusing error message:

    This application failed to start because it could not find or load the Qt platform plugin "cocoa".
    Available platform plugins are: cocoa, minimal, offscreen.
    

    The file libqcocoa.dylib is in the .app package under <app>/Contents/MacOS/platforms Any ideas?

    [Edit] I notice I'm also receiving errors about duplicate libraries - one of which is the libqcocoa.dylib one: objc[83530]: Class QNSMenu is implemented in both /usr/local/Cellar/qt5/5.1.1/plugins/platforms/libqcocoa.dylib and <app>/Contents/MacOS/platforms/libqcocoa.dylib. One of the two will be used. Which one is undefined. So the nonsensical error may be due to this. I'll have a go with the install_name_tool and see if I can fix this up.

  7. Thomas Kluyver reporter

    Thanks. I'm not a Mac user, so I can't offer much help with that. It should create an empty qt.conf file in <app>/Contents/Resources - I thought this told it where to look for plugins.

  8. Martin Fitzpatrick

    That might be the problem then - that file isn't created. However, creating and adding the paths to that file doesn't seem to change anything.

    It might be worth noting that the plugins are ending up in the <app>/Contents/MacOS folder instead of <app>/Contents/MacOS/plugins where the default (no qt.conf) settings would expect them.

    I'll see if I can come up with anything. Thanks for the help!

  9. Thomas Kluyver reporter

    I think that's correct - the individual plugin directories are copied into the build directory without the plugins/ directory.

    It wouldn't create qt.conf if it fails to find qt_menu.nib for some reason - you may want to look into that.

  10. Martin Fitzpatrick

    It looks as if there is no qt_menu.nib file in Qt5. It is in the source (under the plugins/platforms folder) but isn't in the built version - perhaps built into the platform dylibs?

    I've managed to get the app starting up by issuing export DYLD_LIBRARY_PATH=. at the command line before running the app (this was taken from this SO http://stackoverflow.com/questions/9426820/qt-nib-issues-on-macos question). So it looks as though qt_menu.nib isn't required and if the qt.conf can be placed correctly for Qt5 it may just work (assuming that does effectively the same as the DLYD_LIBRARY_PATH export in pointing it where to look). Is that possible?

  11. Thomas Kluyver reporter

    Ah, OK. I think qt_menu.nib was always there with Qt 4, which was why we were relying on it. I don't know what it was, though from the name I guess it was something to do with the global menu bar.

    I'm sure it is possible to set qt.conf up correctly for this, but I don't know what's needed for that - I haven't really worked with Qt 5 yet. If you can work it out, please do open a pull request.

  12. Martin Fitzpatrick

    Just to update: I've spent some more time on this today and the qt_menu.nib appears to be at least part of the problem. However that is solvable with --qt-menu-nib=/usr/local/Cellar/qt5/5.1.0/src/qtbase/src/plugins/platforms/cocoa/qt_menu.nib on the command line (for Homebrew Qt5.1.0/PyQt5) to pull it from the source where it exists. Once that's done the qt.conf file is created correctly.

    There remains an issue with QtWidgets and QtPrintSupport and the cocoa platform plugin is not loading. I think at this stage the latter problem is related the former two - if I can figure out why those two are not being rewritten by setRelativeReferencePaths() this might be solved.

  13. Martin Fitzpatrick

    Success! The final piece of the puzzle was that while the platform plugins are copied to the Contents/MacOS/ folder under platforms the setRelativeReferencePaths function only updates the paths in the root of that folder. By adding the following to the top of the function it correctly processes the plugins:

        # Add platform folders
        files.extend( [os.path.join('platforms',fn) for fn in os.listdir( os.path.join(self.binDir, 'platforms') )] )
    

    (I also did the same for the imageformats plugin.)

    Once this is added and the qt_menu.nib is pointed to on the command line a functional build completes fine and works.

    Does this look like a reasonable fix for the problem? I'm happy to submit a pull request if it looks viable. The qt_menu.nib issue is unfortunately unresolved without the command-line switch - but perhaps it will be possible to get Homebrew/etc. to copy those files where expected or find an alternative marker for Qt5.

  14. Thomas Kluyver reporter

    pull request #23 aimed to do setRelativeReferencePaths for any subdirectories of binDir, I think. It's not got in yet because of other changes in there that Anthony didn't think were correct, IIRC. If you want to submit a PR to do that, I think it could probably get accepted.

    With the .nib file, at present, find_qt_menu_nib tries PyQt4 and PySide, but not PyQt5 - maybe if you update it to check for PyQt5 as well, it will find the nib file? Worth a try, at least.

  15. Martin Fitzpatrick

    Just to update - the latest version of Qt (5.0.2-beta) does not have the qt_menu.nib file anywhere to be seen - and the folder structure is rearranged. However, passing in any old folder will do. I used python setup.py bdist_mac --qt-menu-nib=/usr/local/Cellar/qt5/5.2.0-beta1/plugins/platforms/ and it worked fine.

  16. Thomas Kluyver reporter

    I think the current code in macdist conflates finding qt_menu.nib with detecting that the thing being frozen is a Qt application (see prepare_qt_app()). Explicitly passing any value for qt_menu_nib overrides that, so it believes that it's freezing a Qt app.

    Do you want to make a PR to separate those two things out? It should be a simple bit of refactoring, but I can't easily test it, not being on a Mac.

  17. Colin Duquesnoy

    Hi,

    What is the status of this issue?

    The fix mentioned by Martin Fitzpatrick works fine on development systems where all the required libraries have been installed but it seems to fail on users configuration that do not have qt5 installed. Are you aware of that or am I missing something?

    See this issue for more details on the crash the user got: https://github.com/OpenCobolIDE/OpenCobolIDE/issues/39.

    And here is a link to the setup script used to freeze the application: https://github.com/OpenCobolIDE/OpenCobolIDE/blob/develop/freeze.py

  18. Colin Duquesnoy

    It turns out the issue was related to how I installed Qt5 and PyQt5 on my dev machine (using homebrew). Installing qt5 using the online installer and compiling pyqt5 from source did solve the issue.

    Sorry for the noise.

  19. Patrick Stinson

    Was there ever any resolution of this for qt5 and pyqt5 installed with homebrew? The platform plugs are copied to Contents/MacOS, but not loaded because libqcocoa.dylib still references QtPrintSupport from the qt5 install folder.

    Upon inspection with otool, it looks like platforms/libqcocoa.dylib still references

    /usr/local/Cellar/qt5/5.3.2/lib/QtPrintSupport.framework/Versions/5/QtPrintSupport
    

    instead of

    @executable_path/QtPrintSupport
    

    and does not copy the QtPrintSupport to the app folder. The result is that after renaming my qt install folder (/usr/local/Cellar/qt5) to 'break' the installation, the app bundle will not load and complains with the familiar error message:

    This application failed to start because it could not find or load the Qt platform plugin "cocoa".
    
    Available platform plugins are: cocoa, minimal, offscreen.
    
    Reinstalling the application may fix this problem.
    Abort trap: 6
    

    Alas, everything seems to work fine (albeit with some artifacts on the toolbar buttons) so long as QtPrintSupport is copied and all of the appropriate calls to install_name_tool are made for QtPrintSupport and platforms/libqcocoa.dylib.

    Thoughts?

    Thanks for this great tool, guys!

    UPDATE: A workaround is to import QtPrintSupport in the application, but it might be better if it made it into the code?

  20. Log in to comment