#!/usr/bin/env python3 # -*- coding: utf-8 -*- r"""Java 后端**离线构建**(P12):本机 Maven 仓缺 jar 插件的运行期依赖(maven-archiver 全缺、 plexus-utils:1.1 缺),所以 `mvn package` 打不出 jar;本脚本改走 **`mvn -o compile` + 自组装 `lib/`** 的路线,产出可运行的"瘦 classes + lib"布局。 为什么还要它:用户令是"用本机 maven + 本机 JDK 1.8";本机仓是**残缺**的(离线实逮见下), 与其假装 `package` 可用,不如把可用的路走实,并把缺什么如实记下来。 用法: python app_backEnd/backend-java/build_offline.py # 编译 + 组装 lib/ + 打印启动命令 python app_backEnd/backend-java/build_offline.py --run # 编译后直接起服务(前台) python appBackEnd/backend-java/build_offline.py --port 28120 """ from __future__ import annotations import argparse import os import pathlib import re import shutil import subprocess import sys sys.stdout.reconfigure(encoding='utf-8', errors='replace') HERE = pathlib.Path(__file__).resolve().parent MAIN = 'com.guanlan.backend.GuanlanBackendApplication' def _find(env: str, *cands: str, names: tuple = ()) -> pathlib.Path | None: """按"环境变量 → 候选路径 → PATH 里的可执行名"找工具(不在仓里写死本机盘符)。""" import os import shutil if os.environ.get(env): return pathlib.Path(os.environ[env]) for c in cands: q = pathlib.Path(c) if q.exists(): return q for n in names: w = shutil.which(n) if w: return pathlib.Path(w) return None MVN = _find('GUANLAN_MVN', r'D:\Program Files\apache-maven-3.8.9\bin\mvn.cmd', names=('mvn.cmd', 'mvn')) JDK = _find('JAVA_HOME', r'D:\Program Files\Java\jdk1.8.0_202') SETTINGS = (pathlib.Path(os.environ['GUANLAN_MVN_SETTINGS']) if os.environ.get('GUANLAN_MVN_SETTINGS') else (HERE / 'settings-offline.xml')) def run_maven(args: list[str], extra: list[str] | None = None) -> tuple[int, str]: if MVN is None or JDK is None: print('[X] 找不到 maven 或 JDK —— 设 GUANLAN_MVN / JAVA_HOME 后重试') return 127, '' cmd = [str(MVN), '-s', str(SETTINGS), '-o', '-Dmaven.legacyLocalRepo=true'] + (extra or []) + args env = dict(os.environ) env['JAVA_HOME'] = str(JDK) r = subprocess.run(cmd, cwd=str(HERE), capture_output=True, text=True, errors='replace', env=env) return r.returncode, (r.stdout or '') + (r.stderr or '') def main() -> int: ap = argparse.ArgumentParser() ap.add_argument('--run', action='store_true') ap.add_argument('--port', type=int, default=28120) a = ap.parse_args() print('① 离线编译(mvn -o compile)…') rc, out = run_maven(['compile'], ['-X']) if rc != 0: tail = [l for l in out.splitlines() if l.startswith('[ERROR]')][-6:] print(' [X] 编译失败:' if 'COMPILATION ERROR' in out else ' [X] maven 失败:') for l in tail: print(' ' + l[:150]) return 1 print(' [OK] 编译通过') # ② 从 -X 输出里把"编译类路径"抽出来(这是 Maven 亲自解析出的依赖集,最可靠) cands: list[str] = [] for m in re.finditer(r'([A-Za-z]:\\[^;\r\n"\']+?\.jar)', out): cands.append(m.group(1)) for m in re.finditer(r'/root/\.m2/[^\s;:]+?\.jar', out): cands.append(m.group(0)) jars: list[str] = [] skip_roots = (str(JDK).lower(), r'd:\program files\apache-maven-3.8.9') for c in cands: p = pathlib.Path(c) if not p.is_file() or str(p) in jars: continue # JDK 自带的 rt.jar/jsse.jar… 与 Maven 自己的 plexus-classworlds 都不能进 lib/ # (JRE 在目标机上由随包 JRE 提供;混进来会把包撑大且可能与目标 JRE 冲突) if any(str(p).lower().startswith(r) for r in skip_roots): continue jars.append(str(p)) lib = HERE / 'target' / 'lib' if lib.exists(): shutil.rmtree(lib, ignore_errors=True) lib.mkdir(parents=True, exist_ok=True) for j in jars: shutil.copy2(j, lib / pathlib.Path(j).name) (HERE / 'target' / 'classpath.txt').write_text( ';'.join([str(HERE / 'target' / 'classes')] + [str(lib / pathlib.Path(j).name) for j in jars]), encoding='utf-8') print('② 依赖已组装: %s(%d 个 jar,%.1f MB)' % ( lib.relative_to(HERE).as_posix(), len(jars), sum((lib / pathlib.Path(j).name).stat().st_size for j in jars) / 1e6)) cp = (HERE / 'target' / 'classpath.txt').read_text(encoding='utf-8') java = JDK / 'bin' / 'java.exe' cmd = [str(java), '-Dfile.encoding=UTF-8', '-Dserver.port=%d' % a.port, '-cp', cp, MAIN] print('③ 启动命令:') print(' ' + ' '.join(['"%s"' % str(java)] + cmd[1:4] + ['-cp', '"%s"' % cp, MAIN])) if a.run: print(' 前台运行(Ctrl+C 结束)…') return subprocess.run(cmd).returncode print('\n(加 --run 可直接起服务;或把上面命令交给构建脚本/服务宿主)') return 0 if __name__ == '__main__': sys.exit(main())