aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--justfile3
-rw-r--r--tools/build_gui.py100
2 files changed, 101 insertions, 2 deletions
diff --git a/justfile b/justfile
index fe942fc..cef7016 100644
--- a/justfile
+++ b/justfile
@@ -8,6 +8,5 @@ gui:
# Build the picostream GUI executable
build-gui:
uv pip install pyinstaller pyinstaller-hooks-contrib
- FFI_FLAG := `uv run python -c 'import sys, os, glob; ffi_dll_path = next(glob.iglob(os.path.join(os.path.dirname(sys.executable), "DLLs", "libffi-*.dll")), None) if sys.platform == "win32" else None; ffi_dll_path and print(f"--add-binary \"{ffi_dll_path}{os.pathsep}.\"")'`
- uv run pyinstaller --onefile --windowed --name PicoStream {{FFI_FLAG}} picostream/main.py
+ uv run python tools/build_gui.py
diff --git a/tools/build_gui.py b/tools/build_gui.py
new file mode 100644
index 0000000..e912d8d
--- /dev/null
+++ b/tools/build_gui.py
@@ -0,0 +1,100 @@
+import glob
+import os
+import platform
+import sys
+
+try:
+ import PyInstaller.__main__
+except ImportError:
+ print("Error: PyInstaller is not installed. Please run 'uv pip install pyinstaller'.")
+ sys.exit(1)
+
+
+def find_libffi_dll() -> str | None:
+ """
+ Find the libffi-*.dll file required for _ctypes on Windows.
+
+ Searches in common locations:
+ 1. The active Python environment's DLLs directory.
+ 2. The base Python installation's DLLs directory (if in a venv).
+ 3. A Conda environment's Library/bin directory.
+ """
+ if sys.platform != "win32":
+ return None
+
+ search_paths = []
+ # 1. Active environment's DLLs directory (sys.prefix is the venv path)
+ search_paths.append(os.path.join(sys.prefix, "DLLs"))
+
+ # 2. Base Python installation's DLLs directory (if in a venv)
+ if hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix:
+ search_paths.append(os.path.join(sys.base_prefix, "DLLs"))
+
+ # 3. Conda environment's Library/bin directory
+ conda_prefix = os.environ.get("CONDA_PREFIX")
+ if conda_prefix:
+ search_paths.append(os.path.join(conda_prefix, "Library", "bin"))
+
+ print("--- Searching for libffi DLL on Windows ---")
+ unique_paths = sorted(list(set(p for p in search_paths if os.path.isdir(p))))
+
+ for path in unique_paths:
+ print(f" - Checking: {path}")
+ dll_pattern = os.path.join(path, "libffi-*.dll")
+ found_dlls = glob.glob(dll_pattern)
+ if found_dlls:
+ dll_path = found_dlls[0]
+ print(f" - Found: {dll_path}")
+ return dll_path
+
+ return None
+
+
+def main() -> None:
+ """
+ Build the PicoStream GUI executable using PyInstaller.
+ """
+ print(f"--- Starting PicoStream Build ---")
+ print(f"Python: {sys.version} ({platform.architecture()[0]}) on {sys.platform}")
+
+ entry_script = "picostream/main.py"
+ app_name = "PicoStream"
+
+ pyinstaller_args = [
+ "--onefile",
+ "--windowed",
+ f"--name={app_name}",
+ ]
+
+ if sys.platform == "win32":
+ libffi_path = find_libffi_dll()
+ if libffi_path:
+ # The format for --add-binary is "source;destination" on Windows.
+ # We add the DLL to the root of the bundle.
+ pyinstaller_args.append(f"--add-binary={libffi_path}{os.pathsep}.")
+ else:
+ print("\nERROR: Could not find libffi-*.dll on Windows.")
+ print("This DLL is required for the _ctypes module to work correctly.")
+ print("Searched in the following directories:")
+ for path in sorted(list(set(p for p in search_paths if os.path.isdir(p)))):
+ print(f" - {path}")
+ print("Please ensure you are using a standard CPython or Conda installation.")
+ sys.exit(1)
+
+ # Add the entry script at the end
+ pyinstaller_args.append(entry_script)
+
+ print("\n--- Running PyInstaller ---")
+ print(f"Arguments: {' '.join(pyinstaller_args)}")
+
+ PyInstaller.__main__.run(pyinstaller_args)
+
+ print(f"\n--- Build Complete ---")
+ if sys.platform == "win32":
+ print(f"Executable created at: dist\\{app_name}.exe")
+ else:
+ print(f"Executable created at: dist/{app_name}")
+
+
+if __name__ == "__main__":
+ main()