[cmake-developers] conditionals in generator expressions

Stephen Kelly steveire at gmail.com
Thu Jun 7 13:25:30 EDT 2012


Brad King wrote:

> On 06/07/2012 11:41 AM, Stephen Kelly wrote:
>> Brad King wrote:
>>> On 06/05/2012 04:59 PM, Stephen Kelly wrote:
>>>> generator_expression(myGenExpr)
>>>
>>> That is an interesting idea to keep in mind for the future but I think
>>> the implementation of it is too tricky to be in scope now.
>> 
>> That's a little unfortunate. I'm not familiar enough with the existing
>> implementation to understand why yet that is so.
> 
> Allowing arbitrary code to run during generate time will be very
> difficult to get right.  

Although the questions are rhetorical, I'll answer them anyway to clarify 
the intention of the proposal. I'm not trying to push my proposal, but I 
want to make sure it's understood :).

> In what initial state does it run?

This is for sure the toughest question. 

Assuming that a copy of relevant state can be created by simply copying the 
cmMakefile at the appropriate point, possibilities one might consider are:

1) The state of the makefile after project() was invoked for the first time 
(so the language, platform and compiler variables are set)
2) The state when the add_{library,executable} was invoked.
3) The state when the property containing $<GENERATOR:foo> was set on the 
target.
4) The final state of the makefile before generating started.

(1) is not very useful, because no targets are defined at that point.

(2) is non-optimal because (I think) a copy of the cmMakefile would have to 
be made for each target when it was created, regardless of whether generator 
expressions were used on it or not. 

I also don't think any useful variables are set() between project() and 
add_{library,executable} anyway, so it's probably not useful. Any useful 
information that the buildsystem would want to set for use by a generator 
expression could be set as a property on the target itself, or perhaps a 
global property.

(3) 3 might allow more control of how many cmMakefiles need to be copied, 
but it otherwise no more useful than (2).

(4) I'm not certain this is possible, it is not more useful than (2).

So the best state to make available would probably be a combination of 

* A copy of cmMakefile just after the Platform and Compiler files are 
loaded, language is set etc
* The final read only cmTargets
* Possibly global properties

Directory properties would introduce ambiguity about whether to use the 
directory where the generator_expression appeared, where the target was 
created or where the generator expression was added to the target, so it is 
best avoided entirely.


> Can any command be invoked?  

I would say only a minimum is needed. Access to everything should be 'read-
only'. 

* generate_resolved("foo/bar") would "return" "foo/bar" for the position 
specified, and it is the only thing that has any outside effect
* get_property() would be needed
* if() would be needed
* list() would be needed, partly at least, for APPEND
* local(), which works the same as set(), but named to make it clear that 
its use has no outside effect.

I think that would be the minimum subset of commands and sub-commands to 
make the feature useful.

get_target_property() could be added too for convenience, and foreach() if 
it is deemed useful at some point (currently I don't see why it would be).


> What side effects can it have?

None. The only effect of the generator_expression is to "return" a value 
through generate_resolved(). It has no effect on any properties or variables 
outside of its scope.

> In what order do they run in case side effects interact?  

As there are no side effects, the order doesn't matter. The order is 
sequential as they appear in the property. So if the property contains the 
value:

"/foo/bar;$<GENERATOR:g1>;/bar/bat;$<GENERATOR:g2>"

and g1 happened to return "/bing/bang;/mig/mug" and g2 "/mog/mag", then the 
end result written to the makefile would be:

"/foo/bar;/bing/bang;/mig/mug;/bar/bat;/mog/mag"

No properties on the target or globally would be modified. The 
generator_expression is simply defined and documented to have no side 
effects.

Can you think of a reason to allow side-effects?

> Does
> it run separately for every combination of possible input
> (language, config, etc.)?  

Personally I've only ever used Makefile and Ninja generators which only 
generate for one config, so I can't give a good answer to this. I just 
assumed it was solvable :). 

I'll assume that a generator for an ide generates all configurations in one 
Generate() step. Assuming it populates one 'generic' field, and one field 
for each configuration, what would happen would then be something like 
(semi-pseudo-code):

* For the generic field, process the INCLUDE_DIRECTORIES property without 
this_Config set and write out the result
* foreach configuration, process the INCLUDE_DIRECTORIES property again, 
ignoring non-generator expressions, and execute the generator_expression 
with this_Config set as appropriate. The result is a set of (possibly empty) 
includes which are unique for each configuration in a configuration specific 
field, and a "generic" set.

