PyInstaller Packages for kivy
Question: I have recently tried to build a package out of my
[kivy](http://kivy.org/#home) application using
[PyInstaller](http://www.pyinstaller.org/). My goal was to create a package I
can execute on my Linux (Ubuntu 14.04 64bit) and copy it to other Linux
systems as well.

I tried to execute the PyInstaller properly the whole night, tried various
things and in the end succeeded in creating a package for my own system. First
I'd like to know what exactly I did to functionally package my app.

My `main.spec` file looks like this:

    
    
    # Part A
    from kivy.tools.packaging.pyinstaller_hooks import install_hooks
    install_hooks(globals())
    # -*- mode: python -*-
    
    # Part B
    from PyInstaller.hooks.hookutils import exec_statement
    hiddenimports = ['pysqlite2', 'MySQLdb', 'psycopg2']
    databases = exec_statement("import sqlalchemy.databases;
    print sqlalchemy.databases.__all__")
    databases = eval(databases.strip())
    for n in databases:
        hiddenimports.append("sqlalchemy.databases." + n)
    version = exec_statement('import sqlalchemy; print sqlalchemy.__version__')
    is_alch06 = version >= '0.6'
    
    if is_alch06:
        dialects = exec_statement("import sqlalchemy.dialects; print sqlalchemy.dialects.__all__")
        dialects = eval(dialects.strip())
    
        for n in databases:
            hiddenimports.append("sqlalchemy.dialects." + n)
    
    block_cipher = None
    
    # Part C
    a = Analysis(['main.py'],
             pathex=['/home/grafgustav/Python/ICCHP/accessmail/src','/'],
             hiddenimports=[],
             cipher=block_cipher)
    pyz = PYZ(a.pure,
             cipher=block_cipher)
    exe = EXE(pyz,
          a.scripts,
          exclude_binaries=True,
          name='main',
          debug=False,
          strip=None,
          upx=True,
          console=True )
    
    
    coll = COLLECT(exe, Tree("/home/grafgustav/Python/ICCHP/accessmail"),
               a.binaries, #...,
               a.zipfiles,
               a.datas,
               strip=None,
               upx=True,
               name='main',
               )
    

While I understand why the Part B is mainly redundant (I just copied that part
from somewhere in the internet) and the Part A gets some finished hooks for
kivy, I do not quite understand why this does not pack the whole application
as I'd like it to.

As I understand it, hooks are used to include libraries that do not get
directly imported, but rather implicitly or during runtime. Just like hidden-
imports.

The main problem are the missing parts in my resulting package. There is a
folder "GUI" inside my whole src-directory which contains the kivy-specific
*.kv files. The src folder containing this GUI folder is included in the
resulting package, the *.kv files are still missing when I try to start the
program. So I tried to copy the GUI folder to the same folder my packaged
"main" is located at and the *.kv files are not missing anymore. Why does the
PyInstaller copy the whole src folder but fails to detect the GUI folder
properly? The next error I get is related to sqlalchemy. Since I've already
added some sqlalchemy hooks I figured the packaged application is not able to
find the data.db I use to store my data. This one is located one folder above
the main.py I am packaging. It is included in the result, but not in the right
position. If I take the data.db and copy it one folder above my resulted main,
this error is resolved as well. My final error, which stopped the package from
working was caused again by some kivy-specific stuff. The error message said,
that a file [Package directory]/kivy/data/style.kv was missing. Actually there
was not even a folder "kivy". I searched this file in the kivy sources, copied
it to the right directory and now it works on my system. Now I am wondering,
why I have to copy all these files to my resulting directory manually. How can
I tell PyInstaller to include these files beforehand? It works on my system
(where I packed it), but neither on my second system (Ubuntu 12.04LTS 64-bit,
some error about the wrong OpenGL version), on a testsystem (a fedora, no
error at all, only `main could not be executed`) or on the computers in my
university (Debian 3.2.57 64-bit, `./main: /lib/x86_64-linux-gnu/libc.so.6:
version GLIBC_2.14 not found (required by /libz.so.1).

My friend used PyInstaller on his Windows system, had quite some issues as
well (he had to add the above mentioned files/folders manually to the package
as well) but in the end it worked on Win7 and Win8 equally fine.

How can I use PyInstaller to make executable files for other Linux-systems as
well on my own system? And what exactly did I even do to that `main.spec`
file? Or is there maybe a better way to create an executable for python
applications?


Answer: I'm not an expert, but I'll share what I know

**Adding folders to the resulting package:**

> Now I am wondering, why I have to copy all these files to my resulting
> directory manually. How can I tell PyInstaller to include these files
> beforehand?

When defining your binaries on the COLLECT part, you make a call to Tree(), it
should be made with a `prefix` parameter, so pyInstaller know where to put the
folder accessmail in relation to the 'src' folder:

    
    
    Tree("/home/grafgustav/Python/ICCHP/accessmail", prefix='..')
    

But i never tryed it with `'..'` as prefix, must test it.

**Adding files to the resulting package:**

> the packaged application is not able to find the data.db I use to store my
> data. This one is located one folder above the main.py I am packaging. It is
> included in the result, but not in the right position.

To add an arbitrary file to the binaries, you just need to create a tuple with
3 values like this:

    
    
    a.binaries += [(file_address, file_result_address, 'DATA')]
    

The `file_result_address` is where the file will stay on the resulting
package.

To add more files, just add more tuples between the "[ ]":

    
    
    [(addr,addr,type), (addr,addr,type), ...]
    

**Making it work on several systems:**

The first tip I can give is to try to compile on a 32bits system, so it'll
work on 32 and 64 bits systems.

As suggested on section 3 of [this post](http://bitstream.io/packaging-and-
distributing-a-kivy-application-on-linux.html) you might try to remove system
libraries from your package and add them as dependencies, so it will use
native libraries for those. The link above provides a python function to do
this job inside the `.spec`.

**Making a single binary instead of a folder with all the dependencies:**

> How can I use PyInstaller to make executable files for other Linux-systems
> as well on my own system?

If you want a single binary file as output instead of a folder with all the
dependencies on it, you need to create a `.spec` file with the --onefile
option. You can copy the relevant parts to your code after that:

    
    
    pyinstaller --onefile myscript.py
    

You can get more info on the official [pyinstaller
documentation](http://pythonhosted.org/PyInstaller/#bundling-to-one-folder),
it helped me a lot.

I hope it helps.

