ttkbootstrap icon indicating copy to clipboard operation
ttkbootstrap copied to clipboard

Error after compiling with cx_freeze

Open actioncodes opened this issue 4 years ago • 9 comments

Desktop (please complete the following information):

OS: WINDOWS

Describe the bug

Hello, after compiling with cx_freeze, I get the error cannot import name "utility" from partially initialized module "ttkbootstrap" (most likely due to a circular import)

No vs code opens normally, this error occurs when opening the executable.

Any ideas on how to resolve this?

This happens in the code below.

import tkinter as tk
import ttkbootstrap as ttk
from ttkbootstrap.constants import *

class Main(tk.Tk):

    def __init__(self):
        tk.Tk.__init__(self)
        
        self.resizable(True, True)

        w = 1024
        h = 768
        ws = self.winfo_screenwidth()
        hs = self.winfo_screenheight()
        x = (ws/2) - (w/2)
        y = (hs/2) - (h/2)
        self.geometry('%dx%d+%d+%d' % (w, h, x, y))

        self.title("TITLE")
            
        b1 = ttk.Button(self, text="BUTTON 1", bootstyle=SUCCESS)
        b1.pack(side=LEFT, padx=5, pady=10)

        b2 = ttk.Button(self, text="BUTTON 2", bootstyle=(INFO, OUTLINE))
        b2.pack(side=LEFT, padx=5, pady=10)

if __name__ == "__main__":
 
    Main()
    tk.mainloop()

To Reproduce

No response

Expected behavior

No response

Screenshots

No response

Additional context

No response

actioncodes avatar Mar 15 '22 15:03 actioncodes

That's strange. I've not used cxfreeze before. I'll experiment with it this weekend and see if I can identify the issue.

israel-dryer avatar Mar 15 '22 17:03 israel-dryer

There may be a few issues here. One of the issues is that cx_freeze is not pulling in all of the tcl data files. Typically this is not an issue with Tkinter alone. However, I'm using additional TCL functionality that is defined in one of the tcl folders it is ignoring. This is not a ttkbootstrap bug, but I'll see if I can identify the cx_freeze configuration needed to pull these in.

israel-dryer avatar Mar 23 '22 20:03 israel-dryer

