build_offline.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. r"""Java 后端**离线构建**(P12):本机 Maven 仓缺 jar 插件的运行期依赖(maven-archiver 全缺、
  4. plexus-utils:1.1 缺),所以 `mvn package` 打不出 jar;本脚本改走
  5. **`mvn -o compile` + 自组装 `lib/`** 的路线,产出可运行的"瘦 classes + lib"布局。
  6. 为什么还要它:用户令是"用本机 maven + 本机 JDK 1.8";本机仓是**残缺**的(离线实逮见下),
  7. 与其假装 `package` 可用,不如把可用的路走实,并把缺什么如实记下来。
  8. 用法:
  9. python app_backEnd/backend-java/build_offline.py # 编译 + 组装 lib/ + 打印启动命令
  10. python app_backEnd/backend-java/build_offline.py --run # 编译后直接起服务(前台)
  11. python appBackEnd/backend-java/build_offline.py --port 28120
  12. """
  13. from __future__ import annotations
  14. import argparse
  15. import os
  16. import pathlib
  17. import re
  18. import shutil
  19. import subprocess
  20. import sys
  21. sys.stdout.reconfigure(encoding='utf-8', errors='replace')
  22. HERE = pathlib.Path(__file__).resolve().parent
  23. MAIN = 'com.guanlan.backend.GuanlanBackendApplication'
  24. def _find(env: str, *cands: str, names: tuple = ()) -> pathlib.Path | None:
  25. """按"环境变量 → 候选路径 → PATH 里的可执行名"找工具(不在仓里写死本机盘符)。"""
  26. import os
  27. import shutil
  28. if os.environ.get(env):
  29. return pathlib.Path(os.environ[env])
  30. for c in cands:
  31. q = pathlib.Path(c)
  32. if q.exists():
  33. return q
  34. for n in names:
  35. w = shutil.which(n)
  36. if w:
  37. return pathlib.Path(w)
  38. return None
  39. MVN = _find('GUANLAN_MVN', r'D:\Program Files\apache-maven-3.8.9\bin\mvn.cmd',
  40. names=('mvn.cmd', 'mvn'))
  41. JDK = _find('JAVA_HOME', r'D:\Program Files\Java\jdk1.8.0_202')
  42. SETTINGS = (pathlib.Path(os.environ['GUANLAN_MVN_SETTINGS']) if os.environ.get('GUANLAN_MVN_SETTINGS')
  43. else (HERE / 'settings-offline.xml'))
  44. def run_maven(args: list[str], extra: list[str] | None = None) -> tuple[int, str]:
  45. if MVN is None or JDK is None:
  46. print('[X] 找不到 maven 或 JDK —— 设 GUANLAN_MVN / JAVA_HOME 后重试')
  47. return 127, ''
  48. cmd = [str(MVN), '-s', str(SETTINGS), '-o', '-Dmaven.legacyLocalRepo=true'] + (extra or []) + args
  49. env = dict(os.environ)
  50. env['JAVA_HOME'] = str(JDK)
  51. r = subprocess.run(cmd, cwd=str(HERE), capture_output=True, text=True, errors='replace', env=env)
  52. return r.returncode, (r.stdout or '') + (r.stderr or '')
  53. def main() -> int:
  54. ap = argparse.ArgumentParser()
  55. ap.add_argument('--run', action='store_true')
  56. ap.add_argument('--port', type=int, default=28120)
  57. a = ap.parse_args()
  58. print('① 离线编译(mvn -o compile)…')
  59. rc, out = run_maven(['compile'], ['-X'])
  60. if rc != 0:
  61. tail = [l for l in out.splitlines() if l.startswith('[ERROR]')][-6:]
  62. print(' [X] 编译失败:' if 'COMPILATION ERROR' in out else ' [X] maven 失败:')
  63. for l in tail:
  64. print(' ' + l[:150])
  65. return 1
  66. print(' [OK] 编译通过')
  67. # ② 从 -X 输出里把"编译类路径"抽出来(这是 Maven 亲自解析出的依赖集,最可靠)
  68. cands: list[str] = []
  69. for m in re.finditer(r'([A-Za-z]:\\[^;\r\n"\']+?\.jar)', out):
  70. cands.append(m.group(1))
  71. for m in re.finditer(r'/root/\.m2/[^\s;:]+?\.jar', out):
  72. cands.append(m.group(0))
  73. jars: list[str] = []
  74. skip_roots = (str(JDK).lower(), r'd:\program files\apache-maven-3.8.9')
  75. for c in cands:
  76. p = pathlib.Path(c)
  77. if not p.is_file() or str(p) in jars:
  78. continue
  79. # JDK 自带的 rt.jar/jsse.jar… 与 Maven 自己的 plexus-classworlds 都不能进 lib/
  80. # (JRE 在目标机上由随包 JRE 提供;混进来会把包撑大且可能与目标 JRE 冲突)
  81. if any(str(p).lower().startswith(r) for r in skip_roots):
  82. continue
  83. jars.append(str(p))
  84. lib = HERE / 'target' / 'lib'
  85. if lib.exists():
  86. shutil.rmtree(lib, ignore_errors=True)
  87. lib.mkdir(parents=True, exist_ok=True)
  88. for j in jars:
  89. shutil.copy2(j, lib / pathlib.Path(j).name)
  90. (HERE / 'target' / 'classpath.txt').write_text(
  91. ';'.join([str(HERE / 'target' / 'classes')] + [str(lib / pathlib.Path(j).name) for j in jars]),
  92. encoding='utf-8')
  93. print('② 依赖已组装: %s(%d 个 jar,%.1f MB)' % (
  94. lib.relative_to(HERE).as_posix(), len(jars),
  95. sum((lib / pathlib.Path(j).name).stat().st_size for j in jars) / 1e6))
  96. cp = (HERE / 'target' / 'classpath.txt').read_text(encoding='utf-8')
  97. java = JDK / 'bin' / 'java.exe'
  98. cmd = [str(java), '-Dfile.encoding=UTF-8', '-Dserver.port=%d' % a.port, '-cp', cp, MAIN]
  99. print('③ 启动命令:')
  100. print(' ' + ' '.join(['"%s"' % str(java)] + cmd[1:4] + ['-cp', '"%s"' % cp, MAIN]))
  101. if a.run:
  102. print(' 前台运行(Ctrl+C 结束)…')
  103. return subprocess.run(cmd).returncode
  104. print('\n(加 --run 可直接起服务;或把上面命令交给构建脚本/服务宿主)')
  105. return 0
  106. if __name__ == '__main__':
  107. sys.exit(main())