[PyKDE] Deploying PyQt Applications

Phil Thompson phil at riverbankcomputing.co.uk
Mon Jul 21 17:30:00 BST 2003


Attached is a document describing how to convert a PyQt application into a 
single binary executable. It also means that users of commercial PyQt 
applications would find it difficult to accidentally become PyQt developers.

This is a very manual process which I'm sure can be improved. At the moment it 
only covers UNIX/Linux but a Windows version will be very similar and I'll 
get round to it soon.

Feedback and comments appreciated.

Phil
-------------- next part --------------
INTRODUCTION

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 document describes how to convert a normal PyQt application into a single
binary executable which is both easy to deploy and prevents easy access to
the underlying Qt API.

The technique is to first create a Python interpreter that has the SIP and
PyQt modules built in rather than dynamically loaded.  Secondly, Gordon
McMillan's Installer package (http://www.mcmillan-inc.com/installer_dnld.html)
is used to create the final application.

Note that this document (for the moment at least) only covers UNIX/Linux.
However, all the tools are also available for Windows - we just haven't got
around yet to working through the process on that platform.

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

We would appreciate any feedback and suggestions for improvement.


BOOTSTRAP PYTHON

Unpack the Python source.

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.

For SIP v3.8 and later, 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

For SIP v3.7 and earlier edit siplib/siplib.pro.in and remove "SIP_MAKE_DLL".
Also edit build.py and change "dll" to "staticlib".  Run build.py (with
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 -b /path/to/deploy/bin

Build the static version of SIP by running:

	make
	make install


BUILD PyQt

Unpack the PyQt source.

For PyQt v3.8 and later, 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

For PyQt v3.7 and earlier edit sip/qt*mod.sip, replace "@BL_DLL@" with
"staticlib" and remove "SIP_MAKE_MODULE_DLL". Run  build.py with the -p 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 pyuic and
pylupdate executables in a convenient location.  For example:

	/path/to/deploy/bin/python build.py -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 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.


INSTALL UPX

upx (http://upx.sourceforge.net) is a utility for generating compressed
executables.  It's use is optional but limited testing has shown that it can
reduce the size of your final application by 50%.


INSTALL INSTALLER

Unpack the Installer source.

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

To configure and build Installer, run:

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

Note that all of the above steps only need to be done once, unless you change
the version of Python, SIP, PyQt or Qt you are deploying.


AN EXAMPLE

Using Installer involves two simple steps.  First generate a specification:

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

Next generate the application executable itself:

	/path/to/deploy/bin/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.


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().


More information about the PyQt mailing list