11
votes

Didn't get a response in the kivy forum, so trying here.

When I compile the tutorial pong code as a one file executable, I must still include the pong.kv file in the same folder for it to run. Otherwise, I get the following error when launching the exe:


    GL: EXT_framebuffer_object is supported
    [INFO              ] [GL          ] OpenGL version 
    [INFO              ] [GL          ] OpenGL vendor 
    [INFO              ] [GL          ] OpenGL renderer 
    [INFO              ] [GL          ] OpenGL parsed version: 2, 1
    [INFO              ] [GL          ] Shading version 
    [INFO              ] [GL          ] Texture max size 
    [INFO              ] [GL          ] Texture max units 
    [INFO              ] [Window      ] auto add sdl2 input provider
    [INFO              ] [Window      ] virtual keyboard not allowed,
    single mode, not docked
     Traceback (most recent call last):
       File "", line 81, in 
       File "c:\python34\lib\site-packages\kivy\app.py", line 802, in
    run
         root = self.build()
       File "", line 75, in build
       File "", line 20, in serveBall
     AttributeError: 'NoneType' object has no attribute 'center'
    main returned -1

How can I get it to run as one executable. Here's my pong.spec file:



    # -*- mode: python -*-

    from kivy.deps import sdl2, glew

    block_cipher = None


    a = Analysis(['Code\main.py'],
                 pathex=['E:\\Development\\Pong'],
                 binaries=None,
                 datas=None,
                 hiddenimports=[],
                 hookspath=[],
                 runtime_hooks=[],
                 excludes=[],
                 win_no_prefer_redirects=False,
                 win_private_assemblies=False,
                 cipher=block_cipher)
    pyz = PYZ(a.pure, a.zipped_data,
                 cipher=block_cipher)

    a.datas += [('Code\pong.kv', 'E:\\Development\\Pong\Code\pong.kv', 'DATA')] 

    exe = EXE(pyz,Tree('Code'),
              a.scripts,
              a.binaries,
              a.zipfiles,
              a.datas,
              *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
              name='pong',
              debug=False,
              strip=False,
              upx=True,
              console=True , icon='pong.ico')

Note that I tried to include the pong.kv in the datas list but that didn't help.

Thanks, -Raj

2
I heard it is possible and a few people made it, but I had no luck even with these: 1, 2. Although it may seem like an overkill(because of .exe size), working onefile option should be documented too. - Peter Badida
For other data files, the approach you suggested ended up working for me. However, to load the default .kv file, I ended up calling kivy.resource_add_path(resourcePath()) where resourcePath returned sys._MEIPASS (or local development path if not compiled) in my main section. This seemed to work; maybe will for you too? - Raj
I hope it'll. Could you append your onefile packaging steps with simple example to docs/wiki if the exe works with all resources and .kv? I think it'd be nice to have it there for future reference. - Peter Badida
I posted an answer based on the links you provided. Let me know if that works before revising the wiki. Thanks. - Raj
This really should be a comment but I don't have enough rep, so... Raj's answer is great and works well, but one thing to keep in mind: you have to also import the following into your app.py file (the people here before me probably just happened to already be using it and therefore didn't notice the need to import it, or else thought it too obvious to mention) import kivy, sys, os.path - taigrr

2 Answers

9
votes

Based on the links provided by KeyWeeUsr (Bundling data files with PyInstaller and Using PyInstaller to make EXEs from Python scripts) and combining that with Kivy's resource path method, here's a workable solution. I feel it's a bit rough around the edges because it uses SYS._MEIPASS (I would prefer a public API) and requires adding a code snippet to your Python code. However, the solution works on both Windows and Mac so will share.

Assume I have the following code hierarchy:

MyCode/
    MyApp.py  (This is the main program)
    myapp.kv  (This is the associated kv file)

    MyData/      (This is where data is located that the app uses)
       myapp.icns (e.g. icon file for mac)
       myapp.ico  (e.g. icon file for windows)

Build/
    mac/ 
        myapp.spec (spec file to build on mac platform)
    pc/ 
        myapp.spec (spec file to build on windows platform)

MyHiddenImports/ (Folder containing python files for hidden imports)

I added a MyHiddenImports folder to the example in case your code also appends another folder containing python code to sys.path during run time.

In MyApp.py add the following:

def resourcePath():
    '''Returns path containing content - either locally or in pyinstaller tmp file'''
    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()) # add this line
    my_app = MyApp()

