| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 |
- #!/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()
|