From 0b3a8e9fa699cd680312df5bba5e074b7c88fac2 Mon Sep 17 00:00:00 2001 From: Mohamed <110548351+oop7@users.noreply.github.com> Date: Wed, 27 Aug 2025 01:06:25 +0300 Subject: [PATCH] Add windows-build-universal script --- build/windows/windows-build-universal.ps1 | 483 ++++++++++++++++++++++ 1 file changed, 483 insertions(+) create mode 100644 build/windows/windows-build-universal.ps1 diff --git a/build/windows/windows-build-universal.ps1 b/build/windows/windows-build-universal.ps1 new file mode 100644 index 0000000..1b3297c --- /dev/null +++ b/build/windows/windows-build-universal.ps1 @@ -0,0 +1,483 @@ +#Requires -Version 5.1 + +<# +.SYNOPSIS + YTSage Universal Build Script - Creates MSI Installers and ZIP Distributions +.DESCRIPTION + This PowerShell script builds YTSage using cx_Freeze and creates: + - MSI installer packages + - ZIP file distributions (portable) + - Both standard and FFmpeg versions +.NOTES + Version: Auto-detected from setup.py + Location: build/windows/windows-build-universal.ps1 + Usage: Run from project root directory (build-env/) + + IMPORTANT: This script must be executed from the project root directory! + Example: .\build\windows\windows-build-universal.ps1 -Verbose +#> + +param( + [switch]$SkipVenv, + [switch]$Verbose, + [switch]$FFmpeg, + [switch]$MSIOnly, + [switch]$ZIPOnly, + [string]$OutputDir = "releases", + [string]$FFmpegPath = "$env:LOCALAPPDATA\ffmpeg\ffmpeg-7.1.1-full_build\bin" +) + +# Set error handling +$ErrorActionPreference = "Stop" + +# Script configuration - Auto-detect version from setup.py +$APP_NAME = "YTSage" +$RELEASE_DATE = Get-Date -Format "yyyy-MM-dd" + +# Auto-detect version from setup.py +function Get-VersionFromSetup { + try { + $setupContent = Get-Content "build\windows\setup.py" -Raw + if ($setupContent -match 'version\s*=\s*["\']([^"\']+)["\']') { + $version = $matches[1] + Write-Host "Detected version: $version" -ForegroundColor Gray + return $version + } else { + Write-Warning-Custom "Could not extract version from setup.py, using 1.0.0" + return "1.0.0" + } + } catch { + Write-Warning-Custom "Could not read setup.py, using 1.0.0" + return "1.0.0" + } +} + +$SCRIPT_VERSION = Get-VersionFromSetup + +# Determine build type +$BUILD_TYPE = if ($FFmpeg) { "FFmpeg" } else { "Standard" } +$SETUP_FILE = if ($FFmpeg) { "build\windows\setup-ffmpeg.py" } else { "build\windows\setup.py" } +$OUTPUT_NAME_BASE = if ($FFmpeg) { "$APP_NAME-v$SCRIPT_VERSION-ffmpeg" } else { "$APP_NAME-v$SCRIPT_VERSION" } + +# Banner +Write-Host "================================================" -ForegroundColor Cyan +Write-Host "YTSage Universal Build Script v$SCRIPT_VERSION" -ForegroundColor Cyan +Write-Host "Build Type: $BUILD_TYPE" -ForegroundColor Yellow +Write-Host "================================================" -ForegroundColor Cyan +Write-Host "" +Write-Host "This script will create:" -ForegroundColor Yellow +if (-not $ZIPOnly) { + Write-Host "- MSI installer package" -ForegroundColor White +} +if (-not $MSIOnly) { + Write-Host "- ZIP portable distribution" -ForegroundColor White +} +Write-Host "- Professional packaging with metadata" -ForegroundColor White +Write-Host "" + +function Write-Step { + param($Message) + Write-Host "[STEP] $Message" -ForegroundColor Green +} + +function Write-Error-Custom { + param($Message) + Write-Host "[ERROR] $Message" -ForegroundColor Red +} + +function Write-Warning-Custom { + param($Message) + Write-Host "[WARNING] $Message" -ForegroundColor Yellow +} + +function Test-Command { + param($Command) + try { + & $Command --version | Out-Null + return $true + } + catch { + return $false + } +} + +function Test-FFmpegBinaries { + if (-not $FFmpeg) { return $null } + + Write-Step "Checking FFmpeg binaries..." + + $ffmpegExe = Join-Path $FFmpegPath "ffmpeg.exe" + $ffprobeExe = Join-Path $FFmpegPath "ffprobe.exe" + $ffplayExe = Join-Path $FFmpegPath "ffplay.exe" + + $missingFiles = @() + + if (-not (Test-Path $ffmpegExe)) { $missingFiles += "ffmpeg.exe" } + if (-not (Test-Path $ffprobeExe)) { $missingFiles += "ffprobe.exe" } + if (-not (Test-Path $ffplayExe)) { $missingFiles += "ffplay.exe" } + + if ($missingFiles.Count -gt 0) { + Write-Error-Custom "Missing FFmpeg binaries: $($missingFiles -join ', ')" + Write-Host "Expected FFmpeg path: $FFmpegPath" -ForegroundColor Yellow + throw "FFmpeg binaries not found" + } + + Write-Host "✓ Found all FFmpeg binaries" -ForegroundColor Green + return @{ + ffmpeg = $ffmpegExe + ffprobe = $ffprobeExe + ffplay = $ffplayExe + } +} + +function New-VirtualEnvironment { + Write-Step "Creating virtual environment..." + try { + python -m venv venv + Write-Host "Virtual environment created successfully." -ForegroundColor Green + } + catch { + Write-Error-Custom "Failed to create virtual environment: $($_.Exception.Message)" + throw + } +} + +function Start-VirtualEnvironment { + Write-Step "Activating virtual environment..." + + $venvScript = ".\venv\Scripts\Activate.ps1" + if (-not (Test-Path $venvScript)) { + Write-Error-Custom "Virtual environment activation script not found at: $venvScript" + throw "Virtual environment not properly created" + } + + try { + & $venvScript + Write-Host "Virtual environment activated successfully." -ForegroundColor Green + } + catch { + Write-Error-Custom "Failed to activate virtual environment: $($_.Exception.Message)" + throw + } +} + +function Install-Dependencies { + Write-Step "Installing/Upgrading required packages..." + + try { + Write-Host "Upgrading pip..." -ForegroundColor White + python -m pip install --upgrade pip | Out-Null + + Write-Host "Installing requirements..." -ForegroundColor White + pip install --no-cache-dir -r requirements.txt | Out-Null + + Write-Host "Installing cx_Freeze..." -ForegroundColor White + pip install --no-cache-dir cx_Freeze | Out-Null + + Write-Host "Installing yt-dlp (temporary for build)..." -ForegroundColor White + pip install --no-cache-dir yt-dlp | Out-Null + + Write-Host "All packages installed successfully." -ForegroundColor Green + } + catch { + Write-Error-Custom "Failed to install required packages: $($_.Exception.Message)" + throw + } +} + +function Remove-BuildArtifacts { + Write-Step "Cleaning previous build artifacts..." + + $itemsToRemove = @("build", "dist", $OutputDir) + + foreach ($item in $itemsToRemove) { + if (Test-Path $item) { + Remove-Item $item -Recurse -Force + Write-Host "Removed: $item" -ForegroundColor Gray + } + } + + Write-Host "Build artifacts cleaned." -ForegroundColor Green +} + +function Build-Executable { + param($FFmpegBinaries) + + Write-Step "Building executable with cx_Freeze..." + + try { + if ($FFmpegBinaries) { + $env:FFMPEG_PATH = $FFmpegBinaries.ffmpeg | Split-Path + } + + # Build the executable + python $SETUP_FILE build + + if ($LASTEXITCODE -ne 0) { + throw "cx_Freeze build failed with exit code $LASTEXITCODE" + } + + Write-Host "Executable built successfully." -ForegroundColor Green + } + catch { + Write-Error-Custom "Failed to build executable: $($_.Exception.Message)" + throw + } +} + +function Build-MSI { + Write-Step "Creating MSI installer..." + + try { + # Build MSI package + python $SETUP_FILE bdist_msi + + if ($LASTEXITCODE -ne 0) { + throw "MSI build failed with exit code $LASTEXITCODE" + } + + Write-Host "MSI installer created successfully." -ForegroundColor Green + } + catch { + Write-Error-Custom "Failed to build MSI: $($_.Exception.Message)" + throw + } +} + +function New-ZIPDistribution { + Write-Step "Creating ZIP distribution..." + + try { + # Ensure output directory exists + if (-not (Test-Path $OutputDir)) { + New-Item -ItemType Directory -Path $OutputDir | Out-Null + } + + # Find the built executable directory + $executableDir = if ($FFmpeg) { "dist\YTSage-FFmpeg" } else { "dist\YTSage" } + + if (-not (Test-Path $executableDir)) { + throw "Executable directory not found: $executableDir" + } + + # Create ZIP file + $zipPath = Join-Path $OutputDir "$OUTPUT_NAME_BASE.zip" + Compress-Archive -Path "$executableDir\*" -DestinationPath $zipPath -Force + + Write-Host "ZIP distribution created: $zipPath" -ForegroundColor Green + + # Get ZIP file size + $zipSize = (Get-Item $zipPath).Length + $zipSizeMB = [math]::Round($zipSize / 1MB, 2) + Write-Host "ZIP size: $zipSizeMB MB" -ForegroundColor Gray + + } + catch { + Write-Error-Custom "Failed to create ZIP distribution: $($_.Exception.Message)" + throw + } +} + +function Move-MSIFiles { + Write-Step "Organizing MSI files..." + + try { + # Ensure output directory exists + if (-not (Test-Path $OutputDir)) { + New-Item -ItemType Directory -Path $OutputDir | Out-Null + } + + # Find and move MSI files + $msiFiles = Get-ChildItem -Path "dist" -Filter "*.msi" -Recurse + + foreach ($msiFile in $msiFiles) { + $newName = "$OUTPUT_NAME_BASE.msi" + $destination = Join-Path $OutputDir $newName + + Move-Item $msiFile.FullName $destination -Force + Write-Host "MSI installer moved to: $destination" -ForegroundColor Green + + # Get MSI file size + $msiSize = (Get-Item $destination).Length + $msiSizeMB = [math]::Round($msiSize / 1MB, 2) + Write-Host "MSI size: $msiSizeMB MB" -ForegroundColor Gray + } + } + catch { + Write-Error-Custom "Failed to organize MSI files: $($_.Exception.Message)" + throw + } +} + +function New-ReleaseNotes { + Write-Step "Creating release notes..." + + $releaseNotes = @" +# YTSage v$SCRIPT_VERSION Release + +**Release Date:** $RELEASE_DATE +**Build Type:** $BUILD_TYPE + +## Installation Options + +### MSI Installer (Recommended) +- **File:** $OUTPUT_NAME_BASE.msi +- **Installation:** Double-click to install system-wide +- **Updates:** Automatic update notifications +- **Uninstall:** Via Windows Programs & Features + +### Portable ZIP +- **File:** $OUTPUT_NAME_BASE.zip +- **Installation:** Extract and run - no installation required +- **Updates:** Manual download and replacement +- **Uninstall:** Simply delete the folder + +## System Requirements +- Windows 10/11 (64-bit) +- .NET Framework 4.7.2 or later +- Internet connection for yt-dlp updates + +## Support +For issues or questions, please visit the project repository. + +--- +*Built with cx_Freeze v$(pip show cx_Freeze | Select-String "Version" | ForEach-Object { ($_ -split ":")[1].Trim() })* +"@ + + try { + $releaseNotesPath = Join-Path $OutputDir "RELEASE_NOTES.md" + $releaseNotes | Out-File -FilePath $releaseNotesPath -Encoding UTF8 + Write-Host "Release notes created: $releaseNotesPath" -ForegroundColor Green + } + catch { + Write-Warning-Custom "Failed to create release notes: $($_.Exception.Message)" + } +} + +function Remove-PostBuildArtifacts { + Write-Step "Cleaning up post-build artifacts..." + + try { + if (Test-Path "build") { + Remove-Item "build" -Recurse -Force + Write-Host "Removed build folder." -ForegroundColor Gray + } + + Write-Host "Post-build cleanup completed." -ForegroundColor Green + } + catch { + Write-Warning-Custom "Some cleanup operations failed: $($_.Exception.Message)" + } +} + +function Stop-VirtualEnvironment { + try { + deactivate + Write-Host "Virtual environment deactivated." -ForegroundColor Green + } + catch { + Write-Warning-Custom "Failed to deactivate virtual environment (this might be normal)" + } +} + +function Show-BuildSummary { + Write-Host "" + Write-Host "================================================" -ForegroundColor Cyan + Write-Host "Build completed successfully!" -ForegroundColor Green + Write-Host "================================================" -ForegroundColor Cyan + Write-Host "" + Write-Host "Build Type: $BUILD_TYPE" -ForegroundColor Yellow + Write-Host "Version: $SCRIPT_VERSION" -ForegroundColor Yellow + Write-Host "Output Directory: $OutputDir" -ForegroundColor Yellow + Write-Host "" + + if (Test-Path $OutputDir) { + Write-Host "Created files:" -ForegroundColor Green + Get-ChildItem $OutputDir | ForEach-Object { + $size = if ($_.PSIsContainer) { "Folder" } else { "$([math]::Round($_.Length / 1MB, 2)) MB" } + Write-Host " ✓ $($_.Name) ($size)" -ForegroundColor White + } + } + + Write-Host "" + Write-Host "Distribution ready for release!" -ForegroundColor Green + Write-Host "" +} + +# Main execution +try { + # Validation + Write-Step "Checking prerequisites..." + + if (-not (Test-Command "python")) { + Write-Error-Custom "Python is not installed or not in PATH" + exit 1 + } + + if (-not (Test-Path $SETUP_FILE)) { + Write-Error-Custom "Setup file not found: $SETUP_FILE" + exit 1 + } + + $pythonVersion = python --version + Write-Host "Found: $pythonVersion" -ForegroundColor Green + + # Check for Python 3.13 MSI limitation + $pythonVersionInfo = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" + $isPython313 = $pythonVersionInfo -eq "3.13" + + if ($isPython313 -and -not $ZIPOnly) { + Write-Warning-Custom "Python 3.13 detected. MSI building is not supported yet by cx_Freeze." + Write-Host "See: https://github.com/marcelotduarte/cx_Freeze/issues/2837" -ForegroundColor Yellow + Write-Host "Switching to ZIP-only mode. Use Python 3.12 for MSI support." -ForegroundColor Yellow + $ZIPOnly = $true + $MSIOnly = $false + } + + # Check FFmpeg if needed + $ffmpegBinaries = Test-FFmpegBinaries + + # Virtual environment + if (-not $SkipVenv) { + if (-not (Test-Path "venv")) { + New-VirtualEnvironment + } else { + Write-Step "Using existing virtual environment..." + } + Start-VirtualEnvironment + } + + # Build process + Install-Dependencies + Remove-BuildArtifacts + Build-Executable -FFmpegBinaries $ffmpegBinaries + + # Create distributions + if (-not $ZIPOnly) { + Build-MSI + Move-MSIFiles + } + + if (-not $MSIOnly) { + New-ZIPDistribution + } + + New-ReleaseNotes + Remove-PostBuildArtifacts + Show-BuildSummary +} +catch { + Write-Error-Custom "Build failed: $($_.Exception.Message)" + if ($Verbose) { + Write-Host $_.Exception.StackTrace -ForegroundColor Red + } + exit 1 +} +finally { + if (-not $SkipVenv) { + Stop-VirtualEnvironment + } +} + +Write-Host "Press any key to continue..." -ForegroundColor Gray +$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")