cmake_minimum_required(VERSION 3.4.3)

include(CheckSymbolExists)

project(test-suite C CXX)

# The test-suite is designed to be built in release mode anyway and
# falls over unless -DNDEBUG is set.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  message(STATUS "No build type selected, defaulting to Release")
  set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE)
endif()
# Selecting installation directories or build types is untypical.
mark_as_advanced(CMAKE_INSTALL_PREFIX CMAKE_BUILD_TYPE)
# On the other hand we often want to switch compiler or cflags
mark_as_advanced(CLEAR CMAKE_C_COMPILER CMAKE_CXX_COMPILER CMAKE_LINKER
  CMAKE_C_FLAGS CMAKE_CXX_FLAGS CMAKE_EXE_LINKER_FLAGS)

# The files in cmake/caches often want to pass along additional flags to select
# the target architecture. Note that you should still use
# CMAKE_OSX_ARCHITECTURES and CMAKE_C_COMPILER_TARGET where possible.
set(TEST_SUITE_ARCH_FLAGS CACHE STRING
   "Extra flags to select target architecture.")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${TEST_SUITE_ARCH_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TEST_SUITE_ARCH_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${TEST_SUITE_ARCH_FLAGS}")

set(LLVM_CODESIGNING_IDENTITY "" CACHE STRING
  "Sign executables and dylibs with the given identity or skip if empty (Darwin Only)")

add_definitions(-DNDEBUG)
option(TEST_SUITE_SUPPRESS_WARNINGS "Suppress all warnings" ON)
if(${TEST_SUITE_SUPPRESS_WARNINGS})
  add_definitions(-w)
endif()

# We want reproducible builds, so using __DATE__ and __TIME__ is bad
add_definitions(-Werror=date-time)

# Add path for custom modules
set(CMAKE_MODULE_PATH
  ${CMAKE_MODULE_PATH}
  "${CMAKE_CURRENT_SOURCE_DIR}/cmake"
  "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules"
  )

# Sanity check our source directory to make sure that we are not trying to
# generate an in-tree build (unless on MSVC_IDE, where it is ok), and to make
# sure that we don't have any stray generated files lying around in the tree
# (which would end up getting picked up by header search, instead of the correct
# versions).
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR AND NOT MSVC_IDE)
  message(FATAL_ERROR "In-source builds are not allowed.
CMake would overwrite the makefiles distributed with LLVM.
Please create a directory and run cmake from there, passing the path
to this source directory as the last argument.
This process created the file `CMakeCache.txt' and the directory `CMakeFiles'.
Please delete them.")
endif()

# Sanity check SIZEOF_VOID_P. This is sometimes empty when compiler detection
# failed, error out to avoid a mostly working but invalid setup.
if(NOT CMAKE_SIZEOF_VOID_P)
  message(FATAL_ERROR "CMAKE_SIZEOF_VOID_P is not defined")
endif()

# Remote configuration (will be set in lit.site.cfg)
set(TEST_SUITE_REMOTE_CLIENT "ssh" CACHE STRING "Remote execution client")
set(TEST_SUITE_REMOTE_HOST "" CACHE STRING "Remote execution host")
mark_as_advanced(TEST_SUITE_REMOTE_CLIENT)

if(TEST_SUITE_REMOTE_HOST)
  add_custom_target(rsync
    COMMAND ${PROJECT_SOURCE_DIR}/utils/rsync.sh
                ${TEST_SUITE_REMOTE_HOST} ${PROJECT_BINARY_DIR}
    USES_TERMINAL
  )
endif()

# Run Under configuration for RunSafely.sh (will be set in lit.site.cfg)
set(TEST_SUITE_RUN_UNDER "" CACHE STRING "RunSafely.sh run-under (-u) parameter")

# User mode emulation configuration (e.g. running under qemu)
# (will be set in lit.site.cfg)
set(TEST_SUITE_USER_MODE_EMULATION NO CACHE BOOL
    "RUN_UNDER is used to run tests under emulation.")
# Set value to python style True/False
if (TEST_SUITE_USER_MODE_EMULATION)
  set(TEST_SUITE_USER_MODE_EMULATION "True")
else()
  set(TEST_SUITE_USER_MODE_EMULATION "False")
endif()


# run type/benchmark size configuration (mostly for SPEC at the moment)
set(TEST_SUITE_RUN_TYPE "train" CACHE STRING
    "Type of benchmark inputs (may be test,train or ref)")

get_filename_component(CMAKE_C_COMPILER_DIRECTORY ${CMAKE_C_COMPILER} DIRECTORY)

option(TEST_SUITE_COLLECT_CODE_SIZE "Measure code size of binaries" ON)
if(TEST_SUITE_COLLECT_CODE_SIZE)
  find_program(TEST_SUITE_LLVM_SIZE NAMES "llvm-size"
               HINTS ${CMAKE_C_COMPILER_DIRECTORY})
  mark_as_advanced(TEST_SUITE_LLVM_SIZE)
  if(TEST_SUITE_LLVM_SIZE STREQUAL "TEST_SUITE_LLVM_SIZE-NOTFOUND")
    message(FATAL_ERROR "llvm-size not found.
Make sure it is in your path or set TEST_SUITE_COLLECT_CODE_SIZE to OFF")
  endif()
endif()

option(TEST_SUITE_USE_IR_PGO
  "Use IR PGO instrumentation (requires TEST_SUITE_PROFILE_GENERATE)" OFF)

# Enable profile generate mode in lit. Note that this does not automatically
# add something like -fprofile-instr-generate to the compiler flags.
option(TEST_SUITE_PROFILE_GENERATE "Enable lit profile generate mode" OFF)
# Set value to python style True/False
if(TEST_SUITE_PROFILE_GENERATE)
  find_program(TEST_SUITE_LLVM_PROFDATA NAMES "llvm-profdata"
               HINTS ${CMAKE_C_COMPILER_DIRECTORY})
  mark_as_advanced(TEST_SUITE_LLVM_PROFDATA)
  if(TEST_SUITE_LLVM_PROFDATA STREQUAL "TEST_SUITE_LLVM_PROFDATA-NOTFOUND")
    message(FATAL_ERROR "llvm-profdata not found.
Make sure it is in your path or set TEST_SUITE_PROFILE_GENERATE to OFF")
  endif()

  set(TEST_SUITE_PROFILE_GENERATE "True")

  set(profile_instrumentation_flags -fprofile-instr-generate)
  if(TEST_SUITE_USE_IR_PGO)
    set(profile_instrumentation_flags -fprofile-generate)
  endif()

  list(APPEND CFLAGS   ${profile_instrumentation_flags})
  list(APPEND CXXFLAGS ${profile_instrumentation_flags})
  list(APPEND LDFLAGS  ${profile_instrumentation_flags})
else()
  set(TEST_SUITE_PROFILE_GENERATE "False")
endif()

option(TEST_SUITE_PROFILE_USE
      "Add apropriate -fprofile-instr-use to CFLAGS/CXXFLAGS for each benchmark"
      OFF)

# When running the test-suite in diagnosis mode, use these flags passed by
# LNT to gather data, for examples -ftime-report, or -mllvm -stats. This way
# the user specified CMAKE_C_FLAGS etc. need not be changed.
set(TEST_SUITE_DIAGNOSE_FLAGS CACHE STRING
   "Extra flags appended to CMAKE_C_FLAGS + CMAKE_CXX_FLAGS")
set(TEST_SUITE_DIAGNOSE_LINKER_FLAGS CACHE STRING
    "Extra flags appended to CMAKE_EXE_LINKER_FLAGS")
mark_as_advanced(TEST_SUITE_DIAGNOSE_FLAGS TEST_SUITE_DIAGNOSE_LINKER_FLAGS)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${TEST_SUITE_DIAGNOSE_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TEST_SUITE_DIAGNOSE_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS
    "${CMAKE_EXE_LINKER_FLAGS} ${TEST_SUITE_DIAGNOSE_LINKER_FLAGS}")

# Append extra flags. These extra flags are mainly meant for cache files that
# want to apply flags that get not override even when the user manually
# specifies CMAKE_C_FLAGS and similar.
set(TEST_SUITE_EXTRA_C_FLAGS CACHE STRING "Extra flags for CMAKE_C_FLAGS")
set(TEST_SUITE_EXTRA_CXX_FLAGS CACHE STRING "Extra flags for CMAKE_CXX_FLAGS")
set(TEST_SUITE_EXTRA_EXE_LINKER_FLAGS CACHE STRING
    "Extra flags for CMAKE_EXE_LINKER_FLAGS")
mark_as_advanced(TEST_SUITE_EXTRA_C_FLAGS, TEST_SUITE_EXTRA_CXX_FLAGS,
                 TEST_SUITE_EXTRA_EXE_LINKER_FLAGS)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${TEST_SUITE_EXTRA_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TEST_SUITE_EXTRA_CXX_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS
    "${CMAKE_EXE_LINKER_FLAGS} ${TEST_SUITE_EXTRA_EXE_LINKER_FLAGS}")

# This is either directly the C++ ABI library or the full C++ library
# which pulls in the ABI transitively.
set(TEST_SUITE_CXX_ABI "default" CACHE STRING "Specify C++ ABI library to use.")
set(CXXABIS "none default libstdc++ libsupc++ libc++ libc++abi")
set_property(CACHE TEST_SUITE_CXX_ABI PROPERTY STRINGS "${CXXABIS}")
if (TEST_SUITE_CXX_ABI STREQUAL "default")
  if (APPLE OR CMAKE_SYSTEM_NAME MATCHES "FreeBSD|Fuchsia")
    set(TEST_SUITE_CXX_ABI_LIBNAME "c++abi")
  else()
    set(TEST_SUITE_CXX_ABI_LIBNAME "supc++")
  endif()
else()
  STRING(REGEX REPLACE "^lib" "" TEST_SUITE_CXX_ABI_LIBNAME ${TEST_SUITE_CXX_ABI})
endif()

include(TestSuite)
include(SingleMultiSource)
find_package(TCL)
find_package(OpenMP)

if(NOT DEFINED TARGET_OS)
  message(STATUS "Check target operating system - ${CMAKE_SYSTEM_NAME}")
  set(TARGET_OS ${CMAKE_SYSTEM_NAME})
endif()
if(NOT DEFINED ARCH)
  include(DetectArchitecture)
  detect_architecture(ARCH)
endif()
if(NOT DEFINED X86CPU_ARCH AND ARCH STREQUAL "x86")
  include(DetectArchitecture)
  detect_x86_cpu_architecture(X86CPU_ARCH)
endif()
if(NOT DEFINED ENDIAN)
  include(TestBigEndian)
  test_big_endian(IS_BIGENDIAN)
  if(IS_BIGENDIAN)
    set(ENDIAN "big")
  else()
    set(ENDIAN "little")
  endif()
endif()

# Disabling address space randomization makes the performance of memory/cache
# intensive benchmarks more deterministic.
option(TEST_SUITE_DISABLE_PIE
       "Disable position independent executables and ASLR" ON)
mark_as_advanced(TEST_SUITE_DISABLE_PIE)
if(TEST_SUITE_DISABLE_PIE)
  if(APPLE AND NOT ARCH STREQUAL "AArch64")
    list(APPEND LDFLAGS -Wl,-no_pie)
  endif()
  # TODO: Add apropriate flags to disable PIE/ASLR on linux, bsd, ...
endif()

if(ARCH STREQUAL "Mips")
  check_symbol_exists(__mips16 "" MIPS_IS_MIPS16_ENABLED)
  check_symbol_exists(__mips64 "" MIPS_IS_MIPS64_ENABLED)
endif()

find_program(TEST_SUITE_LIT NAMES "lit" "llvm-lit")
set(TEST_SUITE_LIT_FLAGS "-sv" CACHE STRING "Flags used when running lit")
mark_as_advanced(TEST_SUITE_LIT TEST_SUITE_LIT_FLAGS)
mark_as_advanced(TEST_SUITE_LIT)

add_subdirectory(tools)
# Shortcut for the path to the fpcmp executable
set(FPCMP ${CMAKE_BINARY_DIR}/tools/fpcmp-target)
if (TEST_SUITE_USER_MODE_EMULATION)
  set(FPCMP ${CMAKE_BINARY_DIR}/tools/fpcmp)
endif()

option(TEST_SUITE_COLLECT_COMPILE_TIME
       "Measure compile time by wrapping compiler invocations in timeit" ON)
if(TEST_SUITE_COLLECT_COMPILE_TIME)
  set(CMAKE_C_COMPILE_OBJECT "${CMAKE_BINARY_DIR}/tools/timeit --summary <OBJECT>.time ${CMAKE_C_COMPILE_OBJECT}")
  set(CMAKE_CXX_COMPILE_OBJECT "${CMAKE_BINARY_DIR}/tools/timeit --summary <OBJECT>.time ${CMAKE_CXX_COMPILE_OBJECT}")
  set(CMAKE_C_LINK_EXECUTABLE "${CMAKE_BINARY_DIR}/tools/timeit --summary <TARGET>.link.time ${CMAKE_C_LINK_EXECUTABLE}")
  set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_BINARY_DIR}/tools/timeit --summary <TARGET>.link.time ${CMAKE_CXX_LINK_EXECUTABLE}")
endif()

option(TEST_SUITE_BENCHMARKING_ONLY "Only run the benchmarking only subset" OFF)

option(TEST_SUITE_COLLECT_STATS "Collect LLVM statistics" OFF)
if(TEST_SUITE_COLLECT_STATS)
  list(APPEND CFLAGS -save-stats=obj)
  list(APPEND CXXFLAGS -save-stats=obj)
  # Collect stats for LTO step too.
  if (${CMAKE_C_FLAGS} MATCHES ".*-flto.*" AND
      ${CMAKE_CXX_FLAGS} MATCHES ".*-flto.*")
    list(APPEND LDFLAGS -save-stats=obj)
  endif()
endif()

# Detect and include subdirectories
# This allows to: Place additional test-suites into the toplevel test-suite
# directory where they will be picked up automatically. Alternatively you may
# manually specify directories to include test-suites at external locations
# and to leave out some of the default ones.
if(NOT TEST_SUITE_SUBDIRS)
  file(GLOB sub_cmakelists RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} */CMakeLists.txt)
  set(TEST_SUITE_SUBDIRS "")
  foreach(entry ${sub_cmakelists})
    get_filename_component(subdir ${entry} DIRECTORY)
	 # Exclude tools and CTMark from default list
    if(NOT ${subdir} STREQUAL tools AND NOT ${subdir} STREQUAL CTMark)
      list(APPEND TEST_SUITE_SUBDIRS ${subdir})
    endif()
  endforeach()
  set(TEST_SUITE_SUBDIRS "${TEST_SUITE_SUBDIRS}")
endif()
set(TEST_SUITE_SUBDIRS "${TEST_SUITE_SUBDIRS}" CACHE STRING
    "Semicolon separated list of directories with CMakeLists.txt to include")

foreach(subdir ${TEST_SUITE_SUBDIRS})
  # When we add subdirs outside the toplevel source directory then we have to
  # make up a directory to use for the builddir.
  if(subdir MATCHES "^/" OR subdir MATCHES "\\.\\.")
    if(subdir MATCHES "/$")
      message(FATAL_ERROR "Subdir must not end in '/'")
    endif()
    get_filename_component(subdir_name ${subdir} NAME)
  else()
    set(subdir_name ${subdir})
  endif()
  message(STATUS "Adding directory ${subdir}")
  add_subdirectory(${subdir} ${subdir_name})
endforeach()

option(TEST_SUITE_RUN_BENCHMARKS "Actually run the benchmarks in lit" ON)

set(TEST_SUITE_EXTRA_LIT_MODULES "" CACHE STRING
    "Semicolon separated list of extra lit modules in use")
mark_as_advanced(TEST_SUITE_EXTRA_LIT_MODULES)
# Construct list testing modules (see also litsupport/modules/*.py)
set(LIT_MODULES ${TEST_SUITE_EXTRA_LIT_MODULES})
if(TEST_SUITE_RUN_BENCHMARKS)
  list(APPEND LIT_MODULES run)
endif()
if(TEST_SUITE_COLLECT_CODE_SIZE)
  list(APPEND LIT_MODULES codesize)
endif()
list(APPEND LIT_MODULES hash)
if(TEST_SUITE_COLLECT_COMPILE_TIME)
  list(APPEND LIT_MODULES compiletime)
endif()
if(TEST_SUITE_RUN_UNDER)
  list(APPEND LIT_MODULES run_under)
endif()
list(APPEND LIT_MODULES timeit)
if(TEST_SUITE_PROFILE_GENERATE)
  list(APPEND LIT_MODULES profilegen)
endif()
if(TEST_SUITE_COLLECT_STATS)
  list(APPEND LIT_MODULES stats)
endif()

# Produce lit.site.cfg
configure_file("${PROJECT_SOURCE_DIR}/lit.site.cfg.in" "${CMAKE_BINARY_DIR}/lit.site.cfg")

get_property(TEST_SUITE_TARGETS GLOBAL PROPERTY TEST_SUITE_TARGETS)
add_custom_target(check
  COMMAND ${TEST_SUITE_LIT} ${TEST_SUITE_LIT_FLAGS} .
  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
  DEPENDS ${TEST_SUITE_TARGETS}
  USES_TERMINAL
  )
