diff --git a/source/loaders/py_loader/CMakeLists.txt b/source/loaders/py_loader/CMakeLists.txt index 4d3433c822..9f053c091b 100644 --- a/source/loaders/py_loader/CMakeLists.txt +++ b/source/loaders/py_loader/CMakeLists.txt @@ -294,12 +294,34 @@ endif() # Configuration # +set(PY_LOADER_CONFIG_TEMPLATE "${CMAKE_CURRENT_SOURCE_DIR}/data/py_loader.json.in") + +set(PYTHON_HOME "") +set(Python3_LIBRARY_SEARCH_PATHS "") + +if(PROJECT_OS_FAMILY STREQUAL win32) + # Prefer the interpreter directory as Python home for development builds. + set(PYTHON_HOME_DEVELOPMENT "${Python3_ROOT_DIR}") + + # On installed layouts we place Python runtime artifacts under INSTALL_LIB. + set(PYTHON_HOME_INSTALL "${CMAKE_INSTALL_PREFIX}/${INSTALL_LIB}") + + set(Python3_LIBRARY_SEARCH_PATHS_DEVELOPMENT + "${Python3_ROOT_DIR};${Python3_ROOT_DIR}/DLLs;${Python3_ROOT_DIR}/libs") + set(Python3_LIBRARY_SEARCH_PATHS_INSTALL + "${CMAKE_INSTALL_PREFIX}/${INSTALL_LIB};${CMAKE_INSTALL_PREFIX}/${INSTALL_BIN}") +endif() + # Development -loader_configuration_begin(py_loader) +loader_configuration_begin(py_loader "${PY_LOADER_CONFIG_TEMPLATE}") +set(PYTHON_HOME "${PYTHON_HOME_DEVELOPMENT}") +loader_configuration_paths("${Python3_LIBRARY_SEARCH_PATHS_DEVELOPMENT}") loader_configuration_deps(python "${Python3_LIBRARY_DEVELOPMENT}") loader_configuartion_end_development() # Install -loader_configuration_begin(py_loader) +loader_configuration_begin(py_loader "${PY_LOADER_CONFIG_TEMPLATE}") +set(PYTHON_HOME "${PYTHON_HOME_INSTALL}") +loader_configuration_paths("${Python3_LIBRARY_SEARCH_PATHS_INSTALL}") loader_configuration_deps(python "${Python3_LIBRARY_INSTALL}") loader_configuartion_end_install() diff --git a/source/loaders/py_loader/data/py_loader.json.in b/source/loaders/py_loader/data/py_loader.json.in new file mode 100644 index 0000000000..ec2f52c4ec --- /dev/null +++ b/source/loaders/py_loader/data/py_loader.json.in @@ -0,0 +1,7 @@ +{ + "python_home": "@PYTHON_HOME@", + "search_paths": [ @LOADER_SEARCH_PATHS@ ], + "dependencies": { + @LOADER_DEPENDENCIES@ + } +} diff --git a/source/loaders/py_loader/source/py_loader_impl.c b/source/loaders/py_loader/source/py_loader_impl.c index 3c9800280d..5bda6abc78 100644 --- a/source/loaders/py_loader/source/py_loader_impl.c +++ b/source/loaders/py_loader/source/py_loader_impl.c @@ -190,6 +190,45 @@ static int py_loader_impl_finalize(loader_impl_py py_impl, const int host); static PyObject *py_loader_impl_load_from_memory_compile(loader_impl_py py_impl, const loader_name name, const char *buffer); +#if defined(WIN32) || defined(_WIN32) +static int py_loader_impl_initialize_python_home(configuration config) +{ + value python_home_value; + const char *python_home; + const char *python_home_env = getenv("PYTHONHOME"); + + /* Respect user-provided PYTHONHOME when present. */ + if (python_home_env != NULL && python_home_env[0] != '\0') + { + return 0; + } + + python_home_value = configuration_value_type(config, "python_home", TYPE_STRING); + + if (python_home_value == NULL) + { + return 0; + } + + python_home = value_to_string(python_home_value); + + if (python_home == NULL || python_home[0] == '\0') + { + return 0; + } + + if (_putenv_s("PYTHONHOME", python_home) != 0) + { + log_write("metacall", LOG_LEVEL_ERROR, "Python loader failed to set PYTHONHOME to: %s", python_home); + return 1; + } + + log_write("metacall", LOG_LEVEL_DEBUG, "Python loader configured PYTHONHOME to: %s", python_home); + + return 0; +} +#endif + /* Implements: if __name__ == "__main__": */ static int py_loader_impl_run_main = 1; static char *py_loader_impl_main_module = NULL; @@ -2454,6 +2493,13 @@ loader_impl_data py_loader_impl_initialize(loader_impl impl, configuration confi if (host == 0) { + #if defined(WIN32) || defined(_WIN32) + if (py_loader_impl_initialize_python_home(config) != 0) + { + goto error_init_py; + } + #endif + Py_InitializeEx(0); if (Py_IsInitialized() == 0) @@ -3725,7 +3771,7 @@ static int py_loader_impl_validate_object(loader_impl impl, PyObject *obj, objec log_write("metacall", LOG_LEVEL_DEBUG, "Discover object member %s, type %s", PyUnicode_AsUTF8(dict_key), type_id_name(py_loader_impl_capi_to_value_type(dict_val))); - + } } diff --git a/source/ports/rs_port/src/init.rs b/source/ports/rs_port/src/init.rs index fb578600ae..24902dc917 100644 --- a/source/ports/rs_port/src/init.rs +++ b/source/ports/rs_port/src/init.rs @@ -2,10 +2,44 @@ use crate::{ bindings::{metacall_destroy, metacall_initialize, metacall_is_initialized}, types::MetaCallInitError, }; -use std::ptr; +use std::{env, ptr}; pub struct MetaCallDestroy(unsafe extern "C" fn()); +#[cfg(target_os = "windows")] +fn ensure_python_home() { + use std::path::Path; + + if env::var_os("PYTHONHOME").is_some() { + return; + } + + let Some(configured_home) = option_env!("METACALL_PYTHONHOME") else { + return; + }; + + if configured_home.is_empty() { + return; + } + + let configured_home_path = Path::new(configured_home); + let runtime_python_from_config = configured_home_path.join("runtimes").join("python"); + let runtime_python_from_parent = configured_home_path + .parent() + .map(|p| p.join("runtimes").join("python")); + + let selected_home = if runtime_python_from_config.is_dir() { + runtime_python_from_config.as_os_str() + } else if let Some(runtime_python) = runtime_python_from_parent.as_ref().filter(|p| p.is_dir()) + { + runtime_python.as_os_str() + } else { + configured_home_path.as_os_str() + }; + + env::set_var("PYTHONHOME", selected_home); +} + impl Drop for MetaCallDestroy { fn drop(&mut self) { unsafe { self.0() } @@ -22,6 +56,9 @@ impl Drop for MetaCallDestroy { /// /// ``` pub fn initialize() -> Result { + #[cfg(target_os = "windows")] + ensure_python_home(); + let code = unsafe { metacall_initialize() }; if code != 0 { diff --git a/source/ports/rs_port/sys/src/lib.rs b/source/ports/rs_port/sys/src/lib.rs index 4c77f3fc34..7f4960918d 100644 --- a/source/ports/rs_port/sys/src/lib.rs +++ b/source/ports/rs_port/sys/src/lib.rs @@ -365,6 +365,14 @@ pub fn build() { define_library_search_path(ENV_VAR, SEPARATOR, &lib_path.search) ); + #[cfg(target_os = "windows")] + { + println!( + "cargo:rustc-env=METACALL_PYTHONHOME={}", + lib_path.search.display() + ); + } + println!( "cargo:warning=Library {} found in: {} with runtime search path: {}", lib_path.library,