pyinstaller / pyinstaller

Freeze (package) Python programs into stand-alone executables
http://www.pyinstaller.org
Other
11.87k stars 1.94k forks source link

'tkinter' distribution was not found #6764

Closed devmgardner closed 2 years ago

devmgardner commented 2 years ago

Description of the issue

When packaging a complex program written with Tkinter, most functionality is gone. I have finally been able to package the application and get it to actually open, however: my filedialog() does not appear at startup as it's supposed to, my listbox onselect() function does not update my canvas view as it's supposed to, and two of my three buttons do nothing. None of these issues are present when running the base .py file from command line. I do receive warnings during packaging that tkinter was not found, which is baffling considering it's now included with Python.

Context information (for bug reports)

A minimal example program which shows the error

My main.py file is 38KB and this being a closed-source project, would take me days to get to a point where I could post it here.

Stacktrace / full error message

718 WARNING: Unable to copy metadata for tkinter: The 'tkinter' distribution was not found and is required by the application

1078 INFO: Determining a mapping of distributions to packages...

9110 WARNING: Unable to determine requirements for tkinter: The 'tkinter' distribution was not found and is required by the application

EDIT: This is the command I use to package the file:

pyinstaller --onedir --noconsole --key=ThisIsAKey --hidden-import=tkinter --collect-all=tkinter main.py

bwoodsend commented 2 years ago

That's because tkinter isn't a distribution. If you can't pip install it then --collect-all won't work for it. This boils down to the same issue as #6458 - collect_all() assumes that its argument names both a package and a distribution so it falls if a package is named differently to the distribution it was installed from or, in your case, if the package is part of the standard library.

bwoodsend commented 2 years ago

Why are you adding --hidden-import=tkinter --collect-all=tkinter anyway? What happens if you leave it out?

devmgardner commented 2 years ago

The issue remains the same, but without --hidden-import=tkinter, I get no error message. It was a suggestion given in a SO thread about a similar issue.

devmgardner commented 2 years ago

That's because tkinter isn't a distribution. If you can't pip install it then --collect-all won't work for it. This boils down to the same issue as #6458 - collect_all() assumes that its argument names both a package and a distribution so it falls if a package is named differently to the distribution it was installed from or, in your case, if the package is part of the standard library.

I'm not quite sure why this was closed, as it's not a duplicate. I still have the same issue without the --hidden-import=tkinter and --collect-all=tkinter statements, I just don't get a warning message during packaging.

rokm commented 2 years ago

