# Copyright (c) 2025, NVIDIA CORPORATION.
# SPDX-License-Identifier: BSD-3-Clause
set(CMAKE_POLICY_DEFAULT_CMP0144 NEW)
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)

project(pyhwloc LANGUAGES C VERSION 2.0.0)

# Set default build type to Release if not specified (used by single-config generators; ignored by multi-config generators)
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Release)
endif()

list(APPEND CMAKE_MODULE_PATH "${pyhwloc_SOURCE_DIR}/cmake/")

option(PYHWLOC_FETCH_HWLOC "Fetch hwloc from GitHub instead of using system version" OFF)
option(PYHWLOC_HWLOC_V3 "Use v3 version of hwloc" ON)
option(PYHWLOC_WITH_CUDA "Add CUDA-related components" ON)

set(PYHWLOC_BUILD_HWLOC_CMAKE OFF)
if(WIN32)
  set(PYHWLOC_BUILD_HWLOC_CMAKE ON)
endif()
message(STATUS "PYHWLOC_BUILD_HWLOC_CMAKE: ${PYHWLOC_BUILD_HWLOC_CMAKE}")

add_library(pyhwloc SHARED src/ext/pyhwloc.c)

set(PYHWLOC_OUTPUT_DIR ${pyhwloc_SOURCE_DIR}/src/pyhwloc/_lib/)

if(PYHWLOC_FETCH_HWLOC)
  set(PYHWLOC_CONFIG_HWLOC OFF CACHE BOOL "")

  include(FetchContent)
  if (PYHWLOC_HWLOC_V3)
    file(READ ${pyhwloc_SOURCE_DIR}/dev/hwloc_version HWLOC_VERSION)
  else()
    file(READ ${pyhwloc_SOURCE_DIR}/dev/hwloc_version_v2 HWLOC_VERSION)
  endif()
  message(STATUS "HWLOC_VERSION: ${HWLOC_VERSION}")
  FetchContent_Declare(
    hwloc
    GIT_REPOSITORY https://github.com/open-mpi/hwloc.git
    GIT_TAG        ${HWLOC_VERSION}
  )

  # Configure hwloc build options
  FetchContent_MakeAvailable(hwloc)
  if(NOT PYHWLOC_CONFIG_HWLOC)
    if(PYHWLOC_BUILD_HWLOC_CMAKE)
      execute_process(
        COMMAND
        cmake
        -S ${hwloc_SOURCE_DIR}/contrib/windows-cmake
        -B ${hwloc_BINARY_DIR}
        -G ${CMAKE_GENERATOR}
        -DCMAKE_INSTALL_PREFIX=${PYHWLOC_OUTPUT_DIR}
        -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
        -DBUILD_SHARED_LIBS=ON
        -DHWLOC_WITH_CUDA=${PYHWLOC_WITH_CUDA}
        -DHWLOC_ENABLE_PLUGINS=ON
        WORKING_DIRECTORY ${hwloc_SOURCE_DIR}
      )
    else()
      execute_process(
        COMMAND ${hwloc_SOURCE_DIR}/autogen.sh WORKING_DIRECTORY ${hwloc_SOURCE_DIR}
      )
      # Set compiler flags based on build type
      if(CMAKE_BUILD_TYPE STREQUAL "Debug")
        set(HWLOC_CFLAGS "CFLAGS=-g -O0")
        set(HWLOC_CXXFLAGS "CXXFLAGS=-g -O0")
      else()
        set(HWLOC_CFLAGS "")
        set(HWLOC_CXXFLAGS "")
      endif()
      execute_process(
        # Use avoid version to NOT create versioned sonames like: libhwloc.so.0.0.0
        COMMAND ${hwloc_SOURCE_DIR}/configure --enable-plugins --prefix=${PYHWLOC_OUTPUT_DIR} HWLOC_LDFLAGS=-avoid-version ${HWLOC_CFLAGS} ${HWLOC_CXXFLAGS}
        WORKING_DIRECTORY ${hwloc_BINARY_DIR}
      )
    endif()
    set(PYHWLOC_CONFIG_HWLOC ON CACHE BOOL "" FORCE)
  endif()

  include(ProcessorCount)
  ProcessorCount(nproc)

  # fixme; How to respect the --parallel option?
  if(PYHWLOC_BUILD_HWLOC_CMAKE)
    add_custom_command(
      OUTPUT ${PYHWLOC_OUTPUT_DIR}/bin/hwloc.dll
      COMMAND cmake --build ${hwloc_BINARY_DIR} --config=${CMAKE_BUILD_TYPE} --parallel=${nproc} --target=install
      WORKING_DIRECTORY ${hwloc_BINARY_DIR}
    )
    add_custom_target(install_hwloc DEPENDS ${PYHWLOC_OUTPUT_DIR}/bin/hwloc.dll)

    set(HWLOC_LIBRARY ${PYHWLOC_OUTPUT_DIR}/lib/hwloc.lib)
    set(HWLOC_INCLUDE_DIR ${PYHWLOC_OUTPUT_DIR}/include/)
  else()
    add_custom_command(
      OUTPUT ${PYHWLOC_OUTPUT_DIR}/lib/libhwloc.so
      COMMAND make -j${nproc} install WORKING_DIRECTORY ${hwloc_BINARY_DIR}
    )
    add_custom_target(install_hwloc DEPENDS ${PYHWLOC_OUTPUT_DIR}/lib/libhwloc.so)
    set(HWLOC_LIBRARY ${PYHWLOC_OUTPUT_DIR}/lib/libhwloc.so)
    set(HWLOC_INCLUDE_DIR ${PYHWLOC_OUTPUT_DIR}/include/)
  endif()

  set(HWLOC_ROOT ${PYHWLOC_OUTPUT_DIR})
  set_target_properties(pyhwloc PROPERTIES
    # Do not add anything outside of the package
    INSTALL_RPATH_USE_LINK_PATH OFF
    # Do not add system toolchain path
    INSTALL_REMOVE_ENVIRONMENT_RPATH ON
    # Use relative path
    BUILD_RPATH_USE_ORIGIN ON)
