| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- <#
- .SYNOPSIS
- 在 Windows 上把 Parking Simulator 打包成免安装的 app-image(含 zip)。
- .DESCRIPTION
- 产物路径:dist\win\ParkingSimulator\ (目录形式,双击 ParkingSimulator.exe 直接运行)
- dist\win\ParkingSimulator-win-x64.zip(同上目录的 zip 压缩包,便于分发)
- 依赖:
- - JDK 25(含 jlink / jpackage / javac / jar)
- - Maven 3.9+
- 进阶:如果想生成 .exe 安装向导,请先安装 WiX Toolset 3.x,再放开文件末尾被注释的那段。
- .EXAMPLE
- PS> .\build-win.ps1
- #>
- $ErrorActionPreference = "Stop"
- $OriginalLocation = Get-Location
- try {
- Set-Location (Join-Path $PSScriptRoot "..")
- $AppName = "ParkingSimulator"
- $AppVersion = "1.0.0"
- $AppVendor = "Fujica"
- $MainModule = "com.fujica.parkingtool"
- $MainClass = "com.fujica.parkingtool.App"
- $OutDir = "dist\win"
- $RuntimeDir = "target\runtime-win"
- function Require-Cmd([string]$Name) {
- if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
- throw "缺少命令:$Name"
- }
- }
- Require-Cmd mvn
- Require-Cmd jlink
- Require-Cmd jpackage
- Require-Cmd javac
- Require-Cmd jar
- # 校验当前会话用的是 JDK 25,避免误用低版本 javac 编译失败
- # 报错样例:Fatal error compiling: 无效的标记: --module-path
- $JavacVer = ((& javac -version 2>&1) | Out-String).Trim()
- if ($JavacVer -notmatch 'javac\s+25(\.|$)') {
- $msg = "当前 javac 不是 JDK 25(实际:$JavacVer)。`r`n" +
- "请将 JAVA_HOME 指向 JDK 25 后再次运行:`r`n" +
- " `$env:JAVA_HOME = 'C:\Program Files\Eclipse Adoptium\jdk-25.x.x-hotspot'`r`n" +
- " `$env:Path = `"`$env:JAVA_HOME\bin;`$env:Path`"`r`n" +
- "JDK 25 下载:https://adoptium.net/zh-CN/temurin/releases"
- throw $msg
- }
- Write-Host " javac: $JavacVer"
- Write-Host "==> [1/5] Maven 构建 + 拉取依赖到 target\modules"
- & mvn -q -DskipTests clean package
- if ($LASTEXITCODE -ne 0) { throw "Maven 构建失败" }
- $ModulePath = "target\modules"
- # 防御性清理:删掉 OpenJFX 偶尔会带过来的 < 1KB 空 stub jar
- Get-ChildItem -Path $ModulePath -Filter "*.jar" |
- Where-Object { $_.Length -lt 1024 } |
- ForEach-Object {
- Write-Host " 清理空 stub: $($_.Name)"
- Remove-Item -Force $_.FullName
- }
- # ---------------------------------------------------------------------------
- # 给 Paho v3(automatic module)补一份真正的 module-info,否则 jlink 拒绝它。
- # 必须包含:
- # - uses org.eclipse.paho.client.mqttv3.spi.NetworkModuleFactory
- # - provides ... TCP / SSL / WebSocket / WebSocketSecure 4 个工厂
- # 否则运行期会抛 ServiceConfigurationError: module ... does not declare `uses`
- # ---------------------------------------------------------------------------
- Write-Host "==> [2/5] 给 Paho jar 注入 module-info"
- $PahoJarFile = Get-ChildItem -Path $ModulePath -Filter "org.eclipse.paho.client.mqttv3-*.jar" |
- Select-Object -First 1
- if (-not $PahoJarFile) {
- throw "找不到 Paho mqttv3 jar 在 $ModulePath\"
- }
- $PahoJar = $PahoJarFile.FullName # 绝对路径,子目录内仍可引用
- $PatchTmp = Join-Path $env:TEMP ("paho-modinfo-" + [System.IO.Path]::GetRandomFileName())
- New-Item -ItemType Directory -Path $PatchTmp | Out-Null
- try {
- $ExpDir = Join-Path $PatchTmp "exploded"
- New-Item -ItemType Directory -Path $ExpDir | Out-Null
- Push-Location $ExpDir
- & jar xf $PahoJar
- $JarExtract = $LASTEXITCODE
- Pop-Location
- if ($JarExtract -ne 0) { throw "解压 Paho jar 失败" }
- $ModuleInfo = @"
- module org.eclipse.paho.client.mqttv3 {
- requires transitive java.logging;
- requires transitive java.prefs;
- exports org.eclipse.paho.client.mqttv3;
- exports org.eclipse.paho.client.mqttv3.internal;
- exports org.eclipse.paho.client.mqttv3.internal.security;
- exports org.eclipse.paho.client.mqttv3.internal.websocket;
- exports org.eclipse.paho.client.mqttv3.internal.wire;
- exports org.eclipse.paho.client.mqttv3.logging;
- exports org.eclipse.paho.client.mqttv3.persist;
- exports org.eclipse.paho.client.mqttv3.spi;
- exports org.eclipse.paho.client.mqttv3.util;
- uses org.eclipse.paho.client.mqttv3.spi.NetworkModuleFactory;
- provides org.eclipse.paho.client.mqttv3.spi.NetworkModuleFactory with
- org.eclipse.paho.client.mqttv3.internal.TCPNetworkModuleFactory,
- org.eclipse.paho.client.mqttv3.internal.SSLNetworkModuleFactory,
- org.eclipse.paho.client.mqttv3.internal.websocket.WebSocketNetworkModuleFactory,
- org.eclipse.paho.client.mqttv3.internal.websocket.WebSocketSecureNetworkModuleFactory;
- }
- "@
- $ModuleInfoFile = Join-Path $PatchTmp "module-info.java"
- Set-Content -Path $ModuleInfoFile -Value $ModuleInfo -Encoding ASCII
- & javac --patch-module "org.eclipse.paho.client.mqttv3=$ExpDir" -d $PatchTmp $ModuleInfoFile
- if ($LASTEXITCODE -ne 0) { throw "javac 生成 module-info.class 失败" }
- Push-Location $PatchTmp
- & jar uf $PahoJar "module-info.class"
- $JarUpdate = $LASTEXITCODE
- Pop-Location
- if ($JarUpdate -ne 0) { throw "回写 Paho jar 失败" }
- Write-Host (" Paho 已注入 module-info → " + $PahoJarFile.Name)
- } finally {
- Remove-Item -Recurse -Force $PatchTmp -ErrorAction SilentlyContinue
- }
- # 自动发现需要进入 runtime 的模块集合
- $RuntimeModules = @(
- $MainModule,
- "javafx.base", "javafx.graphics", "javafx.controls", "javafx.swing",
- "org.eclipse.paho.client.mqttv3",
- "jdk.localedata", "jdk.crypto.ec", "jdk.crypto.cryptoki"
- ) -join ","
- Write-Host "==> [3/5] jlink 生成最小运行时 → $RuntimeDir"
- if (Test-Path $RuntimeDir) { Remove-Item -Recurse -Force $RuntimeDir }
- & jlink `
- --module-path "$env:JAVA_HOME\jmods;$ModulePath" `
- --add-modules $RuntimeModules `
- --no-header-files `
- --no-man-pages `
- --strip-debug `
- --compress=zip-9 `
- --ignore-signing-information `
- --output $RuntimeDir
- if ($LASTEXITCODE -ne 0) { throw "jlink 失败" }
- Write-Host "==> [4/5] jpackage 生成 app-image → $OutDir"
- if (Test-Path $OutDir) { Remove-Item -Recurse -Force $OutDir }
- New-Item -ItemType Directory -Path $OutDir | Out-Null
- # ---------------------------------------------------------------------------
- # 准备 Windows 图标:
- # - 期望 src\main\resources\icon.ico(多分辨率:16 / 32 / 48 / 256)
- # - 如果只有 icon.png,建议先用 ImageMagick 等工具转换:
- # magick icon.png -define icon:auto-resize=256,128,64,48,32,16 icon.ico
- # ---------------------------------------------------------------------------
- $IconArgs = @()
- if (Test-Path "src\main\resources\icon.ico") {
- $IconArgs = @("--icon", "src\main\resources\icon.ico")
- Write-Host " 使用图标:src\main\resources\icon.ico"
- } elseif (Test-Path "src\main\resources\com\fujica\parkingtool\icon.ico") {
- $IconArgs = @("--icon", "src\main\resources\com\fujica\parkingtool\icon.ico")
- Write-Host " 使用图标:src\main\resources\com\fujica\parkingtool\icon.ico"
- } elseif (Test-Path "src\main\resources\icon.png") {
- Write-Host " ⚠️ Windows 需要 .ico 格式,未找到 icon.ico,本次将使用默认图标"
- Write-Host " 可用 ImageMagick 生成:magick icon.png -define icon:auto-resize=256,128,64,48,32,16 icon.ico"
- }
- $JpackageArgs = @(
- "--type", "app-image",
- "--name", $AppName,
- "--app-version", $AppVersion,
- "--vendor", $AppVendor,
- "--module-path", $ModulePath,
- "--module", "$MainModule/$MainClass",
- "--runtime-image", $RuntimeDir,
- "--dest", $OutDir,
- "--java-options", "-Xms128m",
- "--java-options", "-Xmx1024m",
- "--java-options", "-Dprism.lcdtext=false",
- "--java-options", "--enable-native-access=javafx.graphics"
- ) + $IconArgs
- & jpackage @JpackageArgs
- if ($LASTEXITCODE -ne 0) { throw "jpackage 失败" }
- Write-Host "==> [5/5] 压缩成 zip 便于分发"
- $ZipName = "$AppName-win-x64.zip"
- $ZipPath = Join-Path $OutDir $ZipName
- if (Test-Path $ZipPath) { Remove-Item -Force $ZipPath }
- Compress-Archive -Path (Join-Path $OutDir $AppName) -DestinationPath $ZipPath -CompressionLevel Optimal
- $AppDir = Join-Path $OutDir $AppName
- $AppExe = Join-Path $AppDir "$AppName.exe"
- $AppSize = "{0:N1} MB" -f ((Get-ChildItem -Recurse $AppDir | Measure-Object -Property Length -Sum).Sum / 1MB)
- $ZipSize = "{0:N1} MB" -f ((Get-Item $ZipPath).Length / 1MB)
- Write-Host ""
- Write-Host "✅ 完成"
- Write-Host " app: $AppDir ($AppSize)"
- Write-Host " exe: $AppExe"
- Write-Host " zip: $ZipPath ($ZipSize)"
- Write-Host " 解压 zip 后双击 $AppName.exe 即可运行,无需安装 JDK。"
- # === 可选:生成 .exe 安装向导(需要预装 WiX Toolset 3.x,并把 candle/light 加到 PATH) ===
- # $InstallerArgs = @(
- # "--type", "exe",
- # "--name", $AppName,
- # "--app-version", $AppVersion,
- # "--vendor", $AppVendor,
- # "--module-path", $ModulePath,
- # "--module", "$MainModule/$MainClass",
- # "--runtime-image", $RuntimeDir,
- # "--dest", $OutDir,
- # "--win-dir-chooser",
- # "--win-shortcut",
- # "--win-menu",
- # "--win-menu-group", $AppVendor
- # ) + $IconArgs
- # & jpackage @InstallerArgs
- } finally {
- Set-Location $OriginalLocation
- }
|