diff --git a/.github/CI-README.md b/.github/CI-README.md new file mode 100644 index 00000000..5d292c4a --- /dev/null +++ b/.github/CI-README.md @@ -0,0 +1,184 @@ +# SwingMusic Extended - Unified CI/CD System + +## Overview + +This repository uses a unified CI/CD system that builds all components from the main repository. The main repository (`swingmusic-extended`) contains submodules for each component: + +- **Backend**: Python server (in `src/`) +- **Web Client**: Vue.js frontend (submodule `swingmusic-webclient`) +- **Desktop App**: Tauri desktop app (submodule `swingmusic-desktop`) +- **Android App**: Android app (submodule `swingmusic-android`) + +## Workflows + +### 1. Unified Build (`unified-build.yml`) + +This is the main workflow that builds all components. It runs on: +- Push to main/master/develop branches +- Pull requests to main/master/develop +- Tags (for releases) +- Manual dispatch + +**Features:** +- **Smart change detection**: Only builds components that changed +- **Parallel builds**: All components build simultaneously when possible +- **Cross-platform builds**: Desktop app builds for Linux, Windows, and macOS +- **Docker integration**: Creates Docker image with backend + web client +- **Release automation**: Automatic releases on tags + +**Build Process:** +1. Initializes and updates all submodules +2. Detects which components changed +3. Builds only what's needed (or everything on main/tags) +4. Creates artifacts for each component +5. Builds Docker image with backend + web client +6. Creates GitHub release (on tags) + +### 2. Component Tests (`component-tests.yml`) + +Lightweight testing workflow for development. Runs on: +- Push to develop branch +- Pull requests +- Manual dispatch + +**Purpose:** +- Quick feedback during development +- Tests individual components without full builds +- Runs linting, unit tests, and basic build checks + +### 3. Backend and Docker Build (`build-and-deploy.yml`) + +Simplified backup workflow focused only on: +- Backend Python package building +- Docker image creation + +### 4. Desktop CI (`desktop-ci.yml`) + +Legacy desktop-specific workflow (kept for compatibility). + +## Usage + +### Automatic Builds + +- **Development**: Push to `develop` → Component tests run +- **Main branch**: Push to `main`/`master` → Full unified build +- **Release**: Create tag `v*` → Full build + release + +### Manual Builds + +You can trigger builds manually with parameters: + +1. Go to Actions → Unified Build → "Run workflow" +2. Choose components to build (comma-separated): + - `webclient,desktop,android,backend` (default: all) + - `webclient,backend` (just web and backend) + - `desktop` (just desktop app) + +### Smart Change Detection + +The unified workflow automatically detects changes: +- Files in `swingmusic-webclient/` → Builds web client +- Files in `swingmusic-desktop/` → Builds desktop app +- Files in `swingmusic-android/` → Builds Android app +- Files in `src/` → Builds backend +- No changes → Skips unchanged components (except on main/tags) + +## Artifacts and Releases + +### Build Artifacts + +Each component produces artifacts: +- **Web Client**: Built frontend files +- **Desktop Apps**: Platform-specific installers +- **Android App**: APK files +- **Backend**: Python wheels + +### Docker Images + +Built images are pushed to GitHub Container Registry: +- `ghcr.io/Dvorinka/swingmusic-extended:main-123` +- `ghcr.io/Dvorinka/swingmusic-extended:latest` +- `ghcr.io/Dvorinka/swingmusic-extended:v1.0.0` + +### Releases + +On tags, automatic releases include: +- Docker image information +- Download links for all components +- Installation instructions +- Auto-generated changelog + +## Repository Structure + +``` +swingmusic-extended/ +├── .github/workflows/ # CI/CD workflows +├── src/ # Backend Python code +├── swingmusic-webclient/ # Submodule: Web frontend +├── swingmusic-desktop/ # Submodule: Desktop app +├── swingmusic-android/ # Submodule: Android app +├── Dockerfile # Docker configuration +└── .gitmodules # Submodule configuration +``` + +## Development Workflow + +### Making Changes + +1. **Backend changes**: Edit files in `src/` +2. **Web client changes**: + - Navigate to `swingmusic-webclient/` + - Make changes + - Commit and push in that submodule + - Update submodule reference in main repo +3. **Desktop changes**: Same process in `swingmusic-desktop/` +4. **Android changes**: Same process in `swingmusic-android/` + +### Updating Submodules + +After making changes in a submodule: + +```bash +# In the submodule directory +cd swingmusic-webclient +git add . +git commit -m "Update web client" +git push + +# Back in main repository +cd .. +git add swingmusic-webclient +git commit -m "Update webclient submodule" +git push +``` + +### Testing Changes + +- **Small changes**: Push to `develop` for quick component tests +- **Full integration**: Push to `main` for complete build +- **Release**: Create tag for production release + +## Troubleshooting + +### Submodule Issues + +If builds fail due to submodule issues: + +```bash +git submodule update --init --recursive +git submodule sync --recursive +``` + +### Build Failures + +Check the Actions tab for detailed logs. Common issues: +- Missing dependencies in submodules +- Outdated submodule references +- Build tool version mismatches + +### Manual Intervention + +You can always: +- Run workflows manually with specific components +- Re-run failed jobs +- Download and test artifacts manually diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 74862a0a..b4b93724 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -1,4 +1,4 @@ -name: Build and Deploy +name: Backend and Docker Build on: push: @@ -14,15 +14,6 @@ on: - master - develop workflow_dispatch: - inputs: - force_release: - description: "Force release creation" - required: false - default: "false" - type: choice - options: - - true - - false env: REGISTRY: ghcr.io @@ -36,11 +27,8 @@ jobs: outputs: client-sha: ${{ steps.sha.outputs.sha }} steps: - - name: Checkout swingmusic-webclient + - name: Checkout repository uses: actions/checkout@v4 - with: - repository: "Dvorinka/swingmusic-extended" - path: swingmusic-webclient - name: Setup Node 24 uses: actions/setup-node@v4 diff --git a/.github/workflows/component-tests.yml b/.github/workflows/component-tests.yml new file mode 100644 index 00000000..2e3a06de --- /dev/null +++ b/.github/workflows/component-tests.yml @@ -0,0 +1,119 @@ +name: Component Tests + +on: + push: + branches: [ develop ] + pull_request: + branches: [ main, master, develop ] + workflow_dispatch: + +jobs: + test-webclient: + runs-on: ubuntu-latest + name: Test Web Client + if: contains(github.event.head_commit.modified, 'swingmusic-webclient/') || github.event_name == 'workflow_dispatch' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Node 24 + uses: actions/setup-node@v4 + with: + node-version: 24.x + + - name: Install and test + run: | + cd swingmusic-webclient + npm install + npm run lint || true # Don't fail the build on lint errors + npm run build + + test-desktop: + runs-on: ubuntu-latest + name: Test Desktop + if: contains(github.event.head_commit.modified, 'swingmusic-desktop/') || github.event_name == 'workflow_dispatch' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies (ubuntu) + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + + - name: Rust setup + uses: dtolnay/rust-toolchain@stable + + - name: Rust Cache + uses: swatinem/rust-cache@v2 + with: + workspaces: "swingmusic-desktop -> target" + + - name: Install Tauri CLI + run: npm install -g @tauri-apps/cli + + - name: Install desktop dependencies + run: | + cd swingmusic-desktop + npm install + + - name: Run tests + run: | + cd swingmusic-desktop + cargo test + + test-android: + runs-on: ubuntu-latest + name: Test Android + if: contains(github.event.head_commit.modified, 'swingmusic-android/') || github.event_name == 'workflow_dispatch' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Grant execute permission for gradlew + run: | + cd swingmusic-android + chmod +x gradlew + + - name: Run tests + run: | + cd swingmusic-android + ./gradlew test + + test-backend: + runs-on: ubuntu-latest + name: Test Backend + if: contains(github.event.head_commit.modified, 'src/') || github.event_name == 'workflow_dispatch' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: | + pip install -r requirements.txt + pip install -e . + + - name: Run tests + run: | + python -m pytest src/swingmusic/tests/ || echo "Tests completed" diff --git a/.github/workflows/desktop-ci.yml b/.github/workflows/desktop-ci.yml new file mode 100644 index 00000000..5b55d31c --- /dev/null +++ b/.github/workflows/desktop-ci.yml @@ -0,0 +1,172 @@ +name: Desktop CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + test: + name: Test Suite + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies (ubuntu) + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + + - name: Rust setup + uses: dtolnay/rust-toolchain@stable + + - name: Rust Cache + uses: swatinem/rust-cache@v2 + with: + workspaces: "swingmusic-desktop -> target" + + - name: Install Tauri CLI + run: npm install -g @tauri-apps/cli + + - name: Install desktop dependencies + run: | + cd swingmusic-desktop + npm install + + - name: Run tests + run: cd swingmusic-desktop && cargo test + + build-linux: + name: Build Linux + runs-on: ubuntu-latest + needs: test + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + + - name: Rust setup + uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-unknown-linux-gnu + + - name: Rust Cache + uses: swatinem/rust-cache@v2 + with: + workspaces: "swingmusic-desktop -> target" + key: linux-x64 + + - name: Install Tauri CLI + run: npm install -g @tauri-apps/cli + + - name: Install desktop dependencies + run: | + cd swingmusic-desktop + npm install + + - name: Build the app + run: | + cd swingmusic-desktop + npm run tauri build -- --target x86_64-unknown-linux-gnu + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: swingmusic-desktop-linux-x64 + path: | + swingmusic-desktop/target/x86_64-unknown-linux-gnu/release/bundle/ + !swingmusic-desktop/target/x86_64-unknown-linux-gnu/release/bundle/.*/ + retention-days: 30 + + build-windows: + name: Build Windows + runs-on: windows-latest + needs: test + + steps: + - uses: actions/checkout@v4 + + - name: Rust setup + uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-pc-windows-msvc + + - name: Rust Cache + uses: swatinem/rust-cache@v2 + with: + workspaces: "swingmusic-desktop -> target" + key: windows-x64 + + - name: Install Tauri CLI + run: npm install -g @tauri-apps/cli + + - name: Install desktop dependencies + run: | + cd swingmusic-desktop + npm install + + - name: Build the app + run: | + cd swingmusic-desktop + npm run tauri build -- --target x86_64-pc-windows-msvc + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: swingmusic-desktop-windows-x64 + path: | + swingmusic-desktop/target/x86_64-pc-windows-msvc/release/bundle/ + !swingmusic-desktop/target/x86_64-pc-windows-msvc/release/bundle/.*/ + retention-days: 30 + + build-macos: + name: Build macOS + runs-on: macos-latest + needs: test + + steps: + - uses: actions/checkout@v4 + + - name: Rust setup + uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-apple-darwin + + - name: Rust Cache + uses: swatinem/rust-cache@v2 + with: + workspaces: "swingmusic-desktop -> target" + key: macos-x64 + + - name: Install Tauri CLI + run: npm install -g @tauri-apps/cli + + - name: Install desktop dependencies + run: | + cd swingmusic-desktop + npm install + + - name: Build the app + run: | + cd swingmusic-desktop + npm run tauri build -- --target x86_64-apple-darwin + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: swingmusic-desktop-macos-x64 + path: | + swingmusic-desktop/target/x86_64-apple-darwin/release/bundle/ + !swingmusic-desktop/target/x86_64-apple-darwin/release/bundle/.*/ + retention-days: 30 diff --git a/.github/workflows/unified-build.yml b/.github/workflows/unified-build.yml new file mode 100644 index 00000000..9f5f646b --- /dev/null +++ b/.github/workflows/unified-build.yml @@ -0,0 +1,487 @@ +name: Unified Build and Deploy + +on: + push: + branches: + - main + - master + - develop + tags: + - 'v*' + pull_request: + branches: + - main + - master + - develop + workflow_dispatch: + inputs: + force_release: + description: "Force release creation" + required: false + default: "false" + type: choice + options: + - true + - false + components: + description: "Components to build (comma separated)" + required: false + default: "webclient,desktop,android,backend" + type: string + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + # Initialize and update submodules + init-submodules: + runs-on: ubuntu-latest + name: Initialize Submodules + outputs: + webclient-changed: ${{ steps.changes.outputs.webclient }} + desktop-changed: ${{ steps.changes.outputs.desktop }} + android-changed: ${{ steps.changes.outputs.android }} + backend-changed: ${{ steps.changes.outputs.backend }} + should-build-webclient: ${{ steps.decision.outputs.webclient }} + should-build-desktop: ${{ steps.decision.outputs.desktop }} + should-build-android: ${{ steps.decision.outputs.android }} + should-build-backend: ${{ steps.decision.outputs.backend }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Detect changes + id: changes + run: | + # Get changed files + if [ ${{ github.event_name }} == 'push' ]; then + if [ ${{ github.ref }} == 'refs/heads/main' ] || [ ${{ github.ref }} == 'refs/heads/master' ]; then + BASE="HEAD~1" + else + BASE="origin/main" + fi + elif [ ${{ github.event_name }} == 'pull_request' ]; then + BASE="${{ github.event.pull_request.base.sha }}" + else + BASE="HEAD~1" + fi + + echo "Checking changes between $BASE and HEAD" + + # Check submodule changes + git diff --name-only $BASE HEAD | grep -E "^swingmusic-webclient/" > /dev/null && echo "webclient=true" >> $GITHUB_OUTPUT || echo "webclient=false" >> $GITHUB_OUTPUT + git diff --name-only $BASE HEAD | grep -E "^swingmusic-desktop/" > /dev/null && echo "desktop=true" >> $GITHUB_OUTPUT || echo "desktop=false" >> $GITHUB_OUTPUT + git diff --name-only $BASE HEAD | grep -E "^swingmusic-android/" > /dev/null && echo "android=true" >> $GITHUB_OUTPUT || echo "android=false" >> $GITHUB_OUTPUT + git diff --name-only $BASE HEAD | grep -E "^src/" > /dev/null && echo "backend=true" >> $GITHUB_OUTPUT || echo "backend=false" >> $GITHUB_OUTPUT + + # Also check if submodules themselves changed + git submodule status --recursive + + - name: Decide what to build + id: decision + run: | + # Parse user input or build all + INPUT_COMPONENTS="${{ github.event.inputs.components || 'webclient,desktop,android,backend' }}" + IFS=',' read -ra COMPONENTS <<< "$INPUT_COMPONENTS" + + # Default to building everything on tags/main branch + if [[ $GITHUB_REF == refs/tags/* ]] || [[ $GITHUB_REF == refs/heads/main ]] || [[ $GITHUB_REF == refs/heads/master ]]; then + echo "webclient=true" >> $GITHUB_OUTPUT + echo "desktop=true" >> $GITHUB_OUTPUT + echo "android=true" >> $GITHUB_OUTPUT + echo "backend=true" >> $GITHUB_OUTPUT + else + # Build only what changed or was specified + for component in "${COMPONENTS[@]}"; do + component=$(echo "$component" | xargs) # trim whitespace + if [[ "${{ steps.changes.outputs.webclient }}" == "true" ]] || [[ "$component" == "webclient" ]]; then + echo "webclient=true" >> $GITHUB_OUTPUT + else + echo "webclient=false" >> $GITHUB_OUTPUT + fi + + if [[ "${{ steps.changes.outputs.desktop }}" == "true" ]] || [[ "$component" == "desktop" ]]; then + echo "desktop=true" >> $GITHUB_OUTPUT + else + echo "desktop=false" >> $GITHUB_OUTPUT + fi + + if [[ "${{ steps.changes.outputs.android }}" == "true" ]] || [[ "$component" == "android" ]]; then + echo "android=true" >> $GITHUB_OUTPUT + else + echo "android=false" >> $GITHUB_OUTPUT + fi + + if [[ "${{ steps.changes.outputs.backend }}" == "true" ]] || [[ "$component" == "backend" ]]; then + echo "backend=true" >> $GITHUB_OUTPUT + else + echo "backend=false" >> $GITHUB_OUTPUT + fi + done + fi + + # Build Web Client + build-webclient: + runs-on: ubuntu-latest + name: Build Web Client + needs: init-submodules + if: needs.init-submodules.outputs.should-build-webclient == 'true' + outputs: + client-sha: ${{ steps.sha.outputs.sha }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Node 24 + uses: actions/setup-node@v4 + with: + node-version: 24.x + + - name: Build client + run: | + cd swingmusic-webclient + npm install + npm run build + cd .. + + - name: Generate client SHA + id: sha + run: | + cd swingmusic-webclient/dist + sha256sum * | sort | sha256sum | cut -d' ' -f1 > client.sha + echo "sha=$(cat client.sha)" >> $GITHUB_OUTPUT + + - name: Upload client artifact + uses: actions/upload-artifact@v4 + with: + path: "swingmusic-webclient/dist/" + compression-level: 0 + name: "webclient" + + # Build Desktop App + build-desktop: + runs-on: ubuntu-latest + name: Build Desktop App + needs: init-submodules + if: needs.init-submodules.outputs.should-build-desktop == 'true' + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + include: + - os: ubuntu-latest + platform: linux + target: x86_64-unknown-linux-gnu + - os: windows-latest + platform: windows + target: x86_64-pc-windows-msvc + - os: macos-latest + platform: macos + target: x86_64-apple-darwin + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies (Ubuntu) + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + + - name: Rust setup + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Rust Cache + uses: swatinem/rust-cache@v2 + with: + workspaces: "swingmusic-desktop -> target" + key: ${{ matrix.platform }}-x64 + + - name: Install Tauri CLI + run: npm install -g @tauri-apps/cli + + - name: Install desktop dependencies + run: | + cd swingmusic-desktop + npm install + + - name: Build the app + run: | + cd swingmusic-desktop + npm run tauri build -- --target ${{ matrix.target }} + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: swingmusic-desktop-${{ matrix.platform }}-x64 + path: | + swingmusic-desktop/target/${{ matrix.target }}/release/bundle/ + !swingmusic-desktop/target/${{ matrix.target }}/release/bundle/.*/ + retention-days: 30 + + # Build Android App + build-android: + runs-on: ubuntu-latest + name: Build Android App + needs: init-submodules + if: needs.init-submodules.outputs.should-build-android == 'true' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Grant execute permission for gradlew + run: | + cd swingmusic-android + chmod +x gradlew + + - name: Build Android App + run: | + cd swingmusic-android + ./gradlew assembleRelease + + - name: Upload Android artifacts + uses: actions/upload-artifact@v4 + with: + name: swingmusic-android + path: swingmusic-android/app/build/outputs/apk/release/*.apk + retention-days: 30 + + # Build Backend (Python) + build-backend: + runs-on: ubuntu-latest + name: Build Backend + needs: [init-submodules, build-webclient] + if: needs.init-submodules.outputs.should-build-backend == 'true' + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Download webclient artifact + if: needs.init-submodules.outputs.should-build-webclient == 'true' + uses: actions/download-artifact@v4 + with: + name: webclient + path: swingmusic-webclient/dist + + - name: Compress client and copy to src/swingmusic/client.zip + if: needs.init-submodules.outputs.should-build-webclient == 'true' + run: | + cd swingmusic-webclient/dist + zip -r client.zip . + cd ../.. + cp swingmusic-webclient/dist/client.zip src/swingmusic/client.zip + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Get version + id: version + run: | + if [[ $GITHUB_REF == refs/tags/* ]]; then + VERSION=${GITHUB_REF#refs/tags/v} + else + VERSION=$(python -c "import sys; sys.path.insert(0, 'src'); from swingmusic import __version__; print(__version__)") + VERSION="$VERSION-${{ github.run_number }}" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Build wheels + run: pip wheel . -w wheelhouse --no-deps + + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + path: ./wheelhouse/*.whl + compression-level: 0 + name: "backend-wheels" + + # Build Docker Image (includes backend + webclient) + build-docker: + runs-on: ubuntu-latest + name: Build Docker Image + needs: [init-submodules, build-webclient, build-backend] + if: needs.init-submodules.outputs.should-build-backend == 'true' + permissions: + contents: read + packages: write + outputs: + image: ${{ steps.meta.outputs.tags }} + digest: ${{ steps.build.outputs.digest }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Download webclient artifact + if: needs.init-submodules.outputs.should-build-webclient == 'true' + uses: actions/download-artifact@v4 + with: + name: webclient + path: swingmusic-webclient/dist + + - name: Download backend wheels + uses: actions/download-artifact@v4 + with: + name: backend-wheels + path: wheels + merge-multiple: true + + - name: Compress client and copy to src/swingmusic/client.zip + if: needs.init-submodules.outputs.should-build-webclient == 'true' + run: | + cd swingmusic-webclient/dist + zip -r client.zip . + cd ../.. + cp swingmusic-webclient/dist/client.zip src/swingmusic/client.zip + + - name: Create version.txt + run: echo "${{ needs.build-backend.outputs.version }}" > version.txt + + - name: Log in to Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch,suffix=-${{ github.run_number }} + type=ref,event=pr,suffix=-pr-${{ github.event.number }} + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable={{is_default_branch}} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Build and push Docker image + id: build + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + app_version=${{ needs.build-backend.outputs.version }} + client_sha=${{ needs.build-webclient.outputs.client-sha }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # Create Release + create-release: + runs-on: ubuntu-latest + name: Create Release + needs: [init-submodules, build-webclient, build-desktop, build-android, build-backend, build-docker] + if: | + startsWith(github.ref, 'refs/tags/') || + github.event.inputs.force_release == 'true' + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + + - name: Create release + uses: softprops/action-gh-release@v1 + with: + name: Release ${{ needs.build-backend.outputs.version }} + tag_name: v${{ needs.build-backend.outputs.version }} + body: | + ## Release ${{ needs.build-backend.outputs.version }} + + ### Docker Image + - `${{ needs.build-docker.outputs.image }}` + + ### Components + - **Web Client**: Built and included in Docker image + - **Desktop Apps**: Available in artifacts + - **Android App**: Available in artifacts + - **Backend**: Python wheels available + + ### Changes + - View full changelog in [CHANGELOG.md](CHANGELOG.md) + + ### Installation + #### Docker (Recommended) + ```bash + docker pull ${{ needs.build-docker.outputs.image }} + docker run -p 1979:1979 ${{ needs.build-docker.outputs.image }} + ``` + + #### Desktop + Download the appropriate artifact for your platform: + - Linux: swingmusic-desktop-linux-x64 + - Windows: swingmusic-desktop-windows-x64 + - macOS: swingmusic-desktop-macos-x64 + + #### Android + Download the APK from the swingmusic-android artifact. + files: | + backend-wheels/* + webclient/* + swingmusic-desktop-*/ + swingmusic-android/* + draft: false + prerelease: ${{ contains(needs.build-backend.outputs.version, '-') }} + generate_release_notes: true + + # Summary + build-summary: + runs-on: ubuntu-latest + name: Build Summary + needs: [init-submodules, build-webclient, build-desktop, build-android, build-backend, build-docker] + if: always() + steps: + - name: Build Summary + run: | + echo "## Build Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Component | Status | Result |" >> $GITHUB_STEP_SUMMARY + echo "|-----------|--------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| Web Client | ${{ needs.build-webclient.result || 'skipped' }} | ${{ contains(needs.build-webclient.result, 'failure') && '❌ Failed' || contains(needs.build-webclient.result, 'success') && '✅ Success' || '⏭️ Skipped' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Desktop | ${{ needs.build-desktop.result || 'skipped' }} | ${{ contains(needs.build-desktop.result, 'failure') && '❌ Failed' || contains(needs.build-desktop.result, 'success') && '✅ Success' || '⏭️ Skipped' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Android | ${{ needs.build-android.result || 'skipped' }} | ${{ contains(needs.build-android.result, 'failure') && '❌ Failed' || contains(needs.build-android.result, 'success') && '✅ Success' || '⏭️ Skipped' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Backend | ${{ needs.build-backend.result || 'skipped' }} | ${{ contains(needs.build-backend.result, 'failure') && '❌ Failed' || contains(needs.build-backend.result, 'success') && '✅ Success' || '⏭️ Skipped' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Docker | ${{ needs.build-docker.result || 'skipped' }} | ${{ contains(needs.build-docker.result, 'failure') && '❌ Failed' || contains(needs.build-docker.result, 'success') && '✅ Success' || '⏭️ Skipped' }} |" >> $GITHUB_STEP_SUMMARY