# Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
# MemCache_Hybrid is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#          http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.

cmake_minimum_required(VERSION 3.12.0)
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" VERSION_CONTENT)
string(STRIP "${VERSION_CONTENT}" PROJECT_VERSION_RAW)
if (NOT PROJECT_VERSION_RAW MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+$")
    message(FATAL_ERROR "Invalid version format in VERSION file: '${PROJECT_VERSION_RAW}'")
endif ()
project(memcache VERSION ${PROJECT_VERSION_RAW} LANGUAGES CXX C)
message(STATUS "in memfabric cmakefile BUILD_FROM_MEMCACHE: ${BUILD_FROM_MEMCACHE}, version: ${PROJECT_VERSION_RAW}.")

list(GET PROJECT_VERSION_RAW 0 DUMMY)
string(REPLACE "." ";" VERSION_LIST "${PROJECT_VERSION_RAW}")
list(LENGTH VERSION_LIST VERSION_LIST_LEN)
if (NOT VERSION_LIST_LEN EQUAL 3)
    message(FATAL_ERROR "Expected exactly 3 version components, got: ${VERSION_LIST_LEN}")
endif ()
list(GET VERSION_LIST 0 VERSION_MAJOR)
list(GET VERSION_LIST 1 VERSION_MINOR)
list(GET VERSION_LIST 2 VERSION_FIX)
message(STATUS "VERSION_MAJOR=${VERSION_MAJOR}")
message(STATUS "VERSION_MINOR=${VERSION_MINOR}")
message(STATUS "VERSION_FIX=${VERSION_FIX}")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF)

option(ENABLE_TRACE_LOG "Enable trace log" OFF)
option(ENABLE_ASAN "Enable ASAN" OFF)
option(BUILD_TESTS "build test or not" OFF)
option(BUILD_OPEN_ABI "build open _GLIBCXX_USE_CXX11_ABI" ON)
option(ENABLE_CPU_MONOTONIC "enable monotonic time using cpu hard instruction" OFF)
option(BUILD_GIT_COMMIT "build library version with commit" ON)
option(BUILD_GIT_COMMIT_GEN_FILE "generate commit log file" ON)
option(BUILD_PYTHON "build python file" ON)
option(ENABLE_PTRACER "build ptracer" OFF)
option(BUILD_CPP_EXAMPLE "Compile and package the c++ example." OFF)

if (NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE RELEASE)
endif ()

message(STATUS "BUILD_TESTS = ${BUILD_TESTS}")
message(STATUS "BUILD_USE_CXX11_ABI = ${BUILD_OPEN_ABI}")
message(STATUS "ENABLE_CPU_MONOTONIC = ${ENABLE_CPU_MONOTONIC}")
message(STATUS "BUILD_GIT_COMMIT = ${BUILD_GIT_COMMIT}")
message(STATUS "BUILD_GIT_COMMIT_GEN_FILE = ${BUILD_GIT_COMMIT_GEN_FILE}")
message(STATUS "BUILD_PYTHON = ${BUILD_PYTHON}")
message(STATUS "ENABLE_PTRACER = ${ENABLE_PTRACER}")
message(STATUS "CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}")
find_program(NINJA_EXE NAMES ninja)
if (NINJA_EXE)
    set(MAKE_CMD ninja)
else ()
    set(MAKE_CMD make)
endif ()
message(STATUS "MAKE_CMD = ${MAKE_CMD}")
add_compile_options(-Wunused-variable -Wunused-value -Wcast-align)
add_compile_options(-fpermissive -Wno-error)
add_compile_options(-Wcast-qual -Winvalid-pch -Wwrite-strings -Wsign-compare -Wfloat-equal -Wextra -Wno-deprecated-declarations)

if (${CMAKE_BUILD_TYPE} MATCHES "RELEASE")
    #add_compile_options(-fvisibility=hidden -fvisibility-inlines-hidden)
    add_compile_options(-fstack-protector-strong -Wstack-usage=16384 -Werror)
    add_compile_options(-Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -fPIE -fPIC -ftrapv -s)
    add_compile_options(-D_FORTIFY_SOURCE=2 -O2)
    add_link_options(-Wl,-z,noexecstack,-z,relro,-z,now)
    add_link_options(-s)
    set(CMAKE_SKIP_BUILD_RPATH TRUE)
    message(STATUS "build type set to RELEASE")
