0

I'm packaging a Kivy app using PyInstaller (3.4) for use on Windows machines. I've gotten it to compile and work on my machine but when moving the exe to another, it fails giving the error:

[WARNING] [Image       ] Unable to load image 
C:\Users\<OTHER_MACHINES_USER>\AppData\Local\Temp_MEI38442\kivy_install\data\glsl\default.png>
[CRITICAL] [Window      ] Unable to find any valuable Window provider.
sdl2 - Exception: SDL2: Unable to load image

This works from the dist folder when the whole thing is copied across (as in ./dist/client_iface.exe) and so I figure its a dependency issue I've been unable to resolve, apparently with SDL2.

I believe that this problem is almost identical to this problem however that question is two years old and as yet has no successful solution.

I've followed a number of guides (including this one which is linked from the above post) but with no success.

The spec file, with adjustments as per the Kivy guide, is as follows:

# -*- mode: python -*-

from kivy.deps import sdl2, glew
import os

block_cipher = None

a = Analysis(
    ['client_iface.py'],
    pathex=['D:\\Users\\<USER>\\Documents\\2_Projects\\py_dice_roller\\client'],
    binaries=[('D:\\Users\\<USER>\\Documents\\2_Projects\\DnD\\py_dice_roller\\client\\SDL2.dll', '.')],
    datas=[],
    hiddenimports=[],
    hookspath=[],
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False
)
a.datas += [
    ('client_iface.kv', '.\\client_iface.kv', 'DATA'), 
    ('active.png', '.\\images\\active.png', 'DATA'),
    ('back.png', '.\\images\\active.png', 'DATA'),
    ('normal.png', '.\\images\\active.png', 'DATA'),
    ('DroidSansMono.ttf', '.\\fonts\\DroidSansMono.ttf', 'DATA')
]
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher
)
exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name='client_iface',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    runtime_tmpdir=None,
    console=True,
    icon='.\images\icon_dWU_icon.ico',
)
coll = COLLECT(
    exe, Tree(os.getcwd()),
    a.binaries,
    a.zipfiles,
    a.datas,
    *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
    strip=False,
    upx=True,
    name='client_iface'
)

And following the above linked I have included the following code in the script:


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

    return os.path.join(os.path.abspath(path))

if __name__ == '__main__':
    kivy.resources.resource_add_path(resourcePath('.'))
    kivy.resources.resource_add_path(resourcePath('./images'))
    main_app().run()

And my command in use is:

python -m PyInstaller -F --onefile .\client_iface.spec

The expected result would be that the single executable produced would run on any like-machine without error-ing or the need for the entire folder of additional files.

Any help would be appreciated.

BodneyC
  • 110
  • 1
  • 11
  • One thing not obvious about `Pyinstaller` is that when using a `spec` file, most of the command line options are ignored. See the [documentation](https://pyinstaller.readthedocs.io/en/stable/spec-files.html). In particular, the `-F` and `--onefile` are ignored. – John Anderson Jan 11 '19 at 23:07
  • Hi **John**, I've now got `[('onefile', None, 'OPTION')]` (and `F`) in the options section of `EXE` but unfortunately with no change in result. Thanks for the tip though. – BodneyC Jan 12 '19 at 11:26
  • The `options` of the `EXE` section in your `.spec` file are options to be passed to the python interpreter when you run your `.exe` result. Those options are not `Pyinstaller` options. – John Anderson Jan 12 '19 at 13:53
  • Try removing your `dist` directory that `Pyinstaller` creates, then run `Pinstaller` again. If the resulting `dist` folder has ONLY an `exe` file, then your `.spec` file is set up to create a `onefile` executable. If there is anything else in the `dist` folder, then your `.spec` file is set up to create a `onedir` executable, and the `exe` file in that folder will not work without the entire folder. – John Anderson Jan 12 '19 at 14:15
  • Okay, how would I go about setting `onefile` in the `.spec` in a way which would affect PyInstaller and not the Python Interpreter. The only reference to `onefile` in the documentation regards OSX (not Windows)? I can't find reference to this option anywhere. – BodneyC Jan 12 '19 at 14:20
  • The `COLLECT` section of the `.spec` file instructs `Pyinstaller` to create a `onedir` executable. I think that just removing that section will result in a `onefile` executable. The best way to create a `onefile` `.spec` file is to move the current `.spec` file aside and run `Pyinstaller` with the `--onefile` option to create a new `.spec` file. But then you would need to edit that `.spec` file to adjust other things (but don't add a `COLLECT` section). – John Anderson Jan 12 '19 at 14:37
  • For a Kivy app, the `COLLECT` call is needed to add sdl2 and glew deps, as per [the Kivy guide](https://kivy.org/doc/stable/guide/packaging-windows.html#pyinstaller-default-hook) – BodneyC Jan 12 '19 at 14:43
  • That example is creating a `onedir` executable. For a `onefile` executable you can add `*[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],` to the `EXE` section in place of the current `[],`. – John Anderson Jan 12 '19 at 14:48
  • Hello again **John**, that has worked wonderfully. I didn't realise that statement could go in the `EXE` call and I don't think I'd have found that without you. If you want to post an answer to the question I'll more than happily accept it. Thanks again. – BodneyC Jan 12 '19 at 15:08

1 Answers1

0

The COLLECT section of your .spec file indicates that a onedir executable will be created. In order to make a onefile executable, you will need to remove the COLLECT section. Also, the sdl2 and glew deps that are listed in the COLLECT section can be added to the EXE section as *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)] in place of the default [], that appears before the first keyword argument.

Also, note that most of the command line options of Pyinstaller are ignored if you are using a .spec file. Refer to the documentation for more details. Unfortunately, the Pyinstaller documentation is rather vague in many places.

John Anderson
  • 35,991
  • 4
  • 13
  • 36