CPython opens the stdlib zip with its own stdio, so it needs a real path.
On the desktop res:// is a directory and globalize_path() is enough; in an
exported build res:// is inside the PCK, which nothing outside Godot reads.
make_stdlib_zip.py now also writes <out>.sha256
mtpython_stdlib_project copies python27.zip + .sha256 into project/
(gitignored), and both export presets' include_filter lists them, so
they enter the PCK — checked with --export-pack
extension/src/python_stdlib.{h,cpp} (Metin2Python.stdlib_path in
GDScript): reads the bytes out of the PCK, writes
user://python27.zip.part, hashes the file as written against the
shipped digest, then renames. A sandbox copy is reused only when it
matches, so a first start killed mid-copy and a zip replaced by a new
build both re-stage instead of feeding zipimport a torn file.
PythonHost::SetDefaultStdLibPath / DefaultStdLibPath() answer with that
path ($MT_PYTHON_STDLIB still wins). python_stdlib.cpp is the only unit
that knows res:// / user://; port_platform stays godot-free.
Also fixes the Windows gate, which step 3b broke: without mtpython the
MinGW build compiled UserInterface/StdAfx.h (it includes ScriptLib/StdAfx.h,
as the original PCH does). port/CMakeLists.txt excludes that header and
PythonPackModule.cpp with ScriptLib, and platform/CMakeLists.txt excludes
platform/ScriptLib/ the same way.
Not done: on-device staging. The macOS test drives the mobile path with
force_stage=true, and nothing in the app boot calls it yet — the
interpreter only starts in the process with 2V0.
gates: python_stdlib_test.gd PASS (in-place path, staging, sha256, bytes
equal res://, no .part, ZIPReader finds encodings/__init__.py, no re-copy
on a second call, corrupted copy re-staged) · macOS ctest 27/27 ·
mingw + android + ios port_platform compile clean · port_map.py check 0
errors · key leak check 8/8 none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
65 lines
2.8 KiB
Python
65 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Pack the vendored CPython 2.7.18 Lib/ into the python27.zip the host puts on sys.path.
|
|
|
|
make_stdlib_zip.py <Lib dir> <out.zip>
|
|
|
|
40250 shipped a real python27 installation, so `import encodings` found Lib/ through the
|
|
interpreter's prefix. The embedded interpreter has no prefix (Py_FrozenFlag, no site), so the host
|
|
supplies the stdlib as this one file — the arrangement validated on the device by
|
|
tools/py_embed_android/build-and-run.sh (PORT-PLAN 批次 2P step 3).
|
|
|
|
Entries are STORED, never deflated: the zlib module is not in the vendored Modules/ (see
|
|
docs/THIRD-PARTY.md), so zipimport could not inflate them. Only .py goes in; zipimport reads
|
|
source as happily as bytecode, and .pyc would pin the magic number to the build host's interpreter.
|
|
Written deterministically (sorted, fixed timestamp) so the file's sha256 identifies its contents.
|
|
The hash is written next to the zip as <out>.sha256 and ships with it: on Android/iOS the zip lives
|
|
in the read-only bundle and has to be copied into the sandbox before CPython can open it, and that
|
|
copy is accepted only when it hashes to this value (extension/src/python_stdlib.cpp, step 3c).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import sys
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
# Test suites, the Tk/2to3/bsddb/pip trees and the platform-specific stubs: none of the 40250 scripts
|
|
# or their imports reach them, and they are most of the size.
|
|
SKIP_DIRS = {"test", "tests", "lib-tk", "idlelib", "lib2to3", "bsddb", "ensurepip", "curses",
|
|
"distutils", "unittest", "wsgiref", "multiprocessing", "compiler"}
|
|
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) != 3:
|
|
print(__doc__, file=sys.stderr)
|
|
return 2
|
|
lib, out = Path(sys.argv[1]), Path(sys.argv[2])
|
|
if not (lib / "os.py").is_file():
|
|
print(f"make_stdlib_zip: {lib} is not a CPython Lib/ directory", file=sys.stderr)
|
|
return 1
|
|
|
|
members = []
|
|
for path in sorted(lib.rglob("*.py")):
|
|
rel = path.relative_to(lib)
|
|
if rel.parts[0].startswith("plat-") or SKIP_DIRS.intersection(rel.parts[:-1]):
|
|
continue
|
|
members.append((rel, path))
|
|
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
tmp = out.with_suffix(".zip.tmp")
|
|
with zipfile.ZipFile(tmp, "w", zipfile.ZIP_STORED) as zf:
|
|
for rel, path in members:
|
|
info = zipfile.ZipInfo(rel.as_posix(), date_time=(1980, 1, 1, 0, 0, 0))
|
|
info.external_attr = 0o644 << 16
|
|
zf.writestr(info, path.read_bytes())
|
|
tmp.replace(out)
|
|
digest = hashlib.sha256(out.read_bytes()).hexdigest()
|
|
out.with_name(out.name + ".sha256").write_text(digest + "\n")
|
|
print(f"make_stdlib_zip: {out} ({len(members)} modules, {out.stat().st_size} bytes, sha256 {digest})")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|