The resources_add_path() tells Kivy where to look for the data/.kv files. For example, on the Mac, when running the pyinstaller app, it pointed to /private/var/folders/80/y766cxq10fb_794019j7qgnh0000gn/T/_MEI25602 and in windows, it pointed to c:\users\raj\AppData\Local\Temp_MEI64zTut (these folders get deleted after exiting app and creates another name when launched again).

I created the initial Mac template spec file with the following command:

pyinstaller --onefile -y --clean --windowed --name myapp --icon=../../Code/Data/myapp.icns --exclude-module _tkinter --exclude-module Tkinter --exclude-module enchant --exclude-module twisted ../../Code/MyApp.py

Here's the modified Mac OS Spec file:

# -*- mode: python -*-

block_cipher = None


a = Analysis(['../../Code/MyApp.py'],
            pathex=['/Users/raj/Development/Build/mac', 
            '../../MyHiddenImports'],    
            binaries=None,
            datas=None,
            hiddenimports=['MyHiddenImports'],    
            hookspath=[],
            runtime_hooks=[],
            excludes=['_tkinter', 'Tkinter', 'enchant', 'twisted'],
            win_no_prefer_redirects=False,
            win_private_assemblies=False,
            cipher=block_cipher)

pyz = PYZ(a.pure, a.zipped_data,
            cipher=block_cipher)

a.datas += [('myapp.kv', '../../MyCode/my.kv', 'DATA')]

exe = EXE(pyz, Tree('../../Code/Data', 'Data'), 
            a.scripts,
            a.binaries,
            a.zipfiles,
            a.datas,
            name='myapp',
            debug=False,
            strip=False,
            upx=True,
            console=False , icon='../../Code/Data/myapp.icns')

app = BUNDLE(exe,
             name='myapp.app',
             icon='../../Code/Data/myapp.icns',
             bundle_identifier=None)

Things to note: I added the hidden imports path to pathex, and referenced the package in hiddenimports. I appended the myapp.kv file to a.datas so it will be copied into the app. In the EXE, I added the Data tree. I included the prefix argument, as I wanted the Data folder to be copied into the app (versus having the children sit at the root level).

To compile the code to create the app and put it into a dmg file I have a make-myapp script that does the following:

pyinstaller -y --clean --windowed myapp.spec
pushd dist
hdiutil create ./myapp.dmg -srcfolder myapp.app -ov
popd
cp ./dist/myapp.dmg .

Similarly, here's the windows spec file:

# -*- mode: python -*-

from kivy.deps import sdl2, glew

block_cipher = None


a = Analysis(['..\\..\\Code\\Cobbler.py'],
             pathex=['E:\\Development\\MyApp\\Build\\pc',
             '..\\..\\MyHiddenImports'],
             binaries=None,
             datas=None,
             hiddenimports=['MyHiddenImports'],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)

pyz = PYZ(a.pure, a.zipped_data,
         cipher=block_cipher)

a.datas += [('myapp.kv', '../../Code/myapp.kv', 'DATA')]

exe = EXE(pyz, Tree('..\\..\\Code\\Data','Data'),
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
          name='myapp',
          debug=False,
          strip=False,
          upx=True,
          console=False, icon='..\\..\\Code\\Data\\myapp.ico' )

And to compile the windows app:

python -m PyInstaller myapp.spec

1
votes

If you don't care about the code length, what about loading kv data inside a .py file using Builder.load_string? This way the whole code is kept inside your python script and that may help to compile it to .exe.