pack-panda Installer SIZE

No problem. But don’t expect any magic tricks.

But before we go into configuration details, let’s have a look a the problem: size of the distribution. What can be done to get size down?

(1) Leave away files that aren’t necessary. Either by configuring py2exe or by deleting them by hand.

(2) If you have to distribute a file, then make it as small as possible.

I don’t know a way to determine what files that are really required by an application. You have to try out for your own application. Is it safe to leave away msvcr71.dll, assuming the user will already have it on his system or download it separate? I don’t know. And afaik it is questionable if you are ALLOWED to distribute it, if you don’t OWN a copy of VisualStudio yourself (many py2exe users don’t own VS). See here for example: http://mail.python.org/pipermail/python-dev/2005-February/051396.html.

How to make files small. For example leaving away comments in python code will gain a few bytes. “python -OO” and py2exe “optimize”:2 will remove doc strings. But this is, well, just a few bytes. A better compression for zipped content would help too, for example 7zip. But the real bulk of your distribution are DLL files: libpanda.dll (16.6M), python24.dll(1.8M). And this is where we can gain the most, by compressing DLL files. I mentioned in my post above how to do this (upx compression), and I have already posted this trick here on this forum: https://discourse.panda3d.org/viewtopic.php?t=1629. Here is the link again:

http://upx.sourceforge.net/

That’s all.


“Hello world” with py2exe:

Here the goal is to make the distribution as small as possible, at any cost. The first file is, obviously, our hello world application itself. hello.py:

print 'Hello world!'

Then we need a script for py2exe. I assume all the files are in the same directory, by the way. setup.py:

from distutils.core import setup
import py2exe

INCLUDES = [ ]
EXCLUDES = [ 'Tkinter', 'Tkconstants', 'tcl', 'unicodedat', 're', 'inspect', 'popen2', 'copy', 'string', 'macpath', 'os2emxpath' ]
DLL_EXCLUDES = [ 'MSVCR71.dll' ]

setup( console = [ { 'script' : 'hello.py',
                     } ],
       zipfile = None,
       options = { 'py2exe': { 'optimize' : 2,
                               'unbuffered' : True,
                               'includes' : INCLUDES,
                               'excludes' : EXCLUDES,
                               'dll_excludes': DLL_EXCLUDES,
                              },
                  },
       data_files = [ ],
       )

Setting zipfile to None will make py2exe append the zipped python files to the .exe it generates (default behavior is to create a separate file “library.zip”, or whatever you name it). No gain in size, just one file less.

EXCLUDES is where py2exe is told to leave away unnecessary python modules. We are building for windows, right? So why including macpath or os2emxpath, ntpath will be sufficient (selfish, I know). string and copy? “Hello world” doesn’t need these. Same for the other excluded modules. In any “real” application you will have to keep these modules, though. Important is to exclude Tk stuff.

DLL_EXCLUDES is where flame wars could start. 340k for ‘MSVCR71.dll’, or 162k packed with upx. Well, we go for minimum size.

Finally we need a batch file to automate the build process. build.bat:

echo off

Rem ____py2exe____
python -OO setup.py py2exe -a

Rem ____upx____
cd dist
..\upx203.exe --best *.*
cd ..

Rem ____clean up____
rd build /s /q

Some notes on what build.bat does:

(1) Create a py2exe distribution using setup.py, optimization (-OO) for python compiler, and -a (ascii) tells py2exe that we don’t need encodings. Without -a py2exe will pack some hundred kilobytes of encodings. The result of this step are two directories, build and dist.

(2) Change to the dist directory and pack everything with upx. I used upx version 2.03 here, the current version. Oh, and before I forget: put the upx executable either somewhere in your path or put it in the same directory as setup.py.

(3) We don’t need the build directory. That’s where py2exe collects all the files. For convenience remove this directory after we are done.

Now run build.bat. Here is a snippet from the output, to demonstrate what upx does here in this case:

...
                       Ultimate Packer for eXecutables
    Copyright (C) 1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006
UPX 2.03w       Markus Oberhumer, Laszlo Molnar & John Reiser    Nov 7th 2006

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
     82523 ->     75867   91.93%    win32/pe     hello.exe
   1871872 ->    788992   42.15%    win32/pe     python24.dll
      4608 ->      3584   77.78%    win32/pe     w9xpopen.exe
   --------------------   ------   -----------   -----------
   1959003 ->    868443   44.33%                 [ 3 files ]

Packed 3 files.

The dist directory should look like this:

11/02/2007  11:08    <DIR>          .
11/02/2007  11:08    <DIR>          ..
11/02/2007  11:08            75,867 hello.exe
18/10/2006  07:35           788,992 python24.dll
18/10/2006  07:18             3,584 w9xpopen.exe
               3 Datei(en)        868,443 Bytes

