# kraken-indexer: the native indexer of a source library (ADR-0010, D-98).
cmake_minimum_required(VERSION 3.28)
project(kraken_indexer LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# A position-independent executable, its libraries built to match (ASLR;
# the distributions' package checks require it).
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
include(CheckPIESupported)
check_pie_supported()

if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  message(FATAL_ERROR "kraken-indexer is built with clang (ADR-0010)")
endif()

option(KRAKEN_INDEXER_TESTS "Build the unit tests" ON)
option(KRAKEN_INDEXER_FUZZ "Build the libFuzzer targets" OFF)
option(KRAKEN_INDEXER_STATIC "Link everything but glibc statically (release)" OFF)
option(KRAKEN_INDEXER_FAULTS "Build the crash points (tests only, ADR-0007)" OFF)
set(KRAKEN_SANITIZE "" CACHE STRING "Sanitizers: '', 'address,undefined' or 'thread'")

# The version and protocol range come from the Python side, so the banner
# says what the node says; a packaged build passes its stamped version.
file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/../../kraken/common/version.py _ver
  REGEX "^(__version__|PROTOCOL_VERSION_MIN|PROTOCOL_VERSION_MAX) =")
string(REGEX MATCH "__version__ = \"([^\"]+)\"" _m "${_ver}")
set(KRAKEN_VERSION "${CMAKE_MATCH_1}" CACHE STRING "The version the banner shows")
string(REGEX MATCH "PROTOCOL_VERSION_MIN = ([0-9]+)" _m "${_ver}")
set(KRAKEN_PROTOCOL_MIN ${CMAKE_MATCH_1})
string(REGEX MATCH "PROTOCOL_VERSION_MAX = ([0-9]+)" _m "${_ver}")
set(KRAKEN_PROTOCOL_MAX ${CMAKE_MATCH_1})

if(KRAKEN_SANITIZE)
  add_compile_options(-fsanitize=${KRAKEN_SANITIZE} -fno-omit-frame-pointer -fno-sanitize-recover=all)
  add_link_options(-fsanitize=${KRAKEN_SANITIZE})
endif()

include(cmake/deps.cmake)

set(KRAKEN_WARNINGS -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wno-sign-conversion
  -Wnon-virtual-dtor -Wold-style-cast -Wimplicit-fallthrough -Werror)

add_library(kraken_indexer_core STATIC
  src/banner.cpp
  src/db.cpp
  src/engine.cpp
  src/faults.cpp
  src/fmt.cpp
  src/hasher.cpp
  src/hashing.cpp
  src/ignore.cpp
  src/log.cpp
  src/metadata.cpp
  src/pool.cpp
  src/pipe.cpp
  src/progress.cpp
  src/pyjson.cpp
  src/rootguard.cpp
  src/store.cpp
  src/tuner.cpp
  src/walk.cpp
  src/inotify.cpp)
target_include_directories(kraken_indexer_core PUBLIC src)
target_compile_options(kraken_indexer_core PRIVATE ${KRAKEN_WARNINGS})
target_compile_definitions(kraken_indexer_core PUBLIC
  KRAKEN_VERSION="${KRAKEN_VERSION}"
  KRAKEN_PROTOCOL_MIN=${KRAKEN_PROTOCOL_MIN}
  KRAKEN_PROTOCOL_MAX=${KRAKEN_PROTOCOL_MAX}
  BOOST_ASIO_NO_DEPRECATED
  $<$<BOOL:${KRAKEN_INDEXER_FAULTS}>:KRAKEN_FAULTS>)
target_link_libraries(kraken_indexer_core PUBLIC
  kraken_sqlite blake3 Boost::asio Boost::json Boost::program_options)

add_executable(kraken-indexer src/main.cpp)
target_compile_options(kraken-indexer PRIVATE ${KRAKEN_WARNINGS})
target_link_libraries(kraken-indexer PRIVATE kraken_indexer_core)
if(KRAKEN_INDEXER_STATIC)
  # Everything but glibc in the binary (ADR-0010 §5): glibc 2.34 is the floor.
  target_link_options(kraken-indexer PRIVATE -static-libstdc++ -static-libgcc)
  set_property(TARGET kraken-indexer PROPERTY INTERPROCEDURAL_OPTIMIZATION ON)
endif()

if(KRAKEN_INDEXER_TESTS)
  enable_testing()
  include(Catch)
  add_executable(kraken-indexer-tests
    tests/test_banner.cpp
    tests/test_hashing.cpp
    tests/test_ignore.cpp
    tests/test_log.cpp
    tests/test_metadata.cpp
    tests/test_pool.cpp
    tests/test_rootguard.cpp
    tests/test_tuner.cpp
    tests/test_pipe.cpp)
  target_compile_options(kraken-indexer-tests PRIVATE ${KRAKEN_WARNINGS})
  target_link_libraries(kraken-indexer-tests PRIVATE kraken_indexer_core Catch2::Catch2WithMain)
  # The vectors the Python side writes (scripts/gen-native-vectors.py).
  target_compile_definitions(kraken-indexer-tests PRIVATE
    KRAKEN_VECTORS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/tests/vectors")
  catch_discover_tests(kraken-indexer-tests)
endif()

if(KRAKEN_INDEXER_FUZZ)
  foreach(target pipe ignore)
    add_executable(fuzz-${target} fuzz/fuzz_${target}.cpp)
    target_compile_options(fuzz-${target} PRIVATE -fsanitize=fuzzer,address,undefined)
    target_link_options(fuzz-${target} PRIVATE -fsanitize=fuzzer,address,undefined)
    target_link_libraries(fuzz-${target} PRIVATE kraken_indexer_core)
  endforeach()
endif()
