From 9b336cae06497b348f07b61156f4724f8e5ad65c Mon Sep 17 00:00:00 2001 From: Lukas Hof Date: Mon, 24 Aug 2026 18:29:43 +0200 Subject: [PATCH 1/3] fixed pybind not finding Pyhton in non-standard install location on Windows --- setup.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 1e46c4db..1eaa32bf 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ import os import re import sys +import sysconfig import platform import subprocess import multiprocessing as mp @@ -50,11 +51,21 @@ def run(self): def build_extension(self, ext): extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) bin_dir_windows = os.path.join(os.path.abspath(self.build_temp), "bin") - cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, - # '-DPYBIND11_PYTHON_VERSION=' + f"{sys.version_info.major}.{sys.version_info.minor}" - '-DPYBIND11_FINDPYTHON=On', - '-DPython_EXECUTABLE=' + sys.executable, - ] + cmake_args = [ + "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=" + extdir, + "-DPYBIND11_FINDPYTHON=On", + "-DPython_EXECUTABLE=" + sys.executable, + "-DPython_INCLUDE_DIR=" + sysconfig.get_path("include"), + ] + if platform.system() == "Windows": + cmake_args.append( + "-DPython_LIBRARY=" + + os.path.join( + sys.base_prefix, + "libs", + f"python{sys.version_info.major}{sys.version_info.minor}.lib", + ) + ) print(f"Using cmake args {cmake_args}") print(f"Python version: {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}") From 79f3bf65ae5cd8bbbd048b164aa9208290ea4914 Mon Sep 17 00:00:00 2001 From: Lukas Hof Date: Tue, 4 Nov 2025 17:31:44 +0100 Subject: [PATCH 2/3] Ensured the module is found in a Python venv in Windows and Linux. --- Utilities/FileSystem.h | 55 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/Utilities/FileSystem.h b/Utilities/FileSystem.h index fc935249..eaf4ae3b 100644 --- a/Utilities/FileSystem.h +++ b/Utilities/FileSystem.h @@ -12,6 +12,7 @@ #else #include #include +#include #endif #ifdef __APPLE__ #include @@ -217,21 +218,65 @@ namespace Utilities static std::string getProgramPath() { + bool inPythonModule = false; char buffer[1000]; -#ifdef WIN32 - GetModuleFileName(NULL, buffer, 1000); + std::string bufferStr(buffer); +#ifdef WIN32 + GetModuleFileNameA(NULL, buffer, 1000); + bufferStr = std::string(buffer); + + // If the program is running as a Python module, the path to the module, not the executable, must be obtained + inPythonModule = bufferStr.size() >= 10 && bufferStr.substr(bufferStr.size() - 10) == "python.exe"; + + if (inPythonModule) { + // Get the module handle for the current module + HMODULE hModule = nullptr; + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(&getProgramPath), &hModule); + + // Retrieve the full path of the module + DWORD length = GetModuleFileNameA(hModule, buffer, 1000); + bufferStr = std::string(buffer); + + bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); + bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); + bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); + } + else { + bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); + } #elif defined(__APPLE__) uint32_t bufferSize = sizeof(buffer); _NSGetExecutablePath(buffer, &bufferSize); + bufferStr = std::string(buffer); + bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); #else char szTmp[32]; sprintf(szTmp, "/proc/%d/exe", getpid()); int bytes = std::min((int)readlink(szTmp, buffer, 1000), 999); buffer[bytes] = '\0'; + bufferStr = std::string(buffer); + + // If the program is running as a Python module, the path to the module, not the executable, must be obtained + inPythonModule = bufferStr.size() >= 6 + && bufferStr.substr(bufferStr.size() - std::min(10, bufferStr.size())).find("python") + != std::string::npos; + + if (inPythonModule) { + Dl_info info{}; + dladdr(reinterpret_cast(&getProgramPath), &info) && info.dli_fname; + bufferStr = info.dli_fname; + bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); + bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); + bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); + bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); + bufferStr += "/bin"; + } + else { + bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); + } #endif - std::string::size_type pos = std::string(buffer).find_last_of("\\/"); - return std::string(buffer).substr(0, pos); - + return bufferStr; } static bool copyFile(const std::string &source, const std::string &dest) From 8e8a060df1d7d60d93b47e44317e9028c15ba0d8 Mon Sep 17 00:00:00 2001 From: Lukas Hof Date: Mon, 24 Aug 2026 20:46:35 +0200 Subject: [PATCH 3/3] improve getProgramPath to be more robust --- Utilities/FileSystem.h | 116 +++++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 52 deletions(-) diff --git a/Utilities/FileSystem.h b/Utilities/FileSystem.h index eaf4ae3b..e783ddce 100644 --- a/Utilities/FileSystem.h +++ b/Utilities/FileSystem.h @@ -218,65 +218,77 @@ namespace Utilities static std::string getProgramPath() { - bool inPythonModule = false; - char buffer[1000]; - std::string bufferStr(buffer); -#ifdef WIN32 - GetModuleFileNameA(NULL, buffer, 1000); - bufferStr = std::string(buffer); - - // If the program is running as a Python module, the path to the module, not the executable, must be obtained - inPythonModule = bufferStr.size() >= 10 && bufferStr.substr(bufferStr.size() - 10) == "python.exe"; - - if (inPythonModule) { - // Get the module handle for the current module - HMODULE hModule = nullptr; - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, - reinterpret_cast(&getProgramPath), &hModule); + std::string executablePath; + std::string dynamicModulePath; - // Retrieve the full path of the module - DWORD length = GetModuleFileNameA(hModule, buffer, 1000); - bufferStr = std::string(buffer); +#ifdef WIN32 + char exeBuffer[MAX_PATH] = { 0 }; + if (GetModuleFileNameA(NULL, exeBuffer, MAX_PATH)) + executablePath = exeBuffer; - bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); - bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); - bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); - } - else { - bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); + HMODULE moduleHandle = nullptr; + if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(&getProgramPath), &moduleHandle) && moduleHandle) + { + char moduleBuffer[MAX_PATH] = { 0 }; + if (GetModuleFileNameA(moduleHandle, moduleBuffer, MAX_PATH)) + dynamicModulePath = moduleBuffer; } #elif defined(__APPLE__) - uint32_t bufferSize = sizeof(buffer); - _NSGetExecutablePath(buffer, &bufferSize); - bufferStr = std::string(buffer); - bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); + char exeBuffer[1024] = { 0 }; + uint32_t bufferSize = sizeof(exeBuffer); + if (_NSGetExecutablePath(exeBuffer, &bufferSize) == 0) + executablePath = exeBuffer; + + Dl_info moduleInfo{}; + if (dladdr(reinterpret_cast(&getProgramPath), &moduleInfo) && moduleInfo.dli_fname) + dynamicModulePath = moduleInfo.dli_fname; #else - char szTmp[32]; - sprintf(szTmp, "/proc/%d/exe", getpid()); - int bytes = std::min((int)readlink(szTmp, buffer, 1000), 999); - buffer[bytes] = '\0'; - bufferStr = std::string(buffer); - - // If the program is running as a Python module, the path to the module, not the executable, must be obtained - inPythonModule = bufferStr.size() >= 6 - && bufferStr.substr(bufferStr.size() - std::min(10, bufferStr.size())).find("python") - != std::string::npos; - - if (inPythonModule) { - Dl_info info{}; - dladdr(reinterpret_cast(&getProgramPath), &info) && info.dli_fname; - bufferStr = info.dli_fname; - bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); - bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); - bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); - bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); - bufferStr += "/bin"; - } - else { - bufferStr = bufferStr.substr(0, bufferStr.find_last_of("\\/")); + char exeBuffer[1024] = { 0 }; + char procPath[32]; + sprintf(procPath, "/proc/%d/exe", getpid()); + int exePathLength = std::min((int)readlink(procPath, exeBuffer, sizeof(exeBuffer) - 1), (int)(sizeof(exeBuffer) - 1)); + if (exePathLength > 0) + { + exeBuffer[exePathLength] = '\0'; + executablePath = exeBuffer; } + + Dl_info moduleInfo{}; + if (dladdr(reinterpret_cast(&getProgramPath), &moduleInfo) && moduleInfo.dli_fname) + dynamicModulePath = moduleInfo.dli_fname; #endif - return bufferStr; + + auto getParentDirectory = [](const std::string &path) -> std::string + { + size_t lastSeparator = path.find_last_of("\\/"); + return (lastSeparator != std::string::npos) ? path.substr(0, lastSeparator) : ""; + }; + + // If running as a dynamic module / Python extension (module differs from executable) + if (!dynamicModulePath.empty() && dynamicModulePath != executablePath) + { + std::string searchDirectory = getParentDirectory(dynamicModulePath); + + // Walk up the directory tree (up to 6 levels) to locate resources/data + for (int level = 0; level < 6 && !searchDirectory.empty(); ++level) + { + if (isDirectory(searchDirectory + "/resources") || isDirectory(searchDirectory + "/data")) + return searchDirectory; + + if (isDirectory(searchDirectory + "/bin/resources") || isDirectory(searchDirectory + "/bin/data")) + return searchDirectory + "/bin"; + + searchDirectory = getParentDirectory(searchDirectory); + } + + return getParentDirectory(dynamicModulePath); + } + else + { + // Standalone executable fallback + return getParentDirectory(executablePath); + } } static bool copyFile(const std::string &source, const std::string &dest)