################################################################################
# Copyright 2018-2025, Barcelona Supercomputing Center (BSC), Spain            #
# Copyright 2015-2025, Johannes Gutenberg Universitaet Mainz, Germany          #
#                                                                              #
# This software was partially supported by the                                 #
# EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu).    #
#                                                                              #
# This software was partially supported by the                                 #
# ADA-FS project under the SPPEXA project funded by the DFG.                   #
#                                                                              #
# This file is part of GekkoFS.                                                #
#                                                                              #
# GekkoFS is free software: you can redistribute it and/or modify              #
# it under the terms of the GNU General Public License as published by         #
# the Free Software Foundation, either version 3 of the License, or            #
# (at your option) any later version.                                          #
#                                                                              #
# GekkoFS is distributed in the hope that it will be useful,                   #
# but WITHOUT ANY WARRANTY; without even the implied warranty of               #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                #
# GNU General Public License for more details.                                 #
#                                                                              #
# You should have received a copy of the GNU General Public License            #
# along with GekkoFS.  If not, see <https://www.gnu.org/licenses/>.            #
#                                                                              #
# SPDX-License-Identifier: GPL-3.0-or-later                                    #
################################################################################

# 3.16+ is required by Catch2 v3.16 and its CMake integration.
cmake_minimum_required(VERSION 3.16)

project(
    GekkoFS
    VERSION 0.9.6
    LANGUAGES ASM CXX C
)

enable_testing()

if (NOT CMAKE_COMPILER_IS_GNUCC)
    message(FATAL_ERROR "The choosen C compiler is not gcc and is not supported")
endif ()
if (NOT CMAKE_COMPILER_IS_GNUCXX)
    message(FATAL_ERROR "The choosen C++ compiler is not g++ and is not supported")
endif ()

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)

if (NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release
        CACHE STRING "Choose the type of build: Debug Release Memcheck" FORCE)
ENDIF (NOT CMAKE_BUILD_TYPE)
message(STATUS "[${PROJECT_NAME}] Build type: ${CMAKE_BUILD_TYPE}")

# Compiler flags for various cmake build types
set(WARNINGS_FLAGS "-Wall -Wextra --pedantic -Wno-unused-parameter -Wno-missing-field-initializers")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG -O3")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${WARNINGS_FLAGS} -g -O0 -DGKFS_DEBUG_BUILD")
set(CMAKE_CXX_FLAGS_MEMCHECK "${WARNINGS_FLAGS} -g -O0 -fsanitize=address -fno-omit-frame-pointer")
set(CMAKE_CXX_FLAGS_MAINTAINER "${WARNINGS_FLAGS} -g -O0 -pg -no-pie")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DNDEBUG -O3")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${WARNINGS_FLAGS} -g -O0 -DGKFS_DEBUG_BUILD")
set(CMAKE_C_FLAGS_MEMCHECK "${WARNINGS_FLAGS} -g -O0 -fsanitize=address -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS_MAINTAINER "${WARNINGS_FLAGS} -g -O0 -pg -no-pie")
mark_as_advanced(CMAKE_CXX_FLAGS_MAINTAINER)

# CMake and general includes
include(CheckCXXSourceCompiles)
include(CMakeDependentOption)
include(GNUInstallDirs)
include(FeatureSummary)

# Project version
set(GIT_VERSION_FOUND FALSE)
if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/.git)
    find_package(Git)
    if (Git_FOUND)
        execute_process(COMMAND /bin/bash -c "${GIT_EXECUTABLE} describe --tags --match='v*.*.*' --long --dirty"
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
            RESULT_VARIABLE GIT_RET
            OUTPUT_VARIABLE GIT_VERSION
            ERROR_VARIABLE GIT_ERR
            OUTPUT_STRIP_TRAILING_WHITESPACE
        )
        if ("${GIT_RET}" STREQUAL "0")
            string(REGEX MATCH
                "^v([0-9]+)\.([0-9]+)\.([0-9]+)-([0-9]+)-(.*)$"
                DROP ${GIT_VERSION})
            set(PROJECT_VERSION_MAJOR ${CMAKE_MATCH_1})
            set(PROJECT_VERSION_MINOR ${CMAKE_MATCH_2})
            set(PROJECT_VERSION_PATCH ${CMAKE_MATCH_3})
            set(GKFS_COMMIT_GAP ${CMAKE_MATCH_4})
            set(GKFS_COMMIT_HASH ${CMAKE_MATCH_5})
            set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-snapshot+${GKFS_COMMIT_GAP}-${GKFS_COMMIT_HASH}")
            set(GIT_VERSION_FOUND TRUE)
        else ()
            message(WARNING "Failed to run `git describe`: [${GIT_RET}] '${GIT_ERR}'")
        endif ()
    endif ()
