CMake/Modules/CMakeAddFortranSubdirectory.cmake
Peter Kokot 47805ca590
CMakeAddFortranSubdirectory: Update documentation
This adds few improvements to the CMakeAddFortranSubdirectory module
documentation.

- Added a basic usage example and "See Also" section
- Function arguments are described separate from the function signature.
2025-03-19 02:00:02 +01:00

269 lines
9.2 KiB
CMake

# 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 ``<subdir>`` 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 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 ``<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()`` 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()