include("../../build_utils/CMakeLists.txt")

cmake_minimum_required(VERSION 3.22)
project(PTI_Tools_UniTrace C CXX)
find_package(Python 3.9 COMPONENTS Interpreter REQUIRED)

list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
SetCompilerFlags()
SetBuildType()

if(WIN32)
  #Win_Todo :  Default MPI is disabled on windows.It will be enabled once verified.
  option(BUILD_WITH_MPI
  "Build with support for MPI tracing"
  OFF
  )
else()
  option(BUILD_WITH_MPI
    "Build with support for MPI tracing"
    ON
  )
endif()

if (NOT DEFINED BUILD_WITH_ITT)
  set(BUILD_WITH_ITT 1) # Defaul value is 1
endif()

if (NOT DEFINED BUILD_WITH_XPTI)
  set(BUILD_WITH_XPTI 1) # Defaul value is 1
endif()

if (NOT DEFINED BUILD_WITH_OPENCL)
  set(BUILD_WITH_OPENCL 1) # Defaul value is 1
endif()

if (DEFINED BUILD_WITH_L0 AND BUILD_WITH_L0 STREQUAL "0")
  message(FATAL_ERROR "BUILD_WITH_L0=0 is not supported. The unitrace tool can't be built without Level Zero")
endif()

if (BUILD_WITH_MPI AND BUILD_WITH_ITT STREQUAL "0")
  message(FATAL_ERROR "BUILD_WITH_ITT=0 and BUILD_WITH_MPI=1 is not a valid configuration for unitrace tool.")
endif()

add_compile_definitions(BUILD_WITH_ITT=${BUILD_WITH_ITT})
add_compile_definitions(BUILD_WITH_XPTI=${BUILD_WITH_XPTI})
add_compile_definitions(BUILD_WITH_OPENCL=${BUILD_WITH_OPENCL})
add_compile_definitions(BUILD_WITH_L0=1)

if(WIN32)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17 /bigobj ")
else()
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -fvisibility=default")
endif()

# Tool Library
macro(FindHeadersPath TARGET L0_GEN_SCRIPT GEN_FILE_NAME custom_target)
  find_path(L0_INC_PATH
    NAMES level_zero
    PATHS ENV CPATH)
  if (NOT L0_INC_PATH)
    message(FATAL_ERROR
      "Level Zero headers path is not found.\n"
      "You may need to install oneAPI Level Zero Driver to fix this issue.")
  else()
    message(STATUS "Level Zero headers are found at ${L0_INC_PATH}")
  endif()

  set(L0_GEN_INC_PATH "${CMAKE_BINARY_DIR}")
  set(OCL_DIR_PATH "${CMAKE_BINARY_DIR}/CL")
  add_custom_target(${custom_target} ALL
                    DEPENDS ${L0_GEN_INC_PATH}/${GEN_FILE_NAME})
  add_custom_command(OUTPUT ${L0_GEN_INC_PATH}/${GEN_FILE_NAME}
                     COMMAND "${Python_EXECUTABLE}" ${L0_GEN_SCRIPT} ${L0_GEN_INC_PATH} "${L0_INC_PATH}/level_zero" "${OCL_DIR_PATH}")
  target_include_directories(${TARGET}
    PUBLIC "${L0_GEN_INC_PATH}")
  add_dependencies(${TARGET}
    ${custom_target})
  add_dependencies(${custom_target} level_zero_headers) # level_zero_headers target added in GetLevelZeroHeaders macro
endmacro()

# Get Git Commit Hash
macro(GetGitCommitHash TARGET GET_COMMIT_HASH_SCRIPT COMMIT_HASH_FILE_NAME target)

  add_custom_target(${target} ALL DEPENDS ${CMAKE_BINARY_DIR}/${COMMIT_HASH_FILE_NAME})
  add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/${COMMIT_HASH_FILE_NAME}
                     COMMAND "${Python_EXECUTABLE}" ${GET_COMMIT_HASH_SCRIPT} ${CMAKE_BINARY_DIR} ${COMMIT_HASH_FILE_NAME})
  add_dependencies(${TARGET} ${target})
endmacro()

if (${BUILD_WITH_ITT})
  # Download itt source from github repo
  set(ITT_HASH 6ddf03848b46f78a8d2cbe596cbe1b5e2602005c)
  if (CMAKE_INSTALL_PREFIX AND INSTALL_ITT)
    set (ITT_INSTALL_PATH ${CMAKE_INSTALL_PREFIX})
  endif()
  execute_process(COMMAND "${Python_EXECUTABLE}" ${PROJECT_SOURCE_DIR}/scripts/get_itt.py ${CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR} ${ITT_HASH} ${ITT_INSTALL_PATH}
    RESULT_VARIABLE result
    ERROR_VARIABLE error_msg
  )
  if (NOT result EQUAL 0)
    message(FATAL_ERROR "Error occurred while building ITT component")
  endif()
