cmake_minimum_required(VERSION 3.15.0)
project(viriatum)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/lib)

add_definitions(-DNO_PRAGMA_LIB)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)

option(VIRIATUM_DEBUG "Enable debug logging and diagnostics" OFF)
if(VIRIATUM_DEBUG OR CMAKE_BUILD_TYPE STREQUAL "Debug")
    add_definitions(-DHAVE_DEBUG)
    message(STATUS "Viriatum: debug mode enabled")
endif()

# instruments every target with the coverage counters of the current
# compiler, the resulting profiles are gathered by scripts/coverage.sh
option(VIRIATUM_COVERAGE "Build with coverage instrumentation" OFF)
if(VIRIATUM_COVERAGE)
    if(CMAKE_C_COMPILER_ID MATCHES "Clang")
        add_compile_options(-fprofile-instr-generate -fcoverage-mapping)
        add_link_options(-fprofile-instr-generate -fcoverage-mapping)
    elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU")
        add_compile_options(--coverage)
        add_link_options(--coverage)
    else()
        message(FATAL_ERROR "Viriatum: coverage is not supported for ${CMAKE_C_COMPILER_ID}")
    endif()
    message(STATUS "Viriatum: coverage instrumentation enabled")
endif()

set(VIRIATUM_WWW_ROOT "" CACHE STRING "Path to the www root directory (sets WITH_WWW_ROOT)")
if(VIRIATUM_WWW_ROOT)
    add_definitions(-DWITH_WWW_ROOT="${VIRIATUM_WWW_ROOT}")
    message(STATUS "Viriatum: www root set to '${VIRIATUM_WWW_ROOT}'")
endif()

include(CheckStructHasMember)
include(CheckSymbolExists)

# checks for IPv6 support mirroring the autoconf ipv6.m4 logic:
# requires sockaddr_in6, getaddrinfo and getnameinfo, on Windows
# the _config_win32.h header already defines HAVE_IP6 so we only
# need to run the detection on Unix systems
if(NOT WIN32)
    check_struct_has_member("struct sockaddr_in6" sin6_addr "netinet/in.h" HAVE_SOCKADDR_IN6)
    check_symbol_exists(getaddrinfo "sys/types.h;sys/socket.h;netdb.h" HAVE_GETADDRINFO)
    check_symbol_exists(getnameinfo "sys/types.h;sys/socket.h;netdb.h" HAVE_GETNAMEINFO)
    if(HAVE_SOCKADDR_IN6 AND HAVE_GETADDRINFO AND HAVE_GETNAMEINFO)
        add_definitions(-DHAVE_IP6)
    endif()
endif()

# passes the effective C flags as a preprocessor define so the
# compilation flags banner can display them at runtime
if(CMAKE_BUILD_TYPE)
    string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_UPPER)
    string(STRIP "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE_UPPER}}" VIRIATUM_CFLAGS)
else()
    string(STRIP "${CMAKE_C_FLAGS}" VIRIATUM_CFLAGS)
endif()
add_definitions(-DCFLAGS="${VIRIATUM_CFLAGS}")