I had the same issue and got it solved through building with the following command which was inspired from a similar case I found on stackoverflow (https://stackoverflow.com/questions/66821044/circular-dependency-while-executing-cx-freeze-result):

python setup.py build_exe -p ttkbootstrap

Joker190714 avatar Apr 25 '22 09:04 Joker190714

@actioncodes, haven't forgotten about this, but looking for a better solution.

The missing files are in ...Python\Python39\tcl\tcl8

cx_Freeze is not grabbing these files. I'm not sure how to tell to grab the files either. But, you can manually copy and paste this folder into lib\tkinter in your build folder and it will work.

israel-dryer avatar May 05 '22 04:05 israel-dryer

This is not a bug with ttkbootstrap, but rather an issue with cx_Freeze not pulling in all of the required library files from tkinter. There is a work-around above where you can copy the missing folder "tcl8" into the build folder. I'm not familiar enough with cx_Freeze to propose a cx_Freeze solution, but perhaps someone in the community can figure that out.

israel-dryer avatar May 05 '22 04:05 israel-dryer

I built a solution that works in my cx_freeze setup scripts. I use a variable BUILDPATH to specify the directory for my distribution. After the call of the cx_freeze setup function I added a call to shutil.copytree to copy the missing tcl8 directory to the distribution directory.

from typing
import List
import shutil

import os
import sys
from cx_Freeze import setup, Executable


NAME = "PictureTools"
DESCRIPTION = "Bearbeiten von Bild Dateien"
VERSION = "1.0.0"

BUILDPATH = "..\\distribution\\PictureTools"

includes: List[str] = []
excludes: List[str] = ['bsddb', 'curses', 'pywin.debugger', 'pywin.debugger.dbgcon', 'pywin.dialogs',
            'asyncio', 'lib2to3', 'unittest', 'pydoc_data', 'email', 'html', 'http',
            'mypy', 'numpy', 'scipy', 'test', 'pycparser', 'wx', 'cffi', 'ctypes']

includefiles: List[str] = [".\\images\\"]
packages: List[str] = ['ttkbootstrap', 'tkinter']
path:List[str] = []


Target_1 = Executable(
    script = "picturetools.pyw",
    # initScript = None,
    base = 'Win32GUI',    # 'Win32GUI' or 'console' or "Win32Service ???"
    icon = '.\\images\\icon.ico',
    target_name = NAME + ".exe"
    )

# ------------------------------------------------------------------------------------------------------------
def main() -> None:
    """Hauptprogramm"""
    setup(version = VERSION,
            description=DESCRIPTION,
            author = "Heribert",
            name = NAME,
            packages = [],
            options = {"build_exe": {
                            "includes": includes,
                            "excludes": excludes,
                            "packages": packages,
                            "path": path,
                            "include_files": includefiles,
                            "silent": True,
                            "optimize": 2,
                            "build_exe": BUILDPATH
                            },
                    },
            executables = [Target_1]
    )

    # Copy tcl8 directory for bootstrap
    # Due to changes in the directory structure theta cx_freeze creates I changed that to 
    for mypath in sys.path:
        if os.path.exists(os.path.join(mypath, "tcl\\tcl8")):
            shutil.copytree(os.path.join(mypath, "tcl\\tcl8"), os.path.join(BUILDPATH, ".\\lib\\tcltk\\tcl8"))
            break
# ---------------------------------------------------------------------------
if __name__ == '__main__':
    main()

Heribert17 avatar Aug 02 '22 17:08 Heribert17

The above shutil doesn't find the tcl8 directory if you use a python virtual environment. So I modified it to work in venv:

    for mypath in sys.path:
        if os.path.exists(os.path.join(mypath, "tcl\\tcl8")):
            shutil.copytree(os.path.join(mypath, "tcl\\tcl8"), os.path.join(BUILDPATH, ".\\lib\\tcltk\\tcl8"))
            break

Heribert17 avatar Aug 06 '22 15:08 Heribert17

This is a hook for that problem. I inserted it into hooks/init.py in the new pre release 6.12.0.dev1

def load_ttkbootstrap(finder: ModuleFinder, module: Module) -> None:
    """The ttkbootstrap package needs the tcl8 directory.
    The tkinter module has data files (also called tcl/tk libraries) that
    are required to be loaded at runtime."""
    folders = []
    tcltk = get_resource_file_path("bases", "tcltk", "")
    if tcltk and tcltk.is_dir():
        # manylinux wheels and macpython wheels store tcl/tk libraries
        folders.append(("TCL_LIBRARY", list(tcltk.glob("tcl*"))[0]))
    else:
        # Windows, MSYS2, Miniconda: collect the tcl/tk libraries
        try:
            tkinter = __import__("tkinter")
        except (ImportError, AttributeError):
            return
        root = tkinter.Tk(useTk=False)
        source_path = Path(root.tk.exprstring("$tcl_library"))
        folders.append(("TCL_LIBRARY", source_path))
    for env_name, source_path in folders:
        # get the root tcl dir. If soure_path.name = tcl8.6 then tcl8
        new_name = os.path.splitext(source_path.name)[0]
        target_path = Path("lib", "tcltk", new_name)
        finder.include_files(os.path.join(os.path.split(source_path)[0], new_name), target_path)

Heribert17 avatar Sep 13 '22 20:09 Heribert17

Marcelo Duarte modified the latest cx_freeze development release 6.15.0.dev1 so that the tkinter hook now copies the tcl8 directory needed for ttkbootstrap. You can get it with: pip install --upgrade --pre --extra-index-url https://marcelotduarte.github.io/packages/ cx_Freeze

Heribert17 avatar Mar 02 '23 20:03 Heribert17