# Minimum CMake that we require is 3.18.
# Some of the recent features we use:
#  * --ignore-eol when comparing unit test results with expected outcomes (3.14)
#  * CROSSCOMPILING_EMULATOR can be a semicolon-separated list to pass arguments (3.15)
#  * SKIP_REGULAR_EXPRESSION to handle skipped tests properly (3.16)
#  * CheckLinkerFlag for HAVE_NEW_DTAGS test (3.18)
#  * cmake -E cat (3.18)
cmake_minimum_required(VERSION 3.18...3.31)

# CMake 3.31.0 issues warnings due to the following bug:
# https://gitlab.kitware.com/cmake/cmake/-/issues/26449
# Setting policy CMP0175 to OLD avoids the warnings.
if(CMAKE_VERSION VERSION_EQUAL "3.31.0")
  cmake_policy(SET CMP0175 OLD)
endif()

# Add etc/cmake to CMake's search path so we can put our private stuff there
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/etc/cmake)

# Set a default build type if none was specified
# This must precede the project() line, which would set the CMAKE_BUILD_TYPE
# to 'Debug' with single-config generators on Windows.
# Note that we must do this only if PROJECT_NAME is not set at this point. If
# it is set, it means that igraph is being used as a subproject of another
# project.
if(NOT PROJECT_NAME)
  include(BuildType)
endif()

# Prevent in-source builds
include(PreventInSourceBuilds)

# Make use of ccache if it is present on the host system -- unless explicitly
# asked to disable it
include(UseCCacheWhenInstalled)

# Figure out the version number from Git
include(version)

# Declare the project, its version number and language
project(
  igraph
  VERSION ${PACKAGE_VERSION_BASE}
  DESCRIPTION "A library for creating and manipulating graphs"
  HOMEPAGE_URL https://igraph.org
  LANGUAGES C CXX
)

# Include some compiler-related helpers and set global compiler options
include(compilers)

# Detect is certain attributes are supported by the compiler
include(attribute_support)

# Set default symbol visibility to hidden
set(CMAKE_C_VISIBILITY_PRESET hidden)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)

# Set C and C++ standard version
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED True)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# Expose the BUILD_SHARED_LIBS option in the ccmake UI
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)

# Add switches to use sanitizers and debugging helpers if needed
include(debugging)
include(sanitizers)

# Enable fuzzer instrumentation if needed
# FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION is a conventional
# macro used to adapt code for fuzzability, for example by
# reducing largest allowed graph sizes when reading various
# file formats.
if(BUILD_FUZZING)
  add_compile_options(-fsanitize=fuzzer-no-link)
  add_compile_definitions(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
endif()

# Add version information
configure_file(
  ${CMAKE_CURRENT_SOURCE_DIR}/include/igraph_version.h.in
  ${CMAKE_CURRENT_BINARY_DIR}/include/igraph_version.h
)

# Create configuration options for optional features
include(features)

# Handle dependencies and dependency-related configuration options
include(dependencies)
find_dependencies()

# Run compile-time checks, generate config.h and igraph_threading.h
include(CheckSymbolExists)
include(CheckIncludeFiles)
include(CMakePushCheckState)

# First we check for some functions and symbols
cmake_push_check_state()
if(NEED_LINKING_AGAINST_LIBM)
  list(APPEND CMAKE_REQUIRED_LIBRARIES m)
endif()
check_symbol_exists(strcasecmp strings.h HAVE_STRCASECMP)
check_symbol_exists(strncasecmp strings.h HAVE_STRNCASECMP)
check_symbol_exists(_stricmp string.h HAVE__STRICMP)
check_symbol_exists(_strnicmp string.h HAVE__STRNICMP)
check_symbol_exists(strdup string.h HAVE_STRDUP)
check_symbol_exists(strndup string.h HAVE_STRNDUP)
check_include_files(xlocale.h HAVE_XLOCALE)
if(HAVE_XLOCALE)
  # On BSD, uselocale() is in xlocale.h instead of locale.h.
  # Some systems provide xlocale.h, but uselocale() is still in locale.h,
  # thus we try both.
  check_symbol_exists(uselocale "xlocale.h;locale.h" HAVE_USELOCALE)
else()
  check_symbol_exists(uselocale locale.h HAVE_USELOCALE)
endif()
check_symbol_exists(_configthreadlocale locale.h HAVE__CONFIGTHREADLOCALE)
cmake_pop_check_state()

# Check for 128-bit integer multiplication support, floating-point endianness,
# support for built-in overflow detection and fast bit operation support.
include(ieee754_endianness)
include(uint128_support)
include(bit_operations_support)
include(safe_math_support)

if(NOT HAVE_USELOCALE AND NOT HAVE__CONFIGTHREADLOCALE)
  message(WARNING "igraph cannot set per-thread locale on this platform. igraph_enter_safelocale() and igraph_exit_safelocale() will not be safe to use in multithreaded programs.")
endif()

# Check for code coverage support
option(IGRAPH_ENABLE_CODE_COVERAGE "Enable code coverage calculation" OFF)
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND IGRAPH_ENABLE_CODE_COVERAGE)
  include(CodeCoverage)
  append_coverage_compiler_flags()
  setup_target_for_coverage_lcov(
    NAME coverage
    EXECUTABLE "${CMAKE_COMMAND}" "--build" "${PROJECT_BINARY_DIR}" "--target" "check"
    # The base directory is changed to allow finding the generated parser sources.
    # The following works with 'Ninja'. For 'Unix Makefiles', this requires ${PROJECT_BINARY_DIR}/src.
    BASE_DIRECTORY "${PROJECT_BINARY_DIR}"
    # /Applications and /Library/Developer are for macOS -- they exclude files from the macOS SDK.
    EXCLUDE "/Applications/Xcode*" "/Library/Developer/*" "examples/*" "interfaces/*" "tests/*" "vendor/pcg/*"
    # These errors, present with lcov 2.x, are likely due to incompatibility with llvm-cov on macOS.
    LCOV_ARGS --ignore-errors inconsistent,format,unused
    GENHTML_ARGS --ignore-errors inconsistent,corrupt,category,unmapped
  )
endif()

# Generate configuration headers
configure_file(
  ${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in
  ${CMAKE_CURRENT_BINARY_DIR}/src/config.h
)
configure_file(
  ${CMAKE_CURRENT_SOURCE_DIR}/include/igraph_config.h.in
  ${CMAKE_CURRENT_BINARY_DIR}/include/igraph_config.h
)
configure_file(
  ${CMAKE_CURRENT_SOURCE_DIR}/include/igraph_threading.h.in
  ${CMAKE_CURRENT_BINARY_DIR}/include/igraph_threading.h
)

# Enable unit tests. Behave nicely and do this only if we are not being
# included as a sub-project in another CMake project
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
  include(CTest)
endif()

# Traverse subdirectories. vendor/ should come first because code in
# src/CMakeLists.txt depends on targets in vendor/
add_subdirectory(vendor)
add_subdirectory(src)
add_subdirectory(interfaces)
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
  add_subdirectory(tests)
endif()
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_FUZZING)
  add_subdirectory(fuzzing)
endif()
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
  add_subdirectory(doc)
endif()

# Configure packaging -- only if igraph is the top-level project and not a
# subproject
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
  include(packaging)
endif()

# Show result of configuration
include(summary)
