# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.

#.rst:
# FindOpenMP
# ----------
#
# Finds OpenMP support
#
# This module can be used to detect OpenMP support in a compiler.  If
# the compiler supports OpenMP, the flags required to compile with
# OpenMP support are returned in variables for the different languages.
# The variables may be empty if the compiler does not need a special
# flag to support OpenMP.
#
# Variables
# ^^^^^^^^^
#
# The module exposes the components ``C``, ``CXX``, and ``Fortran``.
# Each of these controls the various languages to search OpenMP support for.
#
# Depending on the enabled components the following variables will be set:
#
# ``OpenMP_FOUND``
#   Variable indicating that OpenMP flags for all requested languages have been found.
#   If no components are specified, this is true if OpenMP settings for all enabled languages
#   were detected.
# ``OpenMP_VERSION``
#   Minimal version of the OpenMP standard detected among the requested languages,
#   or all enabled languages if no components were specified.
#
# This module will set the following variables per language in your
# project, where ``<lang>`` is one of C, CXX, or Fortran:
#
# ``OpenMP_<lang>_FOUND``
#   Variable indicating if OpenMP support for ``<lang>`` was detected.
# ``OpenMP_<lang>_FLAGS``
#   OpenMP compiler flags for ``<lang>``, separated by spaces.
#
# For linking with OpenMP code written in ``<lang>``, the following
# variables are provided:
#
# ``OpenMP_<lang>_LIB_NAMES``
#   :ref:`;-list <CMake Language Lists>` of libraries for OpenMP programs for ``<lang>``.
# ``OpenMP_<libname>_LIBRARY``
#   Location of the individual libraries needed for OpenMP support in ``<lang>``.
# ``OpenMP_<lang>_LIBRARIES``
#   A list of libraries needed to link with OpenMP code written in ``<lang>``.
#
# Additionally, the module provides :prop_tgt:`IMPORTED` targets:
#
# ``OpenMP::OpenMP_<lang>``
#   Target for using OpenMP from ``<lang>``.
#
# Specifically for Fortran, the module sets the following variables:
#
# ``OpenMP_Fortran_HAVE_OMPLIB_HEADER``
#   Boolean indicating if OpenMP is accessible through ``omp_lib.h``.
# ``OpenMP_Fortran_HAVE_OMPLIB_MODULE``
#   Boolean indicating if OpenMP is accessible through the ``omp_lib`` Fortran module.
#
# The module will also try to provide the OpenMP version variables:
#
# ``OpenMP_<lang>_SPEC_DATE``
#   Date of the OpenMP specification implemented by the ``<lang>`` compiler.
# ``OpenMP_<lang>_VERSION_MAJOR``
#   Major version of OpenMP implemented by the ``<lang>`` compiler.
# ``OpenMP_<lang>_VERSION_MINOR``
#   Minor version of OpenMP implemented by the ``<lang>`` compiler.
# ``OpenMP_<lang>_VERSION``
#   OpenMP version implemented by the ``<lang>`` compiler.
#
# The specification date is formatted as given in the OpenMP standard:
# ``yyyymm`` where ``yyyy`` and ``mm`` represents the year and month of
# the OpenMP specification implemented by the ``<lang>`` compiler.

cmake_policy(PUSH)
cmake_policy(SET CMP0012 NEW) # if() recognizes numbers and booleans
cmake_policy(SET CMP0054 NEW) # if() quoted variables not dereferenced
cmake_policy(SET CMP0057 NEW) # if IN_LIST


if(NOT "$ENV{OMP_PREFIX}" STREQUAL "")
  set(OpenMP_PREFIX "$ENV{OMP_PREFIX}")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" AND EXISTS /opt/homebrew/opt/libomp)
  set(OpenMP_PREFIX "/opt/homebrew/opt/libomp")
endif()

