How to do both efficient and correct System Inspection?

I’m trying to use CMake to do System Inspection as described in “Mastering CMake” (https://cmake.org/cmake/help/book/mastering-cmake/chapter/System%20Inspection.html), and I can’t figure out how to get it to happen if and only if it needs to.

My project is fairly straightforward, and mostly consists of a top-level CMakeLists.txt and main.cpp, plus two libraries:

project(Demo C CXX)
include(detect/CMakeLists.txt)  # I talk about this below
add_subdirectory(lib1)
add_subdirectory(lib2)
add_executable(Demo main.cpp)
target_link_libraries(Demo Demolib1 Demolib2)
target_include_directories(Demo PUBLIC include)

The detect/ directory contains a CMakeLists.txt that probes which of 30+ features are available, and does a bunch of CMake configuration based on what it finds. This configuration applies to both libraries as well as main.cpp. The probing absolutely must happen via try_compile(); I promise there is no more reliable way for me to detect these features. A representative sample looks like:

set(DETECTSRCDIR ${CMAKE_SOURCE_DIR}/detect)

try_compile(RESULT_VAR ${CMAKE_BINARY_DIR} ${DETECTSRCDIR}/feature1.cpp)
if(RESULT_VAR)
  add_definitions(-DHAVE_FEATURE1)
  message(STATUS "feature1 available")
endif()

try_compile(RESULT_VAR ${CMAKE_BINARY_DIR} ${DETECTSRCDIR}/feature2.cpp)
if(RESULT_VAR)
  add_definitions(-DHAVE_FEATURE2)
  set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
  message(STATUS "feature2 available")

  try_compile(RESULT_VAR ${CMAKE_BINARY_DIR} ${DETECTSRCDIR}/feature3.cpp)
  if(RESULT_VAR)
    add_definitions(-DHAVE_FEATURE3)
    set(CMAKE_CXX_EXTENSIONS ON)
    message(STATUS "feature3 available")
  endif()
else()
  try_compile(RESULT_VAR ${CMAKE_BINARY_DIR} ${DETECTSRCDIR}/feature4.cpp)
  if(RESULT_VAR)
    add_definitions(-DHAVE_FEATURE4)
    message(STATUS "feature4 available")
  endif()
endif()

#....

set_property(
  DIRECTORY
  APPEND
  PROPERTY CMAKE_CONFIGURE_DEPENDS
  ${DETECTSRCDIR}/feature1.cpp
  ${DETECTSRCDIR}/feature2.cpp
  ${DETECTSRCDIR}/feature3.cpp
  ${DETECTSRCDIR}/feature4.cpp
  #....
)

All of these feature detections are independent of the other code and CMakeLists.txt. That is, I promise that no CMake setting they make will ever affect how these detections take place. They are only affected by things like compiler, build type, etc.; inputs to the build environment. So a solution that somehow isolates that detection into some kind of sub-project or independent CMake configuration is at least plausible.

And this basically works correctly, but it is terribly inefficient. It is quite common to add a file to lib1 or lib2 during development, which means adding that file to that library’s CMakeLists.txt, which means the entire CMake configuration process happens, which means the entire detection process happens again! This can take quite a long time, since there are so many try_compile() calls needed. This is very frustrating to developers! So I would like to be efficient about this as well.

How can I make this detection happen only if it needs to; meaning, only if the reconfiguration happens because of something in detect/ changing or some compiler/buildtype setting?

I could maybe cache variables for all the features and guard the try_compile() calls, but then I would need to invalidate those cache entries if any of the feature*.cpp files change, and I can’t find a way to do that. Changing the include() to add_subdirectory() doesn’t change anything. I think if I made it an ExternalProject then I couldn’t do all the CMake configuration that it does. I could make it generate a file with a bunch of “set()” commands recording the detection results, and then include() that in my main CMakeLists.txt and duplicate all the configuration there, but that separates detection from configuration and makes maintenance and understanding more difficult and seems more like working around CMake rather than working with it. I also dumped all the CMake variables when a reconfigure happens because of a detect .cpp file changing versus a library’s CMakeLists.txt changing, and there were 0 differences, so I don’t know how to even detect that. So I am out of ideas.

To make things more complicated, I need a solution that ideally works with CMake 3.2, and needs to work with CMake 3.5, but if there is an easy solution that only exists in later versions, I would at least like to know about it. This is also a very cross-platform project, being built with gcc, clang, icc, MSVC, and mingw at the least, so I can’t count on any non-CMake scripting language being available for use (no bash/python/batch files can be used). And it also supports cross-compiling, so I cannot count on being able to execute anything that gets compiled.

If you have any ideas, I would love some help. Thank you for reading.

The issue is that you’re reusing the RESULT_VAR for each run. I would recommend using a separate variable name for each detection so that the cache actually remembers each try_compile individually. Alas, there’s probably no easy way to detect “needs to rerun” other than doing something like a hash of what matters and, if that changes, clearing all of the result variables used from the cache (and therefore letting try_compile have another go).