build-win.ps1 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. <#
  2. .SYNOPSIS
  3. 在 Windows 上把 Parking Simulator 打包成免安装的 app-image(含 zip)。
  4. .DESCRIPTION
  5. 产物路径:dist\win\ParkingSimulator\ (目录形式,双击 ParkingSimulator.exe 直接运行)
  6. dist\win\ParkingSimulator-win-x64.zip(同上目录的 zip 压缩包,便于分发)
  7. 依赖:
  8. - JDK 25(含 jlink / jpackage / javac / jar)
  9. - Maven 3.9+
  10. 进阶:如果想生成 .exe 安装向导,请先安装 WiX Toolset 3.x,再放开文件末尾被注释的那段。
  11. .EXAMPLE
  12. PS> .\build-win.ps1
  13. #>
  14. $ErrorActionPreference = "Stop"
  15. $OriginalLocation = Get-Location
  16. try {
  17. Set-Location (Join-Path $PSScriptRoot "..")
  18. $AppName = "ParkingSimulator"
  19. $AppVersion = "1.0.0"
  20. $AppVendor = "Fujica"
  21. $MainModule = "com.fujica.parkingtool"
  22. $MainClass = "com.fujica.parkingtool.App"
  23. $OutDir = "dist\win"
  24. $RuntimeDir = "target\runtime-win"
  25. function Require-Cmd([string]$Name) {
  26. if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
  27. throw "缺少命令:$Name"
  28. }
  29. }
  30. Require-Cmd mvn
  31. Require-Cmd jlink
  32. Require-Cmd jpackage
  33. Require-Cmd javac
  34. Require-Cmd jar
  35. # 校验当前会话用的是 JDK 25,避免误用低版本 javac 编译失败
  36. # 报错样例:Fatal error compiling: 无效的标记: --module-path
  37. $JavacVer = (& javac -version 2>&1) | Out-String
  38. if ($JavacVer -notmatch 'javac\s+25(\.|$)') {
  39. throw @"
  40. 当前 javac 不是 JDK 25(实际:$($JavacVer.Trim()))。
  41. 请将 JAVA_HOME 指向 JDK 25 后再次运行:
  42. `$env:JAVA_HOME = 'C:\Program Files\Eclipse Adoptium\jdk-25.x.x-hotspot'
  43. `$env:Path = "`$env:JAVA_HOME\bin;`$env:Path"
  44. JDK 25 下载:https://adoptium.net/zh-CN/temurin/releases
  45. "@
  46. }
  47. Write-Host " javac: $($JavacVer.Trim())"
  48. Write-Host "==> [1/5] Maven 构建 + 拉取依赖到 target\modules"
  49. & mvn -q -DskipTests clean package
  50. if ($LASTEXITCODE -ne 0) { throw "Maven 构建失败" }
  51. $ModulePath = "target\modules"
  52. # 防御性清理:删掉 OpenJFX 偶尔会带过来的 < 1KB 空 stub jar
  53. Get-ChildItem -Path $ModulePath -Filter "*.jar" |
  54. Where-Object { $_.Length -lt 1024 } |
  55. ForEach-Object {
  56. Write-Host " 清理空 stub: $($_.Name)"
  57. Remove-Item -Force $_.FullName
  58. }
  59. # ---------------------------------------------------------------------------
  60. # 给 Paho v3(automatic module)补一份真正的 module-info,否则 jlink 拒绝它。
  61. # 必须包含:
  62. # - uses org.eclipse.paho.client.mqttv3.spi.NetworkModuleFactory
  63. # - provides ... TCP / SSL / WebSocket / WebSocketSecure 4 个工厂
  64. # 否则运行期会抛 ServiceConfigurationError: module ... does not declare `uses`
  65. # ---------------------------------------------------------------------------
  66. Write-Host "==> [2/5] 给 Paho jar 注入 module-info"
  67. $PahoJarFile = Get-ChildItem -Path $ModulePath -Filter "org.eclipse.paho.client.mqttv3-*.jar" |
  68. Select-Object -First 1
  69. if (-not $PahoJarFile) {
  70. throw "找不到 Paho mqttv3 jar 在 $ModulePath\"
  71. }
  72. $PahoJar = $PahoJarFile.FullName # 绝对路径,子目录内仍可引用
  73. $PatchTmp = Join-Path $env:TEMP ("paho-modinfo-" + [System.IO.Path]::GetRandomFileName())
  74. New-Item -ItemType Directory -Path $PatchTmp | Out-Null
  75. try {
  76. $ExpDir = Join-Path $PatchTmp "exploded"
  77. New-Item -ItemType Directory -Path $ExpDir | Out-Null
  78. Push-Location $ExpDir
  79. & jar xf $PahoJar
  80. $JarExtract = $LASTEXITCODE
  81. Pop-Location
  82. if ($JarExtract -ne 0) { throw "解压 Paho jar 失败" }
  83. $ModuleInfo = @"
  84. module org.eclipse.paho.client.mqttv3 {
  85. requires transitive java.logging;
  86. requires transitive java.prefs;
  87. exports org.eclipse.paho.client.mqttv3;
  88. exports org.eclipse.paho.client.mqttv3.internal;
  89. exports org.eclipse.paho.client.mqttv3.internal.security;
  90. exports org.eclipse.paho.client.mqttv3.internal.websocket;
  91. exports org.eclipse.paho.client.mqttv3.internal.wire;
  92. exports org.eclipse.paho.client.mqttv3.logging;
  93. exports org.eclipse.paho.client.mqttv3.persist;
  94. exports org.eclipse.paho.client.mqttv3.spi;
  95. exports org.eclipse.paho.client.mqttv3.util;
  96. uses org.eclipse.paho.client.mqttv3.spi.NetworkModuleFactory;
  97. provides org.eclipse.paho.client.mqttv3.spi.NetworkModuleFactory with
  98. org.eclipse.paho.client.mqttv3.internal.TCPNetworkModuleFactory,
  99. org.eclipse.paho.client.mqttv3.internal.SSLNetworkModuleFactory,
  100. org.eclipse.paho.client.mqttv3.internal.websocket.WebSocketNetworkModuleFactory,
  101. org.eclipse.paho.client.mqttv3.internal.websocket.WebSocketSecureNetworkModuleFactory;
  102. }
  103. "@
  104. $ModuleInfoFile = Join-Path $PatchTmp "module-info.java"
  105. Set-Content -Path $ModuleInfoFile -Value $ModuleInfo -Encoding ASCII
  106. & javac --patch-module "org.eclipse.paho.client.mqttv3=$ExpDir" -d $PatchTmp $ModuleInfoFile
  107. if ($LASTEXITCODE -ne 0) { throw "javac 生成 module-info.class 失败" }
  108. Push-Location $PatchTmp
  109. & jar uf $PahoJar "module-info.class"
  110. $JarUpdate = $LASTEXITCODE
  111. Pop-Location
  112. if ($JarUpdate -ne 0) { throw "回写 Paho jar 失败" }
  113. Write-Host (" Paho 已注入 module-info → " + $PahoJarFile.Name)
  114. } finally {
  115. Remove-Item -Recurse -Force $PatchTmp -ErrorAction SilentlyContinue
  116. }
  117. # 自动发现需要进入 runtime 的模块集合
  118. $RuntimeModules = @(
  119. $MainModule,
  120. "javafx.base", "javafx.graphics", "javafx.controls", "javafx.swing",
  121. "org.eclipse.paho.client.mqttv3",
  122. "jdk.localedata", "jdk.crypto.ec", "jdk.crypto.cryptoki"
  123. ) -join ","
  124. Write-Host "==> [3/5] jlink 生成最小运行时 → $RuntimeDir"
  125. if (Test-Path $RuntimeDir) { Remove-Item -Recurse -Force $RuntimeDir }
  126. & jlink `
  127. --module-path "$env:JAVA_HOME\jmods;$ModulePath" `
  128. --add-modules $RuntimeModules `
  129. --no-header-files `
  130. --no-man-pages `
  131. --strip-debug `
  132. --compress=zip-9 `
  133. --ignore-signing-information `
  134. --output $RuntimeDir
  135. if ($LASTEXITCODE -ne 0) { throw "jlink 失败" }
  136. Write-Host "==> [4/5] jpackage 生成 app-image → $OutDir"
  137. if (Test-Path $OutDir) { Remove-Item -Recurse -Force $OutDir }
  138. New-Item -ItemType Directory -Path $OutDir | Out-Null
  139. # ---------------------------------------------------------------------------
  140. # 准备 Windows 图标:
  141. # - 期望 src\main\resources\icon.ico(多分辨率:16 / 32 / 48 / 256)
  142. # - 如果只有 icon.png,建议先用 ImageMagick 等工具转换:
  143. # magick icon.png -define icon:auto-resize=256,128,64,48,32,16 icon.ico
  144. # ---------------------------------------------------------------------------
  145. $IconArgs = @()
  146. if (Test-Path "src\main\resources\icon.ico") {
  147. $IconArgs = @("--icon", "src\main\resources\icon.ico")
  148. Write-Host " 使用图标:src\main\resources\icon.ico"
  149. } elseif (Test-Path "src\main\resources\com\fujica\parkingtool\icon.ico") {
  150. $IconArgs = @("--icon", "src\main\resources\com\fujica\parkingtool\icon.ico")
  151. Write-Host " 使用图标:src\main\resources\com\fujica\parkingtool\icon.ico"
  152. } elseif (Test-Path "src\main\resources\icon.png") {
  153. Write-Host " ⚠️ Windows 需要 .ico 格式,未找到 icon.ico,本次将使用默认图标"
  154. Write-Host " 可用 ImageMagick 生成:magick icon.png -define icon:auto-resize=256,128,64,48,32,16 icon.ico"
  155. }
  156. $JpackageArgs = @(
  157. "--type", "app-image",
  158. "--name", $AppName,
  159. "--app-version", $AppVersion,
  160. "--vendor", $AppVendor,
  161. "--module-path", $ModulePath,
  162. "--module", "$MainModule/$MainClass",
  163. "--runtime-image", $RuntimeDir,
  164. "--dest", $OutDir,
  165. "--java-options", "-Xms128m",
  166. "--java-options", "-Xmx1024m",
  167. "--java-options", "-Dprism.lcdtext=false",
  168. "--java-options", "--enable-native-access=javafx.graphics"
  169. ) + $IconArgs
  170. & jpackage @JpackageArgs
  171. if ($LASTEXITCODE -ne 0) { throw "jpackage 失败" }
  172. Write-Host "==> [5/5] 压缩成 zip 便于分发"
  173. $ZipName = "$AppName-win-x64.zip"
  174. $ZipPath = Join-Path $OutDir $ZipName
  175. if (Test-Path $ZipPath) { Remove-Item -Force $ZipPath }
  176. Compress-Archive -Path (Join-Path $OutDir $AppName) -DestinationPath $ZipPath -CompressionLevel Optimal
  177. $AppDir = Join-Path $OutDir $AppName
  178. $AppExe = Join-Path $AppDir "$AppName.exe"
  179. $AppSize = "{0:N1} MB" -f ((Get-ChildItem -Recurse $AppDir | Measure-Object -Property Length -Sum).Sum / 1MB)
  180. $ZipSize = "{0:N1} MB" -f ((Get-Item $ZipPath).Length / 1MB)
  181. Write-Host ""
  182. Write-Host "✅ 完成"
  183. Write-Host " app: $AppDir ($AppSize)"
  184. Write-Host " exe: $AppExe"
  185. Write-Host " zip: $ZipPath ($ZipSize)"
  186. Write-Host " 解压 zip 后双击 $AppName.exe 即可运行,无需安装 JDK。"
  187. # === 可选:生成 .exe 安装向导(需要预装 WiX Toolset 3.x,并把 candle/light 加到 PATH) ===
  188. # $InstallerArgs = @(
  189. # "--type", "exe",
  190. # "--name", $AppName,
  191. # "--app-version", $AppVersion,
  192. # "--vendor", $AppVendor,
  193. # "--module-path", $ModulePath,
  194. # "--module", "$MainModule/$MainClass",
  195. # "--runtime-image", $RuntimeDir,
  196. # "--dest", $OutDir,
  197. # "--win-dir-chooser",
  198. # "--win-shortcut",
  199. # "--win-menu",
  200. # "--win-menu-group", $AppVendor
  201. # ) + $IconArgs
  202. # & jpackage @InstallerArgs
  203. } finally {
  204. Set-Location $OriginalLocation
  205. }