#===============================================================================
# Copyright 2019-2024 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#===============================================================================

cmake_minimum_required(VERSION 2.8.12)

if("${CMAKE_BUILD_TYPE}" STREQUAL "")
    message(STATUS "CMAKE_BUILD_TYPE is unset, defaulting to Release")
    set(CMAKE_BUILD_TYPE "Release" CACHE STRING
        "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel ...")
endif()
string(TOUPPER "${CMAKE_BUILD_TYPE}" UPPERCASE_CMAKE_BUILD_TYPE)

project (DNNL_EXAMPLES)

set(DNNL_CPU_RUNTIME "SYCL")
set(DNNL_GPU_RUNTIME "SYCL")

if(POLICY CMP0015)
    cmake_policy(SET CMP0015 NEW)
endif()

# Use <PackageName>_ROOT env. variable as a prefix
if(POLICY CMP0074)
    cmake_policy(SET CMP0074 NEW)
endif()

set(DNNL_INSTALL_MODE "BUNDLE_V2")
set(IS_NEW_DIR_LAYOUT FALSE)
if(DNNL_INSTALL_MODE STREQUAL "BUNDLE_V2")
    set(IS_NEW_DIR_LAYOUT TRUE)
endif()

if(NOT DEFINED DNNLROOT AND DEFINED ENV{DNNLROOT})
    set(DNNLROOT "$ENV{DNNLROOT}" CACHE STRING "")
else()
    if(IS_NEW_DIR_LAYOUT)
        set(DNNLROOT "${PROJECT_SOURCE_DIR}/../../../.." CACHE STRING "")
    else()
        set(DNNLROOT "${PROJECT_SOURCE_DIR}/.." CACHE STRING "")
    endif()
endif()

message(STATUS "DNNLROOT: ${DNNLROOT}")

if(IS_NEW_DIR_LAYOUT)
    include_directories(${DNNLROOT}/share/doc/dnnl/examples)
else()
    include_directories(${DNNLROOT}/examples)
endif()

enable_testing()

if(DNNL_CPU_RUNTIME MATCHES "(SYCL|DPCPP)" OR DNNL_GPU_RUNTIME MATCHES "(SYCL|DPCPP)")
    set(DNNL_WITH_SYCL true)
else()
    set(DNNL_WITH_SYCL false)
endif()

if(DNNL_WITH_SYCL)
    include("dpcpp_driver_check.cmake")
endif()

if(CMAKE_BASE_NAME MATCHES "^(icx|icpx)$")
    # icx/icpx may issue a recommendation warning on using
    # -qopenmp over -fopenmp to enable some additional optimizations.
    # Suppress the warning to avoid breaking the build until we figure out
    # whether it makes sense to enable those optimizations.
    # Also, suppress a warning about unknown options as the older compiler
    # versions may not support "-Wno-recommended-option".
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-recommended-option -Wno-unknown-warning-option")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-recommended-option -Wno-unknown-warning-option")
endif()

function(find_libm var)
    # This is to account for the linker cache in OSX11.  might work
    # with lower than 3.9.4, but was not able to test with anything
    # between 2.8 and 3.9. See here for more details:
    # https://gitlab.kitware.com/cmake/cmake/-/issues/20863
    if (APPLE AND (${CMAKE_HOST_SYSTEM_VERSION} VERSION_GREATER "20.0.0")
           AND (${CMAKE_VERSION} VERSION_LESS "3.9.4"))
        message(INFO "Using OSX11 and above with CMAKE older than 3.18 can cause linking issues.")
        set(OSX11_AND_OLDER_CMAKE TRUE)
    endif()

    if(UNIX AND (NOT (APPLE AND OSX11_AND_OLDER_CMAKE)))
        find_library(${var} m REQUIRED)
    endif()
endfunction()


if(UNIX OR MINGW)
    find_libm(LIBM)
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99")

    if(NOT DNNL_WITH_SYCL)
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
    endif()

    if(NOT APPLE)
        set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed")
    endif()
    # XXX: DPC++ compiler generates a lot of warnings
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w")
endif()

