port(2P step 3a/3b): stdlib on sys.path + the pack module

step 3a — 标准库:
  tools/py_embed/make_stdlib_zip.py packs the vendored Lib/ into
  <build>/python27.zip (397 modules, 6876547 bytes, ZIP_STORED — the zlib
  module is trimmed out of our CPython, so zipimport cannot inflate).
  The mtpython_stdlib target builds it; py_embed_test and the launcher test
  both run against it.
  platform/ScriptLib/PythonHost sets the embedding flags before
  Py_Initialize and, after it, puts the zip on sys.path and re-registers
  encodings.search_function with codecs — 2.7 builds the codec registry
  once, at startup, so replacing sys.path afterwards needs it back.

step 3b — pack 模块:
  UserInterface/PythonPackModule.cpp copied verbatim; packExist/packGet/
  initpack read root/ and uiscript/ through CEterPackManager, i.e. through
  asset_io. The CHINA_CRYPT_KEY block in that unit is CAccountConnector's
  and is kept #if 0 until the login slice (2V1). UserInterface/StdAfx.h is
  a documented partial mirror — the original is the whole client's PCH.

The launcher test now runs initpack() as RunMainScript does (its first
line), reads system.py / uiscript / locale text back out of the packs, and
confirms cp1252 decoding works off the zip. system.py still stops at
"No module named app" — the C++ modules are the 2V0 slice.

gates: port_map.py check 0 errors · key leak check 8/8 none · macOS ctest
27/27 · android + ios port_platform compile clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-23 09:16:37 +09:00
co-authored by Claude Opus 5
parent 43bb561551
commit 2b873f190f
13 changed files with 414 additions and 17 deletions
@@ -0,0 +1,106 @@
// See PythonHost.h: the interpreter setup 40250 got from a real python27 installation.
#include "ScriptLib/StdAfx.h"
#include "PythonHost.h"
#include <cstdio>
#include <cstdlib>
namespace
{
// The pending exception as text, so a failed bootstrap says what went wrong instead of leaving the
// error for the first unrelated script to trip over.
std::string take_error()
{
if (!PyErr_Occurred())
return "no exception";
PyObject *type = NULL, *value = NULL, *tb = NULL;
PyErr_Fetch(&type, &value, &tb);
PyErr_NormalizeException(&type, &value, &tb);
std::string text;
if (PyObject* str = value ? PyObject_Str(value) : NULL)
{
if (PyString_Check(str))
text = PyString_AS_STRING(str);
Py_DECREF(str);
}
if (type && text.empty())
text = ((PyTypeObject*) type)->tp_name;
Py_XDECREF(type);
Py_XDECREF(value);
Py_XDECREF(tb);
return text.empty() ? "unknown error" : text;
}
bool fail(std::string* error, const std::string& text)
{
if (error)
*error = text;
return false;
}
}
namespace PythonHost
{
void Configure(const char* program_name)
{
// site.py lives in a prefix this interpreter does not have, the environment must not be able to
// redirect a game's script layer (PYTHONPATH/PYTHONHOME), the "could not find platform libraries"
// probe has nothing to find, and the pack directories are read-only on the mobile targets.
Py_NoSiteFlag = 1;
Py_IgnoreEnvironmentFlag = 1;
Py_FrozenFlag = 1;
Py_DontWriteBytecodeFlag = 1;
Py_SetProgramName((char*) program_name);
}
bool InstallStdLib(const char* stdlib_path, std::string* error)
{
if (!Py_IsInitialized())
return fail(error, "interpreter is not initialized");
if (!stdlib_path || !*stdlib_path)
return fail(error, "no stdlib path (set MT_PYTHON_STDLIB or pass python27.zip)");
// zipimport and the source importer both go through the C library, so the path has to be a real
// file: a pack entry or an Android asset must be staged out first (step 3c).
if (FILE* fp = fopen(stdlib_path, "rb"))
fclose(fp);
else if (FILE* dir = fopen((std::string(stdlib_path) + "/os.py").c_str(), "rb"))
fclose(dir);
else
return fail(error, std::string("cannot open ") + stdlib_path);
PySys_SetPath((char*) stdlib_path);
// Py_Initialize ran _PyCodecRegistry_Init against the then-empty path and 2.7 never retries it
// ("no codec search functions registered" for the rest of the process), so the search function is
// registered again now that encodings is importable.
PyObject* encodings = PyImport_ImportModule("encodings");
if (!encodings)
return fail(error, "import encodings: " + take_error());
PyObject* search = PyObject_GetAttrString(encodings, (char*) "search_function");
Py_DECREF(encodings);
if (!search)
return fail(error, "encodings.search_function: " + take_error());
PyObject* codecs = PyImport_ImportModule("codecs");
PyObject* registered = codecs ? PyObject_CallMethod(codecs, (char*) "register", (char*) "O", search) : NULL;
Py_DECREF(search);
Py_XDECREF(codecs);
if (!registered)
return fail(error, "codecs.register: " + take_error());
Py_DECREF(registered);
return true;
}
std::string DefaultStdLibPath()
{
if (const char* env = getenv("MT_PYTHON_STDLIB"))
return env;
// Step 3c stages python27.zip out of the packed assets on Android/iOS and answers here; until then
// the caller passes the path (the build tree's copy, for the native tests).
return "";
}
}
@@ -0,0 +1,27 @@
#pragma once
// The interpreter setup 40250 got for free from a real python27 installation.
//
// 40250's Main() only constructs CPythonLauncher: python27.dll found its own prefix, so `import site`,
// the codec registry and every stdlib import worked before a single script ran. The embedded
// interpreter has no prefix and no filesystem to probe, so the host does that part itself —
// the flags before Py_Initialize, and the stdlib (python27.zip) on sys.path afterwards.
// See docs/PORT-PLAN.md 批次 2P step 3.
#include <string>
namespace PythonHost
{
// Before the interpreter starts, i.e. before CPythonLauncher's constructor.
// `program_name` is what CPythonLauncher::Create defaults to; it only names the interpreter.
void Configure(const char* program_name = "eter.python");
// After Py_Initialize: replaces sys.path with `stdlib_path` (a python27.zip or a stdlib directory
// on a real filesystem — zipimport and the .py importer both need a path the C library can open)
// and re-registers the codec search function. False leaves *error set.
bool InstallStdLib(const char* stdlib_path, std::string* error);
// The stdlib path this build was given: $MT_PYTHON_STDLIB when set, else the python27.zip next to
// the executable, else "". Mobile staging (copy out of the app resources, checked against the
// manifest sha256) is 批次 2P step 3c and will answer here too.
std::string DefaultStdLibPath();
}