版本号 1.1.0(Windows 支持是新功能) - core/protocol.py APP_VERSION、installer/version_info.txt、installer/iosloc.iss MyAppVersion、Location.spec(mac BUNDLE)、tools/make_dmg.py VERSION 全部同步到 1.1.0 - 产物命名规范:Windows 用 _win、macOS 用 _mac 后缀,Release 页面一眼分清平台 · iOSLocationTrack-1.1.0_win-setup.exe · iOSLocationTrack-1.1.0_win-portable.zip · iOSLocationTrack-1.1.0_mac.dmg README 双语化(默认中文) - docs/README-WINDOWS.en.md 新增(Windows 文档全文英文版) - docs/README-WINDOWS.md 顶部加 [中文] | [English] 语言切换 - README.md / README.en.md 的下载章节改成平台表格,说明 _win / _mac 命名 构建脚本编码自检 - scripts/build.ps1 新增第 0.5 步:检查 scripts/*.ps1 是否为 UTF-8 with BOM, 缺失则自动补回。Windows PowerShell 5.1 没有 BOM 时会按 ANSI(GBK) 解析, 中文乱码且直接语法报错(改个注释就构建不了,本项目实际踩过两次) - 本次修复:build.ps1 的 BOM 在编辑后被工具静默丢弃,已补回 验收(Windows 11 x64 / iPhone 11 iOS 26.6.1) - selftest 83/83、drift_sim 40/40、offline_tiles 13/13 - 打包版 exe 清单含 requireAdministrator,FileVersion 1.1.0.0
183 lines
7.7 KiB
PowerShell
183 lines
7.7 KiB
PowerShell
# iOS 虚拟定位轨迹工具 —— Windows 构建脚本
|
||
#
|
||
# 产出:
|
||
# dist\iOSLocationTrack\iOSLocationTrack.exe 绿色版(直接双击运行)
|
||
# dist\installer\iOSLocationTrack-1.1.0_win-setup.exe 安装包(需要 Inno Setup 6)
|
||
#
|
||
# 用法(在项目根目录):
|
||
# .\scripts\build.ps1 # 打包 exe + 安装包(若装了 Inno Setup)
|
||
# .\scripts\build.ps1 -NoInstaller # 只打包 exe
|
||
# .\scripts\build.ps1 -SkipTests # 跳过测试
|
||
#
|
||
# 前置:
|
||
# 1. 已运行过 .\scripts\run.ps1(或手动建好 .venv 并装好依赖)
|
||
# 2. 图标:resources\AppIcon.ico(缺失时自动用 tools\make_ico.py 生成)
|
||
# 3. 开发者镜像:resources\ddi\personalized\Image.dmg(缺失时自动下载)
|
||
# 4. Inno Setup 6(可选):winget install -e --id JRSoftware.InnoSetup
|
||
|
||
[CmdletBinding()]
|
||
param(
|
||
[switch]$NoInstaller,
|
||
[switch]$SkipTests,
|
||
[switch]$SkipIcon,
|
||
[switch]$SkipDdi
|
||
)
|
||
|
||
$ErrorActionPreference = 'Stop'
|
||
function Write-Bold { param($m) Write-Host "`n=== $m ===" -ForegroundColor White }
|
||
function Write-Ok { param($m) Write-Host " [OK] $m" -ForegroundColor Green }
|
||
function Write-Warn { param($m) Write-Host " [!!] $m" -ForegroundColor Yellow }
|
||
function Write-Fail { param($m) Write-Host " [XX] $m" -ForegroundColor Red }
|
||
|
||
$Root = Split-Path -Parent $PSScriptRoot
|
||
Set-Location $Root
|
||
$VenvPy = Join-Path $Root '.venv\Scripts\python.exe'
|
||
|
||
if (-not (Test-Path $VenvPy)) {
|
||
Write-Fail "找不到 $VenvPy。请先运行 .\scripts\run.ps1 创建环境。"
|
||
exit 1
|
||
}
|
||
|
||
# ---------- 0. 构建依赖 ----------
|
||
Write-Bold "0. 构建依赖"
|
||
& $VenvPy -c "import PyInstaller, PIL" 2>$null
|
||
if ($LASTEXITCODE -ne 0) {
|
||
Write-Warn "缺少 PyInstaller / Pillow,正在安装…"
|
||
$idx = if ($env:IOSLOC_PIP_INDEX) { $env:IOSLOC_PIP_INDEX } else { 'https://mirrors.aliyun.com/pypi/simple/' }
|
||
& $VenvPy -m pip install --disable-pip-version-check -i $idx pyinstaller pillow
|
||
if ($LASTEXITCODE -ne 0) { Write-Fail "构建依赖安装失败"; exit 1 }
|
||
}
|
||
Write-Ok "PyInstaller / Pillow 就绪"
|
||
|
||
# ---------- 0.5 脚本编码自检(.ps1 必须 UTF-8 with BOM)----------
|
||
# Windows PowerShell 5.1 只有看到 BOM 才按 UTF-8 解析 .ps1;否则按系统 ANSI 代码页读,
|
||
# 结果是中文全部乱码并**直接语法报错**(The string is missing the terminator),脚本根本跑不起来。
|
||
# 不少编辑器/工具保存时会静默丢掉 BOM,所以这里自动补回,避免"改个注释就构建不了"。
|
||
$bomFixed = @()
|
||
foreach ($script in Get-ChildItem (Join-Path $PSScriptRoot '*.ps1')) {
|
||
$bytes = [System.IO.File]::ReadAllBytes($script.FullName)
|
||
$hasBom = $bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF
|
||
if (-not $hasBom) {
|
||
$text = [System.IO.File]::ReadAllText($script.FullName, [System.Text.UTF8Encoding]::new($false))
|
||
[System.IO.File]::WriteAllText($script.FullName, $text, [System.Text.UTF8Encoding]::new($true))
|
||
$bomFixed += $script.Name
|
||
}
|
||
}
|
||
if ($bomFixed.Count -gt 0) {
|
||
Write-Warn "已为下列脚本补回 UTF-8 BOM:$($bomFixed -join '、')(下次编辑请保留 BOM)"
|
||
} else {
|
||
Write-Ok ".ps1 编码正常(UTF-8 with BOM)"
|
||
}
|
||
|
||
# ---------- 1. 图标 ----------
|
||
if (-not $SkipIcon) {
|
||
Write-Bold "1. Windows 图标(.ico)"
|
||
if ((Test-Path 'resources\AppIcon.ico') -and
|
||
((Get-Item 'resources\AppIcon.ico').LastWriteTime -gt (Get-Item 'resources\AppIcon.iconset').LastWriteTime)) {
|
||
Write-Ok "resources\AppIcon.ico 已是最新"
|
||
} else {
|
||
& $VenvPy tools\make_ico.py
|
||
if ($LASTEXITCODE -ne 0) { Write-Fail "图标生成失败"; exit 1 }
|
||
}
|
||
}
|
||
|
||
# ---------- 2. 开发者镜像 ----------
|
||
if (-not $SkipDdi) {
|
||
Write-Bold "2. 开发者镜像素材"
|
||
$imageDmg = 'resources\ddi\personalized\Image.dmg'
|
||
if (Test-Path $imageDmg) {
|
||
$mb = [math]::Round((Get-Item $imageDmg).Length / 1MB, 2)
|
||
Write-Ok "$imageDmg 已存在($mb MB)"
|
||
} else {
|
||
Write-Warn "$imageDmg 缺失(仓库中不入库),正在下载…"
|
||
& $VenvPy tools\fetch_ddi.py --personalized-only
|
||
if (-not (Test-Path $imageDmg)) {
|
||
Write-Fail "镜像下载失败。iOS 17+ 挂载会退化为在线获取,打包产物不完整。"
|
||
Write-Fail "可稍后重试:$VenvPy tools\fetch_ddi.py --personalized-only"
|
||
exit 1
|
||
}
|
||
}
|
||
}
|
||
|
||
# ---------- 3. 测试 ----------
|
||
if (-not $SkipTests) {
|
||
Write-Bold "3. 测试(无需设备)"
|
||
$failed = 0
|
||
foreach ($t in @('tests\selftest.py', 'tests\drift_sim.py', 'tests\offline_tiles.py')) {
|
||
Write-Host " --- $t"
|
||
& $VenvPy $t 2>&1 | Select-Object -Last 3
|
||
if ($LASTEXITCODE -ne 0) { $failed++; Write-Fail "$t 失败" }
|
||
}
|
||
if ($failed -gt 0) { Write-Fail "$failed 个测试套件失败,已中止打包"; exit 1 }
|
||
Write-Ok "全部测试通过"
|
||
}
|
||
|
||
# ---------- 4. PyInstaller ----------
|
||
Write-Bold "4. 打包 exe(onedir + requireAdministrator 清单)"
|
||
if (Test-Path 'dist\iOSLocationTrack') { Remove-Item 'dist\iOSLocationTrack' -Recurse -Force }
|
||
& $VenvPy -m PyInstaller --clean --noconfirm Location.spec
|
||
if ($LASTEXITCODE -ne 0) { Write-Fail "PyInstaller 打包失败"; exit 1 }
|
||
|
||
$exe = 'dist\iOSLocationTrack\iOSLocationTrack.exe'
|
||
if (-not (Test-Path $exe)) { Write-Fail "未生成 $exe"; exit 1 }
|
||
$sizeMb = [math]::Round(((Get-ChildItem 'dist\iOSLocationTrack' -Recurse -File |
|
||
Measure-Object Length -Sum).Sum / 1MB), 1)
|
||
Write-Ok "$exe(整包 $sizeMb MB)"
|
||
|
||
# ---------- 5. 校验清单里的 requireAdministrator ----------
|
||
Write-Bold "5. 校验 exe 是否请求管理员权限"
|
||
$manifestOk = $false
|
||
try {
|
||
# manifest 嵌在 exe 资源里;用 findstr 直接找 UTF-8 明文即可
|
||
$bytes = [System.IO.File]::ReadAllBytes((Resolve-Path $exe))
|
||
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
|
||
if ($text -match 'requireAdministrator') {
|
||
$manifestOk = $true
|
||
Write-Ok "清单包含 requireAdministrator(启动时会弹 UAC)"
|
||
}
|
||
} catch { }
|
||
if (-not $manifestOk) {
|
||
Write-Warn "未能在 exe 中确认 requireAdministrator —— 请手动确认 Location.spec 的 uac_admin=True"
|
||
}
|
||
|
||
# ---------- 6. Inno Setup ----------
|
||
if ($NoInstaller) {
|
||
Write-Bold "6. 跳过安装包(-NoInstaller)"
|
||
} else {
|
||
Write-Bold "6. 制作安装包(Inno Setup)"
|
||
$iscc = $null
|
||
foreach ($candidate in @(
|
||
"${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe",
|
||
"$env:ProgramFiles\Inno Setup 6\ISCC.exe",
|
||
"$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe",
|
||
"$env:LOCALAPPDATA\Inno Setup 6\ISCC.exe"
|
||
)) {
|
||
if ($candidate -and (Test-Path $candidate)) { $iscc = $candidate; break }
|
||
}
|
||
if (-not $iscc) {
|
||
$cmd = Get-Command ISCC.exe -ErrorAction SilentlyContinue
|
||
if ($cmd) { $iscc = $cmd.Source }
|
||
}
|
||
if (-not $iscc) {
|
||
Write-Warn "未找到 Inno Setup 6,跳过安装包。"
|
||
Write-Warn "安装:winget install -e --id JRSoftware.InnoSetup"
|
||
Write-Warn "然后重跑:.\scripts\build.ps1 -SkipTests -SkipIcon -SkipDdi"
|
||
} else {
|
||
Write-Host " 使用 $iscc"
|
||
& $iscc 'installer\iosloc.iss'
|
||
if ($LASTEXITCODE -ne 0) { Write-Fail "Inno Setup 编译失败"; exit 1 }
|
||
Get-ChildItem 'dist\installer\*.exe' | ForEach-Object {
|
||
Write-Ok "$($_.FullName)($([math]::Round($_.Length/1MB,1)) MB)"
|
||
}
|
||
}
|
||
}
|
||
|
||
Write-Bold "完成"
|
||
Write-Host " 绿色版:dist\iOSLocationTrack\iOSLocationTrack.exe"
|
||
if (Test-Path 'dist\installer') {
|
||
Get-ChildItem 'dist\installer\*.exe' -ErrorAction SilentlyContinue | ForEach-Object {
|
||
Write-Host " 安装包:$($_.FullName)"
|
||
}
|
||
}
|
||
Write-Host "`n 提示:exe 启动时会请求管理员权限(用于首次自动安装 Apple Mobile Device Support)。"
|