From da2ad0f0bc499edc8f46fdc1c2ba937461aaeda6 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 22 Nov 2017 19:33:17 +0100 Subject: [PATCH] deploy-ng: default to ACP if console codepage codec wasn't frozen This is necessary because when Python is initialized, it takes the codec to use from GetConsoleCP() and GetConsoleOutputCP() without bothering to check whether the given codec is available. However, in most cases, the console codepage will be the same as the ANSI codepage (ie. GetACP()) which is always supported by Python via the 'mbcs' codec. So what we do is we check whether the console codepage is frozen in, and if not, we set the console codepage to the ANSI codepage and set the stdin/stdout/stderr encoding to 'mbcs'. This is still not a perfect solution because the ACP may not be able to encode all characters that the application is printing, which would still result in unexpected errors. Ideally, we'd pull in Python 3.6's _io._WindowsConsoleIO class, which bypasses this whole mess by directly using the wide-character Windows APIs to write to the console. --- direct/src/showutil/FreezeTool.py | 2 + pandatool/src/deploy-stub/deploy-stub.c | 66 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 38c50fa11b..a8a967f93a 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -30,6 +30,8 @@ isDebugBuild = (python.lower().endswith('_d')) # These are modules that Python always tries to import up-front. They # must be frozen in any main.exe. +# NB. if encodings are removed, be sure to remove them from the shortcut in +# deploy-stub.c. startupModules = [ 'encodings', 'encodings.aliases', 'encodings.undefined', 'encodings.ascii', 'encodings.cp1252', 'encodings.latin_1', 'encodings.utf_8', diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index d35d3f59a9..51d7da88a9 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -38,6 +38,32 @@ static struct _inittab extensions[] = { #endif #endif +#if defined(_WIN32) && PY_VERSION_HEX < 0x03060000 +static int supports_code_page(UINT cp) { + /* Shortcut, because we know that these encodings are bundled by default-- + * see FreezeTool.py and Python's encodings/aliases.py */ + if (cp != 0 && cp != 1252 && cp != 367 && cp != 437 && cp != 850 && cp != 819) { + const struct _frozen *moddef; + char codec[100]; + + /* Check if the codec was frozen into the program. We can't check this + * using _PyCodec_Lookup, since Python hasn't been initialized yet. */ + PyOS_snprintf(codec, sizeof(codec), "encodings.cp%u", (unsigned int)cp); + + moddef = PyImport_FrozenModules; + while (moddef->name) { + if (strcmp(moddef->name, codec) == 0) { + return 1; + } + ++moddef; + } + return 0; + } + + return 1; +} +#endif + /* Main program */ #ifdef WIN_UNICODE @@ -64,6 +90,19 @@ int Py_FrozenMain(int argc, char **argv) } #endif +#if defined(MS_WINDOWS) && PY_VERSION_HEX >= 0x03040000 && PY_VERSION_HEX < 0x03060000 + if (!supports_code_page(GetConsoleOutputCP()) || + !supports_code_page(GetConsoleCP())) { + /* Revert to the active codepage, and tell Python to use the 'mbcs' + * encoding (which always uses the active codepage). In 99% of cases, + * this will be the same thing anyway. */ + UINT acp = GetACP(); + SetConsoleCP(acp); + SetConsoleOutputCP(acp); + Py_SetStandardStreamEncoding("mbcs", NULL); + } +#endif + Py_FrozenFlag = 1; /* Suppress errors from getpath.c */ Py_NoSiteFlag = 1; Py_NoUserSiteDirectory = 1; @@ -112,6 +151,33 @@ int Py_FrozenMain(int argc, char **argv) PyWinFreeze_ExeInit(); #endif +#if defined(MS_WINDOWS) && PY_VERSION_HEX < 0x03040000 + if (!supports_code_page(GetConsoleOutputCP()) || + !supports_code_page(GetConsoleCP())) { + /* Same hack as before except for Python 2.7, which doesn't seem to have + * a way to set the encoding ahead of time, and setting PYTHONIOENCODING + * doesn't seem to work. Fortunately, Python 2.7 doesn't usually start + * causing codec errors until the first print statement. */ + PyObject *sys_stream; + UINT acp = GetACP(); + SetConsoleCP(acp); + SetConsoleOutputCP(acp); + + sys_stream = PySys_GetObject("stdin"); + if (sys_stream && PyFile_Check(sys_stream)) { + PyFile_SetEncodingAndErrors(sys_stream, "mbcs", NULL); + } + sys_stream = PySys_GetObject("stdout"); + if (sys_stream && PyFile_Check(sys_stream)) { + PyFile_SetEncodingAndErrors(sys_stream, "mbcs", NULL); + } + sys_stream = PySys_GetObject("stderr"); + if (sys_stream && PyFile_Check(sys_stream)) { + PyFile_SetEncodingAndErrors(sys_stream, "mbcs", NULL); + } + } +#endif + if (Py_VerboseFlag) fprintf(stderr, "Python %s\n%s\n", Py_GetVersion(), Py_GetCopyright());