|
@@ -0,0 +1,250 @@
|
|
|
|
|
+#!/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()
|