Pack an Enthought Traits app inside a .exe using py2exe (Canopy Edit)

10 months ago, I published the updated version of my tutorial to pack an Enthought TraitsUI based application inside an .exe Windows Executable file, using a standard Python 2.7 install and the Enthought Tool Suite 4.0 (ETS4.0). In April 2013, Enthought published their latest distribution called “Canopy”. This distribution marks a clear change in the way Python is installed, providing elaborate, and complicated, virtual environments (User – System – App). When an user installs a package, it gets stored in his User\Lib\site-packages\ folder, while if he uses the module out of the Canopy Box, it’s stored in the Canopy default folder, somewhere like App\appdata\canopy-1.0.1.1189.win-x86\lib\site-packages\

All in all, and because I need to compile my Seismic Data Processing (waveform analysis application), I have to update the py2exe procedure to match those changes.

To Start, I assume you have read a little about the Canopy Structure and its inner workings.

The example.py file is still the same as before:

from traits.api import *

class Person(HasTraits):
    name = Str
    age = CInt
    weight = CFloat

if __name__ == "__main__":
    joe = Person()
    joe.configure_traits()
    print joe.name, joe.age, joe.weight

The way the extra packages data_files were included before doesn’t work, because there is not a single site-packages folder to hold the required modules. So, the logic needs to be replaced using a slightly more clever approach, by defining the paths where modules could be, in the order of the Canopy logic,

import canopy
CanopyPaths= []
CanopyPaths.append(r'C:\Users\thomas\AppData\Local\Enthought\Canopy32\User\Lib\site-packages')
CanopyPaths.append(r'C:\Users\thomas\AppData\Local\Enthought\Canopy32\System\Lib\site-packages')
CanopyPaths.append(os.path.split(canopy.__path__[0])[0])

The last line is the dynamically recovered path of the current Canopy install.

Then, we need to search for the elements and add them to the data_folders array:

toAppend = []
toAppend.append(r'enable/images')
toAppend.append(r'pyface/images')
toAppend.append(r'pyface\ui\wx\images')
toAppend.append(r'pyface\ui\wx\grid\images')
toAppend.append(r'traitsui\wx\images')
toAppend.append(r'traitsui\image\library')
toAppend.append(r'enable\savage\trait_defs\ui\wx\data')
toAppend.append(r'enable\savage\trait_defs\ui\wx\data')

for item in toAppend:
    for path in CanopyPaths:
        if os.path.isdir(os.path.join(path,item)):
            print item, "exists in", path
            data_folders.append((os.path.join(path,item),item))
            break

If you run the setup as-is, you’ll get an error like this:

error

situating the problem within numpy.linalg:

Traceback (most recent call last):
  File "example.py", line 10, in <module>
  File "traits\has_traits.pyc", line 2561, in configure_traits

  File "traitsui\api.pyc", line 35, in <module>

  File "traitsui\editors\__init__.pyc", line 22, in <module>

  File "traitsui\editors\api.pyc", line 6, in <module>

  File "traitsui\editors\array_editor.pyc", line 27, in <module>

  File "numpy\__init__.pyc", line 143, in <module>

  File "numpy\add_newdocs.pyc", line 9, in <module>
    A `flatiter` iterator is returned by ``x.flat`` for any array `x`.
  File "numpy\lib\__init__.pyc", line 13, in <module>
    
  File "numpy\lib\polynomial.pyc", line 17, in <module>
    Functions to operate on polynomials.
  File "numpy\linalg\__init__.pyc", line 48, in <module>
    
  File "numpy\linalg\linalg.pyc", line 23, in <module>
    t
ImportError: DLL load failed: The specified module could not be found.

The numpy.linalg module only contains one precompiled shared library (lapack_lite.pyd), so I checked its dependencies using Dependency Walker, and noticed the missing libraries:

dependency_numpy_linalg_mkl

The missing libraries are:

  • MK2_INTEL_THREAD.DLL
  • MSVCR90.DLL
  • GPSVC.DLL
  • IESHIMS.DLL

The first, MK2_INTEL_THREAD.DLL is a shared library present in the App directory of Canopy (C:\Users\thomas\AppData\Local\Enthought\Canopy32\App\appdata\canopy-1.0.1.1189.win-x86\Scripts in my case). I use Canopy 32bits version, which comes with Numpy built against MKL. I assume all MK2*.dll should be available to the compiled code, so I’ll copy them to the numpy/linalg folder. The second is present in the matplotlib.backends module, same, I’ll copy it to the folder. The two last appear on the image above with a small hourglass, meaning they are delayed libraries (and somehow we can, I assume from the result below, not care about them). To hack this MKL problem, we add the files to the data_files array of our setup.py:

folder = r'C:\Users\thomas\AppData\Local\Enthought\Canopy32\App\appdata\canopy-1.0.1.1189.win-x86\Scripts'
data_files.append((r'numpy/linalg',glob.glob(os.path.join(folder,'*mk2*.dll'))))
data_files.append((r'numpy/linalg',[r'C:\Users\thomas\AppData\Local\Enthought\Canopy32\User\Lib\site-packages\matplotlib\backends\msvcr90.dll']))

Then, run python setup.py py2exe and… Voilàààààà :

example.py

