pythonmacostkinterpyinstallerpy2app

How to compile Tkinter as an executable for MacOS?


I'm trying to compile a Tkinter app as an executable for MacOs. I tried to use py2app and pyinstaller. I almost succeed using py2app, but it returns the following error:

Traceback

The Info.plist file must have a PyRuntimeLocations array containing string values for preferred Python runtime locations.  
These strings should be "otool -L" style mach ids; "@executable_stub" and "~" prefixes will be translated accordingly.

This is how the setup.py looks like:

from setuptools import setup

APP = ['main.py']
DATA_FILES = ['config.json']
OPTIONS = {
    'argv_emulation': True
}

setup(
    app=APP,
    data_files=DATA_FILES,
    options={'py2app': OPTIONS},
    setup_requires=['py2app'],
)

And this is the directory structure:

-modules/---__init.py__
|        | 
|        -- gui_module.py
|        |
|        -- scraper_module.py
|        |
|        -- app.ico
|
-config.json
|
-countries_list.txt
|
-main.py
|
-requirements.txt
|
-setup.py

I'm happy to share more details and the files if you need them.


Solution

  • The problem was that you need to give an executable path for the python framework you have on your MacOs. So I modify the setup.py

    setup.py

    from setuptools import setup
    
    class CONFIG:
        VERSION = 'v1.0.1'
        platform = 'darwin-x86_64'
        executable_stub = '/opt/homebrew/Frameworks/Python.framework/Versions/3.10/lib/libpython3.10.dylib' # this is important, check where is your Python framework and get the `dylib`
        APP_NAME = f'your_app_{VERSION}_{platform}'
        APP = ['main.py']
        DATA_FILES = [
            'config.json', 
            'countries_list.txt', 
            ('modules', ['modules/app.ico']),
            # this modules are automatically added if you use __init__.py in your folder
            # ('modules', ['modules/scraper_module.py']),
            # ('modules', ['modules/gui_module.py']),
        ]
    
        OPTIONS = {
            'argv_emulation': False,
            'iconfile': 'modules/app.ico',
            'plist': {
                'CFBundleName': APP_NAME,
                'CFBundleDisplayName': APP_NAME,
                'CFBundleGetInfoString': APP_NAME,
                'CFBundleVersion': VERSION,
                'CFBundleShortVersionString': VERSION,
                'PyRuntimeLocations': [
                    executable_stub,
                    # also the executable can look like this:
                    #'@executable_path/../Frameworks/libpython3.4m.dylib',
                ]
            }
        }
    
    def main():
        setup(
            name=CONFIG.APP_NAME,
            app=CONFIG.APP,
            data_files=CONFIG.DATA_FILES,
            options={'py2app': CONFIG.OPTIONS},
            setup_requires=['py2app'],
            maintainer='foo bar',
            author_email='foo@domain.com',
        )
    
    if __name__ == '__main__':
        main()
    

    Then you need to run python3 setup.py py2app and now you can go and just double click on your_app_{VERSION}_{platform}.app.

    Recomendations by the py2app docs:

    1. Make sure not to use the -A flag
    2. Do not use --argv-emulation when the program uses a GUI toolkit (as Tkinter) py2app options docs