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.
This commit is contained in:
2022-09-09 13:59:53 +10:00
parent d455f1a0ba
commit f7a4ede79f
2 changed files with 65 additions and 25 deletions

View File

@@ -782,6 +782,7 @@ const char *BKE_appdir_folder_id_version(const int folder_id,
* Access locations of Blender & Python. * Access locations of Blender & Python.
* \{ */ * \{ */
#ifndef WITH_PYTHON_MODULE
/** /**
* Checks if name is a fully qualified filename to an executable. * Checks if name is a fully qualified filename to an executable.
* If not it searches `$PATH` for the file. On Windows it also * 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 * \param fullname: The full path and full name of the executable
* (must be #FILE_MAX minimum) * (must be #FILE_MAX minimum)
* \param name: The name of the executable (usually `argv[0]`) to be checked * \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. */ /* Linux uses `binreloc` since `argv[0]` is not reliable, call `br_init(NULL)` first. */
if (!strict) { {
const char *path = NULL; const char *path = NULL;
path = br_find_exe(NULL); path = br_find_exe(NULL);
if (path) { if (path) {
@@ -809,9 +808,9 @@ static void where_am_i(char *fullname, const size_t maxlen, const char *name, co
return; return;
} }
} }
#endif # endif
#ifdef _WIN32 # ifdef _WIN32
if (!strict) { if (!strict) {
wchar_t *fullname_16 = MEM_mallocN(maxlen * sizeof(wchar_t), "ProgramPath"); wchar_t *fullname_16 = MEM_mallocN(maxlen * sizeof(wchar_t), "ProgramPath");
if (GetModuleFileNameW(0, fullname_16, maxlen)) { 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); MEM_freeN(fullname_16);
} }
#endif # endif
/* Unix and non Linux. */ /* Unix and non Linux. */
if (name && name[0]) { 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); BLI_strncpy(fullname, name, maxlen);
if (name[0] == '.') { if (name[0] == '.') {
BLI_path_abs_from_cwd(fullname, maxlen); BLI_path_abs_from_cwd(fullname, maxlen);
#ifdef _WIN32 # ifdef _WIN32
if (!strict) { if (!strict) {
BLI_path_program_extensions_add_win32(fullname, maxlen); BLI_path_program_extensions_add_win32(fullname, maxlen);
} }
#endif # endif
} }
else if (BLI_path_slash_rfind(name)) { else if (BLI_path_slash_rfind(name)) {
/* Full path. */ /* Full path. */
BLI_strncpy(fullname, name, maxlen); BLI_strncpy(fullname, name, maxlen);
#ifdef _WIN32 # ifdef _WIN32
if (!strict) { if (!strict) {
BLI_path_program_extensions_add_win32(fullname, maxlen); BLI_path_program_extensions_add_win32(fullname, maxlen);
} }
#endif # endif
} }
else { 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. */ /* Remove "/./" and "/../" so string comparisons can be used on the path. */
BLI_path_normalize(NULL, fullname); BLI_path_normalize(NULL, fullname);
#if defined(DEBUG) # if defined(DEBUG)
if (!STREQ(name, fullname)) { if (!STREQ(name, fullname)) {
CLOG_INFO(&LOG, 2, "guessing '%s' == '%s'", 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) 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. /* 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 * Otherwise other methods of detecting the binary that override this argument
* which must point to the Python module for data-files to be detected. */ * 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 #else
const bool strict = false; where_am_i(g_app.program_filepath, sizeof(g_app.program_filepath), argv0);
#endif
where_am_i(g_app.program_filepath, sizeof(g_app.program_filepath), argv0, strict);
BLI_split_dir_part(g_app.program_filepath, g_app.program_dirname, sizeof(g_app.program_dirname)); 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) 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]); BLI_assert(g_app.program_filepath[0]);
#endif
return g_app.program_filepath; return g_app.program_filepath;
} }

View File

@@ -79,8 +79,6 @@ static PyStructSequence_Field app_info_fields[] = {
{"version_string", "The Blender version formatted as a string"}, {"version_string", "The Blender version formatted as a string"},
{"version_cycle", "The release status of this build alpha/beta/rc/release"}, {"version_cycle", "The release status of this build alpha/beta/rc/release"},
{"version_char", "Deprecated, always an empty string"}, {"version_char", "Deprecated, always an empty string"},
{"binary_path",
"The location of Blender's executable, useful for utilities that open new instances"},
{"background", {"background",
"Boolean, True when blender is running without a user interface (started with -b)"}, "Boolean, True when blender is running without a user interface (started with -b)"},
{"factory_startup", "Boolean, True when blender is running with --factory-startup)"}, {"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(STRINGIFY(BLENDER_VERSION_CYCLE));
SetStrItem(""); SetStrItem("");
SetStrItem(BKE_appdir_program_path());
SetObjItem(PyBool_FromLong(G.background)); SetObjItem(PyBool_FromLong(G.background));
SetObjItem(PyBool_FromLong(G.factory_startup)); 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); 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[] = { static PyGetSetDef bpy_app_getsets[] = {
{"debug", bpy_app_debug_get, bpy_app_debug_set, bpy_app_debug_doc, (void *)G_DEBUG}, {"debug", bpy_app_debug_get, bpy_app_debug_set, bpy_app_debug_doc, (void *)G_DEBUG},
{"debug_ffmpeg", {"debug_ffmpeg",
@@ -450,7 +474,14 @@ static PyGetSetDef bpy_app_getsets[] = {
(void *)G_FLAG_SCRIPT_AUTOEXEC_FAIL_QUIET}, (void *)G_FLAG_SCRIPT_AUTOEXEC_FAIL_QUIET},
{"autoexec_fail_message", bpy_app_autoexec_fail_message_get, NULL, NULL, NULL}, {"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}, {NULL, NULL, NULL, NULL, NULL},
}; };