cmake_minimum_required(VERSION 3.24)
# Target the build machine's GPU unless CMAKE_CUDA_ARCHITECTURES or the CUDAARCHS environment
# variable names the architectures.
if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES AND "$ENV{CUDAARCHS}" STREQUAL "")
  set(CMAKE_CUDA_ARCHITECTURES native CACHE STRING "CUDA target architectures")
endif()
project(Brook VERSION 0.1.0 LANGUAGES C CXX CUDA)

if(CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 12.3)
  message(FATAL_ERROR "Brook requires CUDA Toolkit 12.3 or newer")
endif()
# Compute capability 8.0 is the minimum (warp reductions such as __reduce_min_sync are sm_80+).
# The resolved list is also compiled into the library, so the startup check can name it.
set(BROOK_CUDA_ARCHITECTURES "${CMAKE_CUDA_ARCHITECTURES}")
foreach(_brook_keyword native all all-major)
  if(BROOK_CUDA_ARCHITECTURES STREQUAL _brook_keyword)
    string(TOUPPER "${_brook_keyword}" _brook_suffix)
    string(REPLACE "-" "_" _brook_suffix "${_brook_suffix}")
    set(BROOK_CUDA_ARCHITECTURES "${CMAKE_CUDA_ARCHITECTURES_${_brook_suffix}}")
  endif()
endforeach()
set(BROOK_CUDA_SASS)
set(BROOK_CUDA_PTX)
set(_brook_too_old)
foreach(_brook_arch IN LISTS BROOK_CUDA_ARCHITECTURES)
  if(NOT _brook_arch MATCHES "^([0-9]+)([af]?)(-real|-virtual)?$")
    message(FATAL_ERROR "Brook cannot parse the CUDA architecture \"${_brook_arch}\" in "
      "CMAKE_CUDA_ARCHITECTURES=\"${CMAKE_CUDA_ARCHITECTURES}\". Use entries such as 90, 90-real or 120-virtual.")
  endif()
  if(CMAKE_MATCH_1 LESS 80)
    list(APPEND _brook_too_old "${_brook_arch}")
  endif()
  if(NOT CMAKE_MATCH_3 STREQUAL "-virtual")
    list(APPEND BROOK_CUDA_SASS "sm_${CMAKE_MATCH_1}${CMAKE_MATCH_2}")
  endif()
  if(NOT CMAKE_MATCH_3 STREQUAL "-real")
    list(APPEND BROOK_CUDA_PTX "compute_${CMAKE_MATCH_1}${CMAKE_MATCH_2}")
  endif()
endforeach()
if(NOT BROOK_CUDA_ARCHITECTURES)
  message(FATAL_ERROR "Brook found no CUDA architecture to build for (CMAKE_CUDA_ARCHITECTURES=\"${CMAKE_CUDA_ARCHITECTURES}\"). "
    "Set architectures 80 or newer explicitly, for example -DCMAKE_CUDA_ARCHITECTURES=90.")
endif()
if(_brook_too_old)
  string(JOIN ", " _brook_too_old ${_brook_too_old})
  message(FATAL_ERROR "Brook requires NVIDIA GPUs of compute capability 8.0 or newer. "
    "CMAKE_CUDA_ARCHITECTURES=\"${CMAKE_CUDA_ARCHITECTURES}\" includes ${_brook_too_old}. "
    "Use architectures 80 or newer, for example "
    "-DCMAKE_CUDA_ARCHITECTURES=\"80-real;86-real;89-real;90-real;100-real;120-real;80-virtual;120-virtual\", "
    "or native on a machine with a supported GPU.")