So we have 868,443 bytes => 848k. Zipping up with standard windows zip gives a zip-file of about 776k. For even better results try to zip with 7zip.

7za a -tzip -mx9 "foo.zip" -r

7zip gives 771k, not very impressive.


Panda3D with py2exe:

I assume Panda3D 1.3.2 (Windows), Python 2.4.4 and py2exe 0.6.6.

First, to make py2exe work with Panda3D, I did what this post is suggesting: https://discourse.panda3d.org/viewtopic.php?t=2091 and did start with a setup.py from this post: https://discourse.panda3d.org/viewtopic.php?t=1629 (credit goes to kaweh and aaronbstjohn). To sum it up:

(1) File “direct/init.py”: comment all lines.
(2) Copy or move all directories from “direct/src” to “direct/”
(3) File “pandac/extension_native_helpers.py”, after line 8 add these line:

sys.path.append(sys.prefix)

I don’t know if this is the only way to get py2exe working with Panda3D, and I didn’t spend time on finding out. But it works for me.

Then we need a minimal panda application. Not much, just show a window and wait for escape key to be pressed. game.py:

import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject

class World( DirectObject ):

    def __init__( self ):
        base.setBackgroundColor( 0.1, 0.2, 0.5, 0 )
        self.accept( 'escape', self.exit )

    def exit( self ):
        taskMgr.stop( )

world = World( )
run( )

Now we need a setup.py file. Here it is. Please note that I didn’t exclude any python modules or dll’s here, since I don’t think it is worth to fight for every kilobyte. ‘MSVCR71.dll’ is part of the distribution too. setup.py:

from distutils.core import setup
import py2exe
import os

PANDA_DIR = 'C:\Programme\Panda3D-1.3.2'

setup( windows = [ { 'script' : 'game.py',
                     #'icon_resources' : [ ( 1, 'game.ico' ) ],
                     } ],
       zipfile = None,
       options = { 'py2exe': { 'optimize' : 2,
                               'excludes' : [ 'Tkinter' ],
                              },
                  },

packages = [
        'direct',
        'direct.directbase',
        'direct.showbase',
        'direct.interval',
        'direct.actor',
        'direct.gui',
        'direct.task',
        'direct.controls',
        'direct.directnotify',
        'direct.directtools',
        'direct.directutil',
        'direct.fsm',
        'direct.cluster',
        'direct.particles',
        'direct.tkpanels',
        'direct.tkwidgets',
        'direct.directdevices',
        'direct.distributed',
        'pandac', 
        ],
package_dir = {
        'direct' : os.path.join(PANDA_DIR, 'direct'),
        'direct.directbase' : os.path.join(PANDA_DIR, 'direct/directbase'),
        'direct.showbase' : os.path.join(PANDA_DIR, 'direct/showbase'),
        'direct.interval' : os.path.join(PANDA_DIR, 'direct/interval'),
        'direct.actor' : os.path.join(PANDA_DIR, 'direct/actor'),
        'direct.gui' : os.path.join(PANDA_DIR, 'direct/gui'),
        'direct.task' : os.path.join(PANDA_DIR, 'direct/task'),
        'direct.control' : os.path.join(PANDA_DIR, 'direct/control'),
        'direct.directnotify' : os.path.join(PANDA_DIR, 'direct/directnotify'),
        'direct.directtools' : os.path.join(PANDA_DIR, 'direct/directtools'),
        'direct.directutil' : os.path.join(PANDA_DIR, 'direct/directutil'),
        'direct.fsm' : os.path.join(PANDA_DIR, 'direct/fsm'),
        'direct.cluster' : os.path.join(PANDA_DIR, 'direct/cluster'),
        'direct.particles' : os.path.join(PANDA_DIR, 'direct/particles'),
        'direct.tkpanels' : os.path.join(PANDA_DIR, 'direct/tkpanels'),
        'direct.tkwidgets' : os.path.join(PANDA_DIR, 'direct/tkwidgets'),
        'direct.directdevices' : os.path.join(PANDA_DIR, 'direct/directdevices'),
        'direct.distributed' : os.path.join(PANDA_DIR, 'direct/distributed'),
        'pandac' : os.path.join(PANDA_DIR, 'pandac'), 
        },
       data_files = [ ( 'etc', [ 'etc/Config.prc', ] ),
                      ],
       )

Finally the batch script for building. build.bat:

echo off

Rem ____py2exe____
python setup.py py2exe -a

Rem ____upx____
cd dist
copy avcodec-51-panda.dll ..\build\avcodec-51-panda.dll
..\upx203.exe --best *.*
copy ..\build\avcodec-51-panda.dll avcodec-51-panda.dll
cd ..

