# cuSTC -- CUDA-accelerated Syndrome-Trellis Codes
#
# Builds libcustc (shared + static) and, optionally, the Python module.
#
#   cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
#   cmake --build build -j
#
# The default CUDA architectures cover Turing..Hopper as a fat binary; for a
# faster development build target just your GPU, e.g.
#   cmake -S . -B build -DCMAKE_CUDA_ARCHITECTURES=75
cmake_minimum_required(VERSION 3.24)

# Must be set BEFORE project(): enabling CUDA there writes the detected
# default arch into the cache, which would defeat an if(NOT ...) guard below.
if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES OR CMAKE_CUDA_ARCHITECTURES STREQUAL "")
  # real SASS for each major consumer arch + PTX from the newest ("90" without
  # -real keeps PTX embedded) for forward compatibility
  set(CMAKE_CUDA_ARCHITECTURES "75-real;80-real;86-real;89-real;90" CACHE STRING
      "CUDA architectures")
endif()

project(custc VERSION 0.1.0 LANGUAGES CXX CUDA)

if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

option(CUSTC_BUILD_PYTHON "Build the custc Python extension module" OFF)
if(SKBUILD)  # building via pip / scikit-build-core
  set(CUSTC_BUILD_PYTHON ON)
endif()

find_package(CUDAToolkit REQUIRED)
find_package(OpenMP)

# One object collection, PIC, reused by the shared lib, the static lib and the
# Python module.
add_library(custc_objs OBJECT
  ml_stc_src/stc_cuda.cu
  ml_stc_src/stc_ml_cuda.cpp
  ml_stc_src/stc_embed_c.cpp
  ml_stc_src/stc_extract_c.cpp
  ml_stc_src/stc_ml_c.cpp
  ml_stc_src/common.cpp
)
set_target_properties(custc_objs PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_include_directories(custc_objs PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/ml_stc_src>
  $<INSTALL_INTERFACE:include/custc>
)
if(OpenMP_CXX_FOUND)
  target_compile_options(custc_objs PRIVATE
    $<$<COMPILE_LANGUAGE:CXX>:${OpenMP_CXX_FLAGS}>)
endif()

set(CUSTC_PUBLIC_HEADERS
  ml_stc_src/stc_cuda.cuh
  ml_stc_src/stc_ml_cuda.h
  ml_stc_src/stc_ml_c.h
  ml_stc_src/stc_embed_c.h
  ml_stc_src/stc_extract_c.h
  ml_stc_src/common.h
)

add_library(custc SHARED $<TARGET_OBJECTS:custc_objs>)
add_library(custc_static STATIC $<TARGET_OBJECTS:custc_objs>)
set_target_properties(custc PROPERTIES
  VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}
  PUBLIC_HEADER "${CUSTC_PUBLIC_HEADERS}")
set_target_properties(custc_static PROPERTIES OUTPUT_NAME custc)
foreach(tgt custc custc_static)
  target_include_directories(${tgt} INTERFACE
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/ml_stc_src>
    $<INSTALL_INTERFACE:include/custc>)
  target_link_libraries(${tgt} PUBLIC CUDA::cudart)
  if(OpenMP_CXX_FOUND)
    target_link_libraries(${tgt} PUBLIC OpenMP::OpenMP_CXX)
  endif()
endforeach()

if(NOT SKBUILD)  # a pip build should not install the C library into the wheel
  include(GNUInstallDirs)
  install(TARGETS custc custc_static
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/custc)
endif()

# ---- Python bindings --------------------------------------------------------
if(CUSTC_BUILD_PYTHON)
  find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
  find_package(pybind11 CONFIG REQUIRED)
  pybind11_add_module(_core python/bindings.cpp)
  # static cudart so the installed wheel does not need libcudart.so on the
  # library path (the driver is still required at runtime)
  if(TARGET CUDA::cudart_static)
    target_link_libraries(_core PRIVATE custc_objs CUDA::cudart_static)
  else()
    target_link_libraries(_core PRIVATE custc_objs CUDA::cudart)
  endif()
  if(OpenMP_CXX_FOUND)
    target_link_libraries(_core PRIVATE OpenMP::OpenMP_CXX)
  endif()
  install(TARGETS _core LIBRARY DESTINATION custc)
endif()
