mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-10 19:03:44 +00:00
417 lines
13 KiB
Python
417 lines
13 KiB
Python
"""
|
|
generate_wave_script.py — Generate a complete .ps1 migration wave script
|
|
========================================================================
|
|
|
|
Reads mc_type_classifications_original.json (or mc_type_mappings.json) and
|
|
generates a ready-to-run PowerShell migration wave script.
|
|
|
|
Usage:
|
|
python tools/migration/generate_wave_script.py
|
|
python tools/migration/generate_wave_script.py --sources noppes/npcs/controllers/QuestController.java
|
|
python tools/migration/generate_wave_script.py --sources-file tools/migration/_wave_sources.txt
|
|
python tools/migration/generate_wave_script.py --allow-paths "noppes/npcs/controllers/,noppes/npcs/quests/"
|
|
python tools/migration/generate_wave_script.py --output tools/migration/_wave.ps1
|
|
"""
|
|
|
|
import sys
|
|
import json
|
|
import argparse
|
|
from pathlib import Path
|
|
from datetime import datetime, timezone
|
|
|
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
if str(SCRIPT_DIR) not in sys.path:
|
|
sys.path.insert(0, str(SCRIPT_DIR))
|
|
|
|
from pa_rules import (
|
|
NAMING,
|
|
EXISTING_MAPPINGS,
|
|
get_interface_package,
|
|
SERVICE_ROUTING,
|
|
)
|
|
|
|
STANDARD_FORBIDDEN_PREFIXES = [
|
|
"net.minecraft.",
|
|
"cpw.mods.",
|
|
"net.minecraftforge.",
|
|
"noppes.npcs.entity.",
|
|
"noppes.npcs.client.",
|
|
"noppes.npcs.mixin.",
|
|
"kamkeel.npcs.network.",
|
|
"kamkeel.npcs.entity.",
|
|
"noppes.npcs.containers.",
|
|
"noppes.npcs.scripted.",
|
|
"noppes.npcs.janino.",
|
|
"noppes.npcs.wrapper.",
|
|
"noppes.npcs.blocks.",
|
|
"noppes.npcs.items.",
|
|
"noppes.npcs.ai.",
|
|
]
|
|
|
|
DEFAULT_STUBS = [
|
|
{
|
|
"pkg": "noppes.npcs",
|
|
"name": "CustomNpcs",
|
|
"type": "class",
|
|
"body": ' public static java.io.File Dir = new java.io.File(".");\n public static int DefaultInteractLine = 0;',
|
|
},
|
|
{
|
|
"pkg": "noppes.npcs",
|
|
"name": "EventHooks",
|
|
"type": "class",
|
|
"body": " // STUB - event dispatch stays in mc1710",
|
|
},
|
|
{
|
|
"pkg": "noppes.npcs",
|
|
"name": "LogWriter",
|
|
"type": "class",
|
|
"body": " public static void info(String msg) {}\n public static void warn(String msg) {}\n public static void error(String msg) {}\n public static void error(String msg, Throwable e) {}",
|
|
},
|
|
{
|
|
"pkg": "noppes.npcs.controllers",
|
|
"name": "SyncController",
|
|
"type": "class",
|
|
"body": " // STUB - sync stays in mc1710",
|
|
},
|
|
]
|
|
|
|
|
|
def auto_detect_project_root():
|
|
return SCRIPT_DIR.parent.parent
|
|
|
|
|
|
def _normalize_classifications(data):
|
|
types = data.get("types") or data.get("mappings") or {}
|
|
normalized = {}
|
|
for type_name, info in types.items():
|
|
if type_name.startswith("_"):
|
|
normalized[type_name] = info
|
|
continue
|
|
entry = dict(info)
|
|
if "pa_name" in entry and "pa" not in entry:
|
|
entry["pa"] = entry["pa_name"]
|
|
if "pa_package" in entry and "package" not in entry:
|
|
entry["package"] = entry["pa_package"]
|
|
if "service_name" in entry and "service" not in entry:
|
|
entry["service"] = entry["service_name"]
|
|
normalized[type_name] = entry
|
|
return normalized
|
|
|
|
|
|
def _load_classifications_data(project_root, mappings_path=None):
|
|
if mappings_path:
|
|
p = Path(mappings_path)
|
|
if not p.is_absolute():
|
|
p = project_root / mappings_path
|
|
if p.exists():
|
|
with open(p, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
return _normalize_classifications(data), str(p.name)
|
|
for candidate in [
|
|
"tools/migration/mc_type_mappings.json",
|
|
"tools/migration/mc_type_classifications_original.json",
|
|
]:
|
|
p = project_root / candidate
|
|
if p.exists():
|
|
with open(p, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
return _normalize_classifications(data), candidate
|
|
raise FileNotFoundError("No classifications file found")
|
|
|
|
|
|
def _build_all_swaps(types):
|
|
"""Build swap entries from EXISTING + INTERFACE categories."""
|
|
swaps = []
|
|
seen = set()
|
|
for type_name, info in types.items():
|
|
if type_name.startswith("_"):
|
|
continue
|
|
category = info.get("category", "")
|
|
if category not in ("EXISTING", "INTERFACE"):
|
|
continue
|
|
pa_name = info.get("pa")
|
|
if not pa_name:
|
|
pa_name = NAMING.get(type_name, f"I{type_name}")
|
|
fqn = info.get("fqn", "")
|
|
pkg = info.get("package", get_interface_package(pa_name))
|
|
new_fqn = f"{pkg}.{pa_name}"
|
|
key = (type_name, fqn)
|
|
if key not in seen:
|
|
seen.add(key)
|
|
swaps.append((type_name, pa_name, fqn, new_fqn))
|
|
return swaps
|
|
|
|
|
|
def _build_all_transforms(types):
|
|
"""Build transform entries from TRANSFORM category + SERVICE static-transform patterns."""
|
|
transforms = []
|
|
for type_name, info in types.items():
|
|
if type_name.startswith("_"):
|
|
continue
|
|
if info.get("category") != "TRANSFORM":
|
|
continue
|
|
for pat in info.get("patterns", []):
|
|
if len(pat) >= 2:
|
|
transforms.append((pat[0], pat[1]))
|
|
return transforms
|
|
|
|
|
|
def _ps_array_literal(items, indent=" "):
|
|
"""Format a list of strings as a PowerShell @() array literal."""
|
|
if not items:
|
|
return "@()"
|
|
lines = ["@("]
|
|
for i, item in enumerate(items):
|
|
comma = "," if i < len(items) - 1 else ""
|
|
lines.append(f"{indent} '{item}'{comma}")
|
|
lines.append(f"{indent})")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def _ps_swap_array(swaps, indent=" "):
|
|
"""Format swap entries as a PowerShell @(@(...), @(...)) array."""
|
|
if not swaps:
|
|
return "@()"
|
|
lines = ["@("]
|
|
for i, (old, new, old_fqn, new_fqn) in enumerate(swaps):
|
|
comma = "," if i < len(swaps) - 1 else ""
|
|
lines.append(
|
|
f"{indent} @('{old}', '{new}', '{old_fqn}', '{new_fqn}'){comma}"
|
|
)
|
|
lines.append(f"{indent})")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def _ps_transform_array(transforms, indent=" "):
|
|
"""Format transform entries as a PowerShell @(@(...), @(...)) array."""
|
|
if not transforms:
|
|
return "@()"
|
|
lines = ["@("]
|
|
for i, (pattern, replacement) in enumerate(transforms):
|
|
comma = "," if i < len(transforms) - 1 else ""
|
|
lines.append(f"{indent} @('{pattern}', '{replacement}'){comma}")
|
|
lines.append(f"{indent})")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def _ps_stubs_array(stubs, indent=" "):
|
|
"""Format stubs as a PowerShell @(@{...}, @{...}) array."""
|
|
if not stubs:
|
|
return "@()"
|
|
lines = ["@("]
|
|
for i, stub in enumerate(stubs):
|
|
comma = "," if i < len(stubs) - 1 else ""
|
|
body_escaped = stub["body"].replace("'", "''")
|
|
lines.append(
|
|
f"{indent} @{{ pkg='{stub['pkg']}'; name='{stub['name']}'; type='{stub['type']}';"
|
|
)
|
|
lines.append(f"{indent} body='{body_escaped}' }}{comma}")
|
|
lines.append(f"{indent})")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def generate_script(
|
|
sources,
|
|
allow_paths,
|
|
allow_recurse_paths,
|
|
mc_root,
|
|
swaps,
|
|
transforms,
|
|
stubs,
|
|
forbidden_prefixes,
|
|
cls_source,
|
|
):
|
|
now = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
separator = "# " + "=" * 60
|
|
|
|
sources_arr = _ps_array_literal(sources, " ")
|
|
allow_paths_arr = _ps_array_literal(allow_paths, " ") if allow_paths else None
|
|
allow_recurse_arr = (
|
|
_ps_array_literal(allow_recurse_paths, " ") if allow_recurse_paths else None
|
|
)
|
|
forbidden_arr = _ps_array_literal(forbidden_prefixes, "")
|
|
swaps_arr = _ps_swap_array(swaps, "")
|
|
transforms_arr = _ps_transform_array(transforms, "")
|
|
stubs_arr = _ps_stubs_array(stubs, "")
|
|
|
|
lines = []
|
|
|
|
lines.append(separator)
|
|
lines.append(f"# Wave Migration Script")
|
|
lines.append(f"# Generated by generate_wave_script.py on {now}")
|
|
lines.append(f"# Source: {cls_source}")
|
|
lines.append(separator)
|
|
lines.append("")
|
|
lines.append(". (Join-Path $PSScriptRoot 'Copy-Recursive.ps1')")
|
|
lines.append(". (Join-Path $PSScriptRoot 'Nuke-Imports.ps1')")
|
|
lines.append(". (Join-Path $PSScriptRoot 'Symbol-Swap.ps1')")
|
|
lines.append(". (Join-Path $PSScriptRoot 'Static-Transform.ps1')")
|
|
lines.append(". (Join-Path $PSScriptRoot 'Generate-Stubs.ps1')")
|
|
lines.append(". (Join-Path $PSScriptRoot 'Dedupe-Imports.ps1')")
|
|
lines.append(". (Join-Path $PSScriptRoot 'Build-Report.ps1')")
|
|
lines.append("")
|
|
|
|
if sources:
|
|
lines.append(separator)
|
|
lines.append("# STEP 1: Copy files from mc1710 to core")
|
|
lines.append(separator)
|
|
copy_cmd = "Copy-Recursive `\n"
|
|
copy_cmd += f" -Sources {sources_arr} `\n"
|
|
if allow_paths_arr:
|
|
copy_cmd += f" -AllowPaths {allow_paths_arr} `\n"
|
|
if allow_recurse_arr:
|
|
copy_cmd += f" -AllowRecursePaths {allow_recurse_arr} `\n"
|
|
copy_cmd += f" -McRoot '{mc_root}'"
|
|
lines.append(copy_cmd)
|
|
lines.append("")
|
|
|
|
lines.append(separator)
|
|
lines.append("# STEP 2: Nuke MC imports")
|
|
lines.append(separator)
|
|
lines.append(
|
|
f"Nuke-Imports -Directory 'core/src/main/java' -ForbiddenPrefixes {forbidden_arr}"
|
|
)
|
|
lines.append("")
|
|
|
|
lines.append(separator)
|
|
lines.append("# STEP 3: Symbol-Swap — replace MC types with PA abstractions")
|
|
lines.append(separator)
|
|
lines.append(f"Symbol-Swap -Directory 'core/src/main/java' -Swaps {swaps_arr}")
|
|
lines.append("")
|
|
|
|
if transforms:
|
|
lines.append(separator)
|
|
lines.append("# STEP 4: Static-Transform — regex rewrites")
|
|
lines.append(separator)
|
|
lines.append(
|
|
f"Static-Transform -Directory 'core/src/main/java' -Transforms {transforms_arr}"
|
|
)
|
|
lines.append("")
|
|
|
|
lines.append(separator)
|
|
lines.append("# STEP 5: Generate stubs for missing dependencies")
|
|
lines.append(separator)
|
|
lines.append(f"Generate-Stubs -Stubs {stubs_arr}")
|
|
lines.append("")
|
|
|
|
lines.append(separator)
|
|
lines.append("# STEP 6: Deduplicate imports")
|
|
lines.append(separator)
|
|
lines.append("Dedupe-Imports -Directory 'core/src/main/java'")
|
|
lines.append("")
|
|
|
|
lines.append(separator)
|
|
lines.append("# STEP 7: Build and report")
|
|
lines.append(separator)
|
|
lines.append("Build-Report -GradleTask ':core:compileJava'")
|
|
lines.append("")
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate a complete .ps1 migration wave script"
|
|
)
|
|
parser.add_argument(
|
|
"--mappings",
|
|
help="Path to mc_type_mappings.json or classifications JSON",
|
|
)
|
|
parser.add_argument(
|
|
"--sources",
|
|
nargs="*",
|
|
default=[],
|
|
help="Java source files to migrate (relative to mc-root)",
|
|
)
|
|
parser.add_argument(
|
|
"--sources-file",
|
|
help="Text file with one source path per line",
|
|
)
|
|
parser.add_argument(
|
|
"--allow-paths",
|
|
help="Comma-separated AllowPaths prefixes",
|
|
)
|
|
parser.add_argument(
|
|
"--allow-recurse-paths",
|
|
help="Comma-separated AllowRecursePaths prefixes",
|
|
)
|
|
parser.add_argument(
|
|
"--mc-root",
|
|
default="src/main/java",
|
|
help="MC source root (default: src/main/java)",
|
|
)
|
|
parser.add_argument(
|
|
"--output",
|
|
default="tools/migration/_wave.ps1",
|
|
help="Output .ps1 file path (relative to project root)",
|
|
)
|
|
parser.add_argument(
|
|
"--project-root",
|
|
help="Project root (auto-detected if omitted)",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
project_root = (
|
|
Path(args.project_root) if args.project_root else auto_detect_project_root()
|
|
)
|
|
|
|
types, cls_source = _load_classifications_data(project_root, args.mappings)
|
|
|
|
sources = list(args.sources)
|
|
if args.sources_file:
|
|
sf = Path(args.sources_file)
|
|
if not sf.is_absolute():
|
|
sf = project_root / args.sources_file
|
|
if sf.exists():
|
|
with open(sf, "r", encoding="utf-8") as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if line and not line.startswith("#"):
|
|
sources.append(line)
|
|
|
|
allow_paths = None
|
|
if args.allow_paths:
|
|
allow_paths = [p.strip() for p in args.allow_paths.split(",") if p.strip()]
|
|
|
|
allow_recurse_paths = None
|
|
if args.allow_recurse_paths:
|
|
allow_recurse_paths = [
|
|
p.strip() for p in args.allow_recurse_paths.split(",") if p.strip()
|
|
]
|
|
|
|
swaps = _build_all_swaps(types)
|
|
transforms = _build_all_transforms(types)
|
|
|
|
script_content = generate_script(
|
|
sources=sources,
|
|
allow_paths=allow_paths,
|
|
allow_recurse_paths=allow_recurse_paths,
|
|
mc_root=args.mc_root,
|
|
swaps=swaps,
|
|
transforms=transforms,
|
|
stubs=DEFAULT_STUBS,
|
|
forbidden_prefixes=STANDARD_FORBIDDEN_PREFIXES,
|
|
cls_source=cls_source,
|
|
)
|
|
|
|
output_path = project_root / args.output
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(output_path, "w", encoding="utf-8", newline="\n") as f:
|
|
f.write(script_content)
|
|
|
|
print()
|
|
print(f"Wave Script Generator")
|
|
print(f" Source: {cls_source}")
|
|
print(f" Sources: {len(sources)} files")
|
|
print(f" Swaps: {len(swaps)} entries")
|
|
print(f" Transforms: {len(transforms)} patterns")
|
|
print(f" Stubs: {len(DEFAULT_STUBS)} entries")
|
|
print(f" Written to: {args.output}")
|
|
print()
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|