From 98e91cda9df51cc17f13fa14acd7e825ae097896 Mon Sep 17 00:00:00 2001 From: Luke Marshall <52978038+mathgeekcoder@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:25:25 -0700 Subject: [PATCH] Updated build for external dependencies: * Added RPATH for linux/osx to find library * Support for vcpkg BLAS on linux (OpenBLAS) and osx (accelerate, not tested - might not actually work as is) * Fixed type redefinition warning on metis * Fixed `template-depth` compile warning on C (not C++) files * Minor code cleanup (e.g., removed debug messages) * Added thread safety for dependency loading * Added `highspy._core.getExtrasLoadStatus` for debugging the library load * Added highspy COMPONENT to avoid pointless copy of header files * Tested with BUILD_SHARED_LIBS=ON/OFF and BUILD_SHARED_EXTRAS_LIB=ON/OFF and HIPO=ON/OFF (windows and linux) * Tested highspy and highspy-extras (windows and linux) --- app/CMakeLists.txt | 27 +++- app/HighsRuntimeOptions.h | 2 - cmake/FindHipoDeps.cmake | 2 +- extern/CMakeLists.txt | 32 +++- extern/HighsExtrasApi.h | 12 -- extern/metis/metis.h | 27 +--- highs/CMakeLists.txt | 23 ++- highs/HighsExternalDeps.cpp | 259 +++++++++++++++++---------------- highs/HighsExternalDeps.h | 25 ++-- highs/lp_data/HighsOptions.cpp | 65 ++------- highspy/CMakeLists.txt | 8 +- highspy/highs_bindings.cpp | 35 +---- highspy/pyproject.toml | 2 +- 13 files changed, 236 insertions(+), 283 deletions(-) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 32542ed0b5..a06a2583d5 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -20,11 +20,11 @@ if(FAST_BUILD) set_target_properties(highs-bin PROPERTIES POSITION_INDEPENDENT_CODE ON) - if (${BUILD_SHARED_EXTRAS_LIB}) + if (BUILD_SHARED_EXTRAS_LIB) target_compile_definitions(highs-bin PRIVATE HIGHS_SHARED_EXTRAS_LIBRARY) - else() - target_link_libraries(highs-bin PRIVATE highs_extras) - endif() + else() + target_link_libraries(highs-bin PRIVATE highs_extras) + endif() if(UNIX) target_compile_options(highs-bin PUBLIC "-Wno-unused-variable") @@ -63,6 +63,25 @@ if(FAST_BUILD) ) endif() + # Set the build RPATH for the highs app + if (APPLE OR UNIX) + set(highs_bin_build_rpath) + + if(BUILD_SHARED_LIBS) + list(APPEND highs_bin_build_rpath "$") + endif() + + if(HIPO AND BUILD_SHARED_EXTRAS_LIB) + list(APPEND highs_bin_build_rpath "$") + endif() + + if(highs_bin_build_rpath) + list(REMOVE_DUPLICATES highs_bin_build_rpath) + set_target_properties(highs-bin PROPERTIES + BUILD_RPATH "${highs_bin_build_rpath}") + endif() + endif() + if(WIN32 AND HIPO AND BUILD_OPENBLAS AND BUILD_SHARED_LIBS) add_custom_command(TARGET highs-bin POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different diff --git a/app/HighsRuntimeOptions.h b/app/HighsRuntimeOptions.h index 42704455f8..d00b75fe3f 100644 --- a/app/HighsRuntimeOptions.h +++ b/app/HighsRuntimeOptions.h @@ -151,11 +151,9 @@ bool loadOptions(const CLI::App& app, const HighsLogOptions& report_log_options, std::cout << " Githash " << HIGHS_GITHASH << ". "; std::cout << kHighsCopyrightStatement << std::endl; -#ifdef HIPO if (HighsExternalDeps::isAvailable()) { std::cout << HighsExternalDeps::getCopyrightInfo() << std::endl; } -#endif exit(0); } diff --git a/cmake/FindHipoDeps.cmake b/cmake/FindHipoDeps.cmake index ba3d3bbb61..f954da4812 100644 --- a/cmake/FindHipoDeps.cmake +++ b/cmake/FindHipoDeps.cmake @@ -298,7 +298,7 @@ if (NOT USE_CMAKE_FIND_BLAS) endif() else() - if (WIN32 AND NOT BLAS_LIBRARIES AND NOT BLA_VENDOR) + if (NOT BLAS_LIBRARIES AND NOT BLA_VENDOR) find_package(OpenBLAS CONFIG) if(OpenBLAS_FOUND) message(STATUS "OpenBLAS CMake config path: ${OpenBLAS_DIR}") diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 4c688bd2cc..6ef8ddf3ed 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -19,9 +19,8 @@ configure_file(${CMAKE_CURRENT_LIST_DIR}/../highs/HConfig.h.in ${PROJECT_BINARY_ # print the version for debugging message(STATUS "Configuring highs_extras version ${VERSION}") - # support vcpkg for external dependencies -if(WIN32 AND DEFINED ENV{VCPKG_ROOT}) +if(DEFINED ENV{VCPKG_ROOT}) file(TO_CMAKE_PATH "$ENV{VCPKG_ROOT}" VCPKG_ROOT_CMAKE) if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) @@ -35,8 +34,26 @@ if(WIN32 AND DEFINED ENV{VCPKG_ROOT}) set(VCPKG_TARGET_TRIPLET "$ENV{VCPKG_TARGET_TRIPLET}" CACHE STRING "vcpkg triplet") else() - set(VCPKG_TARGET_TRIPLET "x64-windows-static" - CACHE STRING "vcpkg triplet") + if(CMAKE_GENERATOR_PLATFORM STREQUAL "Win32") + set(_vcpkg_arch x86) + elseif(CMAKE_GENERATOR_PLATFORM STREQUAL "ARM64") + set(_vcpkg_arch arm64) + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$") + set(_vcpkg_arch x64) + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64)$") + set(_vcpkg_arch arm64) + endif() + + if(WIN32 AND DEFINED _vcpkg_arch) + set(VCPKG_TARGET_TRIPLET "${_vcpkg_arch}-windows-static" + CACHE STRING "vcpkg triplet") + elseif(APPLE AND DEFINED _vcpkg_arch) + set(VCPKG_TARGET_TRIPLET "${_vcpkg_arch}-osx" + CACHE STRING "vcpkg triplet") + elseif(UNIX AND DEFINED _vcpkg_arch) + set(VCPKG_TARGET_TRIPLET "${_vcpkg_arch}-linux" + CACHE STRING "vcpkg triplet") + endif() endif() endif() @@ -171,7 +188,7 @@ function(highs_extras_link_blas target_name) message(FATAL_ERROR "OpenBLAS not found for highs_extras") endfunction() -if (${BUILD_SHARED_EXTRAS_LIB}) +if (BUILD_SHARED_EXTRAS_LIB) add_library(highs_extras SHARED ${highs_extras_sources} ${highs_extras_headers}) @@ -196,14 +213,15 @@ highs_extras_link_blas(highs_extras) if(MSVC) target_compile_options(highs_extras PRIVATE "/bigobj") else() - target_compile_options(highs_extras PRIVATE "-ftemplate-depth=2048") + target_compile_options(highs_extras PRIVATE + $<$:-ftemplate-depth=2048>) endif() set_target_properties(highs_extras PROPERTIES OUTPUT_NAME "highs_extras" POSITION_INDEPENDENT_CODE ON) -if(${BUILD_SHARED_EXTRAS_LIB}) +if(BUILD_SHARED_EXTRAS_LIB) set_target_properties(highs_extras PROPERTIES CXX_VISIBILITY_PRESET hidden C_VISIBILITY_PRESET hidden diff --git a/extern/HighsExtrasApi.h b/extern/HighsExtrasApi.h index ea80f97f66..67a48ad6c3 100644 --- a/extern/HighsExtrasApi.h +++ b/extern/HighsExtrasApi.h @@ -88,14 +88,6 @@ HIGHS_EXTRAS_API int highs_extras_amd_order(amd_int n, const amd_int Ap[], const amd_int Ai[], amd_int P[], double Control[], double Info[]); -// rcm -// HIGHS_EXTRAS_API int highs_extras_genrcm(HighsInt node_num, -// HighsInt adj_num, -// const HighsInt adj_row[], -// const HighsInt adj[], -// HighsInt perm[]); - - // blas HIGHS_EXTRAS_API void highs_extras_daxpy(const blasint n, const double alpha, const double* x, const blasint incx, @@ -188,10 +180,6 @@ struct amd { using order_t = decltype(&highs_extras_amd_order); }; -//struct rcm { -// using genrcm_t = decltype(&highs_extras_genrcm); -//}; - struct blas { using daxpy_t = decltype(&highs_extras_daxpy); using dcopy_t = decltype(&highs_extras_dcopy); diff --git a/extern/metis/metis.h b/extern/metis/metis.h index 3a2df2ecaf..2a0f331be3 100644 --- a/extern/metis/metis.h +++ b/extern/metis/metis.h @@ -63,31 +63,16 @@ #define COMPILER_GCC #endif -/* Include c99 int definitions and need constants. When building the library, +/* Include c99 int definitions and needed constants. When building the library, * these are already defined by GKlib; hence the test for _GKLIB_H_ */ #ifndef _GKLIB_H_ -#ifdef COMPILER_MSC -#include - -typedef __int32 int32_t; -typedef __int64 int64_t; -#define PRId32 "I32d" -#define PRId64 "I64d" -#define SCNd32 "ld" -#define SCNd64 "I64d" - -#ifdef _WIN32 -#include -#else -#define INT32_MIN ((int32_t)_I32_MIN) -#define INT32_MAX _I32_MAX -#define INT64_MIN ((int64_t)_I64_MIN) -#define INT64_MAX _I64_MAX +#ifdef __cplusplus +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS #endif - -#else -#include #endif +#include +#include #endif diff --git a/highs/CMakeLists.txt b/highs/CMakeLists.txt index 30542e7294..975ed9a005 100644 --- a/highs/CMakeLists.txt +++ b/highs/CMakeLists.txt @@ -160,10 +160,25 @@ else() set_target_properties(highs PROPERTIES INSTALL_RPATH "$ORIGIN") endif() - if (${BUILD_SHARED_EXTRAS_LIB}) - target_compile_definitions(highs PRIVATE HIGHS_SHARED_EXTRAS_LIBRARY) - else() - target_link_libraries(highs PRIVATE highs_extras) + if (HIPO) + if (BUILD_SHARED_EXTRAS_LIB) + target_compile_definitions(highs PRIVATE HIGHS_SHARED_EXTRAS_LIBRARY) + + if(APPLE) + set_target_properties(highs PROPERTIES + INSTALL_RPATH "@loader_path;@loader_path/../${CMAKE_INSTALL_LIBDIR}" + BUILD_RPATH "$" + ) + elseif(UNIX) + set_target_properties(highs PROPERTIES + INSTALL_RPATH "$ORIGIN" + BUILD_RPATH "$" + ) + endif() + + else() + target_link_libraries(highs PRIVATE highs_extras) + endif() endif() target_sources(highs PRIVATE ${sources} ${headers} ${win_version_file}) diff --git a/highs/HighsExternalDeps.cpp b/highs/HighsExternalDeps.cpp index 9313a2112d..14d89db9af 100644 --- a/highs/HighsExternalDeps.cpp +++ b/highs/HighsExternalDeps.cpp @@ -12,38 +12,6 @@ #include "HighsExternalDeps.h" #include "HConfig.h" -// Platform-specific includes for dynamic loading -#if defined(_WIN32) || defined(_WIN64) -#define WIN32_LEAN_AND_MEAN -#include -#define PATH_SEPARATOR "\\" -#else -#include -#define PATH_SEPARATOR "/" -#endif - -std::string getLibraryFilename() { -#if defined(_WIN32) || defined(_WIN64) - return "highs_extras.dll"; -#elif defined(__APPLE__) - return "libhighs_extras.dylib"; -#else - return "libhighs_extras.so"; -#endif -} - -template -bool resolveSymbol(void* handle, FuncType& target, const char* name) { -#if defined(_WIN32) || defined(_WIN64) - target = reinterpret_cast( - GetProcAddress(static_cast(handle), name)); -#else - target = reinterpret_cast(dlsym(handle, name)); -#endif - - return target != nullptr; -} - // c++11 does not support inline static definition HighsExternalDeps::amd HighsExternalDeps::amd_; HighsExternalDeps::blas HighsExternalDeps::blas_; @@ -55,8 +23,42 @@ HighsExternalDeps& HighsExternalDeps::instance() { return _instance; } -#ifdef HIPO #ifdef HIGHS_SHARED_EXTRAS_LIBRARY +// Platform-specific includes for dynamic loading +#if defined(_WIN32) || defined(_WIN64) +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#endif + +#include + +// anonymous namespace to limit the scope of helper functions to this file +namespace { + std::string getLibraryFilename(const std::string& path) { + #if defined(_WIN32) || defined(_WIN64) + return path + (path.empty() ? "" : "\\") + "highs_extras.dll"; + #elif defined(__APPLE__) + return path + (path.empty() ? "" : "@loader_path/") + "libhighs_extras.dylib"; + #else + return path + (path.empty() ? "" : "/") + "libhighs_extras.so"; + #endif + } + + template + bool resolveSymbol(void* handle, FuncType& target, const char* name) { + #if defined(_WIN32) || defined(_WIN64) + target = reinterpret_cast( + GetProcAddress(static_cast(handle), name)); + #else + target = reinterpret_cast(dlsym(handle, name)); + #endif + + return target != nullptr; + } +} + void HighsExternalDeps::clear() { amd_ = amd{}; blas_ = blas{}; @@ -82,100 +84,105 @@ void HighsExternalDeps::unload() { #define STRINGFY(s) STRINGFY0(s) #define STRINGFY0(s) #s +bool HighsExternalDeps::tryLoad() { + static const std::string empty_path = ""; + return tryLoad(empty_path); +} + bool HighsExternalDeps::tryLoad(const std::string& path) { HighsExternalDeps& inst = instance(); - - // Allow multiple attempts. - if (inst.available_) - return true; - - // printf("Attempting to load HiGHS Extras from: %s\n", path.c_str()); - inst.initialized_ = true; - inst.available_ = false; - - // Load library - const std::string full_path = path + PATH_SEPARATOR + getLibraryFilename(); - -#if defined(_WIN32) || defined(_WIN64) - inst.lib_handle_ = static_cast(LoadLibraryA(full_path.c_str())); - if (!inst.lib_handle_) { - DWORD error = GetLastError(); - char* msg = nullptr; - FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - nullptr, error, 0, reinterpret_cast(&msg), 0, - nullptr); - inst.last_error_ = - "Failed to load " + full_path + ": " + (msg ? msg : "Unknown error"); - if (msg) LocalFree(msg); - return false; - } -#else - inst.lib_handle_ = dlopen(full_path.c_str(), RTLD_NOW | RTLD_LOCAL); - if (!inst.lib_handle_) { - const char* err = dlerror(); - inst.last_error_ = - "Failed to load " + full_path + ": " + (err ? err : "Unknown error"); - return false; - } -#endif - - // Resolve all function pointers - void* h = inst.lib_handle_; - bool ok = true; - - // AMD - ok &= resolveSymbol(h, amd_.defaults_,"highs_extras_amd_defaults"); - ok &= resolveSymbol(h, amd_.order_, "highs_extras_amd_order"); - - // BLAS - ok &= resolveSymbol(h, blas_.daxpy_, "highs_extras_daxpy"); - ok &= resolveSymbol(h, blas_.dcopy_, "highs_extras_dcopy"); - ok &= resolveSymbol(h, blas_.dscal_, "highs_extras_dscal"); - ok &= resolveSymbol(h, blas_.dswap_, "highs_extras_dswap"); - ok &= resolveSymbol(h, blas_.dgemv_, "highs_extras_dgemv"); - ok &= resolveSymbol(h, blas_.dtpsv_, "highs_extras_dtpsv"); - ok &= resolveSymbol(h, blas_.dtrsv_, "highs_extras_dtrsv"); - ok &= resolveSymbol(h, blas_.dger_, "highs_extras_dger"); - ok &= resolveSymbol(h, blas_.dgemm_, "highs_extras_dgemm"); - ok &= resolveSymbol(h, blas_.dsyrk_, "highs_extras_dsyrk"); - ok &= resolveSymbol(h, blas_.dtrsm_, "highs_extras_dtrsm"); - ok &= resolveSymbol(h, blas_.set_num_threads_, "highs_extras_openblas_set_num_threads"); - ok &= resolveSymbol(h, blas_.library_, "highs_extras_blas_library"); - - // METIS - ok &= resolveSymbol(h, metis_.set_default_options_, - "highs_extras_metis_set_default_options"); - ok &= resolveSymbol(h, metis_.nodend_, "highs_extras_metis_nodend"); - - //// RCM - //ok &= resolveSymbol(h, rcm.genrcm_, "highs_extras_genrcm"); - - // Check ABI compatibility - highs_extras_api::core::get_version_t get_version = nullptr; - ok &= resolveSymbol(h, get_version, "highs_extras_get_version"); - - ok &= resolveSymbol(h, instance().get_copyright_, "highs_extras_get_copyright"); - - if (!ok) { - inst.last_error_ = "Failed to resolve required external functions"; - inst.unload(); - return false; - } - - std::string highs_version = STRINGFY(HIGHS_VERSION_MAJOR) "." STRINGFY( - HIGHS_VERSION_MINOR) "." STRINGFY(HIGHS_VERSION_PATCH); - std::string extras_version = get_version(); - if (extras_version != highs_version) { - inst.last_error_ = - "HiGHS Extras ABI version mismatch: expected " + highs_version + - ", got " + extras_version + - ". Please reinstall: pip install --force-reinstall highspy[extras]"; - inst.unload(); - return false; - } - - inst.available_ = true; - return true; + static std::once_flag flag; + + // prevents multiple attempts to load the library + // ensure thread safety (multiple threads may call tryLoad simultaneously) + std::call_once(flag, [&]() { + inst.available_ = false; + + // Load library + const std::string full_path = getLibraryFilename(path); + bool ok = true; + + #if defined(_WIN32) || defined(_WIN64) + inst.lib_handle_ = static_cast(LoadLibraryA(full_path.c_str())); + if (!inst.lib_handle_) { + DWORD error = GetLastError(); + char* msg = nullptr; + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, error, 0, reinterpret_cast(&msg), 0, + nullptr); + inst.status_ = + "Extras: Failed to load " + full_path + ": " + (msg ? msg : "Unknown error"); + if (msg) LocalFree(msg); + ok = false; + } + #else + inst.lib_handle_ = dlopen(full_path.c_str(), RTLD_NOW | RTLD_LOCAL); + if (!inst.lib_handle_) { + const char* err = dlerror(); + inst.status_ = + "Extras: Failed to load " + full_path + ": " + (err ? err : "Unknown error"); + ok = false; + } + #endif + else { + // Resolve all function pointers + void* h = inst.lib_handle_; + + // AMD + ok &= resolveSymbol(h, amd_.defaults_,"highs_extras_amd_defaults"); + ok &= resolveSymbol(h, amd_.order_, "highs_extras_amd_order"); + + // BLAS + ok &= resolveSymbol(h, blas_.daxpy_, "highs_extras_daxpy"); + ok &= resolveSymbol(h, blas_.dcopy_, "highs_extras_dcopy"); + ok &= resolveSymbol(h, blas_.dscal_, "highs_extras_dscal"); + ok &= resolveSymbol(h, blas_.dswap_, "highs_extras_dswap"); + ok &= resolveSymbol(h, blas_.dgemv_, "highs_extras_dgemv"); + ok &= resolveSymbol(h, blas_.dtpsv_, "highs_extras_dtpsv"); + ok &= resolveSymbol(h, blas_.dtrsv_, "highs_extras_dtrsv"); + ok &= resolveSymbol(h, blas_.dger_, "highs_extras_dger"); + ok &= resolveSymbol(h, blas_.dgemm_, "highs_extras_dgemm"); + ok &= resolveSymbol(h, blas_.dsyrk_, "highs_extras_dsyrk"); + ok &= resolveSymbol(h, blas_.dtrsm_, "highs_extras_dtrsm"); + ok &= resolveSymbol(h, blas_.set_num_threads_, "highs_extras_openblas_set_num_threads"); + ok &= resolveSymbol(h, blas_.library_, "highs_extras_blas_library"); + + // METIS + ok &= resolveSymbol(h, metis_.set_default_options_, + "highs_extras_metis_set_default_options"); + ok &= resolveSymbol(h, metis_.nodend_, "highs_extras_metis_nodend"); + + // Check ABI compatibility + highs_extras_api::core::get_version_t get_version = nullptr; + ok &= resolveSymbol(h, get_version, "highs_extras_get_version"); + + ok &= resolveSymbol(h, instance().get_copyright_, "highs_extras_get_copyright"); + + if (!ok) { + inst.status_ = "Extras: Failed to resolve required external functions"; + inst.unload(); + } + else { + std::string highs_version = STRINGFY(HIGHS_VERSION_MAJOR) "." STRINGFY( + HIGHS_VERSION_MINOR) "." STRINGFY(HIGHS_VERSION_PATCH); + std::string extras_version = get_version(); + if (extras_version != highs_version) { + inst.status_ = + "Extras: ABI version mismatch: expected " + highs_version + + ", got " + extras_version + + ". Please reinstall: pip install --force-reinstall highspy[extras]"; + inst.unload(); + ok = false; + } + else { + inst.status_ = "Extras: Successfully loaded"; + } + } + } + + inst.available_ = ok; + }); + + return inst.available_; } #endif -#endif diff --git a/highs/HighsExternalDeps.h b/highs/HighsExternalDeps.h index a1c63960fe..c0cc7b5f31 100644 --- a/highs/HighsExternalDeps.h +++ b/highs/HighsExternalDeps.h @@ -176,11 +176,9 @@ struct HighsExternalDeps { } }; + // rcm has MIT license and is always statically linked + // accessed via HighsExternalDeps for consistency struct rcm { -//#ifdef HIGHS_SHARED_EXTRAS_LIBRARY -// highs_extras_api::rcm::genrcm_t genrcm_ = nullptr; -//#endif - static inline int genrcm(HighsInt node_num, HighsInt adj_num, const HighsInt adj_row[], const HighsInt adj[], HighsInt perm[]) { @@ -203,28 +201,28 @@ struct HighsExternalDeps { HighsExternalDeps(const HighsExternalDeps&) = delete; HighsExternalDeps& operator=(const HighsExternalDeps&) = delete; + static bool tryLoad(); #ifndef HIPO static inline bool isAvailable() { return false; } static constexpr bool isAvailableAtCompile() { return false; } - static bool tryLoad(const std::string& path) {return false; } - static void unload() {}; - static const std::string getLastError() { return ""; } - + static inline bool tryLoad(const std::string& path) { return false; } + static inline void unload() {} + static inline const std::string getLoadStatus() { return "Extras: Unavailable"; } #elif defined(HIGHS_SHARED_EXTRAS_LIBRARY) - static inline bool isAvailable() { return instance().available_; } + static inline bool isAvailable() { return tryLoad(); } static constexpr bool isAvailableAtCompile() { return false; } static bool tryLoad(const std::string& path); static void unload(); - static const std::string getLastError() { return instance().last_error_; } + static const std::string getLoadStatus() { return instance().status_; } #else static inline bool isAvailable() { return true; } static constexpr bool isAvailableAtCompile() { return true; } - static inline bool tryLoad(const std::string&) { return true; } + static inline bool tryLoad(const std::string& path) { return true; } static inline void unload() {} - static inline const std::string getLastError() { return ""; } + static inline const std::string getLoadStatus() { return "Extras: Available at compile time"; } #endif static inline std::string getCopyrightInfo() { @@ -243,8 +241,7 @@ struct HighsExternalDeps { highs_extras_api::core::get_copyright_t get_copyright_ = nullptr; bool available_ = false; - bool initialized_ = false; - std::string last_error_; + std::string status_; void clear(); #endif diff --git a/highs/lp_data/HighsOptions.cpp b/highs/lp_data/HighsOptions.cpp index 1e5e9420b7..22e0d720a6 100644 --- a/highs/lp_data/HighsOptions.cpp +++ b/highs/lp_data/HighsOptions.cpp @@ -17,41 +17,6 @@ #include "HighsExternalDeps.h" #include "util/stringutil.h" -#ifdef __linux__ -#include - -#include - -static std::string getExecutableDir() { - char buf[PATH_MAX]; - ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf) - 1); - if (len == -1) return "."; - buf[len] = '\0'; - std::string path(buf); - size_t pos = path.find_last_of('/'); - return path.substr(0, pos); -} -#endif - -bool hipoAvailable() { - if (HighsExternalDeps::tryLoad(".") == false) { -#ifdef __linux__ - const std::string exeDir = getExecutableDir(); - const std::string libDir = exeDir + "/../lib"; - const std::string lib64Dir = exeDir + "/../lib64"; - - if (HighsExternalDeps::tryLoad(exeDir) == false && - HighsExternalDeps::tryLoad(libDir) == false && - HighsExternalDeps::tryLoad(lib64Dir) == false) - std::cout << HighsExternalDeps::getLastError() << std::endl; -#else - std::cout << HighsExternalDeps::getLastError() << std::endl; -#endif - } - - return HighsExternalDeps::isAvailable(); -} - void highsOpenLogFile(HighsLogOptions& log_options, std::vector& option_records, const std::string& log_file) { @@ -114,21 +79,9 @@ bool optionOffOnOk(const HighsLogOptions& report_log_options, return false; } -// #ifndef HIPO -// static void noHipoErrorLog(const HighsLogOptions& report_log_options, -// const string& option_name) { -// highsLogUser( -// report_log_options, HighsLogType::kError, -// "The HiPO solver was requested via the \"%s\" option, but this build " -// "was compiled without HiPO support. Reconfigure with -DFAST_BUILD=ON " -// "and -DHIPO=ON to enable HiPO.\n", -// option_name.c_str()); -// } -// #endif - bool optionSolverOk(const HighsLogOptions& report_log_options, const string& value) { - if (value == kHipoString && !hipoAvailable()) { + if (value == kHipoString && !HighsExternalDeps::isAvailable()) { if (HighsExternalDeps::isAvailableAtCompile()) { highsLogUser( report_log_options, HighsLogType::kError, @@ -150,14 +103,14 @@ bool optionSolverOk(const HighsLogOptions& report_log_options, return false; } if (value == kHighsChooseString || value == kSimplexString || - value == kIpmString || (value == kHipoString && hipoAvailable()) || + value == kIpmString || (value == kHipoString && HighsExternalDeps::isAvailable()) || value == kIpxString || value == kPdlpString) return true; highsLogUser(report_log_options, HighsLogType::kError, "Value \"%s\" for LP solver option (\"%s\") is not one of " "%s\"%s\", \"%s\", \"%s\", \"%s\" or \"%s\"\n", value.c_str(), kSolverString.c_str(), - hipoAvailable() ? (kHipoString + "\", \"").c_str() : "", + HighsExternalDeps::isAvailable() ? (kHipoString + "\", \"").c_str() : "", kHighsChooseString.c_str(), kSimplexString.c_str(), kIpmString.c_str(), kIpxString.c_str(), kPdlpString.c_str()); return false; @@ -165,7 +118,7 @@ bool optionSolverOk(const HighsLogOptions& report_log_options, bool optionMipLpSolverOk(const HighsLogOptions& report_log_options, const string& value) { - if (value == kHipoString && !hipoAvailable()) { + if (value == kHipoString && !HighsExternalDeps::isAvailable()) { if (HighsExternalDeps::isAvailableAtCompile()) { highsLogUser( report_log_options, HighsLogType::kError, @@ -188,14 +141,14 @@ bool optionMipLpSolverOk(const HighsLogOptions& report_log_options, } if (value == kHighsChooseString || value == kSimplexString || - value == kIpmString || (value == kHipoString && hipoAvailable()) || + value == kIpmString || (value == kHipoString && HighsExternalDeps::isAvailable()) || value == kIpxString) return true; highsLogUser(report_log_options, HighsLogType::kError, "Value \"%s\" for MIP LP solver option (\"%s\") is not one of " "%s\"%s\", \"%s\", \"%s\" or \"%s\"\n", value.c_str(), kMipLpSolverString.c_str(), - hipoAvailable() ? (kHipoString + "\", \"").c_str() : "", + HighsExternalDeps::isAvailable() ? (kHipoString + "\", \"").c_str() : "", kHighsChooseString.c_str(), kSimplexString.c_str(), kIpmString.c_str(), kIpxString.c_str()); return false; @@ -203,7 +156,7 @@ bool optionMipLpSolverOk(const HighsLogOptions& report_log_options, bool optionMipIpmSolverOk(const HighsLogOptions& report_log_options, const string& value) { - if (value == kHipoString && !hipoAvailable()) { + if (value == kHipoString && !HighsExternalDeps::isAvailable()) { if (HighsExternalDeps::isAvailableAtCompile()) { highsLogUser( report_log_options, HighsLogType::kError, @@ -225,13 +178,13 @@ bool optionMipIpmSolverOk(const HighsLogOptions& report_log_options, return false; } if (value == kHighsChooseString || value == kIpmString || - (value == kHipoString && hipoAvailable()) || value == kIpxString) + (value == kHipoString && HighsExternalDeps::isAvailable()) || value == kIpxString) return true; highsLogUser(report_log_options, HighsLogType::kError, "Value \"%s\" for MIP IPM solver (\"%s\") option is not one of " "%s\"%s\", \"%s\" or \"%s\"\n", value.c_str(), kMipIpmSolverString.c_str(), - hipoAvailable() ? (kHipoString + "\", \"").c_str() : "", + HighsExternalDeps::isAvailable() ? (kHipoString + "\", \"").c_str() : "", kHighsChooseString.c_str(), kIpmString.c_str(), kIpxString.c_str()); return false; diff --git a/highspy/CMakeLists.txt b/highspy/CMakeLists.txt index 4f05587629..e3be16b799 100644 --- a/highspy/CMakeLists.txt +++ b/highspy/CMakeLists.txt @@ -31,7 +31,7 @@ target_link_libraries(_core PRIVATE pybind11::headers highs ${CMAKE_DL_LIBS}) target_compile_definitions(_core PRIVATE VERSION_INFO=${PROJECT_VERSION}) -if (${BUILD_SHARED_EXTRAS_LIB}) +if (BUILD_SHARED_EXTRAS_LIB) target_compile_definitions(_core PRIVATE HIGHS_SHARED_EXTRAS_LIBRARY) else() target_link_libraries(_core PRIVATE highs_extras) @@ -52,11 +52,13 @@ endif() # The install directory is the output (wheel) directory install(TARGETS _core RUNTIME DESTINATION highspy - LIBRARY DESTINATION highspy) + LIBRARY DESTINATION highspy + COMPONENT python) # Only needed if the wheel is built with a shared library, but can be left in for static linking as well. # Include highs library (but ignore .lib file) install(TARGETS highs RUNTIME DESTINATION highspy LIBRARY DESTINATION highspy - NAMELINK_SKIP) + NAMELINK_SKIP + COMPONENT python) diff --git a/highspy/highs_bindings.cpp b/highspy/highs_bindings.cpp index fdb9621a2e..017aa1f0b4 100644 --- a/highspy/highs_bindings.cpp +++ b/highspy/highs_bindings.cpp @@ -20,25 +20,7 @@ std::string get_parent_directory(const std::string& path) { if (pos == 0) return path.substr(0, 1); return path.substr(0, pos); } - -std::string join_paths(const std::string& base, const std::string& leaf) { - if (base.empty()) return leaf; - if (leaf.empty()) return base; - - const char last = base.back(); - const char first = leaf.front(); - const bool base_has_sep = last == '/' || last == '\\'; - const bool leaf_has_sep = first == '/' || first == '\\'; - - if (base_has_sep && leaf_has_sep) return base + leaf.substr(1); - if (base_has_sep || leaf_has_sep) return base + leaf; -#ifdef _WIN32 - return base + "\\" + leaf; -#else - return base + "/" + leaf; -#endif } -} // namespace // arrays are assumed to be contiguous c-style arrays of correct type // * c_style forces the array to be stored in C-style contiguous order @@ -1012,21 +994,10 @@ std::string highs_locatePythonPackage(const std::string module_name) { } PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) { - auto extras_module = highs_locatePythonPackage("highspy_extras"); + HighsExternalDeps::tryLoad(highs_locatePythonPackage("highspy_extras")); - if (!extras_module.empty()) { - bool loaded = HighsExternalDeps::tryLoad(extras_module); - - /// TODO: Remove DEBUG prints - if (loaded) { - py::print("Successfully loaded highs extras"); - } else { - py::print("Failed to load highs extras; skipping optional DLL preload"); - py::print(HighsExternalDeps::getLastError()); - } - } else { - py::print("highs extras not available; skipping optional DLL preload"); - } + // static function to get the load status of the extras library + m.def("getExtrasLoadStatus", &HighsExternalDeps::getLoadStatus); // To keep a smaller diff, for reviewers, the declarations are not moved, but // keep in mind: diff --git a/highspy/pyproject.toml b/highspy/pyproject.toml index 077bf98c33..f00e0e0495 100644 --- a/highspy/pyproject.toml +++ b/highspy/pyproject.toml @@ -93,9 +93,9 @@ sdist.exclude = [ # # exclude files from CMake output as well. Editable installs may not respect # # this exclusion. wheel.exclude = ["include", "share", "lib*/", "bin", "LICENSE.txt", "README.md"] +install.components = ["python"] [tool.scikit-build.cmake.define] -#HIPO_PYTHON_EXTRAS_BUILD = "ON" # Allows the HiPO IpxWrapper link to be compiled HIPO = "ON" HIPO_PYTHON = "ON"