libpython2.7.a cross-builds with NDK 27.2 (API 24) after disabling HAVE_LANGINFO_H; 30 C modules built in. On a OnePlus 13 (API 36) the harness runs system.py -> prototype.RunApp() and imports the root modules with results identical to macOS (66/74; the 8 failures are stub values or dev-only scripts). py_embed_spike.py now isolates __main__ like CPythonLauncher and reports stdlib usage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
97 lines
4.8 KiB
Python
97 lines
4.8 KiB
Python
#!/usr/bin/env python2.7
|
|
"""Embedded-Python feasibility spike (Python 2.7 only; see docs/PYTHON-EMBED-EVAL.md).
|
|
|
|
Emulates CPythonLauncher::Create + system.py bootstrap: stub native modules (every call returns 0),
|
|
a directory-backed `pack` module over the 40250 Eternexus tree, then imports every root module
|
|
through system.py's own `__pack_import`. Reports loaded/failed modules to --out (default
|
|
py_embed_spike.txt); system.py redirects stdout, so nothing useful is printed.
|
|
|
|
python2.7 py_embed_spike.py [--out FILE] (reference root: MT_40250_SOURCE or audit manifest)
|
|
"""
|
|
import sys, types, os, glob, traceback
|
|
if os.environ.get("MT_ETERNEXUS"): # on-device run: Eternexus tree pushed next to the binary
|
|
E = os.environ["MT_ETERNEXUS"]
|
|
else:
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
REPO = os.path.abspath(os.path.join(HERE, "../../../.."))
|
|
import json
|
|
_ref = os.environ.get("MT_40250_SOURCE") or json.load(open(os.path.join(REPO, "audit/manifest.json")))["reference_root"]
|
|
E = str(os.path.normpath(os.path.join(REPO, _ref, "../../Client/Eternexus"))) # json gives unicode
|
|
OUT = sys.argv[sys.argv.index("--out") + 1] if "--out" in sys.argv else "py_embed_spike.txt"
|
|
FILES = {}
|
|
for p in glob.glob(E + "/root/*"): FILES[os.path.basename(p).lower()] = p
|
|
for base, pre in ((E + "/locale_en/locale/en", "locale/en/"), (E + "/uiscript/uiscript", "uiscript/")):
|
|
for d, _, fs in os.walk(base):
|
|
for f in fs:
|
|
full = os.path.join(d, f)
|
|
FILES[(pre + os.path.relpath(full, base)).lower()] = full
|
|
LOG = open(OUT, "w")
|
|
MISSING = {}
|
|
class Stub(types.ModuleType):
|
|
def __getattr__(self, name):
|
|
if name.startswith("__"): raise AttributeError(name)
|
|
key = "%s.%s" % (self.__name__, name)
|
|
def f(*a, **k):
|
|
MISSING[key] = MISSING.get(key, 0) + 1
|
|
return 0
|
|
setattr(self, name, f); return f
|
|
NATIVE = "app net player chr chrmgr wndMgr grp grpImage grpText grpThing item skill snd chat background miniMap ime systemSetting guild safebox shop exchange quest messenger textTail nonplayer effect event eventMgr fly dbg profiler ServerStateChecker udp".split()
|
|
for n in NATIVE: sys.modules[n] = Stub(n)
|
|
pack = types.ModuleType("pack")
|
|
def norm(n): return n.replace("\\", "/").lower().lstrip("/")
|
|
pack.Exist = lambda n: norm(n) in FILES
|
|
pack.Get = lambda n: open(FILES[norm(n)], "rb").read()
|
|
sys.modules["pack"] = pack
|
|
sys.modules["dbg"].Trace = sys.modules["dbg"].TraceError = lambda *a: None
|
|
sys.modules["dbg"].LogBox = lambda m: LOG.write("LOGBOX: %s\n" % m)
|
|
sys.modules["app"].Loop = lambda: LOG.write("app.Loop reached\n")
|
|
STR = {"app.GetLocalePath": "locale/en", "app.GetLocaleName": "en", "app.GetLocaleServiceName": "en",
|
|
"app.GetLocalePathCommon": "locale/common"}
|
|
for k, v in STR.items():
|
|
m, f = k.split(".")
|
|
setattr(sys.modules[m], f, (lambda v: lambda *a: v)(v))
|
|
import __builtin__
|
|
__builtin__.TRUE = 1; __builtin__.FALSE = 0 # CPythonLauncher::Create
|
|
# A fresh __main__ like CPythonLauncher::Create (whose __builtins__ is the module, not a dict);
|
|
# system.py and prototype.py exec into it, so they must not share this script's globals.
|
|
__main__ = types.ModuleType("__main__")
|
|
__main__.__builtins__ = __builtin__
|
|
sys.modules["__main__"] = __main__
|
|
__main__.__DEBUG__ = 0
|
|
__main__.__COMMAND_LINE__ = ""
|
|
src = pack.Get("system.py")
|
|
try:
|
|
exec compile(src, "system.py", "exec") in __main__.__dict__
|
|
except BaseException:
|
|
LOG.write("system.py raised:\n" + traceback.format_exc())
|
|
sys.stdout, sys.stderr = sys.__stdout__, sys.__stderr__ # system.py redirects both to dbg
|
|
ok, bad = [], []
|
|
_pimp = __main__.__dict__["__pack_import"]
|
|
for name in sorted(FILES):
|
|
if not name.endswith(".py") or "/" in name: continue
|
|
mod = name[:-3]
|
|
real = [m for m in sys.modules if m.lower() == mod]
|
|
if real: ok.append(mod); continue
|
|
try:
|
|
_pimp(mod); ok.append(mod)
|
|
except BaseException as e:
|
|
bad.append((mod, "%s: %s" % (type(e).__name__, repr(e.args)[:100])))
|
|
try:
|
|
LOG.write("modules loaded %d, failed %d\n" % (len(ok), len(bad)))
|
|
for m, e in bad: LOG.write(" FAIL %s %s\n" % (m, e))
|
|
LOG.write("native functions called during bootstrap: %d\n" % len(MISSING))
|
|
LOG.write(" " + " ".join(sorted(MISSING)) + "\n")
|
|
std = []
|
|
for name, m in sorted(sys.modules.items()):
|
|
if m is None or name in NATIVE or name in ("pack", "__main__"): continue
|
|
try: f = m.__dict__.get("__file__")
|
|
except Exception: continue
|
|
if not isinstance(f, str) and f is not None: continue
|
|
if f is None: std.append("builtin:" + name)
|
|
elif f.endswith(".so"): std.append("ext:" + name)
|
|
elif not f.startswith(E) and not f.startswith("<"): std.append("py:" + name)
|
|
LOG.write("stdlib modules in use: " + " ".join(std) + "\n")
|
|
except BaseException:
|
|
LOG.write("report failed:\n" + traceback.format_exc())
|
|
LOG.close()
|