cmake_minimum_required(VERSION 3.15...3.27)

# ---------------------------------------------------------------------------
# Read the project version from nanobind.h
# ---------------------------------------------------------------------------

file(STRINGS "include/nanobind/nanobind.h" _nanobind_h_version REGEX "^#define NB_VERSION_.*$")
string(REGEX MATCH "#define NB_VERSION_MAJOR ([0-9]+)" _ "${_nanobind_h_version}")
set(_major ${CMAKE_MATCH_1})
string(REGEX MATCH "#define NB_VERSION_MINOR ([0-9]+)" _ "${_nanobind_h_version}")
set(_minor ${CMAKE_MATCH_1})
string(REGEX MATCH "#define NB_VERSION_PATCH ([0-9]+)" _ "${_nanobind_h_version}")
set(_patch ${CMAKE_MATCH_1})

project(nanobind LANGUAGES NONE VERSION "${_major}.${_minor}.${_patch}")

# ---------------------------------------------------------------------------
# Only build tests by default if this is the top-level CMake project
# ---------------------------------------------------------------------------

if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
  set(NB_MASTER_PROJECT ON)
else()
  set(NB_MASTER_PROJECT OFF)
endif()

option(NB_CREATE_INSTALL_RULES "Create installation rules" ${NB_MASTER_PROJECT})
option(NB_USE_SUBMODULE_DEPS   "Use the nanobind dependencies shipped as a git submodule of this repository" ON)

option(NB_TEST               "Compile nanobind tests?" ${NB_MASTER_PROJECT})
option(NB_TEST_STABLE_ABI    "Test the stable ABI interface?" OFF)
option(NB_TEST_SHARED_BUILD  "Build a shared nanobind library for the test suite?" OFF)
option(NB_TEST_CUDA          "Force the use of the CUDA/NVCC compiler for testing purposes" OFF)
option(NB_TEST_FREE_THREADED "Build free-threaded extensions for the test suite?" ON)

if (NOT MSVC)
  option(NB_TEST_SANITIZERS_ASAN  "Build tests with the address sanitizer?" OFF)
  option(NB_TEST_SANITIZERS_UBSAN "Build tests with the address undefined behavior sanitizer?" OFF)
  option(NB_TEST_SANITIZERS_TSAN  "Build tests with the thread sanitizer?" OFF)
endif()

# ---------------------------------------------------------------------------
# Do a release build if nothing was specified
# ---------------------------------------------------------------------------

if (NB_MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  message(STATUS "nanobind: setting build type to 'Release' as none was specified.")
  set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
    "MinSizeRel" "RelWithDebInfo")
endif()

# ---------------------------------------------------------------------------
# Check whether all dependencies are present
# ---------------------------------------------------------------------------

if (NB_USE_SUBMODULE_DEPS AND NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ext/robin_map/include")
  message(FATAL_ERROR "The nanobind dependencies are missing! "
    "You probably did not clone the project with --recursive. It is possible to recover "
    "by invoking\n$ git submodule update --init --recursive")
endif()

# ---------------------------------------------------------------------------
# Installation rules
# ---------------------------------------------------------------------------
if(NB_CREATE_INSTALL_RULES AND NOT CMAKE_SKIP_INSTALL_RULES)
  # Silence warning in GNUInstallDirs due to no enabled languages
  set(CMAKE_INSTALL_LIBDIR "")
  include(GNUInstallDirs)
  set(NB_INSTALL_DATADIR "nanobind"
    CACHE PATH "Installation path for read-only architecture-independent nanobind data files")

  # Normally these would be configurable by the user, but we can't allow that
  # because the lookup paths are hard-coded in 'cmake/nanobind-config.cmake'
  set(NB_INSTALL_INC_DIR "${NB_INSTALL_DATADIR}/include")
  set(NB_INSTALL_SRC_DIR "${NB_INSTALL_DATADIR}/src")
  set(NB_INSTALL_EXT_DIR "${NB_INSTALL_DATADIR}/ext")
  set(NB_INSTALL_MODULE_DIR "${NB_INSTALL_DATADIR}")
  set(NB_INSTALL_CMAKE_DIR "${NB_INSTALL_DATADIR}/cmake")

  install(
    DIRECTORY include/
    DESTINATION "${NB_INSTALL_INC_DIR}"
  )

  install(
    DIRECTORY src/
    DESTINATION "${NB_INSTALL_SRC_DIR}"
    PATTERN "*.py" EXCLUDE
  )

  install(
    DIRECTORY src/
    DESTINATION "${NB_INSTALL_MODULE_DIR}"
    FILES_MATCHING PATTERN "*\.py"
    PATTERN "version.py" EXCLUDE
  )

  if(NB_USE_SUBMODULE_DEPS)
    install(
      FILES ext/robin_map/include/tsl/robin_map.h
            ext/robin_map/include/tsl/robin_hash.h
            ext/robin_map/include/tsl/robin_growth_policy.h
      DESTINATION "${NB_INSTALL_EXT_DIR}/robin_map/include/tsl"
    )
  endif()

  install(
    FILES cmake/nanobind-config.cmake
          cmake/darwin-python-path.py
          cmake/darwin-ld-cpython.sym
          cmake/darwin-ld-pypy.sym
    DESTINATION "${NB_INSTALL_CMAKE_DIR}"
  )

  include(CMakePackageConfigHelpers)
  write_basic_package_version_file(
    ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake
    COMPATIBILITY SameMajorVersion
  )
  install(
    FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake
    DESTINATION "${NB_INSTALL_CMAKE_DIR}"
  )
endif()

# Return early to skip finding needless dependencies if the user only wishes to
# install nanobind
if (NB_MASTER_PROJECT AND NOT NB_TEST)
  return()
else()
  enable_language(CXX)
endif()

# ---------------------------------------------------------------------------
# Find the Python interpreter and development libraries
# ---------------------------------------------------------------------------

if (NOT TARGET Python::Module OR NOT TARGET Python::Interpreter)
  set(Python_FIND_FRAMEWORK LAST) # Prefer Brew/Conda to Apple framework python

  if (CMAKE_VERSION VERSION_LESS 3.18)
    set(NB_PYTHON_DEV_MODULE Development)
  else()
    set(NB_PYTHON_DEV_MODULE Development.Module)
  endif()

  find_package(Python 3.8
    REQUIRED COMPONENTS Interpreter ${NB_PYTHON_DEV_MODULE}
    OPTIONAL_COMPONENTS Development.SABIModule)
endif()

# ---------------------------------------------------------------------------
# Include nanobind cmake functionality
# ---------------------------------------------------------------------------
include(cmake/nanobind-config.cmake)

if (NB_TEST)
  add_subdirectory(tests)
endif()
