#!/usr/bin/env python3 """ 统一构建脚本 — 合并 build_v6~v11 为单入口。 用法: python build.py # 基于最新 jar 自动递增版本号 python build.py --version 12 # 指定输出版本号 python build.py --base path/to/base.jar # 指定基底 jar 功能: 1. 收集 lib/ 下所有 jar 作为 classpath(首次运行自动从基底 jar 解压) 2. 用 javac 编译 src/main/java 下全部源码 3. 以基底 fat jar 为模板,替换 BOOT-INF/classes 下的 class 和资源文件, 并补打基底中不存在的新增类 / 资源 4. 输出 target/resource-platform-v{N}.jar """ import zipfile, os, subprocess, glob, sys, shutil, argparse, re # ── 路径常量 ────────────────────────────────────────────── BASE = os.path.dirname(os.path.abspath(__file__)) SRC = os.path.join(BASE, "src", "main", "java") RES = os.path.join(BASE, "src", "main", "resources") OUT = os.path.join(BASE, "build_tmp_classes") LIB = os.path.join(BASE, "lib") TARGET = os.path.join(BASE, "target") JAVAC = r"D:\Program Files\Java\jdk-17\bin\javac.exe" LOMBOK = r"C:\Users\BXD-XTYW\.m2\repository\org\projectlombok\lombok\1.18.32\lombok-1.18.32.jar" # ── 辅助函数 ────────────────────────────────────────────── def find_latest_version(): """扫描 target/ 目录,返回已有的最高版本号;无则返回 0。""" versions = [] pattern = re.compile(r"resource-platform-v(\d+)\.jar$") if os.path.isdir(TARGET): for f in os.listdir(TARGET): m = pattern.match(f) if m: versions.append(int(m.group(1))) return max(versions) if versions else 0 def find_base_jar(current_version): """ 按优先级查找基底 jar: 1. target/resource-platform-v{N}.jar(最高版本) 2. target/resource-platform.jar(Maven 产出,无版本号) """ if current_version > 0: versioned = os.path.join(TARGET, f"resource-platform-v{current_version}.jar") if os.path.isfile(versioned): return versioned # 回退到 Maven 产出的无版本号 jar maven_jar = os.path.join(TARGET, "resource-platform.jar") if os.path.isfile(maven_jar): return maven_jar return None def extract_libs_from_jar(jar_path): """从 fat jar 解压 BOOT-INF/lib/*.jar 到 lib/ 目录。""" os.makedirs(LIB, exist_ok=True) with zipfile.ZipFile(jar_path) as z: for name in z.namelist(): if name.startswith("BOOT-INF/lib/") and name.endswith(".jar"): out = os.path.join(LIB, os.path.basename(name)) with open(out, "wb") as f: f.write(z.read(name)) def ensure_lombok(): """确保 lombok jar 存在于 lib/ 目录。""" dst = os.path.join(LIB, "lombok-1.18.32.jar") if not os.path.exists(dst) and os.path.exists(LOMBOK): shutil.copy(LOMBOK, dst) def collect_classpath(): """收集 lib/ 下所有 jar,拼成 classpath 字符串。""" dep_jars = sorted(glob.glob(os.path.join(LIB, "*.jar"))) print(f"classpath entries: {len(dep_jars)}") return ";".join(dep_jars) def compile_sources(cp): """编译 src/main/java 下全部 .java 到 build_tmp_classes/。""" sources = [] for root, _, files in os.walk(SRC): for f in files: if f.endswith(".java"): sources.append(os.path.join(root, f)) print(f"source files: {len(sources)}") os.makedirs(OUT, exist_ok=True) cmd = [JAVAC, "-parameters", "-encoding", "UTF-8", "-cp", cp, "-d", OUT] + sources r = subprocess.run(cmd, capture_output=True) print(f"javac returncode: {r.returncode}") if r.returncode != 0: err = (r.stderr or b"").decode("utf-8", "replace") print("=== STDERR (first 60 lines) ===") print("\n".join(err.splitlines()[:60])) sys.exit(1) class_count = len(glob.glob(os.path.join(OUT, "**", "*.class"), recursive=True)) print(f"compiled classes: {class_count}") return class_count def repackage(base_jar, dst_jar): """ 以 base_jar 为基底,替换 / 补打 class 和资源文件,生成 dst_jar。 逻辑合并自 build_v7(资源注入)+ build_v8(新增类补打)。 """ with zipfile.ZipFile(base_jar) as zin: infos = zin.infolist() all_names = set(zin.namelist()) def local_for(entry_name): # 已编译 class: 优先用本地编译产物 if entry_name.startswith("BOOT-INF/classes/") and entry_name.endswith(".class"): rel = entry_name[len("BOOT-INF/classes/"):] local = os.path.join(OUT, rel) if os.path.isfile(local): return local return None # 资源文件: 用 src/main/resources 中的版本 if entry_name.startswith("BOOT-INF/classes/"): rel = entry_name[len("BOOT-INF/classes/"):] local = os.path.join(RES, rel) if os.path.isfile(local): return local return None manifest_name = "META-INF/MANIFEST.MF" # 收集 src/main/resources 中新增的、基底 jar 里没有的资源条目 extra_resources = [] for root, _, files in os.walk(RES): for f in files: full = os.path.join(root, f) rel = os.path.relpath(full, RES).replace("\\", "/") entry = "BOOT-INF/classes/" + rel if entry not in all_names: extra_resources.append((entry, full)) # 收集本地编译产物中新增的、基底 jar 里不存在的 class extra_classes = [] for root, _, files in os.walk(OUT): for f in files: if f.endswith(".class"): full = os.path.join(root, f) rel = os.path.relpath(full, OUT).replace("\\", "/") entry = "BOOT-INF/classes/" + rel if entry not in all_names: extra_classes.append((entry, full)) with zipfile.ZipFile(dst_jar, "w", compression=zipfile.ZIP_DEFLATED) as zout: # MANIFEST 必须第一个写入 mi = next(i for i in infos if i.filename == manifest_name) zout.writestr(mi, zin.read(manifest_name)) for i in infos: if i.filename == manifest_name: continue local = local_for(i.filename) if local: with open(local, "rb") as lf: data = lf.read() zi = zipfile.ZipInfo(i.filename) zi.external_attr = i.external_attr zi.date_time = i.date_time zout.writestr(zi, data) else: zout.writestr(i, zin.read(i.filename)) # 补打新增资源 for entry, full in extra_resources: with open(full, "rb") as lf: data = lf.read() zout.writestr(zipfile.ZipInfo(entry), data) # 补打新增 class for entry, full in extra_classes: with open(full, "rb") as lf: data = lf.read() zout.writestr(zipfile.ZipInfo(entry), data) # 校验 zm = zipfile.ZipFile(dst_jar) print(f"manifest first: {zm.namelist()[0] == manifest_name}") print(f"extra resources injected: {len(extra_resources)}") print(f"extra classes injected: {len(extra_classes)}") return zm # ── 主流程 ──────────────────────────────────────────────── def main(): parser = argparse.ArgumentParser(description="统一构建脚本") parser.add_argument("--version", type=int, help="指定输出版本号(默认自动递增)") parser.add_argument("--base", help="指定基底 jar 路径(默认使用最新版本)") args = parser.parse_args() # 1. 确定版本号 latest = find_latest_version() if args.version: new_version = args.version else: new_version = latest + 1 # 2. 确定基底 jar if args.base: base_jar = args.base else: base_jar = find_base_jar(latest) if not base_jar or not os.path.isfile(base_jar): print(f"ERROR: 基底 jar 不存在: {base_jar}") print("请先通过 'mvn clean package -DskipTests' 生成初始 jar,") print("或使用 --base 参数指定基底 jar 路径。") sys.exit(1) dst_jar = os.path.join(TARGET, f"resource-platform-v{new_version}.jar") print(f"base jar: {base_jar}") print(f"output: {dst_jar}") print(f"version: {new_version}") print() # 3. 准备 classpath os.makedirs(LIB, exist_ok=True) if not glob.glob(os.path.join(LIB, "*.jar")): print("lib/ 为空, 从基底 jar 解压依赖...") extract_libs_from_jar(base_jar) ensure_lombok() cp = collect_classpath() print() # 4. 编译 compile_sources(cp) print() # 5. 重打包 zm = repackage(base_jar, dst_jar) print() print(f"built: {dst_jar}") print(f"exists: {os.path.exists(dst_jar)}") if __name__ == "__main__": main()