cmake: how to check if preprocessor is defined - cmake

I can't get cmake to test if a preprocessor has been defined or not. Eg:
cmake_minimum_required(VERSION 2.8.9)
project (cmake-test)
add_definitions(-DOS=LINUX)
if(NOT <what condition goes here?>)
message(FATAL_ERROR "OS is not defined")
endif()
The following tests don't work:
if (NOT COMMAND OS)
if (NOT DEFINED OS)
if (NOT OS)
I can get it to work by using set() and just testing for a regular cmake variable and then defining the preprocessor macro. Eg:
set(OS LINUX)
if (OS)
add_definitions(-DOS=${OS})
else()
message(FATAL_ERROR "OS is not defined")
endif()
In case, you're wondering why I need to test it if the variable/preprocessor is in the same file, it's because in the final implementation these will come from an external file which is includeed in the main CMakeFile.txt Eg:
include(project_defs.txt)
if (OS)
....

This is to complete the answer by arrowd.
I also tried the COMPILE_DEFINITIONS option as mentioned above by arrowd unsuccessfully.
Following the documentation of CMake, at least for version 3.x, it turns out that when you call add_definitions() in CMake, it adds the definitions to the COMPILE_DEFINITIONS directory property.
Therefore, lets say you are defining the following as per your code:
add_definitions(-DOS=LINUX)
To retrieve the string with the definitions added into the variable "MYDEFS" you can use the following lines in CMake:
get_directory_property(MYDEFS COMPILE_DEFINITIONS)
MESSAGE( STATUS "Compile defs contain: " ${MYDEFS} )
Then you can check if in ${MYDEFS} the define you are looking for exists or not. For instance
if(MYDEFS MATCHES "^OS=" OR MYDEFS MATCHES ";OS=")
MESSAGE( STATUS "OS defined" )
else()
# You can define your OS here if desired
endif()

Normally, all definitions that are passed to the compiler are controlled by CMake. That is, you create a CMake variable with
option(SOMEFEATURE "Feature description" ON)
or
set(OS "" CACHE STRING "Select your OS")
User sets them via cmake -D OS=DOS or in the CMake GUI. Then you can use if() operator to conditionally add_definitions() to the compiler command line.
UPDATE:
If you really want to access preprocessor flags, there is a COMPILE_DEFINITIONS target property. You can access it this way:
get_target_property(OUTVAR target COMPILE_DEFINITIONS)

Related

if condition in .CMake files