endif()

if (BUILD_WITH_MPI)
  find_package(MPI REQUIRED)
  add_library(unitrace_mpi SHARED
    "${PROJECT_SOURCE_DIR}/src/mpi/mpi.c")

  target_link_libraries(unitrace_mpi
    PUBLIC MPI::MPI_C
    PRIVATE ${CMAKE_BINARY_DIR}/libittnotify.a)
  target_include_directories(unitrace_mpi
    PRIVATE "${PROJECT_SOURCE_DIR}"
    PRIVATE "${I_MPI_ROOT}/include"
    PRIVATE "${PROJECT_SOURCE_DIR}/src"
    PRIVATE "${PROJECT_SOURCE_DIR}/src/utils"
    PRIVATE "${PROJECT_SOURCE_DIR}/../utils"
    PRIVATE "${PROJECT_SOURCE_DIR}/../../utils"
    PRIVATE "${PROJECT_SOURCE_DIR}/src/itt"
    PRIVATE "${CMAKE_BINARY_DIR}/ittheaders"
    PRIVATE "${PROJECT_SOURCE_DIR}/src/mpi"
    PRIVATE "${PROJECT_SOURCE_DIR}/src/opencl"
    PRIVATE "${PROJECT_SOURCE_DIR}/src/levelzero")
  if(CMAKE_INCLUDE_PATH)
    target_include_directories(unitrace_mpi
      PUBLIC "${CMAKE_INCLUDE_PATH}")
  endif()
endif()


add_library(unitrace_tool SHARED
  "${PROJECT_SOURCE_DIR}/src/tracer.cc")
link_directories(${ONEAPI_COMPILER_HOME}/lib)

if (BUILD_WITH_XPTI)
  find_package(Xptifw REQUIRED)
  if (Xptifw_FOUND)
    message(STATUS "XPTI found")
    target_include_directories(unitrace_tool PRIVATE "${Xptifw_INCLUDE_DIR}")
    target_link_directories(unitrace_tool PRIVATE "${Xptifw_LIBRARY_DIR}")
    if(WIN32)
      target_link_libraries(unitrace_tool ${Xptifw_LIBRARY})
    else()
      target_link_libraries(unitrace_tool xptifw)
    endif()
  endif()
endif()

target_include_directories(unitrace_tool
  PRIVATE "${PROJECT_SOURCE_DIR}"
  PRIVATE "${ONEAPI_COMPILER_HOME}/include"
  PRIVATE "${PROJECT_SOURCE_DIR}/src"
  PRIVATE "${PROJECT_SOURCE_DIR}/src/utils"
  PRIVATE "${PROJECT_SOURCE_DIR}/../utils"
  PRIVATE "${PROJECT_SOURCE_DIR}/../../utils"
  PRIVATE "${PROJECT_SOURCE_DIR}/src/opencl"
  PRIVATE "${PROJECT_SOURCE_DIR}/src/levelzero")

if (BUILD_WITH_XPTI)
  target_include_directories(unitrace_tool
    PRIVATE "${PROJECT_SOURCE_DIR}/src/xpti")
endif()

if (BUILD_WITH_ITT)
  target_include_directories(unitrace_tool
    PRIVATE "${PROJECT_SOURCE_DIR}/src/itt"
    PRIVATE "${CMAKE_BINARY_DIR}/ittheaders")
endif()

target_compile_definitions(unitrace_tool PUBLIC PTI_LEVEL_ZERO=1)
if(CMAKE_INCLUDE_PATH)
  target_include_directories(unitrace_tool
    PUBLIC "${CMAKE_INCLUDE_PATH}")
endif()

if (BUILD_WITH_OPENCL)
  FindOpenCLLibrary(unitrace_tool)
  FindOpenCLHeaders(unitrace_tool)
endif()

# GetComputeRuntimeHeaders is needed for both OpenCL tracing and Level Zero extension APIs
GetComputeRuntimeHeaders(unitrace_tool)

set (LEVEL_ZERO_COMMIT_ID "v1.26.0")
GetLevelZeroHeaders(unitrace_tool)

FindHeadersPath(unitrace_tool "${PROJECT_SOURCE_DIR}/scripts/gen_tracing_common_header.py" "common_header.gen" gen_common_header)
FindHeadersPath(unitrace_tool "${PROJECT_SOURCE_DIR}/scripts/gen_tracing_callbacks.py" "tracing.gen" gen_tracing_header)
FindHeadersPath(unitrace_tool "${PROJECT_SOURCE_DIR}/scripts/gen_l0_loader.py" "l0_loader.gen" gen_l0_loader_header)