else ()
    add_compile_options(-g -ggdb3 -O0 -fno-omit-frame-pointer -rdynamic -fPIC)
endif ()

if (${CMAKE_BUILD_TYPE} MATCHES "ASAN")
    # 开启多种 sanitizer   "address,undefined,pointer-compare,pointer-subtract,alignment"
    set(SANITIZERS "address,undefined,pointer-compare,pointer-subtract,alignment")
    # 编译选项
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=${SANITIZERS}")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=${SANITIZERS}")
    # 链接选项
    add_link_options(-fsanitize=${SANITIZERS})
endif ()

if (BUILD_TESTS STREQUAL "ON")
    add_definitions(-DUT_ENABLED)
    add_compile_options(-fprofile-arcs -ftest-coverage)
    add_link_options(-lgcov --coverage)
    set(BUILD_OPEN_ABI "ON")
endif ()


if (ENABLE_PTRACER STREQUAL "ON")
    add_definitions(-DENABLE_PTRACER=ON)
endif ()

if (NOT BUILD_OPEN_ABI STREQUAL "ON")
    add_compile_options(-D_GLIBCXX_USE_CXX11_ABI=0)
endif ()

if (ENABLE_CPU_MONOTONIC STREQUAL "ON")
    add_compile_options(-DENABLE_CPU_MONOTONIC)
endif ()

# set log commit into compile definition
if (BUILD_GIT_COMMIT STREQUAL "ON")
    find_program(GIT_EXECUTABLE NAMES git)
    if (EXISTS ${GIT_EXECUTABLE})
        # get commit id from file
        execute_process(
                COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
                RESULT_VARIABLE GIT_COMMIT_RESULT
                OUTPUT_VARIABLE GIT_LAST_COMMIT
                OUTPUT_STRIP_TRAILING_WHITESPACE)
        if (${GIT_COMMIT_RESULT} EQUAL 1)
            set(GIT_LAST_COMMIT "empty")
        endif ()
        add_definitions(-DGIT_LAST_COMMIT=${GIT_LAST_COMMIT})
        message(STATUS "add definition for last commit: ${GIT_LAST_COMMIT}")
    else ()
        add_definitions(-DGIT_LAST_COMMIT=empty)
        message(STATUS "Failed to find git command, not GIT_LAST_COMMIT will be set")
    endif ()
else ()
    add_definitions(-DGIT_LAST_COMMIT=empty)
    message(STATUS "add definition for last commit: empty")
endif ()

if (ENABLE_TRACE_L0G)
    add_definitions(-DLOG_TRACE_INFO_ENABLED)
endif ()

set(PROJECT_BUILD_PATH ${PROJECT_BINARY_DIR})
set(PROJECT_3RDPARTY_SRC_DIR ${PROJECT_SOURCE_DIR}/3rdparty)
set(PROJECT_3RDPARTY_BIN_DIR ${PROJECT_BINARY_DIR}/3rdparty)
set(PROJECT_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/output)

# set src base dir and output dir of memcache
set(PROJECT_MMC_SRC_BASE ${PROJECT_SOURCE_DIR}/src/memcache)
set(PROJECT_MMC_OUTPUT ${PROJECT_SOURCE_DIR}/output/memcache)

# set install target top dir
message(STATUS "CMAKE_INSTALL_PREFIX = ${CMAKE_INSTALL_PREFIX}")
if (${CMAKE_INSTALL_PREFIX} MATCHES "/usr/local")
    set(TARGET_INSTALL_DIR ${PROJECT_OUTPUT_PATH}/)
elseif (NOT EXISTS ${CMAKE_INSTALL_PREFIX})
    set(TARGET_INSTALL_DIR ${PROJECT_OUTPUT_PATH}/)
