What makes a condition "context-sensitive" when evaluating a property?

My question is about the HadContextSensitiveCondition flag in cmGeneratorExpressionContext.

Some subclasses of cmGeneratorExpressionNode assign context->HadContextSensitiveCondition = true in their Evaluate method.
I don’t understand what this means exactly. For instance, I’m surprised that CompileLanguageNode does not set this flag to true. I would assume the compile language is considered part of the context (especially because its value is retrieved from the cmGeneratorExpressionContext parameter).
So what is the definition of a context-sensitive expression?

The only “context” dependent bits I see are things like $<CONFIGURATION>, some $<TARGET_PROPERTY> queries, $<TARGET_OBJECTS> from non-IMPORTED targets, and $<TARGET_POLICY>. These all end up either being dependent on the configuration in use or on the target performing the query.

Thank you, this is helpful!
Although I still don’t understand the rationale for not including the compile language in these conditions. I would expect a “non-context-sensitive” generator expression to always have the same output throughout an execution of CMake. Or is that completely wrong?

Which genex are you referring to, specifically?

For instance, the IncludeDirectories test case has this command:

set_property(TARGET iface PROPERTY INTERFACE_INCLUDE_DIRECTORIES
  "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/systemlib_header_only>"
  )

where the value of the property is not considered context-sensitive.

More context in case that helps: what I’m trying to do is cache some property values for some targets to speed up the generate step in a large project. I want to figure out a condition that makes it OK to cache and reuse a property value. I thought the HadContextSensitiveCondition and HadHeadSensitiveCondition flags might be it, but it doesn’t work with the example above.

Hmm, indeed. It looks like COMPILE_LANGUAGE should indeed be context-sensitive since it can change based on the query location. Same with COMPILE_LANG_AND_ID for that matter.

I kind of thought you might be using something like $<C_COMPILER_ID> which is not sensitive since toolchain IDs can’t change within a build, but just wanted to verify. I suppose checking the CompileLanguageNode impl would have helped there…

Cc: @brad.king

Languages aren’t context sensitive. It is a requirement for a given language to be enabled for all contexts that a targets that use it are evaluated in. https://cmake.org/cmake/help/latest/command/enable_language.html

... Furthermore, it must be called in the highest directory common to all targets using the named
language directly for compiling sources or indirectly through link dependencies. It is simplest to enable
all needed languages in the top-level directory of a project.

So it is more a detector of enable_language() than something like “this include path is only relevant for C++ sources”?

My understanding is that the context sensitive refers to the context of where the target holding the generator expression is created, not where it is evaluated.

For the case of COMPILE_LANGUAGE we don’t want to capture the context of the creation but want to always use the context of the users ( HadContextSensitiveCondition == false )

So it is entirely reasonable to create a language based genex in a context that doesn’t have that language enabled, but is only evaluated in contexts that have the language.

project(example LANGUAGES NONE)

add_subdirectory(producer)

enable_language(CXX)
add_library(consumer STATIC main.cxx)
target_link_libraries(consumer PRIVATE producer)

Where producer has:

add_library(producer INTERFACE )
target_include_directories(producer INTERFACE  "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}>")

That doesn’t square at all with $<TARGET_PROPERTY> querying the HeadTarget’s properties (and therefore being context-dependent).

So if it were main.c in your example (with CXX still enabled), that include directory would be added? That doesn’t sound right to me. How can I say something like “use -DMPICH_SKIP_MPICXX if you are C++; C doesn’t care” with the genex? Or is that not something it can be used for?

IIRC, HadContextSensitiveCondition originally meant “uses a $<CONFIG*> expression” because they were the original use case. The idea is to know whether the evaluation result might be different for each configuration. The $<TARGET_OBJECTS> can vary by configuration because we support per-config sources. I’m not sure why $<TARGET_POLICY> sets it because policy settings don’t vary by configuration; maybe it was a cut-n-paste error. However, the $<COMPILE_LANGUAGE> of a source file doesn’t vary with the configuration in which it is compiled.

There is also HadHeadSensitiveCondition, which tracks whether a generator expression implicitly references the target for which evaluation is taking place. This is a feature of the $<TARGET_PROPERTY:prop> genex, which doesn’t explicitly name a target.

There might be other subtle ways in which evaluation results can vary in ways we don’t account for in the memoization map. No one has audited it recently AFAIK.

Thank you all, this is very interesting and helpful!
So it sounds like a generator expression’s output (and more generally, a property’s value for a given target) can be different depending on

  • the configuration (tracked by HadContextSensitiveCondition)
  • the head target (tracked by HadHeadSensitiveCondition)
  • the compile language (does not set any flag)

Is that all or are there other possible sources of variation?

@brad.king what/where is this memoization map you mentioned? It sounds similar to what I’m trying to do.

Run git grep "Get.*Link.*Map" -- Source/ to see examples of the memoization maps.

other possible sources of variation?

There is also HadLinkLanguageSensitiveCondition.

1 Like