cc-haha/desktop/scripts/build-windows-x64.ps1
Relakkes Yang 5d243b657a feat(desktop): Windows 自定义标题栏 & 服务端改进
- Windows 上隐藏原生菜单栏和窗口装饰,前端渲染自定义窗口控制按钮(最小化/最大化/关闭)
- macOS 保持原生菜单栏不变(条件编译)
- Sidebar 在 Windows 上不再添加 macOS 红绿灯的额外顶部间距
- 新增 Windows 构建脚本和 NSIS 安装钩子
- 服务端 CORS、会话服务、对话服务改进及测试补充

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 21:29:35 +08:00

296 lines
8.2 KiB
PowerShell

[CmdletBinding()]
param(
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$TauriArgs
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$desktopDir = (Resolve-Path (Join-Path $scriptDir '..')).Path
$repoRoot = (Resolve-Path (Join-Path $desktopDir '..')).Path
$targetTriple = 'x86_64-pc-windows-msvc'
$tauriTargetDir = Join-Path $desktopDir 'src-tauri\target'
$canonicalOutputDir = Join-Path $desktopDir 'build-artifacts\windows-x64'
$activeOutputDir = $canonicalOutputDir
function Write-Step {
param([string]$Message)
Write-Host "[build-windows-x64] $Message"
}
function Assert-WindowsHost {
if ($env:OS -ne 'Windows_NT') {
throw '[build-windows-x64] This script must run on Windows.'
}
}
function Assert-Command {
param([string]$Name)
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
throw "[build-windows-x64] Missing required command: $Name"
}
}
function Import-VsDevEnvironment {
$vswhere = 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe'
if (-not (Test-Path $vswhere)) {
throw '[build-windows-x64] Could not find vswhere.exe. Install Visual Studio 2022 Build Tools with the C++ workload.'
}
$installationPath = & $vswhere `
-products * `
-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 `
-property installationPath |
Select-Object -First 1
if (-not $installationPath) {
throw '[build-windows-x64] Missing Visual C++ build tools. Install the "Desktop development with C++" / VC.Tools.x86.x64 workload first.'
}
$vsDevCmd = Join-Path $installationPath 'Common7\Tools\VsDevCmd.bat'
if (-not (Test-Path $vsDevCmd)) {
throw "[build-windows-x64] Could not find VsDevCmd.bat under $installationPath"
}
Write-Step "Importing MSVC environment from $vsDevCmd"
$env:VSCMD_SKIP_SENDTELEMETRY = '1'
$envDump = & cmd.exe /d /s /c "`"$vsDevCmd`" -arch=x64 -host_arch=x64 >nul && set"
if ($LASTEXITCODE -ne 0) {
throw "[build-windows-x64] Failed to initialize Visual Studio build environment (exit $LASTEXITCODE)"
}
foreach ($line in $envDump) {
if ($line -match '^(.*?)=(.*)$') {
[Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process')
}
}
}
function Get-RustCargoBinDir {
return Join-Path $env:USERPROFILE '.cargo\bin'
}
function Ensure-RustInPath {
$cargoBinDir = Get-RustCargoBinDir
if ((Test-Path $cargoBinDir) -and -not (($env:Path -split ';') -contains $cargoBinDir)) {
$env:Path = "$cargoBinDir;$env:Path"
}
}
function Get-LatestArtifact {
param(
[string[]]$SearchRoots,
[string[]]$Patterns
)
foreach ($root in $SearchRoots) {
if (-not (Test-Path $root)) {
continue
}
foreach ($pattern in $Patterns) {
$match = Get-ChildItem -Path $root -File -Filter $pattern -ErrorAction SilentlyContinue |
Sort-Object Name |
Select-Object -Last 1
if ($match) {
return $match
}
}
}
return $null
}
function Resolve-OutputDirectory {
param([string]$PreferredPath)
New-Item -ItemType Directory -Force -Path $PreferredPath | Out-Null
$existingArtifacts = Get-ChildItem -Path $PreferredPath -Force -ErrorAction SilentlyContinue
foreach ($artifact in $existingArtifacts) {
try {
Remove-Item -LiteralPath $artifact.FullName -Force -Recurse
} catch {
$fallbackPath = "$PreferredPath-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
Write-Step "Could not clear locked artifact '$($artifact.FullName)'. Using fallback output directory: $fallbackPath"
New-Item -ItemType Directory -Force -Path $fallbackPath | Out-Null
return $fallbackPath
}
}
return $PreferredPath
}
Assert-WindowsHost
Assert-Command bun
Ensure-RustInPath
Import-VsDevEnvironment
Assert-Command cargo
Assert-Command rustc
Assert-Command bunx
if ($env:SKIP_INSTALL -ne '1') {
Write-Step 'Installing root dependencies...'
Push-Location $repoRoot
try {
& bun install
if ($LASTEXITCODE -ne 0) {
throw "[build-windows-x64] bun install failed in repo root (exit $LASTEXITCODE)"
}
} finally {
Pop-Location
}
Write-Step 'Installing desktop dependencies...'
Push-Location $desktopDir
try {
& bun install
if ($LASTEXITCODE -ne 0) {
throw "[build-windows-x64] bun install failed in desktop (exit $LASTEXITCODE)"
}
} finally {
Pop-Location
}
$adaptersDir = Join-Path $repoRoot 'adapters'
if (Test-Path (Join-Path $adaptersDir 'package.json')) {
Write-Step 'Installing adapter dependencies...'
Push-Location $adaptersDir
try {
& bun install
if ($LASTEXITCODE -ne 0) {
throw "[build-windows-x64] bun install failed in adapters (exit $LASTEXITCODE)"
}
} finally {
Pop-Location
}
}
}
$tauriBuildArgs = @(
'tauri',
'build',
'--target',
$targetTriple,
'--bundles',
'nsis,msi',
'--ci'
)
$tempConfigPath = $null
if (-not $env:TAURI_SIGNING_PRIVATE_KEY) {
$tempConfigPath = Join-Path ([System.IO.Path]::GetTempPath()) 'cc-haha.tauri.local.windows.json'
$tempConfig = @{
bundle = @{
createUpdaterArtifacts = $false
}
} | ConvertTo-Json -Depth 10
Set-Content -Path $tempConfigPath -Value $tempConfig -Encoding UTF8
Write-Step 'TAURI_SIGNING_PRIVATE_KEY not set, disabling updater artifacts for local build'
$tauriBuildArgs += @('--config', $tempConfigPath)
}
if ($null -ne $TauriArgs) {
$remainingArgs = @($TauriArgs)
if ($remainingArgs.Count -gt 0) {
$tauriBuildArgs += $remainingArgs
}
}
Write-Step "Building Windows desktop app for $targetTriple"
Push-Location $desktopDir
try {
$env:TAURI_ENV_TARGET_TRIPLE = $targetTriple
& bunx @tauriBuildArgs
if ($LASTEXITCODE -ne 0) {
throw "[build-windows-x64] tauri build failed (exit $LASTEXITCODE)"
}
} finally {
Pop-Location
if ($tempConfigPath -and (Test-Path $tempConfigPath)) {
Remove-Item -LiteralPath $tempConfigPath -Force
}
}
$activeOutputDir = Resolve-OutputDirectory -PreferredPath $canonicalOutputDir
$bundleRoots = @(
(Join-Path $tauriTargetDir "$targetTriple\release\bundle"),
(Join-Path $tauriTargetDir 'release\bundle')
)
$artifactPatterns = @('*.exe', '*.msi', '*.zip', '*.sig', 'latest.json')
$copiedArtifacts = New-Object System.Collections.Generic.List[string]
foreach ($root in $bundleRoots) {
if (-not (Test-Path $root)) {
continue
}
foreach ($pattern in $artifactPatterns) {
$artifacts = Get-ChildItem -Path $root -Recurse -File -Filter $pattern -ErrorAction SilentlyContinue
foreach ($artifact in $artifacts) {
$destination = Join-Path $activeOutputDir $artifact.Name
Copy-Item -LiteralPath $artifact.FullName -Destination $destination -Force
if (-not $copiedArtifacts.Contains($destination)) {
$copiedArtifacts.Add($destination) | Out-Null
}
}
}
}
$nsisInstaller = Get-LatestArtifact -SearchRoots @(
(Join-Path $tauriTargetDir "$targetTriple\release\bundle\nsis"),
(Join-Path $tauriTargetDir 'release\bundle\nsis')
) -Patterns @('*.exe')
$msiInstaller = Get-LatestArtifact -SearchRoots @(
(Join-Path $tauriTargetDir "$targetTriple\release\bundle\msi"),
(Join-Path $tauriTargetDir 'release\bundle\msi')
) -Patterns @('*.msi')
$nsisInstallerPath = if ($nsisInstaller) { $nsisInstaller.FullName } else { 'not found' }
$msiInstallerPath = if ($msiInstaller) { $msiInstaller.FullName } else { 'not found' }
$buildInfo = @(
"Target triple: $targetTriple"
"Canonical output: $canonicalOutputDir"
"Actual output: $activeOutputDir"
"NSIS installer: $nsisInstallerPath"
"MSI installer: $msiInstallerPath"
"Artifacts copied: $($copiedArtifacts.Count)"
"Built at: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz')"
)
Set-Content -Path (Join-Path $activeOutputDir 'BUILD_INFO.txt') -Value $buildInfo -Encoding UTF8
Write-Host ''
Write-Step 'Build finished.'
Write-Step "Artifacts output: $activeOutputDir"
if ($nsisInstaller) {
Write-Step "NSIS installer source: $($nsisInstaller.FullName)"
} else {
Write-Step 'No NSIS installer found under bundle directories.'
}
if ($msiInstaller) {
Write-Step "MSI installer source: $($msiInstaller.FullName)"
} else {
Write-Step 'No MSI installer found under bundle directories.'
}
Write-Step "Canonical output: $canonicalOutputDir"
if ($env:OPEN_OUTPUT -eq '1') {
Invoke-Item $canonicalOutputDir
}