From f7a4ede79f9512f39db8632ff112e08a93f3a9d4 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Fri, 9 Sep 2022 13:59:53 +1000 Subject: Python: change bpy.app.binary_path behavior WITH_PYTHON_MODULE The following changes have been made to this attribute with WITH_PYTHON_MODULE is defined: - Defaults to an empty string (instead of pointing to __init__.so). - It's writable, so script authors can point to a valid Blender binary. `where_am_i(..)` is no longer used by BKE_appdir_program_path_init, there is now a separate code-path for setting the initial program directory, calls after this can be used to set the binary path. --- source/blender/blenkernel/intern/appdir.c | 51 ++++++++++++++++++------------- source/blender/python/intern/bpy_app.c | 39 ++++++++++++++++++++--- 2 files changed, 65 insertions(+), 25 deletions(-) (limited to 'source/blender') diff --git a/source/blender/blenkernel/intern/appdir.c b/source/blender/blenkernel/intern/appdir.c index 845a890ba8b..295e85a5fc4 100644 --- a/source/blender/blenkernel/intern/appdir.c +++ b/source/blender/blenkernel/intern/appdir.c @@ -782,6 +782,7 @@ const char *BKE_appdir_folder_id_version(const int folder_id, * Access locations of Blender & Python. * \{ */ +#ifndef WITH_PYTHON_MODULE /** * Checks if name is a fully qualified filename to an executable. * If not it searches `$PATH` for the file. On Windows it also @@ -793,14 +794,12 @@ const char *BKE_appdir_folder_id_version(const int folder_id, * \param fullname: The full path and full name of the executable * (must be #FILE_MAX minimum) * \param name: The name of the executable (usually `argv[0]`) to be checked - * \param strict: When true, use `argv0` unmodified (besides making absolute & normalizing). - * Otherwise other methods may be used to find the program path, including searching `$PATH`. */ -static void where_am_i(char *fullname, const size_t maxlen, const char *name, const bool strict) +static void where_am_i(char *fullname, const size_t maxlen, const char *name) { -#ifdef WITH_BINRELOC +# ifdef WITH_BINRELOC /* Linux uses `binreloc` since `argv[0]` is not reliable, call `br_init(NULL)` first. */ - if (!strict) { + { const char *path = NULL; path = br_find_exe(NULL); if (path) { @@ -809,9 +808,9 @@ static void where_am_i(char *fullname, const size_t maxlen, const char *name, co return; } } -#endif +# endif -#ifdef _WIN32 +# ifdef _WIN32 if (!strict) { wchar_t *fullname_16 = MEM_mallocN(maxlen * sizeof(wchar_t), "ProgramPath"); if (GetModuleFileNameW(0, fullname_16, maxlen)) { @@ -827,7 +826,7 @@ static void where_am_i(char *fullname, const size_t maxlen, const char *name, co MEM_freeN(fullname_16); } -#endif +# endif /* Unix and non Linux. */ if (name && name[0]) { @@ -835,36 +834,35 @@ static void where_am_i(char *fullname, const size_t maxlen, const char *name, co BLI_strncpy(fullname, name, maxlen); if (name[0] == '.') { BLI_path_abs_from_cwd(fullname, maxlen); -#ifdef _WIN32 +# ifdef _WIN32 if (!strict) { BLI_path_program_extensions_add_win32(fullname, maxlen); } -#endif +# endif } else if (BLI_path_slash_rfind(name)) { /* Full path. */ BLI_strncpy(fullname, name, maxlen); -#ifdef _WIN32 +# ifdef _WIN32 if (!strict) { BLI_path_program_extensions_add_win32(fullname, maxlen); } -#endif +# endif } else { - if (!strict) { - BLI_path_program_search(fullname, maxlen, name); - } + BLI_path_program_search(fullname, maxlen, name); } /* Remove "/./" and "/../" so string comparisons can be used on the path. */ BLI_path_normalize(NULL, fullname); -#if defined(DEBUG) +# if defined(DEBUG) if (!STREQ(name, fullname)) { CLOG_INFO(&LOG, 2, "guessing '%s' == '%s'", name, fullname); } -#endif +# endif } } +#endif /* WITH_PYTHON_MODULE */ void BKE_appdir_program_path_init(const char *argv0) { @@ -872,17 +870,28 @@ void BKE_appdir_program_path_init(const char *argv0) /* NOTE(@campbellbarton): Always use `argv[0]` as is, when building as a Python module. * Otherwise other methods of detecting the binary that override this argument * which must point to the Python module for data-files to be detected. */ - const bool strict = true; + STRNCPY(g_app.program_filepath, argv0); + BLI_path_abs_from_cwd(g_app.program_filepath, sizeof(g_app.program_filepath)); + BLI_path_normalize(NULL, g_app.program_filepath); + + if (g_app.program_dirname[0] == '\0') { + /* First time initializing, the file binary path isn't valid from a Python module. + * Calling again must set the `filepath` and leave the directory as-is. */ + BLI_split_dir_part( + g_app.program_filepath, g_app.program_dirname, sizeof(g_app.program_dirname)); + g_app.program_filepath[0] = '\0'; + } #else - const bool strict = false; -#endif - where_am_i(g_app.program_filepath, sizeof(g_app.program_filepath), argv0, strict); + where_am_i(g_app.program_filepath, sizeof(g_app.program_filepath), argv0); BLI_split_dir_part(g_app.program_filepath, g_app.program_dirname, sizeof(g_app.program_dirname)); +#endif } const char *BKE_appdir_program_path(void) { +#ifndef WITH_PYTHON_MODULE /* Default's to empty when building as as Python module. */ BLI_assert(g_app.program_filepath[0]); +#endif return g_app.program_filepath; } diff --git a/source/blender/python/intern/bpy_app.c b/source/blender/python/intern/bpy_app.c index 939473ceaa0..a0129157b95 100644 --- a/source/blender/python/intern/bpy_app.c +++ b/source/blender/python/intern/bpy_app.c @@ -79,8 +79,6 @@ static PyStructSequence_Field app_info_fields[] = { {"version_string", "The Blender version formatted as a string"}, {"version_cycle", "The release status of this build alpha/beta/rc/release"}, {"version_char", "Deprecated, always an empty string"}, - {"binary_path", - "The location of Blender's executable, useful for utilities that open new instances"}, {"background", "Boolean, True when blender is running without a user interface (started with -b)"}, {"factory_startup", "Boolean, True when blender is running with --factory-startup)"}, @@ -151,7 +149,6 @@ static PyObject *make_app_info(void) SetStrItem(STRINGIFY(BLENDER_VERSION_CYCLE)); SetStrItem(""); - SetStrItem(BKE_appdir_program_path()); SetObjItem(PyBool_FromLong(G.background)); SetObjItem(PyBool_FromLong(G.factory_startup)); @@ -345,6 +342,33 @@ static PyObject *bpy_app_autoexec_fail_message_get(PyObject *UNUSED(self), void return PyC_UnicodeFromByte(G.autoexec_fail); } +PyDoc_STRVAR(bpy_app_binary_path_doc, + "The location of Blender's executable, useful for utilities that open new instances. " + "Read-only unless Blender is built as a Python module - in this case the value is " + "an empty string which script authors may point to a Blender binary."); +static PyObject *bpy_app_binary_path_get(PyObject *UNUSED(self), void *UNUSED(closure)) +{ + return PyC_UnicodeFromByte(BKE_appdir_program_path()); +} + +static int bpy_app_binary_path_set(PyObject *UNUSED(self), PyObject *value, void *UNUSED(closure)) +{ +#ifndef WITH_PYTHON_MODULE + PyErr_SetString(PyExc_AttributeError, + "bpy.app.binary_path is only writable when built as a Python module"); + return -1; +#endif + PyObject *value_coerce = NULL; + const char *filepath = PyC_UnicodeAsByte(value, &value_coerce); + if (filepath == NULL) { + PyErr_Format(PyExc_ValueError, "expected a string or bytes, got %s", Py_TYPE(value)->tp_name); + return -1; + } + BKE_appdir_program_path_init(filepath); + Py_XDECREF(value_coerce); + return 0; +} + static PyGetSetDef bpy_app_getsets[] = { {"debug", bpy_app_debug_get, bpy_app_debug_set, bpy_app_debug_doc, (void *)G_DEBUG}, {"debug_ffmpeg", @@ -450,7 +474,14 @@ static PyGetSetDef bpy_app_getsets[] = { (void *)G_FLAG_SCRIPT_AUTOEXEC_FAIL_QUIET}, {"autoexec_fail_message", bpy_app_autoexec_fail_message_get, NULL, NULL, NULL}, - /* End-of-list marker. */ + /* Support script authors setting the Blender binary path to use, otherwise this value + * is not known when built as a Python module. */ + {"binary_path", + bpy_app_binary_path_get, + bpy_app_binary_path_set, + bpy_app_binary_path_doc, + NULL}, + {NULL, NULL, NULL, NULL, NULL}, }; -- cgit v1.2.3