From fe7c9c3797d2abe1a0554cf76b0557b15e016882 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 2 Sep 2025 15:31:40 +0300 Subject: [PATCH] Add Linux build workflow for release packaging Introduces a GitHub Actions workflow to automate building and packaging Linux releases on tag push. The workflow sets up Python, installs dependencies, builds AppImage, RPM, and DEB packages using cx_Freeze, and uploads artifacts to a draft GitHub release. --- .github/workflows/build-linux.yml | 245 ++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 .github/workflows/build-linux.yml diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml new file mode 100644 index 0000000..ad905a7 --- /dev/null +++ b/.github/workflows/build-linux.yml @@ -0,0 +1,245 @@ +name: Build Linux Release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +env: + PYTHON_VERSION: '3.13.6' + +jobs: + build-linux: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract version from tag + id: get_version + shell: bash + run: | + tag="${GITHUB_REF_NAME}" + version="${tag#v}" + echo "Extracted version: $version" + echo "VERSION=$version" >> "$GITHUB_OUTPUT" + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Cache Python dependencies + uses: actions/cache@v3 + with: + path: | + venv + ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install system dependencies + shell: bash + run: | + sudo apt-get update -y + # Tools commonly required by cx_Freeze on Linux packaging + sudo apt-get install -y --no-install-recommends \ + rpm alien fakeroot patchelf desktop-file-utils file xz-utils zsync + + - name: Create virtual environment and install dependencies + shell: bash + run: | + python -m venv venv + source venv/bin/activate + python -m pip install --upgrade pip + pip install --no-cache-dir -r requirements.txt + pip install --no-cache-dir cx_Freeze yt-dlp + + - name: Prepare build variables + shell: bash + run: | + version="${{ steps.get_version.outputs.VERSION }}" + echo "VERSION=$version" >> "$GITHUB_ENV" + arch="$(uname -m)" # x86_64 or aarch64 + echo "ARCH=$arch" >> "$GITHUB_ENV" + echo "Prepared build variables for version: $version on $(uname -a)" + + - name: Create cx_Freeze setup script (Linux) + shell: bash + run: | + cat > setup_cxfreeze.py <<'PY' + import os + from cx_Freeze import setup, Executable + + version = os.environ.get("VERSION", "0.0.0") + + build_exe_options = dict( + optimize=2, + packages=[ + "PySide6.QtCore", + "PySide6.QtGui", + "PySide6.QtWidgets", + "requests", + "PIL", + "packaging", + "markdown", + "pyglet", + "loguru", + "setuptools", + ], + excludes=[ + "PySide6.QtBluetooth", + "PySide6.QtNetwork", + "PySide6.QtOpenGL", + "PySide6.QtPrintSupport", + "PySide6.QtSvg", + "PySide6.QtTest", + "PySide6.QtXml", + "PySide6.QtSql", + "PySide6.QtHelp", + "PySide6.QtMultimedia", + "PySide6.QtQml", + "PySide6.QtQuick", + "PySide6.QtWebEngineCore", + "PIL.ImageDraw", + "PIL.ImageFont", + "numpy", + "scipy", + "wx", + "pandas", + "tkinter", + "yt_dlp", + "unittest", + "test", + "tests", + ], + include_files=[ + ("src", "src"), + ("assets", "assets"), + ], + ) + + executables = [ + Executable( + script="main.py", + target_name=f"YTSage-v{version}", + icon="assets/branding/icons/icon.png", + ) + ] + + setup( + name="YTSage", + version=version, + description="YTSage", + options={ + "build_exe": build_exe_options, + # AppImage packaging + "bdist_appimage": { + # If ends with .AppImage, use verbatim file name + "target_name": f"YTSage-v{version}.AppImage", + # appimagetool will be auto-downloaded if not present + }, + # RPM packaging metadata (minimal) + "bdist_rpm": { + "release": "1", + "group": "Applications/Multimedia", + # Optional: icon can be picked from Executable's icon + }, + # DEB packaging wraps alien to convert RPM + "bdist_deb": {}, + }, + executables=executables, + ) + PY + + - name: Build AppImage, RPM and DEB + shell: bash + run: | + set -e + source venv/bin/activate + + # Ensure a clean build and build_exe first + python setup_cxfreeze.py build_exe + + # Remove screenshots to reduce size before packaging + build_dir=$(ls -d build/exe.* 2>/dev/null | head -n1 || true) + if [ -n "$build_dir" ] && [ -d "$build_dir/assets/branding/screenshots" ]; then + rm -rf "$build_dir/assets/branding/screenshots" + echo "Removed screenshots folder from $build_dir" + fi + + # Build AppImage + python setup_cxfreeze.py bdist_appimage + + # Build RPM + python setup_cxfreeze.py bdist_rpm + + # Build DEB (via alien from RPM) + python setup_cxfreeze.py bdist_deb || echo "bdist_deb completed with warnings (alien)" + + echo "Post-build directory listing:" + echo "-- dist --"; ls -lah dist || true + echo "-- build --"; ls -lah build || true + + - name: Package and prepare release artifacts (.AppImage, .rpm, .deb) + shell: bash + run: | + version="${{ steps.get_version.outputs.VERSION }}" + mkdir -p artifacts + + # Copy AppImage + appimage_src=$(ls dist/*.AppImage 2>/dev/null | head -n1 || true) + if [ -n "$appimage_src" ]; then + cp "$appimage_src" "artifacts/YTSage-v${version}.AppImage" + echo "Copied AppImage: $appimage_src -> artifacts/YTSage-v${version}.AppImage" + else + echo "Warning: No .AppImage found in dist/" + fi + + # Copy RPM (normalize name) + rpm_src=$(ls dist/*.rpm 2>/dev/null | head -n1 || true) + if [ -n "$rpm_src" ]; then + cp "$rpm_src" "artifacts/YTSage-v${version}.rpm" + echo "Copied RPM: $rpm_src -> artifacts/YTSage-v${version}.rpm" + else + echo "Warning: No .rpm found in dist/" + fi + + # Copy DEB (normalize name) + deb_src=$(ls dist/*.deb 2>/dev/null | head -n1 || true) + if [ -n "$deb_src" ]; then + cp "$deb_src" "artifacts/YTSage-v${version}.deb" + echo "Copied DEB: $deb_src -> artifacts/YTSage-v${version}.deb" + else + echo "Warning: No .deb found in dist/" + fi + + echo "Final artifacts:" + ls -lh artifacts || true + + - name: Create/Update draft release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ github.ref_name }} + name: YTSage ${{ github.ref_name }} + draft: true + prerelease: false + append_body: true + fail_on_unmatched_files: false + body: | + # YTSage ${{ github.ref_name }} + + **Release Date**: ${{ github.event.head_commit.timestamp }} + + **Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ github.ref_name }} + files: | + artifacts/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}