# CirnoBuild.psm1 # Common functions for building and publishing Cirno No Reason # Configuration $script:gitVersionPath = "dotnet-gitversion.exe" $script:configFile = "export_presets.cfg" $script:fileName = "Cirno_No_Reason" # Platform configurations $script:platforms = @{ "win" = @{ ExportName = "Windows Desktop" Extension = ".exe" OutputSubDir = "win" } "linux" = @{ ExportName = "Linux" Extension = ".x86_64" OutputSubDir = "linux" } } function Test-Prerequisites { <# .SYNOPSIS Validates that required tools are available .PARAMETER GodotPath Path to Godot executable .PARAMETER SkipButler Skip Butler validation (not needed for build-only operations) #> param( [string]$GodotPath, [string]$ButlerPath, [switch]$SkipButler ) $errors = @() if ([string]::IsNullOrWhiteSpace($GodotPath)) { $errors += "GODOT environment variable is not set. Please set it to your Godot executable path." $errors += "Example: `$env:GODOT = 'C:\Godot\Godot_v4.x.exe'" } elseif (-not (Test-Path $GodotPath)) { # Try to resolve from PATH if it's just an executable name $resolvedPath = Get-Command $GodotPath -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source if ($resolvedPath) { Write-Host " Resolved Godot path: $resolvedPath" -ForegroundColor Gray # Update the caller's variable return @{ Valid = $true ResolvedGodotPath = $resolvedPath } } else { $errors += "Godot executable not found at: $GodotPath" $errors += "Please update the GODOT environment variable or add Godot to PATH." } } if (-not (Test-Path $script:gitVersionPath) -and -not (Get-Command $script:gitVersionPath -ErrorAction SilentlyContinue)) { $errors += "GitVersion not found. Please install dotnet-gitversion or add it to PATH." } if (-not $SkipButler) { if ([string]::IsNullOrWhiteSpace($ButlerPath)) { $errors += "Butler path is not configured." } elseif (-not (Test-Path $ButlerPath)) { $errors += "Butler executable not found at: $ButlerPath" } } if ($errors.Count -gt 0) { Write-Host "" Write-Host "ERROR: Missing Prerequisites" -ForegroundColor Red Write-Host "=====================================" -ForegroundColor Red foreach ($error in $errors) { Write-Host " $error" -ForegroundColor Yellow } Write-Host "" return @{ Valid = $false } } return @{ Valid = $true ResolvedGodotPath = $GodotPath } } function Get-VersionInfo { <# .SYNOPSIS Gets version information from GitVersion #> Write-Host "Getting version information..." -ForegroundColor Cyan $gitVersionOutput = & $script:gitVersionPath | ConvertFrom-Json $versionInfo = @{ AssemblySemFileVer = $gitVersionOutput.AssemblySemFileVer AssemblySemVer = $gitVersionOutput.AssemblySemVer FullSemVer = $gitVersionOutput.FullSemVer PreReleaseLabel = $gitVersionOutput.PreReleaseLabelWithDash } [System.Environment]::SetEnvironmentVariable("GIT_ASSEMBLY_SEM_FILE_VER", $versionInfo.AssemblySemFileVer, [System.EnvironmentVariableTarget]::Process) [System.Environment]::SetEnvironmentVariable("GIT_ASSEMBLY_SEM_VER", $versionInfo.AssemblySemVer, [System.EnvironmentVariableTarget]::Process) Write-Host " Version: $($versionInfo.FullSemVer)" -ForegroundColor Green Write-Host " File Version: $($versionInfo.AssemblySemFileVer)" -ForegroundColor Green return $versionInfo } function Initialize-BuildDirectories { <# .SYNOPSIS Creates and clears build directories for specified platforms #> param( [string[]]$PlatformsToBuild, [string]$BuildDir ) Write-Host "Initializing build directories..." -ForegroundColor Cyan # Ensure the root build directory exists but DO NOT delete its contents (preserve logs and zips) if (-not (Test-Path $BuildDir)) { New-Item -ItemType Directory -Path $BuildDir | Out-Null } foreach ($platform in $PlatformsToBuild) { $platformDir = Join-Path $BuildDir $script:platforms[$platform].OutputSubDir if (Test-Path $platformDir) { # Clear only the platform directory contents Remove-Item "$platformDir\*" -Recurse -Force -ErrorAction SilentlyContinue } else { New-Item -ItemType Directory -Path $platformDir -Force | Out-Null } Write-Host " Prepared: $platformDir" -ForegroundColor Gray } } function Update-ExportConfig { <# .SYNOPSIS Updates export_presets.cfg with version information #> param($VersionInfo) Write-Host "Updating export configuration..." -ForegroundColor Cyan if (Test-Path $script:configFile) { (Get-Content $script:configFile) ` -replace 'application/file_version="[^"]*"', "application/file_version=`"$($VersionInfo.AssemblySemFileVer)`"" ` -replace 'application/product_version="[^"]*"', "application/product_version=`"$($VersionInfo.AssemblySemVer)`"" ` | Set-Content $script:configFile Write-Host " Export config updated successfully" -ForegroundColor Green } else { Write-Host " WARNING: $script:configFile not found!" -ForegroundColor Red } } function Wait-ForExportCompletion { <# .SYNOPSIS Monitors export output file until it's complete #> param( [string]$OutputPath, [int]$TimeoutSeconds = 300 ) $startTime = Get-Date $checkInterval = 2 $lastSize = -1 $stableCount = 0 $requiredStableChecks = 3 Write-Host " Waiting for export to complete: $OutputPath" -ForegroundColor Gray while (((Get-Date) - $startTime).TotalSeconds -lt $TimeoutSeconds) { Start-Sleep -Seconds $checkInterval if (Test-Path $OutputPath) { $currentSize = (Get-Item $OutputPath).Length if ($currentSize -eq $lastSize -and $currentSize -gt 0) { $stableCount++ if ($stableCount -ge $requiredStableChecks) { Write-Host " Export completed: $OutputPath ($currentSize bytes)" -ForegroundColor Green return $true } } else { $stableCount = 0 } $lastSize = $currentSize } } Write-Host " WARNING: Export timeout reached for $OutputPath" -ForegroundColor Yellow return (Test-Path $OutputPath) } function Create-PlatformZip { <# .SYNOPSIS Creates a zip archive for a platform build .PARAMETER Platform Platform identifier (win, linux) .PARAMETER BuildDir Build directory path .PARAMETER VersionInfo Optional version information hashtable (if provided, includes version in filename) .PARAMETER Force Force recreate zip even if it exists .RETURNS Path to the created zip file, or $null if failed #> param( [string]$Platform, [string]$BuildDir, [hashtable]$VersionInfo = $null, [switch]$Force ) $platformConfig = $script:platforms[$Platform] $platformDir = Join-Path $BuildDir $platformConfig.OutputSubDir # Determine zip filename (with or without version) if ($VersionInfo) { $zipFileName = "$script:fileName.$($VersionInfo.FullSemVer).$Platform.zip" } else { $zipFileName = "$script:fileName.$Platform.zip" } $zipFilePath = Join-Path $BuildDir $zipFileName # Check if zip already exists and force not specified if ((Test-Path $zipFilePath) -and -not $Force) { Write-Host " Using existing zip: $zipFilePath" -ForegroundColor Gray return $zipFilePath } # Validate source files exist $files = Get-ChildItem -Path $platformDir -Recurse -File -ErrorAction SilentlyContinue if (-not $files) { Write-Host " ERROR: No files found in $platformDir" -ForegroundColor Red return $null } # Create zip Write-Host " Creating zip archive: $zipFileName" -ForegroundColor Cyan try { # Do not explicitly delete existing zip to avoid losing historical artifacts; Compress-Archive -Force will overwrite Compress-Archive -Path "$platformDir\*" -DestinationPath $zipFilePath -Force Write-Host " Zip created: $zipFilePath" -ForegroundColor Green return $zipFilePath } catch { Write-Host " ERROR: Failed to create zip: $($_.Exception.Message)" -ForegroundColor Red return $null } } function Export-GodotPlatform { <# .SYNOPSIS Exports a single platform build using Godot - simple and direct #> param( [string]$Platform, [string]$GodotPath, [string]$BuildDir, [hashtable]$VersionInfo = $null ) $platformConfig = $script:platforms[$Platform] $outputPath = Join-Path $BuildDir "$($platformConfig.OutputSubDir)\$script:fileName$($platformConfig.Extension)" Write-Host "Exporting $Platform build..." -ForegroundColor Cyan Write-Host " Export Name: $($platformConfig.ExportName)" -ForegroundColor Gray Write-Host " Output: $outputPath" -ForegroundColor Gray Write-Host "" if (Test-Path $outputPath) { Remove-Item $outputPath -Force } $timeStamp = (Get-Date).ToString('yyyyMMdd-HHmmss') # Put log in parent build folder, not in platform subfolder (avoid including in zip) $logPath = Join-Path $BuildDir "godot-export-$Platform-$timeStamp.log" $exportName = $platformConfig.ExportName Write-Host "--- Godot Export Output ---" -ForegroundColor DarkGray Write-Host "Logging to: $logPath" -ForegroundColor DarkGray Write-Host "" # Save current encoding and set to UTF8 to handle Godot's Unicode output $previousEncoding = [Console]::OutputEncoding try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 # Run Godot directly - output streams to console and gets logged & $GodotPath --headless --export-release $exportName $outputPath 2>&1 | Tee-Object -FilePath $logPath | ForEach-Object { Write-Host $_ } } catch { Write-Host " ERROR: Failed to execute Godot: $($_.Exception.Message)" -ForegroundColor Red return $false } finally { # Restore previous encoding [Console]::OutputEncoding = $previousEncoding } Write-Host "" Write-Host "--- Export command completed ---" -ForegroundColor DarkGray Write-Host "" # Wait for the file to be created and stabilize $success = Wait-ForExportCompletion -OutputPath $outputPath -TimeoutSeconds 30 if ($success) { Write-Host " $Platform export completed successfully!" -ForegroundColor Green # Create zip file after successful export (with version if provided) $zipPath = Create-PlatformZip -Platform $Platform -BuildDir $BuildDir -VersionInfo $VersionInfo -Force if (-not $zipPath) { Write-Host " WARNING: Export succeeded but zip creation failed" -ForegroundColor Yellow } } else { Write-Host " ERROR: $Platform export may have failed!" -ForegroundColor Red Write-Host " Check log: $logPath" -ForegroundColor Yellow } return $success } function Publish-PlatformBuild { <# .SYNOPSIS Publishes a platform build to itch.io #> param( [string]$Platform, [hashtable]$VersionInfo, [string]$BuildDir, [string]$ReleaseDir, [string]$ButlerPath ) $platformConfig = $script:platforms[$Platform] $butlerTarget = "maddoscientisto/cirno-no-reason:$Platform$($VersionInfo.PreReleaseLabel)" Write-Host "Publishing $Platform build..." -ForegroundColor Cyan # Ensure zip exists with version number (create if missing) $zipFilePath = Create-PlatformZip -Platform $Platform -BuildDir $BuildDir -VersionInfo $VersionInfo if (-not $zipFilePath) { Write-Host " ERROR: Failed to get or create zip file" -ForegroundColor Red return $false } # Ensure release directory exists if (!(Test-Path $ReleaseDir)) { New-Item -ItemType Directory -Path $ReleaseDir | Out-Null } # Copy versioned zip to release directory $zipFileName = Split-Path $zipFilePath -Leaf $releaseZipPath = Join-Path $ReleaseDir $zipFileName Copy-Item -Path $zipFilePath -Destination $releaseZipPath -Force Write-Host " Copied to release: $zipFileName" -ForegroundColor Gray # Push to itch.io Write-Host " Pushing to itch.io: $butlerTarget" -ForegroundColor Gray & $ButlerPath push $releaseZipPath $butlerTarget --userversion $VersionInfo.FullSemVer if ($LASTEXITCODE -eq 0) { Write-Host " $Platform published successfully!" -ForegroundColor Green return $true } else { Write-Host " ERROR: Failed to publish $Platform build" -ForegroundColor Red return $false } } function Get-PlatformConfigurations { <# .SYNOPSIS Returns platform configuration hashtable #> return $script:platforms } # Export functions Export-ModuleMember -Function Test-Prerequisites, Get-VersionInfo, Initialize-BuildDirectories, ` Update-ExportConfig, Wait-ForExportCompletion, Create-PlatformZip, Export-GodotPlatform, ` Publish-PlatformBuild, Get-PlatformConfigurations