feat(tools): report each converted title's current stop in import_audit
The stop is the first unresolved import in the registry order, the line the loader fails on. Print it per registry and add it to the --json result.
This commit is contained in:
parent
b8683834f3
commit
a3a3f3a9ce
|
|
@ -424,6 +424,16 @@ def audit(imports, exports, stubs, libraries, modules, module_imports=None, modu
|
|||
return ordered, dict(sorted(missing.items()))
|
||||
|
||||
|
||||
def current_stop(imports, records):
|
||||
"""The first import the loader cannot resolve, in registry order."""
|
||||
classes = {(record["nid"], record["library"]): record for record in records}
|
||||
for nid, library in imports:
|
||||
record = classes.get((nid, library))
|
||||
if record is not None and record["class"] == "absent":
|
||||
return record
|
||||
return None
|
||||
|
||||
|
||||
def load_names(path):
|
||||
if not Path(path).is_file():
|
||||
raise AuditError(f"{path}: names file not found; the audit never downloads one (see tools/nid_names.py for the source)")
|
||||
|
|
@ -469,7 +479,7 @@ def label(record):
|
|||
return f"{text} (imported by {record['module']})" if record.get("module") else text
|
||||
|
||||
|
||||
def render(summary, records, missing):
|
||||
def render(summary, records, missing, stops):
|
||||
lines = [f"{summary['references']} references, {summary['unique_imports']} unique imports"]
|
||||
for kind in CLASSES:
|
||||
lines.append(f" {kind:<12}{summary['unique_by_class'][kind]:>6} imports {summary['references_by_class'][kind]:>6} references")
|
||||
|
|
@ -484,6 +494,12 @@ def render(summary, records, missing):
|
|||
current = record["library"]
|
||||
lines.append(f" [{current or 'library not named'}]")
|
||||
lines.append(f" {label(record)}")
|
||||
if stops:
|
||||
lines.append("")
|
||||
lines.append("Current stop (first unresolved import, in registry order)")
|
||||
for entry in stops:
|
||||
stop = entry["stop"]
|
||||
lines.append(f" {entry['registry']}: none" if stop is None else f" {entry['registry']}: {label(stop)} [{stop['library']}]")
|
||||
if missing:
|
||||
lines.append("")
|
||||
lines.append(f"Needed libraries with no file in --libs or --modules ({len(missing)})")
|
||||
|
|
@ -512,16 +528,22 @@ def main(argv=None):
|
|||
output = args.json.resolve()
|
||||
if any(output == path.resolve() or (args.json.exists() and args.json.samefile(path)) for path in inputs):
|
||||
raise AuditError(f"{args.json}: JSON output would overwrite an input file")
|
||||
imports = [entry for registry in args.registries for entry in read_registry(registry)]
|
||||
registries = [(registry, read_registry(registry)) for registry in args.registries]
|
||||
imports = [entry for _, entries in registries for entry in entries]
|
||||
exports, libraries = built_libraries(args.libs)
|
||||
module_imports, module_exports = read_modules(args.modules)
|
||||
records, missing = audit(imports, exports, source_stubs(args.source), libraries, set(module_imports), module_imports, module_exports)
|
||||
if args.names:
|
||||
attach_names(records, load_names(args.names))
|
||||
summary = summarize(records, sum(record["references"] for record in records), missing)
|
||||
stops = [{"registry": str(path), "stop": current_stop(entries, records)} for path, entries in registries]
|
||||
if args.json:
|
||||
commit, dirty = git_stamp(Path(args.source))
|
||||
result = {"source_commit": commit, "source_dirty": dirty, "registries": [str(path) for path in args.registries], **summary, "imports": records}
|
||||
json_stops = [{"registry": entry["registry"],
|
||||
"nid": entry["stop"]["nid"] if entry["stop"] else None,
|
||||
"library": entry["stop"]["library"] if entry["stop"] else None,
|
||||
"name": entry["stop"]["name"] if entry["stop"] else None} for entry in stops]
|
||||
result = {"source_commit": commit, "source_dirty": dirty, "registries": [str(path) for path in args.registries], **summary, "stops": json_stops, "imports": records}
|
||||
args.json.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
|
||||
except AuditError as error:
|
||||
print(f"FAIL: {error}", file=sys.stderr)
|
||||
|
|
@ -529,7 +551,7 @@ def main(argv=None):
|
|||
except OSError as error:
|
||||
print(f"FAIL: {error.strerror}: {error.filename}" if error.filename else f"FAIL: {error}", file=sys.stderr)
|
||||
return 2
|
||||
print(render(summary, records, missing))
|
||||
print(render(summary, records, missing, stops))
|
||||
return 1 if summary["unique_by_class"]["absent"] or missing else 0
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -304,6 +304,31 @@ class ImportAuditTests(unittest.TestCase):
|
|||
self.assertEqual((code, data["references"], data["unique_imports"], data["imports"]), (0, 0, 0, []))
|
||||
self.assertIn("0 references, 0 unique imports", out)
|
||||
|
||||
def test_current_stop_is_the_first_unresolved_import(self):
|
||||
work = self.work
|
||||
(work.libs / "libSceA.prx").write_bytes(exporting("AAAAAAAAAAA"))
|
||||
registry = work.root / "game.json"
|
||||
write_registry(registry, [("AAAAAAAAAAA", "libSceA.prx"), ("BBBBBBBBBBB", "libSceA.prx"), ("CCCCCCCCCCC", "libSceA.prx")])
|
||||
result = work.root / "audit.json"
|
||||
code, out, err = work.run(registry, "--json", str(result))
|
||||
self.assertEqual((code, err), (1, ""))
|
||||
data = json.loads(result.read_text())
|
||||
self.assertEqual(data["stops"], [{"registry": str(registry), "nid": "BBBBBBBBBBB", "library": "libSceA.prx", "name": None}])
|
||||
self.assertIn("Current stop (first unresolved import, in registry order)", out)
|
||||
self.assertIn("BBBBBBBBBBB", out)
|
||||
|
||||
def test_current_stop_is_none_when_every_import_resolves(self):
|
||||
work = self.work
|
||||
(work.libs / "libSceA.prx").write_bytes(exporting("AAAAAAAAAAA"))
|
||||
registry = work.root / "clean.json"
|
||||
write_registry(registry, [("AAAAAAAAAAA", "libSceA.prx")])
|
||||
result = work.root / "audit.json"
|
||||
code, out, _ = work.run(registry, "--json", str(result))
|
||||
self.assertEqual(code, 0)
|
||||
data = json.loads(result.read_text())
|
||||
self.assertEqual(data["stops"], [{"registry": str(registry), "nid": None, "library": None, "name": None}])
|
||||
self.assertIn(f" {registry}: none", out)
|
||||
|
||||
def test_removing_an_export_flips_the_class(self):
|
||||
work = self.work
|
||||
registry = work.root / "game.json"
|
||||
|
|
|
|||
Loading…
Reference in New Issue