diff options
-rw-r--r-- | source/blender/python/generic/py_capi_utils.c | 34 | ||||
-rw-r--r-- | source/blender/python/generic/py_capi_utils.h | 2 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_interface.c | 211 |
3 files changed, 131 insertions, 116 deletions
diff --git a/source/blender/python/generic/py_capi_utils.c b/source/blender/python/generic/py_capi_utils.c index ec6b8c54ac0..1eb4a51c392 100644 --- a/source/blender/python/generic/py_capi_utils.c +++ b/source/blender/python/generic/py_capi_utils.c @@ -879,40 +879,6 @@ void PyC_MainModule_Restore(PyObject *main_mod) Py_XDECREF(main_mod); } -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name #Py_SetPythonHome Wrapper - * \{ */ - -/** - * - Must be called before #Py_Initialize. - * - Expects output of `BKE_appdir_folder_id(BLENDER_PYTHON, NULL)`. - * - Note that the `PYTHONPATH` environment variable isn't reliable, see T31506. - * Use #Py_SetPythonHome instead. - */ -void PyC_SetHomePath(const char *py_path_bundle) -{ -# ifdef __APPLE__ - /* OSX allow file/directory names to contain : character (represented as / in the Finder) - * but current Python lib (release 3.1.1) doesn't handle these correctly */ - if (strchr(py_path_bundle, ':')) { - fprintf(stderr, - "Warning! Blender application is located in a path containing ':' or '/' chars\n" - "This may make python import function fail\n"); - } -# endif - - /* Set the environment path. */ - wchar_t py_path_bundle_wchar[1024]; - - /* Can't use `mbstowcs` on linux gives bug: T23018. */ - BLI_strncpy_wchar_from_utf8( - py_path_bundle_wchar, py_path_bundle, ARRAY_SIZE(py_path_bundle_wchar)); - - Py_SetPythonHome(py_path_bundle_wchar); -} - bool PyC_IsInterpreterActive(void) { /* instead of PyThreadState_Get, which calls Py_FatalError */ diff --git a/source/blender/python/generic/py_capi_utils.h b/source/blender/python/generic/py_capi_utils.h index 358123657c7..aacc5dd7bea 100644 --- a/source/blender/python/generic/py_capi_utils.h +++ b/source/blender/python/generic/py_capi_utils.h @@ -88,8 +88,6 @@ bool PyC_NameSpace_ImportArray(PyObject *py_dict, const char *imports[]); void PyC_MainModule_Backup(PyObject **r_main_mod); void PyC_MainModule_Restore(PyObject *main_mod); -void PyC_SetHomePath(const char *py_path_bundle); - bool PyC_IsInterpreterActive(void); void *PyC_RNA_AsPointer(PyObject *value, const char *type_name); diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c index c4789407e4e..311612e1499 100644 --- a/source/blender/python/intern/bpy_interface.c +++ b/source/blender/python/intern/bpy_interface.c @@ -304,100 +304,150 @@ static struct _inittab bpy_internal_modules[] = { {NULL, NULL}, }; +/** + * Convenience function for #BPY_python_start. + * + * These should happen so rarely that having comprehensive errors isn't needed. + * For example if `sys.argv` fails to allocate memory. + * + * Show an error just to avoid silent failure in the unlikely event something goes wrong, + * in this case a developer will need to track down the root cause. + */ +static void pystatus_exit_on_error(PyStatus status) +{ + if (UNLIKELY(PyStatus_Exception(status))) { + fputs("Internal error initializing Python!\n", stderr); + /* This calls `exit`. */ + Py_ExitStatusException(status); + } +} + /* call BPY_context_set first */ void BPY_python_start(bContext *C, int argc, const char **argv) { #ifndef WITH_PYTHON_MODULE - PyThreadState *py_tstate = NULL; - /* Needed for Python's initialization for portable Python installations. - * We could use #Py_SetPath, but this overrides Python's internal logic - * for calculating it's own module search paths. - * - * `sys.executable` is overwritten after initialization to the Python binary. */ + /* #PyPreConfig (early-configuration). */ { - const char *program_path = BKE_appdir_program_path(); - wchar_t program_path_wchar[FILE_MAX]; - BLI_strncpy_wchar_from_utf8(program_path_wchar, program_path, ARRAY_SIZE(program_path_wchar)); - Py_SetProgramName(program_path_wchar); - } - - /* must run before python initializes */ - PyImport_ExtendInittab(bpy_internal_modules); + PyPreConfig preconfig; + PyStatus status; - /* Allow to use our own included Python. `py_path_bundle` may be NULL. */ - { - const char *py_path_bundle = BKE_appdir_folder_id(BLENDER_SYSTEM_PYTHON, NULL); - if (py_path_bundle != NULL) { - PyC_SetHomePath(py_path_bundle); + if (py_use_system_env) { + PyPreConfig_InitPythonConfig(&preconfig); } else { - /* Common enough to use the system Python on Linux/Unix, warn on other systems. */ -# if defined(__APPLE__) || defined(_WIN32) - fprintf(stderr, - "Bundled Python not found and is expected on this platform " - "(the 'install' target may have not been built)\n"); -# endif + /* Only use the systems environment variables and site when explicitly requested. + * Since an incorrect 'PYTHONPATH' causes difficult to debug errors, see: T72807. + * An alternative to setting `preconfig.use_environment = 0` */ + PyPreConfig_InitIsolatedConfig(&preconfig); } - } - - /* Force `utf-8` on all platforms, since this is what's used for Blender's internal strings, - * providing consistent encoding behavior across all Blender installations. - * - * This also uses the `surrogateescape` error handler ensures any unexpected bytes are escaped - * instead of raising an error. - * - * Without this `sys.getfilesystemencoding()` and `sys.stdout` for example may be set to ASCII - * or some other encoding - where printing some `utf-8` values will raise an error. - * - * This can cause scripts to fail entirely on some systems. - * - * This assignment is the equivalent of enabling the `PYTHONUTF8` environment variable. - * See `PEP-540` for details on exactly what this changes. */ - Py_UTF8Mode = 1; - /* Suppress error messages when calculating the module search path. - * While harmless, it's noisy. */ - Py_FrozenFlag = 1; - - /* Only use the systems environment variables and site when explicitly requested. - * Since an incorrect 'PYTHONPATH' causes difficult to debug errors, see: T72807. */ - Py_IgnoreEnvironmentFlag = !py_use_system_env; - Py_NoUserSiteDirectory = !py_use_system_env; - - /* Initialize Python (also acquires lock). */ - Py_Initialize(); + /* Force `utf-8` on all platforms, since this is what's used for Blender's internal strings, + * providing consistent encoding behavior across all Blender installations. + * + * This also uses the `surrogateescape` error handler ensures any unexpected bytes are escaped + * instead of raising an error. + * + * Without this `sys.getfilesystemencoding()` and `sys.stdout` for example may be set to ASCII + * or some other encoding - where printing some `utf-8` values will raise an error. + * + * This can cause scripts to fail entirely on some systems. + * + * This assignment is the equivalent of enabling the `PYTHONUTF8` environment variable. + * See `PEP-540` for details on exactly what this changes. */ + preconfig.utf8_mode = true; + + /* Note that there is no reason to call #Py_PreInitializeFromBytesArgs here + * as this is only used so that command line arguments can be handled by Python itself, + * not for setting `sys.argv` (handled below). */ + status = Py_PreInitialize(&preconfig); + pystatus_exit_on_error(status); + } + + /* Must run before python initializes, but after #PyPreConfig. */ + PyImport_ExtendInittab(bpy_internal_modules); - /* We could convert to #wchar_t then pass to #PySys_SetArgv (or use #PyConfig in Python 3.8+). - * However this risks introducing subtle changes in encoding that are hard to track down. - * - * So rely on #PyC_UnicodeFromByte since it's a tried & true way of getting paths - * that include non `utf-8` compatible characters, see: T20021. */ + /* #PyConfig (initialize Python). */ { - PyObject *py_argv = PyList_New(argc); - for (int i = 0; i < argc; i++) { - PyList_SET_ITEM(py_argv, i, PyC_UnicodeFromByte(argv[i])); + PyConfig config; + PyStatus status; + bool has_python_executable = false; + + PyConfig_InitPythonConfig(&config); + + /* Suppress error messages when calculating the module search path. + * While harmless, it's noisy. */ + config.pathconfig_warnings = 0; + + /* When using the system's Python, allow the site-directory as well. */ + config.user_site_directory = py_use_system_env; + + /* While `sys.argv` is set, we don't want Python to interpret it. */ + config.parse_argv = 0; + status = PyConfig_SetBytesArgv(&config, argc, (char *const *)argv); + pystatus_exit_on_error(status); + + /* Needed for Python's initialization for portable Python installations. + * We could use #Py_SetPath, but this overrides Python's internal logic + * for calculating it's own module search paths. + * + * `sys.executable` is overwritten after initialization to the Python binary. */ + { + const char *program_path = BKE_appdir_program_path(); + status = PyConfig_SetBytesString(&config, &config.program_name, program_path); + pystatus_exit_on_error(status); } - PySys_SetObject("argv", py_argv); - Py_DECREF(py_argv); - } - /* Setting the program name is important so the 'multiprocessing' module - * can launch new Python instances. */ - { - const char *sys_variable = "executable"; - char program_path[FILE_MAX]; - if (BKE_appdir_program_python_search( - program_path, sizeof(program_path), PY_MAJOR_VERSION, PY_MINOR_VERSION)) { - PyObject *py_program_path = PyC_UnicodeFromByte(program_path); - PySys_SetObject(sys_variable, py_program_path); - Py_DECREF(py_program_path); + /* Setting the program name is important so the 'multiprocessing' module + * can launch new Python instances. */ + { + char program_path[FILE_MAX]; + if (BKE_appdir_program_python_search( + program_path, sizeof(program_path), PY_MAJOR_VERSION, PY_MINOR_VERSION)) { + status = PyConfig_SetBytesString(&config, &config.executable, program_path); + pystatus_exit_on_error(status); + has_python_executable = true; + } + else { + /* Set to `sys.executable = None` below (we can't do before Python is initialized). */ + fprintf(stderr, + "Unable to find the python binary, " + "the multiprocessing module may not be functional!\n"); + } } - else { - fprintf(stderr, - "Unable to find the python binary, " - "the multiprocessing module may not be functional!\n"); - PySys_SetObject(sys_variable, Py_None); + + /* Allow to use our own included Python. `py_path_bundle` may be NULL. */ + { + const char *py_path_bundle = BKE_appdir_folder_id(BLENDER_SYSTEM_PYTHON, NULL); +# ifdef __APPLE__ + /* OSX allow file/directory names to contain : character (represented as / in the Finder) + * but current Python lib (release 3.1.1) doesn't handle these correctly */ + if (strchr(py_path_bundle, ':')) { + fprintf(stderr, + "Warning! Blender application is located in a path containing ':' or '/' chars\n" + "This may make python import function fail\n"); + } +# endif + if (py_path_bundle != NULL) { + status = PyConfig_SetBytesString(&config, &config.home, py_path_bundle); + pystatus_exit_on_error(status); + } + else { + /* Common enough to use the system Python on Linux/Unix, warn on other systems. */ +# if defined(__APPLE__) || defined(_WIN32) + fprintf(stderr, + "Bundled Python not found and is expected on this platform " + "(the 'install' target may have not been built)\n"); +# endif + } + } + + /* Initialize Python (also acquires lock). */ + status = Py_InitializeFromConfig(&config); + pystatus_exit_on_error(status); + + if (!has_python_executable) { + PySys_SetObject("executable", Py_None); } } @@ -447,8 +497,9 @@ void BPY_python_start(bContext *C, int argc, const char **argv) /* py module runs atexit when bpy is freed */ BPY_atexit_register(); /* this can init any time */ - py_tstate = PyGILState_GetThisThreadState(); - PyEval_ReleaseThread(py_tstate); + /* Free the lock acquired (implicitly) when Python is initialized. */ + PyEval_ReleaseThread(PyGILState_GetThisThreadState()); + #endif #ifdef WITH_PYTHON_MODULE |