# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.

cmake_minimum_required(VERSION ${CMAKE_VERSION})
project(FortranCInterface C Fortran)
include(${FortranCInterface_BINARY_DIR}/Input.cmake OPTIONAL)

# Check if the C compiler supports '$' in identifiers.
include(CheckCSourceCompiles)
check_c_source_compiles("
extern int dollar$(void);
int main() { return 0; }
" C_SUPPORTS_DOLLAR)

# List manglings of global symbol names to try.
set(global_symbols
  my_sub    # VisualAge
  my_sub_   # GNU, Intel, HP, SunPro, PGI
  my_sub__  # GNU g77
  MY_SUB    # Intel on Windows
  mysub     # VisualAge
  mysub_    # GNU, Intel, HP, SunPro, PGI
  MYSUB     # Intel on Windows
  ${FortranCInterface_GLOBAL_SYMBOLS}
  )
list(REMOVE_DUPLICATES global_symbols)

# List manglings of module symbol names to try.
set(module_symbols
  __my_module_MOD_my_sub  # GNU 4.3
  __my_module_NMOD_my_sub # VisualAge
  __my_module__my_sub     # GNU 4.2
  __mymodule_MOD_mysub    # GNU 4.3
  __mymodule_NMOD_mysub   # VisualAge
  __mymodule__mysub       # GNU 4.2
  my_module$my_sub        # HP
  my_module_mp_my_sub_    # Intel
  MY_MODULE_mp_MY_SUB     # Intel on Windows
  my_module_my_sub_       # PGI
  my_module_MP_my_sub     # NAG
  mymodule$mysub          # HP
  mymodule_mp_mysub_      # Intel
  MYMODULE_mp_MYSUB       # Intel on Windows
  mymodule_mysub_         # PGI
  mymodule_MP_mysub       # NAG
  ${FortranCInterface_MODULE_SYMBOLS}
  )
list(REMOVE_DUPLICATES module_symbols)

# Note that some compiler manglings cannot be invoked from C:
#   SunPro uses "my_module.my_sub_"
#   PathScale uses "MY_SUB.in.MY_MODULE"

# Add module symbols only with Fortran90.
if(CMAKE_Fortran_COMPILER_SUPPORTS_F90)
  set(myfort_modules mymodule.f90 my_module.f90)
  set(call_mod call_mod.f90)
  set_property(SOURCE main.F PROPERTY COMPILE_DEFINITIONS CALL_MOD)
else()
  set(module_symbols)
endif()

# Generate C symbol sources.
set(symbol_sources)
if(NOT CMAKE_Fortran_COMPILER_ID MATCHES "^(PathScale|Cray)$")
  # Provide mymodule_ and my_module_ init symbols because:
  #  - PGI Fortran uses module init symbols
  # but not for:
  #  - PathScale Fortran uses module init symbols but module symbols
  #    use '.in.' so we cannot provide them anyway.
  #  - Cray Fortran >= 7.3.2 uses module init symbols but module symbols
  #    use 'mysub$mymodule_' so we cannot provide them anyway.
  list(APPEND symbol_sources mymodule_.c my_module_.c MY_MODULE.c MYMODULE.c)
endif()
foreach(symbol IN LISTS global_symbols module_symbols)
  # Skip symbols with '$' if C cannot handle them.
  if(C_SUPPORTS_DOLLAR OR NOT "${symbol}" MATCHES "\\$")
    if("${symbol}" MATCHES "SUB")
      set(upper "-UPPER")
    else()
      set(upper)
    endif()
    string(REPLACE "$" "S" name "${symbol}")
    set(source ${CMAKE_CURRENT_BINARY_DIR}/symbols/${name}${upper}.c)
    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/symbol.c.in ${source} @ONLY)
    list(APPEND symbol_sources ${source})
  endif()
endforeach()

# Provide symbols through Fortran.
add_library(myfort STATIC mysub.f my_sub.f ${myfort_modules})

# Provide symbols through C but fall back to Fortran.
add_library(symbols STATIC ${symbol_sources})
target_link_libraries(symbols PUBLIC myfort)

# In case the Fortran compiler produces PIC by default make sure
# the C compiler produces PIC even if it is not its default.
set_property(TARGET symbols PROPERTY POSITION_INDEPENDENT_CODE 1)

# Require symbols through Fortran.
add_executable(FortranCInterface main.F call_sub.f ${call_mod})
target_link_libraries(FortranCInterface PUBLIC symbols)

# If IPO is enabled here, GCC gfortran >= 12.0 will obfuscate
# the strings of the return values in the compiled executable,
# which we use to regex match against later.
# The static libraries must be build with IPO and non-IPO objects,
# as that will ensure the verify step will operate on IPO objects,
# if requested by the system compiler flags.
if(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU" AND
  CMAKE_Fortran_COMPILER_VERSION VERSION_GREATER_EQUAL 12)
  target_compile_options(FortranCInterface PRIVATE "-fno-lto")
  target_compile_options(myfort PRIVATE "-flto=auto" "-ffat-lto-objects")
endif()
if(CMAKE_C_COMPILER_ID STREQUAL "GNU" AND
  CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 12)
  target_compile_options(symbols PRIVATE "-flto=auto" "-ffat-lto-objects")
endif()

file(GENERATE OUTPUT exe-$<CONFIG>.cmake CONTENT [[
set(FortranCInterface_EXE "$<TARGET_FILE:FortranCInterface>")
]])
