Files
Hua.Todo/publish-linux.ps1
2026-04-13 23:10:07 +08:00

213 lines
7.7 KiB
PowerShell
Raw Permalink 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.
<#
Hua.Todo Linux 发布脚本(产出 .tar.gz
目标:
- 为 Linux 提供可分发的目录产物(dotnet publish 输出)
- 在 Windows 开发机上也能交叉发布 linux-x64(便于 CI 前的本地验证)
约束:
- Avalonia Linux 入口项目需先落地(参见 docs/project/v1.2.0-tasks/01-*
- 仅打包为 tar.gzFlatpak/AppImage 需要在 Linux 环境执行相关工具链(参见 pack/linux/ 说明)
#>
param(
[ValidateSet("linux-x64", "linux-arm64")]
[string]$RuntimeIdentifier = "linux-x64",
[string]$TargetFramework = "net10.0",
[switch]$SelfContained,
[switch]$SkipProcessStop,
[switch]$SkipRestore,
[ValidateSet("Release", "Debug")]
[string]$Configuration = "Release",
[string]$BaseIntermediateOutputPath
)
$ErrorActionPreference = "Stop"
$ScriptPath = $PSScriptRoot
$DirectoryBuildProps = Join-Path $ScriptPath "Directory.Build.props"
function Get-FirstExistingFilePath {
param(
[Parameter(Mandatory = $true)]
[string[]]$Candidates
)
foreach ($candidate in $Candidates) {
if (Test-Path $candidate) {
return $candidate
}
}
return $null
}
function Read-ProjectVersion {
param(
[Parameter(Mandatory = $true)]
[string]$ProjectFile,
[string]$DirectoryBuildProps
)
$currentVersion = "0.0.0"
# 1. Try Directory.Build.props first
if ($null -ne $DirectoryBuildProps -and (Test-Path $DirectoryBuildProps)) {
[xml]$props = Get-Content $DirectoryBuildProps -Raw
$versionNode = $props.SelectSingleNode("//Version")
if ($null -ne $versionNode -and -not [string]::IsNullOrWhiteSpace($versionNode.InnerText)) {
return $versionNode.InnerText.Trim()
}
}
# 2. Try .csproj
[xml]$csproj = Get-Content $ProjectFile -Raw
$versionNode = $csproj.SelectSingleNode("//Version")
if ($null -ne $versionNode -and -not [string]::IsNullOrWhiteSpace($versionNode.InnerText)) {
return $versionNode.InnerText.Trim()
}
$versionNode = $csproj.SelectSingleNode("//ApplicationDisplayVersion")
if ($null -ne $versionNode -and -not [string]::IsNullOrWhiteSpace($versionNode.InnerText)) {
return $versionNode.InnerText.Trim()
}
return $currentVersion
}
function Stop-ProjectProcesses {
Write-Host "Shutting down dotnet build servers..." -ForegroundColor Yellow
dotnet build-server shutdown | Out-Null
Write-Host "Checking for running processes to prevent file locks..." -ForegroundColor Yellow
# Aggressively look for anything related to the project or MSBuild/dotnet background tasks
$processesToKill = Get-Process | Where-Object {
$_.ProcessName -like "*Hua.Todo*" -or
$_.ProcessName -eq "MSBuild" -or
($_.ProcessName -eq "dotnet" -and ($_.CommandLine -like "*Hua.Todo*" -or $_.CommandLine -like "*msbuild*"))
}
if ($processesToKill) {
Write-Host "Stopping $($processesToKill.Count) running processes..." -ForegroundColor Yellow
foreach ($p in $processesToKill) {
try {
Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue
} catch {
Write-Warning "Failed to stop process $($p.ProcessName) (ID: $($p.Id))"
}
}
Start-Sleep -Seconds 2 # Give OS more time to release file handles
} else {
Write-Host "No conflicting processes found." -ForegroundColor Green
}
}
if (!$SkipProcessStop.IsPresent) {
Stop-ProjectProcesses
}
$candidateProjects = @(
(Join-Path $ScriptPath "src\Hua.Todo.Avalonia\Hua.Todo.Avalonia.csproj"),
(Join-Path $ScriptPath "src\Hua.Todo.Desktop.Avalonia\Hua.Todo.Desktop.Avalonia.csproj")
)
$ProjectFile = Get-FirstExistingFilePath -Candidates $candidateProjects
if ($null -eq $ProjectFile) {
$candidatesText = ($candidateProjects | ForEach-Object { " - $_" }) -join "`n"
Write-Error @"
Avalonia Linux Linux
docs/project/v1.2.0-tasks/01-Linux-AvaloniaWebView.md
$candidatesText
"@
exit 1
}
$version = Read-ProjectVersion -ProjectFile $ProjectFile -DirectoryBuildProps $DirectoryBuildProps
$ProjectBaseName = [System.IO.Path]::GetFileNameWithoutExtension($ProjectFile)
$artifactRoot = Join-Path $ScriptPath "artifacts\linux\$RuntimeIdentifier"
$publishDir = Join-Path $artifactRoot "publish"
if (Test-Path $artifactRoot) {
Remove-Item -Recurse -Force $artifactRoot
}
New-Item -ItemType Directory -Path $publishDir | Out-Null
$selfContainedValue = if ($SelfContained.IsPresent) { "true" } else { "false" }
if ($Configuration -ne "Release") {
Write-Host "⚠️ WARNING: You are publishing in $Configuration configuration!" -ForegroundColor Yellow
Write-Host " Typically, production artifacts MUST be in Release configuration." -ForegroundColor Yellow
Write-Host ""
}
if (!$SkipRestore.IsPresent) {
Write-Host "Restoring $ProjectBaseName for $RuntimeIdentifier ($TargetFramework)..." -ForegroundColor Yellow
$restoreArgs = @("restore", $ProjectFile, "-r", $RuntimeIdentifier, "-p:IsDesktopBuild=true", "-p:SkipWebBuild=true", "--verbosity", "minimal")
if (![string]::IsNullOrWhiteSpace($BaseIntermediateOutputPath)) {
# Ensure trailing slash and avoid backslash escaping the quote in CLI
$path = $BaseIntermediateOutputPath.TrimEnd('\') + '\'
$restoreArgs += "-p:BaseIntermediateOutputPath=$path"
}
& dotnet @restoreArgs
Write-Host "Cleaning $ProjectBaseName ($TargetFramework)..." -ForegroundColor Yellow
$cleanArgs = @("clean", $ProjectFile, "-c", $Configuration, "-r", $RuntimeIdentifier, "-p:IsDesktopBuild=true", "-p:SkipWebBuild=true", "--verbosity", "minimal")
if (![string]::IsNullOrWhiteSpace($BaseIntermediateOutputPath)) {
$path = $BaseIntermediateOutputPath.TrimEnd('\') + '\'
$cleanArgs += "-p:BaseIntermediateOutputPath=$path"
}
& dotnet @cleanArgs
}
$maxAttempts = 3
$publishSuccess = $false
for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) {
Write-Host "Publishing (RID=$RuntimeIdentifier, TFM=$TargetFramework, SelfContained=$selfContainedValue, Config=$Configuration, Attempt $attempt/$maxAttempts)..." -ForegroundColor Cyan
$publishArgs = @("publish", $ProjectFile, "-c", $Configuration, "-r", $RuntimeIdentifier, "--framework", $TargetFramework, "--self-contained", $selfContainedValue, "-p:IsDesktopBuild=true", "-p:SkipWebBuild=true", "-o", $publishDir)
if (![string]::IsNullOrWhiteSpace($BaseIntermediateOutputPath)) {
$path = $BaseIntermediateOutputPath.TrimEnd('\') + '\'
$publishArgs += "-p:BaseIntermediateOutputPath=$path"
}
$publishOutput = (& dotnet @publishArgs 2>&1 | Out-String)
$exitCode = $LASTEXITCODE
if ($exitCode -eq 0) {
$publishSuccess = $true
break
}
Write-Host $publishOutput
if ($attempt -lt $maxAttempts -and ($publishOutput -match 'Access to the path .* is denied|being used by another process|Sharing violation')) {
Write-Host "⚠️ Build failed due to file lock. Retrying in 3 seconds..." -ForegroundColor Yellow
Start-Sleep -Seconds 3
Stop-ProjectProcesses # Try killing processes again before retry
continue
}
Write-Error "dotnet publish failed after $attempt attempts."
exit 1
}
$packageName = "hua.todo-$version-$RuntimeIdentifier.tar.gz"
$packagePath = Join-Path $artifactRoot $packageName
Write-Host "Creating tar.gz: $packageName" -ForegroundColor Cyan
tar -C $publishDir -czf $packagePath .
if ($LASTEXITCODE -ne 0) {
Write-Error "tar.gz packaging failed"
exit 1
}
Write-Host "Linux artifact created: $packagePath" -ForegroundColor Green