| 1025 | } |
| 1026 | |
| 1027 | std::expected<void, std::string> run_scripts(const std::vector<std::filesystem::path>& scripts) { |
| 1028 | if (scripts.empty()) { |
| 1029 | return {}; |
| 1030 | } |
| 1031 | |
| 1032 | ensure_initialized(); |
| 1033 | |
| 1034 | const GilAcquire gil; |
| 1035 | |
| 1036 | // Install output redirect (calls redirect_output() once) |
| 1037 | call_once_redirect([] { redirect_output(); }); |
| 1038 | |
| 1039 | // Add Python module directory (where lichtfeld.so lives) to sys.path |
| 1040 | { |
| 1041 | const auto python_module_dir = lfs::core::getPythonModuleDir(); |
| 1042 | if (!python_module_dir.empty()) { |
| 1043 | const auto python_module_dir_utf8 = lfs::core::path_to_utf8(python_module_dir); |
| 1044 | PyObject* sys_path = PySys_GetObject("path"); // borrowed |
| 1045 | PyObject* py_path = PyUnicode_FromString(python_module_dir_utf8.c_str()); |
| 1046 | PyList_Append(sys_path, py_path); |
| 1047 | Py_DECREF(py_path); |
| 1048 | LOG_DEBUG("Added {} to Python path", python_module_dir_utf8); |
| 1049 | } |
| 1050 | } |
| 1051 | |
| 1052 | // Pre-import lichtfeld module to catch any initialization errors early |
| 1053 | { |
| 1054 | PyObject* lf_module = import_lichtfeld_module("Failed to pre-import lichtfeld module"); |
| 1055 | if (!lf_module) { |
| 1056 | return std::unexpected("Failed to import lichtfeld module - see startup log for traceback"); |
| 1057 | } |
| 1058 | Py_DECREF(lf_module); |
| 1059 | LOG_INFO("Successfully pre-imported lichtfeld module"); |
| 1060 | } |
| 1061 | |
| 1062 | // Load plugins after lichtfeld is fully imported |
| 1063 | ensure_plugins_loaded(); |
| 1064 | |
| 1065 | for (const auto& script : scripts) { |
| 1066 | const auto script_utf8 = lfs::core::path_to_utf8(script); |
| 1067 | if (!std::filesystem::exists(script)) { |
| 1068 | return std::unexpected(std::format("Python script not found: {}", script_utf8)); |
| 1069 | } |
| 1070 | |
| 1071 | // Ensure script directory is on sys.path |
| 1072 | const auto parent_utf8 = lfs::core::path_to_utf8(script.parent_path()); |
| 1073 | if (!parent_utf8.empty()) { |
| 1074 | PyObject* sys_path = PySys_GetObject("path"); // borrowed ref |
| 1075 | PyObject* py_parent = PyUnicode_FromString(parent_utf8.c_str()); |
| 1076 | if (sys_path && py_parent) { |
| 1077 | PyList_Append(sys_path, py_parent); |
| 1078 | } |
| 1079 | Py_XDECREF(py_parent); |
| 1080 | } |
| 1081 | |
| 1082 | #ifdef _WIN32 |
| 1083 | FILE* const fp = _wfopen(script.wstring().c_str(), L"r"); |
| 1084 | #else |