build-win.ps1 9.7 KB

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