mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
129 lines
5.7 KiB
Python
129 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
# MobileGL - scripts/ci/server_link_check.py
|
|
# Copyright (c) 2025-2026 MobileGL-Dev
|
|
# Licensed under the GNU Lesser General Public License v3.0:
|
|
# https://www.gnu.org/licenses/gpl-3.0.txt
|
|
# https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
# SPDX-License-Identifier: LGPL-3.0-only
|
|
# End of Source File Header
|
|
|
|
"""P13 W7 (D12): the exemption list for the server-only link check target.
|
|
|
|
THE TARGET. `MobileGL_server_linkcheck` (CMakeLists.txt) links the SERVER and SHARED module objects
|
|
(mg_util, mg_pipe, mg_backend, mg_remote_transport, mg_remote_server) WITHOUT mg_frontend and
|
|
mg_remote_client, with `--no-undefined`: a server image must link from its own half of the tree.
|
|
The references that still need the frontend are the link ratchet's baseline
|
|
(scripts/data/link_ratchet_baseline.txt) - each one a named debt, not an accident - and this script
|
|
turns exactly those into named exemptions: one `--defsym=<mangled>=mgl_server_link_check_exempt`
|
|
per symbol, written to a compiler-driver response file the target links with. The stub aborts; the target
|
|
is a link proof, never run.
|
|
|
|
WHAT IS REFUSED. A SERVER object's reference that only a FRONTEND object satisfies and that the
|
|
baseline does not list fails here, before the link, naming the symbol and the referrer - the same
|
|
verdict the ratchet gives, raised by the build. References from SHARED objects (Init, the global
|
|
objects, the config loader) into the frontend are exempted and only counted: those files ARE the
|
|
library's entry glue, and a real server image replaces them rather than sheds them.
|
|
|
|
Usage: server_link_check.py --build-dir B --baseline F --out RSP [--nm nm] [--cxxfilt c++filt]
|
|
"""
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
|
import link_ratchet as lr # noqa: E402
|
|
|
|
STUB = "mgl_server_link_check_exempt"
|
|
|
|
|
|
def nm_mangled(nm_tool, mode, keys, cwd):
|
|
"""{key: {mangled: type}} via `nm -o --<mode>-only` (no demangling)."""
|
|
table = {k: {} for k in keys}
|
|
back = {lr.OBJECT_PATHS.get(k, k): k for k in keys}
|
|
for i in range(0, len(keys), 150):
|
|
batch = [lr.OBJECT_PATHS.get(k, k) for k in keys[i:i + 150]]
|
|
done = subprocess.run([nm_tool, "-o", "--" + mode + "-only"] + batch, cwd=cwd,
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
if done.returncode != 0 and done.stderr.strip():
|
|
raise SystemExit("server-link-check: nm failed: " + done.stderr.strip())
|
|
for line in done.stdout.splitlines():
|
|
head, _, rest = line.partition(":")
|
|
key = back.get(head)
|
|
parts = rest.split()
|
|
if key is None or len(parts) < 2:
|
|
continue
|
|
table[key][parts[-1]] = parts[-2]
|
|
return table
|
|
|
|
|
|
def demangle(names, tool):
|
|
names = sorted(names)
|
|
done = subprocess.run([tool], input="\n".join(names) + "\n", stdout=subprocess.PIPE, text=True,
|
|
check=True)
|
|
return dict(zip(names, done.stdout.splitlines()))
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--build-dir", required=True)
|
|
ap.add_argument("--baseline", required=True)
|
|
ap.add_argument("--out", required=True)
|
|
ap.add_argument("--nm", default="nm")
|
|
ap.add_argument("--cxxfilt", default="c++filt")
|
|
args = ap.parse_args()
|
|
|
|
root, objects = lr.find_objects(args.build_dir, "MobileGL")
|
|
roles = {o: lr.classify(o) for o in objects}
|
|
unclassified = [o for o, r in roles.items() if r is None]
|
|
if unclassified:
|
|
raise SystemExit("server-link-check: object(s) match no PARTITION row: " + ", ".join(unclassified))
|
|
server = sorted(o for o, r in roles.items() if r == lr.SERVER)
|
|
shared = sorted(o for o, r in roles.items() if r == lr.SHARED)
|
|
frontend = sorted(o for o, r in roles.items() if r == lr.FRONTEND)
|
|
|
|
undefined = nm_mangled(args.nm, "undefined", server + shared, root)
|
|
defined = nm_mangled(args.nm, "defined", objects, root)
|
|
image_defined = set()
|
|
for obj in server + shared:
|
|
image_defined.update(defined[obj])
|
|
frontend_defined = set()
|
|
for obj in frontend:
|
|
frontend_defined.update(defined[obj])
|
|
|
|
wanted = {} # mangled -> set of referrer keys
|
|
for obj in server + shared:
|
|
for name in undefined[obj]:
|
|
if name in image_defined or name not in frontend_defined:
|
|
continue
|
|
wanted.setdefault(name, set()).add(obj)
|
|
|
|
readable = demangle(wanted, args.cxxfilt)
|
|
baseline, _notes = lr.read_baseline(args.baseline)
|
|
baseline = set(baseline)
|
|
refused, from_shared = [], 0
|
|
for name, referrers in sorted(wanted.items()):
|
|
from_server = [r for r in referrers if roles[r] == lr.SERVER]
|
|
if not from_server:
|
|
from_shared += 1
|
|
continue
|
|
if readable[name] not in baseline:
|
|
refused.append((readable[name], sorted(from_server)))
|
|
if refused:
|
|
for symbol, referrers in refused:
|
|
print("server-link-check: NEW frontend reference from the server half: {} <- {}"
|
|
.format(symbol, ", ".join(referrers)))
|
|
print("server-link-check: {} reference(s) the baseline does not name - fix the reference or, "
|
|
"if it is a debt, add it to {} with its reason".format(len(refused), args.baseline))
|
|
return 1
|
|
with open(args.out, "w", encoding="utf-8") as out:
|
|
for name in sorted(wanted):
|
|
out.write("-Wl,--defsym={}={}\n".format(name, STUB))
|
|
print("server-link-check: {} named exemption(s) ({} from server objects, all in the baseline; {} "
|
|
"from the shared entry glue)".format(len(wanted), len(wanted) - from_shared, from_shared))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|