Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CMake: give one source file different flags in several targets declared in the same directory

Tags:

c++

cmake

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.

Minimal reproducible example

// 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()

Observed:

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).

Required

  1. In each target test.cpp must compile with exactly one -DMODE_X.
  2. No other source in the target may see that flag.
  3. The user’s sources must remain intact (no renaming, copying, or moving).

Questions

  1. 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?

  2. 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?

  3. 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()
like image 671
Sergey Kolesnik Avatar asked Sep 02 '25 04:09

Sergey Kolesnik


1 Answers

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:

  • with -DMODE_A option for target test.MODE_A,
  • with -DMODE_B option for target test.MODE_B and
  • with -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.

like image 60
Tsyvarev Avatar answered Sep 04 '25 19:09

Tsyvarev