fix(tools): audit stubs from shared library sources

This commit is contained in:
Ondra97 2026-10-13 07:59:10 +02:00
parent ea2bbefb2b
commit 66edc7463f
2 changed files with 64 additions and 0 deletions

View File

@ -204,17 +204,30 @@ def source_stubs(source):
if not Path(source).is_dir():
raise AuditError(f"{source}: not a directory")
stubs = {}
shared = {}
for library in sorted(item for item in Path(source).iterdir() if item.is_dir()):
aliases = {}
for file in library.rglob("*.cpp"):
for nid, name in nid_names.source_aliases(file.read_text(errors="ignore")):
aliases[name] = nid
group = progress.scan_library(library)
if "shared_sources" in group:
shared[library.name] = group["shared_sources"]
done = {nid_of(name, aliases) for name in group["done_names"]}
for name in group["todo_names"]:
nid = nid_of(name, aliases)
if nid not in done:
stubs.setdefault((library.name + ".prx", nid), name)
for library, owner in shared.items():
chain = [library]
while owner in shared:
if owner in chain:
raise AuditError(f"{source}: shared source cycle: {' -> '.join(chain + [owner])}")
chain.append(owner)
owner = shared[owner]
for (provider, nid), name in list(stubs.items()):
if provider == owner + ".prx":
stubs[(library + ".prx", nid)] = name
return stubs

View File

@ -191,6 +191,57 @@ class ImportAuditTests(unittest.TestCase):
records, _ = import_audit.audit([(nid, "libB.prx")], exports, stubs, {"libA.prx", "libB.prx"}, set())
self.assertEqual(records[0]["class"], "implemented")
def test_cmake_shared_library_preserves_stub_names_and_implemented_exports(self):
work = self.work
owner, library = "libZOwner", "libAOwner.native"
pending, runtime, live = "scePending", "printf_nid_postfix", "sceLive"
alias_nid, alias_name = "AAAAAAAAAAA", "sceOwnerUnknown"
write_source(work.source, owner, STUB_SOURCE.format(name=pending) + STUB_SOURCE.format(name=runtime)
+ f'APS5_EXPORT("{alias_nid}", {alias_name});\n' + STUB_SOURCE.format(name=alias_name)
+ DONE_SOURCE.format(name=live))
shared = work.source / library
shared.mkdir()
(shared / "CMakeLists.txt").write_text(f'include(${{CMAKE_CURRENT_SOURCE_DIR}}/../{owner}/OwnerLibrary.cmake)\n')
expected = {nid_names.compute_nid(pending): pending, nid_names.compute_nid("printf"): runtime, alias_nid: alias_name}
live_nid = nid_names.compute_nid(live)
(work.libs / (library + ".prx")).write_bytes(exporting(*expected, live_nid))
registry, result = work.root / "game.json", work.root / "audit.json"
write_registry(registry, [(nid, library + ".prx") for nid in [*expected, live_nid]])
code, _, err = work.run(registry, "--json", str(result))
self.assertEqual((code, err), (0, ""))
records = {record["nid"]: record for record in json.loads(result.read_text())["imports"]}
self.assertEqual({nid: (records[nid]["class"], records[nid]["name"]) for nid in expected},
{nid: ("stub", name) for nid, name in expected.items()})
self.assertEqual((records[live_nid]["class"], records[live_nid]["name"]), ("implemented", None))
def test_source_include_shared_library_preserves_stubs(self):
source = self.work.source
write_source(source, "libOwner", STUB_SOURCE.format(name="scePending"))
write_source(source, "libShared", '#include "prx/libOwner/Export.cpp"\n')
nid = nid_names.compute_nid("scePending")
self.assertEqual(import_audit.source_stubs(source), {
("libOwner.prx", nid): "scePending", ("libShared.prx", nid): "scePending"})
def test_transitive_shared_library_preserves_stubs(self):
source = self.work.source
write_source(source, "libZOwner", STUB_SOURCE.format(name="scePending"))
for library, owner in (("libA", "libB"), ("libB", "libZOwner")):
shared = source / library
shared.mkdir()
(shared / "CMakeLists.txt").write_text(f'include(${{CMAKE_CURRENT_SOURCE_DIR}}/../{owner}/OwnerLibrary.cmake)\n')
nid = nid_names.compute_nid("scePending")
self.assertEqual(import_audit.source_stubs(source), {
(library + ".prx", nid): "scePending" for library in ("libA", "libB", "libZOwner")})
def test_shared_source_cycle_fails(self):
source = self.work.source
for library, owner in (("libA", "libB"), ("libB", "libA")):
shared = source / library
shared.mkdir()
(shared / "CMakeLists.txt").write_text(f'include(${{CMAKE_CURRENT_SOURCE_DIR}}/../{owner}/OwnerLibrary.cmake)\n')
with self.assertRaisesRegex(import_audit.AuditError, "shared source cycle.*libA.*libB"):
import_audit.source_stubs(source)
def test_formatted_export_aliases_remain_stubs(self):
work = self.work
nid = "AAAAAAAAAAA"