From 7341ceb674b2fc5c01d4328f398516ef8f358ae5 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 17 Aug 2020 17:46:06 +1000 Subject: 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). --- source/blender/editors/interface/interface.c | 8 +- .../editors/interface/interface_context_menu.c | 7 +- .../editors/interface/interface_region_tooltip.c | 12 +- source/blender/editors/space_script/script_edit.c | 7 +- source/blender/editors/space_text/text_ops.c | 3 +- source/blender/editors/util/numinput.c | 6 +- .../freestyle/intern/system/PythonInterpreter.h | 10 +- source/blender/python/BPY_extern.h | 34 -- source/blender/python/BPY_extern_run.h | 68 ++++ source/blender/python/intern/CMakeLists.txt | 1 + source/blender/python/intern/bpy_interface.c | 332 +---------------- source/blender/python/intern/bpy_interface_run.c | 391 +++++++++++++++++++++ source/blender/windowmanager/intern/wm.c | 3 +- source/blender/windowmanager/intern/wm_files.c | 8 +- 14 files changed, 497 insertions(+), 393 deletions(-) create mode 100644 source/blender/python/BPY_extern_run.h create mode 100644 source/blender/python/intern/bpy_interface_run.c (limited to 'source/blender') diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index 2ee1ecccd56..41e7db3a38c 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -69,7 +69,9 @@ #include "RNA_access.h" -#include "BPY_extern.h" +#ifdef WITH_PYTHON +# include "BPY_extern_run.h" +#endif #include "ED_numinput.h" #include "ED_screen.h" @@ -2812,7 +2814,7 @@ char *ui_but_string_get_dynamic(uiBut *but, int *r_str_size) } /** - * Report a generic error prefix when evaluating a string with #BPY_execute_string_as_number + * Report a generic error prefix when evaluating a string with #BPY_run_string_as_number * as the Python error on it's own doesn't provide enough context. */ #define UI_NUMBER_EVAL_ERROR_PREFIX IFACE_("Error evaluating number, see Info editor for details") @@ -2837,7 +2839,7 @@ static bool ui_number_from_string(bContext *C, const char *str, double *r_value) { bool ok; #ifdef WITH_PYTHON - ok = BPY_execute_string_as_number(C, NULL, str, UI_NUMBER_EVAL_ERROR_PREFIX, r_value); + ok = BPY_run_string_as_number(C, NULL, str, UI_NUMBER_EVAL_ERROR_PREFIX, r_value); #else UNUSED_VARS(C); *r_value = atof(str); diff --git a/source/blender/editors/interface/interface_context_menu.c b/source/blender/editors/interface/interface_context_menu.c index 46876fca9a5..59178e209db 100644 --- a/source/blender/editors/interface/interface_context_menu.c +++ b/source/blender/editors/interface/interface_context_menu.c @@ -47,7 +47,10 @@ #include "RNA_access.h" -#include "BPY_extern.h" +#ifdef WITH_PYTHON +# include "BPY_extern.h" +# include "BPY_extern_run.h" +#endif #include "WM_api.h" #include "WM_types.h" @@ -407,7 +410,7 @@ static void ui_but_user_menu_add(bContext *C, uiBut *but, bUserMenu *um) "'%s').label", idname); char *expr_result = NULL; - if (BPY_execute_string_as_string(C, expr_imports, expr, __func__, &expr_result)) { + if (BPY_run_string_as_string(C, expr_imports, expr, __func__, &expr_result)) { STRNCPY(drawstr, expr_result); MEM_freeN(expr_result); } diff --git a/source/blender/editors/interface/interface_region_tooltip.c b/source/blender/editors/interface/interface_region_tooltip.c index 41b41cb3d75..c324e27dff9 100644 --- a/source/blender/editors/interface/interface_region_tooltip.c +++ b/source/blender/editors/interface/interface_region_tooltip.c @@ -63,7 +63,7 @@ #include "BLT_translation.h" #ifdef WITH_PYTHON -# include "BPY_extern.h" +# include "BPY_extern_run.h" #endif #include "ED_screen.h" @@ -433,7 +433,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is if (has_valid_context == false) { expr_result = BLI_strdup(has_valid_context_error); } - else if (BPY_execute_string_as_string(C, expr_imports, expr, __func__, &expr_result)) { + else if (BPY_run_string_as_string(C, expr_imports, expr, __func__, &expr_result)) { if (STREQ(expr_result, "")) { MEM_freeN(expr_result); expr_result = NULL; @@ -490,7 +490,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is if (has_valid_context == false) { expr_result = BLI_strdup(has_valid_context_error); } - else if (BPY_execute_string_as_string(C, expr_imports, expr, __func__, &expr_result)) { + else if (BPY_run_string_as_string(C, expr_imports, expr, __func__, &expr_result)) { if (STREQ(expr_result, ".")) { MEM_freeN(expr_result); expr_result = NULL; @@ -594,7 +594,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is if (has_valid_context == false) { shortcut = BLI_strdup(has_valid_context_error); } - else if (BPY_execute_string_as_intptr(C, expr_imports, expr, __func__, &expr_result)) { + else if (BPY_run_string_as_intptr(C, expr_imports, expr, __func__, &expr_result)) { if (expr_result != 0) { wmKeyMap *keymap = (wmKeyMap *)expr_result; LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) { @@ -658,7 +658,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is if (has_valid_context == false) { /* pass */ } - else if (BPY_execute_string_as_string_and_size( + else if (BPY_run_string_as_string_and_size( C, expr_imports, expr, __func__, &expr_result, &expr_result_len)) { /* pass. */ } @@ -736,7 +736,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is if (has_valid_context == false) { /* pass */ } - else if (BPY_execute_string_as_intptr(C, expr_imports, expr, __func__, &expr_result)) { + else if (BPY_run_string_as_intptr(C, expr_imports, expr, __func__, &expr_result)) { if (expr_result != 0) { { uiTooltipField *field = text_field_add(data, diff --git a/source/blender/editors/space_script/script_edit.c b/source/blender/editors/space_script/script_edit.c index e9ed1cec228..f9d7e8371b2 100644 --- a/source/blender/editors/space_script/script_edit.c +++ b/source/blender/editors/space_script/script_edit.c @@ -42,7 +42,7 @@ #include "script_intern.h" // own include #ifdef WITH_PYTHON -# include "BPY_extern.h" /* BPY_script_exec */ +# include "BPY_extern_run.h" #endif static int run_pyfile_exec(bContext *C, wmOperator *op) @@ -50,7 +50,7 @@ static int run_pyfile_exec(bContext *C, wmOperator *op) char path[512]; RNA_string_get(op->ptr, "filepath", path); #ifdef WITH_PYTHON - if (BPY_execute_filepath(C, path, op->reports)) { + if (BPY_run_filepath(C, path, op->reports)) { ARegion *region = CTX_wm_region(C); ED_region_tag_redraw(region); return OPERATOR_FINISHED; @@ -120,8 +120,7 @@ static int script_reload_exec(bContext *C, wmOperator *op) /* TODO, this crashes on netrender and keying sets, need to look into why * disable for now unless running in debug mode */ WM_cursor_wait(1); - BPY_execute_string( - C, (const char *[]){"bpy", NULL}, "bpy.utils.load_scripts(reload_scripts=True)"); + BPY_run_string(C, (const char *[]){"bpy", NULL}, "bpy.utils.load_scripts(reload_scripts=True)"); WM_cursor_wait(0); WM_event_add_notifier(C, NC_WINDOW, NULL); return OPERATOR_FINISHED; diff --git a/source/blender/editors/space_text/text_ops.c b/source/blender/editors/space_text/text_ops.c index 201f9dae5d5..688dce3c54e 100644 --- a/source/blender/editors/space_text/text_ops.c +++ b/source/blender/editors/space_text/text_ops.c @@ -55,6 +55,7 @@ #ifdef WITH_PYTHON # include "BPY_extern.h" +# include "BPY_extern_run.h" #endif #include "text_format.h" @@ -756,7 +757,7 @@ static int text_run_script(bContext *C, ReportList *reports) void *curl_prev = text->curl; int curc_prev = text->curc; - if (BPY_execute_text(C, text, reports, !is_live)) { + if (BPY_run_text(C, text, reports, !is_live)) { if (is_live) { /* for nice live updates */ WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL); diff --git a/source/blender/editors/util/numinput.c b/source/blender/editors/util/numinput.c index 384da6fb931..041b2fec00b 100644 --- a/source/blender/editors/util/numinput.c +++ b/source/blender/editors/util/numinput.c @@ -38,7 +38,7 @@ #include "WM_types.h" #ifdef WITH_PYTHON -# include "BPY_extern.h" +# include "BPY_extern_run.h" #endif #include "ED_numinput.h" @@ -294,10 +294,10 @@ bool user_string_to_number(bContext *C, bUnit_ReplaceString( str_unit_convert, sizeof(str_unit_convert), str, unit_scale, unit->system, type); - return BPY_execute_string_as_number(C, NULL, str_unit_convert, error_prefix, r_value); + return BPY_run_string_as_number(C, NULL, str_unit_convert, error_prefix, r_value); } - int success = BPY_execute_string_as_number(C, NULL, str, error_prefix, r_value); + int success = BPY_run_string_as_number(C, NULL, str, error_prefix, r_value); *r_value *= bUnit_PreferredInputUnitScalar(unit, type); *r_value /= unit_scale; return success; diff --git a/source/blender/freestyle/intern/system/PythonInterpreter.h b/source/blender/freestyle/intern/system/PythonInterpreter.h index bae69aa0a42..e2022c1de85 100644 --- a/source/blender/freestyle/intern/system/PythonInterpreter.h +++ b/source/blender/freestyle/intern/system/PythonInterpreter.h @@ -42,7 +42,7 @@ extern "C" { #include "BKE_report.h" #include "BKE_text.h" -#include "BPY_extern.h" +#include "BPY_extern_run.h" #include "bpy_capi_utils.h" @@ -68,12 +68,12 @@ class PythonInterpreter : public Interpreter { BKE_reports_clear(reports); char *fn = const_cast(filename.c_str()); #if 0 - bool ok = BPY_execute_filepath(_context, fn, reports); + bool ok = BPY_run_filepath(_context, fn, reports); #else bool ok; Text *text = BKE_text_load(&_freestyle_bmain, fn, G_MAIN->name); if (text) { - ok = BPY_execute_text(_context, text, reports, false); + ok = BPY_run_text(_context, text, reports, false); BKE_id_delete(&_freestyle_bmain, text); } else { @@ -102,7 +102,7 @@ class PythonInterpreter : public Interpreter { BKE_reports_clear(reports); - if (!BPY_execute_string(_context, NULL, str.c_str())) { + if (!BPY_run_string(_context, NULL, str.c_str())) { BPy_errors_to_report(reports); cerr << "\nError executing Python script from PythonInterpreter::interpretString" << endl; cerr << "Name: " << name << endl; @@ -122,7 +122,7 @@ class PythonInterpreter : public Interpreter { BKE_reports_clear(reports); - if (!BPY_execute_text(_context, text, reports, false)) { + if (!BPY_run_text(_context, text, reports, false)) { cerr << "\nError executing Python script from PythonInterpreter::interpretText" << endl; cerr << "Name: " << name << endl; cerr << "Errors: " << endl; diff --git a/source/blender/python/BPY_extern.h b/source/blender/python/BPY_extern.h index 3fc4df270d5..e1e0d01055a 100644 --- a/source/blender/python/BPY_extern.h +++ b/source/blender/python/BPY_extern.h @@ -67,40 +67,6 @@ void BPY_thread_restore(BPy_ThreadStatePtr tstate); } \ (void)0 -bool BPY_execute_filepath(struct bContext *C, const char *filepath, struct ReportList *reports); -bool BPY_execute_text(struct bContext *C, - struct Text *text, - struct ReportList *reports, - const bool do_jump); - -bool BPY_execute_string_as_number(struct bContext *C, - const char *imports[], - const char *expr, - const char *report_prefix, - double *r_value); -bool BPY_execute_string_as_intptr(struct bContext *C, - const char *imports[], - const char *expr, - const char *report_prefix, - intptr_t *r_value); -bool BPY_execute_string_as_string_and_size(struct bContext *C, - const char *imports[], - const char *expr, - const char *report_prefix, - char **r_value, - size_t *r_value_size); -bool BPY_execute_string_as_string(struct bContext *C, - const char *imports[], - const char *expr, - const char *report_prefix, - char **r_value); - -bool BPY_execute_string_ex(struct bContext *C, - const char *imports[], - const char *expr, - bool use_eval); -bool BPY_execute_string(struct bContext *C, const char *imports[], const char *expr); - void BPY_text_free_code(struct Text *text); void BPY_modules_update( struct bContext *C); // XXX - annoying, need this for pointers that get out of date diff --git a/source/blender/python/BPY_extern_run.h b/source/blender/python/BPY_extern_run.h new file mode 100644 index 00000000000..67ac56f2798 --- /dev/null +++ b/source/blender/python/BPY_extern_run.h @@ -0,0 +1,68 @@ +/* + * 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 python + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "BLI_sys_types.h" + +struct ReportList; +struct Text; +struct bContext; + +/* bpy_interface_run.c */ +bool BPY_run_filepath(struct bContext *C, const char *filepath, struct ReportList *reports); +bool BPY_run_text(struct bContext *C, + struct Text *text, + struct ReportList *reports, + const bool do_jump); + +bool BPY_run_string_as_number(struct bContext *C, + const char *imports[], + const char *expr, + const char *report_prefix, + double *r_value); +bool BPY_run_string_as_intptr(struct bContext *C, + const char *imports[], + const char *expr, + const char *report_prefix, + intptr_t *r_value); +bool BPY_run_string_as_string_and_size(struct bContext *C, + const char *imports[], + const char *expr, + const char *report_prefix, + char **r_value, + size_t *r_value_size); +bool BPY_run_string_as_string(struct bContext *C, + const char *imports[], + const char *expr, + const char *report_prefix, + char **r_value); + +bool BPY_run_string_ex(struct bContext *C, const char *imports[], const char *expr, bool use_eval); + +bool BPY_run_string(struct bContext *C, const char *imports[], const char *expr); + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt index 769618005af..44949c478cc 100644 --- a/source/blender/python/intern/CMakeLists.txt +++ b/source/blender/python/intern/CMakeLists.txt @@ -62,6 +62,7 @@ set(SRC bpy_gizmo_wrap.c bpy_interface.c bpy_interface_atexit.c + bpy_interface_run.c bpy_intern_string.c bpy_library_load.c bpy_library_write.c diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c index 81e1905bed7..3941d25e9f7 100644 --- a/source/blender/python/intern/bpy_interface.c +++ b/source/blender/python/intern/bpy_interface.c @@ -64,6 +64,7 @@ #include "BPY_extern.h" #include "BPY_extern_python.h" +#include "BPY_extern_run.h" #include "../generic/py_capi_utils.h" @@ -428,18 +429,6 @@ void BPY_python_use_system_env(void) py_use_system_env = true; } -static void python_script_error_jump_text(struct 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); - } -} - void BPY_python_backtrace(FILE *fp) { fputs("\n# Python backtrace\n", fp); @@ -467,152 +456,6 @@ typedef struct { } PyModuleObject; #endif -/* 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); -} - -/* Can run a file or text block */ -bool BPY_execute_filepath(bContext *C, const char *filepath, struct ReportList *reports) -{ - return python_script_exec(C, filepath, NULL, reports, false); -} - -bool BPY_execute_text(bContext *C, - struct Text *text, - struct ReportList *reports, - const bool do_jump) -{ - return python_script_exec(C, NULL, text, reports, do_jump); -} - void BPY_DECREF(void *pyob_ptr) { PyGILState_STATE gilstate = PyGILState_Ensure(); @@ -631,177 +474,6 @@ void BPY_DECREF_RNA_INVALIDATE(void *pyob_ptr) PyGILState_Release(gilstate); } -/** - * \return success - */ -bool BPY_execute_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, "", 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_execute_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, "", 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_execute_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_execute_string_as_string_and_size( - C, imports, expr, report_prefix, r_value, &value_dummy_size); -} - -/** - * Support both int and pointers. - * - * \return success - */ -bool BPY_execute_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, "", 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; -} - -bool BPY_execute_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(""); - - 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_execute_string(bContext *C, const char *imports[], const char *expr) -{ - return BPY_execute_string_ex(C, imports, expr, true); -} - void BPY_modules_load_user(bContext *C) { PyGILState_STATE gilstate; @@ -834,7 +506,7 @@ void BPY_modules_load_user(bContext *C) } } else { - BPY_execute_text(C, text, NULL, false); + BPY_run_text(C, text, NULL, false); /* Check if the script loaded a new file. */ if (bmain != CTX_data_main(C)) { 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 + +#include + +#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(""); + + 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, "", 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, "", 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, "", 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; +} + +/** \} */ diff --git a/source/blender/windowmanager/intern/wm.c b/source/blender/windowmanager/intern/wm.c index 43c08a2b980..22210012bb6 100644 --- a/source/blender/windowmanager/intern/wm.c +++ b/source/blender/windowmanager/intern/wm.c @@ -66,6 +66,7 @@ #ifdef WITH_PYTHON # include "BPY_extern.h" +# include "BPY_extern_run.h" #endif /* ****************************************************** */ @@ -270,7 +271,7 @@ void WM_keyconfig_reload(bContext *C) { if (CTX_py_init_get(C) && !G.background) { #ifdef WITH_PYTHON - BPY_execute_string(C, (const char *[]){"bpy", NULL}, "bpy.utils.keyconfig_init()"); + BPY_run_string(C, (const char *[]){"bpy", NULL}, "bpy.utils.keyconfig_init()"); #endif } } diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index a8d393cfbae..1be22b2283c 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -121,8 +121,8 @@ #include "RE_engine.h" #ifdef WITH_PYTHON -# include "BPY_extern.h" # include "BPY_extern_python.h" +# include "BPY_extern_run.h" #endif #include "DEG_depsgraph.h" @@ -580,14 +580,14 @@ static void wm_file_read_post(bContext *C, if (use_userdef || reset_app_template) { /* Only run when we have a template path found. */ if (BKE_appdir_app_template_any()) { - BPY_execute_string( + BPY_run_string( C, (const char *[]){"bl_app_template_utils", NULL}, "bl_app_template_utils.reset()"); reset_all = true; } } if (reset_all) { /* sync addons, these may have changed from the defaults */ - BPY_execute_string(C, (const char *[]){"addon_utils", NULL}, "addon_utils.reset_all()"); + BPY_run_string(C, (const char *[]){"addon_utils", NULL}, "addon_utils.reset_all()"); } if (use_data) { BPY_python_reset(C); @@ -924,7 +924,7 @@ void wm_homefile_read(bContext *C, * * Note that this fits into 'wm_file_read_pre' function but gets messy * since we need to know if 'reset_app_template' is true. */ - BPY_execute_string(C, (const char *[]){"addon_utils", NULL}, "addon_utils.disable_all()"); + BPY_run_string(C, (const char *[]){"addon_utils", NULL}, "addon_utils.disable_all()"); } #endif /* WITH_PYTHON */ } -- cgit v1.2.3