else ()
    set(TARGET_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/)
endif ()
message(STATUS "install dir ${TARGET_INSTALL_DIR}")

if ((BUILD_PYTHON STREQUAL "ON") OR (BUILD_TESTS STREQUAL "ON"))
    # 获取Python头文件路径
    find_program(PYTHON_EXECUTABLE NAMES python3 python)
    if (NOT PYTHON_EXECUTABLE)
        message(FATAL_ERROR "Python not found")
    endif ()

    execute_process(
            COMMAND ${PYTHON_EXECUTABLE} -c
            "import sysconfig; print(sysconfig.get_path('include'))"
            OUTPUT_VARIABLE PYTHON_INCLUDE_DIR
            OUTPUT_STRIP_TRAILING_WHITESPACE
    )

    execute_process(
            COMMAND ${PYTHON_EXECUTABLE} -c
            "import sys; import os; import pybind11; print(os.path.dirname(pybind11.__file__))"
            OUTPUT_VARIABLE PYTHON_PYBIND11_HOME
            OUTPUT_STRIP_TRAILING_WHITESPACE
    )

    set(pybind11_cmake_dir "${PYTHON_PYBIND11_HOME}/share/cmake/pybind11")

    # 检查路径是否存在且是目录
    if (NOT EXISTS "${pybind11_cmake_dir}" OR NOT IS_DIRECTORY "${pybind11_cmake_dir}")
        message(SEND_ERROR "Can not find pybind11 directory: ${pybind11_cmake_dir}\nPlease 'pip3 install pybind11 or pybind11-dev'")
    endif ()
endif ()

# 判断环境变量 CMAKE_PREFIX_PATH 是否存在
if (DEFINED ENV{CMAKE_PREFIX_PATH})
    # 如果环境变量存在,将其值赋给 CMake 变量
    set(CMAKE_PREFIX_PATH "$ENV{CMAKE_PREFIX_PATH}")
    list(APPEND CMAKE_PREFIX_PATH "${pybind11_cmake_dir}")
    message(STATUS "CMAKE_PREFIX_PATH is exist, and append: ${pybind11_cmake_dir}")
    message(STATUS "Now CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}")
else ()
    # 如果环境变量不存在,设置 CMake 变量的默认值
    set(CMAKE_PREFIX_PATH "${pybind11_cmake_dir}")
    message(STATUS "Not set CMAKE_PREFIX_PATH, and initialize to: ${CMAKE_PREFIX_PATH}")
endif ()

include_directories(
        ${PROJECT_SMEM_SRC_BASE}/include/host
        ${PROJECT_UTIL_SRC_BASE}
)

add_subdirectory(3rdparty)

set(HYBRID_SRC ${PROJECT_SOURCE_DIR}/3rdparty/memfabric_hybrid/src)
file(GLOB_RECURSE ALL_HEADERS
        "${HYBRID_SRC}/*.h"
)
list(FILTER ALL_HEADERS EXCLUDE REGEX "${HYBRID_SRC}/hybm/csrc/driver/npu_direct_rdma/.*")

set(INCLUDE_DIRS "")
foreach (header ${ALL_HEADERS})
    get_filename_component(HEADER_DIR ${header} PATH)
    list(APPEND INCLUDE_DIRS ${HEADER_DIR})
endforeach ()
list(REMOVE_DUPLICATES INCLUDE_DIRS)

include_directories(${INCLUDE_DIRS})
add_compile_definitions(PROJECT_VERSION_RAW=\"${PROJECT_VERSION_RAW}\")
add_compile_definitions(VERSION_MAJOR=${VERSION_MAJOR}
        VERSION_MINOR=${VERSION_MINOR}
        VERSION_FIX=${VERSION_FIX})
add_subdirectory(src)

if (BUILD_CPP_EXAMPLE)
    add_subdirectory(example/cpp)
endif ()

if (BUILD_TESTS STREQUAL "ON")
    set(CMAKE_CXX_STANDARD 17)
    message(STATUS "BUILD_TESTS = ON, add compile gov")
    add_subdirectory(test)
endif ()