summaryrefslogtreecommitdiff
path: root/Modules/FortranCInterface.cmake
blob: 2c85029ed413b8204503e7a8cc0328d563b08b58 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.

#[=======================================================================[.rst:
FortranCInterface
-----------------

Fortran/C Interface Detection

This module automatically detects the API by which C and Fortran
languages interact.

Module Variables
^^^^^^^^^^^^^^^^

Variables that indicate if the mangling is found:

``FortranCInterface_GLOBAL_FOUND``
  Global subroutines and functions.

``FortranCInterface_MODULE_FOUND``
  Module subroutines and functions (declared by "MODULE PROCEDURE").

This module also provides the following variables to specify
the detected mangling, though a typical use case does not need
to reference them and can use the `Module Functions`_ below.

``FortranCInterface_GLOBAL_PREFIX``
  Prefix for a global symbol without an underscore.

``FortranCInterface_GLOBAL_SUFFIX``
  Suffix for a global symbol without an underscore.

``FortranCInterface_GLOBAL_CASE``
  The case for a global symbol without an underscore,
  either ``UPPER`` or ``LOWER``.

``FortranCInterface_GLOBAL__PREFIX``
  Prefix for a global symbol with an underscore.

``FortranCInterface_GLOBAL__SUFFIX``
  Suffix for a global symbol with an underscore.

``FortranCInterface_GLOBAL__CASE``
  The case for a global symbol with an underscore,
  either ``UPPER`` or ``LOWER``.

``FortranCInterface_MODULE_PREFIX``
  Prefix for a module symbol without an underscore.

``FortranCInterface_MODULE_MIDDLE``
  Middle of a module symbol without an underscore that appears
  between the name of the module and the name of the symbol.

``FortranCInterface_MODULE_SUFFIX``
  Suffix for a module symbol without an underscore.

``FortranCInterface_MODULE_CASE``
  The case for a module symbol without an underscore,
  either ``UPPER`` or ``LOWER``.

``FortranCInterface_MODULE__PREFIX``
  Prefix for a module symbol with an underscore.

``FortranCInterface_MODULE__MIDDLE``
  Middle of a module symbol with an underscore that appears
  between the name of the module and the name of the symbol.

``FortranCInterface_MODULE__SUFFIX``
  Suffix for a module symbol with an underscore.

``FortranCInterface_MODULE__CASE``
  The case for a module symbol with an underscore,
  either ``UPPER`` or ``LOWER``.

Module Functions
^^^^^^^^^^^^^^^^

.. command:: FortranCInterface_HEADER

  The ``FortranCInterface_HEADER`` function is provided to generate a
  C header file containing macros to mangle symbol names::

    FortranCInterface_HEADER(<file>
                             [MACRO_NAMESPACE <macro-ns>]
                             [SYMBOL_NAMESPACE <ns>]
                             [SYMBOLS [<module>:]<function> ...])

  It generates in ``<file>`` definitions of the following macros::

     #define FortranCInterface_GLOBAL (name,NAME) ...
     #define FortranCInterface_GLOBAL_(name,NAME) ...
     #define FortranCInterface_MODULE (mod,name, MOD,NAME) ...
     #define FortranCInterface_MODULE_(mod,name, MOD,NAME) ...

  These macros mangle four categories of Fortran symbols, respectively:

  * Global symbols without '_': ``call mysub()``
  * Global symbols with '_'   : ``call my_sub()``
  * Module symbols without '_': ``use mymod; call mysub()``
  * Module symbols with '_'   : ``use mymod; call my_sub()``

  If mangling for a category is not known, its macro is left undefined.
  All macros require raw names in both lower case and upper case.

  The options are:

  ``MACRO_NAMESPACE``
    Replace the default ``FortranCInterface_`` prefix with a given
    namespace ``<macro-ns>``.

  ``SYMBOLS``
    List symbols to mangle automatically with C preprocessor definitions::

      <function>          ==> #define <ns><function> ...
      <module>:<function> ==> #define <ns><module>_<function> ...

    If the mangling for some symbol is not known then no preprocessor
    definition is created, and a warning is displayed.

  ``SYMBOL_NAMESPACE``
    Prefix all preprocessor definitions generated by the ``SYMBOLS``
    option with a given namespace ``<ns>``.