endif()
string(JOIN " " BROOK_CUDA_SASS ${BROOK_CUDA_SASS})
string(JOIN " " BROOK_CUDA_PTX ${BROOK_CUDA_PTX})
message(STATUS "Brook CUDA code: SASS [${BROOK_CUDA_SASS}], PTX [${BROOK_CUDA_PTX}]")
configure_file(cpp/cmake/build_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/generated/brook_build_config.h @ONLY)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CUDA_STANDARD 17)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
set(CMAKE_CUDA_RUNTIME_LIBRARY Shared)
option(BROOK_BUILD_PYTHON "Build the Brook Python extension" ON)
option(BROOK_BUILD_TESTS "Build C/C++ tests" ON)
find_package(CUDAToolkit 12.3 REQUIRED)
# Release wheels and the conda recipe name the distribution after its CUDA major version.
if(SKBUILD_PROJECT_NAME MATCHES "^brook[-_]cu([0-9]+)$")
  set(_brook_dist_major "${CMAKE_MATCH_1}")
  if(NOT _brook_dist_major STREQUAL CUDAToolkit_VERSION_MAJOR)
    message(FATAL_ERROR "${SKBUILD_PROJECT_NAME} must be built with a CUDA ${_brook_dist_major} toolkit; "
      "found ${CUDAToolkit_VERSION}")
  endif()
endif()
# libbrook uses std::call_once and may reference dlopen (CCCL's NVTX); glibc before 2.34 keeps
# these in libpthread and libdl.
find_package(Threads REQUIRED)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
# GCC/Clang spellings of "no floating-point contraction" (the device side is --fmad=false) and of
# hidden C symbols. MSVC needs neither: its default /fp:precise does not contract, and it exports
# only what is marked for export.
set(BROOK_GNU "$<NOT:$<BOOL:${MSVC}>>")
set(BROOK_NO_CONTRACT "$<${BROOK_GNU}:-ffp-contract=off>")
set(BROOK_HOST_NO_CONTRACT "$<${BROOK_GNU}:-Xcompiler=-ffp-contract=off>")

# Keep LGPL xs3d independently replaceable by SDK/wheel consumers.
add_library(brook_xs3d SHARED cpp/third_party/xs3d/bridge.cpp)
target_compile_options(brook_xs3d PRIVATE ${BROOK_NO_CONTRACT})
set_target_properties(brook_xs3d PROPERTIES CXX_VISIBILITY_PRESET hidden
  VISIBILITY_INLINES_HIDDEN ON VERSION 1.13.0 SOVERSION 1)

add_library(brook SHARED cpp/src/runtime.cpp cpp/src/device_check.cu cpp/src/layout.cu cpp/src/c_api.cpp cpp/src/dlpack.cpp cpp/src/packed.cpp cpp/src/packed_merge.cu cpp/src/post.cu cpp/src/nearest.cu
  cpp/src/ccl.cu cpp/src/edt.cu cpp/src/cooperative.cu cpp/src/trace_primitives.cu
  cpp/src/preamble.cu cpp/src/holes.cu cpp/src/avocado.cpp cpp/src/skeleton.cpp cpp/src/trace.cu cpp/src/borders.cu cpp/src/borders_batch.cu cpp/src/skeletonize.cu)
set(BROOK_MODULE_SOURCES)
target_sources(brook PRIVATE cpp/src/point_path.cpp cpp/src/connect_points.cu cpp/src/streaming.cu cpp/src/streaming_ccl.cu cpp/src/oversegment.cpp)
target_sources(brook PRIVATE cpp/src/cross_section.cpp cpp/src/public_api.cu)
target_sources(brook PRIVATE cpp/src/batch.cu)
target_sources(brook PRIVATE cpp/src/streaming_trace.cu)
set_source_files_properties(cpp/src/streaming_trace.cu PROPERTIES COMPILE_OPTIONS "--fmad=false;${BROOK_HOST_NO_CONTRACT}")
target_sources(brook PRIVATE cpp/src/cpu_post.cpp cpp/src/cpu_kdtree.c)
set_source_files_properties(cpp/src/cpu_kdtree.c PROPERTIES COMPILE_OPTIONS "${BROOK_NO_CONTRACT};$<${BROOK_GNU}:-fvisibility=hidden>;$<${BROOK_GNU}:-Wno-incompatible-pointer-types>")
set_source_files_properties(cpp/src/connect_points.cu PROPERTIES COMPILE_OPTIONS "--fmad=false;${BROOK_HOST_NO_CONTRACT}")
foreach(module batched lockstep)
  foreach(dtype u8 u16 u32 u64)
    list(APPEND BROOK_MODULE_SOURCES cpp/src/modules/${module}_${dtype}.cu)
  endforeach()
