Add windows-build-universal script
This commit is contained in:
@@ -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")
|
||||||
Reference in New Issue
Block a user