endif ()
message(STATUS "[${PROJECT_NAME}] Project version: ${PROJECT_VERSION}")
set(GKFS_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
set(GKFS_VERSION_MINOR ${PROJECT_VERSION_MINOR})
set(GKFS_VERSION_PATCH ${PROJECT_VERSION_PATCH})
set(GKFS_VERSION_STRING ${PROJECT_VERSION})
configure_file(include/version.hpp.in include/version.hpp)

set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake" ${CMAKE_MODULE_PATH})

# Import some convenience functions
include(gkfs-utils)

################################################################################
## Configuration option definitions:
## =================================
##
## The `gkfs-options` module imports definitions for all the available GekkoFS
## features
################################################################################
include(gkfs-options)

set(CMAKE_EXPORT_COMPILE_COMMANDS 1)


# ##############################################################################
# Check for and/or download dependencies:
#
# GekkoFS has two types of dependencies:
# 1. Dependencies that are installed as system-wide packages and provide
#    a FindXXX.cmake file to find them. These are handled using
#    `find_package()`.
# 2. Depedencies that are used directly from source code (e.g. header-only
#    libraries). The source code for these can be found locally or
#    remotely in a GIT repository. These are handled using our custom
#    `include_from_source()` function defined in `gkfs-utils.cmake`
# ##############################################################################

# Search for dependencies that should already be installed in the system
###############################################################################

### some dependencies don't provide CMake modules, but rely on pkg-config
### instead, make sure that pkg-config is available
find_package(PkgConfig REQUIRED)

### Mercury
message(STATUS "[${PROJECT_NAME}] Checking for Mercury")
find_package(Mercury 2.2.0 REQUIRED)

### Argobots
message(STATUS "[${PROJECT_NAME}] Checking for Argobots")
find_package(Argobots 1.1 REQUIRED)

### Margo
# message(STATUS "[${PROJECT_NAME}] Checking for Margo")
# find_package(Margo 0.14.0 REQUIRED)
message(STATUS "[${PROJECT_NAME}] Checking for Thallium")
find_package(Thallium REQUIRED)

### syscall-intercept
message(STATUS "[${PROJECT_NAME}] Checking for syscall_intercept")
find_package(Syscall_intercept REQUIRED)

message(STATUS "[${PROJECT_NAME}] Checking for Zstd")
find_package(ZStd REQUIRED)

### AGIOS: required for scheduling I/O requests
if (GKFS_ENABLE_AGIOS)
    message(STATUS "[${PROJECT_NAME}] Checking for Agios")
    find_package(AGIOS REQUIRED)
endif ()

if (GKFS_ENABLE_CLIENT_METRICS)
    ### zeromq: required for sending client metrics to FTIO
    message(STATUS "[${PROJECT_NAME}] Checking for cppzmq")
    find_package(cppzmq REQUIRED)
endif ()

### Metadata backends
if (GKFS_ENABLE_ROCKSDB)
    message(STATUS "[${PROJECT_NAME}] Checking for RocksDB")
    add_compile_definitions(GKFS_ENABLE_ROCKSDB)

    # If liburing is available in the system, RocksDB will have been built
    # with liburing support. If so, its native CMake configuration will
    # expect a uring::uring target.
    # Since liburing exports a pkg-config .pc file, we can use it
    # to retrieve its details and create the target if it's missing.
    find_package(PkgConfig REQUIRED)
    pkg_check_modules(liburing IMPORTED_TARGET liburing)
    if (liburing_FOUND AND NOT TARGET uring::uring)
        add_library(uring::uring INTERFACE IMPORTED)
        target_link_libraries(uring::uring INTERFACE PkgConfig::liburing)
    endif ()

    find_package(RocksDB REQUIRED)
    message(STATUS "[${PROJECT_NAME}] RocksDB version ${RocksDB_VERSION}")
    message(STATUS "[${PROJECT_NAME}] RocksDB location ${RocksDB_DIR}")
endif ()

if (GKFS_ENABLE_PARALLAX)
    add_compile_definitions(GKFS_ENABLE_PARALLAX)
    # The current version of Parallax has a direct dependency on libyaml and librt.
    # Make sure that we find them and add them as link dependencies..

    message(STATUS "[${PROJECT_NAME}] Checking for yaml")
    find_package(yaml REQUIRED)

    message(STATUS "[${PROJECT_NAME}] Checking for libaio")
    find_package(AIO REQUIRED)

    message(STATUS "[${PROJECT_NAME}] Checking for Parallax")
    find_package(Parallax REQUIRED)
    target_link_libraries(Parallax::parallax INTERFACE yaml AIO::AIO)
endif ()

### Prometheus-cpp: required for the collection of GekkoFS stats
### (these expose the prometheus-cpp::pull, prometheus-cpp::push,
### prometheus-cpp::core, and curl imported targets
if (GKFS_ENABLE_PROMETHEUS)
    find_package(CURL 7.68.0 REQUIRED)
    find_package(Prometheuscpp REQUIRED)
endif ()

### Other stuff that can be found out using find_package:

# determine the thread library of the system
find_package(Threads REQUIRED)

# some compilers need extra flags for std::filesystem, such as -lstdc++fs, this
# produces a std::filesystem imported target that takes care of all these
# details transparently
find_package(Filesystem REQUIRED)

if (GKFS_BUILD_FUSE)
    find_package(PkgConfig REQUIRED)
    pkg_check_modules(FUSE3 REQUIRED fuse3)
    include_directories(${FUSE3_INCLUDE_DIRS})
    link_directories(${FUSE3_LIBRARY_DIRS})
    add_definitions(${FUSE3_CFLAGS_OTHER})
endif ()

# Search for 'source-only' dependencies
###############################################################################

# the directory where external dependencies should be searched for
set(GKFS_DEPENDENCIES_PATH ${CMAKE_SOURCE_DIR}/external)

### {fmt}: required for sensible output formatting
set(FMT_INSTALL OFF)
include_from_source(fmt
    MESSAGE "[${PROJECT_NAME}] Searching for {fmt}"
    SOURCE_DIR ${GKFS_DEPENDENCIES_PATH}/fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt
    GIT_TAG 1be298e1bd68957e4cd352e1f676f00e07dcfb57 # v12.2.0
)

# ensure that fmt is linked as PIC
set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON)

