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
+16 -2
View File
@@ -312,8 +312,22 @@ extension/third_party/cpython-2.7.18/ # 静态库(2P 批次
`locale/en/locale_game.txt`,非 `.py/.pyc/.txt` 和不存在的文件返回 `None`
`UserInterface/StdAfx.h` 只镜像了当前能编的部分(原文件 include 整个客户端和 DirectShow),
`PythonPackModule.cpp` 里属于 `CAccountConnector` 的登录密钥函数原样保留但编译时排除,随 2V1 回来。
**完成(3c**Android/iOS 从应用资源把 zip 复制进沙盒并校验 sha256;`PythonHost::DefaultStdLibPath()`
现在只认 `MT_PYTHON_STDLIB`
**完成(3c 落盘**`make_stdlib_zip.py` 额外写出 `python27.zip.sha256`;构建把两个文件
`copy_if_different``project/`(目标 `mtpython_stdlib_project`,两文件 gitignore),
导出预设的 `include_filter` 列出它们,所以它们进 PCK——`--export-pack` 验证过两个条目都在。
`extension/src/python_stdlib.{h,cpp}`GDScript 侧 `Metin2Python.stdlib_path(force_stage)`):
桌面 `res://` 就是磁盘目录,`globalize_path` 得到真实路径直接用,不复制;导出后 `res://` 在 PCK 里,
于是读出字节 → 写 `user://python27.zip.part` → 按随包 sha256 校验**落盘后的文件**(不是内存里的 buffer,
磁盘满要在这里报,而不是三个 import 之后变成 zipimport 失败)→ rename 成 `user://python27.zip`
已经在沙盒里且 sha 相符就直接复用;被杀在半路的半成品或换了一版构建的旧 zip 都会重新落盘。
路径交给 `PythonHost::SetDefaultStdLibPath()``DefaultStdLibPath()` 从此答得出(`MT_PYTHON_STDLIB` 仍优先)。
测试 `project/python_stdlib_test.gd``godot --headless --path project --script python_stdlib_test.gd`)在
macOS 上用 `force_stage=true` 跑的就是移动端那条路径:落盘、sha 一致、字节与 `res://` 相同、`.part` 不残留、
`ZIPReader` 能打开且含 `encodings/__init__.py`,重复调用不重复复制,人为弄坏副本后重新落盘。
**未完成**:真机上跑(Android/iOS 的 `user://` 沙盒路径本身没验过),随 step 4 一起;
app 的启动流程还没有调用它——解释器要到 2V0 才在进程里起来,现在只有测试调。
`port_platform` 现在在没有 `mtpython` 时也会排除 `platform/ScriptLib/`(与 `port_logic` 同一条规则),
否则 Windows 门禁会编到 `PythonHost.cpp`
4. 五个平台分别验证:编译、链接无未定义符号、`Py_Initialize`、静态 C 模块逐个 `import`、在 app 进程里跑 `system.py``prototype.RunApp()`(对照结果:74 个模块中 66 个加载成功,引导期调用 33 个 C++ 函数)。
| 平台 | 状态 |
+4 -1
View File
@@ -104,7 +104,10 @@ one via a 1-line shim header, see below).
packs it into `<build>/python27.zip` with `tools/py_embed/make_stdlib_zip.py`,
which is what the host puts on `sys.path` (batch 2P step 3). Entries are
**stored, never deflated** — the `zlib` module is trimmed out above, so
`zipimport` has nothing to inflate with.
`zipimport` has nothing to inflate with. The zip and its `.sha256` are copied
into `project/` (gitignored) so the exporter packs them; on Android/iOS
`extension/src/python_stdlib.cpp` stages the zip out of the PCK into `user://`
and checks that digest, because CPython opens it with its own stdio.
* **Why vendored:** 2.7 is end-of-life, so there is no package to fetch on the
four target SDKs, and the build must keep working offline and on the
cross-build machines. It is the version the 40250 scripts are written for