# xstrumodeler: Python bindings (Boost.Python) for the xstrucore section modeler.
#
# The project is self-contained:
#   - coreobj and xstrucore sources live in lib/
#   - Boost is downloaded at configure time (FetchContent) and Boost.Python is
#     compiled statically against the Python found by CMake.
#
# Build a wheel with:
#
#   pip wheel . -w dist
#
# Offline builds: pass the path of an extracted boost-1.88.0-cmake tarball with
#
#   pip wheel . -w dist -Ccmake.define.FETCHCONTENT_SOURCE_DIR_BOOST=/path/to/boost-1.88.0

cmake_minimum_required(VERSION 3.24)

project(xstrumodeler LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

find_package(Python3 COMPONENTS Interpreter Development.Module REQUIRED)
message(STATUS "Python3 Found: ${Python3_EXECUTABLE}")

# -----------------------------------------------------------------------------
# Boost
# -----------------------------------------------------------------------------
# Boost.Python's own CMake requires the full Python "Development" component
# (libpython), which is not available in manylinux images, and the Boost
# superproject would build many unneeded libraries. Therefore Boost is only
# downloaded (no add_subdirectory): headers are used directly and Boost.Python
# is compiled below as a static library linked to Python3::Module.

include(FetchContent)
FetchContent_Declare(Boost
    URL https://github.com/boostorg/boost/releases/download/boost-1.88.0/boost-1.88.0-cmake.tar.xz
    URL_HASH SHA256=f48b48390380cfb94a629872346e3a81370dc498896f16019ade727ab72eb1ec
    DOWNLOAD_EXTRACT_TIMESTAMP ON
    SOURCE_SUBDIR do-not-configure)
FetchContent_MakeAvailable(Boost)

# Header-only Boost: the cmake tarball keeps each library's headers in
# libs/<lib>/include (libs/numeric/<lib>/include for Boost.Numeric).
file(GLOB BOOST_HEADER_DIRS LIST_DIRECTORIES true
    ${boost_SOURCE_DIR}/libs/*/include
    ${boost_SOURCE_DIR}/libs/numeric/*/include)
add_library(boost_headers INTERFACE)
target_include_directories(boost_headers SYSTEM INTERFACE ${BOOST_HEADER_DIRS})
target_compile_definitions(boost_headers INTERFACE BOOST_ALL_NO_LIB)

set(BOOST_PYTHON_SRC ${boost_SOURCE_DIR}/libs/python/src)
add_library(boost_python_static STATIC
    ${BOOST_PYTHON_SRC}/dict.cpp
    ${BOOST_PYTHON_SRC}/errors.cpp
    ${BOOST_PYTHON_SRC}/exec.cpp
    ${BOOST_PYTHON_SRC}/import.cpp
    ${BOOST_PYTHON_SRC}/list.cpp
    ${BOOST_PYTHON_SRC}/long.cpp
    ${BOOST_PYTHON_SRC}/module.cpp
    ${BOOST_PYTHON_SRC}/object_operators.cpp
    ${BOOST_PYTHON_SRC}/object_protocol.cpp
    ${BOOST_PYTHON_SRC}/slice.cpp
    ${BOOST_PYTHON_SRC}/str.cpp
    ${BOOST_PYTHON_SRC}/tuple.cpp
    ${BOOST_PYTHON_SRC}/wrapper.cpp
    ${BOOST_PYTHON_SRC}/converter/from_python.cpp
    ${BOOST_PYTHON_SRC}/converter/registry.cpp
    ${BOOST_PYTHON_SRC}/converter/type_id.cpp
    ${BOOST_PYTHON_SRC}/converter/builtin_converters.cpp
    ${BOOST_PYTHON_SRC}/converter/arg_to_python_base.cpp
    ${BOOST_PYTHON_SRC}/object/enum.cpp
    ${BOOST_PYTHON_SRC}/object/class.cpp
    ${BOOST_PYTHON_SRC}/object/function.cpp
    ${BOOST_PYTHON_SRC}/object/inheritance.cpp
    ${BOOST_PYTHON_SRC}/object/life_support.cpp
    ${BOOST_PYTHON_SRC}/object/pickle_support.cpp
    ${BOOST_PYTHON_SRC}/object/iterator.cpp
    ${BOOST_PYTHON_SRC}/object/stl_iterator.cpp
    ${BOOST_PYTHON_SRC}/object/function_doc_signature.cpp
)
target_link_libraries(boost_python_static PUBLIC boost_headers Python3::Module)
target_compile_definitions(boost_python_static
    PUBLIC BOOST_PYTHON_NO_LIB BOOST_PYTHON_STATIC_LINK BOOST_PYTHON_STATIC_LIB
    PRIVATE BOOST_PYTHON_SOURCE)

# -----------------------------------------------------------------------------
# xstrumodeler module
# -----------------------------------------------------------------------------
file(GLOB COREOBJ_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/lib/coreobj/*.cpp)
file(GLOB XSTRUCORE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/lib/xstrucore/*.cpp)

# Python3_add_library sets the right module suffix (.so / .pyd) and links
# Python3::Module (no libpython on Linux, as required by manylinux).
Python3_add_library(xstrumodeler MODULE
    boost-wrap.cpp ${COREOBJ_SOURCES} ${XSTRUCORE_SOURCES})

target_include_directories(xstrumodeler PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/lib/coreobj
    ${CMAKE_CURRENT_SOURCE_DIR}/lib/xstrucore)

target_link_libraries(xstrumodeler PRIVATE boost_python_static)

if(MSVC)
    target_compile_options(xstrumodeler PRIVATE /bigobj)
endif()

# Git hash of the current commit, exposed as GIT_HASH macro
find_package(Git)
if(GIT_FOUND)
    execute_process(
        COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
        OUTPUT_VARIABLE GIT_HASH
        OUTPUT_STRIP_TRAILING_WHITESPACE
        ERROR_QUIET)
endif()
if(NOT GIT_HASH)
    set(GIT_HASH "unknown")
endif()
target_compile_definitions(xstrumodeler PRIVATE GIT_HASH="${GIT_HASH}")

# Installed next to src/xstrumodeler/__init__.py inside the wheel
install(TARGETS xstrumodeler DESTINATION xstrumodeler)