I have the following variable in a .cmake file:
add_definitions(-DENABLE_TEST)
if(DEFINED ENABLE_TEST)
message("ENABLE_TEST defined")
else()
message("ENABLE_TEST NOT defined")
endif()
I have to use the variable in other CMakelists.txt too.
Why does the above "if" always coming out as not defined?
How do I check if the variable is defined or not?
The add_definitions() command adds compile definitions to the compilation of your source files. It does not define CMake variables. The if(DEFINED ... syntax checks for the existence of CMake or environment variables, not the existence of compile definitions.
You can use the set() CMake command to define CMake variables, then subsequently, check for their existence in this manner:
set(ENABLE_TEST 1)
if(DEFINED ENABLE_TEST)
message("ENABLE_TEST defined")
else()
message("ENABLE_TEST NOT defined")
endif()

Getting a list of tests in CMake

My CMake project is structured with a submodule that is controlled by a third party. They have a test that is broken, and I'd like to disable it.
I thought this would be something like
add_subdirectory(thirdparty)
set_tests_properties(their_broken_test PROPERTIES DISABLED true)
But not so simple, CMake tells me this test doesn't exist.
So I tried to step back and get a list of all of the tests that CMake does think exists. But this is even more baffling:
project(cmaketest)
cmake_minimum_required(VERSION 3.5.0 FATAL_ERROR)
enable_testing()
add_test(ls /bin/ls)
get_property(LIST_OF_TESTS DIRECTORY . PROPERTY TESTS)
message(STATUS "Here are the tests: ${LIST_OF_TESTS}")
On CMake 3.10.2, this shows no tests. But on CMake 3.12.1, it shows the ls test.
Is this a bug? Or is there a way to do it with the older CMake? (3.10.2 is what is in the Ubuntu Bionic repos.)
The TESTS property was added in CMake 3.12 (release notes):
A TESTS directory property was added to hold the list of tests defined by the add_test() command.
You can do something like this to collect a list of test names as they're added.
if(${CMAKE_VERSION} VERSION_LESS "3.12.0")
function(add_test)
_add_test(${ARGN})
# There are two signatures to the add_test function to handle
if(ARGV0 STREQUAL "NAME")
set(TEST_NAME "${ARGV1}")
else()
set(TEST_NAME "${ARGV0}")
endif()
list(APPEND ALL_TESTS "${TEST_NAME}")
endfunction()
endif()
However, it does not appear you can set properties on a test defined in another directory.

Get CMake to not be silent about sources it doesn't understand?

Suppose you have a very simple CMakeLists.txt
add_executable(silent T.cpp A.asm)
CMake will happily generate a C++ target for building silent, with T.cpp in it, but will silently drop any and all reference to A.asm, because it doesn't know what to do with the suffix.
Is there any way to get CMake to loudly complain about this source file it doesn't understand (to aid in porting a Makefile to CMake).
Ignoring unknown file extensions is - unfortunately for your case - by design.
If I look at the code of cmGeneratorTarget::ComputeKindedSources() anything unknown ends up to be classified as SourceKindExtra (to be added as such to generated IDE files).
So I tested a little and came up with the following script that evaluates your executable target source files for valid file extensions by overwriting add_executable() itself:
cmake_minimum_required(VERSION 3.3)
project(silent CXX)
file(WRITE T.cpp "int main() { return 0; }")
file(WRITE T.h "")
file(WRITE A.asm "")
function(add_executable _target)
_add_executable(${_target} ${ARGN})
get_property(_langs GLOBAL PROPERTY ENABLED_LANGUAGES)
foreach(_lang IN LISTS _langs)
list(APPEND _ignore "${CMAKE_${_lang}_IGNORE_EXTENSIONS}")
endforeach()
get_target_property(_srcs ${_target} SOURCES)
foreach(_src IN LISTS _srcs)
get_source_file_property(_lang "${_src}" LANGUAGE)
get_filename_component(_ext "${_src}" EXT)
string(SUBSTRING "${_ext}" 1 -1 _ext) # remove leading dot
if (NOT _lang AND NOT _ext IN_LIST _ignore)
message(FATAL_ERROR "Target ${_target}: Unknown source file type '${_src}'")
endif()
endforeach()
endfunction()
add_executable(silent T.cpp T.h A.asm)
Since you wanted a rather loudly complain by CMake I declared it an FATAL_ERROR in this example implementation.
CMake doesn't just drop unknown files in add_executable().
If alongside with
add_executable(silent T.cpp A.asm)
you have
add_custom_command(OUTPUT A.asm COMMAND <...>
DEPENDS <dependees>)
then whenever <dependees> changed CMake will rerun command for create A.asm before compiling the executable.
Note, that automatical headers scanning doesn't provide such functionality: if your executable includes foo.h then executable will be rebuilt only when foo.h itself is changed. Any custom command creating this header will be ignored.
However, you may change behavior of add_executable by redefining it. See #Florian's answer for example of such redefinition.

Check CMake Cache Variable in Toolchain File

I'm having trouble setting a configuration variable via the command line. I can't determine it from the system, so I expect the user to specify:
cmake -DCMAKE_TOOLCHAIN_FILE=../android.toolchain -DANDROID_ABI:STRING="arm64" ..
Inside my android.toolchain, I have the following:
message(STATUS "Android ABI: ${ANDROID_ABI}")
if( "${ANDROID_ABI}" STREQUAL "" )
message(FATAL_ERROR "Please specifiy ABI at cmake call -DANDROID_ABI:STRING=armeabi or -DANDROID_ABI:STRING=arm64")
endif()
No matter what, it fails at this line EVEN THOUGH it prints out the correct arm64:
-- Android ABI: arm64
CMake Error at yaml-cpp/android.toolchain:45 (message):
Please specifiy ABI at cmake call -DANDROID_ABI:STRING=armeabi or -DANDROID_ABI:STRING=arm64
Could anyone direct me to what I'm doing wrong?
I think this has to do with:
-D adds a cache variable instead of a normal variable
This is in a toolchain file... it seems to ignore cache variables
Any thoughts or suggestions?
I don't pretend to fully understand what's going on behind the scenes, but here's a workaround that works for me:
# Problem: CMake runs toolchain files multiple times, but can't read cache variables on some runs.
# Workaround: On first run (in which cache variables are always accessible), set an intermediary environment variable.
if (FOO)
# Environment variables are always preserved.
set(ENV{_FOO} "${FOO}")
else ()
set(FOO "$ENV{_FOO}")
endif ()
CMake 3.6 introduces variable CMAKE_TRY_COMPILE_PLATFORM_VARIABLES which contains a list of variables, automatically passed from the main project to the project, created with try_compile.
A toolchain may add its variables to that list, so they could be extracted in a subproject:
message(STATUS "Android ABI: ${ANDROID_ABI}")
if( "${ANDROID_ABI}" STREQUAL "" )
message(FATAL_ERROR "Please specifiy ABI at cmake call -DANDROID_ABI:STRING=armeabi or -DANDROID_ABI:STRING=arm64")
endif()
# propagate the variable into "inner" subprojects.
list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES "ANDROID_ABI")
Caveats:
This approach affects only to source flow of try_compile command. It won't work when try_compile is used for create fully-fledged CMake project with signature
try_compile(<resultVar> <bindir> <srcdir> <projectName> ...)
(Approach with setting environment variable, as described in the #sorbet answer, works perfectly in this case.)
This approach won't work for a subproject, created with ExternalProject_Add.
(Approach with setting environment variable fails in that case too.)

Conditional add_custom_command in CMake

I am using a macro to create precompiled headers for my cmake project. For gcc, this macro uses add_custom_command to create a *.h.gch file which can then be added to the target along with the other source files with add_executable/add_library. The problem is that sometimes the same *.h.gch file is used for two different targets, because some libraries are built both as static and dynamic libs.
I need to call the macro after each of the add_library calls because for MSVC/Xcode, one needs to adjust the target properties to enable PCH usage/compilation. But for gcc, this results in an error as I'm trying to use add_custom_command with an output that already has a build rule (the .gch). Currently I am avoiding this error by just skipping the add_custom_command for any target that contains "Static" in its name - this happens to work because all the static libraries in the project have a "Static" postfix, but its obviously not a very elegant solution.
Is there a way in cmake to check if a target already has a build rule, or alternatively, a way to allow add_custom_command to fail silently without causing an error? Or is there a way to change my design so that I can avoid the problem entirely? I suppose one "solution" would be to add a conditional check in each of the CMakeLists, but I really don't want to do that.
This is the code I am currently using:
The Macro:
macro(SET_PRECOMPILED_HEADER targetName PCHFile)
if(MSVC)
# PCH for MSVC
elseif(${CMAKE_GENERATOR} MATCHES "Xcode")
# PCH for Xcode
else() #gcc
if(NOT ${targetName} MATCHES "Static") ## <-- this is bad
## set the correct "compilerArgs"
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PCHFile}.gch
COMMAND ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1} ${compilerArgs}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${PCHFile}
)
endif()
endmacro(SET_PRECOMPILED_HEADER targetName PCHFile)
...then in the CMakeLists, something like this:
# Dynamic version:
set(MODULE_NAME MyLib)
project(${MODULE_NAME})
## set ${sources}
add_library(${MODULE_NAME} SHARED ${sources} "src/precompiled.h.${PCH_EXT}")
set_target_properties(${MODULE_NAME} PROPERTIES COMPILE_DEFINITIONS MY_DLL_DEFINITION)
SET_PRECOMPILED_HEADER(${MODULE_NAME} "src/precompiled.h")
# Static version:
set(MODULE_NAME MyLibStatic)
project(${MODULE_NAME})
add_library(${MODULE_NAME} ${sources} "src/precompiled.h.${PCH_EXT}")
set_target_properties(${MODULE_NAME} PROPERTIES COMPILE_DEFINITIONS MY_STATIC_DEFINITION)
SET_PRECOMPILED_HEADER(${MODULE_NAME} "src/precompiled.h")
Thanks for your help! I'm sorry if this is a duplicate - there are already several questions on add_custom_command, but none of them quite seem to address what I'm after.
First, you can create target for each PCH and then use this before declaring new target:
if(TARGET ${PCHFile}.gch)
Another way:
In the root CMakeLists.txt:
set(PRECOMPILED_HEADERS "" CACHE INTERNAL "")
In the macro:
list(FIND PRECOMPILED_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/${PCHFile}.gch res)
if(NOT res EQUAL -1)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PCHFile}.gch
COMMAND ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1} ${compilerArgs}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${PCHFile}
)
list(APPEND PRECOMPILED_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/${PCHFile}.gch)
endif()