Lothric
Lothric

Reputation: 1318

Kivy to one exe can't find kv file

I've seen some related questions here and tried every single solution I found like this and this for example, but I couldn't make that work. So the question is simple - after making single exe file from kivy it throws a FileNotFoundError: [Errno 2] No such file or directory: 'main2.kv'. Exe file with related files works good but I don't know what am I doing wrong while building single exe.

My spec file:

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

from kivymd.tools.packaging.pyinstaller import hooks_path as kivymd_hooks_path
from kivy_deps import sdl2, glew
...
a = Analysis(['main.py'],
    ...
    # I tried to write here absolute path, relative path and this method
    datas=[('*.kv', '.')],
    hiddenimports=[],
    hookspath=[kivymd_hooks_path],
    ...
)

# I also tried to put import here - didn't help (when I did that I also tried to change the path in my .py file to 'Data\main2.kv')
# a.datas += [('main2.kv', 'D:\\prog\\Lotto\\main2.kv', 'DATA')]
...

# tried with Tree and without Tree
coll = COLLECT(exe, Tree('D:\\prog\\Lotto','Data'),
    ...
    *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
    ...

In .py file I tried to add

if getattr(sys, 'frozen', False):
    kivy.resources.resource_add_path(sys._MEIPASS)

And

def resourcePath():
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS)
    return os.path.join(os.path.abspath("."))
...

if __name__ == "__main__":
    kivy.resources.resource_add_path(resourcePath())
    MainApp().run()

And tried both of above methods together - nothing. Also tried to change PyInstaller 3.6 to 3.5 version. I don't know what's the problem here, it's my first time trying to make exe file from Kivy.

Upvotes: 1

Views: 1197

Answers (2)

Nikhil Augustine
Nikhil Augustine

Reputation: 197

I would like to put here my experience and how I could solve it. Following is my directory structure.

AppDir
|_ src
      |_ *.kv, *.py
|_ images
      |_ *.png

As suggested in above answer I did not do resource_add_path in my main (It did not work for me), instead just prepend sys._MEIPASS to kv file for Builder.load_file. But then you would face a problem, you will not be able to run it directly as a python script. I solved it by conditionally prepending path based on packaged binary or python script.

This is how I did it, in a common_defines.py file I defined KV_DIRECTORY

# common_defines.py
import os, sys

# Assuming all .kv files are in same directory as this file
KV_DIRECTORY = os.path.abspath(os.path.dirname(__file__))

# If the script is bundled with PyInstaller, override the path with _MEIPASS
if getattr(sys, 'frozen', False):
   # Bundled with PyInstaller
   KV_DIRECTORY = sys._MEIPASS

And wherever you want to load the kv file, load with file with KV_DIRECTORY prepended to file name.

For example

# some_file.py
import os
from kivy.lang import Builder
import common_defines

Builder.load_file(os.path.join(common_defines.KV_DIRECTORY, 'some_file.kv'))

This way kv file path for both scenarios will be set correctly. Then you can just run following command from AppDir to generate executable.

pyinstaller --onefile -w --add-data "src\*.kv;." --add-data "images\*.png;images" src\main.py

Upvotes: 0

Lothric
Lothric

Reputation: 1318

Few hours later finally I got that. I open my kv file as file first and then pass it to Builder to be able to set the encoding. In that case I just needed to define the path of kv file manually as sys._MEIPASS + 'main2.kv' and then it worked.

UPD: To make that work make few imports first:

import os, sys
from kivy.resources import resource_add_path

and then at the end of your app just before MainApp.run() add this:

if __name__ == "__main__":
    # these lines should be added
    if hasattr(sys, '_MEIPASS'):
        resource_add_path(os.path.join(sys._MEIPASS))
    ###
    MainApp().run()

When we build single .exe file it holds all files inside of it, so it unpacks it to some random temporary folder while running. sys._MEIPASS here is the path of that temp folder.

So that should work. If it still can't find files, you can try to change file path like this: for example, instead of 'example.kv' write sys._MEIPASS + '/' + 'example.kv'.

Also make sure you've done everything right in the .spec file, so you should add your files to datas list in Analysis like this:

        a = Analysis(['main.py'],
             ...
             datas=[('main.kv', '.'), ('bg.png', '.'), ('CenturyGothic.ttf', '.'), ('finish.mp3', '.'), ('Logo.png', '.'),],

So if you have only kv file to add it would be:

datas=[('main.kv', '.')],

Upvotes: 1

Related Questions