diff --git a/.gitignore b/.gitignore index af65343..d4ad714 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __pycache__/ *.egg-info/ *.gguf *.log +*.exe log.txt .venv/ .env diff --git a/packaging/build.spec b/packaging/build.spec index c5d0423..b44ad42 100644 --- a/packaging/build.spec +++ b/packaging/build.spec @@ -69,6 +69,49 @@ a = Analysis( # noqa: F821 excludes=[], noarchive=False, ) + +# PyInstaller's own dependency walker separately discovers cublas64_13.dll +# and cublasLt64_13.dll (ggml-cuda.dll imports them directly) and stages its +# own copy at the bundle root, on top of the copy the `binaries` list above +# already places in llama_cpp/lib/ (which is where they need to be, next to +# ggml-cuda.dll, for ITS dependency lookup to find them - see the comment +# above). The root copy is pure duplication: nothing loads cublas from +# there. Drop it - it's ~547MB. +_DUPLICATED_AT_ROOT = {"cublas64_13.dll", "cublasLt64_13.dll"} +a.binaries = [ + entry + for entry in a.binaries + if not (Path(entry[0]).name in _DUPLICATED_AT_ROOT and Path(entry[0]).parent == Path(".")) +] + +# Qt/PyQt6 features this app doesn't use, bundled defensively by PyQt6's +# PyInstaller hook. Confirmed safe to drop: Qt6Widgets/Qt6Gui/Qt6Core don't +# link any of these at the DLL level (only load them on demand), no +# PyQt6.QtNetwork/QtSvg/QtPdf Python module is bundled or imported anywhere +# in this app, no QTranslator is ever installed, and app.setStyle("Fusion") +# in gui.py forces a style that's built into Qt6Widgets itself rather than +# loaded from the styles/ plugin. +_UNUSED_QT_BASENAMES = { + "opengl32sw.dll", # software OpenGL fallback; no QOpenGLWidget/3D used + "qt6pdf.dll", + "qt6network.dll", + "qt6svg.dll", + "qsvg.dll", # imageformats SVG plugin + "qsvgicon.dll", # iconengines SVG plugin + "qpdf.dll", # imageformats PDF plugin (depends on the Qt6Pdf.dll dropped above) + "qmodernwindowsstyle.dll", # styles plugin; app always forces Fusion +} + + +def _is_unused_qt_binary(dest: str) -> bool: + return Path(dest).name.lower() in _UNUSED_QT_BASENAMES + + +a.binaries = [entry for entry in a.binaries if not _is_unused_qt_binary(entry[0])] +# Qt's .qm translation files ship as data, not binaries - this app never +# installs a QTranslator, so none of the 96 bundled languages are ever used. +a.datas = [entry for entry in a.datas if not str(entry[0]).lower().endswith(".qm")] + pyz = PYZ(a.pure) # noqa: F821 exe = EXE( # noqa: F821