.. command:: FortranCInterface_VERIFY

  The ``FortranCInterface_VERIFY`` function is provided to verify
  that the Fortran and C/C++ compilers work together::

    FortranCInterface_VERIFY([CXX] [QUIET])

  It tests whether a simple test executable using Fortran and C (and C++
  when the CXX option is given) compiles and links successfully.  The
  result is stored in the cache entry ``FortranCInterface_VERIFIED_C``
  (or ``FortranCInterface_VERIFIED_CXX`` if ``CXX`` is given) as a boolean.
  If the check fails and ``QUIET`` is not given the function terminates with a
  fatal error message describing the problem.  The purpose of this check
  is to stop a build early for incompatible compiler combinations.  The
  test is built in the ``Release`` configuration.

Example Usage
^^^^^^^^^^^^^

.. code-block:: cmake

   include(FortranCInterface)
   FortranCInterface_HEADER(FC.h MACRO_NAMESPACE "FC_")

This creates a "FC.h" header that defines mangling macros ``FC_GLOBAL()``,
``FC_GLOBAL_()``, ``FC_MODULE()``, and ``FC_MODULE_()``.

.. code-block:: cmake

   include(FortranCInterface)
   FortranCInterface_HEADER(FCMangle.h
                            MACRO_NAMESPACE "FC_"
                            SYMBOL_NAMESPACE "FC_"
                            SYMBOLS mysub mymod:my_sub)

This creates a "FCMangle.h" header that defines the same ``FC_*()``
mangling macros as the previous example plus preprocessor symbols
``FC_mysub`` and ``FC_mymod_my_sub``.

Additional Manglings
^^^^^^^^^^^^^^^^^^^^

FortranCInterface is aware of possible ``GLOBAL`` and ``MODULE`` manglings
for many Fortran compilers, but it also provides an interface to specify
new possible manglings.  Set the variables::

   FortranCInterface_GLOBAL_SYMBOLS
   FortranCInterface_MODULE_SYMBOLS

before including FortranCInterface to specify manglings of the symbols
``MySub``, ``My_Sub``, ``MyModule:MySub``, and ``My_Module:My_Sub``.
For example, the code:

.. code-block:: cmake

   set(FortranCInterface_GLOBAL_SYMBOLS mysub_ my_sub__ MYSUB_)
     #                                  ^^^^^  ^^^^^^   ^^^^^
   set(FortranCInterface_MODULE_SYMBOLS
       __mymodule_MOD_mysub __my_module_MOD_my_sub)
     #   ^^^^^^^^     ^^^^^   ^^^^^^^^^     ^^^^^^
   include(FortranCInterface)

tells FortranCInterface to try given ``GLOBAL`` and ``MODULE`` manglings.
(The carets point at raw symbol names for clarity in this example but
are not needed.)
#]=======================================================================]

#-----------------------------------------------------------------------------
# Execute at most once in a project.
if(FortranCInterface_SOURCE_DIR)
  return()
endif()

cmake_policy(PUSH)
cmake_policy(SET CMP0007 NEW)

#-----------------------------------------------------------------------------
# Verify that C and Fortran are available.
foreach(lang C Fortran)
  if(NOT CMAKE_${lang}_COMPILER_LOADED)
    message(FATAL_ERROR
      "FortranCInterface requires the ${lang} language to be enabled.")
  endif()
endforeach()

#-----------------------------------------------------------------------------
set(FortranCInterface_SOURCE_DIR ${CMAKE_ROOT}/Modules/FortranCInterface)

# MinGW's make tool does not always like () in the path
if("${CMAKE_GENERATOR}" MATCHES "MinGW" AND
    "${FortranCInterface_SOURCE_DIR}" MATCHES "[()]")
  file(COPY ${FortranCInterface_SOURCE_DIR}/
    DESTINATION ${CMAKE_BINARY_DIR}/CMakeFiles/FortranCInterfaceMinGW)
  set(FortranCInterface_SOURCE_DIR ${CMAKE_BINARY_DIR}/CMakeFiles/FortranCInterfaceMinGW)
endif()

# Create the interface detection project if it does not exist.
if(NOT FortranCInterface_BINARY_DIR)
  set(FortranCInterface_BINARY_DIR ${CMAKE_BINARY_DIR}/CMakeFiles/FortranCInterface)
  include(${FortranCInterface_SOURCE_DIR}/Detect.cmake)
endif()

