port(2P step 3c): stage python27.zip into the mobile sandbox

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>
This commit is contained in:
shenlei
2026-09-23 09:39:33 +09:00
co-authored by Claude Opus 5
parent 2b873f190f
commit ffbb5d48d6
17 changed files with 329 additions and 18 deletions
+8 -3
View File
@@ -11,12 +11,15 @@ 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
3c checks that hash when staging the zip into a mobile sandbox.
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
@@ -51,7 +54,9 @@ def main() -> int:
info.external_attr = 0o644 << 16
zf.writestr(info, path.read_bytes())
tmp.replace(out)
print(f"make_stdlib_zip: {out} ({len(members)} modules, {out.stat().st_size} bytes)")
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