# ============================================================================= # Warlock Studio — Gitea Actions CI/CD Pipeline # ============================================================================= name: Build and Release on: push: tags: - 'v*' workflow_dispatch: inputs: version: description: 'Release version (e.g., 6.0.1)' required: true dry_run: description: 'Dry run — build but do not create a release' type: boolean default: false env: APP_NAME: Warlock-Studio PYTHON_VERSION: '3.10' PYTHON_WIN_URL: 'https://www.python.org/ftp/python/3.10.11/python-3.10.11-amd64.exe' jobs: # ========================================================================== # VALIDATE — syntax + lint before building # ========================================================================== validate: name: Validate source runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Setup Python ${{ env.PYTHON_VERSION }} uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pyinstaller pyflakes - name: Syntax check (compileall — hard gate) run: python -m compileall -q *.py - name: Lint info (pyflakes — non-blocking advisory) run: | python -m pyflakes *.py || echo "Pyflakes warnings above (advisory only)" - name: Verify .spec run: | python -c " with open('Warlock-Studio.spec') as f: c = f.read() assert 'Analysis' in c assert 'EXE' in c print('OK .spec valid') " # ========================================================================== # BUILD — matrix: linux (native) + windows (cross via wine) # ========================================================================== build: name: Build (${{ matrix.target }}) needs: [validate] runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: target: [linux, windows] steps: - uses: actions/checkout@v4 # ---- Common: Setup Python ---- - name: Setup Python ${{ env.PYTHON_VERSION }} uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install common system deps run: | sudo apt-get update -qq sudo apt-get install -y -qq libgl1-mesa-glx libegl1 libxkbcommon0 file - name: Install Python deps run: | python -m pip install --upgrade pip wheel setuptools pip install -r requirements.txt pip install pyinstaller # ---- Linux: native build ---- - name: Build Linux native (PyInstaller) if: matrix.target == 'linux' run: pyinstaller Warlock-Studio.spec --noconfirm - name: Package Linux (tar.gz) if: matrix.target == 'linux' run: | v="${{ github.ref_name }}" && v="${v#v}" [ "${{ github.event_name }}" = "workflow_dispatch" ] && v="${{ github.event.inputs.version }}" [ -z "$v" ] && v="dev" a="${{ env.APP_NAME }}-${v}-linux-x64.tar.gz" tar -czf "${a}" -C dist "${{ env.APP_NAME }}" echo "ARCHIVE_NAME=${a}" >> $GITHUB_ENV - name: Verify Linux binary if: matrix.target == 'linux' run: | ls -lh "dist/${{ env.APP_NAME }}/${{ env.APP_NAME }}" file "dist/${{ env.APP_NAME }}/${{ env.APP_NAME }}" # ---- Windows: cross-build via Wine ---- - name: Install Wine + Windows Python if: matrix.target == 'windows' run: | sudo dpkg --add-architecture i386 sudo apt-get update -qq sudo apt-get install -y -qq wine wine32 wine64 xvfb # Download Windows Python into stable location mkdir -p /tmp/winbuild wget -q "${{ env.PYTHON_WIN_URL }}" -O /tmp/winbuild/python-installer.exe - name: Setup Windows Python under Wine if: matrix.target == 'windows' run: | export DISPLAY=:99 Xvfb :99 -screen 0 1024x768x16 &>/dev/null & sleep 1 # Install Python for Windows silently wine /tmp/winbuild/python-installer.exe \ /quiet InstallAllUsers=1 PrependPath=1 \ Include_doc=0 Include_tcltk=1 \ TargetDir='C:\Python310' 2>&1 | tail -5 # Verify wine python --version 2>&1 wine pip --version 2>&1 - name: Install Windows dependencies under Wine if: matrix.target == 'windows' run: | # Map project directory as Windows drive WINEPROJ="Z:$(pwd)" # Install pip deps wine pip install --no-input --no-cache-dir \ -r "${WINEPROJ}/requirements.txt" 2>&1 | tail -5 wine pip install --no-input --no-cache-dir \ pyinstaller onnxruntime-directml 2>&1 | tail -5 - name: Build Windows binary (PyInstaller via Wine) if: matrix.target == 'windows' run: | WINEPROJ="Z:$(pwd)" wine pyinstaller "${WINEPROJ}/Warlock-Studio.spec" \ --noconfirm --distpath "${WINEPROJ}/dist" 2>&1 | tail -10 - name: Verify Windows binary if: matrix.target == 'windows' run: | ls -lh "dist/${{ env.APP_NAME }}/${{ env.APP_NAME }}.exe" file "dist/${{ env.APP_NAME }}/${{ env.APP_NAME }}.exe" - name: Package Windows (ZIP) if: matrix.target == 'windows' run: | v="${{ github.ref_name }}" && v="${v#v}" [ "${{ github.event_name }}" = "workflow_dispatch" ] && v="${{ github.event.inputs.version }}" [ -z "$v" ] && v="dev" a="${{ env.APP_NAME }}-${v}-win64.zip" cd dist zip -r "${a}" "${{ env.APP_NAME }}" cd .. mv "dist/${a}" . echo "ARCHIVE_NAME=${a}" >> $GITHUB_ENV - name: Upload artifact uses: actions/upload-artifact@v4 with: name: ${{ env.APP_NAME }}-${{ matrix.target }} path: ${{ env.ARCHIVE_NAME }} # ========================================================================== # RELEASE — create Gitea release + attach binaries # ========================================================================== release: name: Create Release needs: [build] runs-on: ubuntu-22.04 if: github.event_name != 'workflow_dispatch' || !github.event.inputs.dry_run steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/download-artifact@v4 with: pattern: ${{ env.APP_NAME }}-* path: artifacts merge-multiple: true - name: List artifacts run: ls -lh artifacts/ - name: Determine version id: version run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "tag=v${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT else echo "tag=${{ github.ref_name }}" >> $GITHUB_OUTPUT v="${{ github.ref_name }}" && echo "version=${v#v}" >> $GITHUB_OUTPUT fi - name: Prepare release notes run: | if [ -f CHANGELOG.md ]; then awk '/^## /{if(c) exit; c=1; next} c' CHANGELOG.md > release-notes.md 2>/dev/null || true fi [ -s release-notes.md ] || echo "Release ${{ steps.version.outputs.tag }}" > release-notes.md - name: Create Gitea Release id: create_release env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | BODY=$(cat release-notes.md | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))") PAYLOAD='{ "tag_name": "${{ steps.version.outputs.tag }}", "target_commitish": "${{ github.sha }}", "name": "Warlock Studio ${{ steps.version.outputs.version }}", "body": '"${BODY}"', "draft": false, "prerelease": false }' RESP=$(curl -s -X POST \ -H "Authorization: token ${RELEASE_TOKEN}" \ -H "Content-Type: application/json" \ -d "${PAYLOAD}" \ "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases") ID=$(echo "${RESP}" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])" 2>/dev/null || echo "") if [ -z "${ID}" ]; then echo "ERROR creating release:" echo "${RESP}" exit 1 fi echo "release_id=${ID}" >> $GITHUB_OUTPUT echo "Release ID: ${ID}" - name: Upload assets to release env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | ID="${{ steps.create_release.outputs.release_id }}" URL="${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/${ID}/assets" for a in artifacts/*; do [ -f "${a}" ] || continue fname=$(basename "${a}") echo "Uploading: ${fname} ($(stat -c%s "${a}") bytes)" curl -s -X POST \ -H "Authorization: token ${RELEASE_TOKEN}" \ -H "Content-Type: application/octet-stream" \ --data-binary "@${a}" \ "${URL}?name=${fname}" > /dev/null if [ $? -eq 0 ]; then echo " OK" else echo " FAIL" fi done