endforeach()
target_sources(brook PRIVATE cpp/src/kernel_module.cpp cpp/src/graph.cpp cpp/src/batched.cu cpp/src/assembly.cu cpp/src/component_reductions.cu cpp/src/draft_plan.cu cpp/src/draft_state.cu cpp/src/draft_batch.cpp
  cpp/src/lockstep_order.cu cpp/src/lockstep.cu cpp/src/soma.cu cpp/src/graph_components.cu cpp/src/graph_edt.cu ${BROOK_MODULE_SOURCES})
set_source_files_properties(${BROOK_MODULE_SOURCES} PROPERTIES COMPILE_OPTIONS "--fmad=false")
set_source_files_properties(cpp/src/trace_primitives.cu cpp/src/trace.cu cpp/src/borders.cu cpp/src/skeletonize.cu
  cpp/src/batched.cu cpp/src/lockstep.cu cpp/src/soma.cu cpp/src/assembly.cu cpp/src/packed_merge.cu cpp/src/post.cu cpp/src/nearest.cu cpp/src/draft_plan.cu
  PROPERTIES COMPILE_OPTIONS "--fmad=false;${BROOK_HOST_NO_CONTRACT}")
add_library(Brook::brook ALIAS brook)
target_include_directories(brook PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/cpp/include>
  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
  PRIVATE cpp/src ${CMAKE_CURRENT_BINARY_DIR}/generated)
target_link_libraries(brook PRIVATE CUDA::cudart brook_xs3d Threads::Threads ${CMAKE_DL_LIBS})
target_compile_definitions(brook PRIVATE BROOK_BUILDING_LIBRARY BROOK_VERSION="${PROJECT_VERSION}")
target_compile_options(brook PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${BROOK_NO_CONTRACT}>)
set_target_properties(brook PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION 0
  POSITION_INDEPENDENT_CODE ON INSTALL_RPATH "$ORIGIN")

if(BROOK_BUILD_PYTHON)
  set(PYBIND11_FINDPYTHON ON)
  find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
  find_package(pybind11 CONFIG REQUIRED)
  pybind11_add_module(_core cpp/python/bindings.cpp cpp/python/runtime_bindings.cpp cpp/python/intake_bindings.cpp cpp/python/oversegment.cpp cpp/python/cross_section_bindings.cpp cpp/python/scheduling_bindings.cpp cpp/python/public_bindings.cpp)
  set_source_files_properties(cpp/python/scheduling_bindings.cpp PROPERTIES COMPILE_OPTIONS "${BROOK_NO_CONTRACT}")
  target_include_directories(_core PRIVATE cpp/src)
  target_link_libraries(_core PRIVATE brook CUDA::cudart)

endif()

install(TARGETS brook EXPORT BrookTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT SDK
  ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT SDK RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT SDK)
install(TARGETS brook_xs3d EXPORT BrookTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT SDK)
install(DIRECTORY cpp/third_party/xs3d/ DESTINATION ${CMAKE_INSTALL_DATADIR}/brook/xs3d-source COMPONENT SDK)
install(DIRECTORY cpp/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT SDK)
install(DIRECTORY cpp/licenses/ DESTINATION ${CMAKE_INSTALL_DATADIR}/brook/licenses COMPONENT SDK)
install(EXPORT BrookTargets NAMESPACE Brook:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Brook COMPONENT SDK)
configure_package_config_file(cpp/cmake/BrookConfig.cmake.in
  ${CMAKE_CURRENT_BINARY_DIR}/BrookConfig.cmake
  INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Brook)
