diff options
author | Campbell Barton <ideasman42@gmail.com> | 2020-08-17 10:46:06 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2020-08-17 11:32:20 +0300 |
commit | 7341ceb674b2fc5c01d4328f398516ef8f358ae5 (patch) | |
tree | f755074228baf162cf3d7cc5fcdf8d1ae3193e18 /source/blender/python/intern/bpy_interface_run.c | |
parent | 397cec6a4dad6784604622ee707bb74a2e6a92a1 (diff) |
Cleanup: move Python script execution into BPY_extern_run.h
This commit renames 'execute' to 'run' because:
- This follows Python's "PyRun" which these functions wrap.
- Execution functions can use either exec/eval modes,
making naming awkward (for future API refactoring).
Diffstat (limited to 'source/blender/python/intern/bpy_interface_run.c')
-rw-r--r-- | source/blender/python/intern/bpy_interface_run.c | 391 |
1 files changed, 391 insertions, 0 deletions
diff --git a/source/blender/python/intern/bpy_interface_run.c b/source/blender/python/intern/bpy_interface_run.c new file mode 100644 index 00000000000..f936be2edbf --- /dev/null +++ b/source/blender/python/intern/bpy_interface_run.c @@ -0,0 +1,391 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup pythonintern + */ + +#include <stdio.h> + +#include <Python.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_fileops.h" +#include "BLI_listbase.h" +#include "BLI_path_util.h" +#include "BLI_string.h" + +#include "BKE_context.h" +#include "BKE_main.h" +#include "BKE_text.h" + +#include "DNA_text_types.h" + +#include "BPY_extern.h" +#include "BPY_extern_run.h" + +#include "bpy_capi_utils.h" +#include "bpy_traceback.h" + +#include "../generic/py_capi_utils.h" + +/* -------------------------------------------------------------------- */ +/** \name Private Utilities + * \{ */ + +static void python_script_error_jump_text(Text *text) +{ + int lineno; + int offset; + python_script_error_jump(text->id.name + 2, &lineno, &offset); + if (lineno != -1) { + /* select the line with the error */ + txt_move_to(text, lineno - 1, INT_MAX, false); + txt_move_to(text, lineno - 1, offset, true); + } +} + +/* returns a dummy filename for a textblock so we can tell what file a text block comes from */ +static void bpy_text_filename_get(char *fn, const Main *bmain, size_t fn_len, const Text *text) +{ + BLI_snprintf(fn, fn_len, "%s%c%s", ID_BLEND_PATH(bmain, &text->id), SEP, text->id.name + 2); +} + +static bool python_script_exec( + bContext *C, const char *fn, struct Text *text, struct ReportList *reports, const bool do_jump) +{ + Main *bmain_old = CTX_data_main(C); + PyObject *main_mod = NULL; + PyObject *py_dict = NULL, *py_result = NULL; + PyGILState_STATE gilstate; + + BLI_assert(fn || text); + + if (fn == NULL && text == NULL) { + return 0; + } + + bpy_context_set(C, &gilstate); + + PyC_MainModule_Backup(&main_mod); + + if (text) { + char fn_dummy[FILE_MAXDIR]; + bpy_text_filename_get(fn_dummy, bmain_old, sizeof(fn_dummy), text); + + if (text->compiled == NULL) { /* if it wasn't already compiled, do it now */ + char *buf; + PyObject *fn_dummy_py; + + fn_dummy_py = PyC_UnicodeFromByte(fn_dummy); + + buf = txt_to_buf(text, NULL); + text->compiled = Py_CompileStringObject(buf, fn_dummy_py, Py_file_input, NULL, -1); + MEM_freeN(buf); + + Py_DECREF(fn_dummy_py); + + if (PyErr_Occurred()) { + if (do_jump) { + python_script_error_jump_text(text); + } + BPY_text_free_code(text); + } + } + + if (text->compiled) { + py_dict = PyC_DefaultNameSpace(fn_dummy); + py_result = PyEval_EvalCode(text->compiled, py_dict, py_dict); + } + } + else { + FILE *fp = BLI_fopen(fn, "r"); + + if (fp) { + py_dict = PyC_DefaultNameSpace(fn); + +#ifdef _WIN32 + /* Previously we used PyRun_File to run directly the code on a FILE + * object, but as written in the Python/C API Ref Manual, chapter 2, + * 'FILE structs for different C libraries can be different and + * incompatible'. + * So now we load the script file data to a buffer. + * + * Note on use of 'globals()', it's important not copy the dictionary because + * tools may inspect 'sys.modules["__main__"]' for variables defined in the code + * where using a copy of 'globals()' causes code execution + * to leave the main namespace untouched. see: T51444 + * + * This leaves us with the problem of variables being included, + * currently this is worked around using 'dict.__del__' it's ugly but works. + */ + { + const char *pystring = + "with open(__file__, 'rb') as f:" + "exec(compile(f.read(), __file__, 'exec'), globals().__delitem__('f') or globals())"; + + fclose(fp); + + py_result = PyRun_String(pystring, Py_file_input, py_dict, py_dict); + } +#else + py_result = PyRun_File(fp, fn, Py_file_input, py_dict, py_dict); + fclose(fp); +#endif + } + else { + PyErr_Format( + PyExc_IOError, "Python file \"%s\" could not be opened: %s", fn, strerror(errno)); + py_result = NULL; + } + } + + if (!py_result) { + if (text) { + if (do_jump) { + /* ensure text is valid before use, the script may have freed its self */ + Main *bmain_new = CTX_data_main(C); + if ((bmain_old == bmain_new) && (BLI_findindex(&bmain_new->texts, text) != -1)) { + python_script_error_jump_text(text); + } + } + } + BPy_errors_to_report(reports); + } + else { + Py_DECREF(py_result); + } + + if (py_dict) { +#ifdef PYMODULE_CLEAR_WORKAROUND + PyModuleObject *mmod = (PyModuleObject *)PyDict_GetItem(PyImport_GetModuleDict(), + bpy_intern_str___main__); + PyObject *dict_back = mmod->md_dict; + /* freeing the module will clear the namespace, + * gives problems running classes defined in this namespace being used later. */ + mmod->md_dict = NULL; + Py_DECREF(dict_back); +#endif + +#undef PYMODULE_CLEAR_WORKAROUND + } + + PyC_MainModule_Restore(main_mod); + + bpy_context_clear(C, &gilstate); + + return (py_result != NULL); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Run Text / Filename / String + * \{ */ + +/* Can run a file or text block */ +bool BPY_run_filepath(bContext *C, const char *filepath, struct ReportList *reports) +{ + return python_script_exec(C, filepath, NULL, reports, false); +} + +bool BPY_run_text(bContext *C, struct Text *text, struct ReportList *reports, const bool do_jump) +{ + return python_script_exec(C, NULL, text, reports, do_jump); +} + +bool BPY_run_string_ex(bContext *C, const char *imports[], const char *expr, bool use_eval) +{ + BLI_assert(expr); + PyGILState_STATE gilstate; + PyObject *main_mod = NULL; + PyObject *py_dict, *retval; + bool ok = true; + + if (expr[0] == '\0') { + return ok; + } + + bpy_context_set(C, &gilstate); + + PyC_MainModule_Backup(&main_mod); + + py_dict = PyC_DefaultNameSpace("<blender string>"); + + if (imports && (!PyC_NameSpace_ImportArray(py_dict, imports))) { + Py_DECREF(py_dict); + retval = NULL; + } + else { + retval = PyRun_String(expr, use_eval ? Py_eval_input : Py_file_input, py_dict, py_dict); + } + + if (retval == NULL) { + ok = false; + BPy_errors_to_report(CTX_wm_reports(C)); + } + else { + Py_DECREF(retval); + } + + PyC_MainModule_Restore(main_mod); + + bpy_context_clear(C, &gilstate); + + return ok; +} + +bool BPY_run_string(bContext *C, const char *imports[], const char *expr) +{ + return BPY_run_string_ex(C, imports, expr, true); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Run Python & Evaluate Utilities + * + * Return values as plain C types, useful to run Python scripts + * in code that doesn't deal with Python data-types. + * \{ */ + +/** + * \return success + */ +bool BPY_run_string_as_number(bContext *C, + const char *imports[], + const char *expr, + const char *report_prefix, + double *r_value) +{ + PyGILState_STATE gilstate; + bool ok = true; + + if (!r_value || !expr) { + return -1; + } + + if (expr[0] == '\0') { + *r_value = 0.0; + return ok; + } + + bpy_context_set(C, &gilstate); + + ok = PyC_RunString_AsNumber(imports, expr, "<expr as number>", r_value); + + if (ok == false) { + if (report_prefix != NULL) { + BPy_errors_to_report_ex(CTX_wm_reports(C), report_prefix, false, false); + } + else { + PyErr_Clear(); + } + } + + bpy_context_clear(C, &gilstate); + + return ok; +} + +/** + * \return success + */ +bool BPY_run_string_as_string_and_size(bContext *C, + const char *imports[], + const char *expr, + const char *report_prefix, + char **r_value, + size_t *r_value_size) +{ + BLI_assert(r_value && expr); + PyGILState_STATE gilstate; + bool ok = true; + + if (expr[0] == '\0') { + *r_value = NULL; + return ok; + } + + bpy_context_set(C, &gilstate); + + ok = PyC_RunString_AsStringAndSize(imports, expr, "<expr as str>", r_value, r_value_size); + + if (ok == false) { + if (report_prefix != NULL) { + BPy_errors_to_report_ex(CTX_wm_reports(C), false, false, report_prefix); + } + else { + PyErr_Clear(); + } + } + + bpy_context_clear(C, &gilstate); + + return ok; +} + +bool BPY_run_string_as_string(bContext *C, + const char *imports[], + const char *expr, + const char *report_prefix, + char **r_value) +{ + size_t value_dummy_size; + return BPY_run_string_as_string_and_size( + C, imports, expr, report_prefix, r_value, &value_dummy_size); +} + +/** + * Support both int and pointers. + * + * \return success + */ +bool BPY_run_string_as_intptr(bContext *C, + const char *imports[], + const char *expr, + const char *report_prefix, + intptr_t *r_value) +{ + BLI_assert(r_value && expr); + PyGILState_STATE gilstate; + bool ok = true; + + if (expr[0] == '\0') { + *r_value = 0; + return ok; + } + + bpy_context_set(C, &gilstate); + + ok = PyC_RunString_AsIntPtr(imports, expr, "<expr as intptr>", r_value); + + if (ok == false) { + if (report_prefix != NULL) { + BPy_errors_to_report_ex(CTX_wm_reports(C), report_prefix, false, false); + } + else { + PyErr_Clear(); + } + } + + bpy_context_clear(C, &gilstate); + + return ok; +} + +/** \} */ |