# Load the detection results.
include(${FortranCInterface_BINARY_DIR}/Output.cmake)

#-----------------------------------------------------------------------------
function(FortranCInterface_HEADER file)
  # Parse arguments.
  if(IS_ABSOLUTE "${file}")
    set(FILE "${file}")
  else()
    set(FILE "${CMAKE_CURRENT_BINARY_DIR}/${file}")
  endif()
  set(MACRO_NAMESPACE "FortranCInterface_")
  set(SYMBOL_NAMESPACE)
  set(SYMBOLS)
  set(doing)
  foreach(arg ${ARGN})
    if("x${arg}" MATCHES "^x(SYMBOLS|SYMBOL_NAMESPACE|MACRO_NAMESPACE)$")
      set(doing "${arg}")
    elseif("x${doing}" MATCHES "^x(SYMBOLS)$")
      list(APPEND "${doing}" "${arg}")
    elseif("x${doing}" MATCHES "^x(SYMBOL_NAMESPACE|MACRO_NAMESPACE)$")
      set("${doing}" "${arg}")
      set(doing)
    else()
      message(AUTHOR_WARNING "Unknown argument: \"${arg}\"")
    endif()
  endforeach()

  # Generate macro definitions.
  set(HEADER_CONTENT)
  set(_desc_GLOBAL  "/* Mangling for Fortran global symbols without underscores. */")
  set(_desc_GLOBAL_ "/* Mangling for Fortran global symbols with underscores. */")
  set(_desc_MODULE  "/* Mangling for Fortran module symbols without underscores. */")
  set(_desc_MODULE_ "/* Mangling for Fortran module symbols with underscores. */")
  foreach(macro GLOBAL GLOBAL_ MODULE MODULE_)
    if(FortranCInterface_${macro}_MACRO)
      string(APPEND HEADER_CONTENT "
${_desc_${macro}}
#define ${MACRO_NAMESPACE}${macro}${FortranCInterface_${macro}_MACRO}
")
    endif()
  endforeach()

  # Generate symbol mangling definitions.
  if(SYMBOLS)
    string(APPEND HEADER_CONTENT "
/*--------------------------------------------------------------------------*/
/* Mangle some symbols automatically.                                       */
")
  endif()
  foreach(f ${SYMBOLS})
    if("${f}" MATCHES ":")
      # Module symbol name.  Parse "<module>:<function>" syntax.
      string(REPLACE ":" ";" pieces "${f}")
      list(GET pieces 0 module)
      list(GET pieces 1 function)
      string(TOUPPER "${module}" m_upper)
      string(TOLOWER "${module}" m_lower)
      string(TOUPPER "${function}" f_upper)
      string(TOLOWER "${function}" f_lower)
      if("${function}" MATCHES "_")
        set(form "_")
      else()
        set(form "")
      endif()
      if(FortranCInterface_MODULE${form}_MACRO)
        string(APPEND HEADER_CONTENT "#define ${SYMBOL_NAMESPACE}${module}_${function} ${MACRO_NAMESPACE}MODULE${form}(${m_lower},${f_lower}, ${m_upper},${f_upper})\n")
      else()
        message(AUTHOR_WARNING "No FortranCInterface mangling known for ${f}")
      endif()
    else()
      # Global symbol name.
      if("${f}" MATCHES "_")
        set(form "_")
      else()
        set(form "")
      endif()
      string(TOUPPER "${f}" f_upper)
      string(TOLOWER "${f}" f_lower)
      if(FortranCInterface_GLOBAL${form}_MACRO)
        string(APPEND HEADER_CONTENT "#define ${SYMBOL_NAMESPACE}${f} ${MACRO_NAMESPACE}GLOBAL${form}(${f_lower}, ${f_upper})\n")
      else()
        message(AUTHOR_WARNING "No FortranCInterface mangling known for ${f}")
      endif()
    endif()
  endforeach()

  # Store the content.
  configure_file(${FortranCInterface_SOURCE_DIR}/Macro.h.in ${FILE} @ONLY)
endfunction()

function(FortranCInterface_VERIFY)
  # Check arguments.

  set(lang C)
  set(quiet 0)
  set(verify_cxx 0)
  foreach(arg ${ARGN})
    if("${arg}" STREQUAL "QUIET")
      set(quiet 1)
    elseif("${arg}" STREQUAL "CXX")
      set(lang CXX)
      set(verify_cxx 1)
    else()
      message(FATAL_ERROR
        "FortranCInterface_VERIFY - called with unknown argument:\n  ${arg}")
    endif()
  endforeach()

  if(NOT CMAKE_${lang}_COMPILER_LOADED)
    message(FATAL_ERROR
      "FortranCInterface_VERIFY(${lang}) requires ${lang} to be enabled.")
  endif()

  # Build the verification project if not yet built.
  if(NOT DEFINED FortranCInterface_VERIFIED_${lang})
    set(_desc "Verifying Fortran/${lang} Compiler Compatibility")
    message(CHECK_START "${_desc}")

    # Perform verification with only one architecture.
    # FIXME: Add try_compile whole-project option to forward architectures.
    if(CMAKE_OSX_ARCHITECTURES MATCHES "^([^;]+)(;|$)")
      set(_FortranCInterface_OSX_ARCH "-DCMAKE_OSX_ARCHITECTURES=${CMAKE_MATCH_1}")
    else()
      set(_FortranCInterface_OSX_ARCH "")
    endif()

    cmake_policy(GET CMP0056 _FortranCInterface_CMP0056)
    if(_FortranCInterface_CMP0056 STREQUAL "NEW")
      set(_FortranCInterface_EXE_LINKER_FLAGS "-DCMAKE_EXE_LINKER_FLAGS:STRING=${CMAKE_EXE_LINKER_FLAGS}")
    else()
      set(_FortranCInterface_EXE_LINKER_FLAGS "")
    endif()

    # Build a sample project which reports symbols.
    set(CMAKE_TRY_COMPILE_CONFIGURATION Release)
    try_compile(FortranCInterface_VERIFY_${lang}_COMPILED
      PROJECT VerifyFortranC
      TARGET VerifyFortranC
      SOURCE_DIR ${FortranCInterface_SOURCE_DIR}/Verify
      BINARY_DIR ${FortranCInterface_BINARY_DIR}/Verify${lang}
      CMAKE_FLAGS -DVERIFY_CXX=${verify_cxx}
                  -DCMAKE_VERBOSE_MAKEFILE=ON
                 "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS}"
                 "-DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS}"
                 "-DCMAKE_Fortran_FLAGS:STRING=${CMAKE_Fortran_FLAGS}"
                 "-DCMAKE_C_FLAGS_RELEASE:STRING=${CMAKE_C_FLAGS_RELEASE}"
                 "-DCMAKE_CXX_FLAGS_RELEASE:STRING=${CMAKE_CXX_FLAGS_RELEASE}"
                 "-DCMAKE_Fortran_FLAGS_RELEASE:STRING=${CMAKE_Fortran_FLAGS_RELEASE}"
                 "-DFortranCInterface_BINARY_DIR=${FortranCInterface_BINARY_DIR}"
                 ${_FortranCInterface_OSX_ARCH}
                 ${_FortranCInterface_EXE_LINKER_FLAGS}
      OUTPUT_VARIABLE _output)
    file(WRITE "${FortranCInterface_BINARY_DIR}/Verify${lang}/output.txt" "${_output}")

    # Report results.
    if(FortranCInterface_VERIFY_${lang}_COMPILED)
      message(CHECK_PASS "Success")
      set(FortranCInterface_VERIFIED_${lang} 1 CACHE INTERNAL "Fortran/${lang} compatibility")
    else()
      message(CHECK_FAIL "Failed")
      set(FortranCInterface_VERIFIED_${lang} 0 CACHE INTERNAL "Fortran/${lang} compatibility")
    endif()
    unset(FortranCInterface_VERIFY_${lang}_COMPILED CACHE)
  endif()

  # Error if compilers are incompatible.
  if(NOT FortranCInterface_VERIFIED_${lang} AND NOT quiet)
    file(READ "${FortranCInterface_BINARY_DIR}/Verify${lang}/output.txt" _output)
    string(REPLACE "\n" "\n  " _output "${_output}")
    message(FATAL_ERROR
      "The Fortran compiler:\n  ${CMAKE_Fortran_COMPILER}\n"
      "and the ${lang} compiler:\n  ${CMAKE_${lang}_COMPILER}\n"
      "failed to compile a simple test project using both languages.  "
      "The output was:\n  ${_output}")
  endif()
endfunction()

# Restore including context policies.
cmake_policy(POP)