CMake is a wonderful tool for configuring your build system. It has built-in support for C, C++, and Fortran projects. The support for C and C++ is excellent. The support for Fortran is quite good, but it there is some missing functionality in some areas. For me, the most obvious deficiency was OpenMP support for Fortran. This can be easily fixed with a little detective work and elbow grease.
Pretty much all of the CMake functions are actually implemented as scripts in
$SOMEPATH/share/cmake/Modules
, where for me $SOMEPATH=/usr/local
but your mileage may
very (they will likely be in your install path on a Windows machine). All the packages that
are known to the FIND_PACKAGE
function must be implemented as one of these scripts with
the name FindPackage.cmake
, where “Package” is the package that is searched for.
If you take a look in $SOMEPATH/share/cmake/Modules/FindOpenMP.cmake
, you will notice
that it returns the variables OPENMP_FOUND
, OpenMP_C_FLAGS
, OpenMP_CXX_FLAGS
; notice
that there is no OpenMP_Fortran_FLAGS
. This is OK if the C compiler that CMake found
is the same vendor as your Fortran compiler (i.e. gcc & gfortran or icc & ifort) so that
you can just use the C flags for the Fortran flags. However, this is not a robust solution;
for example, what if the openmp flags between the C and Fortran compilers of a vendor don’t
match? Or, what if you are using a gcc C compiler with an intel Fortran compiler (as I was)?
This obviously won’t work. The best solution is to tell CMake how to look for Fortran OpenMP
flags.
I’m not going to go into details, but basically all known OpenMP compiler
flags are implemented. Here is an excerpt from the FindOpenMP.cmake
file:
set(OpenMP_FLAG_CANDIDATES
#GNU
"-fopenmp"
#Microsoft Visual Studio
"/openmp"
#Intel windows
"-Qopenmp"
#PathScale, Intel
"-openmp"
#Empty, if compiler automatically accepts openmp
" "
#Sun
"-xopenmp"
#HP
"+Oopenmp"
#IBM XL C/c++
"-qsmp"
#Portland Group, MIPSpro
"-mp"
)
It then uses a simple C file that will fail without OpenMP to check for the correct flag:
#include <omp.h>
int main() {
#ifdef _OPENMP
return 0;
#else
breaks_on_purpose
#endif
}
For each flag that is given in the above list, CMake attempts to compile the C snippet
using the check_c_source_compiles
function that comes with CMake.
The first flag that does not cause a compiler error is considered the correct flag.
Note: You can download the file FindOpenMP_Fortran.cmake
at my cmake_fortran_template
GitHub page.
Instead of adding Fortran to FindOpenMP.cmake
, I decided to make a new
FindOpenMP_Fortran.cmake
file. Essentially, I made a copy of the original
FindOpenMP.cmake
and placed it in ${CMAKE_SOURCE_DIR}/cmake/Modules/FindOpenMP_Fortran.cmake
,
where ${CMAKE_SOURCE_DIR}
is the top level directory of my Fortran project.
I decided that in addition to simply finding the Fortran flag, I wanted my function to also determine the max number of threads that OpenMP can use on the current system and return that as well. To do that, I used the following Fortran snippet:
program TestOpenMP
use omp_lib
write(*,'(I2)',ADVANCE='NO') omp_get_num_procs()
end program TestOpenMP
Interestingly, there is no check_fortran_source_compiles
(someone should request this)
so all the testing had to be done manually. The business end of this code is given
below:
TRY_RUN (OpenMP_RUN_FAILED OpenMP_FLAG_DETECTED ${CMAKE_BINARY_DIR}
${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/testFortranOpenMP.f90
COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS}
CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${MACRO_CHECK_FUNCTION_DEFINITIONS}
COMPILE_OUTPUT_VARIABLE OUTPUT
RUN_OUTPUT_VARIABLE OMP_NUM_PROCS_INTERNAL)
The correct flag is given in OpenMp_Fortran_FLAGS
, and the number of OpenMP
threads is given in OMP_NUM_PROCS
.
Using this is as simple as:
SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules/")
FIND_PACKAGE(OpenMP_Fortran)
SET_TARGET_PROPERTIES(${EXECUTABLE_NAME} PROPERTIES
COMPILE_FLAGS "${OpenMP_Fortran_FLAGS}"
LINK_FLAGS "${OpenMP_Fortran_FLAGS}")