1# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2# file Copyright.txt or https://cmake.org/licensing for details.
3
4#[=======================================================================[.rst:
5CMakeAddFortranSubdirectory
6---------------------------
7
8Add a fortran-only subdirectory, find a fortran compiler, and build.
9
10The ``cmake_add_fortran_subdirectory`` function adds a subdirectory
11to a project that contains a fortran-only subproject.  The module will
12check the current compiler and see if it can support fortran.  If no
13fortran compiler is found and the compiler is MSVC, then this module
14will find the MinGW gfortran.  It will then use an external project to
15build with the MinGW tools.  It will also create imported targets for
16the libraries created.  This will only work if the fortran code is
17built into a dll, so :variable:`BUILD_SHARED_LIBS` is turned on in
18the project.  In addition the :variable:`CMAKE_GNUtoMS` option is set
19to on, so that Microsoft ``.lib`` files are created.  Usage is as follows:
20
21::
22
23  cmake_add_fortran_subdirectory(
24   <subdir>                # name of subdirectory
25   PROJECT <project_name>  # project name in subdir top CMakeLists.txt
26   ARCHIVE_DIR <dir>       # dir where project places .lib files
27   RUNTIME_DIR <dir>       # dir where project places .dll files
28   LIBRARIES <lib>...      # names of library targets to import
29   LINK_LIBRARIES          # link interface libraries for LIBRARIES
30    [LINK_LIBS <lib> <dep>...]...
31   CMAKE_COMMAND_LINE ...  # extra command line flags to pass to cmake
32   NO_EXTERNAL_INSTALL     # skip installation of external project
33   )
34
35Relative paths in ``ARCHIVE_DIR`` and ``RUNTIME_DIR`` are interpreted with
36respect to the build directory corresponding to the source directory
37in which the function is invoked.
38
39Limitations:
40
41``NO_EXTERNAL_INSTALL`` is required for forward compatibility with a
42future version that supports installation of the external project
43binaries during ``make install``.
44#]=======================================================================]
45
46include(CheckLanguage)
47include(ExternalProject)
48
49function(_setup_mingw_config_and_build source_dir build_dir)
50  # Look for a MinGW gfortran.
51  find_program(MINGW_GFORTRAN
52    NAMES gfortran
53    PATHS
54      c:/MinGW/bin
55      "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MinGW;InstallLocation]/bin"
56    )
57  if(NOT MINGW_GFORTRAN)
58    message(FATAL_ERROR
59      "gfortran not found, please install MinGW with the gfortran option."
60      "Or set the cache variable MINGW_GFORTRAN to the full path. "
61      " This is required to build")
62  endif()
63
64  # Validate the MinGW gfortran we found.
65  if(CMAKE_SIZEOF_VOID_P EQUAL 8)
66    set(_mingw_target "Target:.*64.*mingw")
67  else()
68    set(_mingw_target "Target:.*mingw32")
69  endif()
70  execute_process(COMMAND "${MINGW_GFORTRAN}" -v
71    ERROR_VARIABLE out ERROR_STRIP_TRAILING_WHITESPACE)
72  if(NOT "${out}" MATCHES "${_mingw_target}")
73    string(REPLACE "\n" "\n  " out "  ${out}")
74    message(FATAL_ERROR
75      "MINGW_GFORTRAN is set to\n"
76      "  ${MINGW_GFORTRAN}\n"
77      "which is not a MinGW gfortran for this architecture.  "
78      "The output from -v does not match \"${_mingw_target}\":\n"
79      "${out}\n"
80      "Set MINGW_GFORTRAN to a proper MinGW gfortran for this architecture."
81      )
82  endif()
83
84  # Configure scripts to run MinGW tools with the proper PATH.
85  get_filename_component(MINGW_PATH ${MINGW_GFORTRAN} PATH)
86  file(TO_NATIVE_PATH "${MINGW_PATH}" MINGW_PATH)
87  string(REPLACE "\\" "\\\\" MINGW_PATH "${MINGW_PATH}")
88  configure_file(
89    ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/CMakeAddFortranSubdirectory/config_mingw.cmake.in
90    ${build_dir}/config_mingw.cmake
91    @ONLY)
92  configure_file(
93    ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/CMakeAddFortranSubdirectory/build_mingw.cmake.in
94    ${build_dir}/build_mingw.cmake
95    @ONLY)
96endfunction()
97
98function(_add_fortran_library_link_interface library depend_library)
99  set_target_properties(${library} PROPERTIES
100    IMPORTED_LINK_INTERFACE_LIBRARIES_NOCONFIG "${depend_library}")
101endfunction()
102
103
104function(cmake_add_fortran_subdirectory subdir)
105  # Parse arguments to function
106  set(options NO_EXTERNAL_INSTALL)
107  set(oneValueArgs PROJECT ARCHIVE_DIR RUNTIME_DIR)
108  set(multiValueArgs LIBRARIES LINK_LIBRARIES CMAKE_COMMAND_LINE)
109  cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
110  if(NOT ARGS_NO_EXTERNAL_INSTALL)
111    message(FATAL_ERROR
112      "Option NO_EXTERNAL_INSTALL is required (for forward compatibility) "
113      "but was not given."
114      )
115  endif()
116
117  # if we are not using MSVC without fortran support
118  # then just use the usual add_subdirectory to build
119  # the fortran library
120  check_language(Fortran)
121  if(NOT (MSVC AND (NOT CMAKE_Fortran_COMPILER)))
122    add_subdirectory(${subdir})
123    return()
124  endif()
125
126  # if we have MSVC without Intel fortran then setup
127  # external projects to build with mingw fortran
128
129  set(source_dir "${CMAKE_CURRENT_SOURCE_DIR}/${subdir}")
130  set(project_name "${ARGS_PROJECT}")
131  set(library_dir "${ARGS_ARCHIVE_DIR}")
132  set(binary_dir "${ARGS_RUNTIME_DIR}")
133  set(libraries ${ARGS_LIBRARIES})
134  # use the same directory that add_subdirectory would have used
135  set(build_dir "${CMAKE_CURRENT_BINARY_DIR}/${subdir}")
136  foreach(dir_var library_dir binary_dir)
137    if(NOT IS_ABSOLUTE "${${dir_var}}")
138      get_filename_component(${dir_var}
139        "${CMAKE_CURRENT_BINARY_DIR}/${${dir_var}}" ABSOLUTE)
140    endif()
141  endforeach()
142  # create build and configure wrapper scripts
143  _setup_mingw_config_and_build("${source_dir}" "${build_dir}")
144  # create the external project
145  externalproject_add(${project_name}_build
146    SOURCE_DIR ${source_dir}
147    BINARY_DIR ${build_dir}
148    CONFIGURE_COMMAND ${CMAKE_COMMAND}
149    -P ${build_dir}/config_mingw.cmake
150    BUILD_COMMAND ${CMAKE_COMMAND}
151    -P ${build_dir}/build_mingw.cmake
152    BUILD_ALWAYS 1
153    INSTALL_COMMAND ""
154    )
155  # create imported targets for all libraries
156  foreach(lib ${libraries})
157    add_library(${lib} SHARED IMPORTED GLOBAL)
158    set_property(TARGET ${lib} APPEND PROPERTY IMPORTED_CONFIGURATIONS NOCONFIG)
159    set_target_properties(${lib} PROPERTIES
160      IMPORTED_IMPLIB_NOCONFIG   "${library_dir}/lib${lib}.lib"
161      IMPORTED_LOCATION_NOCONFIG "${binary_dir}/lib${lib}.dll"
162      )
163    add_dependencies(${lib} ${project_name}_build)
164  endforeach()
165
166  # now setup link libraries for targets
167  set(start FALSE)
168  set(target)
169  foreach(lib ${ARGS_LINK_LIBRARIES})
170    if("${lib}" STREQUAL "LINK_LIBS")
171      set(start TRUE)
172    else()
173      if(start)
174        if(DEFINED target)
175          # process current target and target_libs
176          _add_fortran_library_link_interface(${target} "${target_libs}")
177          # zero out target and target_libs
178          set(target)
179          set(target_libs)
180        endif()
181        # save the current target and set start to FALSE
182        set(target ${lib})
183        set(start FALSE)
184      else()
185        # append the lib to target_libs
186        list(APPEND target_libs "${lib}")
187      endif()
188    endif()
189  endforeach()
190  # process anything that is left in target and target_libs
191  if(DEFINED target)
192    _add_fortran_library_link_interface(${target} "${target_libs}")
193  endif()
194endfunction()
195