Python PySide Qt Pyinstaller Apps on OSX

Ongoing notes about my experience.

Bottom line: don’t use Pyinstaller on OSX with PySide yet (but you can use it with OSX and PyQt, or Windows and PySide?)  As of this writing, Pyinstaller suffers bugs with PySide on OSX that it doesn’t suffer with PyQt.  Specifically, because PySide installs itself in /usr/lib on OSX, Pyinstaller fails to find, or finds too many, dependencies.

You’ve written an app in Python language using PySide bindings which uses Qt framework.  You develop on a Linux machine.  You want to port to other desktop platforms, including Max OSX.

Pyinstaller is a tool that bundles your app.  (There are alternative packaging tools like py2app, py2exe, macdeployqt, freeze, pyside-assistant etc. but AFAIK Pyinstaller has traction and covers more platforms.)

Bundles means puts everything needed into one file.  Also called packaging.  Note the difference in philosophy on different platforms: on Linux, a package does NOT include everything, but knows what other packages it depends on, and downloads and installs them as necessary.  On OSX, and Windows, an app bundle (package) is totally self contained (except for standard platform, system libraries?), and won’t download and install other packages.

Pyinstaller for Linux is strange in that it uses the non-Linux philosophy: it bundles everything into one package, having no dependencies.  It doesn’t make it any easier for users,  since they don’t care which philosophy is used (either SHOULD work reliably.) But Pyinstaller makes it easier for the developer, because they don’t need to worry about the two philosophies on different platforms.

Cross packaging

Pyinstaller is not a cross packager: you must be on the OS for which you are packaging, and the development environment must include all the libraries and Python packages that your app needs.  (It can be a virtual machine.  For example, you could be on a Linux box running Windows in a VirtualBox.  But your Windows virtual machine must have all the libraries and Python packages installed that your app needs.)  Pyinstaller will find those packages and bundle them up; the user will not need to recreate your steps in setting up the development environment.

Using Pyinstaller in the primary development environment

If you are developing on Linux (and your program is working) then presumably everything you need is installed.  You should be able to install and use Pyinstaller.

Except! If you are using an IDE (integrated development environment) like PyDev plugin in Eclipse IDE.  In that case, the IDE may play tricks with the PYTHON_PATH to help your app find dependent packages.  For example, my app used other Python packages that I had written that were separate linked projects in Eclipse.  Pyinstaller was unable to find them.  So I needed to package up (Python packaging: > python setup.py sdist)  those projects and install them (>python setup.py install) on my machine in the standard place.  In other words, make your machine look like a user’s machine without an IDE who was figuring out  the dependencies themselves, and downloading Python packages in order.

Then Pyinstaller worked fine.

Moving to OSX

On another platform, you first need to reconstruct your primary development environment. For example, download and install:

  • (Python comes with OSX)
  • Qt
  • PySide
  • your own dependent Python packages (that you wrote)
  • any other Python packages not included with OSX
  • Pyinstaller (the very latest, dev version since bugs for OSX are more likely fixed there.  I encountered a Pyinstaller bug with “qt_menu.nib not found” that was fixed in the dev version of Pyinstaller)

Then Pyinstaller SHOULD work (and it seems to for me, the jury is still out.)

Pyinstaller works but your app may not.  Especially for apps that use low-level details of the framework, like handling events in the event loop, your app may not work on other platforms.

Crashes on OSX

My app crashes with a seg fault.

A dialog appears titled “Problem Report for myApp”.   It says it quit unexpectedly (user friendly terms for crashed.)  Choose the “Details” button.  It will show you a stack trace.  Note the app is multithreaded.

My program also gets many warnings on the console: “..you may have two sets of Qt binaries..”

Enabling more meaningful stack trace from Python

Your stack trace likely includes many calls inside the Python interpreter.  They won’t be very meaningful unless you are using a Python interpreter built for debugging.  How to build a debugging version of Python is documented on the web.  But I decided to go back to basics.

Hello world

“Hello world” is a tiny program used for teaching and testing.   It is a minimal test.  When you report a bug, or are exploring a bug, you often reduce your program to the smallest program that exhibits the buggy behaviour.   I hacked together this hello world:

import sys
from PySide import QtCore, QtGui
from PySide.QtGui import QApplication, QMessageBox

