Files
SageTube/.github/workflows/build-windows.yml
T
oop7 3ea5fedfa3 Remove Qt DLLs from PySide6 and suppress errors
Add -ErrorAction SilentlyContinue to Get-ChildItem calls and add a check for a lib\PySide6 folder to remove matching Qt DLLs there as well. This prevents failures when no files match the patterns and ensures duplicate Qt DLLs packaged under PySide6 are cleaned up. Applied to both DLL-cleanup sections of the Windows build workflow.
2026-02-03 15:20:09 +02:00

466 lines
22 KiB
YAML

name: Build Windows Release
on:
workflow_dispatch:
inputs:
version:
description: 'Version name for the release (e.g., 1.0.0)'
required: true
type: string
workflow_call:
inputs:
version:
description: 'Version name for the release (e.g., 1.0.0)'
required: true
type: string
permissions:
contents: write
env:
PYTHON_VERSION: '3.13'
jobs:
build-windows:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Get version
id: get_version
shell: powershell
run: |
$version = "${{ inputs.version || github.event.inputs.version }}"
Write-Host "Extracted version: $version"
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Cache Python dependencies
uses: actions/cache@v4
with:
path: |
venv
~\AppData\Local\pip\Cache
key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Create virtual environment and install dependencies
shell: powershell
run: |
python -m venv venv
.\venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
pip install --no-cache-dir .
pip install --no-cache-dir cx_Freeze
- name: Prepare build variables
shell: powershell
run: |
$version = "${{ steps.get_version.outputs.VERSION }}"
echo "VERSION=$version" >> $env:GITHUB_ENV
Write-Host "Prepared build variables for version: $version"
- name: Create cx_Freeze setup script
shell: powershell
run: |
# Create entry point script
$entryContent = @'
from ytsage.main import main
if __name__ == "__main__":
main()
'@
Set-Content -Path "ytsage_entry.py" -Value $entryContent
# Create setup script for cx_Freeze
New-Item -Path "setup_cxfreeze.py" -ItemType File -Force
Add-Content -Path "setup_cxfreeze.py" -Value "import os"
Add-Content -Path "setup_cxfreeze.py" -Value "from cx_Freeze import setup, Executable"
Add-Content -Path "setup_cxfreeze.py" -Value ""
Add-Content -Path "setup_cxfreeze.py" -Value 'version = os.environ.get("VERSION", "0.0.0")'
Add-Content -Path "setup_cxfreeze.py" -Value ""
Add-Content -Path "setup_cxfreeze.py" -Value "build_exe_options = dict("
Add-Content -Path "setup_cxfreeze.py" -Value " optimize=2,"
Add-Content -Path "setup_cxfreeze.py" -Value " silent=True,"
Add-Content -Path "setup_cxfreeze.py" -Value " packages=["
Add-Content -Path "setup_cxfreeze.py" -Value ' "ytsage",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtCore",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtGui",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtWidgets",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtMultimedia",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtNetwork",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "requests",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PIL",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "packaging",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "markdown",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "loguru",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "setuptools",'
Add-Content -Path "setup_cxfreeze.py" -Value " ],"
Add-Content -Path "setup_cxfreeze.py" -Value " zip_include_packages=["
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "shiboken6",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "requests",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PIL",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "packaging",'
Add-Content -Path "setup_cxfreeze.py" -Value " ],"
Add-Content -Path "setup_cxfreeze.py" -Value " excludes=["
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtBluetooth",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtOpenGL",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtPrintSupport",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtSvg",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtTest",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtXml",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtSql",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtHelp",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtQml",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtQuick",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtWebEngineCore",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PIL.ImageDraw",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "PIL.ImageFont",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "numpy",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "scipy",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "wx",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "pandas",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "tkinter",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "yt_dlp",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "unittest",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "pydoc",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "doctest",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "email",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "test",'
Add-Content -Path "setup_cxfreeze.py" -Value ' "tests",'
Add-Content -Path "setup_cxfreeze.py" -Value " ],"
Add-Content -Path "setup_cxfreeze.py" -Value " include_files=["
Add-Content -Path "setup_cxfreeze.py" -Value ' ("ytsage/assets/Icon", "lib/assets/Icon"),'
Add-Content -Path "setup_cxfreeze.py" -Value ' ("ytsage/assets/sound", "lib/assets/sound"),'
Add-Content -Path "setup_cxfreeze.py" -Value ' ("ytsage/languages", "lib/languages"),'
Add-Content -Path "setup_cxfreeze.py" -Value ' ("branding/icons", "lib/assets/branding/icons"),'
Add-Content -Path "setup_cxfreeze.py" -Value " ],"
Add-Content -Path "setup_cxfreeze.py" -Value ")"
Add-Content -Path "setup_cxfreeze.py" -Value ""
Add-Content -Path "setup_cxfreeze.py" -Value "executables = ["
Add-Content -Path "setup_cxfreeze.py" -Value " Executable("
Add-Content -Path "setup_cxfreeze.py" -Value ' script="ytsage_entry.py",'
Add-Content -Path "setup_cxfreeze.py" -Value ' target_name=f"YTSage-v{version}.exe",'
Add-Content -Path "setup_cxfreeze.py" -Value ' base="gui",'
Add-Content -Path "setup_cxfreeze.py" -Value ' icon="branding/icons/YTSage.ico",'
Add-Content -Path "setup_cxfreeze.py" -Value " )"
Add-Content -Path "setup_cxfreeze.py" -Value "]"
Add-Content -Path "setup_cxfreeze.py" -Value ""
Add-Content -Path "setup_cxfreeze.py" -Value "setup("
Add-Content -Path "setup_cxfreeze.py" -Value ' name="YTSage",'
Add-Content -Path "setup_cxfreeze.py" -Value " version=version,"
Add-Content -Path "setup_cxfreeze.py" -Value ' description="YTSage",'
Add-Content -Path "setup_cxfreeze.py" -Value ' options={"build_exe": build_exe_options},'
Add-Content -Path "setup_cxfreeze.py" -Value " executables=executables,"
Add-Content -Path "setup_cxfreeze.py" -Value ")"
- name: Build Standard Version (ZIP)
shell: powershell
run: |
.\venv\Scripts\Activate.ps1
$version = "$env:VERSION"
Write-Host "Building Standard Version v$version ..."
# Clean previous build artifacts
if (Test-Path "build\exe.*") { Remove-Item "build\exe.*" -Recurse -Force }
if (Test-Path "build\bdist.*") { Remove-Item "build\bdist.*" -Recurse -Force }
if (Test-Path "dist") { Remove-Item "dist" -Recurse -Force }
# Build executable using setup script with extra optimization
python -OO setup_cxfreeze.py build_exe --build-exe "dist\YTSage"
- name: Trim unnecessary files from Standard build
shell: powershell
run: |
$distDir = "dist\YTSage"
$libDir = "$distDir\lib"
if (Test-Path $distDir) {
# Remove screenshots
if (Test-Path "$libDir\assets\branding\screenshots") {
Remove-Item "$libDir\assets\branding\screenshots" -Recurse -Force
Write-Host "Removed screenshots folder"
}
# Remove debug PDB files
Get-ChildItem -Path $distDir -Recurse -Filter "*.pdb" | Remove-Item -Force
Write-Host "Removed PDB debug files"
# Remove unused Qt translations (entire folder)
if (Test-Path "$libDir\PySide6\translations") {
Remove-Item "$libDir\PySide6\translations" -Recurse -Force
Write-Host "Removed Qt translations"
}
# Remove unused Qt plugins (based on your excludes; adjust if needed)
# Note: qpdf and qsvg plugins are removed specifically
$unusedPlugins = @("designer", "pdf", "svg", "sql", "help", "qml", "quick", "webengine", "bluetooth", "opengl", "printsupport", "test", "xml")
foreach ($plugin in $unusedPlugins) {
if (Test-Path "$libDir\PySide6\plugins\$plugin") {
Remove-Item "$libDir\PySide6\plugins\$plugin" -Recurse -Force
Write-Host "Removed unused plugin folder: $plugin"
}
}
# Explicitly remove virtualkeyboard input context if it exists
if (Test-Path "$libDir\PySide6\plugins\platforminputcontexts") {
Remove-Item "$libDir\PySide6\plugins\platforminputcontexts" -Recurse -Force
Write-Host "Removed plugin: platforminputcontexts"
}
# Explicitly remove specific image formats
foreach ($fmt in @("qpdf.dll", "qsvg.dll")) {
if (Test-Path "$libDir\PySide6\plugins\imageformats\$fmt") {
Remove-Item "$libDir\PySide6\plugins\imageformats\$fmt" -Force
Write-Host "Removed plugin: $fmt"
}
}
# Explicitly remove specific icon engines
if (Test-Path "$libDir\PySide6\plugins\iconengines\qsvgicon.dll") {
Remove-Item "$libDir\PySide6\plugins\iconengines\qsvgicon.dll" -Force
Write-Host "Removed plugin: qsvgicon.dll"
}
# Remove any duplicate or unnecessary DLLs (e.g., Qt6Qml, Qt6Quick, Qt6OpenGL from root lib)
$bloatDlls = @("Qt6Web*", "Qt6Pdf*", "Qt6Qml*", "Qt6Quick*", "Qt6VirtualKeyboard*", "Qt6OpenGL*")
foreach ($pattern in $bloatDlls) {
# Check in root lib
Get-ChildItem -Path $libDir -Filter $pattern -ErrorAction SilentlyContinue | Remove-Item -Force
# Check in PySide6 folder
if (Test-Path "$libDir\PySide6") {
Get-ChildItem -Path "$libDir\PySide6" -Filter $pattern -ErrorAction SilentlyContinue | Remove-Item -Force
}
}
Write-Host "Removed unnecessary Qt DLLs"
# Remove build tools and unused python modules
$uselessFolders = @("setuptools", "wheel", "pkg_resources", "_distutils_hack", "curses", "_pyrepl")
foreach ($folder in $uselessFolders) {
if (Test-Path "$libDir\$folder") {
Remove-Item "$libDir\$folder" -Recurse -Force
Write-Host "Removed unused module: $folder"
}
}
}
- name: Setup FFmpeg for bundle
shell: powershell
run: |
Write-Host "Setting up FFmpeg for bundled version..."
# Create ffmpeg directory
New-Item -ItemType Directory -Path "ffmpeg-temp" -Force
# Download FFmpeg
$ffmpegUrl = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip"
Write-Host "Downloading FFmpeg from: $ffmpegUrl"
Invoke-WebRequest -Uri $ffmpegUrl -OutFile "ffmpeg-temp\ffmpeg.zip"
# Extract FFmpeg
Expand-Archive -Path "ffmpeg-temp\ffmpeg.zip" -DestinationPath "ffmpeg-temp" -Force
# Find the extracted directory (GyanD ffmpeg has specific naming)
$ffmpegDir = Get-ChildItem -Path "ffmpeg-temp" -Directory | Where-Object { $_.Name -like "ffmpeg-*" } | Select-Object -First 1
if ($ffmpegDir) {
# Set environment variable for the build
$ffmpegBinPath = Join-Path $ffmpegDir.FullName "bin"
Write-Host "FFmpeg binaries found at: $ffmpegBinPath"
echo "FFMPEG_PATH=$ffmpegBinPath" >> $env:GITHUB_ENV
# Verify files exist (only ffmpeg and ffprobe are needed)
$ffmpegExe = Join-Path $ffmpegBinPath "ffmpeg.exe"
$ffprobeExe = Join-Path $ffmpegBinPath "ffprobe.exe"
if (Test-Path $ffmpegExe) {
Write-Host "[OK] ffmpeg.exe found"
} else {
Write-Host "[ERROR] ffmpeg.exe missing"
}
if (Test-Path $ffprobeExe) {
Write-Host "[OK] ffprobe.exe found"
} else {
Write-Host "[ERROR] ffprobe.exe missing"
}
} else {
Write-Host "Error: Could not find FFmpeg directory after extraction"
exit 1
}
- name: Build FFmpeg Version (ZIP)
shell: powershell
run: |
.\venv\Scripts\Activate.ps1
$version = "$env:VERSION"
Write-Host "Building FFmpeg Version v$version ..."
# Clean previous build artifacts
if (Test-Path "build\exe.*") { Remove-Item "build\exe.*" -Recurse -Force }
if (Test-Path "build\bdist.*") { Remove-Item "build\bdist.*" -Recurse -Force }
# Create modified setup script for FFmpeg version
(Get-Content "setup_cxfreeze.py") -replace 'YTSage-v\{version\}\.exe', 'YTSage-v{version}-ffmpeg.exe' | Set-Content "setup_cxfreeze_ffmpeg.py"
# Build executable with FFmpeg using setup script
python -OO setup_cxfreeze_ffmpeg.py build_exe --build-exe "dist\YTSage-FFmpeg"
# Copy FFmpeg binaries into dist folder post-build (more reliable than CLI include)
# Note: ffplay.exe is excluded as it's not needed by the application
if ($env:FFMPEG_PATH) {
$destDir = "dist\YTSage-FFmpeg"
$ffmpegExe = Join-Path $env:FFMPEG_PATH "ffmpeg.exe"
$ffprobeExe = Join-Path $env:FFMPEG_PATH "ffprobe.exe"
foreach ($file in @($ffmpegExe, $ffprobeExe)) {
if (Test-Path $file) {
$name = Split-Path $file -Leaf
Copy-Item $file -Destination (Join-Path $destDir $name) -Force
Write-Host "Copied $name into $destDir"
} else {
Write-Host "Warning: FFmpeg binary not found: $file"
}
}
}
- name: Trim unnecessary files from FFmpeg build
shell: powershell
run: |
$distDir = "dist\YTSage-FFmpeg"
$libDir = "$distDir\lib"
if (Test-Path $distDir) {
# Remove screenshots
if (Test-Path "$libDir\assets\branding\screenshots") {
Remove-Item "$libDir\assets\branding\screenshots" -Recurse -Force
Write-Host "Removed screenshots folder"
}
# Remove debug PDB files
Get-ChildItem -Path $distDir -Recurse -Filter "*.pdb" | Remove-Item -Force
Write-Host "Removed PDB debug files"
# Remove unused Qt translations (entire folder)
if (Test-Path "$libDir\PySide6\translations") {
Remove-Item "$libDir\PySide6\translations" -Recurse -Force
Write-Host "Removed Qt translations"
}
# Remove unused Qt plugins (based on your excludes; adjust if needed)
# Note: qpdf and qsvg plugins are removed specifically
$unusedPlugins = @("designer", "pdf", "svg", "sql", "help", "qml", "quick", "webengine", "bluetooth", "opengl", "printsupport", "test", "xml")
foreach ($plugin in $unusedPlugins) {
if (Test-Path "$libDir\PySide6\plugins\$plugin") {
Remove-Item "$libDir\PySide6\plugins\$plugin" -Recurse -Force
Write-Host "Removed unused plugin folder: $plugin"
}
}
# Explicitly remove virtualkeyboard input context if it exists
if (Test-Path "$libDir\PySide6\plugins\platforminputcontexts") {
Remove-Item "$libDir\PySide6\plugins\platforminputcontexts" -Recurse -Force
Write-Host "Removed plugin: platforminputcontexts"
}
# Explicitly remove specific image formats
foreach ($fmt in @("qpdf.dll", "qsvg.dll")) {
if (Test-Path "$libDir\PySide6\plugins\imageformats\$fmt") {
Remove-Item "$libDir\PySide6\plugins\imageformats\$fmt" -Force
Write-Host "Removed plugin: $fmt"
}
}
# Explicitly remove specific icon engines
if (Test-Path "$libDir\PySide6\plugins\iconengines\qsvgicon.dll") {
Remove-Item "$libDir\PySide6\plugins\iconengines\qsvgicon.dll" -Force
Write-Host "Removed plugin: qsvgicon.dll"
}
# Remove any duplicate or unnecessary DLLs (e.g., Qt6Qml, Qt6Quick, Qt6OpenGL from root lib)
$bloatDlls = @("Qt6Web*", "Qt6Pdf*", "Qt6Qml*", "Qt6Quick*", "Qt6VirtualKeyboard*", "Qt6OpenGL*")
foreach ($pattern in $bloatDlls) {
# Check in root lib
Get-ChildItem -Path $libDir -Filter $pattern -ErrorAction SilentlyContinue | Remove-Item -Force
# Check in PySide6 folder
if (Test-Path "$libDir\PySide6") {
Get-ChildItem -Path "$libDir\PySide6" -Filter $pattern -ErrorAction SilentlyContinue | Remove-Item -Force
}
}
Write-Host "Removed unnecessary Qt DLLs"
# Remove build tools and unused python modules
$uselessFolders = @("setuptools", "wheel", "pkg_resources", "_distutils_hack", "curses", "_pyrepl")
foreach ($folder in $uselessFolders) {
if (Test-Path "$libDir\$folder") {
Remove-Item "$libDir\$folder" -Recurse -Force
Write-Host "Removed unused module: $folder"
}
}
}
- name: Package and prepare release artifacts (ZIPs)
shell: powershell
run: |
$version = "${{ steps.get_version.outputs.VERSION }}"
Write-Host "Preparing ZIP artifacts for version: $version"
New-Item -ItemType Directory -Path "artifacts" -Force
# List dist directories
Write-Host "Files in dist directory:"
if (Test-Path "dist") {
Get-ChildItem -Path "dist" -Recurse | ForEach-Object { Write-Host " $($_.FullName)" }
} else {
Write-Host " No dist directory found!"
exit 1
}
# Create Standard ZIP
$standardDist = Join-Path (Get-Location) "dist\YTSage"
if (Test-Path $standardDist) {
$stdZip = "artifacts\YTSage-v$version-portable.zip"
if (Test-Path $stdZip) { Remove-Item $stdZip -Force }
Compress-Archive -Path "$standardDist\*" -DestinationPath $stdZip -CompressionLevel Optimal
Write-Host "Created: $(Resolve-Path $stdZip)"
} else {
Write-Host "Warning: Standard dist folder not found at $standardDist"
}
# Create FFmpeg ZIP
$ffmpegDist = Join-Path (Get-Location) "dist\YTSage-FFmpeg"
if (Test-Path $ffmpegDist) {
$ffZip = "artifacts\YTSage-v$version-ffmpeg-portable.zip"
if (Test-Path $ffZip) { Remove-Item $ffZip -Force }
Compress-Archive -Path "$ffmpegDist\*" -DestinationPath $ffZip -CompressionLevel Optimal
Write-Host "Created: $(Resolve-Path $ffZip)"
} else {
Write-Host "Warning: FFmpeg dist folder not found at $ffmpegDist"
}
# List all artifacts
Write-Host "Final artifacts:"
if (Test-Path "artifacts") {
$artifactFiles = Get-ChildItem artifacts
if ($artifactFiles) {
$artifactFiles | ForEach-Object { Write-Host " $($_.Name)" }
} else {
Write-Host " No artifacts created!"
}
} else {
Write-Host " Artifacts directory not found!"
}
- name: Create draft release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.get_version.outputs.VERSION }}
name: YTSage v${{ steps.get_version.outputs.VERSION }}
draft: true
prerelease: false
body: |
# YTSage v${{ steps.get_version.outputs.VERSION }}
**Release Date**: ${{ github.event.head_commit.timestamp }}
files: |
artifacts/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}