Application Deployment

Deploying Python applications can be problematical because of the relatively complex nature of a Python installation. Ideally the deployed application would be completely self-contained and not conflict with anything already installed on the target system.

Another issue for commercial PyQt applications is the desire to physically prevent users accessing the underlying PyQt modules for themselves. Doing so means they become developers and need their own commercial Qt and PyQt licenses.

This section is a set of suggestions and not a formal prescription of how PyQt applications should be deployed. We would appreciate any feedback and suggestions for improvement.

Hiding the PyQt Modules (UNIX/Linux)

In this section we describe how to build a version of the Python interpreter with the SIP and PyQt modules built in rather than dynamically loaded. The advantage of doing this is that, when combined with the techniques described below for producing self-contained executables, it is difficult for the end user to gain access to the underlying modules to use in their own applications.

None of the steps below require any special user privileges, and do not affect any existing Python, Qt or PyQt installation.

Note that a further improvement would be achieved by using a static version of the Qt library. This isn't described because it requires installation specific modifications to the Makefiles generated by qmake. (qmake doesn't handle the dependencies of a static Qt library very well.)

Bootstrap Python

Unpack the Python source code.

Build Python as normal, but specify an installation prefix. For example:

./configure --prefix=/path/to/deploy
make
make install

Build SIP

Unpack the SIP source code.

Run build.py with the -k flag (plus whatever flags you would normally use) using the Python interpreter you have just created. You should also use the -b flag to install the sip executable in a convenient location. For example:

/path/to/deploy/bin/python build.py -k -b /path/to/deploy/bin

Build the static version of SIP by running:

make
make install

Build PyQt

Unpack the PyQt source code.

Run build.py with the -k and -p flags (plus whatever flags you would normally use) using the Python interpreter you have just created. You should also use the -b flag to install the pyuic and pylupdate executables in a convenient location. For example:

/path/to/deploy/bin/python build.py -k -p /path/to/deploy/bin/sip -b /path/to/deploy/bin

Edit qtsql/qtsqlcmodule.cpp (or qtsql/qtsqlhuge0.cpp if you used the -c flag with build.py) and comment out the definition of sipName_Connection.

Edit qtxml/qtxmlcmodule.cpp (or qtxml/qtxmlhuge0.cpp if you used the -c flag with build.py) and comment out the definition of sipName_hasFeature.

Note the above two steps are needed because of a mis-feature of SIP that will be fixed in a future release, but will require an incompatible change.

Build the static version of PyQt by running:

make
make install

Rebuilding Python

Return to the Python source directory.

Edit Makefile.pre.in and add the macros PYQT_OBJS and PYQT_LIBS.

PYQT_OBJS contains the path names of all the object files that were created when you built the static SIP and PyQt modules, ie. the outputs of ls siplib/*.o and ls qt*/*.o from the SIP and PyQt source directories respectively.

PYQT_LIBS contains the command line flags needed to link the various Qt libraries. For example:

PYQT_LIBS= -L/usr/local/qt/lib -lqscintilla -lqassistantclient -lqui -lqt-mt

Add $(PYQT_OBJS) to the definition of LIBRARY_OBJS.

Add $(AR) cr $@ $(PYQT_OBJS) to the commands of the $(LIBRARY) target.

Add $(PYQT_LIBS) before $(SYSLIBS) in the $(BUILDPYTHON) target.

Edit Modules/config.c.in and add the following after ADDMODULE MARKER 1:

        extern void initlibsip(void);
        extern void initlibqtc(void);
        extern void initlibqtcanvasc(void);
        extern void initlibqtextc(void);
        extern void initlibqtglc(void);
        extern void initlibqtnetworkc(void);
        extern void initlibqtsqlc(void);
        extern void initlibqttablec(void);
        extern void initlibqtuic(void);
        extern void initlibqtxmlc(void);

Add the following after ADDMODULE MARKER 2:

        {"libsip", initlibsip},
        {"libqtc", initlibqtc},
        {"libqtcanvasc", initlibqtcanvasc},
        {"libqtextc", initlibqtextc},
        {"libqtglc", initlibqtglc},
        {"libqtnetworkc", initlibqtnetworkc},
        {"libqtsqlc", initlibqtsqlc},
        {"libqttablec", initlibqttablec},
        {"libqtuic", initlibqtuic},
        {"libqtxmlc", initlibqtxmlc},

Reconfigure, build and install Python again using the same flags as before.

Hiding the PyQt Modules (Windows)

The same basic approach used on UNIX/Linux should work on Windows except that it is probably easier to use a normal Python installation and only produce a modified Python DLL. MSVC++ also seems to have some issues compiling a static version of the SIP module against a DLL version of the Qt library, so you should also use a static version of the Qt library.

Producing Self-contained Executables

This section outlines how to produce self-contained executables based on Gordon McMillan's Installer package (http://www.mcmillan-inc.com/installer_dnld.html).

The use of upx (http://upx.sourceforge.net) for generating compressed executables is recommended. Limited testing has shown that it can reduce the size of your final application by as much as a half.

Installing Installer

Unpack the Installer package. On Windows, no further steps are required. On UNIX/Linux, the following configuration steps are required.

Edit source/linux/Make.py and insert '$(PYQT_LIBS)' before '$(SYSLIBS)'.

Run the following:

cd source/linux
/path/to/deploy/bin/python Make.py
make
cd ../..
/path/to/deploy/bin/python Configure.py

An Example

Using Installer involves two simple steps: generating a specification then generating the application executable itself.

On UNIX/Linux:

/path/to/deploy/bin/python Makespec.py --onefile --upx --strip /path/to/tut14.py
/path/to/deploy/bin/python Build.py tut14/tut14.spec

On Windows:

\path\to\python Makespec.py --onefile --upx \path\to\tut14.py
\path\to\python Build.py tut14\tut14.spec

The executable can be found in the tut14 sub-directory. On a Linux system the executable is about 6.5Mbytes. On a Windows system the executable is about 2.5Mbytes.

Caveats

Applications with external data (eg. external images, translation files) probably need some additional steps.

When the --onefile option is used with Installer, a temporary directory is created when the application is run that contains the shared libraries needed by the application. If the application terminates normally then this directory is removed automatically. However there appears to be a bug (or maybe it's a feature) in the current version of Installer (v5b5) where the directory is not removed if the application is terminated through sys.exit().