159 lines
6.8 KiB
PowerShell
159 lines
6.8 KiB
PowerShell
param(
|
|
[string]$PythonPath = ""
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$projectRoot = [System.IO.Path]::GetFullPath('C:\wechat_rpa')
|
|
$buildEnvironment = Join-Path $projectRoot ".build-venv"
|
|
$buildPython = Join-Path $buildEnvironment "Scripts\python.exe"
|
|
$requirements = Join-Path $projectRoot "requirements.txt"
|
|
$spec = Join-Path $projectRoot "wechat_rpa.spec"
|
|
|
|
$versionLine = Select-String -LiteralPath (Join-Path $projectRoot "app_version.py") -Pattern '^APP_VERSION\s*=\s*"([^"]+)"$'
|
|
if (-not $versionLine) {
|
|
throw "Cannot read APP_VERSION from app_version.py"
|
|
}
|
|
$version = $versionLine.Matches[0].Groups[1].Value
|
|
$env:WECOM_BUILD_VERSION = $version
|
|
$env:UV_CACHE_DIR = Join-Path $projectRoot ".uv-cache"
|
|
|
|
function Test-BuildPython([string]$Candidate) {
|
|
if (-not $Candidate -or -not (Test-Path -LiteralPath $Candidate)) {
|
|
return $false
|
|
}
|
|
try {
|
|
& $Candidate -c "import ssl, _ssl, PyInstaller, PySide6, requests, numpy, PIL, pyautogui, pyperclip, win32gui, mcp, rapidocr_onnxruntime, Crypto.Cipher.AES" *> $null
|
|
return $LASTEXITCODE -eq 0
|
|
}
|
|
catch {
|
|
# Windows PowerShell 5 treats native stderr as a terminating error with
|
|
# ErrorActionPreference=Stop, even when redirected. Try the next runtime.
|
|
return $false
|
|
}
|
|
}
|
|
|
|
if ($PythonPath -and [IO.Path]::GetFullPath($PythonPath) -ne $buildPython) { throw 'Use the existing production .build-venv runtime only' }
|
|
if (-not (Test-BuildPython $buildPython)) { throw 'Existing build runtime is incomplete; no dependency installation or fallback is performed' }
|
|
& $buildPython -B -c 'import sys; assert sys.version_info[:2] == (3,12)'
|
|
if ($LASTEXITCODE -ne 0) { throw 'Python 3.12 required' }
|
|
$python = $buildPython
|
|
|
|
Write-Output ("Build Python: " + $python)
|
|
|
|
Push-Location $projectRoot
|
|
try {
|
|
& $python (Join-Path $projectRoot "packaging_exporter.py") --project-root $projectRoot
|
|
if ($LASTEXITCODE -ne 0) { throw "Archive exporter preflight failed; see the missing source file above" }
|
|
& $python -m PyInstaller --noconfirm $spec
|
|
if ($LASTEXITCODE -ne 0) { throw "PyInstaller build failed" }
|
|
}
|
|
finally {
|
|
Pop-Location
|
|
}
|
|
|
|
$appName = "ZhenAI-WeCom-Assistant-v$version"
|
|
$appDir = Join-Path $projectRoot "dist\$appName"
|
|
$exe = Join-Path $appDir "$appName.exe"
|
|
if (-not (Test-Path -LiteralPath $exe)) {
|
|
throw ("Build completed but EXE was not found: " + $exe)
|
|
}
|
|
|
|
$internalDir = Join-Path $appDir "_internal"
|
|
# Qt 与其他视觉库可能分别携带不同版本的 VC Runtime。应用根目录优先级
|
|
# 最高,因此明确放置与当前 PySide6/Qt 完全配套的一组,避免 PATH 中第三方
|
|
# 工具的 DLL 在打包分析时抢占同名文件。
|
|
$pysideRuntimeDir = (& $python -c "import pathlib, PySide6; print(pathlib.Path(PySide6.__file__).resolve().parent)" | Select-Object -Last 1).Trim()
|
|
foreach ($runtimeName in @(
|
|
"MSVCP140.dll",
|
|
"MSVCP140_1.dll",
|
|
"MSVCP140_2.dll",
|
|
"VCRUNTIME140.dll",
|
|
"VCRUNTIME140_1.dll"
|
|
)) {
|
|
$runtimeSource = Join-Path $pysideRuntimeDir $runtimeName
|
|
if (-not (Test-Path -LiteralPath $runtimeSource)) {
|
|
throw ("PySide6 runtime file is missing: " + $runtimeName)
|
|
}
|
|
Copy-Item -LiteralPath $runtimeSource -Destination (Join-Path $internalDir $runtimeName) -Force
|
|
}
|
|
$requiredRuntimeFiles = @(
|
|
"PySide6\QtWebEngineProcess.exe",
|
|
"PySide6\resources\qtwebengine_resources.pak",
|
|
"PySide6\resources\icudtl.dat",
|
|
"PySide6\translations\qtwebengine_locales\zh-CN.pak",
|
|
"MSVCP140.dll",
|
|
"MSVCP140_1.dll",
|
|
"MSVCP140_2.dll",
|
|
"VCRUNTIME140.dll",
|
|
"VCRUNTIME140_1.dll"
|
|
)
|
|
foreach ($requiredFile in $requiredRuntimeFiles) {
|
|
if (-not (Test-Path -LiteralPath (Join-Path $internalDir $requiredFile))) {
|
|
throw ("Required embedded runtime file is missing: " + $requiredFile)
|
|
}
|
|
}
|
|
Write-Output "Embedded browser and VC runtime files verified"
|
|
|
|
# 自动升级必须由安装目录之外的独立进程执行,否则 Windows 会阻止正在运行的
|
|
# 主程序覆盖自身。更新器只依赖标准库,单独打成 onefile,主程序启动它前会再
|
|
# 复制到 updates 目录,因此 NSIS 可以安全替换安装目录中的这一份。
|
|
$updaterDist = Join-Path $projectRoot "dist\updater-helper"
|
|
$updaterWork = Join-Path $projectRoot "build\updater-helper"
|
|
New-Item -ItemType Directory -Path $updaterWork -Force | Out-Null
|
|
& $python -m PyInstaller --noconfirm --onefile --name ZhenAIUpdater `
|
|
--distpath $updaterDist --workpath $updaterWork --specpath $updaterWork `
|
|
(Join-Path $projectRoot "updater_main.py")
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Updater EXE build failed"
|
|
}
|
|
$updaterExe = Join-Path $updaterDist "ZhenAIUpdater.exe"
|
|
if (-not (Test-Path -LiteralPath $updaterExe)) {
|
|
throw "Updater EXE was not found"
|
|
}
|
|
& (Join-Path $projectRoot "sign_artifact.ps1") -Path $updaterExe
|
|
if ($LASTEXITCODE -ne 0) { throw "Updater code signing step failed" }
|
|
Copy-Item -LiteralPath $updaterExe -Destination (Join-Path $appDir "ZhenAIUpdater.exe") -Force
|
|
Write-Output "Independent updater built and bundled"
|
|
|
|
# 有证书时给主程序签名(无证书自动跳过)。签名要在自检前完成,
|
|
# 保证自检跑的就是最终分发的那个文件。
|
|
& (Join-Path $projectRoot "sign_artifact.ps1") -Path $exe
|
|
if ($LASTEXITCODE -ne 0) { throw "Code signing step failed" }
|
|
|
|
# 模拟没有开发工具 PATH 的目标电脑,防止漏包 DLL 被构建机意外补齐。
|
|
$buildProcessPath = $env:PATH
|
|
try {
|
|
$env:PATH = (Join-Path $env:SystemRoot "System32") + ";" + $env:SystemRoot
|
|
$selfCheck = Start-Process -FilePath $exe -ArgumentList "--packaging-self-check" -WorkingDirectory $appDir -PassThru -WindowStyle Hidden
|
|
}
|
|
finally {
|
|
$env:PATH = $buildProcessPath
|
|
}
|
|
try {
|
|
if (-not $selfCheck.WaitForExit(90000)) {
|
|
$selfCheck.Kill()
|
|
throw "Packaged Qt WebEngine self-check timed out"
|
|
}
|
|
if ($selfCheck.ExitCode -ne 0) {
|
|
throw ("Packaged Qt WebEngine self-check failed with exit code " + $selfCheck.ExitCode)
|
|
}
|
|
}
|
|
finally {
|
|
if ($selfCheck -and -not $selfCheck.HasExited) {
|
|
$selfCheck.Kill()
|
|
}
|
|
}
|
|
Write-Output "Packaged Qt WebEngine self-check passed"
|
|
Write-Output "Packaged real HTTPS and certificate verification checks passed (clean PATH)"
|
|
Write-Output "Packaged automatic decryption and incremental cloud upload checks passed (synthetic data, mocked transport)"
|
|
Write-Output "Packaged Messages navigation and OCR checks passed (synthetic UI, mocked input)"
|
|
Write-Output "Packaged cross-machine capture, DPI and unread click checks passed (synthetic pixels, mocked input)"
|
|
Write-Output "Packaged self-reply feedback and send guards passed (synthetic archive, mocked input)"
|
|
Write-Output "Packaged background database detection and database-first reply checks passed (synthetic data, mocked input)"
|
|
|
|
$hash = (Get-FileHash -LiteralPath $exe -Algorithm SHA256).Hash
|
|
Write-Output ("App folder: " + $appDir)
|
|
Write-Output ("EXE: " + $exe)
|
|
Write-Output ("SHA256: " + $hash)
|