CMakeAddFortranSubdirectory.cmake 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. # Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. # file LICENSE.rst or https://cmake.org/licensing for details.
  3. #[=======================================================================[.rst:
  4. CMakeAddFortranSubdirectory
  5. ---------------------------
  6. This module provides a command to add a Fortran project located in a
  7. subdirectory.
  8. Load this module in a CMake project with:
  9. .. code-block:: cmake
  10. include(CMakeAddFortranSubdirectory)
  11. Commands
  12. ^^^^^^^^
  13. This module provides the following command:
  14. .. command:: cmake_add_fortran_subdirectory
  15. Adds a Fortran-only subproject from subdirectory to the current project:
  16. .. code-block:: cmake
  17. cmake_add_fortran_subdirectory(
  18. <subdir>
  19. PROJECT <project-name>
  20. ARCHIVE_DIR <dir>
  21. RUNTIME_DIR <dir>
  22. LIBRARIES <libs>...
  23. LINK_LIBRARIES
  24. [LINK_LIBS <lib> <deps>...]...
  25. [CMAKE_COMMAND_LINE <flags>...]
  26. NO_EXTERNAL_INSTALL
  27. )
  28. This command checks whether the current compiler supports Fortran or attempts
  29. to locate a Fortran compiler. If a compatible Fortran compiler is found, the
  30. Fortran project located in ``<subdir>`` is added as a subdirectory to the
  31. current project.
  32. If no Fortran compiler is found and the compiler is ``MSVC``, it searches for
  33. the MinGW ``gfortran`` compiler. In this case, the Fortran project is built
  34. as an external project using MinGW tools, and Fortran-related imported targets
  35. are created. This setup works only if the Fortran code is built as a shared
  36. DLL library, so the :variable:`BUILD_SHARED_LIBS` variable is enabled in the
  37. external project. Additionally, the :variable:`CMAKE_GNUtoMS` variable is set
  38. to ``ON`` to ensure that Microsoft-compatible ``.lib`` files are created.
  39. The options are:
  40. ``PROJECT``
  41. The name of the Fortran project as defined in the top-level
  42. ``CMakeLists.txt`` located in ``<subdir>``.
  43. ``ARCHIVE_DIR``
  44. Directory where the project places ``.lib`` archive files. A relative path
  45. is interpreted as relative to :variable:`CMAKE_CURRENT_BINARY_DIR`.
  46. ``RUNTIME_DIR``
  47. Directory where the project places ``.dll`` runtime files. A relative path
  48. is interpreted as relative to :variable:`CMAKE_CURRENT_BINARY_DIR`.
  49. ``LIBRARIES``
  50. Names of library targets to create or import into the current project.
  51. ``LINK_LIBRARIES``
  52. Specifies link interface libraries for ``LIBRARIES``. This option expects a
  53. list of ``LINK_LIBS <lib> <deps>...`` items, where:
  54. * ``LINK_LIBS`` marks the start of a new pair
  55. * ``<lib>`` is a library target.
  56. * ``<deps>...`` represents one or more dependencies required by ``<lib>``.
  57. ``CMAKE_COMMAND_LINE``
  58. Additional command-line flags passed to :manual:`cmake(1)` command when
  59. configuring the Fortran subproject.
  60. ``NO_EXTERNAL_INSTALL``
  61. Prevents installation of the external project.
  62. .. note::
  63. The ``NO_EXTERNAL_INSTALL`` option is required for forward compatibility
  64. with a future version that supports installation of the external project
  65. binaries during ``make install``.
  66. Examples
  67. ^^^^^^^^
  68. Adding a Fortran subdirectory to a project can be done by including this module
  69. and calling the ``cmake_add_fortran_subdirectory()`` command. In the following
  70. example, a Fortran project provides the ``hello`` library and its dependent
  71. ``world`` library:
  72. .. code-block:: cmake
  73. include(CMakeAddFortranSubdirectory)
  74. cmake_add_fortran_subdirectory(
  75. fortran-subdir
  76. PROJECT FortranHelloWorld
  77. ARCHIVE_DIR lib
  78. RUNTIME_DIR bin
  79. LIBRARIES hello world
  80. LINK_LIBRARIES
  81. LINK_LIBS hello world # hello library depends on the world library
  82. NO_EXTERNAL_INSTALL
  83. )
  84. # The Fortran target can be then linked to the main project target.
  85. add_executable(main main.c)
  86. target_link_libraries(main PRIVATE hello)
  87. See Also
  88. ^^^^^^^^
  89. There are multiple ways to integrate Fortran libraries. Alternative approaches
  90. include:
  91. * The :command:`add_subdirectory` command to add the subdirectory directly to
  92. the build.
  93. * The :command:`export` command can be used in the subproject to provide
  94. :ref:`Imported Targets` or similar for integration with other projects.
  95. * The :module:`FetchContent` or :module:`ExternalProject` modules when working
  96. with external dependencies.
  97. #]=======================================================================]
  98. include(CheckLanguage)
  99. include(ExternalProject)
  100. function(_setup_mingw_config_and_build source_dir build_dir)
  101. # Look for a MinGW gfortran.
  102. find_program(MINGW_GFORTRAN
  103. NAMES gfortran
  104. PATHS
  105. c:/MinGW/bin
  106. "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MinGW;InstallLocation]/bin"
  107. )
  108. if(NOT MINGW_GFORTRAN)
  109. message(FATAL_ERROR
  110. "gfortran not found, please install MinGW with the gfortran option."
  111. "Or set the cache variable MINGW_GFORTRAN to the full path. "
  112. " This is required to build")
  113. endif()
  114. # Validate the MinGW gfortran we found.
  115. if(CMAKE_SIZEOF_VOID_P EQUAL 8)
  116. set(_mingw_target "Target:.*64.*mingw")
  117. else()
  118. set(_mingw_target "Target:.*mingw32")
  119. endif()
  120. execute_process(COMMAND "${MINGW_GFORTRAN}" -v
  121. ERROR_VARIABLE out ERROR_STRIP_TRAILING_WHITESPACE)
  122. if(NOT "${out}" MATCHES "${_mingw_target}")
  123. string(REPLACE "\n" "\n " out " ${out}")
  124. message(FATAL_ERROR
  125. "MINGW_GFORTRAN is set to\n"
  126. " ${MINGW_GFORTRAN}\n"
  127. "which is not a MinGW gfortran for this architecture. "
  128. "The output from -v does not match \"${_mingw_target}\":\n"
  129. "${out}\n"
  130. "Set MINGW_GFORTRAN to a proper MinGW gfortran for this architecture."
  131. )
  132. endif()
  133. # Configure scripts to run MinGW tools with the proper PATH.
  134. get_filename_component(MINGW_PATH ${MINGW_GFORTRAN} PATH)
  135. file(TO_NATIVE_PATH "${MINGW_PATH}" MINGW_PATH)
  136. string(REPLACE "\\" "\\\\" MINGW_PATH "${MINGW_PATH}")
  137. configure_file(
  138. ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/CMakeAddFortranSubdirectory/config_mingw.cmake.in
  139. ${build_dir}/config_mingw.cmake
  140. @ONLY)
  141. configure_file(
  142. ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/CMakeAddFortranSubdirectory/build_mingw.cmake.in
  143. ${build_dir}/build_mingw.cmake
  144. @ONLY)
  145. endfunction()
  146. function(_add_fortran_library_link_interface library depend_library)
  147. set_target_properties(${library} PROPERTIES
  148. IMPORTED_LINK_INTERFACE_LIBRARIES_NOCONFIG "${depend_library}")
  149. endfunction()
  150. function(cmake_add_fortran_subdirectory subdir)
  151. # Parse arguments to function
  152. set(options NO_EXTERNAL_INSTALL)
  153. set(oneValueArgs PROJECT ARCHIVE_DIR RUNTIME_DIR)
  154. set(multiValueArgs LIBRARIES LINK_LIBRARIES CMAKE_COMMAND_LINE)
  155. cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  156. if(NOT ARGS_NO_EXTERNAL_INSTALL)
  157. message(FATAL_ERROR
  158. "Option NO_EXTERNAL_INSTALL is required (for forward compatibility) "
  159. "but was not given."
  160. )
  161. endif()
  162. # if we are not using MSVC without fortran support
  163. # then just use the usual add_subdirectory to build
  164. # the fortran library
  165. check_language(Fortran)
  166. if(NOT (MSVC AND (NOT CMAKE_Fortran_COMPILER)))
  167. add_subdirectory(${subdir})
  168. return()
  169. endif()
  170. # if we have MSVC without Intel fortran then setup
  171. # external projects to build with mingw fortran
  172. set(source_dir "${CMAKE_CURRENT_SOURCE_DIR}/${subdir}")
  173. set(project_name "${ARGS_PROJECT}")
  174. set(library_dir "${ARGS_ARCHIVE_DIR}")
  175. set(binary_dir "${ARGS_RUNTIME_DIR}")
  176. set(libraries ${ARGS_LIBRARIES})
  177. # use the same directory that add_subdirectory would have used
  178. set(build_dir "${CMAKE_CURRENT_BINARY_DIR}/${subdir}")
  179. foreach(dir_var library_dir binary_dir)
  180. if(NOT IS_ABSOLUTE "${${dir_var}}")
  181. get_filename_component(${dir_var}
  182. "${CMAKE_CURRENT_BINARY_DIR}/${${dir_var}}" ABSOLUTE)
  183. endif()
  184. endforeach()
  185. # create build and configure wrapper scripts
  186. _setup_mingw_config_and_build("${source_dir}" "${build_dir}")
  187. # create the external project
  188. externalproject_add(${project_name}_build
  189. SOURCE_DIR ${source_dir}
  190. BINARY_DIR ${build_dir}
  191. CONFIGURE_COMMAND ${CMAKE_COMMAND}
  192. -P ${build_dir}/config_mingw.cmake
  193. BUILD_COMMAND ${CMAKE_COMMAND}
  194. -P ${build_dir}/build_mingw.cmake
  195. BUILD_ALWAYS 1
  196. INSTALL_COMMAND ""
  197. )
  198. # create imported targets for all libraries
  199. foreach(lib ${libraries})
  200. add_library(${lib} SHARED IMPORTED GLOBAL)
  201. set_property(TARGET ${lib} APPEND PROPERTY IMPORTED_CONFIGURATIONS NOCONFIG)
  202. set_target_properties(${lib} PROPERTIES
  203. IMPORTED_IMPLIB_NOCONFIG "${library_dir}/lib${lib}.lib"
  204. IMPORTED_LOCATION_NOCONFIG "${binary_dir}/lib${lib}.dll"
  205. )
  206. add_dependencies(${lib} ${project_name}_build)
  207. endforeach()
  208. # now setup link libraries for targets
  209. set(start FALSE)
  210. set(target)
  211. foreach(lib ${ARGS_LINK_LIBRARIES})
  212. if("${lib}" STREQUAL "LINK_LIBS")
  213. set(start TRUE)
  214. else()
  215. if(start)
  216. if(DEFINED target)
  217. # process current target and target_libs
  218. _add_fortran_library_link_interface(${target} "${target_libs}")
  219. # zero out target and target_libs
  220. set(target)
  221. set(target_libs)
  222. endif()
  223. # save the current target and set start to FALSE
  224. set(target ${lib})
  225. set(start FALSE)
  226. else()
  227. # append the lib to target_libs
  228. list(APPEND target_libs "${lib}")
  229. endif()
  230. endif()
  231. endforeach()
  232. # process anything that is left in target and target_libs
  233. if(DEFINED target)
  234. _add_fortran_library_link_interface(${target} "${target_libs}")
  235. endif()
  236. endfunction()