Or, a generator might not have a "generic" set of includes, and only 
populate config-specific fields. I assume there must be a partial solution 
to this kind of thing already in place, and I think the generator_expression 
would fit into the concept without too much hassle. As you can see, I don't 
really know about multi-config generators though. 

I don't know if languages would add further complications either. How do 
current generators handle that?


> How do names/variables/functions in
> the generator_expression body bind?

They don't. They are local to the generator_expression and are discarded at 
the end of it or when generate_resolved is invoked, assuming I understood 
the question.

> The above questions are rhetorical to illustrate the difficulty of
> the proposal.

I think the only difficult part is deciding how the state of it should be 
defined. 

Handling 'public' ones also shouldn't be too difficult. The ExportImport 
generator would just have to write the expressions out verbatim (probably 
with a namespaced name).

> I'm quite happy with the alternative now proposed.

Yes, it seems reasonable.

>>>  $<TARGET_PROPERTY_BOOL:WIN32_EXECUTABLE,tgt>  = 0 or 1
>> 
>> Are you sure about this one? Given your further explanation below, and
>> assuming the intention is 'link to tgt if *this has a WIN32_EXECUTABLE
>> property which is True', it should rather be:
>> 
>> $<$<TARGET_PROPERTY_BOOL:WIN32_EXECUTABLE>:tgt>
> 
> The TARGET_PROPERTY_BOOL condition needs to know in what target
> to check the property.  Therefore it needs both the target name
> and the property name:
> 
>   $<$<TARGET_PROPERTY_BOOL:WIN32_EXECUTABLE,exe-getting-linked>:lib-to-
link>
> 
> I suppose the "exe-getting-linked" can be implied when the
> expression appears inside the list of link libraries for a target,

Yes. I had assumed this was the only possibility actually. We could simply 
define that to be the way it will work, for simplicity. 

I'd even define these declarative expressions to only be valid on target 
properties, so that, eg, they can't be used in an include_directories() 
call. 

I can't think of any reason to need to check the value of a property on a 
target foo when generating for a target bar, assuming at least that 
transitive (PUBLIC) properties have already been processed to enough of an 
extent by the time this expression is evaluated.

> just as the $<LANGUAGE:...> condition tests the language implied by
> the context.
> 
>>>  $<$<CONFIG:Debug>:/path/for/Debug>
>>>  $<$<NOT:$<CONFIG:Debug>>:/path/for/non-Debug>
>>>  $<$<AND:$<CONFIG:Debug>,$<LANGUAGE:CXX>>:/path/for/Debug/Cxx>
>> 
>> There's a lot of repetition, at least in these examples. If there's a lot
>> also in the real world, it might be more maintenance burden than
>> convenience.
> 
> The goal is to provide a minimal workable interface.  If it becomes
> too verbose we can enhance it later.  The above examples are contrived
> to demonstrate capabilities.  A more realistic example might be:
> 
>  set(FOO_INCLUDE_DIRS
>    $<$<CONFIG:Debug>:${FOO_INCLUDE_DIRS_DEBUG}>
>    $<$<CONFIG:Release>:${FOO_INCLUDE_DIRS_RELEASE}>
>    )
> 
> Even if a given condition needs to be repeated it can be stored in
> a normal variable "cond" and referenced later "${cond}".

Yes, true. I guess a larger real-world use would have to be mocked up to get 
a full idea of the (multiple) syntaxes which would no doubt appear in the 
wild and in mailing list queries appear such as:

* $<$<CONFIG:Debug>:${FOO_INCLUDE_DIRS_DEBUG}>
* $<${debug_cond}:${FOO_INCLUDE_DIRS_DEBUG}>
* $<${debugCond}:${FOO_INCLUDE_DIRS_DEBUG}>
* $<${debugCond}${FOO_INCLUDE_DIRS_DEBUG}> # An error if cond is empty.

> The
> final verbose generator expressions that the evaluator sees do not
> have to appear verbatim in source.

Yes.

I don't think yours is a bad idea, but I'm trying to tease out some of the 
consequences of it. We'll still need to plan the implementation of it anyway 
:)

Thanks,

Steve.





More information about the cmake-developers mailing list