def main():
    a = QApplication(sys.argv)
    m = QMessageBox(QMessageBox.Information, "Hello", "World")
    m.exec_()

if __name__=="__main__":
    main()

It opens a dialog box, using Qt and PySide.

This program does not crash or give warnings about “two copies”.  However, I found that if you omit the second import, it does crash when you exit the program.  This suggests that Pyinstaller is not quite figuring out the dependencies reliably.

At this point, I decided that the OSX platform is not receiving all the love it should:

  • from Apple for not making it easier for open source developers
  • from PySide for not installing itself so  that Pyinstaller could determine dependencies as on other platforms
  • from Pyinstaller for not hard-coding exceptions on the OSX platform for its dependency determination algorithm

I decided to move on to test Pyinstaller and PySide on a more popular platform (Windows.)  In other words, pick low hanging fruit, least effort for the most reward.

To install gdb on OSX

If you can’t see your problem in the stack trace,  you may need a debugger, like gdb, to examine the stack trace.

  • get an Apple ID so you can use the App Store
  • install Xcode (the official Apple IDE) free, in the store
  • start Xcode and choose “Xcode>Preferences>Downloads”, and choose the “Install” button next to  “Command Line Tools”

(gdb is apparently not part of OSX, and not part of Xcode until you perform this secondary install step from within Xcode.)

(I started to download gdb from the gnu site, and quickly ran into dependency hell: .configure told me gcc was not installed. I figure the Apple Xcode package is best.)

(You DON’T want to learn Xcode.  The learning curve is steep.  There is a reason you are writing in Python.  You want to think about objects and not the tools.  You DO want to learn Xcode.  It is pretty and comprehensive.  The Objective-C language derives from an object-oriented, high-level language SmallTalk but also derives from low-level C.  It supports the mobile platform iOS.)

To be continued…

Testing

You should test your bundle on a clean machine.  It may work on your development environment only because your app finds packages there that were not properly bundled.  Another place where a virtual machine is handy.

Final packaging

Pyinstaller only creates a single executable (or a directory), but does not create an archive suitable for distribution (compressed, signed, with the proper suffix, etc.) through official, one-click-to-install, channels.  (You could distribute the single executable to trusting and computer knowledgeable users.)

Advertisements

5 thoughts on “Python PySide Qt Pyinstaller Apps on OSX

  1. I have a Pyside python app.that works fine with Pyinstaller under Windows. When porting to Mac the app itself works fine but the packaged version produced by Pyinstaller goes all to heck. Any recommendations for a Pyinstaller alternative for Mac?

    Thanks

  2. My recommendation is to switch to PyQt. That is, the problem is not in the freezing, but in the binding to Qt? I could be wrong.

    I *seem* to have succeeded with the toolchain: OSX, Qt5, Python 2.7, PyQt5, Pyinstaller. (But until I retest on a clean machine, I can’t be sure.)

    I have the opposite problem, with the toolchain: Windows Vista, Qt5, Python2.7, PyQt5, Pyinstaller. The problem I am struggling with currently: PyQt5 does not ship in a version for Windows and Python2.x (only Python3.3.) So I am struggling to build PyQt5 myself.

    The whole context of ‘administering a cross-platform toolchain’ is a nightmare. There are too many combinations, too much flux. So my goal is to go with the latest tools, except for Python (Qt5, PyQt5, Python2.6, PyQt5, Pyinstaller daily build) in hopes that when I do get it working, it will be supported (by the tool builders) for a while. (The reason I am not using Python3.3 is that PyInstaller doesn’t support it yet!)

    I DO like to support open source, but in the PyQt/PySide case, I have found that having commercial support is valuable, and that PySide has some quirks (failure to serialize enums) that result from it’s open source nature (volunteers, with their own agendas.)

  3. Bootchk,

    Do you know if it is possible to compile PySide Qt/Python code in a way that it can’t be decompiled after?

    PyInstaller and py2exe seems to embed source code, so it would be easy to read it.

    • One thing you can do is compile from .pyo instead of .pyc. (I think it is a command line option for the python interpreter, something like -o.) I suppose a determined hacker could still reverse compile the .pyo.)

      I am not too concerned about it. I think I can write a better program in Python than I could in a language that is not so easy to reverse engineer. I would rather ship a program and have someone interested enought to reverse engineer it, than not to ship at all.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s