Now, one could argue that building an application on Canopy is not clever, because this or that. The fact is that Canopy is my work environment, and most of the improvements that I bring to the seismic software come from this everyday work. Building the application with state of the art and regularly updated modules and packages is a necessary regular exercise not to loose contact with the advances, plus I don’t have time/envy to play/hack/any with other Python distributions.

Let me know if you succeed building your applications with this tutorial, and if you have ideas to improve it !

The code of setup.py is after the break:

from distutils.core import setup
import py2exe
import os
import glob

import sys

includes = []
includes.append('numpy')
includes.append('numpy.*')
includes.append('numpy.linalg')
includes.append('numpy.core')
includes.append('configobj')
includes.append('reportlab')
includes.append('reportlab.pdfbase')
includes.append('reportlab.pdfbase.*')
includes.append('scipy')
includes.append('xml')
includes.append('xml.etree')
includes.append('xml.etree.*')

includes.append('wx')
includes.append('wx.*')

includes.append('traits')
includes.append('traitsui')
includes.append('traitsui.editors')
includes.append('traitsui.editors.*')
includes.append('traitsui.extras')
includes.append('traitsui.extras.*')

includes.append('traitsui.wx')
includes.append('traitsui.wx.*')

includes.append('kiva')

includes.append('pyface')
includes.append('pyface.*')
includes.append('pyface.wx')

includes.append('pyface.ui.wx')
includes.append('pyface.ui.wx.init')
includes.append('pyface.ui.wx.*')
includes.append('pyface.ui.wx.grid.*')
includes.append('pyface.ui.wx.action.*')
includes.append('pyface.ui.wx.timer.*')
includes.append('pyface.ui.wx.wizard.*')
includes.append('pyface.ui.wx.workbench.*')

includes.append('enable')
includes.append('enable.drawing')
includes.append('enable.tools')
includes.append('enable.wx')
includes.append('enable.wx.*')

includes.append('enable.savage')
includes.append('enable.savage.*')
includes.append('enable.savage.svg')
includes.append('enable.savage.svg.*')
includes.append('enable.savage.svg.backends')
includes.append('enable.savage.svg.backends.wx')
includes.append('enable.savage.svg.backends.wx.*')
includes.append('enable.savage.svg.css')
includes.append('enable.savage.compliance')
includes.append('enable.savage.trait_defs')
includes.append('enable.savage.trait_defs.*')
includes.append('enable.savage.trait_defs.ui')
includes.append('enable.savage.trait_defs.ui.*')
includes.append('enable.savage.trait_defs.ui.wx')
includes.append('enable.savage.trait_defs.ui.wx.*')

packages = []

data_folders = []

# Traited apps:

import canopy
CanopyPaths= []
CanopyPaths.append(r'C:\Users\thomas\AppData\Local\Enthought\Canopy32\User\Lib\site-packages')
CanopyPaths.append(r'C:\Users\thomas\AppData\Local\Enthought\Canopy32\System\Lib\site-packages')
CanopyPaths.append(os.path.split(canopy.__path__[0])[0])

toAppend = []
toAppend.append(r'enable/images')
toAppend.append(r'pyface/images')
toAppend.append(r'pyface\ui\wx\images')
toAppend.append(r'pyface\ui\wx\grid\images')
toAppend.append(r'traitsui\wx\images')
toAppend.append(r'traitsui\image\library')
toAppend.append(r'enable\savage\trait_defs\ui\wx\data')
toAppend.append(r'enable\savage\trait_defs\ui\wx\data')

for item in toAppend:
    for path in CanopyPaths:
        if os.path.isdir(os.path.join(path,item)):
            print item, "exists in", path
            data_folders.append((os.path.join(path,item),item))
            break

# Matplotlib
import matplotlib as mpl
data_files = mpl.get_py2exe_datafiles()

# Parsing folders and building the data_files table
for folder, relative_path in data_folders:
    for file in os.listdir(folder):
        f1 = os.path.join(folder,file)
        if os.path.isfile(f1): # skip directories
            f2 = relative_path, [f1]
            data_files.append(f2)

item = r'enable\images.zip'
for path in CanopyPaths:
    if os.path.isfile(os.path.join(path,item)):
        print item, "exists in", path
        data_files.append((r'enable',[os.path.join(path,item)]))
        break

#TEMP MKL HACK
folder = r'C:\Users\thomas\AppData\Local\Enthought\Canopy32\App\appdata\canopy-1.0.1.1189.win-x86\Scripts'
data_files.append((r'numpy/linalg',glob.glob(os.path.join(folder,'*mk2*.dll'))))
data_files.append((r'numpy/linalg',[r'C:\Users\thomas\AppData\Local\Enthought\Canopy32\User\Lib\site-packages\matplotlib\backends\msvcr90.dll']))

setup(windows=['example.py'],
    author="Geophysique.be",
    version = "1.1",
    description = "Geophysique.be Canopy/Py2exe example",
    name = "Geophysique.be Canopy/Py2exe example",
    options = {"py2exe": {    "optimize": 0,
                              "packages": packages,
                              "includes": includes,
                              "dist_dir": 'dist',
                              "bundle_files":2,
                              "xref": False,
                              "skip_archive": True,
                              "ascii": False,
                              "custom_boot_script": '',
                              "compressed":False,
                              "dll_excludes":['libzmq.dll','MSVCP90.dll'],
                             },},
    data_files=data_files)

Leave a Reply

Your email address will not be published. Required fields are marked *

*