function(_OPENMP_FLAG_CANDIDATES LANG)
  if(NOT OpenMP_${LANG}_FLAG)
    unset(OpenMP_FLAG_CANDIDATES)

    set(OMP_FLAG_GNU "-fopenmp")
    if(CMAKE_${LANG}_COMPILER_ID STREQUAL "Clang" AND CMAKE_${LANG}_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
      # clang-cl specific flags
      set(OMP_FLAG_Clang "-Xclang -fopenmp=libomp" "-Xclang -fopenmp=libiomp5" "-Xclang -fopenmp")
    else()
      # regular clang flags
      set(OMP_FLAG_Clang "-fopenmp=libomp" "-fopenmp=libiomp5" "-fopenmp")
    endif()

    if(WIN32)
      # Prefer Intel OpenMP header which can be provided by CMAKE_INCLUDE_PATH.
      # Note that CMAKE_INCLUDE_PATH is searched before CMAKE_SYSTEM_INCLUDE_PATH (MSVC path in this case)
      find_path(__header_dir "omp.h")
    else()
      # AppleClang may need a header file, search for omp.h with hints to brew
      # default include dir
      find_path(__header_dir "omp.h" HINTS "/usr/local/include" "${OpenMP_PREFIX}/include")
    endif()
    set(OMP_FLAG_AppleClang "-Xpreprocessor -fopenmp" "-Xpreprocessor -fopenmp -I${__header_dir}")

    set(OMP_FLAG_HP "+Oopenmp")
    if(WIN32)
      set(OMP_FLAG_Intel "-Qopenmp")
    elseif(CMAKE_${LANG}_COMPILER_ID STREQUAL "Intel" AND
           "${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS "15.0.0.20140528")
      set(OMP_FLAG_Intel "-openmp")
    else()
      set(OMP_FLAG_Intel "-qopenmp")
    endif()
    set(OMP_FLAG_MIPSpro "-mp")
    if(__header_dir MATCHES ".*Microsoft Visual Studio.*")
      # MSVC header. No need to pass it as additional include.
      set(OMP_FLAG_MSVC "-openmp:experimental" "-openmp")
    else()
      set(OMP_FLAG_MSVC "-openmp:experimental -I${__header_dir}" "-openmp -I${__header_dir}")
    endif()
    set(OMP_FLAG_PathScale "-openmp")
    set(OMP_FLAG_NAG "-openmp")
    set(OMP_FLAG_Absoft "-openmp")
    set(OMP_FLAG_PGI "-mp")
    set(OMP_FLAG_Flang "-fopenmp")
    set(OMP_FLAG_SunPro "-xopenmp")
    set(OMP_FLAG_XL "-qsmp=omp")
    # Cray compiler activate OpenMP with -h omp, which is enabled by default.
    set(OMP_FLAG_Cray " " "-h omp")

    # If we know the correct flags, use those
    if(DEFINED OMP_FLAG_${CMAKE_${LANG}_COMPILER_ID})
      set(OpenMP_FLAG_CANDIDATES "${OMP_FLAG_${CMAKE_${LANG}_COMPILER_ID}}")
    # Fall back to reasonable default tries otherwise
    else()
      set(OpenMP_FLAG_CANDIDATES "-openmp" "-fopenmp" "-mp" " ")
    endif()
    set(OpenMP_${LANG}_FLAG_CANDIDATES "${OpenMP_FLAG_CANDIDATES}" PARENT_SCOPE)
  else()
    set(OpenMP_${LANG}_FLAG_CANDIDATES "${OpenMP_${LANG}_FLAG}" PARENT_SCOPE)
  endif()
endfunction()

# sample openmp source code to test
set(OpenMP_C_CXX_TEST_SOURCE
"
#include <omp.h>
int main(void) {
#ifdef _OPENMP
  omp_get_max_threads();
  return 0;
#else
  breaks_on_purpose
#endif
}
")

# in Fortran, an implementation may provide an omp_lib.h header
# or omp_lib module, or both (OpenMP standard, section 3.1)
# Furthmore !$ is the Fortran equivalent of #ifdef _OPENMP (OpenMP standard, 2.2.2)
# Without the conditional compilation, some compilers (e.g. PGI) might compile OpenMP code
# while not actually enabling OpenMP, building code sequentially
set(OpenMP_Fortran_TEST_SOURCE
  "
      program test
      @OpenMP_Fortran_INCLUDE_LINE@
  !$  integer :: n
      n = omp_get_num_threads()
      end program test
  "
)

function(_OPENMP_WRITE_SOURCE_FILE LANG SRC_FILE_CONTENT_VAR SRC_FILE_NAME SRC_FILE_FULLPATH)
  set(WORK_DIR ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/FindOpenMP)
  if("${LANG}" STREQUAL "C")
    set(SRC_FILE "${WORK_DIR}/${SRC_FILE_NAME}.c")
    file(WRITE "${SRC_FILE}" "${OpenMP_C_CXX_${SRC_FILE_CONTENT_VAR}}")
  elseif("${LANG}" STREQUAL "CXX")
    set(SRC_FILE "${WORK_DIR}/${SRC_FILE_NAME}.cpp")
    file(WRITE "${SRC_FILE}" "${OpenMP_C_CXX_${SRC_FILE_CONTENT_VAR}}")
  elseif("${LANG}" STREQUAL "Fortran")
    set(SRC_FILE "${WORK_DIR}/${SRC_FILE_NAME}.f90")
    file(WRITE "${SRC_FILE}_in" "${OpenMP_Fortran_${SRC_FILE_CONTENT_VAR}}")
    configure_file("${SRC_FILE}_in" "${SRC_FILE}" @ONLY)
  endif()
  set(${SRC_FILE_FULLPATH} "${SRC_FILE}" PARENT_SCOPE)
endfunction()

include(CMakeParseImplicitLinkInfo)

function(_OPENMP_GET_FLAGS LANG FLAG_MODE OPENMP_FLAG_VAR OPENMP_LIB_NAMES_VAR)
  _OPENMP_FLAG_CANDIDATES("${LANG}")
  _OPENMP_WRITE_SOURCE_FILE("${LANG}" "TEST_SOURCE" OpenMPTryFlag _OPENMP_TEST_SRC)

  unset(OpenMP_VERBOSE_COMPILE_OPTIONS)
  if(UNIX)
    separate_arguments(OpenMP_VERBOSE_OPTIONS UNIX_COMMAND "${CMAKE_${LANG}_VERBOSE_FLAG}")
  else()
    separate_arguments(OpenMP_VERBOSE_OPTIONS WINDOWS_COMMAND "${CMAKE_${LANG}_VERBOSE_FLAG}")
  endif()
  foreach(_VERBOSE_OPTION IN LISTS OpenMP_VERBOSE_OPTIONS)
    if(NOT _VERBOSE_OPTION MATCHES "^-Wl,")
      list(APPEND OpenMP_VERBOSE_COMPILE_OPTIONS ${_VERBOSE_OPTION})
    endif()
  endforeach()

  foreach(OPENMP_FLAG IN LISTS OpenMP_${LANG}_FLAG_CANDIDATES)
    set(OPENMP_FLAGS_TEST "${OPENMP_FLAG}")
    if(OpenMP_VERBOSE_COMPILE_OPTIONS)
      string(APPEND OPENMP_FLAGS_TEST " ${OpenMP_VERBOSE_COMPILE_OPTIONS}")
    endif()
    string(REGEX REPLACE "[-/=+]" "" OPENMP_PLAIN_FLAG "${OPENMP_FLAG}")

    # NOTE [ Linking both MKL and OpenMP ]
    #
    # It is crucial not to link two `libomp` libraries together, even when they
    # are both Intel or GNU. Otherwise, you will end up with this nasty error,
    # and may get incorrect results.
    #
    #   OMP: Error #15: Initializing libomp.dylib, but found libiomp5.dylib
    #   already initialized.
    #
    #   OMP: Hint This means that multiple copies of the OpenMP runtime have
    #   been linked into the program. That is dangerous, since it can degrade
    #   performance or cause incorrect results. The best thing to do is to
    #   ensure that only a single OpenMP runtime is linked into the process,
    #   e.g. by avoiding static linking of the OpenMP runtime in any library. As
    #   an unsafe, unsupported, undocumented workaround you can set the
    #   environment variable KMP_DUPLICATE_LIB_OK=TRUE to allow the program to
    #   continue to execute, but that may cause crashes or silently produce
    #   incorrect results. For more information, please see
    #   http://openmp.llvm.org/
    #
    # So here, before we test each flag combination, we first try directly
    # linking against any `libomp` MKL has linked to (if any and when MKL is
    # specified). This allows us to do sensible things in tricky (yet common)
    # conditions like:
    #   - using `clang` (so no native GNU OpenMP), and
    #   - having `brew` `libomp` installed at `/usr/local/`, and
    #   - having `conda` `mkl` installed at `$HOME/conda/`, with includes a copy
    #     of `libiomp5`.
    # Rather than blindly picking one, we pick what ever `FindMKL.cmake` chooses
    # to avoid conflicts.
    #
    # `FindMKL.cmake` calls `FindOpenMP.cmake` when trying to find `gomp` and
    # thus will cause infinite recursion if this is not taken care of. Therefore,
    # we record an internal flag to detect repeatedly inclusion.

    if(NOT MKL_OPENMP_LIBRARY AND NOT IN_FIND_OMP AND BLAS STREQUAL "MKL")
      set(IN_FIND_OMP ON CACHE BOOL "" FORCE)
      find_package(MKL QUIET)
      unset(IN_FIND_OMP CACHE)
    endif()

    if(MKL_OPENMP_LIBRARY)
      # If we already link OpenMP via MKL, use that. Otherwise at run-time
      # OpenMP will complain about being initialized twice (OMP: Error #15),
      # can may cause incorrect behavior.
      set(OpenMP_libomp_LIBRARY "${MKL_OPENMP_LIBRARY}" CACHE STRING "libomp location for OpenMP")
    endif()

    if ((NOT OpenMP_libomp_LIBRARY) AND MSVC AND CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64")
      # On MSVC ARM64, OpenMP is provided by vcomp, which is a part of the Visual Studio installation.
      find_library(OpenMP_libomp_LIBRARY
      NAMES vcomp
      HINTS ${CMAKE_${LANG}_IMPLICIT_LINK_DIRECTORIES}
      DOC "vcomp location for OpenMP on MSVC ARM64"
      )
      mark_as_advanced(OpenMP_libomp_LIBRARY)
    endif()

    # Check if we are using  OpenBLAS which is linked against libgomp
    # we may end up with  multiple omp runtimes linked
    # against libtorch_cpu.so
    if(OpenBLAS_LIB AND OPENBLAS_USES_LIBGOMP)
      find_library(OpenMP_libomp_LIBRARY
        NAMES gomp
        HINTS ${CMAKE_${LANG}_IMPLICIT_LINK_DIRECTORIES}
        DOC "libomp location for OpenMP"
      )
      mark_as_advanced(OpenMP_libomp_LIBRARY)
    endif()

    if (NOT OpenMP_libomp_LIBRARY)
      find_library(OpenMP_libomp_LIBRARY
        NAMES omp gomp iomp5
        HINTS ${CMAKE_${LANG}_IMPLICIT_LINK_DIRECTORIES}
        DOC "libomp location for OpenMP"
      )
      mark_as_advanced(OpenMP_libomp_LIBRARY)
    endif()

    # Use OpenMP_PREFIX if defined
    if (NOT OpenMP_libomp_LIBRARY AND NOT "${OpenMP_PREFIX}" STREQUAL "")
      find_library(OpenMP_libomp_LIBRARY
        NAMES omp gomp iomp5
        HINTS "${OpenMP_PREFIX}/lib"
        DOC "libomp location for OpenMP"
      )
      mark_as_advanced(OpenMP_libomp_LIBRARY)
    endif()

    if(OpenMP_libomp_LIBRARY MATCHES "iomp5")
      set(OpenMP_libiomp5_LIBRARY "${MKL_OPENMP_LIBRARY}" CACHE STRING "libiomp5 location for OpenMP")
      if("-fopenmp=libiomp5" IN_LIST OpenMP_${LANG}_FLAG_CANDIDATES)
        get_filename_component(iomp5_dir "${OpenMP_libomp_LIBRARY}" DIRECTORY)
        set(OPENMP_FLAGS_TEST "-fopenmp=libiomp5 -L${iomp5_dir}")
      endif()
    endif()

    if(OpenMP_libomp_LIBRARY)
      message(STATUS "Check OMP with lib ${OpenMP_libomp_LIBRARY} and flags ${OPENMP_FLAGS_TEST}")
      try_compile( OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG} ${CMAKE_BINARY_DIR} ${_OPENMP_TEST_SRC}
        CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=${OPENMP_FLAGS_TEST}"
        LINK_LIBRARIES ${CMAKE_${LANG}_VERBOSE_FLAG} ${OpenMP_libomp_LIBRARY}
        OUTPUT_VARIABLE OpenMP_TRY_COMPILE_OUTPUT
      )
      if(OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG})
        set("${OPENMP_FLAG_VAR}" "${OPENMP_FLAG}" PARENT_SCOPE)
        if(OpenMP_libomp_LIBRARY MATCHES "iomp5")
          set("${OPENMP_LIB_NAMES_VAR}" "libiomp5" PARENT_SCOPE)
        else()
          set("${OPENMP_LIB_NAMES_VAR}" "libomp" PARENT_SCOPE)
        endif()
        break()
      else()
        message(WARNING "Detecting ${LANG} OpenMP compiler ABI info compiled with the following output:\n${OpenMP_TRY_COMPILE_OUTPUT}")
      endif()
    endif()

    try_compile( OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG} ${CMAKE_BINARY_DIR} ${_OPENMP_TEST_SRC}
      CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=${OPENMP_FLAGS_TEST}"
      LINK_LIBRARIES ${CMAKE_${LANG}_VERBOSE_FLAG}
      OUTPUT_VARIABLE OpenMP_TRY_COMPILE_OUTPUT
    )

    if(OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG})
      set("${OPENMP_FLAG_VAR}" "${OPENMP_FLAG}" PARENT_SCOPE)

      if(CMAKE_${LANG}_VERBOSE_FLAG)
        unset(OpenMP_${LANG}_IMPLICIT_LIBRARIES)
        unset(OpenMP_${LANG}_IMPLICIT_LINK_DIRS)
        unset(OpenMP_${LANG}_IMPLICIT_FWK_DIRS)
        unset(OpenMP_${LANG}_LOG_VAR)

        file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log
        "Detecting ${LANG} OpenMP compiler ABI info compiled with the following output:\n${OpenMP_TRY_COMPILE_OUTPUT}\n\n")

        cmake_parse_implicit_link_info("${OpenMP_TRY_COMPILE_OUTPUT}"
          OpenMP_${LANG}_IMPLICIT_LIBRARIES
          OpenMP_${LANG}_IMPLICIT_LINK_DIRS
          OpenMP_${LANG}_IMPLICIT_FWK_DIRS
          OpenMP_${LANG}_LOG_VAR
          "${CMAKE_${LANG}_IMPLICIT_OBJECT_REGEX}"
        )

        file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log
        "Parsed ${LANG} OpenMP implicit link information from above output:\n${OpenMP_${LANG}_LOG_VAR}\n\n")

        unset(_OPENMP_LIB_NAMES)
        foreach(_OPENMP_IMPLICIT_LIB IN LISTS OpenMP_${LANG}_IMPLICIT_LIBRARIES)
          get_filename_component(_OPENMP_IMPLICIT_LIB_DIR "${_OPENMP_IMPLICIT_LIB}" DIRECTORY)
          get_filename_component(_OPENMP_IMPLICIT_LIB_NAME "${_OPENMP_IMPLICIT_LIB}" NAME)
          get_filename_component(_OPENMP_IMPLICIT_LIB_PLAIN "${_OPENMP_IMPLICIT_LIB}" NAME_WE)
          string(REGEX REPLACE "([][+.*?()^$])" "\\\\\\1" _OPENMP_IMPLICIT_LIB_PLAIN_ESC "${_OPENMP_IMPLICIT_LIB_PLAIN}")
          string(REGEX REPLACE "([][+.*?()^$])" "\\\\\\1" _OPENMP_IMPLICIT_LIB_PATH_ESC "${_OPENMP_IMPLICIT_LIB}")
          if(NOT ( "${_OPENMP_IMPLICIT_LIB}" IN_LIST CMAKE_${LANG}_IMPLICIT_LINK_LIBRARIES
            OR "${CMAKE_${LANG}_STANDARD_LIBRARIES}" MATCHES "(^| )(-Wl,)?(-l)?(${_OPENMP_IMPLICIT_LIB_PLAIN_ESC}|${_OPENMP_IMPLICIT_LIB_PATH_ESC})( |$)"
            OR "${CMAKE_${LANG}_LINK_EXECUTABLE}" MATCHES "(^| )(-Wl,)?(-l)?(${_OPENMP_IMPLICIT_LIB_PLAIN_ESC}|${_OPENMP_IMPLICIT_LIB_PATH_ESC})( |$)" ) )
            if(_OPENMP_IMPLICIT_LIB_DIR)
              set(OpenMP_${_OPENMP_IMPLICIT_LIB_PLAIN}_LIBRARY "${_OPENMP_IMPLICIT_LIB}" CACHE FILEPATH
                "Path to the ${_OPENMP_IMPLICIT_LIB_PLAIN} library for OpenMP")
            else()
              find_library(OpenMP_${_OPENMP_IMPLICIT_LIB_PLAIN}_LIBRARY
                NAMES "${_OPENMP_IMPLICIT_LIB_NAME}"
                DOC "Path to the ${_OPENMP_IMPLICIT_LIB_PLAIN} library for OpenMP"
                HINTS ${OpenMP_${LANG}_IMPLICIT_LINK_DIRS}
                CMAKE_FIND_ROOT_PATH_BOTH
                NO_DEFAULT_PATH
              )
            endif()
            mark_as_advanced(OpenMP_${_OPENMP_IMPLICIT_LIB_PLAIN}_LIBRARY)
            list(APPEND _OPENMP_LIB_NAMES ${_OPENMP_IMPLICIT_LIB_PLAIN})
          endif()
        endforeach()
        set("${OPENMP_LIB_NAMES_VAR}" "${_OPENMP_LIB_NAMES}" PARENT_SCOPE)
      else()
        # We do not know how to extract implicit OpenMP libraries for this compiler.
        # Assume that it handles them automatically, e.g. the Intel Compiler on
        # Windows should put the dependency in its object files.
        set("${OPENMP_LIB_NAMES_VAR}" "" PARENT_SCOPE)
      endif()
      break()
    elseif((CMAKE_${LANG}_COMPILER_ID STREQUAL "AppleClang") AND
           (NOT CMAKE_${LANG}_COMPILER_VERSION VERSION_LESS "7.0"))

      # LLVM 3.7 supports OpenMP 3.1, and continues to add more features to
      # support newer OpenMP standards in new versions.
      # http://releases.llvm.org/3.7.0/tools/clang/docs/ReleaseNotes.html#openmp-support
      #
      # Apple Clang 7.0 is the first version based on LLVM 3.7 or later.
      # https://en.wikipedia.org/wiki/Xcode#Latest_versions
      #
      # Check for separate OpenMP library on AppleClang 7+
      find_library(OpenMP_libomp_LIBRARY
        NAMES omp gomp iomp5
        HINTS ${CMAKE_${LANG}_IMPLICIT_LINK_DIRECTORIES}
        DOC "libomp location for OpenMP"
      )
      mark_as_advanced(OpenMP_libomp_LIBRARY)

      if(OpenMP_libomp_LIBRARY)
        try_compile( OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG} ${CMAKE_BINARY_DIR} ${_OPENMP_TEST_SRC}
          CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=${OPENMP_FLAGS_TEST}"
          LINK_LIBRARIES ${CMAKE_${LANG}_VERBOSE_FLAG} ${OpenMP_libomp_LIBRARY}
          OUTPUT_VARIABLE OpenMP_TRY_COMPILE_OUTPUT
        )
        if(OpenMP_COMPILE_RESULT_${FLAG_MODE}_${OPENMP_PLAIN_FLAG})
          set("${OPENMP_FLAG_VAR}" "${OPENMP_FLAG}" PARENT_SCOPE)
          set("${OPENMP_LIB_NAMES_VAR}" "libomp" PARENT_SCOPE)
          break()
        endif()
      endif()
    else()
      file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log
        "Detecting ${LANG} OpenMP failed with the following output:\n${OpenMP_TRY_COMPILE_OUTPUT}\n\n")
    endif()
    if (NOT ${OpenMP_${LANG}_FIND_QUIETLY})
      message(STATUS "OpenMP try_compile log:\n${OpenMP_TRY_COMPILE_OUTPUT}\n\n")
    endif()
    set("${OPENMP_LIB_NAMES_VAR}" "NOTFOUND" PARENT_SCOPE)
    set("${OPENMP_FLAG_VAR}" "NOTFOUND" PARENT_SCOPE)
  endforeach()

  unset(OpenMP_VERBOSE_COMPILE_OPTIONS)
