port 2A step 2: copy the 40250 header closure into the port mirror
76 closure headers (EterBase, EterLocale, EterPack, EterLib, GameLib, MilesLib, EffectLib, SphereLib, EterImageLib) copied by the new port_copy.py (CP949->UTF-8, LF, include-path case only); platform-class headers go to the mirror path too, as the interface platform/ implements. Six manual `// PORT:` edits (Random, Singleton, Pool, Stl, Utils, FlyTarget). - common/D3D8Types.h: D3D8 value types with SDK values/layout, opaque COM interfaces - common/shim/sdk (d3d8/d3dx8/mss) on every platform, common/shim/win32 off Windows - Win32Types/Win32Crt: HRESULT, GUID, LARGE_INTEGER, LOGFONT/TEXTMETRIC, _atoi64, CRITICAL_SECTION on std::recursive_mutex - header gate prepends each library's StdAfx.h (40250's precompiled header) port_gate: macOS, Android, iOS, Windows (mingw) PASS; Linux BLOCKED (no toolchain). ScriptLib/EterPythonLib/UserInterface wait for 2P (their StdAfx includes Python). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -148,6 +148,9 @@ behavior verified from code inspection alone.
|
||||
— function inventory (active `.vcxproj` sources + the 40250 Python root), progress per library and
|
||||
per layer, and tag/port-map/layout consistency. Run `check` before every commit that touches
|
||||
`audit/port-map/`.
|
||||
- `scripts/port_copy.py copy <Lib/File>... | diff [<Lib/File>...]` — copy 40250 files into the
|
||||
`extension/src/port/` mirror with mechanical edits only (CP949→UTF-8, LF, include-path case); `diff`
|
||||
lists the manual `// PORT:` edits.
|
||||
- `scripts/port_deps.py closure <file> | slices [--write] | order [--write]` — `#include` graph with the
|
||||
implicit edges each library's `StdAfx.h` supplies; writes the 2V0-2V3 unit lists and the batch-2
|
||||
topological order to `audit/slices/`. Regenerate after changing a slice definition.
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Copy 40250 sources into the port mirror (extension/src/port/<Lib>/<File>) with mechanical edits only.
|
||||
|
||||
port_copy.py copy <rel> [<rel>...] [--force] write the mechanical copy (refuses to overwrite without --force)
|
||||
port_copy.py diff [<rel>...] show the manual edits: port file vs its mechanical copy
|
||||
(no args: every mirrored 40250 file that differs)
|
||||
|
||||
`<rel>` is a reference path such as `GameLib/ActorInstance.h`, matched case-insensitively.
|
||||
|
||||
Mechanical edits, and nothing else:
|
||||
- CP949 is decoded and written as UTF-8 (comments are Korean). A non-ASCII string or character
|
||||
literal would change bytes at runtime, so such a file is refused and must be handled by hand.
|
||||
- CRLF becomes LF.
|
||||
- `#include "..."` paths get the on-disk case of the file they name (40250 builds on a
|
||||
case-insensitive file system and writes `../eterLib/StdAfx.h`, `Parser.h` for `parser.h`, ...),
|
||||
keeping the relative form, so the original include resolves the same way on every platform.
|
||||
Everything else in a port file that differs from this copy is a manual edit, marked `// PORT:`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import difflib
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import refroot # script directory is on sys.path when run directly
|
||||
|
||||
REPO = refroot.repo_root()
|
||||
PORT = REPO / "extension" / "src" / "port"
|
||||
INCLUDE = re.compile(r'^(\s*#\s*include\s*")([^"]+)(")', re.M)
|
||||
LITERAL = re.compile(r'"(?:\\.|[^"\\\n])*"|\'(?:\\.|[^\'\\\n])*\'')
|
||||
COMMENT = re.compile(r"//[^\n]*|/\*.*?\*/", re.S)
|
||||
|
||||
|
||||
def ref_index(ref: Path) -> dict[str, Path]:
|
||||
return {p.relative_to(ref).as_posix().lower(): p for p in ref.rglob("*") if p.is_file()}
|
||||
|
||||
|
||||
def canonical(ref: Path, index: dict[str, Path], path: Path) -> str:
|
||||
return index[path.relative_to(ref).as_posix().lower()].relative_to(ref).as_posix()
|
||||
|
||||
|
||||
def resolve(ref: Path, index: dict[str, Path], rel: str) -> Path:
|
||||
hit = index.get(rel.replace("\\", "/").lower())
|
||||
if not hit:
|
||||
raise SystemExit(f"{rel}: not in the 40250 reference tree")
|
||||
return hit
|
||||
|
||||
|
||||
def fix_include(ref: Path, index: dict[str, Path], src: Path, target: str) -> str:
|
||||
base = src.parent.relative_to(ref).as_posix()
|
||||
parts = [] if base == "." else base.split("/")
|
||||
for piece in target.replace("\\", "/").split("/"):
|
||||
if piece == "..":
|
||||
if not parts:
|
||||
return target
|
||||
parts.pop()
|
||||
elif piece != ".":
|
||||
parts.append(piece)
|
||||
hit = index.get("/".join(parts).lower())
|
||||
if not hit:
|
||||
return target # unresolved (commented-out or absent in 40250): leave untouched
|
||||
real = hit.relative_to(ref).as_posix().split("/")
|
||||
# Keep the original shape: same number of `..` and segments, only the case changes.
|
||||
kept = [p for p in target.replace("\\", "/").split("/") if p in ("..", ".")]
|
||||
tail = real[len(real) - (len(target.replace("\\", "/").split("/")) - len(kept)):]
|
||||
return "/".join(kept + tail)
|
||||
|
||||
|
||||
def mechanical(ref: Path, index: dict[str, Path], src: Path) -> str:
|
||||
raw = src.read_bytes()
|
||||
try:
|
||||
text = raw.decode("cp949")
|
||||
except UnicodeDecodeError as e:
|
||||
raise SystemExit(f"{src.relative_to(ref)}: not CP949 ({e})")
|
||||
text = text.replace("\r\n", "\n").lstrip("")
|
||||
code = COMMENT.sub(lambda m: " " * len(m.group(0)), text)
|
||||
bad = [m.group(0) for m in LITERAL.finditer(code) if not m.group(0).isascii()]
|
||||
if bad:
|
||||
raise SystemExit(f"{src.relative_to(ref)}: non-ASCII literal(s), copy by hand: {bad[:3]}")
|
||||
return INCLUDE.sub(lambda m: m.group(1) + fix_include(ref, index, src, m.group(2)) + m.group(3), text)
|
||||
|
||||
|
||||
def cmd_copy(args) -> int:
|
||||
ref = refroot.reference_root()
|
||||
index = ref_index(ref)
|
||||
for rel in args.rel:
|
||||
src = resolve(ref, index, rel)
|
||||
dst = PORT / canonical(ref, index, src)
|
||||
if dst.exists() and not args.force:
|
||||
print(f"skip {dst.relative_to(REPO)} (exists; --force overwrites manual edits)")
|
||||
continue
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
dst.write_text(mechanical(ref, index, src), encoding="utf-8")
|
||||
print(f"copy {dst.relative_to(REPO)}")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_diff(args) -> int:
|
||||
ref = refroot.reference_root()
|
||||
index = ref_index(ref)
|
||||
if args.rel:
|
||||
pairs = [(resolve(ref, index, r), PORT / canonical(ref, index, resolve(ref, index, r))) for r in args.rel]
|
||||
else:
|
||||
pairs = []
|
||||
for dst in sorted(PORT.rglob("*")):
|
||||
key = dst.relative_to(PORT).as_posix().lower()
|
||||
if dst.is_file() and key in index and not key.startswith("common/"):
|
||||
pairs.append((index[key], dst))
|
||||
changed = 0
|
||||
for src, dst in pairs:
|
||||
if not dst.exists():
|
||||
print(f"missing {dst.relative_to(REPO)}")
|
||||
continue
|
||||
want = mechanical(ref, index, src).splitlines(keepends=True)
|
||||
have = dst.read_text(encoding="utf-8").splitlines(keepends=True)
|
||||
diff = list(difflib.unified_diff(want, have, f"40250/{src.relative_to(ref).as_posix()}",
|
||||
dst.relative_to(REPO).as_posix()))
|
||||
if diff:
|
||||
changed += 1
|
||||
sys.stdout.writelines(diff)
|
||||
print(f"{changed} of {len(pairs)} file(s) with manual edits", file=sys.stderr)
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||
c = sub.add_parser("copy")
|
||||
c.add_argument("rel", nargs="+")
|
||||
c.add_argument("--force", action="store_true")
|
||||
d = sub.add_parser("diff")
|
||||
d.add_argument("rel", nargs="*")
|
||||
args = ap.parse_args()
|
||||
return {"copy": cmd_copy, "diff": cmd_diff}[args.cmd](args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -49,7 +49,7 @@ LAYER_PATTERNS = [
|
||||
"EterLib/LensFlare.cpp", "EterLib/ScreenFilter.cpp", "EterLib/Decal.cpp", "EterLib/EnvironmentMap.cpp",
|
||||
"EterLib/JpegFile.cpp", "EterLib/TargaResource.cpp", "EterLib/DibBar.cpp", "EterLib/BlockTexture.cpp",
|
||||
"EterLib/CullingManager.cpp", "EterLib/Thread.cpp", "EterLib/Mutex.cpp", "EterLib/FileLoaderThread.cpp",
|
||||
"EterLib/TextBar.cpp", "EterBase/*", "GameLib/MapOutdoorRender*.cpp", "GameLib/MapOutdoorWater.cpp",
|
||||
"EterLib/TextBar.cpp", "EterLib/Dimm.h", "EterBase/*", "GameLib/MapOutdoorRender*.cpp", "GameLib/MapOutdoorWater.cpp",
|
||||
"GameLib/SnowParticle.cpp", "GameLib/SnowEnvironment.cpp", "GameLib/TerrainDecal.cpp",
|
||||
"GameLib/TerrainPatch.cpp", "UserInterface/PythonApplicationLogo.cpp", "UserInterface/MovieMan.cpp",
|
||||
"UserInterface/HackShield.cpp", "UserInterface/NProtectGameGuard.cpp", "UserInterface/ProcessCRC.cpp",
|
||||
|
||||
Reference in New Issue
Block a user