1
0
mirror of https://github.com/darkzoul5/YoutubePlaylistSync.git synced 2026-07-03 04:23:59 +03:00
Files
YoutubePlaylistSync/.github/workflows/build-release.yml
T
dark_zoul 49fedecd43 feat: add app icon;
feat: add app to tray;
2026-05-17 13:51:15 +03:00

289 lines
8.8 KiB
YAML

name: Build & Release
on:
workflow_dispatch:
inputs:
tag:
description: "Tag to create (e.g., v2.0.0)"
required: true
default: "v2.0.0"
type: string
force_tag:
description: "Recreate tag if it already exists"
required: false
default: false
type: boolean
draft:
description: "Create release as draft"
required: false
default: true
type: boolean
permissions:
contents: write
jobs:
tag:
name: Create tag
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Validate input tag
shell: bash
run: |
set -euo pipefail
TAG="${{ inputs.tag }}"
if [[ -z "$TAG" ]]; then
echo "tag is required" >&2
exit 1
fi
if [[ "$TAG" != v* ]]; then
echo "tag must start with 'v' (got: $TAG)" >&2
exit 1
fi
- name: Create/push tag
shell: bash
env:
TAG: ${{ inputs.tag }}
FORCE: ${{ inputs.force_tag }}
run: |
set -euo pipefail
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
if [[ "${FORCE}" != "true" ]]; then
echo "Tag ${TAG} already exists. Re-run with force_tag=true to recreate." >&2
exit 1
fi
git tag -d "${TAG}" || true
git push origin ":refs/tags/${TAG}" || true
fi
git tag "${TAG}" "${GITHUB_SHA}"
git push origin "${TAG}"
test:
name: Unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.12"
cache: "pip"
- name: Install
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]" || pip install -e .
pip install pytest
- name: Run tests
env:
PYTHONPATH: ${{ github.workspace }}/src
run: pytest
build:
name: Build packages
needs: tag
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, ubuntu-latest]
ffmpeg: [bundled, none]
steps:
- uses: actions/checkout@v6
- name: Derive version
id: version
shell: bash
run: |
TAG="${{ inputs.tag }}"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
- uses: actions/setup-python@v6
with:
python-version: "3.12"
cache: "pip"
- name: Install build deps
run: |
python -m pip install --upgrade pip
pip install -e .
pip install pyinstaller
- name: Build binary (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
pyinstaller --noconfirm --onefile --noconsole --name "ytpl-sync" --icon "assets/icon.ico" --add-data "assets/icon.png;assets" --distpath "dist/pyinstaller" --workpath "build/pyinstaller" --specpath "build/pyinstaller" --paths "src" "ytpl-sync-entry.py"
- name: Build binary (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
set -euo pipefail
pyinstaller --noconfirm --onefile --noconsole --name "ytpl-sync" --icon "assets/icon.png" --add-data "assets/icon.png:assets" --distpath "dist/pyinstaller" --workpath "build/pyinstaller" --specpath "build/pyinstaller" --paths "src" "ytpl-sync-entry.py"
- name: Stage package
shell: bash
run: |
set -euo pipefail
OS="${{ runner.os }}"
FFMPEG="${{ matrix.ffmpeg }}"
VERSION="${{ steps.version.outputs.version }}"
PKG_ROOT="$GITHUB_WORKSPACE/package"
rm -rf "$PKG_ROOT"
mkdir -p "$PKG_ROOT/config" "$PKG_ROOT/bin"
# Binary
if [[ "$OS" == "Windows" ]]; then
cp "$GITHUB_WORKSPACE/dist/pyinstaller/ytpl-sync.exe" "$PKG_ROOT/ytpl-sync.exe"
else
cp "$GITHUB_WORKSPACE/dist/pyinstaller/ytpl-sync" "$PKG_ROOT/ytpl-sync.exe"
chmod +x "$PKG_ROOT/ytpl-sync.exe"
fi
# Config: ship example as the default config
python - <<'PY'
import json
import os
from pathlib import Path
workspace = Path(os.environ["GITHUB_WORKSPACE"])
pkg_root = workspace / "package"
src = workspace / "config" / "yt-playlist-config.example.json"
dst = pkg_root / "config" / "yt-playlist-config.json"
data = json.loads(src.read_text(encoding="utf-8"))
os_name = os.environ["RUNNER_OS"]
ffmpeg_mode = os.environ["FFMPEG_MODE"]
if ffmpeg_mode == "bundled":
data["ffmpeg_path"] = "./bin/ffmpeg.exe" if os_name == "Windows" else "./bin/ffmpeg"
else:
data["ffmpeg_path"] = "ffmpeg"
dst.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
PY
env:
FFMPEG_MODE: ${{ matrix.ffmpeg }}
- name: Bundle FFmpeg (Windows)
if: runner.os == 'Windows' && matrix.ffmpeg == 'bundled'
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
New-Item -ItemType Directory -Force -Path "package/bin" | Out-Null
Invoke-WebRequest -Uri "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip" -OutFile "ffmpeg.zip"
Expand-Archive "ffmpeg.zip" -DestinationPath "ffmpeg_tmp"
$ffmpegExe = Get-ChildItem -Path "ffmpeg_tmp" -Filter "ffmpeg.exe" -Recurse | Select-Object -First 1
if (-not $ffmpegExe) { throw "ffmpeg.exe not found in archive" }
Copy-Item $ffmpegExe.FullName "package/bin/ffmpeg.exe"
Remove-Item -Force "ffmpeg.zip"
Remove-Item -Recurse -Force "ffmpeg_tmp"
- name: Bundle FFmpeg (Linux)
if: runner.os == 'Linux' && matrix.ffmpeg == 'bundled'
shell: bash
run: |
set -euo pipefail
mkdir -p package/bin ffmpeg_tmp
curl -L "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz" -o ffmpeg.tar.xz
tar -xf ffmpeg.tar.xz -C ffmpeg_tmp --strip-components=1
mv ffmpeg_tmp/ffmpeg package/bin/ffmpeg
chmod +x package/bin/ffmpeg
- name: Archive (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$version = "${{ steps.version.outputs.version }}"
$variant = "${{ matrix.ffmpeg }}"
if ($variant -eq "bundled") {
$name = "ytpl-sync-windows-$version-ffmpeg.zip"
} else {
$name = "ytpl-sync-windows-$version.zip"
}
Compress-Archive -Path "package/*" -DestinationPath $name
- name: Archive (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
set -euo pipefail
version="${{ steps.version.outputs.version }}"
variant="${{ matrix.ffmpeg }}"
if [[ "$variant" == "bundled" ]]; then
name="ytpl-sync-linux-${version}-ffmpeg.tar.gz"
else
name="ytpl-sync-linux-${version}.tar.gz"
fi
tar -C package -czf "$name" .
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: packages-${{ runner.os }}-${{ matrix.ffmpeg }}
path: |
ytpl-sync-*.zip
ytpl-sync-*.tar.gz
release:
name: Create Release
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Download artifacts
uses: actions/download-artifact@v8
with:
path: artifacts
- name: Generate release notes (since last tag)
shell: bash
env:
TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
git fetch --tags --force
prev_tag="$(git tag --sort=-creatordate | grep -Fxv "$TAG" | head -n 1 || true)"
{
echo "## Changes"
echo
if [[ -n "$prev_tag" ]]; then
echo "Compared to \`$prev_tag\`:"
echo
git log "${prev_tag}..${TAG}" --no-merges --pretty=format:'- %s (%h)' || true
else
echo "First tagged release:"
echo
git log "${TAG}" --no-merges --pretty=format:'- %s (%h)' || true
fi
echo
} > release-notes.md
- name: Create release
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ inputs.tag }}
draft: ${{ inputs.draft }}
body_path: release-notes.md
files: |
artifacts/**/*.zip
artifacts/**/*.tar.gz