if(WIN32 AND ${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
    add_definitions(/Qpar)
    add_definitions(/openmp)
else()
    find_package(OpenMP)
    #newer version for findOpenMP (>= v. 3.9)
    if(CMAKE_VERSION VERSION_LESS "3.9" AND OPENMP_FOUND)
        if(${CMAKE_MAJOR_VERSION} VERSION_LESS "3" AND
                ${CMAKE_CXX_COMPILER_ID} STREQUAL "Intel")
            # Override FindOpenMP flags for Intel Compiler (otherwise deprecated)
            set(OpenMP_CXX_FLAGS "-fopenmp")
            set(OpenMP_C_FLAGS "-fopenmp")
        endif()
        set(OpenMP_C_FOUND true)
        set(OpenMP_CXX_FOUND true)
    endif()
    if(OpenMP_C_FOUND)
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
    endif()
    if(OpenMP_CXX_FOUND)
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
    endif()
endif()

if(UPPERCASE_CMAKE_BUILD_TYPE STREQUAL "DEBUG" AND NOT APPLE)
    if(${CMAKE_CXX_COMPILER_ID} MATCHES "IntelLLVM" OR CMAKE_BASE_NAME MATCHES "(icx|icpx|dpcpp)" OR DNNL_WITH_SYCL)
        # When using Debug build mode CMake adds '-debug' or '-g' without
        # any optimization-level option that will turn off most compiler
        # optimizations similar to use of '-Od' or '-O0'.
        # Here we disable the warning and remark aboiut this issue.
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-debug-disables-optimization -Rno-debug-disables-optimization")
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-debug-disables-optimization -Rno-debug-disables-optimization")
        if(WIN32)
            # Disabling OMP SIMD feature on Windows ICX debug builds and
            # suppressing the warning: Using /MTd or /MDd with '#pragma omp simd'
            # may lead to unexpected fails due to the debugging version of iterators
            # that cannot be vectorized correctly.
            set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Qopenmp-simd- -Wno-debug-option-simd")
            set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Qopenmp-simd- -Wno-debug-option-simd")
        endif()
    endif()
endif()

if(WIN32 AND DNNL_WITH_SYCL)
    # XXX: SYCL does not like __thiscall convention coming from TBB,
    # suppress warnings for now.
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-ignored-attributes")

    # XXX: compiler always pulls in release C++ runtime by default, until
    # this is fixed we have to explicitly drop release C++ runtime for
    # debug build types.
    if(UPPERCASE_CMAKE_BUILD_TYPE STREQUAL "DEBUG" AND NOT CMAKE_BASE_NAME MATCHES "(icx|dpcpp)")
        set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Xlinker /NODEFAULTLIB:msvcrt")
    endif()
endif()

if(NOT IS_NEW_DIR_LAYOUT)
    # DNNL_CONFIGURATION is only used by oneAPI CMake config and ignored otherwise.
    get_filename_component(DNNL_CONFIGURATION "${DNNLROOT}" ABSOLUTE)
    get_filename_component(DNNL_CONFIGURATION "${DNNL_CONFIGURATION}" NAME)
    # The hint can be used to find CMake config in oneAPI directory layout.
    set(ONEAPI_CMAKE_CONFIG_HINT "../..")
endif()

find_package(dnnl 3.6.1 CONFIG REQUIRED HINTS ${DNNLROOT} ${ONEAPI_CMAKE_CONFIG_HINT})

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DNNL_COMPILE_FLAGS}")

if(WIN32)
    set(CTESTCONFIG_PATH "${DNNLROOT}\\bin")
elseif(APPLE)
    set(CTESTCONFIG_PATH "${DNNLROOT}/lib")
endif()

# Common configuration for tests / test cases on Windows and Apple
function(maybe_configure_test name kind)
    if(WIN32)
        string(REPLACE  ";" "\;" PATH "${CTESTCONFIG_PATH};$ENV{PATH}")
        set_property(${kind} ${name} PROPERTY ENVIRONMENT "PATH=${PATH}")
        if(CMAKE_GENERATOR MATCHES "Visual Studio")
            configure_file(template.vcxproj.user ${name}.vcxproj.user @ONLY)
        endif()
    elseif(APPLE)
        # When LIBRARY_PATH is set (e.g. when using compiler env. scripts)
        # cmake may stop passing `rpath` linker option. The hack below adds the
        # LIBRARY_PATH to DYLD_LIBRARY_PATH to make the executable find its
        # dependencies.
        # TODO: the problem may be in older version of cmake (2.8.11), revisit.
        set_property(${kind} ${name} PROPERTY ENVIRONMENT
                "DYLD_LIBRARY_PATH=${CTESTCONFIG_PATH}:$ENV{LIBRARY_PATH}:$ENV{DYLD_LIBRARY_PATH}")
    endif()
endfunction()

# Register example
#   name -- name of the executable
#   srcs -- list of source, if many must be enclosed with ""
#   test -- "test" to mark executable as a test, "" otherwise
function(register_example name srcs test)
    add_executable(${name} ${srcs})
    string(TOUPPER "dnnl" LIB_NAMESPACE)
    target_link_libraries(${name} ${LIB_NAMESPACE}::dnnl ${LIBM})
    if("x${test}" STREQUAL "xtest")
        add_test(${name} ${name})
        maybe_configure_test("${example_name}" TEST)
    endif()
endfunction()

file(GLOB sources *.cpp *.c)
file(GLOB_RECURSE tutorial_sources tutorials/*.cpp tutorials/*.c)
list(APPEND sources ${tutorial_sources})

foreach(src ${sources})
    file(RELATIVE_PATH src_rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${src})
    string(REGEX REPLACE "[/_\\.]" "-" example_name ${src_rel_path})

    # Put hw-specific part of the name in front.
    # It is important for examples in subdirectories.
    foreach(pat "cpu-" "gpu-" "cross-engine-")
        string(REGEX REPLACE "^(.*)${pat}" "${pat}\\1"
            example_name ${example_name})
    endforeach()

    if(${example_name} MATCHES "(cross-engine|cpu|gpu)-")
        # Example name contains cross-engine, cpu or gpu
        if(NOT ${example_name} MATCHES ".*opencl" OR DNNL_GPU_RUNTIME STREQUAL "OCL")
            register_example(${example_name} ${src} "test" ${LIBM})
        endif()
    else()
        set(cpu_rt_pattern "(SEQ|OMP|TBB|SYCL|DPCPP)")
        set(gpu_rt_pattern "(OCL|SYCL|DPCPP)")
        if(${example_name} MATCHES "sycl.*")
            set(cpu_rt_pattern "(SYCL|DPCPP)")
            set(gpu_rt_pattern "(SYCL|DPCPP)")
        endif()
        if(DNNL_CPU_RUNTIME MATCHES ${cpu_rt_pattern})
            # Adding test for CPU
            add_test("cpu-${example_name}" "${example_name}" cpu)
            maybe_configure_test("cpu-${example_name}" TEST)
        endif()
        if(DNNL_GPU_RUNTIME MATCHES ${gpu_rt_pattern})
            # Adding test for GPU
            add_test("gpu-${example_name}" "${example_name}" gpu)
            maybe_configure_test("gpu-${example_name}" TEST)
        endif()
        register_example(${example_name} ${src} "" ${LIBM})
    endif()
endforeach()
