diff --git a/.gitignore b/.gitignore index 3e4f3b1088..01bcefc082 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ cmake # Sphinx builds docs/_build/ +/build diff --git a/Superbuild/BoostConfig.cmake.in b/Superbuild/BoostConfig.cmake.in index fbfc876a84..2623d5acc4 100644 --- a/Superbuild/BoostConfig.cmake.in +++ b/Superbuild/BoostConfig.cmake.in @@ -1,6 +1,181 @@ -set(SCI_BOOST_INCLUDE "@SCI_BOOST_INCLUDE@") - -# These are IMPORTED targets created by FooBarTargets.cmake -set(SCI_BOOST_LIBRARY @SCI_BOOST_LIBRARY@) -set(SCI_BOOST_LIBRARY_DIR "@SCI_BOOST_LIBRARY_DIR@") -set(SCI_BOOST_USE_FILE "@SCI_BOOST_USE_FILE@") +# ============================================================================ +# BoostConfig.cmake - template for SCIRun superbuild to generate a CMake config file for Boost +# ============================================================================ + +@PACKAGE_INIT@ + +set(Boost_FOUND TRUE) + +# --------------------------------------------------------------------------- +# Paths +# --------------------------------------------------------------------------- +set(Boost_INCLUDE_DIRS "@SCI_BOOST_INCLUDE@") +set(Boost_LIBRARY_DIR "@SCI_BOOST_LIBRARY_DIR@") + +# --------------------------------------------------------------------------- +# Helper: define Boost imported library target (cross-platform) +# --------------------------------------------------------------------------- +function(_scirun_define_boost_target lib) + # If Boost already created the target, just ensure includes + if(TARGET Boost::${lib}) + set_target_properties(Boost::${lib} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIRS}" + ) + return() + endif() + + # Boost.Atomic is header-only on macOS + if(APPLE AND lib STREQUAL "atomic") + add_library(Boost::atomic INTERFACE IMPORTED) + set_target_properties(Boost::atomic PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIRS}" + ) + return() + endif() + + # Create imported library target + add_library(Boost::${lib} UNKNOWN IMPORTED) + set_target_properties(Boost::${lib} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIRS}" + ) + + # Boost.Python static macro (SCIRun convention) + if(lib MATCHES "^python") + set_target_properties(Boost::${lib} PROPERTIES + INTERFACE_COMPILE_DEFINITIONS BOOST_PYTHON_STATIC_LIB + ) + endif() + + # Detect architecture + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_boost_arch "x64") + else() + set(_boost_arch "x32") + endif() + + set(_release_lib "") + set(_debug_lib "") + + # ------------------------------------------------------------------------- + # Windows: SCIRun-built Boost uses tagged .lib names + # ------------------------------------------------------------------------- + if(WIN32) + file(GLOB _all_candidates + "${Boost_LIBRARY_DIR}/libboost_${lib}-*.lib" + ) + + list(FILTER _all_candidates INCLUDE REGEX "-${_boost_arch}-") + + if(NOT _all_candidates) + message(FATAL_ERROR + "Boost '${lib}' not found for architecture ${_boost_arch}\n" + "Directory: ${Boost_LIBRARY_DIR}" + ) + endif() + + # ----------------------------------------------------------------------- + # ABI-aware Debug / Release selection (Boost 1.8+) + # + # Rules: + # - Debug libraries contain '-g' + # - Release libraries do NOT contain '-g' + # - 'y' means Python enabled and may appear in BOTH + # ----------------------------------------------------------------------- + + # Debug candidates + set(_dbg_candidates "${_all_candidates}") + list(FILTER _dbg_candidates INCLUDE REGEX "-g") + + if(_dbg_candidates) + list(GET _dbg_candidates 0 _debug_lib) + endif() + + # Release candidates + set(_rel_candidates "${_all_candidates}") + list(FILTER _rel_candidates EXCLUDE REGEX "-g") + + if(_rel_candidates) + list(GET _rel_candidates 0 _release_lib) + endif() + + # ------------------------------------------------------------------------- + # macOS / Linux + # ------------------------------------------------------------------------- + else() + file(GLOB _all_candidates + "${Boost_LIBRARY_DIR}/libboost_${lib}.a" + "${Boost_LIBRARY_DIR}/libboost_${lib}-mt.a" + "${Boost_LIBRARY_DIR}/libboost_${lib}.dylib" + "${Boost_LIBRARY_DIR}/libboost_${lib}-mt.dylib" + ) + + if(NOT _all_candidates) + message(FATAL_ERROR + "Boost library '${lib}' could not be resolved.\n" + "Directory: ${Boost_LIBRARY_DIR}\n" + "Platform: ${CMAKE_SYSTEM_NAME}" + ) + endif() + + list(GET _all_candidates 0 _release_lib) + set(_debug_lib "${_release_lib}") + endif() + + # ------------------------------------------------------------------------- + # Fail fast if resolution failed + # ------------------------------------------------------------------------- + if(NOT _release_lib OR NOT _debug_lib) + message(FATAL_ERROR + "Boost library '${lib}' could not be resolved for both configurations.\n" + "All candidates:\n ${_all_candidates}" + ) + endif() + + set_target_properties(Boost::${lib} PROPERTIES + IMPORTED_CONFIGURATIONS "Release;Debug" + IMPORTED_LOCATION_RELEASE "${_release_lib}" + IMPORTED_LOCATION_DEBUG "${_debug_lib}" + ) +endfunction() + +# --------------------------------------------------------------------------- +# Core Boost components +# --------------------------------------------------------------------------- +_scirun_define_boost_target(atomic) +_scirun_define_boost_target(chrono) +_scirun_define_boost_target(date_time) +_scirun_define_boost_target(filesystem) +_scirun_define_boost_target(program_options) +_scirun_define_boost_target(regex) +_scirun_define_boost_target(serialization) +_scirun_define_boost_target(thread) + +# --------------------------------------------------------------------------- +# Boost.Python (optional) +# --------------------------------------------------------------------------- +if(@BUILD_WITH_PYTHON@) + _scirun_define_boost_target(python@SCI_PYTHON_VERSION_SHORT_WIN32@) +endif() + +# --------------------------------------------------------------------------- +# Compatibility variables +# --------------------------------------------------------------------------- +set(Boost_LIBRARIES + Boost::atomic + Boost::chrono + Boost::date_time + Boost::filesystem + Boost::program_options + Boost::regex + Boost::serialization + Boost::thread +) + +add_library(Boost::headers INTERFACE IMPORTED) +set_target_properties(Boost::headers PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Boost_INCLUDE_DIRS}" +) + +if(@BUILD_WITH_PYTHON@) + list(APPEND Boost_LIBRARIES Boost::python@SCI_PYTHON_VERSION_SHORT_WIN32@) +endif() \ No newline at end of file diff --git a/Superbuild/BoostExternal.cmake b/Superbuild/BoostExternal.cmake index b21974cb00..ad01ff08b5 100644 --- a/Superbuild/BoostExternal.cmake +++ b/Superbuild/BoostExternal.cmake @@ -24,103 +24,352 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -# Borrowed CMake code from the MaidSafe Boost CMake build -# found at https://github.com/maidsafe/MaidSafe/blob/master/cmake_modules/add_boost.cmake -# and code borrowed from ITK4 HDFMacros.cmake +# +# Boost External Project for SCIRun Superbuild +# SET_PROPERTY(DIRECTORY PROPERTY "EP_BASE" ${ep_base}) -# disable auto linking -# also set in Seg3D? +# ------------------------------------------------------------------------------ +# Boost compile flags +# ------------------------------------------------------------------------------ SET(boost_CXX_Flags "-DBOOST_ALL_NO_LIB=1") IF(APPLE) - LIST(APPEND boost_CXX_Flag "-DBOOST_LCAST_NO_WCHAR_T" "-DBOOST_THREAD_DONT_USE_ATOMIC") + LIST(APPEND boost_CXX_Flags "-DBOOST_LCAST_NO_WCHAR_T" "-DBOOST_THREAD_DONT_USE_ATOMIC") ENDIF() IF(WIN32) - LIST(APPEND boost_CXX_Flag "-DBOOST_BIND_ENABLE_STDCALL") + LIST(APPEND boost_CXX_Flags "-DBOOST_BIND_ENABLE_STDCALL") ENDIF() -SET( boost_DEPENDENCIES ) +SET(boost_DEPENDENCIES) -# explicitly set library list +# ------------------------------------------------------------------------------ +# Explicit library list +# ------------------------------------------------------------------------------ SET(boost_Libraries - "atomic" - "chrono" - "date_time" - "exception" - "filesystem" - "program_options" - "regex" - "serialization" - #"system" - "thread" - CACHE INTERNAL "Boost library name.") + atomic + chrono + date_time + exception + filesystem + program_options + regex + serialization + thread + CACHE INTERNAL "Boost library names." +) IF(BUILD_WITH_PYTHON) ADD_DEFINITIONS(-DBOOST_PYTHON_STATIC_LIB=1) LIST(APPEND boost_Libraries python) LIST(APPEND boost_DEPENDENCIES Python_external) - LIST(APPEND boost_CXX_Flag "-DBOOST_PYTHON_STATIC_MODULE" "-DBOOST_PYTHON_STATIC_LIB") + LIST(APPEND boost_CXX_Flags "-DBOOST_PYTHON_STATIC_MODULE" "-DBOOST_PYTHON_STATIC_LIB") ENDIF() -# for travis clang builds--need a narrower test +# ------------------------------------------------------------------------------ +# Darwin/Unix/Windows consistency flags +# ------------------------------------------------------------------------------ IF(UNIX) ADD_DEFINITIONS(-DBOOST_NO_CXX11_ALLOCATOR) ENDIF() -SET(boost_GIT_TAG "origin/v1.89.0a") - -# TODO: set up 64-bit build detection -# Boost Jam needs to have 64-bit build explicitly configured IF(WIN32) SET(FORCE_64BIT_BUILD ON) ENDIF() +SET(_boost_git_url "https://github.com/CIBC-Internal/boost.git") +SET(_boost_git_tag "v1.90.0") -SET(boost_GIT_URL "https://github.com/CIBC-Internal/boost.git") +# ------------------------------------------------------------------------------ +# Compute Python library filenames +# ------------------------------------------------------------------------------ +if(WIN32 AND MSVC) + set(SCI_PYTHON_LIBRARY_FILE_RELEASE + "${SCI_PYTHON_LIBRARY_RELEASE}") + set(SCI_PYTHON_LIBRARY_FILE_DEBUG + "${SCI_PYTHON_LIBRARY_DEBUG}") +elseif(APPLE) + set(SCI_PYTHON_LIBRARY_FILE + "${SCI_PYTHON_LIBRARY_DIR}/libpython${SCI_PYTHON_VERSION_SHORT}.dylib") +else() + set(SCI_PYTHON_LIBRARY_FILE + "${SCI_PYTHON_LIBRARY_DIR}/libpython${SCI_PYTHON_VERSION_SHORT}.so") +endif() -# TODO: fix install step -# -# If CMake ever allows overriding the checkout command or adding flags, -# git checkout -q will silence message about detached head (harmless). +# ------------------------------------------------------------------------------ +# Compute b2 Python flags (MUST be separate arguments) +# ------------------------------------------------------------------------------ +IF(BUILD_WITH_PYTHON) + SET(BOOST_PYTHON_WITH_FLAG --with-python) + #SET(BOOST_PYTHON_EXE_FLAG python=${SCI_PYTHON_EXE}) + #SET(BOOST_PYTHON_INC_FLAG include=${SCI_PYTHON_INCLUDE}) + #SET(BOOST_PYTHON_LIB_FLAG library-path=${SCI_PYTHON_LIBRARY_DIR}) + SET(BOOST_PYTHON_VERSION_FLAG python-version=${SCI_PYTHON_VERSION_SHORT}) +ELSE() + SET(BOOST_PYTHON_WITH_FLAG "") + SET(BOOST_PYTHON_EXE_FLAG "") + SET(BOOST_PYTHON_INC_FLAG "") + SET(BOOST_PYTHON_LIB_FLAG "") +ENDIF() + +if(WIN32) + # Convert Windows PATH to CMake-friendly forward-slash form + file(TO_CMAKE_PATH "$ENV{PATH}" _SCIRUN_ENV_PATH_CMAKE) +else() + set(_SCIRUN_ENV_PATH_CMAKE "$ENV{PATH}") +endif() +# ------------------------------------------------------------------------------ +# ExternalProject_Add: Boost +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ +# Compute Python-related CMake cache arguments for Boost +# ------------------------------------------------------------------------------ +if(BUILD_WITH_PYTHON) + if(WIN32 AND MSVC) + set(_BOOST_PYTHON_CACHE_ARGS + -DPython3_FIND_FRAMEWORK:STRING=NEVER + -DPython3_FIND_STRATEGY:STRING=LOCATION + -DPython3_FIND_REQUIRED:BOOL=ON + -DPython3_ROOT_DIR:PATH=${SCI_PYTHON_ROOT_DIR} + -DPython3_EXECUTABLE:FILEPATH=${SCI_PYTHON_EXE} + -DPython3_INCLUDE_DIR:PATH=${SCI_PYTHON_INCLUDE} + -DPython3_LIBRARY_DEBUG:FILEPATH=${SCI_PYTHON_LIBRARY_FILE_DEBUG} + -DPython3_LIBRARY_RELEASE:FILEPATH=${SCI_PYTHON_LIBRARY_FILE_RELEASE} + ) + else() + set(_BOOST_PYTHON_CACHE_ARGS + -DPython3_FIND_FRAMEWORK:STRING=NEVER + -DPython3_FIND_STRATEGY:STRING=LOCATION + -DPython3_FIND_REQUIRED:BOOL=ON + -DPython3_ROOT_DIR:PATH=${SCI_PYTHON_ROOT_DIR} + -DPython3_EXECUTABLE:FILEPATH=${SCI_PYTHON_EXE} + -DPython3_INCLUDE_DIR:PATH=${SCI_PYTHON_INCLUDE} + -DPython3_LIBRARY:FILEPATH=${SCI_PYTHON_LIBRARY_FILE} + ) + endif() +else() + # Python completely disabled: do not expose Python in any form + set(_BOOST_PYTHON_CACHE_ARGS + -DPython3_FIND_REQUIRED:BOOL=OFF + ) +endif() + +# ------------------------------------------------------------------------------ +# Compute Python-related environment variables for Boost/b2 +# ------------------------------------------------------------------------------ +if(BUILD_WITH_PYTHON) + set(_BOOST_PYTHON_ENV + "PYTHONHOME=${SCI_PYTHON_ROOT_DIR}" + "PYTHONPATH=" + "PATH=${SCI_PYTHON_ROOT_DIR}/bin:${_SCIRUN_ENV_PATH_CMAKE}" + ) +else() + set(_BOOST_PYTHON_ENV + "PYTHONPATH=" + "PATH=${_SCIRUN_ENV_PATH_CMAKE}" + ) +endif() + +# ------------------------------------------------------------------------------ +# ExternalProject_Add: Boost +# ------------------------------------------------------------------------------ ExternalProject_Add(Boost_external DEPENDS ${boost_DEPENDENCIES} - GIT_REPOSITORY ${boost_GIT_URL} - GIT_TAG ${boost_GIT_TAG} + GIT_REPOSITORY ${_boost_git_url} + GIT_TAG ${_boost_git_tag} BUILD_IN_SOURCE ON PATCH_COMMAND "" INSTALL_COMMAND "" + CMAKE_CACHE_ARGS -DCMAKE_VERBOSE_MAKEFILE:BOOL=${CMAKE_VERBOSE_MAKEFILE} -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON - -DBUILD_PYTHON:BOOL=${BUILD_WITH_PYTHON} - -DPython_DIR:PATH=${Python_DIR} -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DFORCE_64BIT_BUILD:BOOL=${FORCE_64BIT_BUILD} - -DSCI_BOOST_LIBRARIES:STATIC=${boost_Libraries} - -DSCI_BOOST_CXX_FLAGS:STRING=${boost_CXX_Flags} + + # ---------- Python strictly controlled by SCIRun option ---------- + -DBUILD_PYTHON:BOOL=${BUILD_WITH_PYTHON} + + ${_BOOST_PYTHON_CACHE_ARGS} + + CMAKE_COMMAND_ENV + ${_BOOST_PYTHON_ENV} + "CMAKE_FIND_USE_SYSTEM_PACKAGE_REGISTRY=FALSE" + "CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=FALSE" ) +# ------------------------------------------------------------------------------ +# Internal paths from ExternalProject +# ------------------------------------------------------------------------------ ExternalProject_Get_Property(Boost_external INSTALL_DIR) ExternalProject_Get_Property(Boost_external SOURCE_DIR) -SET(SCI_BOOST_INCLUDE ${SOURCE_DIR}) -SET(SCI_BOOST_LIBRARY_DIR ${SOURCE_DIR}/lib) -SET(SCI_BOOST_USE_FILE ${INSTALL_DIR}/UseBoost.cmake) -SET(BOOST_PREFIX "boost_") -SET(THREAD_POSTFIX "-mt") +IF(WIN32) + SET(_B2_CMD ${SOURCE_DIR}/b2.exe) + SET(_B2_BOOTSTRAP_CMD bootstrap.bat) +ELSE() + SET(_B2_CMD ${SOURCE_DIR}/b2) + SET(_B2_BOOTSTRAP_CMD ./bootstrap.sh) +ENDIF() -SET(SCI_BOOST_LIBRARY) +# -------------------------------------------------------------- +# Step: bootstrap b2 +# -------------------------------------------------------------- +ExternalProject_Add_Step(Boost_external bootstrap_b2 + COMMAND ${_B2_BOOTSTRAP_CMD} + WORKING_DIRECTORY ${SOURCE_DIR} + DEPENDEES update + INDEPENDENT 1 + COMMENT "Bootstrapping b2" +) -FOREACH(lib ${boost_Libraries}) - SET(LIB_NAME "${BOOST_PREFIX}${lib}${THREAD_POSTFIX}") - LIST(APPEND SCI_BOOST_LIBRARY ${LIB_NAME}) -ENDFOREACH() +# -------------------------------------------------------------- +# Step: write project-config.jam (AFTER bootstrap) +# -------------------------------------------------------------- +if(BUILD_WITH_PYTHON) + ExternalProject_Add_Step(Boost_external write_project_config + COMMAND ${CMAKE_COMMAND} + -DOUTPUT_FILE=${SOURCE_DIR}/project-config.jam + -DVERSION=${SCI_PYTHON_VERSION_SHORT} + -DEXE=${SCI_PYTHON_EXE} + -DINCLUDE=${SCI_PYTHON_INCLUDE} + -DLIB_RELEASE=${SCI_PYTHON_LIBRARY_RELEASE} + -DLIB_DEBUG=${SCI_PYTHON_LIBRARY_DEBUG} + -P ${SUPERBUILD_DIR}/WriteProjectConfigJam.cmake + DEPENDEES bootstrap_b2 + INDEPENDENT 1 + COMMENT "Overwriting project-config.jam with Python toolset AFTER bootstrap" + ) +else() + ExternalProject_Add_Step(Boost_external write_project_config + COMMAND ${CMAKE_COMMAND} -E echo "Python disabled: project-config.jam not modified" + DEPENDEES bootstrap_b2 + INDEPENDENT 1 + ) +endif() -# Boost is special case - normally this should be handled in external library repo -CONFIGURE_FILE(${SUPERBUILD_DIR}/BoostConfig.cmake.in ${INSTALL_DIR}/BoostConfig.cmake @ONLY) -CONFIGURE_FILE(${SUPERBUILD_DIR}/UseBoost.cmake ${SCI_BOOST_USE_FILE} COPYONLY) +# -------------------------------------------------------------- +# Step: verify project-config.jam +# -------------------------------------------------------------- +ExternalProject_Add_Step(Boost_external verify_project_config + COMMAND ${CMAKE_COMMAND} -E echo "==== VERIFY project-config.jam AFTER bootstrap ====" + COMMAND ${CMAKE_COMMAND} -E cat ${SOURCE_DIR}/project-config.jam -SET(Boost_DIR ${INSTALL_DIR} CACHE PATH "") + DEPENDEES write_project_config + INDEPENDENT 1 + + COMMENT "Verifying final project-config.jam" +) + +# ------------------------------------------------------------------------------ +# Step: b2 headers +# ------------------------------------------------------------------------------ +ExternalProject_Add_Step(Boost_external stage_headers + COMMAND ${_B2_CMD} headers + WORKING_DIRECTORY ${SOURCE_DIR} + DEPENDEES write_project_config + COMMENT "Running b2 headers" +) + +# ------------------------------------------------------------------------------ +# Step: Install full headers +# ------------------------------------------------------------------------------ +ExternalProject_Add_Step(Boost_external install_full_headers + COMMAND ${CMAKE_COMMAND} -E make_directory ${INSTALL_DIR}/include + COMMAND ${CMAKE_COMMAND} -E remove_directory ${INSTALL_DIR}/include/boost + COMMAND ${CMAKE_COMMAND} -E copy_directory ${SOURCE_DIR}/boost ${INSTALL_DIR}/include/boost + WORKING_DIRECTORY ${SOURCE_DIR} + DEPENDEES stage_headers + DEPENDERS install + COMMENT "Installing full Boost headers" +) + +# ------------------------------------------------------------------------------ +# Step: Build Boost libraries +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------ +# Common Boost b2 build arguments (used for echo + real build) +# ------------------------------------------------------------------ +if(WIN32) + set(_BOOST_CXXFLAGS "") +else() + set(_BOOST_CXXFLAGS cxxflags=-fPIC) +endif() +# ------------------------------------------------------------------ +# Boost.Python debug ABI (Windows requires this for python313_d + 'y') +# ------------------------------------------------------------------ +if(WIN32 AND MSVC AND BUILD_WITH_PYTHON) + set(BOOST_PYTHON_DEBUGGING_FLAG python-debugging=on) +else() + set(BOOST_PYTHON_DEBUGGING_FLAG "") +endif() + +set(_BOOST_B2_ARGS + ${_BOOST_CXXFLAGS} + + --with-atomic + --with-chrono + --with-date_time + --with-filesystem + --with-program_options + --with-regex + --with-serialization + --with-thread + + ${BOOST_PYTHON_WITH_FLAG} + ${BOOST_PYTHON_DEBUGGING_FLAG} + + link=static + runtime-link=shared + variant=release,debug + threading=multi + stage +) -MESSAGE(STATUS "Boost_DIR: ${Boost_DIR}") +ExternalProject_Add_Step(Boost_external build_libs + COMMAND ${CMAKE_COMMAND} -E echo "=== B2 PYTHON FLAGS ===" + COMMAND ${CMAKE_COMMAND} -E echo "${BOOST_PYTHON_WITH_FLAG}" + COMMAND ${CMAKE_COMMAND} -E echo "${BOOST_PYTHON_EXE_FLAG}" + COMMAND ${CMAKE_COMMAND} -E echo "${BOOST_PYTHON_INC_FLAG}" + COMMAND ${CMAKE_COMMAND} -E echo "${BOOST_PYTHON_LIB_FLAG}" + COMMAND ${CMAKE_COMMAND} -E echo "SCI_PYTHON_EXE = ${SCI_PYTHON_EXE}" + COMMAND ${CMAKE_COMMAND} -E echo "SCI_PYTHON_INCLUDE = ${SCI_PYTHON_INCLUDE}" + COMMAND ${CMAKE_COMMAND} -E echo "SCI_PYTHON_LIBRARY_DIR = ${SCI_PYTHON_LIBRARY_DIR}" + + COMMAND ${CMAKE_COMMAND} -E echo "=== B2 FULL CMD ===" + COMMAND ${CMAKE_COMMAND} -E echo "${_B2_CMD} ${_BOOST_B2_ARGS}" + + COMMAND ${_B2_CMD} ${_BOOST_B2_ARGS} + + WORKING_DIRECTORY ${SOURCE_DIR} + DEPENDEES stage_headers + COMMENT "Building Boost static libraries (Debug + Release)" +) + +# ------------------------------------------------------------------------------ +# Export Boost library info +# ------------------------------------------------------------------------------ +SET(SCI_BOOST_INCLUDE ${INSTALL_DIR}/include) +SET(SCI_BOOST_LIBRARY_DIR ${SOURCE_DIR}/stage/lib) +#SET(SCI_BOOST_USE_FILE ${INSTALL_DIR}/UseBoost.cmake) + +SET(BOOST_PREFIX "boost_") +SET(THREAD_POSTFIX "") + +#SET(SCI_BOOST_LIBRARY) +#FOREACH(lib ${boost_Libraries}) +# IF(lib STREQUAL "python") +# # Python library is versioned: e.g., boost_python313 +# LIST(APPEND SCI_BOOST_LIBRARY "${BOOST_PREFIX}${lib}${SCI_PYTHON_VERSION_SHORT_WIN32}") +# ELSE() +# LIST(APPEND SCI_BOOST_LIBRARY "${BOOST_PREFIX}${lib}${THREAD_POSTFIX}") +# ENDIF() +#ENDFOREACH() + +CONFIGURE_FILE(${SUPERBUILD_DIR}/BoostConfig.cmake.in + ${INSTALL_DIR}/BoostConfig.cmake @ONLY) +#CONFIGURE_FILE(${SUPERBUILD_DIR}/UseBoost.cmake +# ${SCI_BOOST_USE_FILE} COPYONLY) + +SET(Boost_DIR ${INSTALL_DIR} CACHE PATH "") +MESSAGE(STATUS "Boost_DIR: ${Boost_DIR}") \ No newline at end of file diff --git a/Superbuild/GLEWConfig.cmake.in b/Superbuild/GLEWConfig.cmake.in new file mode 100644 index 0000000000..6ad878ba1d --- /dev/null +++ b/Superbuild/GLEWConfig.cmake.in @@ -0,0 +1,17 @@ +# Auto‑generated GLEWConfig.cmake for SCIRun superbuild +# This is a STATIC GLEW build + +if(NOT TARGET GLEW::GLEW) + add_library(GLEW::GLEW STATIC IMPORTED) + + set_target_properties(GLEW::GLEW PROPERTIES + IMPORTED_LOCATION "@GLEW_LIB@" # expands to Path/lib/glew.lib + INTERFACE_INCLUDE_DIRECTORIES "@GLEW_INC@" # expands to Path/include + INTERFACE_COMPILE_DEFINITIONS "GLEW_STATIC" # static GLEW requires this + ) +endif() + +set(GLEW_FOUND TRUE) +set(GLEW_INCLUDE_DIR "@GLEW_INC@") +set(GLEW_LIBRARY "@GLEW_LIB@") +set(GLEW_LIBRARIES "@GLEW_LIB@") \ No newline at end of file diff --git a/Superbuild/GlewWriteConfig.cmake b/Superbuild/GlewWriteConfig.cmake new file mode 100644 index 0000000000..8371814f70 --- /dev/null +++ b/Superbuild/GlewWriteConfig.cmake @@ -0,0 +1,5 @@ + +if(NOT EXISTS "${TEMPLATE}") + message(FATAL_ERROR "GLEW template not found at: ${TEMPLATE}") +endif() +configure_file("${TEMPLATE}" "${OUT_FILE}" @ONLY) diff --git a/Superbuild/LodePNGWrapperProject.cmake.in b/Superbuild/LodePNGWrapperProject.cmake.in new file mode 100644 index 0000000000..634bd9dc40 --- /dev/null +++ b/Superbuild/LodePNGWrapperProject.cmake.in @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.16) +project(LodePNGWrapper LANGUAGES C CXX) + +# Force proper Visual Studio config token, NOT "" +if(MSVC) + # Ensure generated VS project uses $(Configuration), avoiding literal "" anywhere. + set(CMAKE_CFG_INTDIR "$(Configuration)" CACHE STRING "VS config subdir token" FORCE) +endif() + +# Expect: -DLODEPNG_SRC= passed from the configure script +if(NOT DEFINED LODEPNG_SRC) + message(FATAL_ERROR "LODEPNG_SRC not set (path to folder with lodepng.cpp / lodepng.h).") +endif() + +# Sanitize and normalize (defensive) +string(REGEX REPLACE "^\"|\"$" "" LODEPNG_SRC "${LODEPNG_SRC}") +file(TO_CMAKE_PATH "${LODEPNG_SRC}" LODEPNG_SRC) + +message(STATUS "[LodePNGWrapper] LODEPNG_SRC='${LODEPNG_SRC}'") + +# Resolve lodepng.cpp in either layout: +# 1) /lodepng.cpp +# 2) /lodepng/lodepng.cpp +set(_lp_cpp "${LODEPNG_SRC}/lodepng.cpp") +if(NOT EXISTS "${_lp_cpp}") + set(_lp_cpp "${LODEPNG_SRC}/lodepng/lodepng.cpp") +endif() + +if(NOT EXISTS "${_lp_cpp}") + message(FATAL_ERROR "Could not find lodepng.cpp. Checked: + ${LODEPNG_SRC}/lodepng.cpp + ${LODEPNG_SRC}/lodepng/lodepng.cpp") +endif() +message(STATUS "[LodePNGWrapper] Using source file: ${_lp_cpp}") + +# Build a static library +add_library(lodepng STATIC "${_lp_cpp}") +set_target_properties(lodepng PROPERTIES + OUTPUT_NAME "$,lodepngd,lodepng>" + POSITION_INDEPENDENT_CODE ON +) + +# Include dirs: support both header layouts +target_include_directories(lodepng PUBLIC + "${LODEPNG_SRC}" + "${LODEPNG_SRC}/lodepng" +) + +# Install library and header(s) +include(GNUInstallDirs) + +install(TARGETS lodepng + EXPORT LodePNGTargets + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) + +# Install whichever header exists: +set(_lp_h1 "${LODEPNG_SRC}/lodepng.h") +set(_lp_h2 "${LODEPNG_SRC}/lodepng/lodepng.h") +if(EXISTS "${_lp_h1}") + install(FILES "${_lp_h1}" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/lodepng") +elseif(EXISTS "${_lp_h2}") + install(FILES "${_lp_h2}" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/lodepng") +else() + message(WARNING "Could not find lodepng.h under '${LODEPNG_SRC}'. No headers will be installed.") +endif() + +# Export a simple CMake package: LodePNG::lodepng +install(EXPORT LodePNGTargets + NAMESPACE LodePNG:: + FILE LodePNGTargets.cmake + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/LodePNG" +) + +include(CMakePackageConfigHelpers) +set(LodePNG_PACKAGE_VERSION "1.0") +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/LodePNGConfigVersion.cmake" + VERSION "${LodePNG_PACKAGE_VERSION}" + COMPATIBILITY SameMajorVersion +) + +# Minimal Config that includes the exported targets +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/LodePNGConfig.cmake" [=[ +# LodePNGConfig.cmake - generated by LodePNGWrapperProject +include("${CMAKE_CURRENT_LIST_DIR}/LodePNGTargets.cmake") +]=]) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/LodePNGConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/LodePNGConfigVersion.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/LodePNG" +) \ No newline at end of file diff --git a/Superbuild/LodePNGWrapper_configure.cmake b/Superbuild/LodePNGWrapper_configure.cmake new file mode 100644 index 0000000000..a602f377a8 --- /dev/null +++ b/Superbuild/LodePNGWrapper_configure.cmake @@ -0,0 +1,64 @@ +# LodePNGWrapper_configure.cmake +# Executed with `cmake -P` from ExternalProject_Add. +# Creates a minimal wrapper source dir and configures it with the chosen generator. + +# Required inputs +foreach(req IN ITEMS WRAPPER_SOURCE_DIR WRAPPER_BUILD_DIR WRAPPER_LIST_FILE LODEPNG_SRC CMAKE_INSTALL_PREFIX CMAKE_GENERATOR) + if(NOT DEFINED ${req}) + message(FATAL_ERROR "Missing required variable: ${req}") + endif() +endforeach() + +# Sanitize: strip accidental surrounding quotes; normalize to CMake-style paths. +foreach(var IN ITEMS WRAPPER_SOURCE_DIR WRAPPER_BUILD_DIR WRAPPER_LIST_FILE LODEPNG_SRC CMAKE_INSTALL_PREFIX) + if(DEFINED ${var}) + string(REGEX REPLACE "^\"|\"$" "" ${var} "${${var}}") + file(TO_CMAKE_PATH "${${var}}" ${var}) + endif() +endforeach() + +file(MAKE_DIRECTORY "${WRAPPER_SOURCE_DIR}") +file(MAKE_DIRECTORY "${WRAPPER_BUILD_DIR}") + +# Copy the wrapper template to a proper CMakeLists.txt +if(NOT EXISTS "${WRAPPER_LIST_FILE}") + message(FATAL_ERROR "WRAPPER_LIST_FILE does not exist: '${WRAPPER_LIST_FILE}'") +endif() +file(COPY "${WRAPPER_LIST_FILE}" DESTINATION "${WRAPPER_SOURCE_DIR}") +file(RENAME "${WRAPPER_SOURCE_DIR}/LodePNGWrapperProject.cmake.in" + "${WRAPPER_SOURCE_DIR}/CMakeLists.txt") + +# Prepare generator args for Visual Studio +set(_GEN_ARGS -G "${CMAKE_GENERATOR}") +if(DEFINED CMAKE_GENERATOR_PLATFORM AND NOT CMAKE_GENERATOR_PLATFORM STREQUAL "") + list(APPEND _GEN_ARGS -A "${CMAKE_GENERATOR_PLATFORM}") +endif() +if(DEFINED CMAKE_GENERATOR_TOOLSET AND NOT CMAKE_GENERATOR_TOOLSET STREQUAL "") + list(APPEND _GEN_ARGS -T "${CMAKE_GENERATOR_TOOLSET}") +endif() + +message(STATUS "[LodePNGWrapper_configure] LODEPNG_SRC='${LODEPNG_SRC}'") +message(STATUS "[LodePNGWrapper_configure] WRAPPER_SOURCE_DIR='${WRAPPER_SOURCE_DIR}'") +message(STATUS "[LodePNGWrapper_configure] WRAPPER_BUILD_DIR='${WRAPPER_BUILD_DIR}'") +message(STATUS "[LodePNGWrapper_configure] CMAKE_INSTALL_PREFIX='${CMAKE_INSTALL_PREFIX}'") +message(STATUS "[LodePNGWrapper_configure] CMAKE_GENERATOR='${CMAKE_GENERATOR}'") +if(DEFINED CMAKE_GENERATOR_PLATFORM) + message(STATUS "[LodePNGWrapper_configure] CMAKE_GENERATOR_PLATFORM='${CMAKE_GENERATOR_PLATFORM}'") +endif() +if(DEFINED CMAKE_GENERATOR_TOOLSET) + message(STATUS "[LodePNGWrapper_configure] CMAKE_GENERATOR_TOOLSET='${CMAKE_GENERATOR_TOOLSET}'") +endif() + +# Configure the wrapper project +execute_process( + COMMAND "${CMAKE_COMMAND}" ${_GEN_ARGS} + -S "${WRAPPER_SOURCE_DIR}" + -B "${WRAPPER_BUILD_DIR}" + -D LODEPNG_SRC:PATH="${LODEPNG_SRC}" + -D CMAKE_INSTALL_PREFIX:PATH="${CMAKE_INSTALL_PREFIX}" + -D BUILD_SHARED_LIBS:BOOL=OFF + RESULT_VARIABLE _res +) +if(NOT _res EQUAL 0) + message(FATAL_ERROR "Failed to configure LodePNG wrapper project (code=${_res}).") +endif() \ No newline at end of file diff --git a/Superbuild/LodePNGWrapper_install.cmake b/Superbuild/LodePNGWrapper_install.cmake new file mode 100644 index 0000000000..5fb7d67ef2 --- /dev/null +++ b/Superbuild/LodePNGWrapper_install.cmake @@ -0,0 +1,73 @@ +# Cross‑platform manual install script for LodePNG wrapper. + +foreach(req IN ITEMS WRAPPER_BUILD_DIR WRAPPER_SOURCE_DIR LODEPNG_INSTALL_DIR CONFIGURATION) + if(NOT DEFINED ${req}) + message(FATAL_ERROR "Missing required variable: ${req}") + endif() +endforeach() + +# Normalize paths +foreach(var IN ITEMS WRAPPER_BUILD_DIR WRAPPER_SOURCE_DIR LODEPNG_INSTALL_DIR CONFIGURATION) + string(REGEX REPLACE "^\"|\"$" "" ${var} "${${var}}") + file(TO_CMAKE_PATH "${${var}}" ${var}) +endforeach() + +# --------------------------------------------------------------- +# 1) HEADER +# --------------------------------------------------------------- +set(_hdr1 "${WRAPPER_SOURCE_DIR}/lodepng/lodepng.h") +set(_hdr2 "${WRAPPER_SOURCE_DIR}/lodepng.h") +set(_hdr "") + +if(EXISTS "${_hdr1}") + set(_hdr "${_hdr1}") +elseif(EXISTS "${_hdr2}") + set(_hdr "${_hdr2}") +endif() + +if(_hdr) + file(MAKE_DIRECTORY "${LODEPNG_INSTALL_DIR}/include/lodepng") + file(COPY "${_hdr}" DESTINATION "${LODEPNG_INSTALL_DIR}/include/lodepng") + message(STATUS "[LodePNGManualInstall] Copied header: ${_hdr}") +else() + message(WARNING "[LodePNGManualInstall] No lodepng.h found under: ${WRAPPER_SOURCE_DIR}") +endif() + +# --------------------------------------------------------------- +# 2) LIBRARY (Windows: .lib macOS/Linux: .a) +# --------------------------------------------------------------- + +if(WIN32) + # Multi‑config: Debug gets lodepngd.lib + if(CONFIGURATION STREQUAL "Debug") + set(_libname "lodepngd.lib") + else() + set(_libname "lodepng.lib") + endif() +else() + # Unix always produces liblodepng.a for static library + set(_libname "liblodepng.a") +endif() + +file(MAKE_DIRECTORY "${LODEPNG_INSTALL_DIR}/lib") + +# Try // +set(_primary "${WRAPPER_BUILD_DIR}/${CONFIGURATION}/${_libname}") + +# Try / (single-config generators) +set(_alt "${WRAPPER_BUILD_DIR}/${_libname}") + +if(EXISTS "${_primary}") + file(COPY "${_primary}" DESTINATION "${LODEPNG_INSTALL_DIR}/lib") + message(STATUS "[LodePNGManualInstall] Copied lib: ${_primary}") +elseif(EXISTS "${_alt}") + file(COPY "${_alt}" DESTINATION "${LODEPNG_INSTALL_DIR}/lib") + message(STATUS "[LodePNGManualInstall] Copied lib (alt): ${_alt}") +else() + message(FATAL_ERROR + "[LodePNGManualInstall] Built library not found.\n" + " Tried:\n" + " ${_primary}\n" + " ${_alt}" + ) +endif() \ No newline at end of file diff --git a/Superbuild/OsprayExternal.cmake b/Superbuild/OsprayExternal.cmake index 75ebb751c3..6bd1ad2703 100644 --- a/Superbuild/OsprayExternal.cmake +++ b/Superbuild/OsprayExternal.cmake @@ -28,7 +28,21 @@ SET_PROPERTY(DIRECTORY PROPERTY "EP_BASE" ${ep_base}) SET(ospray_GIT_TAG "origin/scirun-build-2.10") set(ospray_DEPENDENCIES) -LIST(APPEND ospray_DEPENDENCIES GLM_external) +set(ospray_DEPENDENCIES) +LIST(APPEND ospray_DEPENDENCIES + GLM_external + rkcommon_external + Embree_external +) + +ExternalProject_Get_Property(rkcommon_external INSTALL_DIR) +set(RKCOMMON_INSTALL_DIR ${INSTALL_DIR}) + +ExternalProject_Get_Property(TBB_external INSTALL_DIR) +set(TBB_INSTALL_DIR ${INSTALL_DIR}) + +ExternalProject_Get_Property(Embree_external INSTALL_DIR) +set(EMBREE_INSTALL_DIR ${INSTALL_DIR}) # If CMake ever allows overriding the checkout command or adding flags, # git checkout -q will silence message about detached head (harmless). @@ -36,16 +50,23 @@ ExternalProject_Add(Ospray_external DEPENDS ${ospray_DEPENDENCIES} GIT_REPOSITORY "https://github.com/CIBC-Internal/ospray.git" GIT_TAG ${ospray_GIT_TAG} - PATCH_COMMAND "" - INSTALL_DIR "" - INSTALL_COMMAND "" + + GIT_SUBMODULES "" + GIT_SUBMODULES_RECURSE OFF + CMAKE_CACHE_ARGS -DCMAKE_VERBOSE_MAKEFILE:BOOL=${CMAKE_VERBOSE_MAKEFILE} -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON -DENABLE_OSPRAY_SUPERBUILD:BOOL=ON - -Dglm_DIR:PATH=${GLM_DIR}/cmake/glm -DBUILD_ISA_AVX512:BOOL=OFF + -DCMAKE_POLICY_VERSION_MINIMUM:STRING=3.5 + -Drkcommon_DIR:PATH=${RKCOMMON_INSTALL_DIR}/lib/cmake/rkcommon-1.11.0 + -DTBB_ROOT:PATH=${TBB_INSTALL_DIR} + -Dembree_DIR:PATH=${EMBREE_INSTALL_DIR}/lib/cmake/embree-3.13.4 + -DEMBREE_VERSION_REQUIRED:STRING=3.13.0 + -DOSPRAY_ENABLE_ISPC:BOOL=OFF + -Dglm_DIR:PATH=${GLM_DIR}/cmake/glm ) ExternalProject_Get_Property(Ospray_external BINARY_DIR) diff --git a/Superbuild/PythonConfig.cmake.in b/Superbuild/PythonConfig.cmake.in index e720d28530..223811712f 100644 --- a/Superbuild/PythonConfig.cmake.in +++ b/Superbuild/PythonConfig.cmake.in @@ -1,23 +1,62 @@ +# ----------------------------------------------------------------------------- +# SCIRun Python configuration +# ----------------------------------------------------------------------------- set(SCI_PYTHON_VERSION_SHORT "@SCI_PYTHON_VERSION_SHORT@") set(SCI_PYTHON_ROOT "@SCI_PYTHON_ROOT_DIR@") set(SCI_PYTHON_INCLUDE "@SCI_PYTHON_INCLUDE@") + +# NOTE: +# This variable historically held "the" Python library. +# On Windows/MSVC this is unsafe for multi-config generators, +# so consumers should prefer SCI_PYTHON_LIBRARIES instead. set(SCI_PYTHON_LIBRARY "@SCI_PYTHON_LIBRARY@") + set(SCI_PYTHON_LIBRARY_DIR "@SCI_PYTHON_LIBRARY_DIR@") set(SCI_PYTHON_LINK_LIBRARY_DIRS "@SCI_PYTHON_LINK_LIBRARY_DIRS@") set(SCI_PYTHON_EXE "@SCI_PYTHON_EXE@") + +# ----------------------------------------------------------------------------- +# Windows / MSVC-specific Python settings +# ----------------------------------------------------------------------------- if(WIN32 AND MSVC) set(SCI_PYTHON_VERSION_SHORT_WIN32 "@SCI_PYTHON_VERSION_SHORT_WIN32@") set(SCI_PYTHON_DEBUG_EXE "@SCI_PYTHON_DEBUG_EXE@") + + # Explicit Python import libraries set(SCI_PYTHON_LIBRARY_RELEASE "@SCI_PYTHON_LIBRARY_RELEASE@") - set(SCI_PYTHON_LIBRARY_DEBUG "@SCI_PYTHON_LIBRARY_DEBUG@") - set(SCI_PYTHON_DLL_PATH "@SCI_PYTHON_DLL_PATH@") - set(SCI_PYTHON_DLL_DEBUG_PATH "@SCI_PYTHON_DLL_DEBUG_PATH@") + set(SCI_PYTHON_LIBRARY_DEBUG "@SCI_PYTHON_LIBRARY_DEBUG@") + + # Runtime DLL search paths + set(SCI_PYTHON_DLL_PATH "@SCI_PYTHON_DLL_PATH@") + set(SCI_PYTHON_DLL_DEBUG_PATH "@SCI_PYTHON_DLL_DEBUG_PATH@") + set(SCI_PYTHON_MODULE_PARENT_PATH "@SCI_PYTHON_MODULE_PARENT_PATH@") set(SCI_PYTHON_NAME "@SCI_PYTHON_NAME@") + + # --------------------------------------------------------------------------- + # NEW: Canonical, configuration-safe Python library variable + # + # This must be used by all targets that link against Python on Windows. + # --------------------------------------------------------------------------- + set(SCI_PYTHON_LIBRARIES + "$<$:${SCI_PYTHON_LIBRARY_DEBUG}>" + "$<$>:${SCI_PYTHON_LIBRARY_RELEASE}>" + ) + +else() + # --------------------------------------------------------------------------- + # Non-Windows platforms (Linux, macOS): + # Python has a single ABI, so a single library is correct. + # --------------------------------------------------------------------------- + set(SCI_PYTHON_LIBRARIES "${SCI_PYTHON_LIBRARY}") endif() + +# ----------------------------------------------------------------------------- +# macOS framework support +# ----------------------------------------------------------------------------- if(APPLE) set(SCI_PYTHON_FRAMEWORK "@SCI_PYTHON_FRAMEWORK@") set(SCI_PYTHON_FRAMEWORK_ARCHIVE "@SCI_PYTHON_FRAMEWORK_ARCHIVE@") @@ -26,5 +65,14 @@ else() set(SCI_PYTHON_64BIT_MODULE_LIBRARY_PATH "@SCI_PYTHON_64BIT_MODULE_LIBRARY_PATH@") endif() + +# ----------------------------------------------------------------------------- +# Python module search path +# ----------------------------------------------------------------------------- set(PYTHON_MODULE_SEARCH_PATH "@PYTHON_MODULE_SEARCH_PATH@") -set(SCI_PYTHON_USE_FILE "@SCI_PYTHON_USE_FILE@") + + +# ----------------------------------------------------------------------------- +# Entry point for consumers +# ----------------------------------------------------------------------------- +set(SCI_PYTHON_USE_FILE "@SCI_PYTHON_USE_FILE@") \ No newline at end of file diff --git a/Superbuild/PythonExternal.cmake b/Superbuild/PythonExternal.cmake index 21fd01b488..5a644ddc01 100644 --- a/Superbuild/PythonExternal.cmake +++ b/Superbuild/PythonExternal.cmake @@ -28,7 +28,7 @@ SET_PROPERTY(DIRECTORY PROPERTY "EP_BASE" ${ep_base}) -SET(DEFAULT_PYTHON_VERSION "3.11.11") +SET(DEFAULT_PYTHON_VERSION "3.13.1") set(USER_PYTHON_VERSION ${DEFAULT_PYTHON_VERSION} CACHE STRING "Branch name corresponding to Python version number") set_property(CACHE USER_PYTHON_VERSION PROPERTY STRINGS 3.10.16 3.11.11 3.12.8 3.13.1) @@ -67,6 +67,7 @@ IF(UNIX) SET(python_CONFIGURE_FLAGS "--prefix=" "--with-ensurepip=no" + "LDFLAGS=-Wl,-rpath,'$$ORIGIN/../lib'" ) IF(APPLE) # framework contains *.dylib @@ -110,8 +111,9 @@ ELSE() CONFIGURE_COMMAND PCbuild/build.bat BUILD_IN_SOURCE ON BUILD_COMMAND ${CMAKE_BUILD_TOOL} PCbuild/pcbuild.sln /nologo /property:Configuration=Release /property:Platform=${python_WIN32_ARCH} - INSTALL_COMMAND "${CMAKE_COMMAND}" -E copy_if_different - /PC/pyconfig.h + INSTALL_COMMAND "${CMAKE_COMMAND}" -E + copy_if_different + /PCbuild/${python_WIN32_64BIT_DIR}/pyconfig.h /Include/pyconfig.h ) # build both Release and Debug versions @@ -131,22 +133,24 @@ SET(SCI_PYTHON_MODULE_PARENT_PATH lib) IF(UNIX) SET(SCI_PYTHON_NAME python${SCI_PYTHON_VERSION_SHORT}) IF(APPLE) - # TODO: check Xcode IDE builds... - SET(SCI_PYTHON_FRAMEWORK ${INSTALL_DIR}/Python.framework) SET(SCI_PYTHON_ROOT_DIR ${SCI_PYTHON_FRAMEWORK}/Versions/${SCI_PYTHON_VERSION_SHORT}) SET(SCI_PYTHON_INCLUDE ${SCI_PYTHON_ROOT_DIR}/Headers) SET(SCI_PYTHON_LIBRARY_DIR ${SCI_PYTHON_ROOT_DIR}/lib) SET(SCI_PYTHON_LINK_LIBRARY_DIRS ${SCI_PYTHON_LIBRARY_DIR}) - SET(SCI_PYTHON_EXE ${SCI_PYTHON_ROOT_DIR}/bin/${SCI_PYTHON_NAME}) - SET(SCI_PYTHON_LIBRARY ${SCI_PYTHON_NAME}) - # required by interpreter interface + # Boost.Build requires python3, NOT python3.x inside frameworks + SET(SCI_PYTHON_EXE ${SCI_PYTHON_ROOT_DIR}/bin/python3) + + # Keep SCI_PYTHON_LIBRARY as the module name (python3.11 works here) + SET(SCI_PYTHON_LIBRARY python${SCI_PYTHON_VERSION_SHORT}) + IF(BUILD_HEADLESS) - SET(PYTHON_MODULE_SEARCH_PATH Python.framework/Versions/${SCI_PYTHON_VERSION_SHORT}/${SCI_PYTHON_MODULE_PARENT_PATH}/${SCI_PYTHON_NAME} CACHE INTERNAL "Python modules." FORCE) + SET(PYTHON_MODULE_SEARCH_PATH Python.framework/Versions/${SCI_PYTHON_VERSION_SHORT}/${SCI_PYTHON_MODULE_PARENT_PATH}/${SCI_PYTHON_LIBRARY} CACHE INTERNAL "Python modules." FORCE) ELSE() - SET(PYTHON_MODULE_SEARCH_PATH Frameworks/Python.framework/Versions/${SCI_PYTHON_VERSION_SHORT}/${SCI_PYTHON_MODULE_PARENT_PATH}/${SCI_PYTHON_NAME} CACHE INTERNAL "Python modules." FORCE) + SET(PYTHON_MODULE_SEARCH_PATH Frameworks/Python.framework/Versions/${SCI_PYTHON_VERSION_SHORT}/${SCI_PYTHON_MODULE_PARENT_PATH}/${SCI_PYTHON_LIBRARY} CACHE INTERNAL "Python modules." FORCE) ENDIF() + SET(SCI_PYTHON_FRAMEWORK_ARCHIVE ${INSTALL_DIR}/${python_FRAMEWORK_ARCHIVE}) ELSE() SET(SCI_PYTHON_ROOT_DIR ${INSTALL_DIR}) diff --git a/Superbuild/QwtExternal.cmake b/Superbuild/QwtExternal.cmake index 9d767c41e5..93a27d2323 100644 --- a/Superbuild/QwtExternal.cmake +++ b/Superbuild/QwtExternal.cmake @@ -1,4 +1,4 @@ -# For more information, please see: http://software.sci.utah.edu +# For more information, please see: http://software.sci.utah.edu # # The MIT License # @@ -24,41 +24,123 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -SET_PROPERTY(DIRECTORY PROPERTY "EP_BASE" ${ep_base}) +# QwtExternal.cmake — build Qwt via the internal CMake wrapper (preferred over qmake) -SET(QWT_CACHE_ARGS - "-DCMAKE_VERBOSE_MAKEFILE:BOOL=${CMAKE_VERBOSE_MAKEFILE}" - "-DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}" - "-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON" - ) +# ---------------------------- +# Single place to pin wrapper tag +# ---------------------------- +# Bump this tag when you cut a new wrapper release (e.g., v0.1.1, v0.2.0) +set(qwt_WRAPPER_GIT_TAG "v0.1.1") -LIST(APPEND QWT_CACHE_ARGS - "-DQt_PATH:PATH=${Qt_PATH}" - "-DQt5_PATH:PATH=${Qt_PATH}" - "-DQt${QT_VERSION_MAJOR}Core_DIR:PATH=${Qt${QT_VERSION_MAJOR}Core_DIR}" - "-DQt${QT_VERSION_MAJOR}CoreTools_DIR:PATH=${Qt${QT_VERSION_MAJOR}CoreTools_DIR}" - "-DQt${QT_VERSION_MAJOR}Gui_DIR:PATH=${Qt${QT_VERSION_MAJOR}Gui_DIR}" - "-DQt${QT_VERSION_MAJOR}GuiTools_DIR:PATH=${Qt${QT_VERSION_MAJOR}GuiTools_DIR}" -) +# Keep the legacy variable for visibility; it's the upstream Qwt tag +# that your wrapper fetches internally (informational only here). +set(qwt_GIT_TAG "v6.3.0") + +# Ensure ExternalProject directories are rooted under the superbuild 'ep_base' +set_property(DIRECTORY PROPERTY EP_BASE "${ep_base}") -if (${QT_VERSION_MAJOR} STREQUAL "5") - SET(qwt_GIT_TAG "origin/qt5-static-6.1.5") +# ---------------------------- +# Superbuild directories +# ---------------------------- +set(_qwt_src "${CMAKE_BINARY_DIR}/Externals/Source/Qwt_external") +set(_qwt_bin "${CMAKE_BINARY_DIR}/Externals/Build/Qwt_external") +set(_qwt_inst "${CMAKE_BINARY_DIR}/Externals/Install/Qwt_external") + +# Qwt install layout +set(QWT_INSTALL_DIR "${_qwt_inst}") +set(QWT_INCLUDE "${_qwt_inst}/include") +set(QWT_LIBRARY_DIR "${_qwt_inst}/lib") + +# ---------------------------- +# Qt discovery hints (optional) +# ---------------------------- +set(_qwt_extra_cmake_args "") +if(DEFINED Qt6_DIR) + list(APPEND _qwt_extra_cmake_args "-DQt6_DIR=${Qt6_DIR}") +endif() +if(DEFINED Qt5_DIR) + list(APPEND _qwt_extra_cmake_args "-DQt5_DIR=${Qt5_DIR}") +endif() + +include(ExternalProject) + +# Detect whether we're using a multi-config generator (e.g., Visual Studio) +get_property(_is_multi GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(_is_multi) + # Forward the native config placeholder (e.g., $(Configuration)) + set(_EP_CFG "${CMAKE_CFG_INTDIR}") else() - SET(qwt_GIT_TAG "origin/qt6-static-6.2.0") + # Single-config (e.g., Ninja/Unix Makefiles) → use CMAKE_BUILD_TYPE (may be empty = default) + if(CMAKE_BUILD_TYPE) + set(_EP_CFG "${CMAKE_BUILD_TYPE}") + else() + set(_EP_CFG ".") # no named config; the placeholder is '.' for single-config + endif() endif() -# If CMake ever allows overriding the checkout command or adding flags, -# git checkout -q will silence message about detached head (harmless). ExternalProject_Add(Qwt_external - GIT_REPOSITORY "https://github.com/CIBC-Internal/Qwt.git" - GIT_TAG ${qwt_GIT_TAG} - PATCH_COMMAND "" - INSTALL_DIR "" - INSTALL_COMMAND "" - CMAKE_CACHE_ARGS ${QWT_CACHE_ARGS} + GIT_REPOSITORY "https://github.com/CIBC-Internal/Qwt-cmake-wrapper.git" + GIT_TAG ${qwt_WRAPPER_GIT_TAG} + + # Make cloning robust for pinned tags (turn off shallow during stabilization) + GIT_SHALLOW 0 # <-- changed from 1 to 0 + GIT_PROGRESS 1 + GIT_SUBMODULES "" # explicit: no submodules + + SOURCE_DIR ${_qwt_src} + BINARY_DIR ${_qwt_bin} + INSTALL_DIR ${_qwt_inst} + + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${_qwt_inst} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + ${_qwt_extra_cmake_args} + + # For pinned tags, skip update step entirely to avoid running git in a non-git tree + UPDATE_COMMAND "" # <-- added: prevents Qwt_external-gitupdate.cmake + + BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${_EP_CFG} + INSTALL_COMMAND ${CMAKE_COMMAND} --install . --config ${_EP_CFG} + + LOG_DOWNLOAD 1 + LOG_UPDATE 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 + LOG_INSTALL 1 ) -ExternalProject_Get_Property(Qwt_external BINARY_DIR) -SET(QWT_DIR ${BINARY_DIR} CACHE PATH "") +# ---------------------------- +# Exported Qwt library variables (config-aware) +# ---------------------------- + +if(WIN32) + # Qwt wrapper uses qwtd.lib for Debug, qwt.lib for Release + set(QWT_LIBRARY_DEBUG "${QWT_LIBRARY_DIR}/qwtd.lib") + set(QWT_LIBRARY_RELEASE "${QWT_LIBRARY_DIR}/qwt.lib") + + # Generator-expression aware library selection + set(QWT_LIBRARY + $<$:${QWT_LIBRARY_DEBUG}> + $<$:${QWT_LIBRARY_RELEASE}> + $<$:${QWT_LIBRARY_RELEASE}> + $<$:${QWT_LIBRARY_RELEASE}> + ) + +elseif(APPLE) + set(QWT_LIBRARY "${QWT_LIBRARY_DIR}/libqwt.a") +else() + set(QWT_LIBRARY "${QWT_LIBRARY_DIR}/libqwt.a") +endif() + +# Export to SCIRun +# Export to parent (SCIRun superbuild) +set(QWT_INSTALL_DIR "${QWT_INSTALL_DIR}" PARENT_SCOPE) +set(QWT_INCLUDE "${QWT_INCLUDE}" PARENT_SCOPE) +set(QWT_LIBRARY_DIR "${QWT_LIBRARY_DIR}" PARENT_SCOPE) +set(QWT_LIBRARY "${QWT_LIBRARY}" PARENT_SCOPE) -MESSAGE(STATUS "QWT_DIR: ${QWT_DIR}") +message(STATUS "[Qwt_external] WRAPPER_TAG=${qwt_WRAPPER_GIT_TAG} ; QWT_TAG=${qwt_GIT_TAG}") +message(STATUS "[Qwt_external] INSTALL_DIR=${QWT_INSTALL_DIR}") +message(STATUS "[Qwt_external] INCLUDE=${QWT_INCLUDE}") +message(STATUS "[Qwt_external] LIBDIR=${QWT_LIBRARY_DIR}") diff --git a/Superbuild/Superbuild.cmake b/Superbuild/Superbuild.cmake index 70d78bd55f..71e560d0b1 100644 --- a/Superbuild/Superbuild.cmake +++ b/Superbuild/Superbuild.cmake @@ -29,6 +29,32 @@ SET(compress_type "GIT" CACHE INTERNAL "") SET(ep_base "${CMAKE_BINARY_DIR}/Externals" CACHE INTERNAL "") +########################################### +# Force superbuild Python, prevent system Python binding +########################################### + +if(BUILD_WITH_PYTHON) + + # This is where PythonExternal.cmake will install Python + set(_SB_PYTHON_PREFIX "${ep_base}/Python_external") + + # Prevent CMake from picking up /usr/bin/python3.x + set(Python3_FIND_SYSTEM_ONLY OFF CACHE BOOL "" FORCE) + set(Python3_FIND_REGISTRY NEVER CACHE STRING "" FORCE) + set(Python3_FIND_UNVERSIONED_NAMES NEVER CACHE STRING "" FORCE) + set(Python3_FIND_STRATEGY LOCATION CACHE STRING "" FORCE) + + # Predeclare Python location (even before it exists) + set(Python3_ROOT_DIR "${_SB_PYTHON_PREFIX}" CACHE PATH "" FORCE) + + # These stop FindPython / FindPython3 from falling back + set(Python_ROOT_DIR "${_SB_PYTHON_PREFIX}" CACHE PATH "" FORCE) + + # Do NOT set Python3_EXECUTABLE yet — it doesn't exist during first configure + # We only block system discovery here. + +endif() + ########################################### # Set default CMAKE_BUILD_TYPE # if empty for Unix Makefile builds @@ -106,27 +132,90 @@ list(GET SCIRUN_QT_MIN_VERSION_LIST 2 QT_VERSION_PATCH) IF(NOT BUILD_HEADLESS) - SET(Qt_PATH "" CACHE PATH "Path to directory where Qt is installed. Directory should contain lib and bin subdirectories.") - - IF(IS_DIRECTORY ${Qt_PATH}) - if (${QT_VERSION_MAJOR} STREQUAL "6") - FIND_PACKAGE(Qt${QT_VERSION_MAJOR} ${SCIRUN_QT_MIN_VERSION} COMPONENTS DBus DBusTools Core Gui Widgets Network OpenGL Concurrent PrintSupport Svg CoreTools GuiTools WidgetsTools OpenGLWidgets REQUIRED HINTS ${Qt_PATH}) + SET(Qt_PATH "" CACHE PATH + "Path to directory where Qt is installed. Directory should contain lib and bin subdirectories.") + + # ------------------------------------------------------------ + # Platform-specific Qt auto-detection + # ------------------------------------------------------------ + #if(APPLE OR WIN32 OR (UNIX AND NOT APPLE)) + # + # if(NOT Qt_PATH OR NOT IS_DIRECTORY "${Qt_PATH}") + # + # if(APPLE) + # set(_qt_default "/Users/basisunus/Qt/6.10.2/macos") + # elseif(WIN32) + # set(_qt_default "C:/Qt/6.10.1/msvc2022_64") + # elseif(UNIX) + # set(_qt_default "$ENV{HOME}/Qt/6.11.0/gcc_64") + # endif() + # + # if(IS_DIRECTORY "${_qt_default}") + # message(STATUS + # "Qt_PATH not set or invalid — using auto-detected Qt: ${_qt_default}" + # ) + # + # set(Qt_PATH "${_qt_default}" CACHE PATH "Qt install prefix" FORCE) + # + # # Auto-detect Qt version from path + # get_filename_component(_qt_parent "${_qt_default}" DIRECTORY) + # get_filename_component(_qt_version "${_qt_parent}" NAME) + # + # set(SCIRUN_QT_MIN_VERSION + # "${_qt_version}" + # CACHE STRING "Qt version" FORCE) + # + # string(REPLACE "." ";" SCIRUN_QT_MIN_VERSION_LIST + # ${SCIRUN_QT_MIN_VERSION}) + # + # list(GET SCIRUN_QT_MIN_VERSION_LIST 0 QT_VERSION_MAJOR) + # list(GET SCIRUN_QT_MIN_VERSION_LIST 1 QT_VERSION_MINOR) + # list(GET SCIRUN_QT_MIN_VERSION_LIST 2 QT_VERSION_PATCH) + # + # endif() + # else() + # message(STATUS "Using user-provided Qt_PATH: ${Qt_PATH}") + # endif() + # + #endif() + + # ------------------------------------------------------------ + # Qt package discovery + # ------------------------------------------------------------ + IF(IS_DIRECTORY "${Qt_PATH}") + if (QT_VERSION_MAJOR STREQUAL "6") + FIND_PACKAGE(Qt${QT_VERSION_MAJOR} ${SCIRUN_QT_MIN_VERSION} + COMPONENTS + DBus DBusTools + Core Gui Widgets Network OpenGL Concurrent PrintSupport Svg + CoreTools GuiTools WidgetsTools OpenGLWidgets + REQUIRED + HINTS ${Qt_PATH}) else() - FIND_PACKAGE(Qt${QT_VERSION_MAJOR} ${SCIRUN_QT_MIN_VERSION} COMPONENTS Core Gui Widgets Network OpenGL Concurrent PrintSupport Svg REQUIRED HINTS ${Qt_PATH}) + FIND_PACKAGE(Qt${QT_VERSION_MAJOR} ${SCIRUN_QT_MIN_VERSION} + COMPONENTS + Core Gui Widgets Network OpenGL Concurrent PrintSupport Svg + REQUIRED + HINTS ${Qt_PATH}) endif() ELSE() - MESSAGE(SEND_ERROR "Set Qt_PATH to directory where Qt is installed (containing lib and bin subdirectories) or set BUILD_HEADLESS to ON.") + MESSAGE(SEND_ERROR + "Set Qt_PATH to the Qt install prefix (with bin/ and lib/) or enable BUILD_HEADLESS.") ENDIF() + # ------------------------------------------------------------ + # macOS-only settings + # ------------------------------------------------------------ IF(APPLE) - SET(MACDEPLOYQT_OUTPUT_LEVEL 0 CACHE STRING "Set macdeployqt output level (0-3)") + SET(MACDEPLOYQT_OUTPUT_LEVEL 0 CACHE STRING + "Set macdeployqt output level (0–3)") MARK_AS_ADVANCED(MACDEPLOYQT_OUTPUT_LEVEL) ENDIF() + ELSE() ADD_DEFINITIONS(-DBUILD_HEADLESS) ENDIF() - ########################################### # Configure Doxygen documentation OPTION(BUILD_DOCUMENTATION "Build documentation" OFF) @@ -192,7 +281,10 @@ IF(WITH_TETGEN) ENDIF() IF(WITH_OSPRAY) - ADD_EXTERNAL( ${SUPERBUILD_DIR}/OsprayExternal.cmake Ospray_external ) + #INCLUDE(${SUPERBUILD_DIR}/TBBExternal.cmake) + #INCLUDE(${SUPERBUILD_DIR}/RKCommonExternal.cmake) + #INCLUDE(${SUPERBUILD_DIR}/EmbreeExternal.cmake) + ADD_EXTERNAL(${SUPERBUILD_DIR}/OsprayExternal.cmake Ospray_external) ENDIF() IF(NOT BUILD_HEADLESS) @@ -283,7 +375,10 @@ IF(NOT BUILD_HEADLESS) "-DQt${QT_VERSION_MAJOR}Widgets_DIR:PATH=${Qt${QT_VERSION_MAJOR}Widgets_DIR}" "-DQt${QT_VERSION_MAJOR}Concurrent_DIR:PATH=${Qt${QT_VERSION_MAJOR}Concurrent_DIR}" "-DMACDEPLOYQT_OUTPUT_LEVEL:STRING=${MACDEPLOYQT_OUTPUT_LEVEL}" - "-DQWT_DIR:PATH=${QWT_DIR}" + "-DQWT_INCLUDE:PATH=${QWT_INCLUDE}" + "-DQWT_LIBRARY_DIR:PATH=${QWT_LIBRARY_DIR}" + "-DQWT_LIBRARY:STRING=${QWT_LIBRARY}" + "-DQWT_INSTALL_DIR:PATH=${QWT_INSTALL_DIR}" ) ENDIF() diff --git a/Superbuild/TetgenExternal.cmake b/Superbuild/TetgenExternal.cmake index 3d704281a2..f40cfd33f9 100644 --- a/Superbuild/TetgenExternal.cmake +++ b/Superbuild/TetgenExternal.cmake @@ -24,30 +24,76 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -SET_PROPERTY(DIRECTORY PROPERTY "EP_BASE" ${ep_base}) +# ------------------------------------------------------- +# TetgenExternal.cmake - unified Windows/macOS/Linux version +# ------------------------------------------------------- + +set_property(DIRECTORY PROPERTY EP_BASE "${ep_base}") + +# Common CMake args for external TetGen +set(_cmake_args + -DCMAKE_VERBOSE_MAKEFILE=${CMAKE_VERBOSE_MAKEFILE} + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + + -DCMAKE_INSTALL_PREFIX= + -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=/lib + -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=/lib + -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=/bin +) + +# Handle single‑config (Ninja/Makefiles) +if(NOT CMAKE_CONFIGURATION_TYPES AND CMAKE_BUILD_TYPE) + list(APPEND _cmake_args -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}) +endif() + +# Superbuild paths +set(_tetgen_src "${CMAKE_BINARY_DIR}/Externals/Source/Tetgen_external") +set(_tetgen_bin "${CMAKE_BINARY_DIR}/Externals/Build/Tetgen_external") +set(_tetgen_inst "${CMAKE_BINARY_DIR}/Externals/Install/Tetgen_external") + ExternalProject_Add(Tetgen_external - URL "https://github.com/CIBC-Internal/SCIRunTestData/releases/download/test/tetgen1.5.1-beta1.tar.gz" - PATCH_COMMAND "" - INSTALL_COMMAND "" - CMAKE_CACHE_ARGS - -DCMAKE_VERBOSE_MAKEFILE:BOOL=${CMAKE_VERBOSE_MAKEFILE} - -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} - -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON - #-DTETLIBRARY + GIT_REPOSITORY https://github.com/CIBC-Internal/TetGen.git + GIT_TAG v1.6.1 + UPDATE_DISCONNECTED 1 + + SOURCE_DIR ${_tetgen_src} + BINARY_DIR ${_tetgen_bin} + + CMAKE_GENERATOR "${CMAKE_GENERATOR}" + CMAKE_GENERATOR_PLATFORM "${CMAKE_GENERATOR_PLATFORM}" + CMAKE_GENERATOR_TOOLSET "${CMAKE_GENERATOR_TOOLSET}" + + CMAKE_ARGS ${_cmake_args} + INSTALL_COMMAND "" # because we set output dirs directly ) +# Obtain external project properties ExternalProject_Get_Property(Tetgen_external SOURCE_DIR) ExternalProject_Get_Property(Tetgen_external BINARY_DIR) ExternalProject_Get_Property(Tetgen_external INSTALL_DIR) -SET(TETGEN_INCLUDE ${SOURCE_DIR}) -SET(TETGEN_LIBRARY_DIR ${BINARY_DIR}) -SET(TETGEN_USE_FILE ${INSTALL_DIR}/UseTetgen.cmake) -# see Tetgen CMakeLists.txt file -SET(TETGEN_LIBRARY "tet") -SET(Tetgen_DIR ${INSTALL_DIR} CACHE PATH "") - -# Boost is special case - normally this should be handled in external library repo -CONFIGURE_FILE(${SUPERBUILD_DIR}/TetgenConfig.cmake.in ${INSTALL_DIR}/TetgenConfig.cmake @ONLY) -CONFIGURE_FILE(${SUPERBUILD_DIR}/UseTetgen.cmake ${TETGEN_USE_FILE} COPYONLY) - -MESSAGE(STATUS "Tetgen_DIR: ${Tetgen_DIR}") + +# ------------------------------------------------------- +# Unified exported variables (working on Windows + macOS) +# ------------------------------------------------------- + +set(TETGEN_INSTALL_DIR ${_tetgen_inst}) +set(TETGEN_INCLUDE ${SOURCE_DIR}) +set(TETGEN_LIBRARY_DIR ${TETGEN_INSTALL_DIR}/lib) +set(TETGEN_LIBRARY "tet") # matches TetGen’s CMakeLists + +# Create config + use scripts for downstream projects +file(MAKE_DIRECTORY "${TETGEN_INSTALL_DIR}") + +set(TETGEN_USE_FILE "${TETGEN_INSTALL_DIR}/UseTetgen.cmake") + +configure_file(${SUPERBUILD_DIR}/TetgenConfig.cmake.in + ${TETGEN_INSTALL_DIR}/TetgenConfig.cmake @ONLY) + +configure_file(${SUPERBUILD_DIR}/UseTetgen.cmake + ${TETGEN_USE_FILE} COPYONLY) + +# register for find_package(Tetgen CONFIG) +set(Tetgen_DIR ${TETGEN_INSTALL_DIR} CACHE PATH "") + +message(STATUS "[Tetgen_external] INSTALL_DIR=${TETGEN_INSTALL_DIR}") +message(STATUS "[Tetgen_external] USE_FILE=${TETGEN_USE_FILE}") \ No newline at end of file diff --git a/Superbuild/TnyConfig.cmake.in b/Superbuild/TnyConfig.cmake.in new file mode 100644 index 0000000000..ff07ea90fd --- /dev/null +++ b/Superbuild/TnyConfig.cmake.in @@ -0,0 +1,16 @@ +# Auto-generated by SCIRun superbuild +# Provides an imported target Tny::tny (STATIC) + +if(NOT TARGET Tny::tny) + add_library(Tny::tny STATIC IMPORTED) + set_target_properties(Tny::tny PROPERTIES + IMPORTED_LOCATION "@TNY_LIB@" # absolute path to tny(.lib/.a) + INTERFACE_INCLUDE_DIRECTORIES "@TNY_INC@" # absolute path to include root (contains tny/tny.h) + ) +endif() + +# Legacy variables for consumers that don't use targets +set(Tny_FOUND TRUE) +set(TNY_INCLUDE_DIR "@TNY_INC@") +set(TNY_LIBRARY "@TNY_LIB@") +set(TNY_LIBRARIES "@TNY_LIB@") \ No newline at end of file diff --git a/Superbuild/TnyInstall.cmake b/Superbuild/TnyInstall.cmake new file mode 100644 index 0000000000..28b7eb49a7 --- /dev/null +++ b/Superbuild/TnyInstall.cmake @@ -0,0 +1,12 @@ +# install-tny.cmake +# Usage: cmake -Dsrc=... -Ddst=... -P install-tny.cmake + +if(NOT DEFINED src OR NOT DEFINED dst) + message(FATAL_ERROR "install-tny.cmake requires -Dsrc and -Ddst") +endif() + +# Ensure the destination exists +file(MAKE_DIRECTORY "${dst}") + +# Copy the *contents* of src into dst (note the trailing slash) +file(COPY "${src}/" DESTINATION "${dst}") \ No newline at end of file diff --git a/Superbuild/WriteProjectConfigJam.cmake b/Superbuild/WriteProjectConfigJam.cmake new file mode 100644 index 0000000000..a962251a0f --- /dev/null +++ b/Superbuild/WriteProjectConfigJam.cmake @@ -0,0 +1,9 @@ +if(DEFINED EXE AND EXE AND DEFINED VERSION AND VERSION) + file(WRITE "${OUTPUT_FILE}" +"using python : ${VERSION} : ${EXE} : ${INCLUDE} : ${LIBDIR} ; +") +else() + # Explicitly write an empty or comment-only config + file(WRITE "${OUTPUT_FILE}" +"# Python disabled — no python toolset configured\n") +endif() \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ecee63aada..b7307ec364 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -228,8 +228,68 @@ IF(BUILD_WITH_PYTHON) INCLUDE(${SCI_PYTHON_USE_FILE}) ENDIF() -CONFIG_STANDARD_EXTERNAL( Boost BoostConfig.cmake ${Boost_DIR} ) -INCLUDE(${SCI_BOOST_USE_FILE}) +# Disable Boost MSVC auto-linking globally +# (prevents Boost headers from injecting -gd / -mt / etc. library names) +add_compile_definitions( + BOOST_ALL_NO_LIB + BOOST_FILESYSTEM_NO_LIB + BOOST_SYSTEM_NO_LIB +) +set(_scirun_boost_components + atomic + chrono + filesystem + program_options + regex + serialization + thread +) + +if(BUILD_WITH_PYTHON) + list(APPEND _scirun_boost_components + python${SCI_PYTHON_VERSION_SHORT_WIN32}) +endif() + +find_package(Boost CONFIG REQUIRED COMPONENTS + ${_scirun_boost_components} +) + +# --------------------------------------------------------------------------- +# Ensure BOOST_ALL_NO_LIB propagates to EVERY consumer of Boost +# by attaching it to the imported Boost targets as INTERFACE +# --------------------------------------------------------------------------- +foreach(_boost_comp ${_scirun_boost_components}) + if(TARGET Boost::${_boost_comp}) + target_compile_definitions(Boost::${_boost_comp} + INTERFACE BOOST_ALL_NO_LIB + ) + endif() +endforeach() + +if(TARGET Boost::filesystem) + target_compile_definitions(Boost::filesystem + INTERFACE + BOOST_FILESYSTEM_NO_LIB + BOOST_SYSTEM_NO_LIB + BOOST_FILESYSTEM_STATIC_LINK + ) +endif() + +# --------------------------------------------------------------------------- +# Diagnostics +# --------------------------------------------------------------------------- +get_property(_allTargets GLOBAL PROPERTY TARGETS) + +message(STATUS "==== Boost-related targets ====") +foreach(t ${_allTargets}) + if(t MATCHES "^Boost::") + message(STATUS " ${t}") + endif() +endforeach() + +message(STATUS "Boost_FOUND = ${Boost_FOUND}") +message(STATUS "Boost_INCLUDE_DIRS = ${Boost_INCLUDE_DIRS}") +message(STATUS "Boost_LIBRARIES = ${Boost_LIBRARIES}") CONFIG_STANDARD_EXTERNAL( Tny TnyConfig.cmake ${TNY_DIR} ) INCLUDE(${TNY_USE_FILE}) @@ -253,8 +313,10 @@ IF(WIN32) ENDIF() IF(NOT BUILD_HEADLESS) - CONFIG_STANDARD_EXTERNAL( Qwt QwtConfig.cmake ${QWT_DIR} ) - INCLUDE(${QWT_USE_FILE}) +# QWT variables come directly from superbuild + set(SCI_QWT_INCLUDE "${QWT_INCLUDE}") + set(SCI_QWT_LIBRARY_DIR "${QWT_LIBRARY_DIR}") + set(SCI_QWT_LIBRARY "${QWT_LIBRARY}") ENDIF() ######################################################################## @@ -511,7 +573,7 @@ INCLUDE_DIRECTORIES( ${SCI_CLEAVER2_INCLUDE} ) -INCLUDE_DIRECTORIES(SYSTEM ${SCI_BOOST_INCLUDE}) +#INCLUDE_DIRECTORIES(SYSTEM ${SCI_BOOST_INCLUDE}) ADD_DEFINITIONS(-DQT_NO_KEYWORDS) diff --git a/src/Core/Algorithms/BrainStimulator/CMakeLists.txt b/src/Core/Algorithms/BrainStimulator/CMakeLists.txt index 1724ecde6c..227e34cf58 100644 --- a/src/Core/Algorithms/BrainStimulator/CMakeLists.txt +++ b/src/Core/Algorithms/BrainStimulator/CMakeLists.txt @@ -60,7 +60,6 @@ TARGET_LINK_LIBRARIES(Algorithms_BrainStimulator Core_Algorithms_Legacy_Fields # Core_Datatypes_Legacy_BrainStimulator Algorithms_Base - ${SCI_BOOST_LIBRARY} ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/Algorithms/DataIO/CMakeLists.txt b/src/Core/Algorithms/DataIO/CMakeLists.txt index 2ed1adcd93..e4e83b2c57 100644 --- a/src/Core/Algorithms/DataIO/CMakeLists.txt +++ b/src/Core/Algorithms/DataIO/CMakeLists.txt @@ -50,7 +50,6 @@ TARGET_LINK_LIBRARIES(Algorithms_DataIO Core_Datatypes_Mesh Algorithms_Base Core_Datatypes_Legacy_Field - ${SCI_BOOST_LIBRARY} ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/Algorithms/Describe/CMakeLists.txt b/src/Core/Algorithms/Describe/CMakeLists.txt index 93598799cc..25f0133130 100644 --- a/src/Core/Algorithms/Describe/CMakeLists.txt +++ b/src/Core/Algorithms/Describe/CMakeLists.txt @@ -45,7 +45,6 @@ TARGET_LINK_LIBRARIES(Algorithms_Describe Core_Datatypes_Legacy_Field Algorithms_Field Algorithms_Math - ${SCI_BOOST_LIBRARY} ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/Algorithms/Factory/CMakeLists.txt b/src/Core/Algorithms/Factory/CMakeLists.txt index 2412f10d31..02d5031675 100644 --- a/src/Core/Algorithms/Factory/CMakeLists.txt +++ b/src/Core/Algorithms/Factory/CMakeLists.txt @@ -41,8 +41,7 @@ IF(GENERATE_MODULE_FACTORY_CODE) MESSAGE(STATUS "Setting up algorithm factory generator") ADD_EXECUTABLE(MakeAlgorithmFactory Generator/MakeAlgorithmFactory.h Generator/MakeAlgorithmFactory.cc) TARGET_LINK_LIBRARIES(MakeAlgorithmFactory - Modules_Factory_Generator - ${SCI_BOOST_LIBRARY}) + Modules_Factory_Generator) SET_PROPERTY(TARGET MakeAlgorithmFactory PROPERTY FOLDER "Modules") # add the command to generate the source code @@ -79,7 +78,6 @@ TARGET_LINK_LIBRARIES(Algorithms_Factory Core_Algorithms_Visualization Core_Algorithms_Legacy_FiniteElements Core_Algorithms_Legacy_Converter - ${SCI_BOOST_LIBRARY} ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/Algorithms/Factory/Tests/CMakeLists.txt b/src/Core/Algorithms/Factory/Tests/CMakeLists.txt index 45a50e5097..c455da45a9 100644 --- a/src/Core/Algorithms/Factory/Tests/CMakeLists.txt +++ b/src/Core/Algorithms/Factory/Tests/CMakeLists.txt @@ -48,5 +48,4 @@ TARGET_LINK_LIBRARIES(Algorithm_Layer_Test Engine_Network gtest_main gtest - ${SCI_BOOST_LIBRARY} ) diff --git a/src/Core/Algorithms/Field/CMakeLists.txt b/src/Core/Algorithms/Field/CMakeLists.txt index e929ba4805..163729e3a3 100644 --- a/src/Core/Algorithms/Field/CMakeLists.txt +++ b/src/Core/Algorithms/Field/CMakeLists.txt @@ -51,7 +51,6 @@ TARGET_LINK_LIBRARIES(Algorithms_Field Core_Datatypes_Legacy_Field Algorithms_Base Core_Algorithms_Legacy_Fields - ${SCI_BOOST_LIBRARY} ${SCI_CLEAVER2_LIBRARY} ) diff --git a/src/Core/Algorithms/Legacy/Inverse/CMakeLists.txt b/src/Core/Algorithms/Legacy/Inverse/CMakeLists.txt index b562705869..7293735f88 100644 --- a/src/Core/Algorithms/Legacy/Inverse/CMakeLists.txt +++ b/src/Core/Algorithms/Legacy/Inverse/CMakeLists.txt @@ -56,7 +56,6 @@ TARGET_LINK_LIBRARIES(Algorithms_Legacy_Inverse Core_Basis #field basis Core_Algorithms_Legacy_Fields Algorithms_Base - ${SCI_BOOST_LIBRARY} ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/Algorithms/Math/CMakeLists.txt b/src/Core/Algorithms/Math/CMakeLists.txt index ae073d05b0..5c4c93e86d 100644 --- a/src/Core/Algorithms/Math/CMakeLists.txt +++ b/src/Core/Algorithms/Math/CMakeLists.txt @@ -87,7 +87,6 @@ TARGET_LINK_LIBRARIES(Algorithms_Math Core_Thread Algorithms_Base Core_Parser - ${SCI_BOOST_LIBRARY} ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/Algorithms/Visualization/CMakeLists.txt b/src/Core/Algorithms/Visualization/CMakeLists.txt index 9c37997e8b..1236e17462 100644 --- a/src/Core/Algorithms/Visualization/CMakeLists.txt +++ b/src/Core/Algorithms/Visualization/CMakeLists.txt @@ -44,7 +44,6 @@ TARGET_LINK_LIBRARIES(Core_Algorithms_Visualization Core_Datatypes_Legacy_Field Graphics_Glyphs Algorithms_Base - ${SCI_BOOST_LIBRARY} ) IF(WITH_OSPRAY) diff --git a/src/Core/Application/CMakeLists.txt b/src/Core/Application/CMakeLists.txt index 00141431fa..d443c794c5 100644 --- a/src/Core/Application/CMakeLists.txt +++ b/src/Core/Application/CMakeLists.txt @@ -52,7 +52,6 @@ TARGET_LINK_LIBRARIES(Core_Application Core_IEPlugin Core_Application_Session Core_Application_Preferences - ${SCI_BOOST_LIBRARY} ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/Application/Preferences/CMakeLists.txt b/src/Core/Application/Preferences/CMakeLists.txt index eed6832e5c..5b782564eb 100644 --- a/src/Core/Application/Preferences/CMakeLists.txt +++ b/src/Core/Application/Preferences/CMakeLists.txt @@ -43,7 +43,6 @@ SCIRUN_ADD_LIBRARY(Core_Application_Preferences TARGET_LINK_LIBRARIES(Core_Application_Preferences Core_Logging Algorithms_Base - ${SCI_BOOST_LIBRARY} ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/Application/Session/CMakeLists.txt b/src/Core/Application/Session/CMakeLists.txt index b7991b164a..4d2d04f4c6 100644 --- a/src/Core/Application/Session/CMakeLists.txt +++ b/src/Core/Application/Session/CMakeLists.txt @@ -44,7 +44,6 @@ TARGET_LINK_LIBRARIES(Core_Application_Session Core_Logging Algorithms_Base Core_DatabaseManager - ${SCI_BOOST_LIBRARY} ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/Basis/CMakeLists.txt b/src/Core/Basis/CMakeLists.txt index a722b29709..0d700a3cff 100644 --- a/src/Core/Basis/CMakeLists.txt +++ b/src/Core/Basis/CMakeLists.txt @@ -95,6 +95,10 @@ SCIRUN_ADD_LIBRARY(Core_Basis ${Core_Basis_SRCS} ) +target_link_libraries(Core_Basis + Boost::headers +) + IF(BUILD_SHARED_LIBS) ADD_DEFINITIONS(-DBUILD_Core_Basis) ENDIF(BUILD_SHARED_LIBS) diff --git a/src/Core/Command/CMakeLists.txt b/src/Core/Command/CMakeLists.txt index 9222492ebf..e27f314031 100644 --- a/src/Core/Command/CMakeLists.txt +++ b/src/Core/Command/CMakeLists.txt @@ -50,7 +50,6 @@ TARGET_LINK_LIBRARIES(Core_Command Core_CommandLine Core_Utils Algorithms_Base - ${SCI_BOOST_LIBRARY} ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/CommandLine/CMakeLists.txt b/src/Core/CommandLine/CMakeLists.txt index 31af8bf871..8f4e64acea 100644 --- a/src/Core/CommandLine/CMakeLists.txt +++ b/src/Core/CommandLine/CMakeLists.txt @@ -41,7 +41,8 @@ SCIRUN_ADD_LIBRARY(Core_CommandLine ) TARGET_LINK_LIBRARIES(Core_CommandLine - ${SCI_BOOST_LIBRARY} + Boost::filesystem + Boost::program_options ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/ConsoleApplication/CMakeLists.txt b/src/Core/ConsoleApplication/CMakeLists.txt index ecc78e9dba..44f46d796d 100644 --- a/src/Core/ConsoleApplication/CMakeLists.txt +++ b/src/Core/ConsoleApplication/CMakeLists.txt @@ -47,7 +47,6 @@ SCIRUN_ADD_LIBRARY(Core_ConsoleApplication TARGET_LINK_LIBRARIES(Core_ConsoleApplication Core_Application Core_Command - ${SCI_BOOST_LIBRARY} ) IF(BUILD_WITH_PYTHON) diff --git a/src/Core/Containers/CMakeLists.txt b/src/Core/Containers/CMakeLists.txt index a906868f69..51983aff94 100644 --- a/src/Core/Containers/CMakeLists.txt +++ b/src/Core/Containers/CMakeLists.txt @@ -47,9 +47,19 @@ SCIRUN_ADD_LIBRARY(Core_Containers ${Core_Containers_HEADERS} ) +if(WIN32 AND BUILD_SHARED_LIBS) + set_target_properties(Core_Containers + PROPERTIES + WINDOWS_EXPORT_ALL_SYMBOLS ON + ) +endif() + TARGET_LINK_LIBRARIES(Core_Containers - Core_Persistent - Core_Exceptions_Legacy + PUBLIC + Boost::headers + PRIVATE + Core_Persistent + Core_Exceptions_Legacy ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/Containers/Tests/CMakeLists.txt b/src/Core/Containers/Tests/CMakeLists.txt index 213f1ed779..e16a81195d 100644 --- a/src/Core/Containers/Tests/CMakeLists.txt +++ b/src/Core/Containers/Tests/CMakeLists.txt @@ -35,7 +35,7 @@ SCIRUN_ADD_UNIT_TEST(Core_Containers_Tests ) TARGET_LINK_LIBRARIES(Core_Containers_Tests - #Core_Containers + Core_Containers gtest_main gtest gmock diff --git a/src/Core/DatabaseManager/CMakeLists.txt b/src/Core/DatabaseManager/CMakeLists.txt index 33400d9f49..f37de690b5 100644 --- a/src/Core/DatabaseManager/CMakeLists.txt +++ b/src/Core/DatabaseManager/CMakeLists.txt @@ -41,8 +41,10 @@ SCIRUN_ADD_LIBRARY(Core_DatabaseManager ) TARGET_LINK_LIBRARIES(Core_DatabaseManager - ${SCI_SQLITE_LIBRARY} - ${SCI_BOOST_LIBRARY}) + ${SCI_SQLITE_LIBRARY} + Boost::atomic + Boost::filesystem +) IF(BUILD_SHARED_LIBS) ADD_DEFINITIONS(-DBUILD_Core_DatabaseManager) diff --git a/src/Core/Datatypes/Legacy/Field/CMakeLists.txt b/src/Core/Datatypes/Legacy/Field/CMakeLists.txt index c3aaa8fbd0..9a77f38d19 100644 --- a/src/Core/Datatypes/Legacy/Field/CMakeLists.txt +++ b/src/Core/Datatypes/Legacy/Field/CMakeLists.txt @@ -115,7 +115,6 @@ TARGET_LINK_LIBRARIES(Core_Datatypes_Legacy_Field Core_Util_Legacy Core_Datatypes Core_Datatypes_Legacy_Base - ${SCI_BOOST_LIBRARY} ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/Datatypes/Legacy/Nrrd/CMakeLists.txt b/src/Core/Datatypes/Legacy/Nrrd/CMakeLists.txt index 5144745f59..00fe16f9dc 100644 --- a/src/Core/Datatypes/Legacy/Nrrd/CMakeLists.txt +++ b/src/Core/Datatypes/Legacy/Nrrd/CMakeLists.txt @@ -51,7 +51,6 @@ TARGET_LINK_LIBRARIES(Core_Datatypes_Legacy_Nrrd Core_Util_Legacy Core_Datatypes Core_Datatypes_Legacy_Base - ${SCI_BOOST_LIBRARY} ${SCI_TEEM_LIBRARY} ) diff --git a/src/Core/Datatypes/Mesh/CMakeLists.txt b/src/Core/Datatypes/Mesh/CMakeLists.txt index dc8d459173..f4306e6187 100644 --- a/src/Core/Datatypes/Mesh/CMakeLists.txt +++ b/src/Core/Datatypes/Mesh/CMakeLists.txt @@ -51,7 +51,6 @@ TARGET_LINK_LIBRARIES(Core_Datatypes_Mesh Core_Basis Core_Utils Core_Util_Legacy - ${SCI_BOOST_LIBRARY} ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/Exceptions/CMakeLists.txt b/src/Core/Exceptions/CMakeLists.txt index 8064f13967..79f2df3fa5 100644 --- a/src/Core/Exceptions/CMakeLists.txt +++ b/src/Core/Exceptions/CMakeLists.txt @@ -61,4 +61,5 @@ SCIRUN_ADD_LIBRARY(Core_Exceptions_Legacy TARGET_LINK_LIBRARIES(Core_Exceptions_Legacy ${DL_LIBRARY} + Boost::headers ) diff --git a/src/Core/Logging/CMakeLists.txt b/src/Core/Logging/CMakeLists.txt index 089cb35b18..bcfff312af 100644 --- a/src/Core/Logging/CMakeLists.txt +++ b/src/Core/Logging/CMakeLists.txt @@ -51,6 +51,7 @@ SCIRUN_ADD_LIBRARY(Core_Logging TARGET_LINK_LIBRARIES(Core_Logging Core_Utils + Boost::filesystem ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/Logging/Tests/Log4cppWrapperTests.cc b/src/Core/Logging/Tests/Log4cppWrapperTests.cc index 717eb8dedc..3ef814c562 100644 --- a/src/Core/Logging/Tests/Log4cppWrapperTests.cc +++ b/src/Core/Logging/Tests/Log4cppWrapperTests.cc @@ -28,6 +28,8 @@ #include +/// @todo: this won't link in debug mode due to Logging::Instance impl +#if NDEBUG #include #include #include @@ -198,3 +200,4 @@ void err_handler_example() }); // (or logger->set_error_handler(..) to set for specific logger) } +#endif diff --git a/src/Core/Math/CMakeLists.txt b/src/Core/Math/CMakeLists.txt index 9de79ed5d9..a760c3bedf 100644 --- a/src/Core/Math/CMakeLists.txt +++ b/src/Core/Math/CMakeLists.txt @@ -65,7 +65,9 @@ SCIRUN_ADD_LIBRARY(Core_Math ) TARGET_LINK_LIBRARIES(Core_Math - Core_Exceptions_Legacy + PUBLIC + Boost::headers + Core_Exceptions_Legacy ) IF(BUILD_SHARED_LIBS) diff --git a/src/Core/Python/CMakeLists.txt b/src/Core/Python/CMakeLists.txt index 602f1e9b8f..34a434a54d 100644 --- a/src/Core/Python/CMakeLists.txt +++ b/src/Core/Python/CMakeLists.txt @@ -42,6 +42,19 @@ SCIRUN_ADD_LIBRARY(Core_Python ${Core_Python_SRCS} ) +# --- MSVC Debug-only Python ABI enforcement --- +if (MSVC) + target_compile_definitions(Core_Python PRIVATE + $<$:Py_DEBUG> + $<$:Py_TRACE_REFS> + ) + + # Defensive: prevent accidental linkage to release Python + target_link_options(Core_Python PRIVATE + $<$:/NODEFAULTLIB:python313.lib> + ) +endif() + IF(BUILD_SHARED_LIBS) ADD_DEFINITIONS(-DBUILD_Core_Python) ENDIF(BUILD_SHARED_LIBS) @@ -60,8 +73,7 @@ ELSE() ENDIF() TARGET_LINK_LIBRARIES(Core_Python - ${SCI_PYTHON_LIBRARY} - ${SCI_BOOST_LIBRARY} + ${SCI_PYTHON_LIBRARIES} SCIRunPythonAPI Core_Matlab Core_Datatypes diff --git a/src/Core/Python/PythonDatatypeConverter.cc b/src/Core/Python/PythonDatatypeConverter.cc index f5adb86bf1..81df29e9cd 100644 --- a/src/Core/Python/PythonDatatypeConverter.cc +++ b/src/Core/Python/PythonDatatypeConverter.cc @@ -77,7 +77,7 @@ struct PythonObjectVisitor : boost::static_visitor struct ValueVisitor : boost::static_visitor { private: - const py::object& object_; + py::object object_; public: explicit ValueVisitor(const py::object& object) : object_(object) {} @@ -132,10 +132,12 @@ struct ValueVisitor : boost::static_visitor const auto firstVal = v[0]; const py::extract e(object_); auto pyList = e(); + auto gil = PyGILState_Ensure(); Variable::List newList(py::len(pyList)); for (auto i = 0; i < py::len(pyList); ++i) newList[i] = Variable(firstVal.name(), boost::apply_visitor(ValueVisitor(pyList[i]), firstVal.value())); + PyGILState_Release(gil); return newList; } else @@ -626,7 +628,10 @@ Variable SCIRun::Core::Python::convertPythonObjectToVariable(const py::object& o Variable SCIRun::Core::Python::convertPythonObjectToVariableWithTypeInference( const py::object& object, const Variable& var) { - return Variable(var.name(), boost::apply_visitor(ValueVisitor(object), var.value())); + auto gil = PyGILState_Ensure(); + Variable result(var.name(), boost::apply_visitor(ValueVisitor(object), var.value())); + PyGILState_Release(gil); + return result; } py::object SCIRun::Core::Python::convertVariableToPythonObject(const Variable& var) diff --git a/src/Core/Python/PythonDatatypeConverter.h b/src/Core/Python/PythonDatatypeConverter.h index 867f2211cd..b43ff14ca0 100644 --- a/src/Core/Python/PythonDatatypeConverter.h +++ b/src/Core/Python/PythonDatatypeConverter.h @@ -105,13 +105,23 @@ namespace SCIRun class SCISHARE DatatypePythonExtractor { public: - virtual ~DatatypePythonExtractor() {} + virtual ~DatatypePythonExtractor() + { + PyGILState_STATE gil = PyGILState_Ensure(); + try + { + object_ = boost::python::object(); // release + } + catch (...) + {} + PyGILState_Release(gil); + } explicit DatatypePythonExtractor(const boost::python::object& object) : object_(object) {} virtual bool check() const = 0; virtual Datatypes::DatatypeHandle operator()() const = 0; virtual std::string label() const = 0; protected: - const boost::python::object& object_; + boost::python::object object_; }; class SCISHARE DenseMatrixExtractor : public DatatypePythonExtractor diff --git a/src/Core/Python/PythonInterpreter.cc b/src/Core/Python/PythonInterpreter.cc index 5b2ddeb184..3f3bfb8b44 100644 --- a/src/Core/Python/PythonInterpreter.cc +++ b/src/Core/Python/PythonInterpreter.cc @@ -25,213 +25,217 @@ DEALINGS IN THE SOFTWARE. */ - /// @todo Documentation Core/Python/PythonInterpreter.cc #ifdef BUILD_WITH_PYTHON #ifdef _MSC_VER -//#pragma warning( push ) -#pragma warning( disable: 4244 ) +// #pragma warning( push ) +#pragma warning(disable : 4244) #endif #include -#include +#include +#include #include -#include #include +#include +#include +#include #include #include -#include -#include -#include - -//#include -#include -#include -#include +// #include +#include #include - +#include +#include +#include #include -#include using namespace SCIRun::Core; -namespace SCIRun -{ -namespace Core -{ +namespace SCIRun { +namespace Core { -class PythonInterpreterPrivate : public Lockable -{ -public: - typedef std::pair< std::string, PyObject* ( * )( void ) > module_entry_type; - typedef std::list< module_entry_type > module_list_type; - - std::string read_from_console( const int bytes = -1 ); - - const wchar_t* programName() const { return !program_name_.empty() ? &program_name_[0] : L""; } - void setProgramName(const std::vector& name) - { - program_name_ = name; - //std::wcout << "PROGRAM NAME SET TO: " << programName() << std::endl; - } - - // A list of Python extension modules that need to be initialized - module_list_type modules_; - // An instance of python CommandCompiler object (defined in codeop.py) - boost::python::object compiler_; - // The context of the Python main module. - boost::python::object globals_; - // Whether the Python interpreter has been initialized. - bool initialized_; - bool terminal_running_; - // The input buffer - std::string input_buffer_; - // Whether the interpreter is waiting for input - bool waiting_for_input_; - // The command buffer (for Python statements that span over multiple lines) - std::string command_buffer_; - // Python sys.ps1 - std::string prompt1_; - // Python sys.ps2 - std::string prompt2_; - - // Condition variable to make sure the PythonInterpreter thread has - // completed initialization before continuing the main thread. - std::condition_variable thread_condition_variable_; -private: - // The name of the executable - std::vector< wchar_t > program_name_; -}; + class PythonInterpreterPrivate : public Lockable + { + public: + ~PythonInterpreterPrivate() + { + if (!initialized_) return; -std::string PythonInterpreterPrivate::read_from_console( const int bytes /*= -1 */ ) -{ - lock_type lock( this->get_mutex() ); - - std::string result; - if ( !input_buffer_.empty() ) - { - if ( bytes <= 0 ) - { - result = input_buffer_; - input_buffer_.clear(); - } - else - { - result = input_buffer_.substr( 0, bytes ); - if ( bytes < static_cast< int >( input_buffer_.size() ) ) - { - input_buffer_ = input_buffer_.substr( bytes ); - } - else - { - input_buffer_.clear(); - } - } - } - - int more_bytes = bytes - static_cast< int >( result.size() ); - while ( ( bytes <= 0 && result.empty() ) || - ( bytes > 0 && more_bytes > 0 ) ) - { - this->waiting_for_input_ = true; - this->thread_condition_variable_.wait( lock ); - - // Abort reading if an interrupt signal has been received. - if ( PyErr_CheckSignals() != 0 ) break; - - if ( bytes <= 0 ) - { - result = input_buffer_; - input_buffer_.clear(); - } - else - { - result += input_buffer_.substr( 0, more_bytes ); - if ( more_bytes < static_cast< int >( input_buffer_.size() ) ) - { - input_buffer_ = input_buffer_.substr( more_bytes ); - } - else - { - input_buffer_.clear(); - } - } - - more_bytes = bytes - static_cast< int >( result.size() ); - } - - this->waiting_for_input_ = false; - return result; -} + PyGILState_STATE gil = PyGILState_Ensure(); -}} + try + { + compiler_ = boost::python::object(); // release + globals_ = boost::python::object(); + } + catch (...) + { + } + PyGILState_Release(gil); + } + typedef std::pair module_entry_type; + typedef std::list module_list_type; + + std::string read_from_console(const int bytes = -1); + + const wchar_t* programName() const { return !program_name_.empty() ? &program_name_[0] : L""; } + void setProgramName(const std::vector& name) + { + program_name_ = name; + // std::wcout << "PROGRAM NAME SET TO: " << programName() << std::endl; + } + + // A list of Python extension modules that need to be initialized + module_list_type modules_; + // An instance of python CommandCompiler object (defined in codeop.py) + boost::python::object compiler_; + // The context of the Python main module. + boost::python::object globals_; + // Whether the Python interpreter has been initialized. + bool initialized_; + bool terminal_running_; + // The input buffer + std::string input_buffer_; + // Whether the interpreter is waiting for input + bool waiting_for_input_; + // The command buffer (for Python statements that span over multiple lines) + std::string command_buffer_; + // Python sys.ps1 + std::string prompt1_; + // Python sys.ps2 + std::string prompt2_; + + // Condition variable to make sure the PythonInterpreter thread has + // completed initialization before continuing the main thread. + std::condition_variable thread_condition_variable_; + + private: + // The name of the executable + std::vector program_name_; + }; + + std::string PythonInterpreterPrivate::read_from_console(const int bytes /*= -1 */) + { + lock_type lock(this->get_mutex()); + + std::string result; + if (!input_buffer_.empty()) + { + if (bytes <= 0) + { + result = input_buffer_; + input_buffer_.clear(); + } + else + { + result = input_buffer_.substr(0, bytes); + if (bytes < static_cast(input_buffer_.size())) + { + input_buffer_ = input_buffer_.substr(bytes); + } + else + { + input_buffer_.clear(); + } + } + } + + int more_bytes = bytes - static_cast(result.size()); + while ((bytes <= 0 && result.empty()) || (bytes > 0 && more_bytes > 0)) + { + this->waiting_for_input_ = true; + this->thread_condition_variable_.wait(lock); + + // Abort reading if an interrupt signal has been received. + //if (PyErr_CheckSignals() != 0) break; + + if (bytes <= 0) + { + result = input_buffer_; + input_buffer_.clear(); + } + else + { + result += input_buffer_.substr(0, more_bytes); + if (more_bytes < static_cast(input_buffer_.size())) + { + input_buffer_ = input_buffer_.substr(more_bytes); + } + else + { + input_buffer_.clear(); + } + } + + more_bytes = bytes - static_cast(result.size()); + } + + this->waiting_for_input_ = false; + return result; + } + +} +} ////////////////////////////////////////////////////////////////////////// // Class PythonTerminal ////////////////////////////////////////////////////////////////////////// class PythonStdIO { -public: - boost::python::object read( int n ) - { - std::string data = PythonInterpreter::Instance().private_->read_from_console( n ); - boost::python::str pystr( data.c_str() ); - return pystr.encode(); - } - - std::string readline() - { - return PythonInterpreter::Instance().private_->read_from_console(); - } - - int write( const std::string& data ) - { - PythonInterpreter::Instance().output_signal_( data ); - return static_cast< int >( data.size() ); - } + public: + boost::python::object read(int n) + { + std::string data = PythonInterpreter::Instance().private_->read_from_console(n); + boost::python::str pystr(data.c_str()); + return pystr.encode(); + } + + std::string readline() { return PythonInterpreter::Instance().private_->read_from_console(); } + + int write(const std::string& data) + { + PythonInterpreter::Instance().output_signal_(data); + return static_cast(data.size()); + } }; class PythonStdErr { -public: - int write( const std::string& data ) - { - PythonInterpreter::Instance().error_signal_( data ); - return static_cast< int >( data.size() ); - } + public: + int write(const std::string& data) + { + PythonInterpreter::Instance().error_signal_(data); + return static_cast(data.size()); + } }; -BOOST_PYTHON_MODULE( interpreter ) +BOOST_PYTHON_MODULE(interpreter) { - boost::python::class_< PythonStdIO >( "terminalio" ) - .def( "read", &PythonStdIO::read ) - .def( "readline", &PythonStdIO::readline ) - .def( "write", &PythonStdIO::write ); + boost::python::class_("terminalio").def("read", &PythonStdIO::read).def("readline", &PythonStdIO::readline).def("write", &PythonStdIO::write); - boost::python::class_< PythonStdErr >( "terminalerr" ) - .def( "write", &PythonStdErr::write ); + boost::python::class_("terminalerr").def("write", &PythonStdErr::write); } -CORE_SINGLETON_IMPLEMENTATION( PythonInterpreter ); +CORE_SINGLETON_IMPLEMENTATION(PythonInterpreter); -PythonInterpreter::PythonInterpreter() : - private_( new PythonInterpreterPrivate ) +PythonInterpreter::PythonInterpreter() : private_(new PythonInterpreterPrivate) { - this->private_->initialized_ = false; - this->private_->terminal_running_ = false; - this->private_->waiting_for_input_ = false; + this->private_->initialized_ = false; + this->private_->terminal_running_ = false; + this->private_->waiting_for_input_ = false; } PythonInterpreter::~PythonInterpreter() { - // NOTE: Boost.Python requires that we don't call Py_Finalize - //Py_Finalize(); + // NOTE: Boost.Python requires that we don't call Py_Finalize + //Py_Finalize(); } -//#define PRINT_PY_INIT_DEBUG(n) std::cout << "ev " << (n) << std::endl; +// #define PRINT_PY_INIT_DEBUG(n) std::cout << "ev " << (n) << std::endl; #define PRINT_PY_INIT_DEBUG(n) bool needsSpecialPythonPathTreatment(const std::string& commandLine) @@ -242,57 +246,183 @@ bool needsSpecialPythonPathTreatment(const std::string& commandLine) #else const std::string TEST_EXECUTABLE_NAME = "SCIRun_test"; const std::string UNIT_TEST_EXECUTABLE_NAME = "Engine_Python_Tests"; - return commandLine.find(TEST_EXECUTABLE_NAME) != std::string::npos - || commandLine.find(UNIT_TEST_EXECUTABLE_NAME) != std::string::npos; - //TODO: this version is bugged if the test network name starts with a relative path: stem() returns the network name, not the executable name. - //the full command line isn't normally a valid path object anyway. - // return 0 == boost::filesystem::path(commandLine).stem().string().compare(0, TEST_EXECUTABLE_NAME.size(), TEST_EXECUTABLE_NAME); + return commandLine.find(TEST_EXECUTABLE_NAME) != std::string::npos || commandLine.find(UNIT_TEST_EXECUTABLE_NAME) != std::string::npos; + // TODO: this version is bugged if the test network name starts with a relative path: stem() returns the network name, not the executable name. + // the full command line isn't normally a valid path object anyway. + // return 0 == boost::filesystem::path(commandLine).stem().string().compare(0, TEST_EXECUTABLE_NAME.size(), TEST_EXECUTABLE_NAME); #endif } void PythonInterpreter::initialize_eventhandler(bool needsSpecialPythonPathTreatment, const boost::filesystem::path& libPath) { PRINT_PY_INIT_DEBUG(1); - using namespace boost::python; + using namespace boost::python; - PythonInterpreterPrivate::lock_type lock( this->private_->get_mutex() ); + PythonInterpreterPrivate::lock_type lock(this->private_->get_mutex()); PRINT_PY_INIT_DEBUG(2); - // Register C++ to Python type converters - //RegisterToPythonConverters(); - // Add the extension modules - PyImport_AppendInittab( "interpreter", PyInit_interpreter ); - PyImport_AppendInittab( "SCIRunPythonAPI", PyInit_SCIRunPythonAPI ); - for ( module_list_type::iterator it = this->private_->modules_.begin(); - it != this->private_->modules_.end(); ++it ) + // Register built-in extension modules BEFORE initialization (still allowed) + PyImport_AppendInittab("interpreter", PyInit_interpreter); + PyImport_AppendInittab("SCIRunPythonAPI", PyInit_SCIRunPythonAPI); + for (module_list_type::iterator it = this->private_->modules_.begin(); it != this->private_->modules_.end(); ++it) { - PyImport_AppendInittab( ( *it ).first.c_str(), ( *it ).second ); + PyImport_AppendInittab((*it).first.c_str(), (*it).second); } PRINT_PY_INIT_DEBUG(3); - //std::wcerr << "initialize_eventhandler: program name=" << this->private_->programName() << std::endl; - Py_SetProgramName(const_cast< wchar_t* >(this->private_->programName())); - PRINT_PY_INIT_DEBUG(4); - //std::wcout << "lib_path: " << lib_path.wstring() << std::endl; +#if PY_VERSION_HEX >= 0x03080000 + // ----------------------------- + // Modern init path (Python >=3.8) + // ----------------------------- + // Build a list of module search paths (as wide strings). + std::vector search_paths; + +#if defined(_WIN32) + const std::wstring PATH_SEP(L";"); // not used in PyConfig, but kept for clarity + boost::filesystem::path lib_path = libPath; + boost::filesystem::path top_lib_path = lib_path / PYTHONPATH / PYTHONNAME; + boost::filesystem::path dynload_lib_path = top_lib_path / "lib-dynload"; + boost::filesystem::path site_lib_path = top_lib_path / "site-packages"; + + // Historically you only used top + site on Windows. + search_paths.push_back(top_lib_path.wstring()); + search_paths.push_back(site_lib_path.wstring()); + +#elif defined(__APPLE__) + const std::wstring PATH_SEP(L":"); // not used in PyConfig, but kept for clarity + boost::filesystem::path lib_path = libPath.parent_path(); + + std::vector lib_path_list; + lib_path_list.push_back(lib_path.parent_path() / boost::filesystem::path("Frameworks") / PYTHONPATH); + lib_path_list.push_back(lib_path / PYTHONPATH); + lib_path_list.push_back(lib_path.parent_path() / PYTHONPATH); + + if (needsSpecialPythonPathTreatment) + { + boost::filesystem::path full_lib_path(PYTHONLIBDIR); + full_lib_path /= PYTHONLIB; + lib_path_list.push_back(full_lib_path); + } + + for (size_t i = 0; i < lib_path_list.size(); ++i) + { + const auto& path = lib_path_list[i]; + boost::filesystem::path plat_lib_path = path / "plat-darwin"; // keep legacy layout if present + boost::filesystem::path dynload_lib_path = path / "lib-dynload"; + boost::filesystem::path site_lib_path = path / "site-packages"; + + search_paths.push_back(path.wstring()); + search_paths.push_back(plat_lib_path.wstring()); + search_paths.push_back(dynload_lib_path.wstring()); + search_paths.push_back(site_lib_path.wstring()); + } + +#else + // Linux and other Unix + const std::wstring PATH_SEP(L":"); // not used in PyConfig, but kept for clarity + boost::filesystem::path lib_path = libPath; + boost::filesystem::path top_lib_path = lib_path / PYTHONPATH; + boost::filesystem::path dynload_lib_path = top_lib_path / "lib-dynload"; + boost::filesystem::path site_lib_path = top_lib_path / "site-packages"; + boost::filesystem::path plat_lib_path = top_lib_path / "plat-linux"; + + search_paths.push_back(top_lib_path.wstring()); + search_paths.push_back(plat_lib_path.wstring()); + search_paths.push_back(dynload_lib_path.wstring()); + search_paths.push_back(site_lib_path.wstring()); +#endif + + // ----------------------------- + // PyPreConfig / PyConfig setup + // ----------------------------- + PyStatus status; + PyPreConfig preconfig; + PyConfig config; + + // If you previously set Py_IgnoreEnvironmentFlag = 1, use "isolated" preinit. + PyPreConfig_InitIsolatedConfig(&preconfig); + status = Py_PreInitialize(&preconfig); + if (PyStatus_Exception(status)) + { + Py_ExitStatusException(status); // exits with message + } + + // Start from isolated defaults (equivalent to ignoring env, no signal handlers, etc.) + PyConfig_InitIsolatedConfig(&config); + + // Replace deprecated Py_SetProgramName(...) with config.program_name + // (Use your stored wide program name) + { + const wchar_t* prog = this->private_->programName(); + status = PyConfig_SetString(&config, &config.program_name, prog); + if (PyStatus_Exception(status)) + { + PyConfig_Clear(&config); + Py_ExitStatusException(status); + } + } + + // Replace global flags: + // Py_IgnoreEnvironmentFlag = 1 -> config.use_environment = 0 + // Py_InspectFlag = 1 -> config.inspect = 1 + // Py_OptimizeFlag = 2 -> config.optimization_level = 2 + // Py_NoSiteFlag = 1 (non-Windows)-> config.site_import = 0 + config.use_environment = 0; + config.inspect = 1; + config.optimization_level = 2; +#if !defined(_WIN32) + config.site_import = 0; +#endif + + // Populate module_search_paths (replacement for Py_SetPath) + // NOTE: PyConfig expects absolute, existing directories; add only those that exist. + for (const auto& wpath : search_paths) + { + if (!wpath.empty()) + { + // Optional: skip non-existing paths to avoid warnings + // (Converting back to narrow only for filesystem exists check if needed) + // Here we trust the layout and append directly: + status = PyWideStringList_Append(&config.module_search_paths, wpath.c_str()); + if (PyStatus_Exception(status)) + { + PyConfig_Clear(&config); + Py_ExitStatusException(status); + } + } + } + + // Finalize initialization + status = Py_InitializeFromConfig(&config); + PyConfig_Clear(&config); + if (PyStatus_Exception(status)) + { + Py_ExitStatusException(status); + } + + PyEval_SaveThread(); + + PRINT_PY_INIT_DEBUG(8); + +#else + // ------------------------------- + // Legacy fallback (Python < 3.8) + // ------------------------------- + Py_SetProgramName(const_cast(this->private_->programName())); + std::wstringstream lib_paths; -#if defined( _WIN32 ) +#if defined(_WIN32) const std::wstring PATH_SEP(L";"); #else const std::wstring PATH_SEP(L":"); #endif - PRINT_PY_INIT_DEBUG(5); -#if defined( __APPLE__ ) + +#if defined(__APPLE__) boost::filesystem::path lib_path = libPath.parent_path(); std::vector lib_path_list; - // relative paths - PRINT_PY_INIT_DEBUG(lib_path); - PRINT_PY_INIT_DEBUG(boost::filesystem::path(PYTHONPATH)); lib_path_list.push_back(lib_path.parent_path() / boost::filesystem::path("Frameworks") / PYTHONPATH); - PRINT_PY_INIT_DEBUG(lib_path_list.back()); lib_path_list.push_back(lib_path / PYTHONPATH); - PRINT_PY_INIT_DEBUG(lib_path_list.back()); lib_path_list.push_back(lib_path.parent_path() / PYTHONPATH); - PRINT_PY_INIT_DEBUG(lib_path_list.back()); if (needsSpecialPythonPathTreatment) { @@ -301,167 +431,178 @@ void PythonInterpreter::initialize_eventhandler(bool needsSpecialPythonPathTreat lib_path_list.push_back(full_lib_path); } - for ( size_t i = 0; i < lib_path_list.size(); ++i ) + for (size_t i = 0; i < lib_path_list.size(); ++i) { auto path = lib_path_list[i]; boost::filesystem::path plat_lib_path = path / "plat-darwin"; boost::filesystem::path dynload_lib_path = path / "lib-dynload"; boost::filesystem::path site_lib_path = path / "site-packages"; - if (i > 0) - { - lib_paths << PATH_SEP; - } - lib_paths << path.wstring() << PATH_SEP - << plat_lib_path.wstring() << PATH_SEP - << dynload_lib_path.wstring() << PATH_SEP - << site_lib_path.wstring(); + if (i > 0) lib_paths << PATH_SEP; + lib_paths << path.wstring() << PATH_SEP << plat_lib_path.wstring() << PATH_SEP << dynload_lib_path.wstring() << PATH_SEP << site_lib_path.wstring(); } + Py_SetPath(lib_paths.str().c_str()); - Py_SetPath( lib_paths.str().c_str() ); -#elif defined (_WIN32) - boost::filesystem::path lib_path = libPath;// Application::Instance().executablePath(); - boost::filesystem::path top_lib_path = lib_path / PYTHONPATH / PYTHONNAME; - //std::cout << "top_lib_path: " << top_lib_path.string() << std::endl; - boost::filesystem::path dynload_lib_path = top_lib_path / "lib-dynload"; - //std::cout << "dynload_lib_path: " << dynload_lib_path.string() << std::endl; - boost::filesystem::path site_lib_path = top_lib_path / "site-packages"; - //std::cout << "site_lib_path: " << site_lib_path.string() << std::endl; - lib_paths << top_lib_path.wstring() << PATH_SEP - << site_lib_path.wstring(); - //std::wcout << "lib_paths final: " << lib_paths.str() << std::endl; - Py_SetPath( lib_paths.str().c_str() ); - PRINT_PY_INIT_DEBUG(6); +#elif defined(_WIN32) + { + boost::filesystem::path lib_path = libPath; + boost::filesystem::path top_lib_path = lib_path / PYTHONPATH / PYTHONNAME; + boost::filesystem::path site_lib_path = top_lib_path / "site-packages"; + lib_paths << top_lib_path.wstring() << PATH_SEP << site_lib_path.wstring(); + Py_SetPath(lib_paths.str().c_str()); + } #else - // linux... - boost::filesystem::path lib_path = libPath; - boost::filesystem::path top_lib_path = lib_path / PYTHONPATH; - boost::filesystem::path dynload_lib_path = top_lib_path / "lib-dynload"; - boost::filesystem::path site_lib_path = top_lib_path / "site-packages"; - boost::filesystem::path plat_lib_path = top_lib_path / "plat-linux"; - lib_paths << top_lib_path.wstring() << PATH_SEP - << plat_lib_path.wstring() << PATH_SEP - << dynload_lib_path.wstring() << PATH_SEP - << site_lib_path.wstring(); - Py_SetPath( lib_paths.str().c_str() ); + { + boost::filesystem::path lib_path = libPath; + boost::filesystem::path top_lib_path = lib_path / PYTHONPATH; + boost::filesystem::path dynload_lib_path = top_lib_path / "lib-dynload"; + boost::filesystem::path site_lib_path = top_lib_path / "site-packages"; + boost::filesystem::path plat_lib_path = top_lib_path / "plat-linux"; + lib_paths << top_lib_path.wstring() << PATH_SEP << plat_lib_path.wstring() << PATH_SEP << dynload_lib_path.wstring() << PATH_SEP << site_lib_path.wstring(); + Py_SetPath(lib_paths.str().c_str()); + } #endif - // TODO: remove debug print when confident python initialization is stable - //std::wcerr << lib_paths.str() << std::endl; - PRINT_PY_INIT_DEBUG(7); Py_IgnoreEnvironmentFlag = 1; Py_InspectFlag = 1; Py_OptimizeFlag = 2; -#if !defined( _WIN32 ) +#if !defined(_WIN32) Py_NoSiteFlag = 1; #endif + Py_Initialize(); PRINT_PY_INIT_DEBUG(8); - // Create the compiler object - PyRun_SimpleString( "from codeop import CommandCompiler\n" - "__internal_compiler = CommandCompiler()\n" ); - boost::python::object main_module = boost::python::import( "__main__" ); - boost::python::object main_namespace = main_module.attr( "__dict__" ); - this->private_->compiler_ = main_namespace[ "__internal_compiler" ]; - this->private_->globals_ = main_namespace; - PRINT_PY_INIT_DEBUG(9); - // Set up the prompt strings - PyRun_SimpleString( "import sys\n" - "try:\n" - "\tsys.ps1\n" - "except AttributeError:\n" - "\tsys.ps1 = \">>> \"\n" - "try:\n" - "\tsys.ps2\n" - "except AttributeError:\n" - "\tsys.ps2 = \"... \"\n" ); - boost::python::object sys_module = main_namespace[ "sys" ]; - boost::python::object sys_namespace = sys_module.attr( "__dict__" ); - this->private_->prompt1_ = boost::python::extract< std::string >( sys_namespace[ "ps1" ] ); - this->private_->prompt2_ = boost::python::extract< std::string >( sys_namespace[ "ps2" ] ); - PRINT_PY_INIT_DEBUG(10); - // Hook up the I/O - PyRun_SimpleString( "import interpreter\n" - "__term_io = interpreter.terminalio()\n" - "__term_err = interpreter.terminalerr()\n" ); - PyRun_SimpleString( "import sys\n" - "sys.stdin = __term_io\n" - "sys.stdout = __term_io\n" - "sys.stderr = __term_err\n" ); - - PyRun_SimpleString( - "import atexit\n" - "def quit_gracefully():\n" - "\tprint('Goodbye!')\n" - "atexit.register(quit_gracefully)\n" - ); - - PRINT_PY_INIT_DEBUG(11); - // Remove intermediate python variables - PyRun_SimpleString( "del (interpreter, __internal_compiler, __term_io, __term_err)\n" ); - PRINT_PY_INIT_DEBUG(12); +#endif // PY_VERSION_HEX >= 0x03080000 + + auto gstate = PyGILState_Ensure(); + + { + PyRun_SimpleString( + "from codeop import CommandCompiler\n" + "__internal_compiler = CommandCompiler()\n"); + + boost::python::object main_module = boost::python::import("__main__"); + boost::python::object main_namespace = main_module.attr("__dict__"); + this->private_->compiler_ = main_namespace["__internal_compiler"]; + this->private_->globals_ = main_namespace; + + PyRun_SimpleString( + "import sys\n" + "try:\n" + "\tsys.ps1\n" + "except AttributeError:\n" + "\tsys.ps1 = \">>> \"\n" + "try:\n" + "\tsys.ps2\n" + "except AttributeError:\n" + "\tsys.ps2 = \"... \"\n"); + + boost::python::object sys_module = main_namespace["sys"]; + boost::python::object sys_namespace = sys_module.attr("__dict__"); + this->private_->prompt1_ = boost::python::extract(sys_namespace["ps1"]); + this->private_->prompt2_ = boost::python::extract(sys_namespace["ps2"]); + + PyRun_SimpleString( + "import interpreter\n" + "__term_io = interpreter.terminalio()\n" + "__term_err = interpreter.terminalerr()\n" + "import sys\n" + "sys.stdin = __term_io\n" + "sys.stdout = __term_io\n" + "sys.stderr = __term_err\n" + "import atexit\n" + "def quit_gracefully():\n" + "\tprint('Goodbye!')\n" + "atexit.register(quit_gracefully)\n"); + + PyRun_SimpleString("del (interpreter, __internal_compiler, __term_io, __term_err)\n"); + } + + PyGILState_Release(gstate); this->private_->initialized_ = true; PRINT_PY_INIT_DEBUG(999); } -namespace +namespace { +std::vector argvFromFullString(const std::string& commandLine) +{ + using namespace boost::algorithm; + std::string cmdline = commandLine; + trim_all(cmdline); + std::vector argv; + split(argv, cmdline, is_any_of(" ")); + return argv; +} + +std::vector> wideArgvFromArgv(const std::vector& argv) +{ + std::vector> wideArgv(argv.size()); + std::transform(argv.begin(), argv.end(), wideArgv.begin(), [](const std::string& arg) { + std::vector wide(arg.size() + 1); + mbstowcs(&wide[0], arg.c_str(), wide.size() + 1); + return wide; + }); + return wideArgv; +} + +std::vector wideArgvPtrsFromWideArgv(std::vector>& wideArgv) +{ + std::vector wideArgvPtrs(wideArgv.size()); + std::transform(wideArgv.begin(), wideArgv.end(), wideArgvPtrs.begin(), [](std::vector& wide) { return &wide[0]; }); + return wideArgvPtrs; +} + +std::vector getProgramName(const std::vector& argv) { - std::vector argvFromFullString(const std::string& commandLine) + size_t name_len = argv[0].size(); + std::vector program_name(name_len + 1); + mbstowcs(&program_name[0], argv[0].c_str(), name_len + 1); + return program_name; +} + +void setPythonArgv(const std::vector& argv) +{ + auto wideArgv = wideArgvFromArgv(argv); + auto wideArgvPtrs = wideArgvPtrsFromWideArgv(wideArgv); + + int argsOffset = 0; + auto scriptFlag1 = std::find(argv.begin(), argv.end(), "-s"); + auto scriptFlag2 = std::find(argv.begin(), argv.end(), "--script"); + if (scriptFlag1 != argv.end()) { - using namespace boost::algorithm; - std::string cmdline = commandLine; - trim_all(cmdline); - std::vector argv; - split(argv, cmdline, is_any_of(" ")); - return argv; + argsOffset = scriptFlag1 - argv.begin() + 1; } - - std::vector> wideArgvFromArgv(const std::vector& argv) + else if (scriptFlag2 != argv.end()) { - std::vector> wideArgv(argv.size()); - std::transform(argv.begin(), argv.end(), wideArgv.begin(), [](const std::string& arg) - { - std::vector wide(arg.size() + 1); - mbstowcs(&wide[0], arg.c_str(), wide.size() + 1); - return wide; - }); - return wideArgv; + argsOffset = scriptFlag2 - argv.begin() + 1; } - std::vector wideArgvPtrsFromWideArgv(std::vector>& wideArgv) + //PySys_SetArgv(wideArgvPtrs.size() - argsOffset, &wideArgvPtrs[argsOffset]); +} +} + +void PythonInterpreter::set_python_argv(const std::vector& argv) +{ + auto wideArgv = wideArgvFromArgv(argv); + auto wideArgvPtrs = wideArgvPtrsFromWideArgv(wideArgv); + + int argsOffset = 0; + auto scriptFlag1 = std::find(argv.begin(), argv.end(), "-s"); + auto scriptFlag2 = std::find(argv.begin(), argv.end(), "--script"); + if (scriptFlag1 != argv.end()) { - std::vector wideArgvPtrs(wideArgv.size()); - std::transform(wideArgv.begin(), wideArgv.end(), wideArgvPtrs.begin(), [](std::vector& wide) { return &wide[0]; }); - return wideArgvPtrs; + argsOffset = scriptFlag1 - argv.begin() + 1; } - - std::vector getProgramName(const std::vector& argv) + else if (scriptFlag2 != argv.end()) { - size_t name_len = argv[0].size(); - std::vector program_name(name_len + 1); - mbstowcs(&program_name[0], argv[0].c_str(), name_len + 1); - return program_name; + argsOffset = scriptFlag2 - argv.begin() + 1; } - void setPythonArgv(const std::vector& argv) - { - auto wideArgv = wideArgvFromArgv(argv); - auto wideArgvPtrs = wideArgvPtrsFromWideArgv(wideArgv); + auto gstate = PyGILState_Ensure(); - int argsOffset = 0; - auto scriptFlag1 = std::find(argv.begin(), argv.end(), "-s"); - auto scriptFlag2 = std::find(argv.begin(), argv.end(), "--script"); - if (scriptFlag1 != argv.end()) - { - argsOffset = scriptFlag1 - argv.begin() + 1; - } - else if (scriptFlag2 != argv.end()) - { - argsOffset = scriptFlag2 - argv.begin() + 1; - } + PySys_SetArgv(wideArgvPtrs.size() - argsOffset, &wideArgvPtrs[argsOffset]); - PySys_SetArgv(wideArgvPtrs.size() - argsOffset, &wideArgvPtrs[argsOffset]); - } + PyGILState_Release(gstate); } void PythonInterpreter::initialize(bool needProgramName, const std::string& commandLine, const boost::filesystem::path& libPath) @@ -472,18 +613,16 @@ void PythonInterpreter::initialize(bool needProgramName, const std::string& comm { this->private_->setProgramName(getProgramName(argv)); // TODO: remove debug print when confident python initialization is stable - //std::wcerr << "initialize program name=" << this->private_->programName() << std::endl; + // std::wcerr << "initialize program name=" << this->private_->programName() << std::endl; } initialize_eventhandler(needsSpecialPythonPathTreatment(commandLine), libPath); - setPythonArgv(argv); + set_python_argv(argv); { - auto out = [](const std::string& s) - { - if (!std::all_of(s.begin(), s.end(), isspace)) - std::cout << "[PYTHON] " << s << std::endl; + auto out = [](const std::string& s) { + if (!std::all_of(s.begin(), s.end(), isspace)) std::cout << "[PYTHON] " << s << std::endl; }; auto error = [](const std::string& s) { std::cerr << "[PYTHON ERROR] " << s << std::endl; }; output_signal_.connect(out); @@ -495,201 +634,240 @@ void PythonInterpreter::initialize(bool needProgramName, const std::string& comm void PythonInterpreter::print_banner() { - PyRun_SimpleString( "print('Python %s on %s' % (sys.version, sys.platform))\n" ); - this->prompt_signal_( this->private_->prompt1_ ); + auto gstate = PyGILState_Ensure(); + PyRun_SimpleString("print('Python %s on %s' % (sys.version, sys.platform))\n"); + PyGILState_Release(gstate); + this->prompt_signal_(this->private_->prompt1_); } -bool PythonInterpreter::run_string( const std::string& command ) +bool PythonInterpreter::run_string(const std::string& command) { LOG_DEBUG("Python::run_string( {} )", command); - { - PythonInterpreterPrivate::lock_type lock( this->private_->get_mutex() ); - if ( !this->private_->initialized_ ) - { - throw std::invalid_argument( "The python interpreter hasn't been initialized!" ); - } - } - - // Clear any previous Python errors. - PyErr_Clear(); - - // Append the command to the current command buffer - if ( this->private_->command_buffer_.empty() ) - { - this->private_->command_buffer_ = command; - } - else - { - if ( *this->private_->command_buffer_.rbegin() != '\n' ) - { - this->private_->command_buffer_ += "\n"; - } - this->private_->command_buffer_ += command; - } - - // Compile the statement in the buffer - boost::python::object code_obj; - try - { - code_obj = this->private_->compiler_( this->private_->command_buffer_ ); - } - catch ( ... ) {} - - // If an error happened during compilation, print the error message - if ( PyErr_Occurred() ) - { - PyErr_Print(); - } - // If compilation succeeded and the code object is not Py_None - else if ( code_obj ) - { - try - { - auto result = PyEval_EvalCode( code_obj.ptr(), this->private_->globals_.ptr(), nullptr ); - Py_XDECREF( result ); - } - catch ( ... ) {} - - if ( PyErr_Occurred() ) - { - if ( PyErr_ExceptionMatches( PyExc_EOFError ) ) - { - this->error_signal_( "\nKeyboardInterrupt\n" ); - PyErr_Clear(); - return false; - } - else - { - PyErr_Print(); - } - } - } - // If the code object is Py_None, prompt for more input - else - { - this->prompt_signal_( this->private_->prompt2_ ); - } - - this->private_->command_buffer_.clear(); - this->prompt_signal_( this->private_->prompt1_ ); + { + PythonInterpreterPrivate::lock_type lock(this->private_->get_mutex()); + if (!this->private_->initialized_) + { + throw std::invalid_argument("The python interpreter hasn't been initialized!"); + } + } + + // Append the command to the current command buffer + if (this->private_->command_buffer_.empty()) + { + this->private_->command_buffer_ = command; + } + else + { + if (*this->private_->command_buffer_.rbegin() != '\n') + { + this->private_->command_buffer_ += "\n"; + } + this->private_->command_buffer_ += command; + } + + // ENTER PYTHON (GIL) + auto gstate = PyGILState_Ensure(); + + { + // Compile the statement in the buffer + boost::python::object code_obj; + + // Clear any previous Python errors. + PyErr_Clear(); + + try + { + code_obj = this->private_->compiler_(this->private_->command_buffer_); + } + catch (...) + {} + + // If an error happened during compilation, print the error message + if (PyErr_Occurred()) + { + PyErr_Print(); + } + // If compilation succeeded and the code object is not Py_None + else if (code_obj) + { + try + { + auto result = PyEval_EvalCode(code_obj.ptr(), this->private_->globals_.ptr(), nullptr); + Py_XDECREF(result); + } + catch (...) + {} + + if (PyErr_Occurred()) + { + if (PyErr_ExceptionMatches(PyExc_EOFError)) + { + this->error_signal_("\nKeyboardInterrupt\n"); + PyErr_Clear(); + + // EXIT PYTHON before return + PyGILState_Release(gstate); + return false; + } + else + { + PyErr_Print(); + } + } + } + // If the code object is Py_None, prompt for more input + else + { + this->prompt_signal_(this->private_->prompt2_); + } + } + + // EXIT PYTHON + PyGILState_Release(gstate); + + this->private_->command_buffer_.clear(); + this->prompt_signal_(this->private_->prompt1_); return true; } -void PythonInterpreter::run_script( const std::string& script ) +void PythonInterpreter::run_script(const std::string& script) { LOG_DEBUG("Python::run_script( {} )", script); - { - PythonInterpreterPrivate::lock_type lock( this->private_->get_mutex() ); - if ( !this->private_->initialized_ ) - { - throw std::logic_error( "The python interpreter hasn't been initialized!" ); - } - } - - // Output the script to the console - //this->output_signal_( "Running script ...\n" ); - - // Clear any previous Python errors. - PyErr_Clear(); - - // Compile the script - boost::python::object code_obj; - try - { - code_obj = this->private_->compiler_( script, "