The pattern that we have used here is:
- Define a function or macro and place it in a module
- Include the module
- Call the function or macro
From the output, we can see that the code checks each flag in the list and as soon as the check is successful, it prints the successful compile flag. Let us look inside the set_compiler_flag.cmake module. This module, in turn, includes three modules:
include(CheckCCompilerFlag)
include(CheckCXXCompilerFlag)
include(CheckFortranCompilerFlag)
These are standard CMake modules and CMake will locate them in ${CMAKE_MODULE_PATH}. These modules provide the check_c_compiler_flag, check_cxx_compiler_flag, and check_fortran_compiler_flag macros, respectively. Then comes the function definition:
function(set_compiler_flag _result _lang)
...
endfunction()
The set_compiler_flag function expects two arguments and we call them _result (this will hold the successful compile flag or the empty string "") and _lang (which specifies the language: C, C++, or Fortran).
We would like to be able to call the function like this:
set_compiler_flag(working_compile_flag C REQUIRED "-Wall" "-warn all")
This call has five arguments, but the function header only expects two. This means that REQUIRED, "-Wall", and "-warn all" will be placed in ${ARGN}. From ${ARGN}, we first build a list of flags using foreach. At the same time, we filter out REQUIRED from the list of flags and use it to set _flag_is_required:
# build a list of flags from the arguments
set(_list_of_flags)
# also figure out whether the function
# is required to find a flag
set(_flag_is_required FALSE)
foreach(_arg IN ITEMS ${ARGN})
string(TOUPPER "${_arg}" _arg_uppercase)
if(_arg_uppercase STREQUAL "REQUIRED")
set(_flag_is_required TRUE)
else()
list(APPEND _list_of_flags "${_arg}")
endif()
endforeach()
Now, we will loop over ${_list_of_flags}, try each flag, and if _flag_works is set to TRUE, we set _flag_found to TRUE and abort a further search:
set(_flag_found FALSE)
# loop over all flags, try to find the first which works
foreach(flag IN ITEMS ${_list_of_flags})
unset(_flag_works CACHE)
if(_lang STREQUAL "C")
check_c_compiler_flag("${flag}" _flag_works)
elseif(_lang STREQUAL "CXX")
check_cxx_compiler_flag("${flag}" _flag_works)
elseif(_lang STREQUAL "Fortran")
check_Fortran_compiler_flag("${flag}" _flag_works)
else()
message(FATAL_ERROR "Unknown language in set_compiler_flag: ${_lang}")
endif()
# if the flag works, use it, and exit
# otherwise try next flag
if(_flag_works)
set(${_result} "${flag}" PARENT_SCOPE)
set(_flag_found TRUE)
break()
endif()
endforeach()
The unset(_flag_works CACHE) line is there to make sure that the result of check_*_compiler_flag is not cached between calls using the same _flag_works result variable.
If a flag is found and _flag_works set to TRUE, we define the variable mapped to by _result:
set(${_result} "${flag}" PARENT_SCOPE)
This needs to be done with PARENT_SCOPE since we are modifying a variable that we wish to print and use outside the function body. Note, in addition, how we dereferenced the variable _result passed from parent scope using the ${_result} syntax. This is necessary to ensure that the working flag is set as value of the variable passed from parent scope when invoking the function, regardless of its name. If no flag is found and the REQUIRED keyword was provided, we stop the configuration with an error message:
# raise an error if no flag was found
if(_flag_is_required AND NOT _flag_found)
message(FATAL_ERROR "None of the required flags were supported")
endif()