### spdlog: required for logging
include_from_source(spdlog
    MESSAGE "[${PROJECT_NAME}] Searching for spdlog"
    SOURCE_DIR ${GKFS_DEPENDENCIES_PATH}/spdlog
    GIT_REPOSITORY https://github.com/gabime/spdlog.git
    GIT_TAG 79524ddd08a4ec981b7fea76afd08ee05f83755d # v1.17.0
)

### CLI11: used for parsing command-line options
include_from_source(cli11
    MESSAGE "[${PROJECT_NAME}] Searching for CLI11"
    SOURCE_DIR ${GKFS_DEPENDENCIES_PATH}/CLI11
    GIT_REPOSITORY https://github.com/CLIUtils/CLI11
    GIT_TAG v2.7.2
)

if (GKFS_ENABLE_CLIENT_METRICS)
    ### MessagePack: used for monitoring information on the client
    include_from_source(MessagePack
        MESSAGE "[${PROJECT_NAME}] Searching for MessagePackCPP"
        SOURCE_DIR ${GKFS_DEPENDENCIES_PATH}/MessagePack
        GIT_REPOSITORY https://github.com/GekkoFS/MessagePackCPP
        GIT_TAG e3ee9ad2a4cd01551c29596ea1dd3f127664e10a # CMake 3.10 compatibility
    )
endif ()

### GSL: Guidelines Support Library
include_from_source(gsl
	MESSAGE "[${PROJECT_NAME}] Searching for GSL"
	SOURCE_DIR ${GKFS_DEPENDENCIES_PATH}/GSL
  GIT_REPOSITORY https://github.com/microsoft/GSL
  GIT_TAG v5.0.0
)

################################################################################
## Check configured variables/options and act accordingly
################################################################################
if (GKFS_BUILD_DOCUMENTATION)
    add_subdirectory(docs)
endif ()

if (GKFS_FOLLOW_EXTERNAL_SYMLINKS)
    add_definitions(-DGKFS_FOLLOW_EXTERNAL_SYMLINKS)
endif ()

if (GKFS_MAX_INTERNAL_FDS)
    add_definitions(-DGKFS_MAX_INTERNAL_FDS=${GKFS_MAX_INTERNAL_FDS})
endif ()

if (GKFS_MAX_OPEN_FDS)
    add_definitions(-DGKFS_MAX_OPEN_FDS=${GKFS_MAX_OPEN_FDS})
endif ()

if (GKFS_ENABLE_CLIENT_LOG)
    add_definitions(-DGKFS_ENABLE_LOGGING)
    add_definitions(-DLIBGKFS_LOG_MESSAGE_SIZE=${GKFS_CLIENT_LOG_MESSAGE_SIZE})
endif ()

