Is there a way to implement a feature similar to CMAKE_MY_VAR for setting properties?

To disable some compile flags based on a target property, consumers of my library have to do something like the following:

find_package(Mylib)
add_executable(app_target)
target_link_libraries(app_target INTERFACE mylib::mylib)
set_target_properties(app_target PROPERTIES DISABLE_SPECIAL_FLAG 1)

and my library when created has something like the following

add_library(SHARED mylib)
set(genex_condition "$<NOT:$<BOOL:$<TARGET_PROPERTY:DISABLE_SPECIAL_FLAG>>>")
set(flags "$<${genex_condition}:-my-nice-flag>")
target_compile_options("mylib" INTERFACE "${flags}")

The above API forces users to explicitly set the DISABLE_SPECIAL_FLAG property on each target that is directly linking against mylib (I’m aware that users can work around it by linking publically against their own interface library that sets it only once).

I’m wondering if there’s a way to implement something like CMAKE_AUTOMOC, where if the CMAKE_AUTOMOC variable is set in a scope, all targets created below that variable get their AUTOMOC property initialized to whatever is set in CMAKE_AUTOMOC.

So I’d like the user to set a GLOBAL_DISABLE_SPECIAL_FLAG variable to 1, and then the DISABLE_SPECIAL_FLAG property is automatically populated with 1 for all targets created below that statement.

Is there some way to implement that?

Variable-initialized properties are handled by CMake at the C++ level, with a hard-coded list. There is no way to update this list without changing CMake’s C++ code. If you would like such a feature, please open a bug report with a proposed solution.

Filed here https://gitlab.kitware.com/cmake/cmake/-/issues/20698

This sounds like using define_property(INHERITED) to make get_property “chain up to the next higher scope when the requested property is not set in the scope given to the command.” (see https://cmake.org/cmake/help/latest/command/define_property.html for more details)

From what I’ve read, INHERITED only affects get_property()-like calls, whereas i would need an inherited set_property()-like call so that generator expressions can evaluate the appropriate values.

I guess an example would speak more: cmake-topic-1196.zip (2.8 KB)

This zip archive contains a small CMake project with a top-level CMakeLists.txt that defines mylib, and three sub-folders that each define an executable target.

  • top-level CMakeLists.txt:

    cmake_minimum_required(VERSION 3.4)
    
    add_library(mylib INTERFACE)
    define_property(TARGET
      PROPERTY DISABLE_SPECIAL_FLAG
      INHERITED
      BRIEF_DOCS "Disable special flag"
      FULL_DOCS "When true, don't add -my-nice-flag to the compile options of the targets that depend on mylib."
    )
    set(genex_condition "$<NOT:$<BOOL:$<TARGET_PROPERTY:DISABLE_SPECIAL_FLAG>>>")
    set(flags "$<${genex_condition}:-my-nice-flag>")
    target_compile_options(mylib INTERFACE "${flags}")
    
    add_subdirectory(default)
    add_subdirectory(disable_special_flag)
    add_subdirectory(not_disable_special_flag)
    
  • default/CMakeLists.txt:

    # don't set DISABLE_SPECIAL_FLAG to anything
    
    add_executable(appDefault "main.cpp")
    target_link_libraries(appDefault PRIVATE mylib)
    
  • disable_special_flag/CMakeLists.txt:

    set_directory_properties(PROPERTIES DISABLE_SPECIAL_FLAG 1)
    
    add_executable(appDisabled "main.cpp")
    target_link_libraries(appDisabled PRIVATE mylib)
    
  • not_disable_special_flag/CMakeLists.txt:

    set_directory_properties(PROPERTIES DISABLE_SPECIAL_FLAG 0)
    
    add_executable(appNotDisabled "main.cpp")
    target_link_libraries(appNotDisabled PRIVATE mylib)
    

When you build that project, you’ll see that -my-nice-flag is passed to the compiler when building appDefault and appNotDisabled, but not when building appDisabled. On my Windows machine, this results in the following build output:

$ ~/dev/bin/cmake-3.4.0/bin/cmake --build . -- -v:m
Microsoft (R) Build Engine version 14.0.25420.1
Copyright (C) Microsoft Corporation. All rights reserved.

cl : Command line warning D9002: ignoring unknown option '-my-nice-flag' [D:\dev\tmp\cmake-topic-1196\build\default\
appDefault.vcxproj]
  main.cpp
  appDefault.vcxproj -> D:\dev\tmp\cmake-topic-1196\build\default\Debug\appDefault.exe
  appDefault.vcxproj -> D:/dev/tmp/cmake-topic-1196/build/default/Debug/appDefault.pdb (Full PDB)
  main.cpp
  appDisabled.vcxproj -> D:\dev\tmp\cmake-topic-1196\build\disable_special_flag\Debug\appDisabled.exe
  appDisabled.vcxproj -> D:/dev/tmp/cmake-topic-1196/build/disable_special_flag/Debug/appDisabled.pdb (Full PDB)
cl : Command line warning D9002: ignoring unknown option '-my-nice-flag' [D:\dev\tmp\cmake-topic-1196\build\not_disa
ble_special_flag\appNotDisabled.vcxproj]
  main.cpp
  appNotDisabled.vcxproj -> D:\dev\tmp\cmake-topic-1196\build\not_disable_special_flag\Debug\appNotDisabled.exe
  appNotDisabled.vcxproj -> D:/dev/tmp/cmake-topic-1196/build/not_disable_special_flag/Debug/appNotDisabled.pdb (Ful
  l PDB)

Let me know if I missed something or if I’m totally off wrt. your use case.

Indeed, thank you kindly for the example!

The missing piece of the puzzle for me was the usage of set_directory_properties.

Indeed in this case i could create a function called my_namespaced_disable_special_flag(), which would both define the inherited property, and set the the directory property.

The downsides to this approach that I see compared to how CMAKE_AUTOMOC works (variable-initialized properties), is that with the inherited property, the considered property value can escape all the way to the global scope, and the ordering of calls is not respected.

Undesired case:

define_property(TARGET
  PROPERTY DISABLE_SPECIAL_FLAG
  INHERITED
  BRIEF_DOCS "Disable special flag"
  FULL_DOCS "When true, don't add -my-nice-flag to the compile options of the targets that depend on mylib."
)
set_directory_properties(PROPERTIES DISABLE_SPECIAL_FLAG 1)
add_subdirectory(d1)
set_directory_properties(PROPERTIES DISABLE_SPECIAL_FLAG 0)

The final property modification will still affect targets in d1 as if “0” is the value, whereas if i use a hypothetical

set(CMAKE_DISABLE_SPECIAL_FLAG 0)
add_subdirectory(d1)
set(CMAKE_DISABLE_SPECIAL_FLAG 1)

it would behave as expected.