feat(python): 在 app 进程里起 40250 脚本层(2P step 4,macOS)

移植 UserInterface/RunMainScript 到 platform/ScriptLib/PythonBoot:40250 把
CPythonLauncher 放在 Main() 的栈上,这里进程归 Godot,launcher 必须活过创建它的
那次调用,所以拆成 Start / RunMainScript / RunLine / Stop。37 个模块初始化函数里
只调已移植的 initpack(),system.py 因此仍停在第一个 import。

脚本报错能传回 GDScript,靠的是 platform/EterBase/Debug.cpp 实现 LogBox/LogBoxf:
ScriptLib 的 Traceback() 在 PyErr_Fetch 之后只剩这一条路,LastLogBoxMessage()
(platform/EterBase/LogBox.h)是 traceback 唯一幸存的地方。

GDScript 侧新增 Metin2PythonHost(python_host_node.{h,cpp})——唯一同时认识
godot-cpp 和 port 树的单元,PythonBoot 保持 godot-free。检查脚本
python_host_check.gd 分两段:解释器 + 标准库那段不需要 40250 的 pack,哪个平台都
能跑;system.py 那段需要 pack,没有就跳过。桌面入口 python_host_test.gd,真机入口
MT_TEST_MODE=python(导出模板不接受 --script)。

验证(macOS):标准库可 import、codec 注册表可用、sys.path 非空,system.py 停在和
ctest port.python_launcher 完全相同的 ImportError: No module named app;
ctest 27/27。Android/iOS 进程内未做,正式启动路径仍不起解释器(2V0)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-23 10:43:10 +09:00
co-authored by Claude Opus 5
parent e926759ff5
commit 0a1cdf3d3e
16 changed files with 638 additions and 76 deletions
@@ -0,0 +1,41 @@
#pragma once
// Starting the script layer inside a host process (docs/PORT-PLAN.md 批次 2P step 4).
//
// 40250 runs the interpreter on Main()'s stack (UserInterface/UserInterface.cpp:419-434): construct
// CPythonLauncher, Create(), RunMainScript(), Clear(), all inside one WinMain. Godot owns the process
// here and calls in from GDScript, so the launcher has to outlive the call that starts it — that split
// (Start / RunMainScript / Stop) is the only difference from the original control flow.
//
// It lives under platform/ScriptLib rather than platform/UserInterface because it is only buildable
// with the embedded interpreter, and that is the directory both port_logic and port_platform drop when
// `mtpython` is absent (extension/src/port/CMakeLists.txt).
//
// godot-free, like everything else under platform/: extension/src/python_host_node.cpp is what GDScript
// talks to.
#include <string>
namespace PythonBoot
{
// PythonHost::Configure() -> CPythonLauncher -> Create() -> PythonHost::InstallStdLib(stdlib_path).
// An empty stdlib_path means PythonHost::DefaultStdLibPath(). Starting twice is a no-op that succeeds.
// On failure *error is set and the interpreter is shut down again, so the next attempt starts clean.
// The packs must already be registered (platform/PackBackend.h), as 40250 registers them in Main()
// before the launcher exists.
bool Start(const char* stdlib_path, std::string* error);
bool IsRunning();
// 40250: RunMainScript(CPythonLauncher&, const char*) — the module initializers, __DEBUG__,
// __COMMAND_LINE__ and RunFile("system.py"). Only the initializers that are ported are called; the rest
// arrive with the 2V slices, which is why system.py still stops at its first import.
// False leaves *error set to what the launcher reported through LogBox (platform/EterBase/LogBox.h).
bool RunMainScript(const char* lpCmdLine, std::string* error);
// CPythonLauncher::RunLine, with the reported text handed back rather than only shown. The host's own
// check uses it to prove the interpreter and the standard library work in-process on a device that has
// no 40250 packs, which is where RunMainScript cannot run at all.
bool RunLine(const char* source, std::string* error);
// 40250 Main() calls Clear() and lets the launcher leave scope.
void Stop();
}