A really simple guide to packaging your PyQt application with cx_Freeze

Python is great for writing programs that run on your own machine or deploy to a web server, but when you want to distribute your applications to friends or customers, things can get very annoying very quickly.

– “It doesn’t work. I don’t know how to run this.”
– “Ok, did you install the Python interpreter?”
– “No, what’s that?”
– “You have to download it from www.python.org. Get the 2.7 version.”
– “Yeah, it’s ok. I’ll just use something else.”

We’ve all been there. If you’re going to distribute your software to people who aren’t Python programmers, you had better package it in a friendly way.

My preferred solution is cx_Freeze. Unlike py2exe, it is cross platform; you can use it to build packages for Windows, OSX and Linux. This post will walk through how to package a simple PyQt4 GUI application for all three platforms. The sample application is a Tetris clone I found in the PyQt4 tutorial on www.zetcode.com.

First, go here, copy the full version of the game and save it in a file called tetris.py.

Make sure you have cx_Freeze installed:

Now go to the directory where you saved tetris.py and run the quickstart command to generate a scaffold setup.py. This is a distutils setup script that tells cx_Freeze how to package your application.

You will be prompted for some information. When it asks for the “Python file to make executable from”, type the name of the script that is the main entry point of your application. In this case it’s tetris.py. The generated setup.py will look something like this:

Not very PEP8, but we can let that pass. Let’s walk through this script to see what is going on.

Build options control what Python packages and modules, and what non-Python files (such as assets) are included in the packaged application. cx_Freeze tries to figure out what is needed on its own, but you may need to manually specify some stuff here if you are using dynamic module imports anywhere in your program.

The “packages” and the “excludes” keys are included in the automatically generated buildOptions dictionary. The main other key that you might need to add are “includes” and “include_files”. ”includes” takes a list of modules that need to be included, and “include_files” takes a list of non-Python files, e.g.

The next interesting line is:

On Windows, GUI applications require a different Python base. They must be executed with the pythonw.exe interpreter, or a command prompt will open and remain open for the duration of the program.

If we read further in the generated setup.py, we see:

An instance of the cxFreeze.Executable class must be instantiated for the file that is the main entry point of your program. In general, you will only have one executable, but you can have more if, for instance, you are packaging a suite of command line tools.

The final section of the file contains the call to the setup function, passing in the buildOptions dictionary and the other options you specified when you ran cxfreeze-quickstart, such as the program name, version and description.

For this tetris.py script, we do not need to modify the autogenerated setup.py. You can go ahead and build the package by running

This will create a new build/ directory below your project. Below that, it will create another directory prefaced by exe. and followed by the name of your current platform. It does this so that the build command can be run on multiple platforms without overwriting.

Inside the platform subfolder, you will see your executable.

There’s a little gotcha that I should note here. When you run the build command, it will complete even if certain imports could not be found. Sometimes it won’t matter, but you should review the output of the command to make sure that everything necessary is included.

The build command will dump everything out into the platform folder. When packaging applications for Linux I recommend that you just distribute a .tar.gz of this, but for OSX and Windows, you should make use of the platform-specific packaging commands.

On OSX, you have the option of building a .dmg or a .app, by executing one of these at the prompt:

On Windows, you can build a .msi package as follows:

Unfortunately, you can’t build packages for one platform on another, so you can only build Windows packages on Windows and OSX packages on OSX, but the setup.py is the same across platforms.

Before I go, I should say something about accessing files inside your packaged application. When your packaged executable is running, the global __file__ variable is not set. If you try to grab the current path using os.path.dirname(__file__) it won’t work. You need to use os.path.dirname(sys.executable) .
Luckily, when your packaged application is running, a “frozen” attribute in sys is set. You can use this fact to grab a handle to files regardless of whether the packaged version of the program is running or not.

The cx_Freeze documentation lists this function as a starting point.

You might need to modify this function slightly, as it assumes that your data files are stored on the same level as your executable, not in a subdirectory.

That’s it! After working through this post, you should have more than enough information to start packaging and distributing your creations.

Download Mastering Decorators

Mastering_decorators_cover

Enjoyed this article? Join the newsletter and get Mastering Decorators - a gentle 22-page introduction to one of the trickiest parts of Python.

Weekly-ish. No spam. Unsubscribe any time. Powered by ConvertKit
  • Luis Padron

    Getting this error: [Errno 2] No such file or directory: ‘/usr/local/opt/pyqt/lib/python3.5/site-packages/PyQt4/libQtCore.dylib’

    Using your exact tetris file and your setup.py file, file runs fine from console. Can’t seem to build this however

    Edit: Fixed using this http://sourceforge.net/p/cx-freeze/mailman/message/31930573/

    • Evan Dempsey

      Yes, I ran into that problem on OSX before, but I fixed it and forgot to mention it when I wrote this post. Thanks for adding the Sourceforge link.

  • Interesting post.

    But should it be getattr(…, True)

    ?

  • Sunny Lee

    — figured it out myself. was a dumb question 🙂