if (GKFS_ENABLE_UNUSED_FUNCTIONS)
    add_definitions(-DGKFS_ENABLE_UNUSED_FUNCTIONS)
endif ()

if (GKFS_ENABLE_PROMETHEUS)
    add_definitions(-DGKFS_ENABLE_PROMETHEUS)
endif ()

if (GKFS_ENABLE_LOCKING)
    add_definitions(-DENABLE_LOCKING)
endif ()

configure_file(include/common/cmake_configure.hpp.in include/common/cmake_configure.hpp)


set(INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include")

# define include directories that are relevant for all targets
include_directories(
    ${INCLUDE_DIR}
    ${CMAKE_BINARY_DIR}/include
    /usr/include
)

add_subdirectory(src)
add_subdirectory(include)
add_subdirectory(examples)

install(FILES "${CMAKE_SOURCE_DIR}/scripts/run/gkfs_run"
    DESTINATION ${CMAKE_INSTALL_BINDIR}
    PERMISSIONS
        OWNER_READ OWNER_WRITE OWNER_EXECUTE
        GROUP_READ GROUP_EXECUTE
        WORLD_READ WORLD_EXECUTE)

if (GKFS_BUILD_TOOLS)
    add_subdirectory(tools)
endif ()

### Mark any CMake variables imported from {fmt} and spdlog as advanced, so
### that they don't appear in cmake-gui or ccmake. Similarly for FETCHCONTENT
### variables.
mark_variables_as_advanced(REGEX "^(FETCHCONTENT|fmt|FMT|spdlog|SPDLOG)_.*$")

if (GKFS_BUILD_PERFORMANCE)
    message(STATUS "[${PROJECT_NAME}] Building performance benchmarks...")
    add_subdirectory(perf_tests)
endif()

if (GKFS_BUILD_TESTS)
    # Boost preprocessor header-only is supplied by the Mercury installation
    find_package(Boost_preprocessor REQUIRED)
    # check symbols exists doesn't work for statx. This is a workaround
    check_cxx_source_compiles("
        #include <fcntl.h>
        #include <sys/stat.h>

        int main() {
            struct statx buf;
            statx(AT_FDCWD, \"/foo\", AT_EMPTY_PATH, STATX_BASIC_STATS, &buf);
            return 0;
        }
        " GLIBC_HAS_STATX)
    # STATX_TYPE must be set for c++17 for statx() to be found for tests
    if (GLIBC_HAS_STATX)
        add_definitions(-DSTATX_TYPE=1)
    endif ()
    message(STATUS "[gekkofs] Preparing tests...")
    set(GKFS_TESTS_INTERFACE "lo" CACHE STRING "Network interface to use when running tests (default: lo)")
    if (GKFS_TESTS_INTERFACE STREQUAL "")
        message(FATAL_ERROR "GKFS_TESTS_INTERFACE must not be empty when tests are enabled")
    endif ()
    message(STATUS "[gekkofs] Network interface for tests: ${GKFS_TESTS_INTERFACE}")

    message(STATUS "[gekkofs] Check for forwarding tests...")
    set(GKFS_TESTS_FORWARDING ON CACHE BOOL "Enable I/O forwarding tests")
    message(STATUS "[gekkofs] Forwarding tests: ${GKFS_TESTS_FORWARDING}")
    message(STATUS "[gekkofs] Check for guided distributor tests...")
    if (GKFS_USE_GUIDED_DISTRIBUTION)
        set(GKFS_TESTS_GUIDED_DISTRIBUTION ON CACHE BOOL "Enable guided distributor tests" FORCE)
    else ()
        set(GKFS_TESTS_GUIDED_DISTRIBUTION OFF CACHE BOOL "Enable guided distributor tests" FORCE)
    endif ()
    message(STATUS "[gekkofs] Guided distributor tests: ${GKFS_TESTS_GUIDED_DISTRIBUTION}")

    add_subdirectory(tests)
else ()
    unset(GKFS_TESTS_INTERFACE CACHE)
endif ()

################################################################################
## Print GekkoFS configuration summary
################################################################################
gkfs_feature_summary(DESCRIPTION "${PROJECT_NAME} configuration summary:")

configure_file(
    "${CMAKE_SOURCE_DIR}/CMake/gkfs-config-report.txt.in"
    "${CMAKE_BINARY_DIR}/cmake/configuration-summary.txt"
    @ONLY
)

add_custom_target(cmake-report
    COMMAND ${CMAKE_COMMAND} -E cat
        "${CMAKE_BINARY_DIR}/cmake/configuration-summary.txt"
    VERBATIM
    COMMENT "Printing GekkoFS CMake configuration summary"
)
