diff --git a/.github/workflows/unified-release.yml b/.github/workflows/unified-release.yml new file mode 100644 index 00000000..4d43bed1 --- /dev/null +++ b/.github/workflows/unified-release.yml @@ -0,0 +1,381 @@ +name: Unified Release + +on: + push: + branches: [ "main" ] + workflow_dispatch: + inputs: + version_number: + description: 'Version Number (e.g., 1.2.3) - leave empty for auto' + required: false + type: string + default: '' + components: + description: 'Components to release (comma separated)' + required: false + default: 'desktop,android,backend' + type: string + +env: + CARGO_TERM_COLOR: always + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + get-version: + name: Calculate Unified Version + runs-on: ubuntu-latest + if: github.event.inputs.version_number == '' + outputs: + version: ${{ steps.version.outputs.version }} + tag_name: ${{ steps.version.outputs.tag_name }} + release_notes: ${{ steps.version.outputs.release_notes }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Initialize submodules + run: git submodule update --init --recursive + + - name: Get Last Release + id: last_release + run: | + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") + echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT + + - name: Analyze Commits and Calculate Version + id: version + run: | + LAST_TAG="${{ steps.last_release.outputs.tag }}" + CURRENT_VERSION=${LAST_TAG#v} + + # Get commits from main repo and submodules + MAIN_COMMITS=$(git log $LAST_TAG..HEAD --oneline --no-merges) + + cd swingmusic-desktop && git fetch --tags && DESKTOP_COMMITS=$(git log $LAST_TAG..HEAD --oneline --no-merges 2>/dev/null || echo "") && cd .. + cd swingmusic-android && git fetch --tags && ANDROID_COMMITS=$(git log $LAST_TAG..HEAD --oneline --no-merges 2>/dev/null || echo "") && cd .. + cd src/swingmusic && git fetch --tags && BACKEND_COMMITS=$(git log $LAST_TAG..HEAD --oneline --no-merges 2>/dev/null || echo "") && cd ../.. + + # Combine all commits for analysis + ALL_COMMITS="$MAIN_COMMITS $DESKTOP_COMMITS $ANDROID_COMMITS $BACKEND_COMMITS" + + MAJOR_COUNT=$(echo "$ALL_COMMITS" | grep -iE "BREAKING CHANGE|major|!:|breaking" | wc -l || echo "0") + MINOR_COUNT=$(echo "$ALL_COMMITS" | grep -iE "feat|feature|add|new|enhance" | wc -l || echo "0") + PATCH_COUNT=$(echo "$ALL_COMMITS" | grep -iE "fix|bug|patch|update|improve|refactor|docs|style|test|chore" | wc -l || echo "0") + + # Calculate next version + IFS='.' read -ra PARTS <<< "$CURRENT_VERSION" + MAJOR=${PARTS[0]:-0} + MINOR=${PARTS[1]:-0} + PATCH=${PARTS[2]:-0} + + if [ "$MAJOR_COUNT" -gt 0 ]; then + MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0; BUMP_TYPE="major" + elif [ "$MINOR_COUNT" -gt 0 ]; then + MINOR=$((MINOR + 1)); PATCH=0; BUMP_TYPE="minor" + else + PATCH=$((PATCH + 1)); BUMP_TYPE="patch" + fi + + NEW_VERSION="$MAJOR.$MINOR.$PATCH" + TAG_NAME="v$NEW_VERSION" + + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + + # Generate release notes + RELEASE_NOTES="## 🎵 SwingMusic v$NEW_VERSION\n\n" + if [ "$MAJOR_COUNT" -gt 0 ]; then + RELEASE_NOTES+="### 🚨 BREAKING CHANGES\n" + echo "$ALL_COMMITS" | grep -iE "BREAKING CHANGE|major|!:|breaking" | sed 's/^/- /' >> /tmp/major_commits.txt + RELEASE_NOTES+="$(cat /tmp/major_commits.txt)\n\n" + fi + if [ "$MINOR_COUNT" -gt 0 ]; then + RELEASE_NOTES+="### ✨ New Features\n" + echo "$ALL_COMMITS" | grep -iE "feat|feature|add|new|enhance" | sed 's/^/- /' >> /tmp/minor_commits.txt + RELEASE_NOTES+="$(cat /tmp/minor_commits.txt)\n\n" + fi + if [ "$PATCH_COUNT" -gt 0 ]; then + RELEASE_NOTES+="### 🐛 Bug Fixes & Improvements\n" + echo "$ALL_COMMITS" | grep -iE "fix|bug|patch|update|improve|refactor|docs|style|test|chore" | sed 's/^/- /' >> /tmp/patch_commits.txt + RELEASE_NOTES+="$(cat /tmp/patch_commits.txt)\n\n" + fi + + echo "release_notes<> $GITHUB_OUTPUT + echo -e "$RELEASE_NOTES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + echo "New version: $NEW_VERSION ($BUMP_TYPE)" + + build-desktop: + name: Build Desktop App + runs-on: ${{ matrix.os }} + needs: get-version + if: contains(github.event.inputs.components, 'desktop') + + strategy: + fail-fast: false + matrix: + include: + - platform: 'linux-x64' + os: ubuntu-latest + rust_target: 'x86_64-unknown-linux-gnu' + - platform: 'win-x64' + os: windows-latest + rust_target: 'x86_64-pc-windows-msvc' + - platform: 'darwin-x64' + os: macos-latest + rust_target: 'x86_64-apple-darwin' + - platform: 'darwin-arm64' + os: macos-latest + rust_target: 'aarch64-apple-darwin' + + steps: + - uses: actions/checkout@v4 + - name: Initialize submodules + run: git submodule update --init --recursive + + - name: Install dependencies (ubuntu) + if: startsWith(matrix.platform, 'linux') + 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.rust_target }} + + - name: Rust Cache + uses: swatinem/rust-cache@v2 + with: + workspaces: "swingmusic-desktop -> target" + key: desktop-${{ matrix.platform }} + + - name: Install Tauri CLI + run: npm install -g @tauri-apps/cli + + - name: Build Desktop App + run: | + cd swingmusic-desktop + npm ci + npm run tauri build -- --target ${{ matrix.rust_target }} + + - name: Upload Desktop artifacts + uses: actions/upload-artifact@v4 + with: + name: desktop-${{ matrix.platform }} + path: | + swingmusic-desktop/target/${{ matrix.rust_target }}/release/bundle/ + !swingmusic-desktop/target/${{ matrix.rust_target }}/release/bundle/.*/ + retention-days: 30 + + build-android: + name: Build Android App + runs-on: ubuntu-latest + needs: get-version + if: contains(github.event.inputs.components, 'android') + + steps: + - uses: actions/checkout@v4 + - name: Initialize submodules + run: git submodule update --init --recursive + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: gradle- + + - 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: android-release + path: swingmusic-android/app/build/outputs/apk/release/*.apk + retention-days: 30 + + build-backend: + name: Build Backend + runs-on: ubuntu-latest + needs: get-version + if: contains(github.event.inputs.components, 'backend') + + steps: + - uses: actions/checkout@v4 + - name: Initialize submodules + run: git submodule update --init --recursive + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Cache Python dependencies + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: pip- + + - name: Install dependencies + run: | + cd src/swingmusic + pip install -r requirements.txt + + - name: Build Backend Package + run: | + cd src/swingmusic + pip install build + python -m build + + - name: Upload Backend artifacts + uses: actions/upload-artifact@v4 + with: + name: backend-package + path: src/swingmusic/dist/ + retention-days: 30 + + create-release: + name: Create Unified Release + runs-on: ubuntu-latest + needs: [get-version, build-desktop, build-android, build-backend] + if: success() + + steps: + - uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Prepare release assets + run: | + mkdir -p release-assets + + # Desktop apps + find artifacts -name "*.exe" -exec cp {} release-assets/ \; 2>/dev/null || true + find artifacts -name "*.dmg" -exec cp {} release-assets/ \; 2>/dev/null || true + find artifacts -name "*.AppImage" -exec cp {} release-assets/ \; 2>/dev/null || true + find artifacts -name "*.deb" -exec cp {} release-assets/ \; 2>/dev/null || true + find artifacts -name "*.rpm" -exec cp {} release-assets/ \; 2>/dev/null || true + + # Android APK + find artifacts -name "*.apk" -exec cp {} release-assets/ \; 2>/dev/null || true + + # Backend package + find artifacts -name "*.whl" -exec cp {} release-assets/ \; 2>/dev/null || true + find artifacts -name "*.tar.gz" -exec cp {} release-assets/ \; 2>/dev/null || true + + ls -la release-assets/ + + - name: Extract version + id: version + run: | + if [ -n "${{ github.event.inputs.version_number }}" ]; then + VERSION="${{ github.event.inputs.version_number }}" + TAG_NAME="v$VERSION" + else + VERSION="${{ needs.get-version.outputs.version }}" + TAG_NAME="${{ needs.get-version.outputs.tag_name }}" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.version.outputs.tag_name }} + name: SwingMusic v${{ steps.version.outputs.version }} + draft: false + prerelease: false + generate_release_notes: false + files: release-assets/* + body: | + ${{ github.event.inputs.version_number == '' && needs.get-version.outputs.release_notes || '' }} + + ## 📦 Installation + + ### 🖥️ Desktop Applications + - **Windows**: Download `.exe` installer + - **macOS Intel**: Download `x64.dmg` disk image + - **macOS ARM64**: Download `arm64.dmg` disk image + - **Linux x64**: Choose `.deb`, `.rpm`, or `.AppImage` + + ### 📱 Mobile Application + - **Android**: Download `.apk` file and install + + ### 🔧 Backend Server + - **Python**: Download `.whl` file: `pip install swingmusic-*.whl` + - **Source**: Download `.tar.gz` for manual installation + + ### 🚀 Quick Start + + #### Desktop + ```bash + # Linux AppImage + chmod +x SwingMusic*.AppImage + ./SwingMusic*.AppImage + + # Windows + # Run the downloaded .exe installer + + # macOS + # Open the .dmg and drag to Applications + ``` + + #### Backend + ```bash + # Install from wheel + pip install swingmusic-*.whl + + # Or from source + tar -xzf swingmusic-*.tar.gz + cd swingmusic-* + pip install -r requirements.txt + python -m swingmusic + ``` + + #### Android + ```bash + # Install APK + adb install swingmusic-*.apk + ``` + + --- + + ## 📋 Components + - ✅ **Desktop**: Cross-platform desktop application (4 platforms) + - ✅ **Android**: Native Android application + - ✅ **Backend**: Python-based REST API server + + ## 🔗 Links + - **Repository**: ${{ github.repository }} + - **Issues**: ${{ github.server_url }}/${{ github.repository }}/issues + + --- + + 🚀 **Thank you for using SwingMusic!** + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}