File: CMakeAddFortranSubdirectory.cmake

package info (click to toggle)
cmake 4.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 147,284 kB
  • sloc: ansic: 403,915; cpp: 290,772; sh: 4,102; python: 3,357; yacc: 3,106; lex: 1,189; f90: 532; asm: 471; lisp: 375; cs: 270; java: 266; fortran: 230; perl: 217; objc: 215; xml: 198; makefile: 97; javascript: 83; pascal: 63; tcl: 55; php: 25; ruby: 22
file content (280 lines) | stat: -rw-r--r-- 9,613 bytes parent folder | download
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
# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
# file LICENSE.rst or https://cmake.org/licensing for details.

#[=======================================================================[.rst:
CMakeAddFortranSubdirectory
---------------------------

This module provides a command to add a Fortran project located in a
subdirectory.

Load it in a CMake project with:

.. code-block:: cmake

  include(CMakeAddFortranSubdirectory)

Commands
^^^^^^^^

This module provides the following command:

.. command:: cmake_add_fortran_subdirectory

  Adds a Fortran-only subproject from subdirectory to the current project:

  .. code-block:: cmake

    cmake_add_fortran_subdirectory(
      <subdir>
      PROJECT <project-name>
      ARCHIVE_DIR <dir>
      RUNTIME_DIR <dir>
      LIBRARIES <libs>...
      LINK_LIBRARIES
        [LINK_LIBS <lib> <deps>...]...
      [CMAKE_COMMAND_LINE <flags>...]
      NO_EXTERNAL_INSTALL
    )

  This command checks whether the current compiler supports Fortran or attempts
  to locate a Fortran compiler.  If a compatible Fortran compiler is found, the
  Fortran project located in ``<subdir>`` is added as a subdirectory to the
  current project.

  If no Fortran compiler is found and the compiler is ``MSVC``, it searches for
  the MinGW ``gfortran`` compiler.  In this case, the Fortran project is built
  as an external project using MinGW tools, and Fortran-related imported targets
  are created.  This setup works only if the Fortran code is built as a shared
  DLL library, so the :variable:`BUILD_SHARED_LIBS` variable is enabled in the
  external project.  Additionally, the :variable:`CMAKE_GNUtoMS` variable is set
  to ``ON`` to ensure that Microsoft-compatible ``.lib`` files are created.

  The options are:

  ``PROJECT``
    The name of the Fortran project as defined in the top-level
    ``CMakeLists.txt`` located in ``<subdir>``.

  ``ARCHIVE_DIR``
    Directory where the project places ``.lib`` archive files.  A relative path
    is interpreted as relative to :variable:`CMAKE_CURRENT_BINARY_DIR`.

  ``RUNTIME_DIR``
    Directory where the project places ``.dll`` runtime files.  A relative path
    is interpreted as relative to :variable:`CMAKE_CURRENT_BINARY_DIR`.

  ``LIBRARIES``
    Names of library targets to create or import into the current project.

  ``LINK_LIBRARIES``
    Specifies link interface libraries for ``LIBRARIES``.  This option expects a
    list of ``LINK_LIBS <lib> <deps>...`` items, where:

    * ``LINK_LIBS`` marks the start of a new pair
    * ``<lib>`` is a library target.
    * ``<deps>...`` represents one or more dependencies required by ``<lib>``.

  ``CMAKE_COMMAND_LINE``
    Additional command-line flags passed to :manual:`cmake(1)` command when
    configuring the Fortran subproject.

  ``NO_EXTERNAL_INSTALL``
    Prevents installation of the external project.

    .. note::

      The ``NO_EXTERNAL_INSTALL`` option is required for forward compatibility
      with a future version that supports installation of the external project
      binaries during ``make install``.

Examples
^^^^^^^^

Adding a Fortran subdirectory to a project can be done by including this module
and calling the ``cmake_add_fortran_subdirectory()`` command.  In the following
example, a Fortran project provides the ``hello`` library and its dependent
``world`` library:

.. code-block:: cmake

  include(CMakeAddFortranSubdirectory)

  cmake_add_fortran_subdirectory(
    fortran-subdir
    PROJECT FortranHelloWorld
    ARCHIVE_DIR lib
    RUNTIME_DIR bin
    LIBRARIES hello world
    LINK_LIBRARIES
      LINK_LIBS hello world # hello library depends on the world library
    NO_EXTERNAL_INSTALL
  )

  # The Fortran target can be then linked to the main project target.
  add_executable(main main.c)
  target_link_libraries(main PRIVATE hello)

See Also
^^^^^^^^

There are multiple ways to integrate Fortran libraries.  Alternative approaches
include:

* The :command:`add_subdirectory` command to add the subdirectory directly to
  the build.
* The :command:`export` command can be used in the subproject to provide
  :ref:`Imported Targets` or similar for integration with other projects.
* The :module:`FetchContent` or :module:`ExternalProject` modules when working
  with external dependencies.
#]=======================================================================]

include(CheckLanguage)
include(ExternalProject)

