chore(release): 0.0.51 #156
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Multi-Platform Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| branches: | |
| - master | |
| pull_request: | |
| branches: | |
| - master | |
| workflow_dispatch: | |
| inputs: | |
| create_release: | |
| description: 'Create GitHub Release' | |
| required: false | |
| default: false | |
| type: boolean | |
| env: | |
| PYTHON_VERSION: '3.11' | |
| QDC_QUIET: '1' | |
| jobs: | |
| build: | |
| name: Build ${{ matrix.os }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # macOS Intel (macos-13 retired, using macos-15 with x64 arch) | |
| - os: macos-15 | |
| platform: macos-intel | |
| arch: x86_64 | |
| runner_arch: x64 | |
| # macOS Apple Silicon | |
| - os: macos-14 | |
| platform: macos-arm64 | |
| arch: arm64 | |
| runner_arch: arm64 | |
| # Linux x86_64 | |
| - os: ubuntu-latest | |
| platform: linux | |
| arch: x86_64 | |
| runner_arch: x64 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python ${{ env.PYTHON_VERSION }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| architecture: ${{ matrix.runner_arch }} | |
| - name: Install system dependencies (Linux) | |
| if: matrix.platform == 'linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| python3-dev \ | |
| build-essential \ | |
| libgl1-mesa-dev \ | |
| libxkbcommon-x11-0 \ | |
| libxcb-cursor0 \ | |
| libxcb-icccm4 \ | |
| libxcb-image0 \ | |
| libxcb-keysyms1 \ | |
| libxcb-randr0 \ | |
| libxcb-render-util0 \ | |
| libxcb-xinerama0 \ | |
| libxcb-xfixes0 \ | |
| libxcb-shape0 \ | |
| libxcb-sync1 \ | |
| libxcb-shm0 \ | |
| libxcb-render0 \ | |
| libxcb-util1 \ | |
| libxcb-glx0 \ | |
| libxcb-dri2-0 \ | |
| libxcb-dri3-0 \ | |
| libxcb-present0 \ | |
| libxcb1 \ | |
| libxcb-xkb1 \ | |
| libxcb-xinput0 \ | |
| libfontconfig1 \ | |
| libfreetype6 \ | |
| libx11-6 \ | |
| libx11-xcb1 \ | |
| libxi6 \ | |
| libxrender1 \ | |
| libdbus-1-3 \ | |
| libegl1 \ | |
| libxcb-xtest0 | |
| - name: Install system dependencies (macOS) | |
| if: startsWith(matrix.platform, 'macos') | |
| run: | | |
| # Install Homebrew if not present (for self-hosted runners) | |
| if ! command -v brew &> /dev/null; then | |
| /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" | |
| fi | |
| # Update Homebrew | |
| brew update | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v4 | |
| - name: Install Python dependencies | |
| run: uv sync | |
| - name: Verify PyQt6 installation | |
| run: | | |
| uv run python -c "from PyQt6 import QtCore; print(f'PyQt6 version: {QtCore.PYQT_VERSION_STR}')" | |
| uv run python -c "from PyQt6 import QtWidgets; print('PyQt6.QtWidgets imported successfully')" | |
| - name: Set up display (Linux) | |
| if: matrix.platform == 'linux' | |
| run: | | |
| # Set up virtual display for GUI applications | |
| export DISPLAY=:99 | |
| sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & | |
| sleep 3 | |
| echo "DISPLAY=:99" >> $GITHUB_ENV | |
| - name: Test application startup (quick smoke test) | |
| env: | |
| QT_QPA_PLATFORM: ${{ matrix.platform == 'linux' && 'offscreen' || '' }} | |
| DISPLAY: ${{ matrix.platform == 'linux' && ':99' || '' }} | |
| run: | | |
| uv run python - <<'PY' | |
| import os | |
| import subprocess | |
| import sys | |
| cmd = [sys.executable, "lazy_blacktea_pyqt.py", "--version"] | |
| env = os.environ.copy() | |
| try: | |
| result = subprocess.run(cmd, check=False, timeout=30, env=env) | |
| print(f"Command exited with return code {result.returncode}") | |
| except subprocess.TimeoutExpired: | |
| print("Command timed out after 30 seconds") | |
| PY | |
| - name: Clean previous builds | |
| run: | | |
| rm -rf build/ dist/ __pycache__/ | |
| find . -name "*.pyc" -delete | |
| - name: Build application | |
| run: | | |
| echo "Starting build for platform: ${{ matrix.platform }}" | |
| echo "Architecture: ${{ matrix.arch }}" | |
| echo "Working directory: $(pwd)" | |
| uv run python build-scripts/build.py | |
| echo "Build script completed with exit code: $?" | |
| echo "Contents of dist/ after build:" | |
| ls -la dist/ || echo "No dist directory created" | |
| env: | |
| # Ensure we're building for the correct architecture | |
| ARCHFLAGS: ${{ matrix.platform == 'macos-arm64' && '-arch arm64' || matrix.platform == 'macos-intel' && '-arch x86_64' || '' }} | |
| CFLAGS: ${{ matrix.platform == 'macos-arm64' && '-arch arm64' || matrix.platform == 'macos-intel' && '-arch x86_64' || '' }} | |
| LDFLAGS: ${{ matrix.platform == 'macos-arm64' && '-arch arm64' || matrix.platform == 'macos-intel' && '-arch x86_64' || '' }} | |
| - name: Verify build artifacts | |
| run: | | |
| ls -la dist/ | |
| if [ "${{ matrix.platform }}" = "linux" ]; then | |
| # Verify Linux build | |
| [ -d "dist/lazyblacktea" ] && echo "✅ Linux executable found" || (echo "❌ Linux executable missing" && exit 1) | |
| [ -f "dist/lazyblacktea-linux.tar.gz" ] && echo "✅ Linux tarball found" || echo "⚠️ Linux tarball missing" | |
| [ -f "dist/LazyBlacktea-x86_64.AppImage" ] && echo "✅ AppImage found" || echo "⚠️ AppImage missing" | |
| else | |
| # Verify macOS build | |
| [ -d "dist/LazyBlacktea.app" ] && echo "✅ macOS app bundle found" || (echo "❌ macOS app bundle missing" && exit 1) | |
| [ -f "dist/LazyBlacktea.dmg" ] && echo "✅ macOS DMG found" || echo "⚠️ macOS DMG missing" | |
| fi | |
| - name: Test built application (quick smoke test) | |
| env: | |
| QT_QPA_PLATFORM: ${{ matrix.platform == 'linux' && 'offscreen' || '' }} | |
| DISPLAY: ${{ matrix.platform == 'linux' && ':99' || '' }} | |
| MATRIX_PLATFORM: ${{ matrix.platform }} | |
| run: | | |
| uv run python - <<'PY' | |
| import os | |
| import subprocess | |
| platform = os.environ["MATRIX_PLATFORM"] | |
| target = "./dist/lazyblacktea/lazyblacktea" if platform == "linux" else "./dist/LazyBlacktea.app/Contents/MacOS/LazyBlacktea" | |
| env = os.environ.copy() | |
| try: | |
| result = subprocess.run([target, "--version"], check=False, timeout=30, env=env) | |
| print(f"{target} exited with return code {result.returncode}") | |
| except FileNotFoundError as err: | |
| print(f"Executable not found: {err}") | |
| except subprocess.TimeoutExpired: | |
| print("Command timed out after 30 seconds") | |
| PY | |
| - name: Prepare artifacts | |
| run: | | |
| mkdir -p artifacts | |
| echo "Platform: ${{ matrix.platform }}" | |
| echo "Architecture: ${{ matrix.arch }}" | |
| echo "Available files in dist/:" | |
| ls -la dist/ || echo "No dist directory found" | |
| if [ "${{ matrix.platform }}" = "linux" ]; then | |
| # Copy Linux artifacts | |
| echo "Looking for Linux artifacts..." | |
| if [ -f "dist/lazyblacktea-linux.tar.gz" ]; then | |
| cp "dist/lazyblacktea-linux.tar.gz" "artifacts/lazyblacktea-linux-${{ matrix.arch }}.tar.gz" | |
| echo "✅ Copied tarball" | |
| else | |
| echo "⚠️ Tarball not found" | |
| fi | |
| if [ -f "dist/LazyBlacktea-x86_64.AppImage" ]; then | |
| cp "dist/LazyBlacktea-x86_64.AppImage" "artifacts/LazyBlacktea-linux-${{ matrix.arch }}.AppImage" | |
| echo "✅ Copied AppImage" | |
| else | |
| echo "⚠️ AppImage not found" | |
| fi | |
| else | |
| # Copy macOS artifacts | |
| echo "Looking for macOS artifacts..." | |
| if [ -f "dist/LazyBlacktea.dmg" ]; then | |
| cp "dist/LazyBlacktea.dmg" "artifacts/LazyBlacktea-${{ matrix.platform }}.dmg" | |
| echo "✅ Copied DMG" | |
| else | |
| echo "⚠️ DMG not found" | |
| fi | |
| # Also create a zip of the app bundle as fallback | |
| if [ -d "dist/LazyBlacktea.app" ]; then | |
| cd dist && zip -r "../artifacts/LazyBlacktea-${{ matrix.platform }}.zip" LazyBlacktea.app && cd .. | |
| echo "✅ Created ZIP from app bundle" | |
| else | |
| echo "⚠️ App bundle not found" | |
| fi | |
| fi | |
| echo "Final artifacts:" | |
| ls -la artifacts/ || echo "No artifacts created" | |
| # Ensure we have at least one file to upload | |
| if [ ! "$(ls -A artifacts/)" ]; then | |
| echo "No artifacts were created. Creating a build report..." | |
| echo "Build completed but no artifacts were generated." > artifacts/build-report-${{ matrix.platform }}.txt | |
| echo "Platform: ${{ matrix.platform }}" >> artifacts/build-report-${{ matrix.platform }}.txt | |
| echo "Architecture: ${{ matrix.arch }}" >> artifacts/build-report-${{ matrix.platform }}.txt | |
| echo "Build time: $(date)" >> artifacts/build-report-${{ matrix.platform }}.txt | |
| fi | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: build-${{ matrix.platform }} | |
| path: artifacts/* | |
| retention-days: 30 | |
| if-no-files-found: warn | |
| - name: Upload build logs on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: build-logs-${{ matrix.platform }}-${{ github.run_number }} | |
| path: | | |
| build/ | |
| *.log | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| release: | |
| name: Create Release | |
| needs: build | |
| runs-on: ubuntu-latest | |
| if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && github.event.inputs.create_release == 'true') | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts/ | |
| pattern: build-* | |
| merge-multiple: true | |
| - name: Prepare release assets | |
| run: | | |
| mkdir -p release-assets | |
| # Copy all build artifacts to release assets | |
| find artifacts/ -type f \( -name "*.dmg" -o -name "*.zip" -o -name "*.tar.gz" -o -name "*.AppImage" \) -exec cp {} release-assets/ \; | |
| # List all release assets | |
| echo "Release assets:" | |
| ls -la release-assets/ | |
| - name: Generate release notes | |
| id: release_notes | |
| run: | | |
| set -euo pipefail | |
| if [[ "${{ github.ref }}" =~ refs/tags/(v.+) ]]; then | |
| VERSION="${BASH_REMATCH[1]}" | |
| else | |
| VERSION="development-$(date +%Y%m%d-%H%M%S)" | |
| fi | |
| git fetch --tags --force >/dev/null 2>&1 || true | |
| CURRENT_TAG="" | |
| if git describe --tags --exact-match >/dev/null 2>&1; then | |
| CURRENT_TAG="$(git describe --tags --exact-match)" | |
| fi | |
| PREVIOUS_TAG="" | |
| if [[ -n "${CURRENT_TAG}" ]]; then | |
| PREVIOUS_TAG="$(git describe --tags --abbrev=0 "${CURRENT_TAG}^" 2>/dev/null || true)" | |
| else | |
| PREVIOUS_TAG="$(git describe --tags --abbrev=0 2>/dev/null || true)" | |
| fi | |
| if [[ -n "${PREVIOUS_TAG}" ]]; then | |
| CHANGELOG="$(git log --no-merges --pretty=format:'- %s (%h)' "${PREVIOUS_TAG}"..HEAD)" | |
| else | |
| CHANGELOG="$(git log --no-merges --pretty=format:'- %s (%h)')" | |
| fi | |
| if [[ -z "${CHANGELOG}" ]]; then | |
| CHANGELOG='- No code changes detected.' | |
| fi | |
| cat > release-notes.md <<EOF | |
| # Lazy Blacktea ${VERSION} | |
| ## 📦 Downloads | |
| ### macOS | |
| - **macOS Intel (x86_64)**: \`LazyBlacktea-macos-intel.dmg\` or \`LazyBlacktea-macos-intel.zip\` | |
| - **macOS Apple Silicon (ARM64)**: \`LazyBlacktea-macos-arm64.dmg\` or \`LazyBlacktea-macos-arm64.zip\` | |
| ### Linux | |
| - **Linux x86_64**: \`lazyblacktea-linux-x86_64.tar.gz\` or \`LazyBlacktea-linux-x86_64.AppImage\` | |
| ## 🚀 Installation | |
| ### macOS | |
| 1. Download the appropriate \`.dmg\` file for your Mac (Intel or Apple Silicon) | |
| 2. Open the \`.dmg\` file and drag Lazy Blacktea to Applications | |
| 3. **Important**: macOS will show security warnings on first launch | |
| #### If you see "Cannot verify the app is free of malware": | |
| **Method 1 - Right-click to Open:** | |
| - Right-click on LazyBlacktea.app | |
| - Select "Open" from context menu | |
| - Click "Open" again when prompted | |
| **Method 2 - System Preferences:** | |
| - Try to open the app normally (it will be blocked) | |
| - Go to System Preferences → Security & Privacy → General | |
| - Click "Open Anyway" button next to the LazyBlacktea message | |
| **Method 3 - Terminal (if app crashes immediately):** | |
| ```bash | |
| # Remove quarantine and re-sign the app | |
| xattr -rd com.apple.quarantine /Applications/LazyBlacktea.app | |
| codesign --force --deep --sign - /Applications/LazyBlacktea.app | |
| ``` | |
| > **Note**: The app is signed with ad-hoc signature but not notarized by Apple. This is safe to use - it's just not distributed through the App Store. | |
| ### Linux | |
| - **AppImage**: Download and run \`chmod +x LazyBlacktea-linux-x86_64.AppImage && ./LazyBlacktea-linux-x86_64.AppImage\` | |
| - **Tarball**: Extract and run \`tar -xzf lazyblacktea-linux-x86_64.tar.gz && ./lazyblacktea/lazy_blacktea\` | |
| ## 📝 Changes | |
| ${CHANGELOG} | |
| Built from commit: \`${{ github.sha }}\` | |
| ## 🔒 Security Note & Troubleshooting | |
| **macOS Apps**: These applications are signed with ad-hoc signatures for security but are not notarized by Apple. | |
| ### Common Issues: | |
| 1. **"Cannot verify the app is free of malware"** - Use methods above ✅ | |
| > **Latest builds (v0.0.6+)**: Include improved signing and should work without manual intervention. | |
| **Fixed Issues (v0.0.6+)**: | |
| - ✅ **App immediate closure fixed**: Previous versions crashed due to ADB path detection issues | |
| - ✅ **Improved code signing**: Apps now properly signed during build process | |
| - ✅ **Better dependency detection**: PyInstaller spec includes all required modules | |
| ### Why security warnings occur: | |
| - ✅ The apps are properly signed and safe to use | |
| - ⚠️ macOS Gatekeeper shows warnings for apps not distributed through App Store | |
| - 💡 Our automated build process includes proper signing to prevent crashes | |
| For production releases, consider upgrading to Apple Developer Program for full notarization. | |
| --- | |
| **Note**: This is an automated release built with GitHub Actions. | |
| EOF | |
| echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT | |
| - name: Create Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || format('v{0}', steps.release_notes.outputs.VERSION) }} | |
| name: Lazy Blacktea ${{ steps.release_notes.outputs.VERSION }} | |
| body_path: release-notes.md | |
| files: release-assets/* | |
| draft: false | |
| prerelease: ${{ contains(github.ref_name, 'beta') || contains(github.ref_name, 'alpha') || contains(github.ref_name, 'rc') }} | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| allowUpdates: true | |
| notify: | |
| name: Build Status Notification | |
| needs: [build] | |
| runs-on: ubuntu-latest | |
| if: always() | |
| steps: | |
| - name: Report build status | |
| run: | | |
| if [ "${{ needs.build.result }}" = "success" ]; then | |
| echo "✅ All builds completed successfully!" | |
| else | |
| echo "❌ Some builds failed. Check the build logs for details." | |
| exit 1 | |
| fi |