my_tool_func()
is a CMake-API utility function that injects per-source compile options into a user-supplied target.
Everything works until the user (or my “matrix” test-suite) creates several targets in the same CMakeLists.txt that all reuse the same file.
At that point CMake merges the per-source properties and each target ends up with the union of flags.
// test.cpp
#include <iostream>
int main()
{
#ifdef MODE_A
std::cout << "mode A\n";
#elif defined(MODE_B)
std::cout << "mode B\n";
#elif defined(MODE_C)
std::cout << "mode C\n";
#endif
}
# CMakeLists.txt
# TARGET_DIRECTORY requires at least 3.18
cmake_minimum_required(VERSION 3.18)
project(multimode LANGUAGES CXX)
set(modes MODE_A MODE_B MODE_C)
foreach(mode IN LISTS modes)
add_executable(test.${mode} test.cpp)
# my_tool_func() effectively does this ↓
set_source_files_properties(test.cpp
TARGET_DIRECTORY test.${mode} #< Doesn't work
PROPERTIES
COMPILE_OPTIONS "-D${mode}"
)
endforeach()
Because every target is declared in the same directory, CMake stores
COMPILE_OPTIONS
once per directory + filename.
On the second and third loop iterations it concatenates the new value, so each
build receives all three switches (-DMODE_A -DMODE_B -DMODE_C
).
-DMODE_X
.Is there any native CMake facility to apply per-target, per-source compile options so identical source paths in the same directory don’t share properties?
For my test-suite CMake project that auto-generates an N×M target matrix, what pattern lets me keep all targets in one directory while still assigning unique flags to test.cpp?
If a true per-target/per-source mechanism does not exist, can my_tool_func()
reliably detect that the same path already has conflicting COMPILE_OPTIONS
set elsewhere and emit a warning or error (e.g. by storing state in a project-wide cache variable)?
Any approach that ensures a single, unpolluted flag on test.cpp
per target without affecting other files is welcome.
For the test-suite that generates targets for matrix-type input, a naive solution that comes to mind is to just create the subfolders for each target in the build tree:
foreach(_mode ${_modes})
string(TOLOWER "${_mode}" _mode)
set(_suffix ".${_mode}-mode")
set(_target "${_base_name}${_suffix}")
# in order to not leak the flags from other generated targets,
# the targets must reside in different subdirectories
set(_subdir "${CMAKE_BINARY_DIR}/${_target}")
file(MAKE_DIRECTORY "${_subdir}")
file(WRITE "${_subdir}/CMakeLists.txt" "add_executable(${_target})")
add_subdirectory("${_subdir}" "${_subdir}")
target_sources(${_target} PRIVATE "${TEST_SOURCE_DIR}/test.cpp")
# ...
endforeach()
By using $<TARGET_PROPERTY:prop>
generator expression in the properties for the source file, you may refer to the properties of the target in which the file is compiled. That is, you may assign source file property once, but assign different values for the target property. Example:
set(modes MODE_A MODE_B MODE_C)
foreach(mode IN LISTS modes)
add_executable(test.${mode} test.cpp other_file.cpp)
# Assign test mode as a property for the target.
set_target_properties(test.${mode} PROPERTIES TEST_MODE ${mode})
endforeach()
# Assign source file property only once for all targets.
set_source_files_properties(test.cpp
PROPERTIES
COMPILE_OPTIONS "-D$<TARGET_PROPERTY:TEST_MODE>"
)
Here the file test.cpp
will be compiled:
-DMODE_A
option for target test.MODE_A
,-DMODE_B
option for target test.MODE_B
and-DMODE_C
option for target test.MODE_C
.As for file other_file.cpp
, it will be compiled without any of that options.
If you want to check whether a specific compiler option is set for a source file, then you could extract source file-specific compiler options with get_source_file_property
property command:
get_source_file_property(compile_options test.cpp COMPILE_OPTIONS)
if ("-D$<TARGET_PROPERTY:TEST_MODE>" IN compile_options)
message("Test mode-specific compiler definition is already set for the file 'test.cpp'")
endif()
Note, that such check uses generator expression string: exactly that string is stored in the property.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With