192 lines
8.8 KiB
PowerShell
192 lines
8.8 KiB
PowerShell
param(
|
||
[switch]$NoVersionBump,
|
||
[switch]$SkipPublish,
|
||
[switch]$SkipGit,
|
||
[string]$TargetVersion = ""
|
||
)
|
||
|
||
$ErrorActionPreference = "Stop"
|
||
$Root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||
Set-Location -LiteralPath $Root
|
||
|
||
function Find-Python {
|
||
foreach ($candidate in @("python", "py")) {
|
||
try {
|
||
& $candidate --version 2>$null | Out-Null
|
||
if ($LASTEXITCODE -eq 0) { return $candidate }
|
||
}
|
||
catch { }
|
||
}
|
||
throw "Python wurde nicht gefunden."
|
||
}
|
||
|
||
function Find-InnoCompiler {
|
||
$candidates = @(
|
||
"$env:LOCALAPPDATA\Programs\Inno Setup 7\ISCC.exe",
|
||
"$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe",
|
||
"$env:ProgramFiles\Inno Setup 7\ISCC.exe",
|
||
"${env:ProgramFiles(x86)}\Inno Setup 7\ISCC.exe",
|
||
"${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe",
|
||
"$env:ProgramFiles\Inno Setup 6\ISCC.exe"
|
||
)
|
||
foreach ($candidate in $candidates) {
|
||
if (Test-Path -LiteralPath $candidate -PathType Leaf) { return $candidate }
|
||
}
|
||
throw "Inno Setup 6 oder 7 wurde nicht gefunden."
|
||
}
|
||
|
||
function Write-Utf8NoBom {
|
||
param([string]$LiteralPath, [string]$Content)
|
||
[System.IO.File]::WriteAllText($LiteralPath, $Content, [System.Text.UTF8Encoding]::new($false))
|
||
}
|
||
|
||
function Get-Sha256Hex {
|
||
param([string]$LiteralPath)
|
||
return (Get-FileHash -LiteralPath $LiteralPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
||
}
|
||
|
||
$VersionPath = Join-Path $Root "version.txt"
|
||
$PyprojectPath = Join-Path $Root "pyproject.toml"
|
||
$ChangelogPath = Join-Path $Root "CHANGELOG.md"
|
||
$originalVersion = [System.IO.File]::ReadAllText($VersionPath, [System.Text.Encoding]::UTF8)
|
||
$originalPyproject = [System.IO.File]::ReadAllText($PyprojectPath, [System.Text.Encoding]::UTF8)
|
||
$currentVersion = $originalVersion.Trim()
|
||
if ($currentVersion -notmatch '^\d+\.\d+\.\d+$') {
|
||
throw "Ungültige Version in version.txt: $currentVersion"
|
||
}
|
||
|
||
$appVersion = $currentVersion
|
||
if (-not [string]::IsNullOrWhiteSpace($TargetVersion)) {
|
||
if ($NoVersionBump) { throw "TargetVersion und NoVersionBump dürfen nicht gemeinsam verwendet werden." }
|
||
if ($TargetVersion -notmatch '^\d+\.\d+\.\d+$') { throw "Ungültige Zielversion: $TargetVersion" }
|
||
if ([version]$TargetVersion -le [version]$currentVersion) {
|
||
throw "Die Zielversion $TargetVersion muss neuer als $currentVersion sein."
|
||
}
|
||
$appVersion = $TargetVersion
|
||
}
|
||
elseif (-not $NoVersionBump) {
|
||
$parts = @($currentVersion.Split('.') | ForEach-Object { [int]$_ })
|
||
$parts[2] += 1
|
||
$appVersion = "$($parts[0]).$($parts[1]).$($parts[2])"
|
||
}
|
||
|
||
$changelog = [System.IO.File]::ReadAllText($ChangelogPath, [System.Text.Encoding]::UTF8)
|
||
$sectionPattern = '(?ms)^##\s+' + [regex]::Escape($appVersion) + '\s+.*?(?=^##\s+\d+\.\d+\.\d+\s+|\z)'
|
||
$sectionMatch = [regex]::Match($changelog, $sectionPattern)
|
||
if (-not $sectionMatch.Success) {
|
||
throw "CHANGELOG.md enthält keinen Abschnitt für Version $appVersion."
|
||
}
|
||
$releaseNotes = "# SyncVito – Änderungsprotokoll`r`n`r`n$($sectionMatch.Value.Trim())`r`n"
|
||
|
||
$versionParts = @($appVersion.Split('.') | ForEach-Object { [int]$_ })
|
||
$fileVersion = "$($versionParts[0]).$($versionParts[1]).$($versionParts[2]).0"
|
||
$python = Find-Python
|
||
$iscc = Find-InnoCompiler
|
||
$sourceCommitted = $false
|
||
|
||
try {
|
||
Write-Utf8NoBom -LiteralPath $VersionPath -Content "$appVersion`n"
|
||
$versionRegex = [regex]::new('(?m)^(version\s*=\s*")[^"]+("\s*)$')
|
||
$updatedPyproject = $versionRegex.Replace($originalPyproject, "`${1}$appVersion`${2}", 1)
|
||
if ($updatedPyproject -eq $originalPyproject -and $appVersion -ne $currentVersion) {
|
||
throw "Die Projektversion in pyproject.toml konnte nicht aktualisiert werden."
|
||
}
|
||
Write-Utf8NoBom -LiteralPath $PyprojectPath -Content $updatedPyproject
|
||
|
||
$buildRoot = Join-Path $Root "build"
|
||
$appDist = Join-Path $buildRoot "app"
|
||
$workPath = Join-Path $buildRoot "pyinstaller"
|
||
New-Item -ItemType Directory -Force -Path $buildRoot, $appDist | Out-Null
|
||
|
||
$versionInfoPath = Join-Path $buildRoot "version_info.txt"
|
||
$versionInfo = @"
|
||
VSVersionInfo(
|
||
ffi=FixedFileInfo(filevers=($($versionParts[0]), $($versionParts[1]), $($versionParts[2]), 0), prodvers=($($versionParts[0]), $($versionParts[1]), $($versionParts[2]), 0), mask=0x3f, flags=0x0, OS=0x40004, fileType=0x1, subtype=0x0, date=(0, 0)),
|
||
kids=[StringFileInfo([StringTable('040704B0', [StringStruct('CompanyName', 'Davito'), StringStruct('FileDescription', 'SyncVito'), StringStruct('FileVersion', '$fileVersion'), StringStruct('InternalName', 'SyncVito'), StringStruct('OriginalFilename', 'SyncVito.exe'), StringStruct('ProductName', 'SyncVito'), StringStruct('ProductVersion', '$appVersion')])]), VarFileInfo([VarStruct('Translation', [1031, 1200])])]
|
||
)
|
||
"@
|
||
Write-Utf8NoBom -LiteralPath $versionInfoPath -Content $versionInfo
|
||
|
||
Write-Host "Tests werden ausgeführt …"
|
||
& $python -m pytest
|
||
if ($LASTEXITCODE -ne 0) { throw "Die Tests sind fehlgeschlagen." }
|
||
& $python -m ruff check .
|
||
if ($LASTEXITCODE -ne 0) { throw "Ruff hat Fehler gefunden." }
|
||
|
||
Write-Host "Programmsymbol wird erzeugt …"
|
||
& $python tools\generate_icon.py
|
||
if ($LASTEXITCODE -ne 0) { throw "Das Programmsymbol konnte nicht erzeugt werden." }
|
||
|
||
Write-Host "Baue SyncVito.exe mit PyInstaller …"
|
||
& $python -m PyInstaller --noconfirm --clean --distpath $appDist --workpath $workPath SyncVito.spec
|
||
if ($LASTEXITCODE -ne 0) { throw "Der PyInstaller-Build ist fehlgeschlagen." }
|
||
$appExe = Join-Path $appDist "SyncVito.exe"
|
||
if (-not (Test-Path -LiteralPath $appExe -PathType Leaf)) { throw "SyncVito.exe wurde nicht erzeugt." }
|
||
|
||
Write-Host "Prüfe den gebauten Programmstart …"
|
||
$smoke = Start-Process -FilePath $appExe -ArgumentList "--smoke-test" -PassThru -Wait -WindowStyle Hidden
|
||
if ($smoke.ExitCode -ne 0) { throw "Der EXE-Smoke-Test ist fehlgeschlagen (Exitcode $($smoke.ExitCode))." }
|
||
|
||
New-Item -ItemType Directory -Force -Path (Join-Path $Root "release") | Out-Null
|
||
Write-Host "Baue Windows-Installer …"
|
||
& $iscc "/DMyAppVersion=$appVersion" "/DMyAppFileVersion=$fileVersion" "/DMyAppExeSource=$appExe" (Join-Path $Root "installer\SyncVito.iss")
|
||
if ($LASTEXITCODE -ne 0) { throw "Der Inno-Setup-Build ist fehlgeschlagen." }
|
||
|
||
$installerName = "SyncVitoSetup-$appVersion.exe"
|
||
$installerPath = Join-Path $Root "release\$installerName"
|
||
if (-not (Test-Path -LiteralPath $installerPath -PathType Leaf)) { throw "Installer wurde nicht gefunden: $installerPath" }
|
||
$hash = Get-Sha256Hex -LiteralPath $installerPath
|
||
$checksumPath = "$installerPath.sha256"
|
||
Write-Utf8NoBom -LiteralPath $checksumPath -Content "$hash *$installerName`n"
|
||
$releaseChangelogPath = Join-Path $Root "release\CHANGELOG-$appVersion.md"
|
||
Write-Utf8NoBom -LiteralPath $releaseChangelogPath -Content $releaseNotes
|
||
|
||
if (-not $SkipGit) {
|
||
git add --all
|
||
if ($LASTEXITCODE -ne 0) { throw "Git konnte die Release-Änderungen nicht vormerken." }
|
||
git diff --cached --quiet
|
||
if ($LASTEXITCODE -ne 0) {
|
||
git commit -m "Release SyncVito $appVersion"
|
||
if ($LASTEXITCODE -ne 0) { throw "Der Release-Commit ist fehlgeschlagen." }
|
||
}
|
||
$sourceCommitted = $true
|
||
$pushTarget = "origin"
|
||
$tokenPath = @(
|
||
(Join-Path $env:LOCALAPPDATA "SyncVito\developer\gitea-token.dpapi"),
|
||
(Join-Path $env:LOCALAPPDATA "MusicBrain\developer\gitea-token.dpapi")
|
||
) | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1
|
||
if (-not [string]::IsNullOrWhiteSpace($env:GITEA_TOKEN)) {
|
||
$pushTarget = "https://Davito:$($env:GITEA_TOKEN.Trim())@gitea.davito.synology.me/Davito/SyncVito.git"
|
||
}
|
||
elseif ($tokenPath) {
|
||
try {
|
||
$sec = (Get-Content $tokenPath -Raw).Trim() | ConvertTo-SecureString
|
||
$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec)
|
||
$t = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)
|
||
$pushTarget = "https://Davito:$t@gitea.davito.synology.me/Davito/SyncVito.git"
|
||
} catch { }
|
||
}
|
||
git push $pushTarget main
|
||
if ($LASTEXITCODE -ne 0) { throw "Der Quellstand konnte nicht nach Gitea gepusht werden." }
|
||
}
|
||
|
||
if (-not $SkipPublish) {
|
||
& (Join-Path $Root "publish_gitea_release.ps1") -Version $appVersion
|
||
if ($LASTEXITCODE -ne 0) { throw "Der Gitea-Release ist fehlgeschlagen." }
|
||
}
|
||
|
||
Write-Host ""
|
||
Write-Host "Release-Workflow abgeschlossen:"
|
||
Write-Host " Installer: $installerPath"
|
||
Write-Host " Prüfsumme: $checksumPath"
|
||
Write-Host " SHA256: $hash"
|
||
}
|
||
catch {
|
||
if (-not $sourceCommitted) {
|
||
Write-Utf8NoBom -LiteralPath $VersionPath -Content $originalVersion
|
||
Write-Utf8NoBom -LiteralPath $PyprojectPath -Content $originalPyproject
|
||
Write-Warning "Build fehlgeschlagen; Versionsdateien wurden zurückgesetzt."
|
||
}
|
||
throw
|
||
}
|