diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8acdc80..6230f58 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,15 +7,13 @@ on: - main jobs: - # First job to check if we should run the release check_release: runs-on: ubuntu-latest if: | - github.event.pull_request.merged == true && - (contains(github.event.pull_request.head.ref, 'hotfix/v') || - contains(github.event.pull_request.head.ref, 'Hotfix/v') || - contains(github.event.pull_request.head.ref, 'release/v') || - contains(github.event.pull_request.head.ref, 'Release/v')) + github.event.pull_request.merged == true && ( + contains(github.event.pull_request.head.ref, 'hotfix/v') || + contains(github.event.pull_request.head.ref, 'release/v') + ) outputs: version: ${{ steps.get_version.outputs.version }} steps: @@ -26,98 +24,44 @@ jobs: VERSION=${BRANCH#*/v} # Remove prefix up to v echo "version=$VERSION" >> $GITHUB_OUTPUT - # Build job that runs on each OS - build: + build-and-release: needs: check_release - strategy: - matrix: - include: - - os: windows-latest - build_os: windows - artifact_name: rtt-drone-gcs-windows.exe - release_name: RTT-Drone-GCS-windows-x64 - - os: ubuntu-latest - build_os: linux - artifact_name: rtt-drone-gcs-linux - release_name: RTT-Drone-GCS-linux-x64 - - runs-on: ${{ matrix.os }} - + runs-on: ubuntu-latest steps: - - name: Check out code - uses: actions/checkout@v3 - + - uses: actions/checkout@v3 + - name: Set up Python uses: actions/setup-python@v4 with: python-version: '3.13' - - - name: Install Windows SDK - if: matrix.os == 'windows-latest' - run: | - # Install Windows 10 SDK with required components - choco install windows-sdk-10-version-2004-all - + - name: Set up Node.js uses: actions/setup-node@v3 with: node-version: '22' - - name: Install Python dependencies + - name: Install Poetry run: | python -m pip install --upgrade pip pip install poetry - poetry install - - name: Install Linux system dependencies - if: matrix.os == 'ubuntu-latest' - run: | - sudo apt-get update - sudo apt-get install -y \ - libpulse0 \ - libxkbcommon-x11-0 \ - libxcb-cursor0 \ - libxcb-icccm4 \ - libxcb-keysyms1 \ - libxcb-shape0 \ - libxcb-xkb1 \ - libxcb-render-util0 \ - libxcb-image0 - - - name: Install Node.js dependencies + - name: Build frontend working-directory: frontend run: | - # Generate a fresh package-lock.json and install dependencies - npm install --package-lock-only npm install + npm run build - - name: Build for ${{ matrix.os }} - run: poetry run python scripts/build.py --os ${{ matrix.build_os }} - - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - name: ${{ matrix.release_name }} - path: dist/${{ matrix.artifact_name }} - - # Create release after all builds complete - create_release: - needs: [check_release, build] - runs-on: ubuntu-latest - steps: - - name: Download all artifacts - uses: actions/download-artifact@v3 - with: - path: dist + - name: Build wheel + run: poetry build - - name: Create Release + - name: Create GitHub Release uses: softprops/action-gh-release@v1 with: tag_name: v${{ needs.check_release.outputs.version }} name: Release v${{ needs.check_release.outputs.version }} files: | - dist/RTT-Drone-GCS-windows-x64/rtt-drone-gcs-windows.exe - dist/RTT-Drone-GCS-linux-x64/rtt-drone-gcs-linux + dist/*.whl + dist/*.tar.gz draft: false prerelease: false env: diff --git a/README.md b/README.md index 94d9591..e266d01 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ - [Features](#features) - [System Requirements](#system-requirements) - [Download and Installation](#download-and-installation) - - [Prebuilt Releases](#prebuilt-releases) + - [Using pip (from GitHub)](#using-pip-from-github) - [Building from Source](#building-from-source) - [Hot-running the app](#hot-running-the-app) - [Troubleshooting](#troubleshooting) @@ -48,11 +48,19 @@ The **GCS** complements the **FDS** software (which runs on the drone payload), ## Download and Installation -### Prebuilt Releases +### Using pip (from GitHub) -Precompiled executables are availabe under the [**Releases** tab](https://github.com/UCSD-E4E/radio-telemetry-tracker-drone-gcs/releases). +Install directly from the latest GitHub release: -Download the one for your platform, unzip it, and run the executable. +```bash +pip install https://github.com/UCSD-E4E/radio-telemetry-tracker-drone-gcs/releases/latest/download/radio_telemetry_tracker_drone_gcs-latest.whl +``` + +After installation, you can run the application with: + +```bash +rtt-drone-gcs +``` ### Building from Source @@ -78,11 +86,10 @@ cd radio-telemetry-tracker-drone-gcs npm install ``` -3. **Build** the app: +3. **Run** the app: ```bash - poetry run rtt-gcs-build + poetry run rtt-drone-gcs ``` - This generates a `dist/` folder with the executable. Note that PyInstaller only builds for the current platform. ### Hot-running the app diff --git a/pyproject.toml b/pyproject.toml index a6b56bf..b3c6f29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,12 +22,11 @@ scipy = "^1.15.1" [tool.poetry.group.dev.dependencies] ruff = "^0.8.4" pytest = "^8.3.4" -pyinstaller = "^6.11.1" pytest-qt = "^4.4.0" [tool.poetry.scripts] -rtt-gcs-dev = "scripts.dev:main" -rtt-gcs-build = "scripts.build:main" +rtt-drone-gcs-dev = "scripts.dev:main" +rtt-drone-gcs = "radio_telemetry_tracker_drone_gcs.main:main" [tool.ruff] line-length = 120 diff --git a/radio_telemetry_tracker_drone_gcs/main.py b/radio_telemetry_tracker_drone_gcs/main.py index 6ff7aaf..4b3048d 100644 --- a/radio_telemetry_tracker_drone_gcs/main.py +++ b/radio_telemetry_tracker_drone_gcs/main.py @@ -1,5 +1,6 @@ """Main entry point for the RTT Drone GCS application.""" +import logging import sys from PyQt6.QtWidgets import QApplication @@ -8,21 +9,26 @@ from radio_telemetry_tracker_drone_gcs.services.tile_db import init_db from radio_telemetry_tracker_drone_gcs.window import MainWindow +logger = logging.getLogger(__name__) def main() -> int: """Start the RTT Drone GCS application.""" - # Initialize DB (tiles + POIs) - init_db() - - app = QApplication(sys.argv) - window = MainWindow() - - # Create bridging object - bridge = CommunicationBridge() - window.set_bridge(bridge) - - window.show() - return app.exec() + try: + # Initialize DB (tiles + POIs) + init_db() + + app = QApplication(sys.argv) + window = MainWindow() + + # Create bridging object + bridge = CommunicationBridge() + window.set_bridge(bridge) + + window.show() + return app.exec() + except Exception: + logger.exception("Failed to initialize DB") + return 1 if __name__ == "__main__": diff --git a/scripts/build.py b/scripts/build.py deleted file mode 100644 index 867b977..0000000 --- a/scripts/build.py +++ /dev/null @@ -1,98 +0,0 @@ -"""Build script for creating an executable with PyInstaller. - -This script also handles building the frontend before bundling everything. -""" - -import argparse -import logging -import subprocess -import sys -from pathlib import Path - -from scripts.utils import build_frontend - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - - -def get_executable_name(os_type: str) -> str: - """Get the executable name based on the OS.""" - if os_type == "windows": - return "rtt-drone-gcs-windows.exe" - if os_type == "linux": - return "rtt-drone-gcs-linux" - msg = f"Unsupported OS: {os_type}" - raise ValueError(msg) - - -def validate_main_script(root_dir: Path) -> Path: - """Validate and return the path to the main script.""" - main_script = root_dir / "radio_telemetry_tracker_drone_gcs" / "main.py" - if not main_script.exists(): - msg = f"Main script not found at {main_script}" - raise FileNotFoundError(msg) - return main_script - - -def validate_output(dist_dir: Path) -> None: - """Validate that PyInstaller produced output files.""" - if not any(dist_dir.iterdir()): - msg = "PyInstaller did not produce any output files" - raise RuntimeError(msg) - - -def main() -> None: - """Build the executable using PyInstaller.""" - parser = argparse.ArgumentParser() - parser.add_argument("--os", choices=["windows", "linux"], required=True) - args = parser.parse_args() - - try: - root_dir = Path(__file__).parent.parent - logger.info("Building frontend...") - frontend_dir = build_frontend() - logger.info("Frontend build complete") - - executable_name = get_executable_name(args.os) - cmd = [ - "pyinstaller", - f"--name={executable_name}", - "--windowed", - "--onefile", - "--add-data", - f"{frontend_dir / 'dist'}{';' if args.os == 'windows' else ':'}frontend/dist", - # Add hidden imports for path utilities - "--hidden-import=radio_telemetry_tracker_drone_gcs.utils.paths", - # Add scipy hidden imports - "--hidden-import=scipy.special._cdflib", - ] - - # Optional: add an icon if you have one in assets/ - icon_path = root_dir / "assets" / "icon.ico" - if icon_path.exists(): - cmd.extend(["--icon", str(icon_path)]) - - # Main script - main_script = validate_main_script(root_dir) - cmd.append(str(main_script)) - - logger.info("Building executable with PyInstaller...") - logger.info("Command: %s", " ".join(cmd)) - result = subprocess.run(cmd, check=True, capture_output=True, text=True) # noqa: S603 - - if result.stdout: - logger.info("PyInstaller output:\n%s", result.stdout) - if result.stderr: - logger.warning("PyInstaller warnings/errors:\n%s", result.stderr) - - dist_dir = root_dir / "dist" - validate_output(dist_dir) - logger.info("Build complete! Files in dist directory: %s", list(dist_dir.iterdir())) - - except Exception: - logger.exception("Build failed") - sys.exit(1) - - -if __name__ == "__main__": - main()