"""PyInstaller freeze helpers: pin CPython Tcl/Tk, drop Graphviz's copy.""" from __future__ import annotations import os import sys from pathlib import Path def _python_dll_dir() -> Path | None: candidates = [ Path(sys.base_prefix) / "DLLs", Path(sys.prefix) / "DLLs", Path(sys.executable).resolve().parent / "DLLs", Path(sys.executable).resolve().parent.parent / "DLLs", ] for folder in candidates: if (folder / "tcl86t.dll").is_file() or (folder / "tk86t.dll").is_file(): return folder return None def prepare_tcltk_env() -> None: """Make Analysis / isolated hook subprocesses see this interpreter's Tcl.""" dll_dir = _python_dll_dir() prefix = Path(sys.base_prefix) tcl_lib = prefix / "tcl" / "tcl8.6" tk_lib = prefix / "tcl" / "tk8.6" if not tcl_lib.is_dir() and dll_dir is not None: tcl_lib = dll_dir.parent / "tcl" / "tcl8.6" tk_lib = dll_dir.parent / "tcl" / "tk8.6" if tcl_lib.is_dir(): os.environ["TCL_LIBRARY"] = str(tcl_lib) if tk_lib.is_dir(): os.environ["TK_LIBRARY"] = str(tk_lib) if dll_dir is not None: os.environ["PATH"] = str(dll_dir) + os.pathsep + os.environ.get("PATH", "") def collect_tkinterdnd2(): datas: list = [] hidden: list = ["tkinterdnd2", "tkinterdnd2.TkinterDnD"] try: from PyInstaller.utils.hooks import collect_data_files, collect_submodules datas += collect_data_files("tkinterdnd2") hidden = list(collect_submodules("tkinterdnd2")) or hidden except Exception: pass return datas, hidden def _toc_name(item) -> str: dest = str(item[0]).replace("\\", "/") return os.path.basename(dest).lower() def _toc_blob(item) -> str: dest = str(item[0]).replace("\\", "/").lower() src = str(item[1]).replace("\\", "/").lower() if len(item) > 1 else "" return dest + " " + src def _graphviz_tcl_names() -> set[str]: try: from md2gost.dfd import GRAPHVIZ_TCL_DLL_NAMES return {n.lower() for n in GRAPHVIZ_TCL_DLL_NAMES} except Exception: return { "tcl86t.dll", "tk86t.dll", "tcl86.dll", "tk86.dll", "tcldot.dll", "tcldot_builtin.dll", "tclplan.dll", "gdtclft.dll", } def _is_graphviz_tcl(item) -> bool: if _toc_name(item) not in _graphviz_tcl_names(): return False return "graphviz" in _toc_blob(item) def _wrap_toc(items): try: from PyInstaller.building.datastruct import TOC return TOC(items) except Exception: return items def sanitize_analysis(a): """Drop Graphviz Tcl/Tk DLLs and force this CPython's tcl86t/tk86t into the exe.""" a.datas = _wrap_toc([x for x in a.datas if not _is_graphviz_tcl(x)]) binaries = [x for x in a.binaries if not _is_graphviz_tcl(x)] dll_dir = _python_dll_dir() pin = ("tcl86t.dll", "tk86t.dll", "zlib1.dll") if dll_dir is not None: pin_lower = {n.lower() for n in pin} binaries = [x for x in binaries if _toc_name(x) not in pin_lower] for name in pin: path = dll_dir / name if path.is_file(): binaries.append((name, str(path), "BINARY")) a.binaries = _wrap_toc(binaries) return a