Files
XplorePlane/tools/build-release.ps1
T
zhengxuan.zhang ea7a350530 修复打包脚本
2026-08-06 13:49:48 +08:00

293 lines
11 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
param(
# 不传时自动从 XplorePlane.csproj 的 <Version> 读取,传入则覆盖工程配置(用于临时测试版本号)
[string]$Version,
# Keep the technical PackId stable so existing Velopack installations can update.
[string]$PackId = "Hexagon.XplorePlane",
[string]$PackTitle = "XplorePlane",
[string]$PackAuthors = "Hexagon",
[string]$MainExe = "XplorePlane.exe",
[string]$DeployDir = "C:\wamp64\www\updates\xploreplane",
[switch]$SkipBuild,
# By default also generate an MSI installer so the user can choose the install location.
# Use -NoMsi when only the per-user Setup.exe is required.
[switch]$NoMsi,
[ValidateSet("Either", "PerUser", "PerMachine")]
[string]$InstallLocation = "Either",
[string]$Configuration = "Release",
[string]$RuntimeId = "win-x64"
)
$ErrorActionPreference = "Stop"
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
$ProjectPath = Join-Path $RepoRoot "XplorePlane\XplorePlane.csproj"
$PublishDir = Join-Path $RepoRoot "publish"
$ReleasesDir = Join-Path $RepoRoot "Releases"
# 版本号来源:命令行 -Version 优先,否则从 csproj 的 <Version> 属性读取,
# 确保发布包版本号与工程配置保持单一来源,不再需要每次手动传参维护。
if (-not $Version) {
Write-Host "Reading version from csproj..." -ForegroundColor DarkGray
$Version = (dotnet msbuild $ProjectPath -nologo -getProperty:Version).Trim()
if ($LASTEXITCODE -ne 0 -or -not $Version) {
Write-Host "FAILED: Could not read <Version> from $ProjectPath. Pass -Version explicitly or add <Version> to the csproj." -ForegroundColor Red
exit 1
}
}
Write-Host ""
Write-Host "========================================================" -ForegroundColor Cyan
Write-Host " XplorePlane Build and Release Script" -ForegroundColor Cyan
Write-Host "========================================================" -ForegroundColor Cyan
Write-Host " Version: $Version" -ForegroundColor Cyan
Write-Host " PackId: $PackId" -ForegroundColor Cyan
Write-Host " PackTitle: $PackTitle" -ForegroundColor Cyan
Write-Host " Publisher: $PackAuthors" -ForegroundColor Cyan
Write-Host " Config: $Configuration" -ForegroundColor Cyan
Write-Host " Runtime: $RuntimeId" -ForegroundColor Cyan
Write-Host " MSI: $(-not $NoMsi)" -ForegroundColor Cyan
if (-not $NoMsi) { Write-Host " Install scope: $InstallLocation" -ForegroundColor Cyan }
Write-Host " DeployDir: $DeployDir" -ForegroundColor Cyan
Write-Host "========================================================" -ForegroundColor Cyan
Write-Host ""
# Step 1: Clean
# 仅在即将重新构建时清理 publish 目录:-SkipBuild 场景下需要复用已有的发布产物
# (例如仅因 vpk 打包阶段文件占用失败而重试),此前无条件清理会导致 -SkipBuild
# 传入后必然因目录被清空而报错,参数形同虚设。
if (-not $SkipBuild) {
Write-Host "[1/5] Cleaning publish directory..." -ForegroundColor Yellow
if (Test-Path $PublishDir) { Remove-Item $PublishDir -Recurse -Force }
} else {
Write-Host "[1/5] Skipped clean (-SkipBuild, reusing existing publish directory)" -ForegroundColor DarkGray
}
# Step 2: Build and Publish
if (-not $SkipBuild) {
Write-Host "[2/5] Building and publishing ($Configuration, $RuntimeId)..." -ForegroundColor Yellow
dotnet publish $ProjectPath -c $Configuration --self-contained -r $RuntimeId -o $PublishDir -p:Version=$Version
if ($LASTEXITCODE -ne 0) {
Write-Host "FAILED: dotnet publish failed!" -ForegroundColor Red
exit 1
}
Write-Host "OK: Build succeeded" -ForegroundColor Green
} else {
Write-Host "[2/5] Skipped build (-SkipBuild)" -ForegroundColor DarkGray
if (-not (Test-Path $PublishDir)) {
Write-Host "FAILED: publish directory not found, run without -SkipBuild first" -ForegroundColor Red
exit 1
}
}
# Ensure the packaged application starts with the virtual navigation camera.
# Navigation camera settings are stored in config.json, not App.config.
$cameraConfigPath = Join-Path $PublishDir "config.json"
$cameraConfig = [ordered]@{
Language = "zh-CN"
LogLevel = "Debug"
CameraType = "Simulated"
CameraSimulatedImagePath = ""
}
if (Test-Path -LiteralPath $cameraConfigPath) {
try {
$existingCameraConfig = Get-Content -LiteralPath $cameraConfigPath -Raw | ConvertFrom-Json
foreach ($property in $existingCameraConfig.PSObject.Properties) {
$cameraConfig[$property.Name] = $property.Value
}
} catch {
Write-Host " WARN: Existing config.json is invalid; recreating camera defaults." -ForegroundColor Yellow
}
}
$cameraConfig["CameraType"] = "Simulated"
$cameraConfig["CameraSimulatedImagePath"] = ""
$cameraConfig | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $cameraConfigPath -Encoding UTF8
Write-Host "OK: Packaged navigation camera default: Simulated" -ForegroundColor Green
function Rename-ExistingMsi {
param(
[string]$Directory,
[string]$PackageId
)
$legacyMsi = Join-Path $Directory "$PackageId-win.msi"
if (-not (Test-Path -LiteralPath $legacyMsi)) {
return
}
try {
$installer = New-Object -ComObject WindowsInstaller.Installer
$database = $installer.OpenDatabase($legacyMsi, 0)
$view = $database.OpenView("SELECT Value FROM Property WHERE Property='ProductVersion'")
$view.Execute()
$record = $view.Fetch()
$oldVersion = $record.StringData(1)
}
catch {
throw "无法读取旧 MSI 的 ProductVersion$legacyMsi$($_.Exception.Message)"
}
finally {
# Windows Installer COM objects keep the MSI open until every RCW is released.
foreach ($comObject in @($record, $view, $database, $installer)) {
if ($null -ne $comObject) {
[void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($comObject)
}
}
$record = $null
$view = $null
$database = $null
$installer = $null
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
if ([string]::IsNullOrWhiteSpace($oldVersion)) {
throw "旧 MSI 未包含有效的 ProductVersion$legacyMsi"
}
$versionedMsi = Join-Path $Directory "XplorePlane-$oldVersion-win.msi"
if (Test-Path -LiteralPath $versionedMsi) {
throw "旧 MSI 的版本化文件已存在,为避免覆盖而停止:$versionedMsi"
}
$moved = $false
for ($attempt = 1; $attempt -le 5; $attempt++) {
try {
Move-Item -LiteralPath $legacyMsi -Destination $versionedMsi -ErrorAction Stop
$moved = $true
break
}
catch [System.IO.IOException] {
if ($attempt -eq 5) { throw }
Start-Sleep -Milliseconds 500
}
}
if (-not $moved) {
throw "无法移动旧 MSI$legacyMsi"
}
Write-Host " Archived existing MSI: $versionedMsi" -ForegroundColor DarkGray
}
# Step 3: Velopack Pack
Write-Host "[3/5] Packing with vpk..." -ForegroundColor Yellow
$vpkCmd = Get-Command vpk -ErrorAction SilentlyContinue
if (-not $vpkCmd) {
Write-Host " vpk not found, installing..." -ForegroundColor Yellow
dotnet tool install -g vpk
if ($LASTEXITCODE -ne 0) {
Write-Host "FAILED: vpk install failed" -ForegroundColor Red
exit 1
}
# 当前 PowerShell 进程的 PATH 可能尚未包含全局工具目录,安装后重新解析一次。
$vpkCmd = Get-Command vpk -ErrorAction SilentlyContinue
}
$vpkPath = if ($vpkCmd) { $vpkCmd.Source } else { Join-Path $HOME ".dotnet\tools\vpk.exe" }
if (-not (Test-Path -LiteralPath $vpkPath)) {
Write-Host "FAILED: vpk executable not found: $vpkPath" -ForegroundColor Red
exit 1
}
if (-not $NoMsi) {
Rename-ExistingMsi -Directory $ReleasesDir -PackageId $PackId
}
$vpkArgs = @(
"pack",
"--packId", $PackId,
"--packTitle", $PackTitle,
"--packAuthors", $PackAuthors,
"--packVersion", $Version,
"--packDir", $PublishDir,
"--mainExe", $MainExe,
"--outputDir", $ReleasesDir
)
if (-not $NoMsi) {
# Setup.exe remains the per-user one-click installer. MSI with Either shows
# the Windows Installer location selection page to the end user.
$vpkArgs += "--msi"
$vpkArgs += "--instLocation"
$vpkArgs += $InstallLocation
}
& $vpkPath @vpkArgs
if ($LASTEXITCODE -ne 0) {
Write-Host "FAILED: vpk pack failed!" -ForegroundColor Red
exit 1
}
if (-not $NoMsi) {
$generatedMsi = Join-Path $ReleasesDir "$PackId-win.msi"
$versionedMsi = Join-Path $ReleasesDir "XplorePlane-$Version-win.msi"
if (Test-Path -LiteralPath $generatedMsi) {
Move-Item -LiteralPath $generatedMsi -Destination $versionedMsi -Force
Write-Host "OK: MSI archived as $versionedMsi" -ForegroundColor Green
}
}
Write-Host "OK: Pack succeeded, output in $ReleasesDir" -ForegroundColor Green
# Step 4: Deploy to WampServer
Write-Host "[4/5] Deploying to WampServer..." -ForegroundColor Yellow
if (-not (Test-Path $DeployDir)) {
New-Item -ItemType Directory -Path $DeployDir -Force | Out-Null
Write-Host " Created directory: $DeployDir" -ForegroundColor DarkGray
}
Copy-Item -Path (Join-Path $ReleasesDir "*") -Destination $DeployDir -Force -Recurse
Write-Host "OK: Deployed to $DeployDir" -ForegroundColor Green
# Step 5: Verify
Write-Host "[5/5] Verifying deployment..." -ForegroundColor Yellow
$releasesFile = Join-Path $DeployDir "RELEASES"
if (Test-Path $releasesFile) {
Write-Host " OK: RELEASES file exists" -ForegroundColor Green
$fullPkg = Get-ChildItem $DeployDir -Filter "*-full.nupkg" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
if ($fullPkg) {
$sizeMB = [math]::Round($fullPkg.Length / 1MB, 1)
Write-Host " OK: Full package: $($fullPkg.Name) ($sizeMB MB)" -ForegroundColor Green
}
$deltaPkg = Get-ChildItem $DeployDir -Filter "*-delta.nupkg" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
if ($deltaPkg) {
$sizeMB = [math]::Round($deltaPkg.Length / 1MB, 1)
Write-Host " OK: Delta package: $($deltaPkg.Name) ($sizeMB MB)" -ForegroundColor Green
}
$setupExe = Get-ChildItem $DeployDir -Filter "Setup*.exe" | Select-Object -First 1
if ($setupExe) {
Write-Host " OK: Setup: $($setupExe.Name)" -ForegroundColor Green
}
$msi = Get-ChildItem $DeployDir -Filter "*.msi" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
if ($msi) {
Write-Host " OK: MSI: $($msi.Name)" -ForegroundColor Green
}
} else {
Write-Host " WARN: RELEASES file not found, deployment may be incomplete" -ForegroundColor Yellow
}
Write-Host ""
Write-Host "========================================================" -ForegroundColor Cyan
Write-Host " Release complete!" -ForegroundColor Green
Write-Host ""
Write-Host " Update URL: http://192.168.16.134/updates/xploreplane" -ForegroundColor White
Write-Host " Setup: $DeployDir\Setup.exe (per-user, fixed default location)" -ForegroundColor White
if (-not $NoMsi) {
Write-Host " MSI: $DeployDir\*.msi (user-selectable location)" -ForegroundColor White
}
Write-Host ""
Write-Host " Make sure WampServer is running (Apache online)." -ForegroundColor DarkGray
Write-Host " Client App.config Update:ServerUrl should be:" -ForegroundColor DarkGray
Write-Host " http://<server-ip>/updates/xploreplane" -ForegroundColor White
Write-Host "========================================================" -ForegroundColor Cyan