endfunction()

set(OpenMP_C_CXX_CHECK_VERSION_SOURCE
"
#include <stdio.h>
#include <omp.h>
const char ompver_str[] = { 'I', 'N', 'F', 'O', ':', 'O', 'p', 'e', 'n', 'M',
                            'P', '-', 'd', 'a', 't', 'e', '[',
                            ('0' + ((_OPENMP/100000)%10)),
                            ('0' + ((_OPENMP/10000)%10)),
                            ('0' + ((_OPENMP/1000)%10)),
                            ('0' + ((_OPENMP/100)%10)),
                            ('0' + ((_OPENMP/10)%10)),
                            ('0' + ((_OPENMP/1)%10)),
                            ']', '\\0' };
int main(void)
{
  puts(ompver_str);
  return 0;
}
")

set(OpenMP_Fortran_CHECK_VERSION_SOURCE
"
      program omp_ver
      @OpenMP_Fortran_INCLUDE_LINE@
      integer, parameter :: zero = ichar('0')
      integer, parameter :: ompv = openmp_version
      character, dimension(24), parameter :: ompver_str =&
      (/ 'I', 'N', 'F', 'O', ':', 'O', 'p', 'e', 'n', 'M', 'P', '-',&
         'd', 'a', 't', 'e', '[',&
         char(zero + mod(ompv/100000, 10)),&
         char(zero + mod(ompv/10000, 10)),&
         char(zero + mod(ompv/1000, 10)),&
         char(zero + mod(ompv/100, 10)),&
         char(zero + mod(ompv/10, 10)),&
         char(zero + mod(ompv/1, 10)), ']' /)
      print *, ompver_str
      end program omp_ver
")

function(_OPENMP_GET_SPEC_DATE LANG SPEC_DATE)
  _OPENMP_WRITE_SOURCE_FILE("${LANG}" "CHECK_VERSION_SOURCE" OpenMPCheckVersion _OPENMP_TEST_SRC)

  set(BIN_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/FindOpenMP/ompver_${LANG}.bin")
  string(REGEX REPLACE "[-/=+]" "" OPENMP_PLAIN_FLAG "${OPENMP_FLAG}")
  try_compile(OpenMP_SPECTEST_${LANG}_${OPENMP_PLAIN_FLAG} "${CMAKE_BINARY_DIR}" "${_OPENMP_TEST_SRC}"
              CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=${OpenMP_${LANG}_FLAGS}"
              COPY_FILE ${BIN_FILE}
              OUTPUT_VARIABLE OpenMP_TRY_COMPILE_OUTPUT)

  if(${OpenMP_SPECTEST_${LANG}_${OPENMP_PLAIN_FLAG}})
    file(STRINGS ${BIN_FILE} specstr LIMIT_COUNT 1 REGEX "INFO:OpenMP-date")
    set(regex_spec_date ".*INFO:OpenMP-date\\[0*([^]]*)\\].*")
    if("${specstr}" MATCHES "${regex_spec_date}")
      set(${SPEC_DATE} "${CMAKE_MATCH_1}" PARENT_SCOPE)
    endif()
  else()
    file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log
        "Detecting ${LANG} OpenMP version failed with the following output:\n${OpenMP_TRY_COMPILE_OUTPUT}\n\n")
  endif()
endfunction()

macro(_OPENMP_SET_VERSION_BY_SPEC_DATE LANG)
  set(OpenMP_SPEC_DATE_MAP
    # Preview versions
    "201611=5.0" # OpenMP 5.0 preview 1
    # Combined versions, 2.5 onwards
    "201511=4.5"
    "201307=4.0"
    "201107=3.1"
    "200805=3.0"
    "200505=2.5"
    # C/C++ version 2.0
    "200203=2.0"
    # Fortran version 2.0
    "200011=2.0"
    # Fortran version 1.1
    "199911=1.1"
    # C/C++ version 1.0 (there's no 1.1 for C/C++)
    "199810=1.0"
    # Fortran version 1.0
    "199710=1.0"
  )

  if(OpenMP_${LANG}_SPEC_DATE)
    string(REGEX MATCHALL "${OpenMP_${LANG}_SPEC_DATE}=([0-9]+)\\.([0-9]+)" _version_match "${OpenMP_SPEC_DATE_MAP}")
  else()
    set(_version_match "")
  endif()
  if(NOT _version_match STREQUAL "")
    set(OpenMP_${LANG}_VERSION_MAJOR ${CMAKE_MATCH_1})
    set(OpenMP_${LANG}_VERSION_MINOR ${CMAKE_MATCH_2})
    set(OpenMP_${LANG}_VERSION "${OpenMP_${LANG}_VERSION_MAJOR}.${OpenMP_${LANG}_VERSION_MINOR}")
  else()
    unset(OpenMP_${LANG}_VERSION_MAJOR)
    unset(OpenMP_${LANG}_VERSION_MINOR)
    unset(OpenMP_${LANG}_VERSION)
  endif()
  unset(_version_match)
  unset(OpenMP_SPEC_DATE_MAP)
endmacro()

foreach(LANG IN ITEMS C CXX)
  if(CMAKE_${LANG}_COMPILER_LOADED)
    if(NOT DEFINED OpenMP_${LANG}_FLAGS OR "${OpenMP_${LANG}_FLAGS}" STREQUAL "NOTFOUND"
      OR NOT DEFINED OpenMP_${LANG}_LIB_NAMES OR "${OpenMP_${LANG}_LIB_NAMES}" STREQUAL "NOTFOUND")
      _OPENMP_GET_FLAGS("${LANG}" "${LANG}" OpenMP_${LANG}_FLAGS_WORK OpenMP_${LANG}_LIB_NAMES_WORK)
    endif()

    set(OpenMP_${LANG}_FLAGS "${OpenMP_${LANG}_FLAGS_WORK}"
      CACHE STRING "${LANG} compiler flags for OpenMP parallelization")
    set(OpenMP_${LANG}_LIB_NAMES "${OpenMP_${LANG}_LIB_NAMES_WORK}"
      CACHE STRING "${LANG} compiler libraries for OpenMP parallelization")
    mark_as_advanced(OpenMP_${LANG}_FLAGS OpenMP_${LANG}_LIB_NAMES)
  endif()
endforeach()

if(CMAKE_Fortran_COMPILER_LOADED)
  if(NOT DEFINED OpenMP_Fortran_FLAGS OR "${OpenMP_Fortran_FLAGS}" STREQUAL "NOTFOUND"
    OR NOT DEFINED OpenMP_Fortran_LIB_NAMES OR "${OpenMP_Fortran_LIB_NAMES}" STREQUAL "NOTFOUND"
    OR NOT DEFINED OpenMP_Fortran_HAVE_OMPLIB_MODULE)
    set(OpenMP_Fortran_INCLUDE_LINE "use omp_lib\n      implicit none")
    _OPENMP_GET_FLAGS("Fortran" "FortranHeader" OpenMP_Fortran_FLAGS_WORK OpenMP_Fortran_LIB_NAMES_WORK)
    if(OpenMP_Fortran_FLAGS_WORK)
      set(OpenMP_Fortran_HAVE_OMPLIB_MODULE TRUE CACHE BOOL INTERNAL "")
    endif()

    set(OpenMP_Fortran_FLAGS "${OpenMP_Fortran_FLAGS_WORK}"
      CACHE STRING "Fortran compiler flags for OpenMP parallelization")
    set(OpenMP_Fortran_LIB_NAMES "${OpenMP_Fortran_LIB_NAMES_WORK}"
      CACHE STRING "Fortran compiler libraries for OpenMP parallelization")
    mark_as_advanced(OpenMP_Fortran_FLAGS OpenMP_Fortran_LIB_NAMES)
  endif()

  if(NOT DEFINED OpenMP_Fortran_FLAGS OR "${OpenMP_Fortran_FLAGS}" STREQUAL "NOTFOUND"
    OR NOT DEFINED OpenMP_Fortran_LIB_NAMES OR "${OpenMP_Fortran_LIB_NAMES}" STREQUAL "NOTFOUND"
    OR NOT DEFINED OpenMP_Fortran_HAVE_OMPLIB_HEADER)
    set(OpenMP_Fortran_INCLUDE_LINE "implicit none\n      include 'omp_lib.h'")
    _OPENMP_GET_FLAGS("Fortran" "FortranModule" OpenMP_Fortran_FLAGS_WORK OpenMP_Fortran_LIB_NAMES_WORK)
    if(OpenMP_Fortran_FLAGS_WORK)
      set(OpenMP_Fortran_HAVE_OMPLIB_HEADER TRUE CACHE BOOL INTERNAL "")
    endif()

    set(OpenMP_Fortran_FLAGS "${OpenMP_Fortran_FLAGS_WORK}"
      CACHE STRING "Fortran compiler flags for OpenMP parallelization")

    set(OpenMP_Fortran_LIB_NAMES "${OpenMP_Fortran_LIB_NAMES}"
      CACHE STRING "Fortran compiler libraries for OpenMP parallelization")
  endif()

  if(OpenMP_Fortran_HAVE_OMPLIB_MODULE)
    set(OpenMP_Fortran_INCLUDE_LINE "use omp_lib\n      implicit none")
  else()
    set(OpenMP_Fortran_INCLUDE_LINE "implicit none\n      include 'omp_lib.h'")
  endif()
endif()

if(NOT OpenMP_FIND_COMPONENTS)
  set(OpenMP_FINDLIST C CXX Fortran)
else()
  set(OpenMP_FINDLIST ${OpenMP_FIND_COMPONENTS})
endif()

unset(_OpenMP_MIN_VERSION)

include(FindPackageHandleStandardArgs)

foreach(LANG IN LISTS OpenMP_FINDLIST)
  if(CMAKE_${LANG}_COMPILER_LOADED)
    if (NOT OpenMP_${LANG}_SPEC_DATE AND OpenMP_${LANG}_FLAGS)
      _OPENMP_GET_SPEC_DATE("${LANG}" OpenMP_${LANG}_SPEC_DATE_INTERNAL)
      set(OpenMP_${LANG}_SPEC_DATE "${OpenMP_${LANG}_SPEC_DATE_INTERNAL}" CACHE
        INTERNAL "${LANG} compiler's OpenMP specification date")
      _OPENMP_SET_VERSION_BY_SPEC_DATE("${LANG}")
    endif()

    set(OpenMP_${LANG}_FIND_QUIETLY ${OpenMP_FIND_QUIETLY})
    set(OpenMP_${LANG}_FIND_REQUIRED ${OpenMP_FIND_REQUIRED})
    set(OpenMP_${LANG}_FIND_VERSION ${OpenMP_FIND_VERSION})
    set(OpenMP_${LANG}_FIND_VERSION_EXACT ${OpenMP_FIND_VERSION_EXACT})

    set(_OPENMP_${LANG}_REQUIRED_VARS OpenMP_${LANG}_FLAGS)
    if("${OpenMP_${LANG}_LIB_NAMES}" STREQUAL "NOTFOUND")
      set(_OPENMP_${LANG}_REQUIRED_LIB_VARS OpenMP_${LANG}_LIB_NAMES)
    else()
      foreach(_OPENMP_IMPLICIT_LIB IN LISTS OpenMP_${LANG}_LIB_NAMES)
        list(APPEND _OPENMP_${LANG}_REQUIRED_LIB_VARS OpenMP_${_OPENMP_IMPLICIT_LIB}_LIBRARY)
      endforeach()
    endif()

    find_package_handle_standard_args(OpenMP_${LANG}
      REQUIRED_VARS OpenMP_${LANG}_FLAGS ${_OPENMP_${LANG}_REQUIRED_LIB_VARS}
      VERSION_VAR OpenMP_${LANG}_VERSION
    )

    if(OpenMP_${LANG}_FOUND)
      if(DEFINED OpenMP_${LANG}_VERSION)
        if(NOT _OpenMP_MIN_VERSION OR _OpenMP_MIN_VERSION VERSION_GREATER OpenMP_${LANG}_VERSION)
          set(_OpenMP_MIN_VERSION OpenMP_${LANG}_VERSION)
        endif()
      endif()
      set(OpenMP_${LANG}_LIBRARIES "")
      foreach(_OPENMP_IMPLICIT_LIB IN LISTS OpenMP_${LANG}_LIB_NAMES)
        list(APPEND OpenMP_${LANG}_LIBRARIES "${OpenMP_${_OPENMP_IMPLICIT_LIB}_LIBRARY}")
      endforeach()

      if(NOT TARGET OpenMP::OpenMP_${LANG})
        add_library(OpenMP::OpenMP_${LANG} INTERFACE IMPORTED)
      endif()
      if(OpenMP_${LANG}_FLAGS)
        if(UNIX)
          separate_arguments(_OpenMP_${LANG}_OPTIONS UNIX_COMMAND "${OpenMP_${LANG}_FLAGS}")
        else()
          separate_arguments(_OpenMP_${LANG}_OPTIONS WINDOWS_COMMAND "${OpenMP_${LANG}_FLAGS}")
        endif()
        set_property(TARGET OpenMP::OpenMP_${LANG} PROPERTY
          INTERFACE_COMPILE_OPTIONS "$<$<COMPILE_LANGUAGE:${LANG}>:${_OpenMP_${LANG}_OPTIONS}>")
        unset(_OpenMP_${LANG}_OPTIONS)
      endif()
      if(OpenMP_${LANG}_LIBRARIES)
        set_property(TARGET OpenMP::OpenMP_${LANG} PROPERTY
          INTERFACE_LINK_LIBRARIES "${OpenMP_${LANG}_LIBRARIES}")
      endif()
    endif()
  endif()
endforeach()

unset(_OpenMP_REQ_VARS)
foreach(LANG IN ITEMS C CXX Fortran)
  if((NOT OpenMP_FIND_COMPONENTS AND CMAKE_${LANG}_COMPILER_LOADED) OR LANG IN_LIST OpenMP_FIND_COMPONENTS)
    list(APPEND _OpenMP_REQ_VARS "OpenMP_${LANG}_FOUND")
  endif()
endforeach()

find_package_handle_standard_args(OpenMP
    REQUIRED_VARS ${_OpenMP_REQ_VARS}
    VERSION_VAR ${_OpenMP_MIN_VERSION}
    HANDLE_COMPONENTS)

set(OPENMP_FOUND ${OpenMP_FOUND})

if(CMAKE_Fortran_COMPILER_LOADED AND OpenMP_Fortran_FOUND)
  if(NOT DEFINED OpenMP_Fortran_HAVE_OMPLIB_MODULE)
    set(OpenMP_Fortran_HAVE_OMPLIB_MODULE FALSE CACHE BOOL INTERNAL "")
  endif()
  if(NOT DEFINED OpenMP_Fortran_HAVE_OMPLIB_HEADER)
    set(OpenMP_Fortran_HAVE_OMPLIB_HEADER FALSE CACHE BOOL INTERNAL "")
  endif()
endif()

if(NOT ( CMAKE_C_COMPILER_LOADED OR CMAKE_CXX_COMPILER_LOADED OR CMAKE_Fortran_COMPILER_LOADED ))
  message(SEND_ERROR "FindOpenMP requires the C, CXX or Fortran languages to be enabled")
endif()

unset(OpenMP_C_CXX_TEST_SOURCE)
unset(OpenMP_Fortran_TEST_SOURCE)
unset(OpenMP_C_CXX_CHECK_VERSION_SOURCE)
unset(OpenMP_Fortran_CHECK_VERSION_SOURCE)
unset(OpenMP_Fortran_INCLUDE_LINE)
unset(OpenMP_PREFIX)

cmake_policy(POP)