Rem ____clean up____
rd build /s /q

One note here: avcodec-51-panda.dll for some reason crashes if compressed with upx. So I save an uncompressed copy first and then put it back into the dist directory after everything has been compressed. An better way would be to call upx for every file that has to be compressed, but I am lazy when it comes to typing scripts.

Run build.bat and have a look at what upx does here:

...
                       Ultimate Packer for eXecutables
    Copyright (C) 1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006
UPX 2.03w       Markus Oberhumer, Laszlo Molnar & John Reiser    Nov 7th 2006

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   2799762 ->   1098898   39.25%    win32/pe     avcodec-51-panda.dll
upx203: etc: IOException: not a regular file -- skipped
   1360668 ->   1354012   99.51%    win32/pe     game.exe
   1101824 ->    278016   25.23%    win32/pe     libp3direct.dll
    151552 ->     70144   46.28%    win32/pe     libp3dtool.dll
    372736 ->    160256   42.99%    win32/pe     libp3dtoolconfig.dll
      5632 ->      4096   72.73%    win32/pe     libp3heapq.dll
  17027072 ->   3918336   23.01%    win32/pe     libpanda.dll
    999424 ->    447488   44.77%    win32/pe     LIBPANDAEAY.dll
   2416640 ->    579072   23.96%    win32/pe     libpandaegg.dll
   2220032 ->    499200   22.49%    win32/pe     libpandaexpress.dll
    237568 ->     69120   29.09%    win32/pe     libpandafx.dll
    229376 ->     89088   38.84%    win32/pe     libpandajpeg.dll
   1269760 ->    275968   21.73%    win32/pe     libpandaphysics.dll
    131072 ->     58880   44.92%    win32/pe     libpandapng13.dll
    192512 ->     83456   43.35%    win32/pe     LIBPANDASSL.dll
    360448 ->     80896   22.44%    win32/pe     libpandatiff.dll
     59904 ->     36352   60.68%    win32/pe     libpandazlib1.dll
    348160 ->    165888   47.65%    win32/pe     MSVCR71.dll
    147456 ->     69120   46.88%    win32/pe     nspr4.dll
   1867776 ->    787456   42.16%    win32/pe     python24.dll
      4608 ->      3584   77.78%    win32/pe     w9xpopen.exe
   --------------------   ------   -----------   -----------
  33303982 ->  10129326   30.41%                 [ 21 files ]

“libpanda.dll” down from 16.6M to 3.8M, and total size down to 30.41% !!! And this is what the dist directory should look like:

11/02/2007  11:57    <DIR>          .
11/02/2007  11:57    <DIR>          ..
29/11/2006  19:31         2,799,762 avcodec-51-panda.dll
11/02/2007  11:53    <DIR>          etc
11/02/2007  11:53         1,354,012 game.exe
29/11/2006  19:58           278,016 libp3direct.dll
29/11/2006  19:32            70,144 libp3dtool.dll
29/11/2006  19:32           160,256 libp3dtoolconfig.dll
29/11/2006  19:58             4,096 libp3heapq.dll
29/11/2006  19:51         3,918,336 libpanda.dll
29/11/2006  19:31           447,488 LIBPANDAEAY.dll
29/11/2006  19:55           579,072 libpandaegg.dll
29/11/2006  19:33           499,200 libpandaexpress.dll
29/11/2006  19:55            69,120 libpandafx.dll
29/11/2006  19:31            89,088 libpandajpeg.dll
29/11/2006  19:57           275,968 libpandaphysics.dll
29/11/2006  19:31            58,880 libpandapng13.dll
29/11/2006  19:31            83,456 LIBPANDASSL.dll
29/11/2006  19:31            80,896 libpandatiff.dll
29/11/2006  19:31            36,352 libpandazlib1.dll
29/11/2006  19:31           165,888 MSVCR71.dll
29/11/2006  19:31            69,120 nspr4.dll
29/11/2006  19:31           787,456 python24.dll
18/10/2006  07:18             3,584 w9xpopen.exe
              21 Datei(en)     11,830,190 Bytes

11,830,190 bytes is 11553k is 11.282M. Zipped up this is about 7.5M. Here we are.

For the final kick you could delete some files by hand, bringing size further down. I don’t recommend doing this, and if you should check if the application still runs on a different machine (which I didn’t do). I found that this (THIS!) Panda3D application starts up without the following files:

libp3dtool.dll
libp3dtoolconfig.dll
LIBPANDAEAY.dll
libpandajpeg.dll
libpandapng13.dll
LIBPANDASSL.dll
libpandatiff.dll
libpandazlib1.dll
MSVCR71.dll
nspr4.dll

I hope this extensive post sheds some light on py2exe in combination with Panda3D.
The essence is: use UPX.
enn0x