# 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 function to add a Fortran project located in a subdirectory: .. command:: cmake_add_fortran_subdirectory Adds a Fortran-only subproject from ```` to the current project. .. code-block:: cmake cmake_add_fortran_subdirectory( PROJECT ARCHIVE_DIR RUNTIME_DIR LIBRARIES ... LINK_LIBRARIES [LINK_LIBS ...]... [CMAKE_COMMAND_LINE ...] NO_EXTERNAL_INSTALL ) This function checks whether the current compiler supports Fortran or attempts to locate a Fortran compiler. If a compatible Fortran compiler is found, the Fortran project is added as a subdirectory. 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 ````. ``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 ...`` items, where: * ``LINK_LIBS`` marks the start of a new pair * ```` is a library target. * ``...`` represents one or more dependencies required by ````. ``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()`` function. 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()