function(_setup_mingw_config_and_build source_dir build_dir)
  # Look for a MinGW gfortran.
  find_program(MINGW_GFORTRAN
    NAMES gfortran
    PATHS
      c:/MinGW/bin
      "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MinGW;InstallLocation]/bin"
    )
  if(NOT MINGW_GFORTRAN)
    message(FATAL_ERROR
      "gfortran not found, please install MinGW with the gfortran option."
      "Or set the cache variable MINGW_GFORTRAN to the full path. "
      " This is required to build")
  endif()

  # Validate the MinGW gfortran we found.
  if(CMAKE_SIZEOF_VOID_P EQUAL 8)
    set(_mingw_target "Target:.*64.*mingw")
  else()
    set(_mingw_target "Target:.*mingw32")
  endif()
  execute_process(COMMAND "${MINGW_GFORTRAN}" -v
    ERROR_VARIABLE out ERROR_STRIP_TRAILING_WHITESPACE)
  if(NOT "${out}" MATCHES "${_mingw_target}")
    string(REPLACE "\n" "\n  " out "  ${out}")
    message(FATAL_ERROR
      "MINGW_GFORTRAN is set to\n"
      "  ${MINGW_GFORTRAN}\n"
      "which is not a MinGW gfortran for this architecture.  "
      "The output from -v does not match \"${_mingw_target}\":\n"
      "${out}\n"
      "Set MINGW_GFORTRAN to a proper MinGW gfortran for this architecture."
      )
  endif()

  # Configure scripts to run MinGW tools with the proper PATH.
  get_filename_component(MINGW_PATH ${MINGW_GFORTRAN} PATH)
  file(TO_NATIVE_PATH "${MINGW_PATH}" MINGW_PATH)
  string(REPLACE "\\" "\\\\" MINGW_PATH "${MINGW_PATH}")
  configure_file(
    ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/CMakeAddFortranSubdirectory/config_mingw.cmake.in
    ${build_dir}/config_mingw.cmake
    @ONLY)
  configure_file(
    ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/CMakeAddFortranSubdirectory/build_mingw.cmake.in
    ${build_dir}/build_mingw.cmake
    @ONLY)
endfunction()

function(_add_fortran_library_link_interface library depend_library)
  set_target_properties(${library} PROPERTIES
    IMPORTED_LINK_INTERFACE_LIBRARIES_NOCONFIG "${depend_library}")
endfunction()


function(cmake_add_fortran_subdirectory subdir)
  # Parse arguments to function
  set(options NO_EXTERNAL_INSTALL)
  set(oneValueArgs PROJECT ARCHIVE_DIR RUNTIME_DIR)
  set(multiValueArgs LIBRARIES LINK_LIBRARIES CMAKE_COMMAND_LINE)
  cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  if(NOT ARGS_NO_EXTERNAL_INSTALL)
    message(FATAL_ERROR
      "Option NO_EXTERNAL_INSTALL is required (for forward compatibility) "
      "but was not given."
      )
  endif()

  # if we are not using MSVC without fortran support
  # then just use the usual add_subdirectory to build
  # the fortran library
  check_language(Fortran)
  if(NOT (MSVC AND (NOT CMAKE_Fortran_COMPILER)))
    add_subdirectory(${subdir})
    return()
  endif()

  # if we have MSVC without Intel fortran then setup
  # external projects to build with mingw fortran

  set(source_dir "${CMAKE_CURRENT_SOURCE_DIR}/${subdir}")
  set(project_name "${ARGS_PROJECT}")
  set(library_dir "${ARGS_ARCHIVE_DIR}")
  set(binary_dir "${ARGS_RUNTIME_DIR}")
  set(libraries ${ARGS_LIBRARIES})
  # use the same directory that add_subdirectory would have used
  set(build_dir "${CMAKE_CURRENT_BINARY_DIR}/${subdir}")
  foreach(dir_var library_dir binary_dir)
    if(NOT IS_ABSOLUTE "${${dir_var}}")
      get_filename_component(${dir_var}
        "${CMAKE_CURRENT_BINARY_DIR}/${${dir_var}}" ABSOLUTE)
    endif()
  endforeach()
  # create build and configure wrapper scripts
  _setup_mingw_config_and_build("${source_dir}" "${build_dir}")
  # create the external project
  externalproject_add(${project_name}_build
    SOURCE_DIR ${source_dir}
    BINARY_DIR ${build_dir}
    CONFIGURE_COMMAND ${CMAKE_COMMAND}
    -P ${build_dir}/config_mingw.cmake
    BUILD_COMMAND ${CMAKE_COMMAND}
    -P ${build_dir}/build_mingw.cmake
    BUILD_ALWAYS 1
    INSTALL_COMMAND ""
    )
  # create imported targets for all libraries
  foreach(lib ${libraries})
    add_library(${lib} SHARED IMPORTED GLOBAL)
    set_property(TARGET ${lib} APPEND PROPERTY IMPORTED_CONFIGURATIONS NOCONFIG)
    set_target_properties(${lib} PROPERTIES
      IMPORTED_IMPLIB_NOCONFIG   "${library_dir}/lib${lib}.lib"
      IMPORTED_LOCATION_NOCONFIG "${binary_dir}/lib${lib}.dll"
      )
    add_dependencies(${lib} ${project_name}_build)
  endforeach()

  # now setup link libraries for targets
  set(start FALSE)
  set(target)
  foreach(lib ${ARGS_LINK_LIBRARIES})
    if("${lib}" STREQUAL "LINK_LIBS")
      set(start TRUE)
    else()
      if(start)
        if(DEFINED target)
          # process current target and target_libs
          _add_fortran_library_link_interface(${target} "${target_libs}")
          # zero out target and target_libs
          set(target)
          set(target_libs)
        endif()
        # save the current target and set start to FALSE
        set(target ${lib})
        set(start FALSE)
      else()
        # append the lib to target_libs
        list(APPEND target_libs "${lib}")
      endif()
    endif()
  endforeach()
  # process anything that is left in target and target_libs
  if(DEFINED target)
    _add_fortran_library_link_interface(${target} "${target_libs}")
  endif()
endfunction()