diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f04a745..281f5fa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,25 +12,10 @@ name: Build Multi-Platform Binaries on: workflow_dispatch: inputs: - branch: - description: "Branch to build from" - required: false - default: "main" - type: string jobs: description: "Comma-separated jobs to run (e.g., build-windows,build-debian,build-arch,build-rhel)" required: true default: "build-windows,build-debian,build-arch,build-rhel" - DO_RELEASE: - description: "Set to 'true' to create a GitHub release after successful builds" - required: false - default: "false" - UPLOAD_S3: - description: "Set to 'true' to upload build artifacts to S3 after builds" - required: false - default: "false" - pull_request: - branches: [ main, develop ] permissions: contents: write @@ -43,20 +28,64 @@ env: CI_CD: true jobs: + merge-develop-to-main: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Fast-forward main to develop + run: | + set -e + git fetch origin main develop + + # Verify develop is ahead of main + MERGE_BASE=$(git merge-base origin/main origin/develop) + MAIN_SHA=$(git rev-parse origin/main) + + if [ "$MERGE_BASE" != "$MAIN_SHA" ]; then + echo "::error::Main branch has commits not in develop. Cannot fast-forward." + echo "::error::Please merge or rebase main into develop first." + exit 1 + fi + + # Ensure develop is actually ahead + DEVELOP_SHA=$(git rev-parse origin/develop) + if [ "$MAIN_SHA" = "$DEVELOP_SHA" ]; then + echo "Main is already up to date with develop. Nothing to merge." + exit 0 + fi + + # Fast-forward merge develop into main + git checkout main + git merge origin/develop --ff-only + + echo "Successfully fast-forwarded main to develop" + git log origin/main..HEAD --oneline + + git push origin main + run-unit-tests-linux: + needs: [merge-develop-to-main] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.branch || github.head_ref || github.ref }} + ref: main - name: Display build information run: | echo "Event: ${{ github.event_name }}" - echo "Branch: ${{ github.event.inputs.branch || github.head_ref || github.ref }}" echo "Jobs to run: ${{ github.event.inputs.jobs || 'build-windows,build-debian,build-arch,build-rhel' }}" - echo "Create release: ${{ github.event.inputs.DO_RELEASE || 'false' }}" - echo "Upload to S3: ${{ github.event.inputs.UPLOAD_S3 || 'false' }}" - name: Set up Python uses: actions/setup-python@v5 @@ -75,11 +104,12 @@ jobs: poetry run pytest tests/ -v run-unit-tests-windows: + needs: [merge-develop-to-main] runs-on: windows-latest steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.branch || github.head_ref || github.ref }} + ref: main - name: Set up Python uses: actions/setup-python@v5 @@ -109,13 +139,13 @@ jobs: poetry run pytest tests/ -v build-windows: needs: [run-unit-tests-linux, run-unit-tests-windows] - if: ${{ github.event_name == 'pull_request' || contains(github.event.inputs.jobs, 'build-windows') }} + if: ${{ contains(github.event.inputs.jobs, 'build-windows') }} runs-on: windows-latest steps: - name: Checkout code uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.branch || github.head_ref || github.ref }} + ref: main - name: Set up Python 3.12 uses: actions/setup-python@v5 @@ -155,7 +185,7 @@ jobs: build-debian: needs: [run-unit-tests-linux, run-unit-tests-windows] - if: ${{ github.event_name == 'pull_request' || contains(github.event.inputs.jobs, 'build-debian') }} + if: ${{ contains(github.event.inputs.jobs, 'build-debian') }} env: DISTRO_TYPE: debian runs-on: ubuntu-latest @@ -176,7 +206,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.branch || github.head_ref || github.ref }} + ref: main - name: Set up Python 3.12 uses: actions/setup-python@v5 @@ -242,7 +272,7 @@ jobs: build-arch: needs: [run-unit-tests-linux, run-unit-tests-windows] - if: ${{ github.event_name == 'pull_request' || contains(github.event.inputs.jobs, 'build-arch') }} + if: ${{ contains(github.event.inputs.jobs, 'build-arch') }} env: DISTRO_TYPE: arch runs-on: ubuntu-latest @@ -263,7 +293,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.branch || github.head_ref || github.ref }} + ref: main - name: Set up Python 3.12 uses: actions/setup-python@v5 @@ -322,7 +352,7 @@ jobs: build-rhel: needs: [run-unit-tests-linux, run-unit-tests-windows] - if: ${{ github.event_name == 'pull_request' || contains(github.event.inputs.jobs, 'build-rhel') }} + if: ${{ contains(github.event.inputs.jobs, 'build-rhel') }} permissions: contents: read packages: read @@ -335,7 +365,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.branch || github.head_ref || github.ref }} + ref: main - name: Build RHEL package run: | @@ -376,52 +406,14 @@ jobs: do-release: - needs: [run-unit-tests-linux, run-unit-tests-windows, build-windows, build-debian, build-arch, build-rhel] - if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.DO_RELEASE == 'true' && needs.build-windows.result == 'success' && needs.build-debian.result == 'success' && needs.build-arch.result == 'success' && needs.build-rhel.result == 'success' }} + needs: [build-windows, build-debian, build-arch, build-rhel] + if: ${{ needs.build-windows.result == 'success' && needs.build-debian.result == 'success' && needs.build-arch.result == 'success' && needs.build-rhel.result == 'success' }} runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.branch || github.head_ref || github.ref }} - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure git - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: Fast-forward main to develop - run: | - set -e - git fetch origin main develop - - # Verify develop is ahead of main - MERGE_BASE=$(git merge-base origin/main origin/develop) - MAIN_SHA=$(git rev-parse origin/main) - - if [ "$MERGE_BASE" != "$MAIN_SHA" ]; then - echo "::error::Main branch has commits not in develop. Cannot fast-forward." - echo "::error::Please merge or rebase main into develop first." - exit 1 - fi - - # Ensure develop is actually ahead - DEVELOP_SHA=$(git rev-parse origin/develop) - if [ "$MAIN_SHA" = "$DEVELOP_SHA" ]; then - echo "Main is already up to date with develop. Nothing to merge." - exit 0 - fi - - # Fast-forward merge develop into main - git checkout main - git merge origin/develop --ff-only - - echo "Successfully fast-forwarded main to develop" - git log origin/main..HEAD --oneline - - git push origin main + ref: main - name: Install Poetry uses: snok/install-poetry@v1 @@ -456,14 +448,14 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} upload-s3: - needs: [run-unit-tests-linux, run-unit-tests-windows, build-windows, build-debian, build-arch, build-rhel] - if: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.UPLOAD_S3 == 'true' || contains(github.event.inputs.jobs, 'upload-s3')) }} + needs: [build-windows, build-debian, build-arch, build-rhel] + if: ${{ needs.build-windows.result == 'success' && needs.build-debian.result == 'success' && needs.build-arch.result == 'success' && needs.build-rhel.result == 'success' }} runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.branch || github.head_ref || github.ref }} + ref: main - name: Install AWS CLI run: | @@ -498,5 +490,5 @@ jobs: if: always() && needs.do-release.result == 'success' uses: ./.github/workflows/sync-wiki.yml with: - branch: ${{ github.event.inputs.branch || github.ref }} + branch: main secrets: inherit \ No newline at end of file diff --git a/.github/workflows/status-checks.yml b/.github/workflows/status-checks.yml new file mode 100644 index 0000000..8576608 --- /dev/null +++ b/.github/workflows/status-checks.yml @@ -0,0 +1,322 @@ +# Status checks workflow for pull requests +# Runs tests and builds on all platforms to verify code quality +name: Status Checks + +on: + pull_request: + branches: [ main, develop ] + +permissions: + contents: read + packages: read + +env: + FPM_VERSION: "1.16.0" + CI_CD: true + +jobs: + run-unit-tests-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Poetry + uses: snok/install-poetry@v1 + + - name: Install dependencies + run: | + poetry install + + - name: Run tests + run: | + poetry run pytest tests/ -v + + run-unit-tests-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + version: latest + virtualenvs-create: true + virtualenvs-in-project: true + + - name: Ensure Poetry is on PATH (Windows) + shell: pwsh + run: | + # Add Poetry user bin to PATH for subsequent steps in this job + $poetryPath = Join-Path $env:USERPROFILE ".local\bin" + Write-Output $poetryPath >> $Env:GITHUB_PATH + + - name: Install dependencies + run: | + poetry install + + - name: Run tests + run: | + poetry run pytest tests/ -v + + build-windows: + needs: [run-unit-tests-linux, run-unit-tests-windows] + runs-on: windows-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + version: latest + virtualenvs-create: true + virtualenvs-in-project: true + + - name: Ensure Poetry is on PATH (Windows) + shell: pwsh + run: | + # Add Poetry user bin to PATH for subsequent steps in this job + $poetryPath = Join-Path $env:USERPROFILE ".local\bin" + Write-Output $poetryPath >> $Env:GITHUB_PATH + + - name: Install dependencies + run: | + poetry install + + - name: Build Windows executable + run: | + # Use the Windows spec file so packaging is consistent and reproducible + poetry run pyinstaller scripts/spec_scripts/android-file-handler-windows.spec + + - name: Upload Windows artifact + uses: actions/upload-artifact@v4 + with: + name: windows-binary + path: | + dist/**/android-file-handler*.exe + dist/android-file-handler.exe + + build-debian: + needs: [run-unit-tests-linux, run-unit-tests-windows] + env: + DISTRO_TYPE: debian + runs-on: ubuntu-latest + container: + image: python:3.12-slim + steps: + - name: Install system dependencies & gem fpm (include Tcl/Tk) + run: | + set -euo pipefail + apt-get update + # Install Tcl/Tk runtimes, dev headers and common X libraries required by tkinter + apt-get install -y --no-install-recommends \ + curl git build-essential ruby ruby-dev gcc make zlib1g-dev ca-certificates python3-tk \ + tcl8.6 tk8.6 tcl8.6-dev tk8.6-dev libx11-6 libxext6 libxrender1 libxcb1 + # install pinned fpm to the system gem dir (will be available under gem env's EXECUTABLE DIRECTORY or /usr/local/bin) + gem install --no-document -v "${FPM_VERSION}" fpm + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + version: latest + virtualenvs-create: true + virtualenvs-in-project: true + + - name: Configure Poetry + run: | + echo 'export PATH="$HOME/.local/bin:$PATH"' >> $GITHUB_ENV + export PATH="$HOME/.local/bin:$PATH" + poetry config virtualenvs.create true + poetry config virtualenvs.in-project true + + - name: Install dependencies & build executable + run: | + export PATH="$HOME/.local/bin:$PATH" + poetry env use python3.12 || true + # Debug: show tkinter/_tkinter and Tcl library discovery in the Poetry venv + poetry run python -c 'import tkinter, _tkinter, sys; print("tkinter=", getattr(tkinter, "__file__", None)); print("_tkinter=", getattr(_tkinter, "__file__", None)); import tkinter as tk; print("TCL_LIBRARY=", tk.Tcl().eval("info library"))' + + # Build distro-specific package layout using the build script via Poetry + poetry run python scripts/build_package_linux.py + + - name: Package .deb (fpm) + shell: bash + run: | + set -euo pipefail + export PATH="$HOME/.local/bin:$PATH" + VERSION="$(poetry version -s)" + PKG_DIR="pkg_dist_debian" + mkdir -p dist + # Debug listing + echo "Packaging from $PKG_DIR" + ls -la "$PKG_DIR" || true + + ICON_PATH="$PKG_DIR/usr/share/icons/hicolor/256x256/apps/android-file-handler.png" + PKG_ITEMS=( "usr/local/bin/android-file-handler" "usr/share/applications/android-file-handler.desktop" ) + if [ -f "$ICON_PATH" ]; then + PKG_ITEMS+=( "usr/share/icons/hicolor/256x256/apps/android-file-handler.png" ) + else + echo "Note: icon not present, packaging without icon" + fi + + fpm -s dir -t deb -n android-file-handler -v "$VERSION" \ + --architecture amd64 --prefix /usr/local/bin --deb-user root --deb-group root \ + --after-install scripts/debian_postinst.sh \ + -p "dist/android-file-handler_${VERSION}_amd64.deb" -C "$PKG_DIR" "${PKG_ITEMS[@]}" + + - name: Upload Debian .deb + uses: actions/upload-artifact@v4 + with: + name: debian-package + path: | + dist/android-file-handler_*.deb + pkg_dist_debian/** + + build-arch: + needs: [run-unit-tests-linux, run-unit-tests-windows] + env: + DISTRO_TYPE: arch + runs-on: ubuntu-latest + container: + image: archlinux:latest + steps: + - name: Install system dependencies (Arch) and system Ruby + run: | + set -euo pipefail + pacman -Syu --noconfirm + pacman -S --noconfirm ruby base-devel curl git tar ca-certificates tk tcl libx11 libxext libxrender libxcb + # Install fpm system-wide and pin version so fpm will be in /usr/in + gem install --no-document erb + gem install --no-document -v "${FPM_VERSION}" fpm --bindir /usr/bin + # persist system bindir to subsequent steps (usually already on PATH) + echo "/usr/local/bin" >> $GITHUB_PATH + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + version: latest + virtualenvs-create: true + virtualenvs-in-project: true + + - name: Configure Poetry + run: | + echo 'export PATH="$HOME/bin:$PATH"' >> $GITHUB_ENV + export PATH="$HOME/bin:$PATH" + + - name: Install dependencies & build executable + run: | + export PATH="$HOME/bin:/usr/bin:$PATH" + # Use unified build script to produce pkg_dist_arch layout via Poetry + poetry run python scripts/build_package_linux.py + + - name: Package pacman (fpm) + shell: bash + run: | + set -euo pipefail + export PATH="$HOME/.local/bin:/usr/bin:$PATH" + VERSION="$(poetry version -s)" + PKG_DIR="pkg_dist_arch" + mkdir -p dist + echo "Packaging from $PKG_DIR" + ls -la "$PKG_DIR" || true + + ICON_PATH="$PKG_DIR/usr/share/icons/hicolor/256x256/apps/android-file-handler.png" + PKG_ITEMS=( "usr/bin/android-file-handler" "usr/share/applications/android-file-handler.desktop" ) + if [ -f "$ICON_PATH" ]; then + PKG_ITEMS+=( "usr/share/icons/hicolor/256x256/apps/android-file-handler.png" ) + else + echo "Note: icon not present, packaging without icon" + fi + + fpm -s dir -t pacman -n android-file-handler -v "$VERSION" \ + --architecture x86_64 --prefix /usr/bin \ + -p "dist/android-file-handler-${VERSION}-1-x86_64.pkg.tar.zst" -C "$PKG_DIR" "${PKG_ITEMS[@]}" + + - name: Upload Arch package + uses: actions/upload-artifact@v4 + with: + name: arch-package + path: | + dist/*.pkg.tar.* + pkg_dist_arch/** + + build-rhel: + needs: [run-unit-tests-linux, run-unit-tests-windows] + permissions: + contents: read + packages: read + env: + DISTRO_TYPE: rhel + runs-on: ubuntu-latest + container: + image: ghcr.io/jmr-dev/android-file-handler-adb:v0.1.0 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Build RHEL package + run: | + set -euo pipefail + export CI_CD=true + export DISTRO_TYPE=rhel + poetry install --no-interaction + poetry run python scripts/build_package_linux.py + + - name: Package RHEL (fpm) + shell: bash + run: | + set -euo pipefail + VERSION="$(poetry version -s)" + PKG_DIR="pkg_dist_rhel" + mkdir -p dist + echo "Packaging from $PKG_DIR (version=$VERSION)" + ls -la "$PKG_DIR" || true + + ICON_PATH="$PKG_DIR/usr/share/icons/hicolor/256x256/apps/android-file-handler.png" + PKG_ITEMS=( "usr/bin/android-file-handler" "usr/share/applications/android-file-handler.desktop" ) + if [ -f "$ICON_PATH" ]; then + PKG_ITEMS+=( "usr/share/icons/hicolor/256x256/apps/android-file-handler.png" ) + else + echo "Note: icon not present, packaging without icon" + fi + + fpm -s dir -t rpm -n android-file-handler -v "$VERSION" --architecture x86_64 --prefix /usr/bin --after-install scripts/rhel_postinst.sh -p "dist/android-file-handler-${VERSION}.x86_64.rpm" -C "$PKG_DIR" "${PKG_ITEMS[@]}" + + - name: Upload RHEL artifacts + uses: actions/upload-artifact@v4 + with: + name: rhel-package + path: | + dist/*.rpm + pkg_dist_rhel/**