aboutsummaryrefslogtreecommitdiff
path: root/tools/build_gui.py
blob: c9811a3156482fa04ab8b6f2882241585fa86d71 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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() -> tuple[str | None, list[str]]:
    """
    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.

    Returns:
        A tuple containing the path to the DLL (or None) and a list of
        directories that were searched.
    """
    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, unique_paths

    return None, unique_paths


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, searched_paths = 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 searched_paths:
                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()