# gen_l0_loader_header needs extensions from compute_runtime_headers
add_dependencies(gen_l0_loader_header compute_runtime_headers)
# gen_tracing_header needs extensions from compute_runtime_headers for extension API tracing
add_dependencies(gen_tracing_header compute_runtime_headers)
# gen_common_header needs CL tracing headers from compute_runtime_headers
add_dependencies(gen_common_header compute_runtime_headers)

target_include_directories(unitrace_tool PRIVATE "${L0_INC_PATH}")

# Loader

add_executable(unitrace "${PROJECT_SOURCE_DIR}/src/unitrace.cc")
# unitrace to depend on gen_l0_loader_header to ensure that it exists
add_dependencies(unitrace gen_l0_loader_header)
# unitrace_tool to depend on unitrace to build unitrace first and then we can
# remove l0_loader.gen when unitrace_tool is built
add_dependencies(unitrace_tool unitrace)

if(UNIX)
    include(CMakePushCheckState)
    cmake_push_check_state()
    cmake_reset_check_state()
    include(CheckCXXSourceCompiles)
    set(code
        "#include <filesystem>
        int main()
        {
            return std::filesystem::exists(std::filesystem::path(\"/tmp\"));
        }")
    check_cxx_source_compiles("${code}" FILESYSTEM_JUST_WORKS)
    if(FILESYSTEM_JUST_WORKS)
        set(CXX_STD_FILESYSTEM_EXPERIMENTAL 0)
    else()
    set(code
        "#include <experimental/filesystem>
        int main()
        {
            return std::experimental::filesystem::exists(std::experimental::filesystem::path(\"/tmp\"));
        }")
        set(saved_libs ${CMAKE_REQUIRED_LIBRARIES})
        set(CMAKE_REQUIRED_LIBRARIES ${saved_libs} "-lstdc++fs")
        check_cxx_source_compiles("${code}" FILESYSTEM_NEEDS_LIBSTDCXXFS)
        if (FILESYSTEM_NEEDS_LIBSTDCXXFS)
            set(CXX_STD_FILESYSTEM_EXPERIMENTAL 1)
            target_link_libraries(unitrace stdc++fs)
        else()
            set(CMAKE_REQUIRED_LIBRARIES ${saved_libs} "-lc++fs")
            check_cxx_source_compiles("${code}" FILESYSTEM_NEEDS_LIBCXXFS)
            if (FILESYSTEM_NEEDS_LIBCXXFS)
                set(CXX_STD_FILESYSTEM_EXPERIMENTAL 1)
                target_link_libraries(unitrace c++fs)
            else()
                message(FATAL_ERROR "C++17 filesystem support is required")
            endif()
        endif()
    endif()
    cmake_pop_check_state()
    target_compile_definitions(unitrace PRIVATE CXX_STD_FILESYSTEM_EXPERIMENTAL=${CXX_STD_FILESYSTEM_EXPERIMENTAL})
endif()

set(_use_mpi 1)

if (BUILD_WITH_MPI)
  set(_use_mpi 1)
else()
  set(_use_mpi 0)
endif()
target_compile_definitions(unitrace PRIVATE BUILD_WITH_MPI=${_use_mpi})
target_include_directories(unitrace
  PRIVATE "${CMAKE_BINARY_DIR}"
  PRIVATE "${PROJECT_SOURCE_DIR}/src"
  PRIVATE "${PROJECT_SOURCE_DIR}/src/levelzero"
  PRIVATE "${PROJECT_SOURCE_DIR}/src/utils"
  PRIVATE "${PROJECT_SOURCE_DIR}/../utils"
  PRIVATE "${PROJECT_SOURCE_DIR}/../../utils")
if(UNIX)
  target_link_libraries(unitrace pthread dl rt)
  target_link_libraries(unitrace_tool rt)
endif()

GetLevelZeroHeaders(unitrace)
GetGitCommitHash(unitrace "${PROJECT_SOURCE_DIR}/scripts/get_commit_hash.py" "unitrace_commit_hash.h" get_git_commit_hash_unitrace)
GetGitCommitHash(unitrace_tool "${PROJECT_SOURCE_DIR}/scripts/get_commit_hash.py" "unitrace_tool_commit_hash.h" get_git_commit_hash_unitrace_tool)

if(UNIX)
  add_custom_command(TARGET unitrace POST_BUILD COMMAND "rm" "-rf" "unitrace_commit_hash.h")
  add_custom_command(TARGET unitrace_tool POST_BUILD COMMAND "rm" "-rf" "unitrace_tool_commit_hash.h")
else()
  add_custom_command(TARGET unitrace POST_BUILD COMMAND "del" "unitrace_commit_hash.h")
  add_custom_command(TARGET unitrace_tool POST_BUILD COMMAND "del" "unitrace_tool_commit_hash.h")
endif()

# Testing
enable_testing()
add_test(NAME test_unitrace COMMAND "${Python_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/test/test_unitrace.py" --test-dir "${PROJECT_SOURCE_DIR}/test" --config "${PROJECT_SOURCE_DIR}/test/test_config.json")

# Clearning files only for release build, for any other build types lets skip deletion for better debuggability
string(TOLOWER "${CMAKE_BUILD_TYPE}" LOWER_CMAKE_BUILD_TYPE)
if(NOT LOWER_CMAKE_BUILD_TYPE STREQUAL "debug")
  if(UNIX)
    add_custom_command(TARGET unitrace_tool POST_BUILD COMMAND "rm" "-rf" "common_header.gen")
    add_custom_command(TARGET unitrace_tool POST_BUILD COMMAND "rm" "-rf" "tracing.gen")
    add_custom_command(TARGET unitrace_tool POST_BUILD COMMAND "rm" "-rf" "tracing.gen.types")
    add_custom_command(TARGET unitrace_tool POST_BUILD COMMAND "rm" "-rf" "l0_loader.gen")
  else()
    add_custom_command(TARGET unitrace_tool POST_BUILD COMMAND "del" "common_header.gen")
    add_custom_command(TARGET unitrace_tool POST_BUILD COMMAND "del" "tracing.gen")
    add_custom_command(TARGET unitrace_tool POST_BUILD COMMAND "del" "tracing.gen.types")
    add_custom_command(TARGET unitrace_tool POST_BUILD COMMAND "del" "l0_loader.gen")
  endif()
endif()

configure_file(${PROJECT_SOURCE_DIR}/scripts/uniview.py ${CMAKE_BINARY_DIR}/scripts/uniview.py COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/scripts/tracemerge/mergetrace.py ${CMAKE_BINARY_DIR}/scripts/tracemerge/mergetrace.py COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/scripts/metrics/analyzeperfmetrics.py ${CMAKE_BINARY_DIR}/scripts/metrics/analyzeperfmetrics.py COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/scripts/metrics/addrasm.py ${CMAKE_BINARY_DIR}/scripts/metrics/addrasm.py COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/scripts/metrics/config/pvc/ComputeBasic.txt ${CMAKE_BINARY_DIR}/scripts/metrics/config/pvc/ComputeBasic.txt COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/scripts/metrics/config/pvc/GpuOffload.txt ${CMAKE_BINARY_DIR}/scripts/metrics/config/pvc/GpuOffload.txt COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/scripts/metrics/config/pvc/MemProfile.txt ${CMAKE_BINARY_DIR}/scripts/metrics/config/pvc/MemProfile.txt COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/scripts/metrics/config/pvc/L1ProfileReads.txt ${CMAKE_BINARY_DIR}/scripts/metrics/config/pvc/L1ProfileReads.txt COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/scripts/metrics/config/pvc/L1ProfileWrites.txt ${CMAKE_BINARY_DIR}/scripts/metrics/config/pvc/L1ProfileWrites.txt COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/scripts/metrics/config/pvc/L1ProfileSlmBankConflicts.txt ${CMAKE_BINARY_DIR}/scripts/metrics/config/pvc/L1ProfileSlmBankConflicts.txt COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/scripts/metrics/config/bmg/ComputeBasic.txt ${CMAKE_BINARY_DIR}/scripts/metrics/config/bmg/ComputeBasic.txt COPYONLY)
configure_file(${PROJECT_SOURCE_DIR}/scripts/metrics/config/bmg/MemoryProfile.txt ${CMAKE_BINARY_DIR}/scripts/metrics/config/bmg/MemoryProfile.txt COPYONLY)

# Installation
install(TARGETS unitrace)
install(TARGETS unitrace_tool RUNTIME)
install(PROGRAMS ${PROJECT_SOURCE_DIR}/scripts/uniview.py DESTINATION bin)
install(PROGRAMS ${PROJECT_SOURCE_DIR}/scripts/tracemerge/mergetrace.py DESTINATION bin)
install(PROGRAMS ${PROJECT_SOURCE_DIR}/scripts/metrics/analyzeperfmetrics.py DESTINATION bin)
install(PROGRAMS ${PROJECT_SOURCE_DIR}/scripts/metrics/addrasm.py DESTINATION bin)
install(DIRECTORY ${PROJECT_SOURCE_DIR}/scripts/metrics/config DESTINATION etc/unitrace/metrics)
install(DIRECTORY ${PROJECT_SOURCE_DIR}/scripts/summary DESTINATION bin)

if (BUILD_WITH_MPI)
  install(TARGETS unitrace_mpi RUNTIME)
endif()
