Browse Source

Add CMakeAddFortranSubdirectory to use MinGW gfortran in VS

This patch adds a new module that allows for easy integration of MinGW
gfortran and the Visual Studio compiler. It is done in a function called
cmake_add_fortran_subdirectory. The patch also includes a test for this
feature.
Bill Hoffman 14 years ago
parent
commit
538c3452ad

+ 167 - 0
Modules/CMakeAddFortranSubdirectory.cmake

@@ -0,0 +1,167 @@
+# - Use MinGW gfortran from VS if a fortran compiler is not found.
+# The 'add_fortran_subdirectory' function adds a subdirectory
+# to a project that contains a fortran only sub-project. The module
+# will check the current compiler and see if it can support fortran.
+# If no fortran compiler is found and the compiler is MSVC, then
+# this module will find the MinGW gfortran.  It will then use
+# an external project to build with the MinGW tools.  It will also
+# create imported targets for the libraries created.  This will only
+# work if the fortran code is built into a dll, so BUILD_SHARED_LIBS
+# is turned on in the project.  In addition the GNUtoMS option is set
+# to on, so that the MS .lib files are created.
+# Usage is as follows:
+# cmake_add_fortran_subdirectory(
+#   <subdir>                 # name of subdirectory
+#    PROJECT <project_name>  # project name in sbudir toplevel CMakeLists.txt
+#  ARCHIVE_DIR <dir>         # .lib location relative to root binary tree (lib)
+#  RUNTIME_DIR <dir>         # .dll location relative to root binary tree (bin)
+#  LIBRARIES lib2 lib2    # names of libraries created and exported
+#  LINK_LIBRARIES            # link interface libraries for LIBRARIES
+#   LINK_LIBS <lib1>  <dep1> <dep2> ... <depN>
+#   LINK_LIBS <lib2> <dep1> <dep2> ... <depN>
+#  CMAKE_COMMAND_LINE        # extra command line flags to pass to cmake
+#   )
+#
+
+#=============================================================================
+# Copyright 2002-2009 Kitware, Inc.
+#
+# Distributed under the OSI-approved BSD License (the "License");
+# see accompanying file Copyright.txt for details.
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+# (To distribute this file outside of CMake, substitute the full
+#  License text for the above reference.)
+
+
+set(_MS_MINGW_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR})
+include(CheckLanguage)
+include(ExternalProject)
+include(CMakeParseArguments)
+
+function(_setup_mingw_config_and_build source_dir)
+  find_program(MINGW_GFORTRAN NAMES gfortran
+    HINTS
+    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()
+  execute_process(COMMAND ${MINGW_GFORTRAN} -v ERROR_VARIABLE out)
+  if(NOT "${out}" MATCHES "Target:.*mingw32")
+    message(FATAL_ERROR "Non-MinGW gfortran found: ${MINGW_GFORTRAN}\n"
+      "output from -v [${out}]\n"
+      "set MINGW_GFORTRAN to the path to MinGW fortran.")
+  endif()
+  get_filename_component(MINGW_PATH ${MINGW_GFORTRAN} PATH)
+  file(TO_NATIVE_PATH "${MINGW_PATH}" MINGW_PATH)
+  string(REPLACE "\\" "\\\\" MINGW_PATH "${MINGW_PATH}")
+  configure_file(
+    ${_MS_MINGW_SOURCE_DIR}/CMakeAddFortranSubdirectory/config_mingw.cmake.in
+    ${CMAKE_CURRENT_BINARY_DIR}/config_mingw.cmake
+    @ONLY)
+  configure_file(
+    ${_MS_MINGW_SOURCE_DIR}/CMakeAddFortranSubdirectory/build_mingw.cmake.in
+    ${CMAKE_CURRENT_BINARY_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)
+  # 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
+
+  # Parse arguments to function
+  set(oneValueArgs PROJECT ARCHIVE_DIR RUNTIME_DIR)
+  set(multiValueArgs LIBRARIES LINK_LIBRARIES CMAKE_COMMAND_LINE)
+  cmake_parse_arguments(ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+  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}")
+  # create build and configure wrapper scripts
+  _setup_mingw_config_and_build(${source_dir})
+  # create the external project
+  externalproject_add(${project_name}_build
+    SOURCE_DIR ${source_dir}
+    BINARY_DIR ${build_dir}
+    CONFIGURE_COMMAND ${CMAKE_COMMAND}
+    -P ${CMAKE_CURRENT_BINARY_DIR}/config_mingw.cmake
+    BUILD_COMMAND ${CMAKE_COMMAND}
+    -P ${CMAKE_CURRENT_BINARY_DIR}/build_mingw.cmake
+    INSTALL_COMMAND ""
+    )
+  # make the external project always run make with each build
+  externalproject_add_step(${project_name}_build forcebuild
+    COMMAND ${CMAKE_COMMAND}
+    -E remove
+    ${CMAKE_CURRENT_BUILD_DIR}/${project_name}-prefix/src/${project_name}-stamp/${project_name}-build
+    DEPENDEES configure
+    DEPENDERS build
+    ALWAYS 1
+    )
+  # create imported targets for all libraries
+  foreach(lib ${libraries})
+    add_library(${lib} SHARED IMPORTED)
+    set_property(TARGET ${lib} APPEND PROPERTY IMPORTED_CONFIGURATIONS NOCONFIG)
+    set_target_properties(${lib} PROPERTIES
+      IMPORTED_IMPLIB_NOCONFIG
+      "${build_dir}/${library_dir}/lib${lib}.lib"
+      IMPORTED_LOCATION_NOCONFIG
+      "${build_dir}/${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()

+ 2 - 0
Modules/CMakeAddFortranSubdirectory/build_mingw.cmake.in

@@ -0,0 +1,2 @@
+set(ENV{PATH} "@MINGW_PATH@\;$ENV{PATH}")
+execute_process(COMMAND "@CMAKE_COMMAND@" --build . )

+ 8 - 0
Modules/CMakeAddFortranSubdirectory/config_mingw.cmake.in

@@ -0,0 +1,8 @@
+set(ENV{PATH} "@MINGW_PATH@\;$ENV{PATH}")
+execute_process(
+  COMMAND "@CMAKE_COMMAND@" "-GMinGW Makefiles"
+  -DCMAKE_Fortran_COMPILER:PATH=@MINGW_GFORTRAN@
+  -DBUILD_SHARED_LIBS=ON
+  -DCMAKE_GNUtoMS=ON
+  @ARGS_CMAKE_COMMAND_LINE@
+  "@source_dir@")

+ 9 - 0
Tests/CMakeLists.txt

@@ -163,6 +163,15 @@ IF(BUILD_TESTING)
   IF(CMAKE_Fortran_COMPILER)
     ADD_TEST_MACRO(FortranOnly FortranOnly)
   ENDIF()
+  # test Visual Studio GNU Fortran mixing with cmake_add_fortran_subdirectory
+  # run this project if we have a working fortran compiler or
+  # the test is enabled with CMAKE_TEST_CMAKE_ADD_FORTRAN cache variable.
+  # If you enable the test, CMake should find the MinGW fortran install,
+  # or in some cases you might need to set the PATH so that cmake can find
+  # the gfortran from mingw.
+  IF(CMAKE_Fortran_COMPILER OR CMAKE_TEST_CMAKE_ADD_FORTRAN)
+    ADD_TEST_MACRO(VSGNUFortran ${CMAKE_COMMAND} -P runtest.cmake)
+  ENDIF()
   ADD_TEST_MACRO(COnly COnly)
   ADD_TEST_MACRO(CxxOnly CxxOnly)
   ADD_TEST_MACRO(IPO COnly/COnly)

+ 39 - 0
Tests/VSGNUFortran/CMakeLists.txt

@@ -0,0 +1,39 @@
+cmake_minimum_required(VERSION 2.8)
+project(VSGNUFortran)
+# force the executable to be put out of Debug/Release dir
+# because gmake build of fortran will not be in a config
+# directory, and for easier testing we want the exe and .dll
+# to be in the same directory.
+if(CMAKE_CONFIGURATION_TYPES)
+  foreach(config ${CMAKE_CONFIGURATION_TYPES})
+    string(TOUPPER "${config}" config)
+    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${config}
+      "${PROJECT_BINARY_DIR}/bin")
+    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${config}
+      "${PROJECT_BINARY_DIR}/bin")
+  endforeach()
+else()
+  set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
+  set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
+endif()
+
+include(CMakeAddFortranSubdirectory)
+# add the fortran subdirectory as a fortran project
+# the subdir is fortran, the project is FortranHello
+cmake_add_fortran_subdirectory(fortran
+  PROJECT FortranHello  # project name in toplevel CMakeLists.txt
+  ARCHIVE_DIR ../bin # .lib location relative to root binary tree
+  RUNTIME_DIR ../bin # .dll location relative to root binary tree
+  LIBRARIES hello world # target libraries created
+  CMAKE_COMMAND_LINE -DEXECUTABLE_OUTPUT_PATH=../bin
+                     -DLIBRARY_OUTPUT_PATH=../bin
+  LINK_LIBRARIES  # link interface libraries
+   LINK_LIBS hello world  # hello needs world to link
+   )
+
+include_directories(${VSGNUFortran_BINARY_DIR}/fortran)
+add_subdirectory(c_code)
+# use a cmake script to run the executable so that PATH
+# can be set with the MinGW/bin in it, and the fortran
+# runtime libraries can be found.
+configure_file(runtest.cmake.in runtest.cmake @ONLY)

+ 2 - 0
Tests/VSGNUFortran/c_code/CMakeLists.txt

@@ -0,0 +1,2 @@
+add_executable(c_using_fortran main.c)
+target_link_libraries(c_using_fortran hello)

+ 7 - 0
Tests/VSGNUFortran/c_code/main.c

@@ -0,0 +1,7 @@
+#include <HelloWorldFCMangle.h> // created by FortranCInterface
+extern void FC_hello(void);
+int main()
+{
+ FC_hello();
+ return 0;
+}

+ 12 - 0
Tests/VSGNUFortran/fortran/CMakeLists.txt

@@ -0,0 +1,12 @@
+cmake_minimum_required(VERSION 2.8)
+project(FortranHello Fortran C)
+
+include(FortranCInterface)
+FortranCInterface_HEADER(HelloWorldFCMangle.h
+                         MACRO_NAMESPACE "FC_"
+                         SYMBOL_NAMESPACE "FC_"
+                         SYMBOLS hello world)
+
+add_library(hello SHARED hello.f)
+add_library(world SHARED world.f)
+target_link_libraries(hello world)

+ 7 - 0
Tests/VSGNUFortran/fortran/hello.f

@@ -0,0 +1,7 @@
+!DEC$ ATTRIBUTES DLLEXPORT :: HELLO
+	SUBROUTINE HELLO
+
+	PRINT *, 'Hello'
+	CALL WORLD
+
+	END

+ 6 - 0
Tests/VSGNUFortran/fortran/world.f

@@ -0,0 +1,6 @@
+!DEC$ ATTRIBUTES DLLEXPORT :: WORLD
+	SUBROUTINE WORLD
+
+	PRINT *, 'World!'
+
+	END

+ 23 - 0
Tests/VSGNUFortran/runtest.cmake.in

@@ -0,0 +1,23 @@
+get_filename_component(MINGW_PATH "@MINGW_GFORTRAN@" PATH)
+if(NOT EXISTS "${MINGW_PATH}")
+  set(test_exe
+    "@VSGNUFortran_BINARY_DIR@/bin/c_using_fortran@CMAKE_EXECUTABLE_SUFFIX@")
+  message("run: ${test_exe}")
+  execute_process(COMMAND "${test_exe}"
+    RESULT_VARIABLE res)
+  if(NOT "${res}" EQUAL 0)
+    message(FATAL_ERROR "${test_exe} returned a non 0 value")
+  endif()
+  return()
+endif()
+file(TO_NATIVE_PATH "${MINGW_PATH}" MINGW_PATH)
+string(REPLACE "\\" "\\\\" MINGW_PATH "${MINGW_PATH}")
+message("${MINGW_PATH}")
+set(test_exe "@VSGNUFortran_BINARY_DIR@/bin/c_using_fortran.exe")
+set(ENV{PATH} "${MINGW_PATH}";$ENV{PATH})
+message("run ${test_exe}")
+execute_process(COMMAND "${test_exe}"
+  RESULT_VARIABLE res)
+if(NOT "${res}" EQUAL 0)
+  message(FATAL_ERROR "${test_exe} returned a non 0 value")
+endif()