cmake_minimum_required(VERSION 3.27 FATAL_ERROR)
project(c10 CXX)

set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard whose features are requested to build this target.")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Main build file for the C10 library.
#
# Note that the C10 library should maintain minimal dependencies - especially,
# it should not depend on any library that is implementation specific or
# backend specific. It should in particular NOT be dependent on any generated
# protobuf header files, because protobuf header files will transitively force
# one to link against a specific protobuf version.

if(BUILD_LIBTORCHLESS)
  find_library(C10_LIB c10 PATHS $ENV{LIBTORCH_LIB_PATH} NO_DEFAULT_PATH)
else()
  set(C10_LIB c10)
endif()

set(C10_USE_GFLAGS ${USE_GFLAGS})  # also used in torch/headeronly
set(C10_USE_GLOG ${USE_GLOG})  # also used in torch/headeronly
set(C10_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})  # also used in torch/headeronly
set(C10_USE_NUMA ${USE_NUMA})  # also used in torch/headeronly
set(C10_USE_MSVC_STATIC_RUNTIME ${CAFFE2_USE_MSVC_STATIC_RUNTIME})  # also used in torch/headeronly
set(C10_USE_ROCM_KERNEL_ASSERT ${USE_ROCM_KERNEL_ASSERT})  # also used in torch/headeronly

  # Note: if you want to add ANY dependency to the c10 library, make sure you
  # check with the core PyTorch developers as the dependency will be
  # transitively passed on to all libraries dependent on PyTorch.
  file(GLOB C10_SRCS CONFIGURE_DEPENDS
          *.cpp
          core/*.cpp
          core/impl/*.cpp
          mobile/*.cpp
          macros/*.cpp
          util/*.cpp
        )
  file(GLOB C10_HEADERS CONFIGURE_DEPENDS
          *.h
          core/*.h
          core/impl/*.h
          mobile/*.h
          macros/*.h
          util/*.h
        )
if(NOT BUILD_LIBTORCHLESS)
  add_library(c10 ${C10_SRCS} ${C10_HEADERS})
  torch_compile_options(c10)
  torch_optimize_layout_if_enabled(c10)
  if(HAVE_SOVERSION)
    set_target_properties(c10 PROPERTIES
        VERSION ${TORCH_VERSION} SOVERSION ${TORCH_SOVERSION})
  endif()
  # If building shared library, set dllimport/dllexport proper.
  target_compile_definitions(c10 PRIVATE C10_BUILD_MAIN_LIB)
  # Enable hidden visibility if compiler supports it.
  if(${COMPILER_SUPPORTS_HIDDEN_VISIBILITY})
    target_compile_options(c10 PRIVATE "-fvisibility=hidden")
  endif()

  option(C10_USE_IWYU "Use include-what-you-use to clean up header inclusion" OFF)
  if(C10_USE_IWYU)
    find_program(iwyu NAMES include-what-you-use)
    if(iwyu)
      set(iwyu_cmd
          "include-what-you-use"
          "-Xiwyu"
          "--transitive_includes_only"
          "-Xiwyu"
          "--no_fwd_decls"
          "-Xiwyu"
          "--prefix_header_includes=keep"
          "-Xiwyu"
          "--mapping_file=${CMAKE_CURRENT_LIST_DIR}/../tools/iwyu/all.imp"
        )
      set_property(TARGET c10 PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${iwyu_cmd})
    endif()
  endif()

  if(WERROR)
    target_compile_options_if_supported(c10 "-Werror=sign-compare")
    target_compile_options_if_supported(c10 "-Werror=shadow")
  endif()

  # ---[ Dependency of c10
  if(C10_USE_GFLAGS)
    target_link_libraries(c10 PUBLIC gflags)
  endif()

  if(C10_USE_GLOG)
    target_link_libraries(c10 PUBLIC glog::glog)
  endif()

  target_link_libraries(c10 PUBLIC headeronly)
  target_link_libraries(c10 PRIVATE fmt::fmt-header-only)
  target_link_libraries(c10 PRIVATE nlohmann)
  target_link_libraries(c10 PRIVATE moodycamel)

  if(C10_USE_NUMA)
    message(STATUS "NUMA paths:")
    message(STATUS ${Numa_INCLUDE_DIR})
    message(STATUS ${Numa_LIBRARIES})
    target_include_directories(c10 PRIVATE ${Numa_INCLUDE_DIR})
    target_link_libraries(c10 PRIVATE ${Numa_LIBRARIES})
    # libnuma is a PRIVATE impl detail of c10, but libc10.so's DT_NEEDED
    # libnuma.so.1 still has to resolve at link time when in-tree targets
    # (test executables, extensions) link c10. The conda compat linker has a
    # narrow sysroot view, so propagate libnuma's directory as an INTERFACE
    # rpath-link entry. Wrapped in BUILD_INTERFACE so this build-machine path
    # is not baked into the installed Caffe2Targets.cmake export (it is
    # meaningless to downstream find_package(Torch) consumers). ELF/GNU-ld only.
    #
    # Skip the entry entirely when libnuma lives in a directory the toolchain
    # already searches implicitly (the common distro case,
    # e.g. /usr/lib/x86_64-linux-gnu). There it buys nothing -- ld falls back to
    # its default directories for DT_NEEDED resolution anyway -- but it does
    # real harm: GNU ld probes every -rpath-link directory, in command-line
    # order, *before* -rpath and before its default directories. Naming a
    # system directory here therefore hoists it to the front of the dependency
    # search order, so a stale system copy of some *other* library shadows the
    # intended one from a self-contained toolchain. Concretely, an apt-installed
    # /usr/lib/x86_64-linux-gnu/libhsa-runtime64.so.1 would win over the ROCm
    # SDK's own copy when resolving libamdhip64.so's DT_NEEDED, breaking every
    # HIP-linking target with undefined hsa_* references. Only the conda-style
    # narrow-sysroot case (libnuma outside the implicit dirs) needs the flag.
    if(NOT WIN32 AND NOT APPLE)
      get_filename_component(_numa_dir "${Numa_LIBRARIES}" DIRECTORY)
      get_filename_component(_numa_dir "${_numa_dir}" REALPATH)
      set(_numa_dir_is_implicit FALSE)
      foreach(_implicit_dir IN LISTS CMAKE_C_IMPLICIT_LINK_DIRECTORIES
                                     CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES)
        get_filename_component(_implicit_dir "${_implicit_dir}" REALPATH)
        if(_numa_dir STREQUAL _implicit_dir)
          set(_numa_dir_is_implicit TRUE)
          break()
        endif()
      endforeach()
      if(_numa_dir_is_implicit)
        message(STATUS
          "Not adding -rpath-link for ${_numa_dir}: already an implicit "
          "linker search directory")
      else()
        target_link_options(
          c10 INTERFACE "$<BUILD_INTERFACE:LINKER:-rpath-link,${_numa_dir}>")
      endif()
    endif()
  else()
    message(STATUS "don't use NUMA")
  endif()

  if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(s390x|ppc64le)$")
    target_link_libraries(c10 INTERFACE "$<BUILD_INTERFACE:cpuinfo>")
    target_link_libraries(c10 PRIVATE "$<LINK_LIBRARY:WHOLE_ARCHIVE,cpuinfo>")
  endif()

  find_package(Backtrace)
  if(Backtrace_FOUND)
    target_include_directories(c10 PRIVATE ${Backtrace_INCLUDE_DIRS})
    target_link_libraries(c10 PRIVATE ${Backtrace_LIBRARIES})
    target_compile_definitions(c10 PRIVATE SUPPORTS_BACKTRACE=1)
  else()
    target_compile_definitions(c10 PRIVATE SUPPORTS_BACKTRACE=0)
  endif()

  if(USE_MIMALLOC)
    target_link_libraries(c10 PRIVATE "mimalloc-static")
    add_dependencies(c10 mimalloc-static)
  endif()

  if(LINUX)
    target_link_libraries(c10 PRIVATE Threads::Threads)
    target_link_libraries(c10 PRIVATE dl)
  endif()

  if(ANDROID)
    target_link_libraries(c10 PRIVATE log)
  endif()

  target_include_directories(
      c10 PUBLIC
      $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../>
      $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>
      $<INSTALL_INTERFACE:include>)
endif()

add_subdirectory(test)
add_subdirectory(benchmark)

if(USE_CUDA)
  add_subdirectory(cuda)
endif()

if(USE_ROCM)
  # NB: This directory is generated by the HIPIFY script; it's
  # not checked in
  add_subdirectory(hip)
endif()

if(USE_XPU)
  add_subdirectory(xpu)
endif()

if(NOT BUILD_LIBTORCHLESS)
  # ---[ Installation
  # Note: for now, we will put all export path into one single Caffe2Targets group
  # to deal with the cmake deployment need. Inside the Caffe2Targets set, the
  # individual libraries like libc10.so and libcaffe2.so are still self-contained.
  install(TARGETS c10 EXPORT Caffe2Targets DESTINATION lib)
endif()

install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
        DESTINATION include
        FILES_MATCHING PATTERN "*.h")

if(MSVC AND C10_BUILD_SHARED_LIBS)
  install(FILES $<TARGET_PDB_FILE:c10> DESTINATION lib OPTIONAL)
endif()
