136 lines
3.9 KiB
Python
136 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from patch_layout import PATCH_LAYOUT, normalized_path
|
|
|
|
|
|
EXTENSIONS = {
|
|
"MoonWell",
|
|
"wxl-db2",
|
|
"wxl-fdid-moonwell",
|
|
"wxl-grasswind",
|
|
"wxl-modern-adt",
|
|
"wxl-modern-blp",
|
|
"wxl-modern-m2",
|
|
"wxl-modern-wmo",
|
|
"wxl-moonwell-storage-fallback",
|
|
"wxl-unit-outline",
|
|
}
|
|
EXTENSION_KEYS = {name.casefold() for name in EXTENSIONS}
|
|
|
|
ALLOWED_TOP_LEVEL_FILES = {
|
|
"d3d9.dll",
|
|
"warcraftxl.dll",
|
|
"wow.exe",
|
|
}
|
|
ALLOWED_TOP_LEVEL_DIRS = {"data", "extensions", "utils"}
|
|
|
|
REQUIRED_RUNTIME_FILES = {
|
|
"wow.exe",
|
|
"d3d9.dll",
|
|
"warcraftxl.dll",
|
|
*PATCH_LAYOUT.repository_patches,
|
|
"utils/d3d9-native.dll",
|
|
*(f"extensions/{name.casefold()}/{name.casefold()}.dll" for name in EXTENSIONS),
|
|
}
|
|
|
|
ALLOWED_DLL_FILES = {
|
|
path for path in REQUIRED_RUNTIME_FILES if path.endswith(".dll")
|
|
}
|
|
|
|
BUILD_ONLY_SUFFIXES = {
|
|
".bak",
|
|
".exp",
|
|
".ilk",
|
|
".lib",
|
|
".log",
|
|
".obj",
|
|
".old",
|
|
".orig",
|
|
".pdb",
|
|
".pyc",
|
|
".rej",
|
|
".temp",
|
|
".tmp",
|
|
}
|
|
|
|
|
|
def normalized(path: Path) -> str:
|
|
return normalized_path(path.as_posix())
|
|
|
|
|
|
def validate(root: Path) -> list[str]:
|
|
errors: list[str] = []
|
|
files = sorted(path for path in root.rglob("*") if path.is_file())
|
|
relative_files = {normalized(path.relative_to(root)) for path in files}
|
|
|
|
for missing in sorted(REQUIRED_RUNTIME_FILES - relative_files):
|
|
errors.append(f"missing required runtime file: {missing}")
|
|
|
|
for entry in root.iterdir():
|
|
key = entry.name.casefold()
|
|
if entry.is_file() and key not in ALLOWED_TOP_LEVEL_FILES:
|
|
errors.append(f"unexpected top-level file: {entry.name}")
|
|
elif entry.is_dir() and key not in ALLOWED_TOP_LEVEL_DIRS:
|
|
errors.append(f"unexpected top-level directory: {entry.name}")
|
|
|
|
for path in files:
|
|
relative = path.relative_to(root)
|
|
relative_key = normalized(relative)
|
|
parts = relative.parts
|
|
|
|
if PATCH_LAYOUT.is_external_graphics_patch(relative.as_posix()):
|
|
errors.append(
|
|
f"externally managed graphics patch in repository package: {relative.as_posix()}"
|
|
)
|
|
|
|
if path.suffix.casefold() in BUILD_ONLY_SUFFIXES:
|
|
errors.append(f"build-only or temporary file: {relative.as_posix()}")
|
|
|
|
if path.suffix.casefold() == ".dll" and relative_key not in ALLOWED_DLL_FILES:
|
|
errors.append(f"unexpected DLL: {relative.as_posix()}")
|
|
|
|
if path.suffix.casefold() == ".exe" and relative_key != "wow.exe":
|
|
errors.append(f"unexpected executable: {relative.as_posix()}")
|
|
|
|
if parts and parts[0].casefold() == "extensions":
|
|
if len(parts) != 3:
|
|
errors.append(f"unexpected Extensions layout: {relative.as_posix()}")
|
|
continue
|
|
extension_name = parts[1]
|
|
expected_file = f"{extension_name}.dll"
|
|
if extension_name.casefold() not in EXTENSION_KEYS:
|
|
errors.append(f"unexpected extension: {relative.as_posix()}")
|
|
elif parts[2].casefold() != expected_file.casefold():
|
|
errors.append(f"unexpected file in extension package: {relative.as_posix()}")
|
|
|
|
return errors
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Validate MoonWell package layout")
|
|
parser.add_argument("--dir", required=True, help="Package staging directory")
|
|
args = parser.parse_args()
|
|
|
|
root = Path(args.dir).resolve()
|
|
if not root.is_dir():
|
|
print(f"ERROR: package directory not found: {root}", file=sys.stderr)
|
|
return 1
|
|
|
|
errors = validate(root)
|
|
if errors:
|
|
print("ERROR: package validation failed:", file=sys.stderr)
|
|
for error in errors:
|
|
print(f" - {error}", file=sys.stderr)
|
|
return 1
|
|
|
|
print(f"OK: package layout validated ({len(list(root.rglob('*')))} entries)")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|