file(GLOB_RECURSE VIRIATUM_COMMON_SOURCES src/viriatum_commons/*.c)
add_library(viriatum_commons ${VIRIATUM_COMMON_SOURCES})
set_target_properties(viriatum_commons PROPERTIES POSITION_INDEPENDENT_CODE ON)

target_include_directories(viriatum_commons PUBLIC src/viriatum_commons)

# links winsock and process status API on Windows, these
# are normally auto-linked via pragma comments in the
# headers but NO_PRAGMA_LIB disables that mechanism
if(WIN32)
    target_link_libraries(viriatum_commons ws2_32 psapi)
endif()

# the core sources are compiled once into an object library so that the
# executable and both library flavours may share them, the main.c file is
# deliberately excluded as it holds the main() entry point and a library
# carrying it may not be linked into a host process (eg: a Python module)
file(GLOB_RECURSE VIRIATUM_SOURCES src/viriatum/*.c)
list(REMOVE_ITEM VIRIATUM_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/viriatum/main.c)
add_library(viriatum_objects OBJECT ${VIRIATUM_SOURCES})
set_target_properties(viriatum_objects PROPERTIES POSITION_INDEPENDENT_CODE ON)

target_include_directories(viriatum_objects PUBLIC src/viriatum)
target_compile_definitions(viriatum_objects PRIVATE WITH_MODULE_ROOT="${CMAKE_BINARY_DIR}/modules")
target_link_libraries(viriatum_objects PUBLIC viriatum_commons)

# the static and shared flavours of the core library, both named after the
# autotools libviriatum_http target so that the produced artifacts match,
# under windows the static library and the import library of the shared one
# are both archive artifacts, so the static flavour takes a distinct name to
# avoid the two overwriting each other in the archive output directory
add_library(viriatum_static STATIC $<TARGET_OBJECTS:viriatum_objects>)
if(WIN32)
    set_target_properties(viriatum_static PROPERTIES OUTPUT_NAME viriatum_http_static)
else()
    set_target_properties(viriatum_static PROPERTIES OUTPUT_NAME viriatum_http)
endif()
target_link_libraries(viriatum_static PUBLIC viriatum_objects)

add_library(viriatum_shared SHARED $<TARGET_OBJECTS:viriatum_objects>)
set_target_properties(viriatum_shared PROPERTIES
    OUTPUT_NAME viriatum_http
    WINDOWS_EXPORT_ALL_SYMBOLS ON)
target_link_libraries(viriatum_shared PUBLIC viriatum_objects)

add_executable(viriatum src/viriatum/main.c)
target_link_libraries(viriatum viriatum_static)

# links the dynamic loader on Unix systems
if(NOT WIN32)
    target_link_libraries(viriatum_objects PUBLIC ${CMAKE_DL_LIBS})
endif()

find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
    pkg_check_modules(PCRE libpcre QUIET)
endif()
if(PCRE_FOUND)
    # verifies that the pcre.h header is actually available at the
    # reported include path before enabling PCRE support, this avoids
    # build failures when pkg-config metadata exists but headers are
    # not installed (common on macOS CI runners)
    set(CMAKE_REQUIRED_INCLUDES ${PCRE_INCLUDE_DIRS})
    include(CheckIncludeFile)
    check_include_file("pcre.h" HAVE_PCRE_HEADER)
    if(HAVE_PCRE_HEADER)
        target_compile_definitions(viriatum_objects PRIVATE VIRIATUM_PCRE HAVE_PCRE_H)
        target_include_directories(viriatum_objects PRIVATE ${PCRE_INCLUDE_DIRS})
        target_link_libraries(viriatum_objects PUBLIC ${PCRE_LINK_LIBRARIES})
    else()
        message(STATUS "Viriatum: PCRE pkg-config found but pcre.h not available, disabling")
    endif()
endif()

# the python extension module is built as a separate target, it is
# deliberately kept outside of the modules subtree so that it does not
# inherit the module naming rules, that would corrupt the file name that
# the interpreter expects for an importable extension
option(VIRIATUM_BUILD_PYTHON "Build the viriatum python extension" OFF)
if(VIRIATUM_BUILD_PYTHON)
    find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
    file(GLOB_RECURSE VIRIATUM_PYTHON_SOURCES src/viriatum_python/*.c)
    Python_add_library(viriatum_python MODULE WITH_SOABI ${VIRIATUM_PYTHON_SOURCES})
    set_target_properties(viriatum_python PROPERTIES OUTPUT_NAME _viriatum)
    target_include_directories(viriatum_python PRIVATE src/viriatum_python)
    target_link_libraries(viriatum_python PRIVATE viriatum_static)
    install(TARGETS viriatum_python LIBRARY DESTINATION viriatum)
    message(STATUS "Viriatum: python extension enabled")
endif()

# modules are built as shared libraries loaded at runtime,
# the VIRIATUM_BUILD_MODULES option controls whether any
# modules are built and individual options allow fine-grained
# control over each module
option(VIRIATUM_BUILD_MODULES "Build viriatum modules" ON)
if(VIRIATUM_BUILD_MODULES)
    add_subdirectory(modules)
endif()
