From 3db44fe4e610f3e79dc06aa31c392a72557cecf0 Mon Sep 17 00:00:00 2001 From: alexpolo1 Date: Sat, 20 Jun 2026 09:47:41 +0200 Subject: [PATCH] build: bundle conda native DLLs + fix shopper SSL crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified by building locally and running both exes to their menus. build.py: prepend the conda env's Library\bin, Library\lib and DLLs dirs to PATH before invoking PyInstaller. PyInstaller resolves binary deps via PATH (not --paths, which only affects Python imports), so without this the frozen exe crashed at startup with "DLL load failed while importing _ctypes" (missing ffi-8.dll, plus liblzma/libbz2). Now ffi-8/lzma/bz2/leptonica/tesseract52 all bundle. src/shopper.py: mirror main.py's startup header — add the ssl.load_default_certs monkey-patch (corrupted Windows cert store made aiohttp crash at import with ASN1 NOT_ENOUGH_DATA) and drop Library\bin from os.add_dll_directory (it ships mismatched OpenSSL DLLs that break _ssl). shopper.exe previously crashed on any machine with a bad cert store; CI's clean runner masked it. Co-Authored-By: Claude Sonnet 4.6 --- build.py | 15 ++++++++++++++- src/shopper.py | 20 +++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/build.py b/build.py index c540f8d..7035671 100644 --- a/build.py +++ b/build.py @@ -78,7 +78,20 @@ if __name__ == "__main__": key_cmd = " --key " + key botty_env = os.path.join(args.conda_path, "envs", "botty") pyinstaller_exe = os.path.join(botty_env, "Scripts", "pyinstaller.exe") - installer_cmd = f"{pyinstaller_exe} --onefile --noconsole --distpath {botty_dir}{key_cmd} --exclude-module graphviz --exclude-module keyboard --exclude-module mouse --exclude-module pyclick --exclude-module mouseinfo --paths .\\src --paths {botty_env}\\Lib\\site-packages src\\{exe}" + # Conda ships native DLLs (ffi-8/liblzma/libbz2 for _ctypes/_lzma/_bz2, + # plus leptonica/tesseract52 for tesserocr) in Library\bin and DLLs. + # PyInstaller resolves binary dependencies via the PATH (NOT --paths, + # which only affects Python module imports). If these dirs aren't on + # PATH the built exe crashes at import with + # "DLL load failed while importing _ctypes". Prepend them for both + # local and CI builds. + dll_dirs = [ + os.path.join(botty_env, "Library", "bin"), + os.path.join(botty_env, "Library", "lib"), + os.path.join(botty_env, "DLLs"), + ] + os.environ["PATH"] = os.pathsep.join(dll_dirs) + os.pathsep + os.environ.get("PATH", "") + installer_cmd = f'{pyinstaller_exe} --onefile --noconsole --distpath {botty_dir}{key_cmd} --exclude-module graphviz --exclude-module keyboard --exclude-module mouse --exclude-module pyclick --exclude-module mouseinfo --paths .\\src --paths "{botty_env}\\Lib\\site-packages" src\\{exe}' ret = os.system(installer_cmd) if ret != 0: raise RuntimeError(f"PyInstaller failed for {exe} (exit {ret})") diff --git a/src/shopper.py b/src/shopper.py index 8999b4d..67de823 100644 --- a/src/shopper.py +++ b/src/shopper.py @@ -1,14 +1,32 @@ # Fix: On Windows, Python 3.8+ requires os.add_dll_directory for conda-forge DLLs. +# Must run BEFORE any import that might trigger tesserocr loading. +# IMPORTANT: Do NOT add Library/bin - it has mismatched OpenSSL DLLs that break _ssl. import os, sys if sys.platform == "win32": for _d in [ - os.path.join(sys.prefix, "Library", "bin"), os.path.join(sys.prefix, "Library", "mingw-w64", "bin"), os.path.join(sys.prefix, "Library", "usr", "bin"), ]: if os.path.isdir(_d): os.add_dll_directory(_d) +# Fix: OpenSSL 3.x + corrupted Windows cert store causes aiohttp to crash at import time. +# Monkey-patch ssl.create_default_context to skip Windows cert store loading. +if sys.platform == "win32": + import ssl + _orig_load_default_certs = ssl.SSLContext.load_default_certs + def _safe_load_default_certs(self, purpose=ssl.Purpose.CLIENT_AUTH): + try: + _orig_load_default_certs(self, purpose) + except ssl.SSLError: + # Windows cert store is corrupted, use certifi instead + try: + import certifi + self.load_verify_locations(certifi.where()) + except Exception: + pass # No certs available, continue without verification + ssl.SSLContext.load_default_certs = _safe_load_default_certs + from beautifultable import BeautifulTable import logging import traceback