Add Ubuntu 22.04/24.04 compatibility and Linux packaging script. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
251 lines
7.2 KiB
Python
251 lines
7.2 KiB
Python
"""Build Ameba Control Panel Linux binary with PyInstaller (onedir)."""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def _add_data(src: Path, dest: str) -> str:
|
|
return f"{src}:{dest}"
|
|
|
|
|
|
_EXCLUDES = [
|
|
"tkinter",
|
|
"matplotlib",
|
|
"numpy",
|
|
"scipy",
|
|
"pandas",
|
|
"PIL",
|
|
"PySide6.QtWebEngine",
|
|
"PySide6.QtWebEngineCore",
|
|
"PySide6.QtWebEngineWidgets",
|
|
"PySide6.Qt3DCore",
|
|
"PySide6.Qt3DRender",
|
|
"PySide6.Qt3DInput",
|
|
"PySide6.Qt3DAnimation",
|
|
"PySide6.Qt3DExtras",
|
|
"PySide6.Qt3DLogic",
|
|
"PySide6.QtCharts",
|
|
"PySide6.QtDataVisualization",
|
|
"PySide6.QtMultimedia",
|
|
"PySide6.QtMultimediaWidgets",
|
|
"PySide6.QtQuick",
|
|
"PySide6.QtQuick3D",
|
|
"PySide6.QtQuickWidgets",
|
|
"PySide6.QtQml",
|
|
"PySide6.QtRemoteObjects",
|
|
"PySide6.QtSensors",
|
|
"PySide6.QtSerialBus",
|
|
"PySide6.QtBluetooth",
|
|
"PySide6.QtNfc",
|
|
"PySide6.QtPositioning",
|
|
"PySide6.QtLocation",
|
|
"PySide6.QtTest",
|
|
"PySide6.QtPdf",
|
|
"PySide6.QtPdfWidgets",
|
|
"PySide6.QtSvgWidgets",
|
|
"PySide6.QtNetworkAuth",
|
|
"PySide6.QtDesigner",
|
|
"PySide6.QtHelp",
|
|
"PySide6.QtOpenGL",
|
|
"PySide6.QtOpenGLWidgets",
|
|
"PySide6.QtSpatialAudio",
|
|
"PySide6.QtStateMachine",
|
|
"PySide6.QtTextToSpeech",
|
|
"PySide6.QtHttpServer",
|
|
"PySide6.QtGraphs",
|
|
]
|
|
|
|
_DESKTOP_ENTRY = """\
|
|
[Desktop Entry]
|
|
Name=Ameba Control Panel
|
|
Comment=Realtek Ameba device control panel for serial communication, flashing, and debugging
|
|
Exec={exec_path}
|
|
Icon={icon_path}
|
|
Terminal=false
|
|
Type=Application
|
|
Categories=Development;Electronics;
|
|
StartupWMClass=AmebaControlPanel
|
|
"""
|
|
|
|
_UDEV_RULES = """\
|
|
# Ameba Control Panel - USB serial device permissions
|
|
# Install: sudo cp 99-ameba-serial.rules /etc/udev/rules.d/
|
|
# Reload: sudo udevadm control --reload-rules && sudo udevadm trigger
|
|
|
|
# FTDI devices (FT232, FT2232, etc.)
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", MODE="0666"
|
|
|
|
# Silicon Labs CP210x
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", MODE="0666"
|
|
|
|
# WCH CH340/CH341
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", MODE="0666"
|
|
|
|
# Prolific PL2303
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="067b", MODE="0666"
|
|
|
|
# Realtek devices
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="0bda", MODE="0666"
|
|
|
|
# SEGGER J-Link
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="1366", MODE="0666"
|
|
|
|
# STMicroelectronics STLink
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", MODE="0666"
|
|
|
|
# Generic USB CDC ACM devices (covers most USB-to-serial adapters)
|
|
SUBSYSTEM=="tty", KERNEL=="ttyACM[0-9]*", MODE="0666"
|
|
SUBSYSTEM=="tty", KERNEL=="ttyUSB[0-9]*", MODE="0666"
|
|
"""
|
|
|
|
_LAUNCH_SCRIPT = """\
|
|
#!/bin/bash
|
|
# Ameba Control Panel launcher
|
|
# Handles X11/Wayland compatibility
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
# Detect display server and set Qt platform if needed
|
|
if [ -n "$WAYLAND_DISPLAY" ]; then
|
|
# Wayland session — let Qt use wayland or xcb fallback
|
|
export QT_QPA_PLATFORM="${{QT_QPA_PLATFORM:-wayland;xcb}}"
|
|
elif [ -n "$DISPLAY" ]; then
|
|
# X11 session
|
|
export QT_QPA_PLATFORM="${{QT_QPA_PLATFORM:-xcb}}"
|
|
fi
|
|
|
|
# Ensure bundled libs are found
|
|
export LD_LIBRARY_PATH="$SCRIPT_DIR:$LD_LIBRARY_PATH"
|
|
|
|
exec "$SCRIPT_DIR/AmebaControlPanel" "$@"
|
|
"""
|
|
|
|
|
|
def _generate_support_files(dist_dir: Path) -> None:
|
|
"""Generate udev rules, desktop entry, and launcher script."""
|
|
app_dir = dist_dir / "AmebaControlPanel"
|
|
app_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Udev rules
|
|
rules_path = app_dir / "99-ameba-serial.rules"
|
|
rules_path.write_text(_UDEV_RULES, encoding="utf-8")
|
|
print(f" udev rules: {rules_path}")
|
|
|
|
# Launcher script
|
|
launcher = app_dir / "ameba-control-panel.sh"
|
|
launcher.write_text(_LAUNCH_SCRIPT, encoding="utf-8")
|
|
launcher.chmod(0o755)
|
|
print(f" launcher: {launcher}")
|
|
|
|
# Desktop entry
|
|
desktop = app_dir / "ameba-control-panel.desktop"
|
|
desktop.write_text(
|
|
_DESKTOP_ENTRY.format(
|
|
exec_path=str(app_dir / "ameba-control-panel.sh"),
|
|
icon_path=str(app_dir / "icon.png") if (app_dir / "icon.png").exists() else "",
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
print(f" desktop: {desktop}")
|
|
|
|
# Install helper
|
|
install_script = app_dir / "install.sh"
|
|
install_script.write_text(
|
|
"#!/bin/bash\n"
|
|
"set -e\n"
|
|
'SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"\n'
|
|
"\n"
|
|
"echo 'Installing udev rules for serial port access...'\n"
|
|
'sudo cp "$SCRIPT_DIR/99-ameba-serial.rules" /etc/udev/rules.d/\n'
|
|
"sudo udevadm control --reload-rules\n"
|
|
"sudo udevadm trigger\n"
|
|
"echo 'Udev rules installed.'\n"
|
|
"\n"
|
|
"echo 'Installing desktop entry...'\n"
|
|
'DESKTOP_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications"\n'
|
|
'mkdir -p "$DESKTOP_DIR"\n'
|
|
'sed "s|Exec=.*|Exec=$SCRIPT_DIR/ameba-control-panel.sh|" '
|
|
'"$SCRIPT_DIR/ameba-control-panel.desktop" > "$DESKTOP_DIR/ameba-control-panel.desktop"\n'
|
|
"echo 'Desktop entry installed.'\n"
|
|
"\n"
|
|
"echo 'Done. You may need to log out and back in for serial port permissions to take effect.'\n",
|
|
encoding="utf-8",
|
|
)
|
|
install_script.chmod(0o755)
|
|
print(f" installer: {install_script}")
|
|
|
|
|
|
def build(*, icon: str | None = None) -> None:
|
|
root = Path(__file__).resolve().parent.parent
|
|
entry = root / "script" / "auto_run.py"
|
|
flash_dir = root / "Flash"
|
|
|
|
if not entry.exists():
|
|
sys.exit(f"Entry script missing: {entry}")
|
|
if not flash_dir.exists():
|
|
sys.exit(f"Flash folder missing: {flash_dir}")
|
|
|
|
os.chdir(root)
|
|
|
|
try:
|
|
import PyInstaller.__main__ as pyinstaller
|
|
except ImportError:
|
|
sys.exit("PyInstaller is not installed. Run: pip install PyInstaller")
|
|
|
|
args = [
|
|
"--noconfirm",
|
|
"--clean",
|
|
"--onedir",
|
|
"--name=AmebaControlPanel",
|
|
f"--distpath={root / 'dist'}",
|
|
f"--workpath={root / 'build'}",
|
|
f"--specpath={root}",
|
|
"--paths", str(root),
|
|
"--collect-all", "PySide6",
|
|
"--hidden-import=serial",
|
|
"--hidden-import=serial.tools.list_ports",
|
|
"--hidden-import=pyDes",
|
|
"--hidden-import=colorama",
|
|
"--add-data", _add_data(flash_dir, "Flash"),
|
|
]
|
|
|
|
for mod in _EXCLUDES:
|
|
args.extend(["--exclude-module", mod])
|
|
|
|
if icon:
|
|
icon_path = Path(icon)
|
|
if icon_path.exists():
|
|
args.extend(["--icon", str(icon_path)])
|
|
else:
|
|
print(f"Warning: icon not found: {icon_path}", file=sys.stderr)
|
|
|
|
args.append(str(entry))
|
|
|
|
print("Building with PyInstaller...")
|
|
pyinstaller.run(args)
|
|
|
|
# Copy icon if provided
|
|
dist_app = root / "dist" / "AmebaControlPanel"
|
|
if icon and Path(icon).exists() and dist_app.exists():
|
|
shutil.copy2(icon, dist_app / "icon.png")
|
|
|
|
print("\nGenerating support files...")
|
|
_generate_support_files(root / "dist")
|
|
|
|
print(f"\nBuild complete: {dist_app}")
|
|
print("\nTo install on target system:")
|
|
print(f" 1. Copy {dist_app} to the target machine")
|
|
print(f" 2. Run: {dist_app}/install.sh")
|
|
print(f" 3. Launch: {dist_app}/ameba-control-panel.sh")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Build Ameba Control Panel for Linux")
|
|
parser.add_argument("--icon", help="Path to .png icon file")
|
|
opts = parser.parse_args()
|
|
build(icon=opts.icon)
|