write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/BrookConfigVersion.cmake
  VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/BrookConfig.cmake
  ${CMAKE_CURRENT_BINARY_DIR}/BrookConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Brook COMPONENT SDK)
file(RELATIVE_PATH BROOK_PC_PREFIX_RELATIVE
  "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/pkgconfig" "${CMAKE_INSTALL_PREFIX}")
configure_file(cpp/cmake/brook.pc.in brook.pc @ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/brook.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT SDK)
include(cpp/cmake/Distribution.cmake)

if(BROOK_BUILD_TESTS)
  enable_testing()
  add_executable(brook_component_order_smoke cpp/tests/component_order_smoke.cpp)
  target_include_directories(brook_component_order_smoke PRIVATE cpp/src)
  target_link_libraries(brook_component_order_smoke PRIVATE Brook::brook CUDA::cudart)
  add_test(NAME brook_component_order_smoke COMMAND brook_component_order_smoke)
  add_executable(brook_streaming_trace_smoke cpp/tests/streaming_trace_smoke.cpp)
  target_link_libraries(brook_streaming_trace_smoke PRIVATE Brook::brook)
  add_test(NAME brook_streaming_trace_smoke COMMAND brook_streaming_trace_smoke)
  add_executable(brook_streaming_ccl_smoke cpp/tests/streaming_ccl_smoke.cpp)
  target_link_libraries(brook_streaming_ccl_smoke PRIVATE Brook::brook)
  add_test(NAME brook_streaming_ccl_smoke COMMAND brook_streaming_ccl_smoke)
  add_executable(brook_streaming_edt_smoke cpp/tests/streaming_edt_smoke.cpp)
  target_link_libraries(brook_streaming_edt_smoke PRIVATE Brook::brook)
  add_test(NAME brook_streaming_edt_smoke COMMAND brook_streaming_edt_smoke)
  add_executable(brook_cross_section_smoke cpp/tests/cross_section_smoke.cpp)
  target_compile_options(brook_cross_section_smoke PRIVATE -UNDEBUG)
  target_link_libraries(brook_cross_section_smoke PRIVATE Brook::brook)
  add_test(NAME brook_cross_section_smoke COMMAND brook_cross_section_smoke)
  set_tests_properties(brook_cross_section_smoke PROPERTIES ENVIRONMENT "CUDA_VISIBLE_DEVICES=")
  add_executable(brook_c_smoke cpp/tests/c_smoke.c)
  target_link_libraries(brook_c_smoke PRIVATE Brook::brook)
  add_test(NAME brook_c_smoke COMMAND brook_c_smoke)
  add_executable(brook_cpp_smoke cpp/tests/cpp_smoke.cpp)
  target_link_libraries(brook_cpp_smoke PRIVATE Brook::brook)
  add_test(NAME brook_cpp_smoke COMMAND brook_cpp_smoke)
  add_executable(brook_holes_smoke cpp/tests/holes_smoke.cpp)
  target_link_libraries(brook_holes_smoke PRIVATE Brook::brook)
  add_test(NAME brook_holes_smoke COMMAND brook_holes_smoke)
  add_executable(brook_avocados_smoke cpp/tests/avocados_smoke.cpp)
  target_link_libraries(brook_avocados_smoke PRIVATE Brook::brook)
  add_test(NAME brook_avocados_smoke COMMAND brook_avocados_smoke)
  add_executable(brook_connect_points_smoke cpp/tests/connect_points_smoke.cpp)
  target_link_libraries(brook_connect_points_smoke PRIVATE Brook::brook)
  add_test(NAME brook_connect_points_smoke COMMAND brook_connect_points_smoke)
  add_executable(brook_point_frontier_smoke cpp/tests/point_frontier_smoke.cpp)
  target_include_directories(brook_point_frontier_smoke PRIVATE cpp/src)
  target_link_libraries(brook_point_frontier_smoke PRIVATE Brook::brook CUDA::cudart)
  add_test(NAME brook_point_frontier_smoke COMMAND brook_point_frontier_smoke)
  add_test(NAME brook_point_frontier_max_grid COMMAND brook_point_frontier_smoke)
  set_tests_properties(brook_point_frontier_max_grid PROPERTIES ENVIRONMENT "BROOK_FLOOD_GRID=0")
  add_executable(brook_oversegment_smoke cpp/tests/oversegment_smoke.cpp)
  target_link_libraries(brook_oversegment_smoke PRIVATE Brook::brook)
  add_test(NAME brook_oversegment_smoke COMMAND brook_oversegment_smoke)
  add_executable(brook_cpu_post_smoke cpp/tests/cpu_post_smoke.cpp)
  target_link_libraries(brook_cpu_post_smoke PRIVATE Brook::brook)
  add_test(NAME brook_cpu_post_smoke COMMAND brook_cpu_post_smoke)
  set_tests_properties(brook_cpu_post_smoke PROPERTIES ENVIRONMENT "CUDA_VISIBLE_DEVICES=")
  add_executable(brook_void_reuse_smoke cpp/tests/void_reuse_smoke.cpp)
  target_include_directories(brook_void_reuse_smoke PRIVATE cpp/src)
  target_link_libraries(brook_void_reuse_smoke PRIVATE Brook::brook CUDA::cudart)
  add_test(NAME brook_void_reuse_smoke COMMAND brook_void_reuse_smoke)
  add_executable(brook_host_smoke cpp/tests/host_smoke.cpp)
  target_link_libraries(brook_host_smoke PRIVATE Brook::brook)
  add_test(NAME brook_host_smoke COMMAND brook_host_smoke)
  set_tests_properties(brook_host_smoke PROPERTIES ENVIRONMENT "CUDA_VISIBLE_DEVICES=")
  add_executable(brook_draft_state_smoke cpp/tests/draft_state_smoke.cpp)
  target_include_directories(brook_draft_state_smoke PRIVATE cpp/src)
  target_link_libraries(brook_draft_state_smoke PRIVATE Brook::brook CUDA::cudart)
  add_test(NAME brook_draft_state_smoke COMMAND brook_draft_state_smoke)
  add_executable(brook_pdrf_fusion_smoke cpp/tests/pdrf_fusion_smoke.cu)
  set_source_files_properties(cpp/tests/pdrf_fusion_smoke.cu PROPERTIES COMPILE_OPTIONS "--fmad=false;${BROOK_HOST_NO_CONTRACT}")
  target_include_directories(brook_pdrf_fusion_smoke PRIVATE cpp/src)
  target_link_libraries(brook_pdrf_fusion_smoke PRIVATE Brook::brook CUDA::cudart)
  add_test(NAME brook_pdrf_fusion_smoke COMMAND brook_pdrf_fusion_smoke)
  add_executable(brook_graph_smoke cpp/tests/graph_smoke.cu)
  target_include_directories(brook_graph_smoke PRIVATE cpp/src)
  target_link_libraries(brook_graph_smoke PRIVATE Brook::brook CUDA::cudart)
  add_test(NAME brook_graph_smoke COMMAND brook_graph_smoke)
  add_executable(brook_device_check_smoke cpp/tests/device_check_smoke.cpp)
  target_include_directories(brook_device_check_smoke PRIVATE cpp/src)
  target_link_libraries(brook_device_check_smoke PRIVATE Brook::brook CUDA::cudart)
  add_test(NAME brook_device_check_smoke COMMAND brook_device_check_smoke)
  add_test(NAME brook_device_check_no_device COMMAND brook_device_check_smoke)
  set_tests_properties(brook_device_check_no_device PROPERTIES ENVIRONMENT "CUDA_VISIBLE_DEVICES=")
  # Tests that need no GPU or driver: `ctest -L cpu` (CI runs these).
  set_tests_properties(brook_host_smoke brook_cross_section_smoke brook_cpu_post_smoke
    brook_device_check_no_device PROPERTIES LABELS cpu)
endif()
