Skip to main content

Ralsina.Me — Roberto Alsina's website

Shipping your PyQt app for windows

I have writ­ten about this in the past, with the gen­er­al con­clu­sion be­ing "it's a pain in the as­s".

So, now, here is how it's done.

  1. Start with a work­ing PyQt ap­­pli­­ca­­tion. In this ex­am­­ple, I will use de­vi­­cen­­zo.py most­­ly be­­cause:

    1. It is a work­ing PyQt ap­­­pli­­­ca­­­tion.

    2. It us­es a big chunk of PyQt

    3. It's easy to test

  2. Now you need a set­up.py. Here's one that work­s, with ex­ten­sive com­m­ments.

# We will be using py2exe to build the binaries.
# You may use other tools, but I know this one.

from distutils.core import setup
import py2exe

# Now you need to pass arguments to setup
# windows is a list of scripts that have their own UI and
# thus don't need to run in a console.

setup(windows=['devicenzo.py'],
      options={

# And now, configure py2exe by passing more options;

          'py2exe': {

# This is magic: if you don't add these, your .exe may
# or may not work on older/newer versions of windows.

              "dll_excludes": [
                  "MSVCP90.dll",
                  "MSWSOCK.dll",
                  "mswsock.dll",
                  "powrprof.dll",
                  ],

# Py2exe will not figure out that you need these on its own.
# You may need one, the other, or both.

              'includes': [
                  'sip',
                  'PyQt4.QtNetwork',
                  ],

# Optional: make one big exe with everything in it, or
# a folder with many things in it. Your choice
#             'bundle_files': 1,
          }
      },

# Qt's dynamically loaded plugins and py2exe really don't
# get along.

data_files = [
            ('phonon_backend', [
                'C:\Python27\Lib\site-packages\PyQt4\plugins\phonon_backend\phonon_ds94.dll'
                ]),
            ('imageplugins', [
            'c:\Python27\lib\site-packages\PyQt4\plugins\imageformats\qgif4.dll',
            'c:\Python27\lib\site-packages\PyQt4\plugins\imageformats\qjpeg4.dll',
            'c:\Python27\lib\site-packages\PyQt4\plugins\imageformats\qsvg4.dll',
            ]),
],

# If you choose the bundle above, you may want to use this, too.
#     zipfile=None,
)
  1. Run python set­up.py py2exe and get a dist fold­er full of bi­na­ry good­ness.

And that's it. Ex­cept of course, that's not it.

What this will do is cre­ate a bi­na­ry set, ei­ther a fold­er full of things, or a sin­gle EXE file. And that's not enough. You have to con­sid­er at least the fol­low­ing:

  1. Put ev­ery­thing in re­­source files: im­ages, qss files, icon­s, etc. Ev­ery file your app need­s? Put it in a re­­source file and load it from there. That way you don't have to care about them if you go the "one ex­e" road.

  2. Com­pile .ui files to .py (same rea­­son)

  3. Fig­ure out if you use Qt's plu­g­in­s, and make them work. This in­­­cludes: us­ing Phonon, us­ing Qt­SQL, and us­ing any im­age for­­mats oth­­er than PNG.

Af­ter you have that, are you done? NO!

Your win­dows us­er will want an in­stall­er. I am not go­ing to go in­to de­tail­s, but I had a good time us­ing Bi­tRock­'s In­stall­Builder for Qt. It's a nice tool, and it work­s. That's a lot in this field.

But is that al­l? NO!

You have to take care of the Vis­ual Stu­dio Run­time. My sug­ges­tion? Get a copy of the 1.1MB vcre­dis­t_x86.exe (not the larg­er one, the 1.1MB one), and ei­ther tell peo­ple to in­stall it man­u­al­ly, or add it to your in­stall­er. You are legal­ly al­lowed (AFAIK) to re­dis­tribute that thing as a whole. But not what's in it (un­less you have a VS li­cense).

And we are done? NO!

Once you run your app "in­stalled", if it ev­er prints any­thing to stder­r, you will get ei­ther a di­a­log telling you it did, or worse (if you are in ay­thing new­er than XP), a di­a­log telling you it can't write to a log file, and the app will nev­er work again.

This is be­cause py2exe catch­es stderr and tries to save it on a log­file. Which it tries to cre­ate in the same fold­er as the bi­na­ry. Which is usu­al­ly not al­lowed be­cause of per­mis­sion­s.

So­lu­tion? Your app should nev­er write to stder­r. Write an ex­cepthook and catch that. And then re­move stderr or re­place it with a log file, or some­thing. Just don't let py2exe do it, be­cause the way py2exe does it is bro­ken.

And is that it?

Well, ba­si­cal­ly yes. Of course you should get 4 or 5 dif­fer­ent ver­sions of win­dows to test it on, but you are pret­ty much free to ship your app as you wish. Oh, mind you, don't up­load it to down­load­s.­com be­cause they will wrap your in­stall­er in a larg­er one that in­stalls bloat­ware and crap.

So, there you go.