Maybe try packaging with console enabled first, so you can see error messages during runtime? I imagine your buttons "do nothing" because the callbacks trigger exceptions and in no-console mode, you have no way of seeing those (unless they also bring whole application down, but that's not necessary the case with callbacks).

Other than that, you'll have to provide a minimum reproducer - without seeing what your code does, our guess about what's going on is as good as yours (except the fact that message about tkinter missing is a red herring, and is result of using --collect-all against a package that has no dist metadata).

devmgardner commented 2 years ago

Packaged with console enabled, I get an error about a free variable being referenced before assignment on one of the buttons, but that's it for console output. The button that's assigned quit() does nothing and produces no console output, and there's no onselect() console output either to tell me why the tkinter canvas isn't updating.

I'll have to figure something out for a minimum reproducer; I was intending to push this program to production over the weekend but haven't been able to compile for Windows or macOS and actually get it functioning, and my Linux machine is down right now due to display manager issues.

Legorooj commented 2 years ago

@devmgardner can you post the traceback?

devmgardner commented 2 years ago
Exception in Tkinter callback
Traceback (most recent call last):
  File "tk_main.py", line 58, in ini_check
FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\Workstation2\\Desktop\\vsc\\tk_main\\binaries\\windows\\pyinstaller_test\\dist\\tk_main\\tk_main.ini'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "tkinter\__init__.py", line 1921, in __call__
  File "tk_main.py", line 348, in <lambda>
NameError: free variable 'button1_func' referenced before assignment in enclosing scope
Legorooj commented 2 years ago

What's the tk_main.ini file for?

devmgardner commented 2 years ago

It's a file meant to house the license key as well as some other important info for the program; it's basically just a deterrent to dissuade anyone from trying to distribute the program as their own. It gets generated on first run, and is checked at each subsequent run. I'm not sure why the ini_check() function is in the stack trace as it's only called at the end of the tk_main.py file.

if __name__ == '__main__':
    ini_check()
Legorooj commented 2 years ago

Are all the paths you use in your app relative or absolute? If they aren't relative then make them relative to __file__.

devmgardner commented 2 years ago

I only ever use relative paths; I have the three lines below set as a snippet for my Python development.

currentdir = os.path.dirname(os.path.realpath(__file__))
parentdir = os.path.dirname(currentdir)
sys.path.append(parentdir)

I use os.path.join(currentdir, 'filename.ext') for everything.

devmgardner commented 2 years ago

I do have an update, however minor it may be. With a completely fresh install of Python 3.10.4 from Python.org/downloads instead of through anaconda, I'm able to reproduce the same exact issues on MacOS now as I've had on Windows. So that tells me I'll at least be able to provide cross-platform support for my application once these issues are resolved.

Legorooj commented 2 years ago

I'd recommend switching to using pathlib where possible, but that's up to you :P.

Do you get the same issue when you build in onedir mode? It's generally best to get it working with onedirectory first, then convert it to onefile.

devmgardner commented 2 years ago

Unfortunately yes, I do still get the same issue with onedir mode. IIRC, the last time I built it with with onefile it didn't work at all.

devmgardner commented 2 years ago

Fantastic news! I went through and restructured a significant chunk of my code in my TopLevel, and it mostly works now! For whatever reason, a lot of the problems seemed to be related to functions being called before they were technically defined, and I've restructured it in a good order to get everything defined before it's called; it was quite the puzzle considering what references what and where/how, but I was able to manage it. My only remaining problem is in --onefile mode, the program does not seem to generate the .log file or the .ini file. Below is an excerpt from my ini_gen() function to show how it's being written. Any suggestions? Btw, it does work properly in --onedir mode, just not --onefile mode. Ideally I'd like to get it working in --onefile mode so I can more easily package it for MacOS as well.

def ini_gen(license_key):
    fhand = open(os.path.join(currentdir, 'tk_main.ini'), 'w')
    timestamp = round(time.time(),2)
    fhand.write(f'{timestamp}\n')
    fhand.write(f'{license_key}\n')
    hasher = hashlib.md5()
    if platform.system() == 'Windows':
        with open(os.path.join(currentdir, 'tk_main.exe'), 'rb') as khand:
            buf = khand.read()
            hasher.update(buf)
rokm commented 2 years ago

My only remaining problem is in --onefile mode, the program does not seem to generate the .log file or the .ini file.

fhand = open(os.path.join(currentdir, 'tk_main.ini'), 'w')

(and from earlier comment:)

currentdir = os.path.dirname(os.path.realpath(__file__))

If currentdir is based on __file__, then in onefile mode, this ends up pointing into temporary directory where the onefile program is extracted. So your ini and log files are likely written there, and then removed once the program exists.

I'd like to get it working in --onefile mode so I can more easily package it for MacOS as well.

Why would onefile be easier for macOS? Assuming you want to generate app bundle, you're more likely to run into issues with code signing and notarization that you won't be able to solve via post-processing...

rokm commented 2 years ago

And from more general perspective, if this is meant to be serious application, you should avoid writing files into application bundle at runtime:

So it might be better to store the persistent run-time-generated files into appropriate OS-specific user-settings directory.

devmgardner commented 2 years ago

I've since updated it to write the .log and .ini files into the user's home directory, which will only pose a problem if the user changes/updates their HOME $PATH variable frequently, which I don't foresee being an issue considering the target audience of the program.

The final issue I'm having is a segmentation fault 11 with the MacOS version of the program. A lot of the issues I've run into stemmed from either the way I wrote functions, the paths I used, or the order in which my functions were executing. The program is ready to deploy for both Windows and Linux, but this segmentation fault 11 on running the compiled app on MacOS is posing a problem.

I can get the licensing screen to appear and go through with the API call properly, but as soon as it tries to load my main window it exits with the seg fault.

EDIT: My logger gave me the following output AFTER generating the tk_main.ini file in the exact directory in which it said it couldn't find it.

2022-04-22 13:28:18,160 - tk_main - ERROR
[Errno 2] No such file or directory: '/Users/devingardner/tk_main.ini'
2022-04-22 13:28:18,161 - tk_main - ERROR
Traceback (most recent call last):
  File "tk_main.py", line 59, in ini_check
FileNotFoundError: [Errno 2] No such file or directory: '/Users/devingardner/tk_main.ini'
devmgardner commented 2 years ago

Y'know, come to think of it. I don't think the seg fault is coming from ini_check(). Because ini_check() determines it's not able to find the file, then calls the licensing screen. The licensing screen is then (after successful license activation) supposed to call the main window. I'm getting the seg fault after successful activation, so somewhere between the activation and the main window coming up, I'm having an issue that's not being logged.

Random other problem, --onefile is now not making a .app file or using my icon, but I believe that has something to do with my use of --uac-admin.

Legorooj commented 2 years ago

--uac-admin is ignored on MacOS.

devmgardner commented 2 years ago

Interesting, then I'm not sure why it's suddenly just making a Unix exec instead of a .app file.

Legorooj commented 2 years ago

Are you sure using --uac-admin makes a difference to the output?

bwoodsend commented 2 years ago

A .app is made by --windowed mode.

devmgardner commented 2 years ago

That's odd, as for most of my compiling time these last few days it was making a .app with just --onefile. I imported faulthandler to see where the seg fault was occurring, and this is the best I got. Seems it's having trouble with tkinter maybe?

Current thread 0x000000010fbef600 (most recent call first):
  File "tkinter/commondialog.py", line 45 in show
  File "tkinter/filedialog.py", line 384 in askopenfilename
  File "tk_main.py", line 740 in choose_file
  File "tk_main.py", line 327 in choose
  File "tk_main.py", line 731 in __init__
  File "tk_main.py", line 773 in main
  File "tk_main.py", line 207 in submit_license
  File "tk_main.py", line 264 in <lambda>
  File "tkinter/__init__.py", line 1921 in __call__
  File "tkinter/__init__.py", line 1458 in mainloop
  File "tk_main.py", line 274 in license_screen
  File "tk_main.py", line 74 in ini_check
  File "tk_main.py", line 777 in <module>

Extension modules: PIL._imaging, PIL._imagingft (total: 2)
Segmentation fault: 11
logout
devmgardner commented 2 years ago

I also got this in the error report.

Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
Exception Codes:       KERN_INVALID_ADDRESS at 0x000000000000001c
Exception Codes:       0x0000000000000001, 0x000000000000001c
Exception Note:        EXC_CORPSE_NOTIFY

Termination Reason:    Namespace SIGNAL, Code 11 Segmentation fault: 11
Terminating Process:   exc handler [56253]
devmgardner commented 2 years ago

I have resolved every issue except for the segmentation fault. It really doesn't impede the usage of the application; it merely requires the user to reopen the app after licensing. However, I don't necessarily want to deploy with an existing seg fault in the app and force MacOS users to have to reopen the app after licensing. I could most likely (using pyinstaller --onedir and Platypus) refactor my code and skip around this seg fault, but that would take another half day (that I don't have today) of work at the least.

It seems I'm not the only one getting this seg fault issue with tkinter and pyinstaller, but I've yet to find a fix that works. Any help would be greatly appreciated.

rokm commented 2 years ago

Can you reproduce the segfault with a minimal stand-alone example (e.g., a window and button that opens that filedialog)? If you can, then please provide a reproducer (along with macOS version and python version you are using). Otherwise, you're pretty much on your own...

devmgardner commented 2 years ago

Below is my minimum reproducer. I've removed some things for security of course, and was able to reproduce the same segmentation fault at the same time using one of my testing license keys.

import tkinter as tk, requests as rq, platform, os, sys, time, hashlib, logging, traceback, Vigenere_Helper, zipfile, re, subprocess
import tkinter.ttk as ttk
from tkinter.constants import *
from tkinter import PhotoImage, messagebox
from logging.handlers import RotatingFileHandler
#
import faulthandler
faulthandler.enable()
#
currentdir = os.path.dirname(os.path.realpath(__file__))
parentdir = os.path.dirname(currentdir)
sys.path.append(parentdir)
homedir = os.path.expanduser(f'~{os.getlogin()}')
#
logger = logging.getLogger("tk_main")
logger.setLevel(logging.DEBUG)
handler = RotatingFileHandler(os.path.join(homedir, 'tk_main.log'), maxBytes=100000, backupCount=5)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s\n%(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
#
def get_device_uuid():
    if platform.system() == "Linux":
        # linux
        try:
            return subprocess.run('cat /etc/machine-id', shell=True).stdout
        except Exception as e:
            logger.error(str(e))
            logger.error(traceback.format_exc())
    elif platform.system() == "Darwin":
        # OS X
        cmd = """ioreg -ad2 -c IOPlatformExpertDevice | 
  xmllint --xpath '//key[.="IOPlatformUUID"]/following-sibling::*[1]/text()' -
"""
        try:
            return subprocess.run(cmd, shell=True).stdout
        except Exception as e:
            logger.error(str(e))
            logger.error(traceback.format_exc())
    elif platform.system() == "Windows":
        try:
            from wmi import WMI
            return WMI().Win32_ComputerSystemProduct()[0].UUID
        except Exception as e:
            logger.error(str(e))
            logger.error(traceback.format_exc())
#
def ini_gen(license_key):
    with open(os.path.join(homedir, 'tk_main.ini'), 'w') as fhand:
        timestamp = round(time.time(),2)
        fhand.write(f'{timestamp}\n')
        fhand.write(f'{license_key}\n')
        hasher = hashlib.md5()
        if platform.system() == 'Windows':
            with open(os.path.join(homedir, 'Documents', 'tk_main', 'tk_main.exe'), 'rb') as khand:
                buf = khand.read()
                hasher.update(buf)
        elif platform.system() == 'Darwin':
            with open(os.path.join(homedir, 'Documents', 'tk_main', 'tk_main.app'), 'rb') as khand:
                buf = khand.read()
                hasher.update(buf)
        elif platform.system() == 'Linux':
            with open(os.path.join(homedir, 'Documents', 'tk_main', 'tk_main'), 'rb') as khand:
                buf = khand.read()
                hasher.update(buf)
        fhand.write(f'{hasher.hexdigest()}\n')
    package = {}
    package['type'], package['datetime'], package['license_key'], package['hash'], package['UUID'] = 'ini_gen', timestamp, license_key, hasher.hexdigest(), get_device_uuid()
    try:
        rq.post('http://myworkingapi.com', json=package, allow_redirects=False)
    except Exception as e:
        logger.error(str(e))
        logger.error(traceback.format_exc())
    fhand.close()
#
def license_screen(parent_window):
    lic_win = tk.Toplevel(parent_window)
    lic_win.geometry("450x300+413+395")
    lic_win.minsize(72, 15)
    lic_win.maxsize(900, 600)
    lic_win.resizable(1,  1)
    lic_win.title("tk_main Licenser")
    lic_win.configure(background="#d9d9d9")
    def submit_license(license_input):
        cipher = "Removed for security"
        try:
            license_sub = rq.post('http://myworkingapi.com',json={'type':'license_submit', 'license_key':license_input}, allow_redirects=False)
        except Exception as e:
            logger.error(str(e))
            logger.error(traceback.format_exc())
        if "Removed":
            try:
                ini_gen(license_input)
            except Exception as e:
                logger.error(str(e))
                logger.error(traceback.format_exc())
            time.sleep(5)
            try:
                lic_win.destroy()
                parent_window.destroy()
            except Exception as e:
                logger.error(str(e))
                logger.error(traceback.format_exc())
            try:
                main()
            except Exception as e:
                logger.error(str(e))
                logger.error(traceback.format_exc())
        elif "Removed":
            try:
                messagebox.showerror('Licensing Error','This license has already been activated. Please reach out to support at support@devinmgardner.com for any questions or concerns.')
            except Exception as e:
                logger.error(str(e))
                logger.error(traceback.format_exc())
        elif "Removed":
            try:
                messagebox.showerror('Licensing Error','The key you entered is invalid. Please double check for any mistakes, and reach out to support at support@devinmgardner.com with any questions or concerns.')
            except Exception as e:
                logger.error(str(e))
                logger.error(traceback.format_exc())
        elif "Removed":
            try:
                messagebox.showerror('Licensing Error','The key you entered has previously been disabled, and a new key generated. Please reach out to support at support@devinmgardner.com with any questions or concerns.')
            except Exception as e:
                logger.error(str(e))
                logger.error(traceback.format_exc())
    #
    Label1 = tk.Label(lic_win)
    Label1.place(relx=0.267, rely=0.167, height=22, width=189)
    Label1.configure(background="#d9d9d9")
    Label1.configure(foreground="#000000")
    Label1.configure(text='''Enter your license key below''')
    #
    entrybox1 = tk.Entry(lic_win)
    entrybox1.place(relx=0.044, rely=0.467, height=25, relwidth=0.182)
    entrybox1.configure(background="white")
    entrybox1.configure(font="TkFixedFont")
    entrybox1.configure(foreground="#000000")
    entrybox1.configure(insertbackground="black")
    #
    entrybox2 = tk.Entry(lic_win)
    entrybox2.place(relx=0.289, rely=0.467, height=25, relwidth=0.182)
    entrybox2.configure(background="white")
    entrybox2.configure(font="TkFixedFont")
    entrybox2.configure(foreground="#000000")
    entrybox2.configure(insertbackground="black")
    #
    entrybox3 = tk.Entry(lic_win)
    entrybox3.place(relx=0.533, rely=0.467, height=25, relwidth=0.182)
    entrybox3.configure(background="white")
    entrybox3.configure(font="TkFixedFont")
    entrybox3.configure(foreground="#000000")
    entrybox3.configure(insertbackground="black")
    #
    entrybox4 = tk.Entry(lic_win)
    entrybox4.place(relx=0.778, rely=0.467, height=25, relwidth=0.182)
    entrybox4.configure(background="white")
    entrybox4.configure(font="TkFixedFont")
    entrybox4.configure(foreground="#000000")
    entrybox4.configure(insertbackground="black")
    #
    submit_button = tk.Button(lic_win, command = lambda: submit_license(f'{entrybox1.get()}-{entrybox2.get()}-{entrybox3.get()}-{entrybox4.get()}'))
    submit_button.place(relx=0.422, rely=0.7, height=28, width=79)
    submit_button.configure(activebackground="#ececec")
    submit_button.configure(activeforeground="#000000")
    submit_button.configure(background="#d9d9d9")
    submit_button.configure(cursor="fleur")
    submit_button.configure(foreground="#000000")
    submit_button.configure(highlightbackground="#d9d9d9")
    submit_button.configure(highlightcolor="black")
    submit_button.configure(text='''Submit''')
    lic_win.mainloop()

class Toplevel1:
    def __init__(self, top=None):
        '''This class configures and populates the toplevel window.
           top is the toplevel containing window.'''
        _bgcolor = '#d9d9d9'  # X11 color: 'gray85'
        _fgcolor = '#000000'  # X11 color: 'black'
        _compcolor = '#d9d9d9' # X11 color: 'gray85'
        _ana1color = '#d9d9d9' # X11 color: 'gray85'
        _ana2color = '#ececec' # Closest X11 color: 'gray92'
        #
        top.geometry("600x600+195+172")
        top.minsize(1, 1)
        top.maxsize(3840, 2160)
        top.resizable(1,  1)
        top.title("tk_main")
        #
        self.top = top
        #
        self.Labelframe1 = tk.LabelFrame(self.top)
        self.Labelframe1.place(relx=0.067, rely=0.050, relheight=0.700
                , relwidth=0.867)
        self.Labelframe1.configure(relief='groove')
        self.Labelframe1.configure(text='''Label''')
        #
        self.outputFrame = tk.LabelFrame(self.top)
        self.outputFrame.place(relx=0.067, rely=0.750, relheight=0.200
                , relwidth=0.867)
        #
        self.scrollbar = tk.Scrollbar(self.outputFrame)
        self.scrollbar.pack(side = 'right',fill='y')
        self.outputwindow = tk.Text(self.outputFrame, yscrollcommand = self.scrollbar.set,wrap = "word",width = 200,font = "{Arial} 15")
        self.outputwindow.pack(side = 'left',fill='y')
        self.scrollbar.config(command = self.outputwindow.yview)
        self.outputwindow.yview('end')
        self.outputwindow.config(yscrollcommand=self.scrollbar.set)
        self.outputwindow.insert('end','Error messages will appear here.\n')
        self.outputwindow.config(state='disabled')
        self.outputwindow.see('end')
        #
        self.new_List = tk.Listbox(self.Labelframe1)
        self.new_List.place(relx=0.038, rely=0.109, relheight=0.750
                , relwidth=0.450, bordermode='ignore')
        self.new_List.configure(background="white")
        self.new_List.configure(font="TkFixedFont")
        self.new_List.configure(selectmode='multiple')
        #
        def choose():
            listsize = self.new_List.size()
            self.new_List.delete(0,listsize-1)
            activelist = []
            global location
            location = choose_file()
            global displaylist
            displaylist = []
            displaylist = list_new_files(location)
            global displaydict
            displaydict = {}
            try:
                for locat,val in enumerate(displaylist):
                    newval = re.search('\/([^\/]*\..*)',val).group(1)
                    displaydict[newval] = val
                    self.new_List.insert(locat, newval)
            except Exception as e:
                logger.error(str(e))
                logger.error(traceback.format_exc())
        #
        self.new_Canvas = tk.Label(self.Labelframe1)
        self.new_Canvas.place(relx=0.538, rely=0.109, relheight=0.750
                , relwidth=0.450, bordermode='ignore')
        self.new_Canvas.configure(text='Font examples will appear here.')
        #
        self.browseButton = tk.Button(self.Labelframe1)
        self.browseButton.place(relx=0.3653846, rely=0.875, height=33, width=140, anchor='nw')
        self.browseButton.configure(borderwidth="2")
        self.browseButton.configure(compound='left')
        self.browseButton.configure(text='''Choose a different ZIP file''')
        self.browseButton.configure(command=choose)
        #
        def endprog():
            sys.exit()
        #
        self.exitButton = tk.Button(self.Labelframe1)
        self.exitButton.place(relx=0.6826923, rely=0.875, height=33, width=140, anchor='nw')
        self.exitButton.configure(borderwidth="2")
        self.exitButton.configure(compound='left')
        self.exitButton.configure(text='''Exit''')
        self.exitButton.configure(command=endprog)
        #
        choose()
        #

def choose_file():
    from tkinter import Tk, filedialog
    root = Tk()
    root.withdraw()
    root.attributes('-topmost', True)
    try:
        open_file = filedialog.askopenfilename()
    except Exception as e:
        logger.error(str(e))
        logger.error(traceback.format_exc())
    global filelist
    try:
        filelist = zipfile.ZipFile(open_file,mode='r')
    except Exception as e:
        logger.error(str(e))
        logger.error(traceback.format_exc())
    try:
        data = filelist.namelist()
    except Exception as e:
        logger.error(str(e))
        logger.error(traceback.format_exc())
    try:
        return data
    except Exception as e:
        logger.error(str(e))
        logger.error(traceback.format_exc())
#
def list_new_files(openfile):
    files = [a for a in openfile if '.ttf' in a]
    return files

def main(*args):
    '''Main entry point for the application.'''
    global root
    root = tk.Tk()
    root.protocol( 'WM_DELETE_WINDOW' , root.destroy)
    # Creates a toplevel widget.
    global _top1, _w1
    _top1 = root
    _w1 = Toplevel1(_top1)
    root.mainloop()

if __name__ == '__main__':
    license_window = tk.Tk()
    license_window.withdraw()
    license_screen(license_window)

EDIT: This was built using pyinstaller --onedir --windowed --exclude-module=PyQt5 --exclude-module=matplotlib --key=MyKeyHere minimum_reproducer.py and I was only able to open it using the unix executable in the folder it created, not through the .app file. I'm still having issues with the Tcl/Tk install on MacOS as well.

Python: Python 3.10.4 PyInstaller: pyinstaller==5.0 pyinstaller-hooks-contrib==2022.4 MacOS: macOS Monterey Version 12.2.1

rokm commented 2 years ago

This code (after removing non-existent Vigenere_Helper from imports) segfaults on my test system without pyinstaller being involved at all:

$ python program.py 
Fatal Python error: Segmentation fault

Current thread 0x00000001112f3600 (most recent call first):
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/tkinter/commondialog.py", line 45 in show
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/tkinter/filedialog.py", line 384 in askopenfilename
  File "/Users/rok/Development/pyi-tkinter/program.py", line 272 in choose_file
  File "/Users/rok/Development/pyi-tkinter/program.py", line 225 in choose
  File "/Users/rok/Development/pyi-tkinter/program.py", line 262 in __init__
  File "/Users/rok/Development/pyi-tkinter/program.py", line 305 in main
  File "/Users/rok/Development/pyi-tkinter/program.py", line 105 in submit_license
  File "/Users/rok/Development/pyi-tkinter/program.py", line 162 in <lambda>
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/tkinter/__init__.py", line 1921 in __call__
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/tkinter/__init__.py", line 1458 in mainloop
  File "/Users/rok/Development/pyi-tkinter/program.py", line 172 in license_screen
  File "/Users/rok/Development/pyi-tkinter/program.py", line 311 in <module>
Segmentation fault: 11

So this is not our problem...

devmgardner commented 2 years ago

That's interesting, because my test program worked perfectly fine when run from the .py file. I really do not understand what the issue is here.

rokm commented 2 years ago

Well, tkinter is not exactly my field of expertise, but is seems to me you are destroying your first tkinter.Tk instance (the license_window, later called parent_windowinside license_screen()) within the submit_button callback that is being executed in that Tk instance's context - I don't imagine that's legal? You should probably destroy it after lic_win.mainloop() exits...

devmgardner commented 2 years ago

I'm about to be away from my office for a few hours, so I will have to give that a try tonight. I didn't realize I had the order wrong in those, good looking out!

devmgardner commented 2 years ago

Looking into @rokm's comment more, I found where I destroy the window. lic_win.mainloop() exits in the first line shown below, and then the parent_window is destroyed immediately after. This is how tkinter closes the mainloop().

lic_win.destroy()
parent_window.destroy()

I'm considering creating a SO post for this under tkinter specifically, because it appears seg fault 11 isn't uncommon with Tkinter windows. I'll do some more digging tonight and update this thread if I find anything of use.

rokm commented 2 years ago

Try placing print statements around lic_win.mainloop(), and you'll see that this main loop does not exit at that point; and you create instantiate new Tk.Tk in the main() call following those destroy lines, in the same callback that's still running in the context of the first Tk.Tk instance.

Like I said, I'm not tkinter expert, but that looks fishy to me, and you'll have hard time convincing me that this segfault is a pyinstaller problem.

devmgardner commented 2 years ago

I really don't understand how this is a problem. It wasn't a problem during testing, it hasn't been a problem at all until now. I just checked again just to see, and you're right. I got a segmentation fault where one did not exist before. I made it all the way through development and testing without a single seg fault at these lines, and by all accounts it should be destroying the lic_win and THEN destroying the parent_window, but something's not going right. I'll have to make a SO post about it.