else()
  find_package(hwloc REQUIRED)
endif()

message(STATUS "HWLOC_LIBRARY: ${HWLOC_LIBRARY}")

target_link_libraries(pyhwloc PRIVATE ${HWLOC_LIBRARY})
target_include_directories(pyhwloc PRIVATE ${HWLOC_INCLUDE_DIR})
list(APPEND PYHWLOC_LIBS pyhwloc)

if(PYHWLOC_WITH_CUDA)
  find_package(CUDAToolkit REQUIRED COMPONENTS nvml cuda_driver cudart_static)

  add_library(pyhwloc_cuda SHARED src/ext/cudr.c)
  target_link_libraries(pyhwloc_cuda PRIVATE CUDA::cuda_driver ${HWLOC_LIBRARY})
  target_include_directories(pyhwloc_cuda PRIVATE ${HWLOC_INCLUDE_DIR})
  list(APPEND PYHWLOC_LIBS pyhwloc_cuda)

  add_library(pyhwloc_cudart SHARED src/ext/cudart.c)
  target_link_libraries(pyhwloc_cudart PRIVATE CUDA::cudart_static ${HWLOC_LIBRARY})
  target_include_directories(pyhwloc_cudart PRIVATE ${HWLOC_INCLUDE_DIR})
  list(APPEND PYHWLOC_LIBS pyhwloc_cudart)

  add_library(pyhwloc_nvml SHARED src/ext/nvml.c)
  target_link_libraries(pyhwloc_nvml PRIVATE CUDA::nvml ${HWLOC_LIBRARY})
  target_include_directories(pyhwloc_nvml PRIVATE ${HWLOC_INCLUDE_DIR})
  list(APPEND PYHWLOC_LIBS pyhwloc_nvml)
endif()

include(GenerateExportHeader)

foreach(lib IN LISTS PYHWLOC_LIBS)
  target_include_directories(${lib} PRIVATE ${pyhwloc_BINARY_DIR})
  # One header for each shared object as Windows distinguishes import and export.
  generate_export_header(${lib})
  if(PYHWLOC_FETCH_HWLOC)
    set_target_properties(${lib} PROPERTIES
      INSTALL_RPATH "$ORIGIN/lib"
      INSTALL_RPATH_USE_LINK_PATH OFF
      BUILD_RPATH_USE_ORIGIN ON)
    add_dependencies(${lib} install_hwloc)
  endif()
endforeach()

include(GNUInstallDirs)
install(TARGETS ${PYHWLOC_LIBS}
  ARCHIVE DESTINATION ${PYHWLOC_OUTPUT_DIR}
  LIBRARY DESTINATION ${PYHWLOC_OUTPUT_DIR}
  RUNTIME DESTINATION ${PYHWLOC_OUTPUT_DIR}
  INCLUDES DESTINATION ${PYHWLOC_OUTPUT_DIR}
)
