Using pyinstaller to package kivy and kivyMD desktop apps

Packaging kivy applications can be quite daunting. The documentation can be pretty confusing at times. This guide will walk you through how to package a kivyMD application for windows using pyinstaller through the use of an simple example.

Let's get right into it!🙂

I assume that you already have kivy and kivyMD installed in a virtual environment.

The first step is to install pyinstaller with pip. Open up the command prompt terminal, activate your virtual environment and type in the following:

pip install pyinstaller

Create two files:

  • main.py - which is going to contain the main code.
  • main.kv - which is going to contain the code written in kivy lang

Note: These files should be in the same directory.

Inside main.py, enter the following code:

import os, sys
from kivy.resources import resource_add_path, resource_find
from kivymd.app import MDApp

class MainApp(MDApp):
    def build(self):
        self.theme_cls.primary_palette = "DeepPurple"

if __name__ == '__main__':
    try:
        if hasattr(sys, '_MEIPASS'):
            resource_add_path(os.path.join(sys._MEIPASS))
        app = MainApp()
        app.run()
    except Exception as e:
        print(e)
        input("Press enter.")

The above code is very crucial when it comes to packaging our application. The try-catch statement helps us identify errors in our program and the input("Press enter.") stops the console from closing before we want it to.

Edit main.kv:

MDRectangleFlatButton:
    text: "Hello World"
    pos_hint: {'center_x': .5, 'center_y': .5}

We are just going to display a simple button.

Save the files then open the command prompt terminal, activate your environment and navigate to the folder containing the two files.

Now, before you even start the packaging process, make sure when you run your application you are not getting any console errors.

Type pyinstaller --onefile main.py in command prompt and press enter.

When execution is complete, two folders should have been created in the directory containing main.py; dist and build and a file named main.spec.

Navigate into the dist folder. Inside it, there is an executable file, main.exe. If you double click it, a terminal will open and close very quickly.

To solve this issue, add the following lines to the top of main.spec:

from kivy_deps import sdl2, glew
from kivymd import hooks_path as kivymd_hooks_path

Note: If you are not packaging a kivyMD application, there is no need for the second import or modifying hookspath as shown shortly below.

Add in Analysis the lines:

a = Analysis(
    #...
    datas=[('main.kv', '.')],
    hookspath=[kivymd_hooks_path],
   #...
)

datas is made up of a list of tuples where the first item in the tuple is the file name and the second item is the directory that is going to be used to store the file. If for example, you had a folder named images in the same directory as main.py, you would add them as follows:

datas=[('main.kv', '.'), ('./images/*.png', 'images')],

The above line is basically saying, 'Get all the png images in the images folder and save them to a folder named images in the final application.'

Next, edit EXE:

exe = EXE(
    #...
    *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
    #....
)

The final main.spec file looks like this:

# -*- mode: python ; coding: utf-8 -*-

block_cipher = None
from kivy_deps import sdl2, glew
from kivymd import hooks_path as kivymd_hooks_path

a = Analysis(['main.py'],
             pathex=['C:\\Users\\path\\to\\file'],
             binaries=[],
             datas=[('main.kv', '.')],
             hiddenimports=[],
             hookspath=[kivymd_hooks_path],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
          name='main',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=True )

Now save and run: pyinstaller main.spec in the terminal.

After it has finished executing, try executing main.exe again.

And that's it! You have successfully packaged your application.

Errors you may encounter

You may encounter import errors when running pyinstaller. I encountered errors importing pyenchant and cv2. These can easily be fixed by running the following commands in your terminal:

pip install pyenchant
pip install opencv-python

After the installation is complete, run pyinstaller main.spec in your terminal and try executing the main.exe again.

Final thoughts

I hope this guide can prove useful to you. It is by no means a complete guide covering all the aspects of packaging kivy application for windows but I hope you can learn something from it.

For more information, check out:

18