Variables set with PARENT_SCOPE are empty in the corresponding child scope. Why?
Asked Answered
P

5

29

Consider the following minimal example:

.
├── bar
│   └── CMakeLists.txt
└── CMakeLists.txt

where ./CMakeLists.txt is

project( foo )
cmake_minimum_required( VERSION 2.8 )

set( FOO "Exists in both, parent AND in child scope." )

add_subdirectory( bar )
message( STATUS "Variable BAR in ./     = ${BAR}" )
message( STATUS "Variable FOO in ./     = ${FOO}" )

and ./bar/CMakeLists.txt is

set( BAR "Exists in parent scope only." PARENT_SCOPE )
message( STATUS "Variable BAR in ./bar/ = ${BAR}" )

The relevant part of the output of cmake is this:

...
-- Variable BAR in ./bar/ =
-- Variable FOO in ./bar/ = Exists in both, parent AND in child scope.
-- Variable BAR in ./     = Exists in parent scope only.
-- Variable FOO in ./     = Exists in both, parent AND in child scope.
...

Since the variable BAR is placed into the parent scope I would expect it to be available in the current child scope as well (and in those that follow) -- just like the variable FOO, which is defined the parent scope to begin with. But as can be seen in the above lines the variable BAR is empty in ./bar/CMakeLists.txt, which lead me to the following questions:

Why is the modified parent scope not immediately accessible in the child scope, ./bar/? Can this be mitigated? If yes, how? And if no, what is a work-around? Or am I completely missing something obvious?

Context: my project consists of several executables and libraries. For a library, e.g. bar, I'd like to set a variable bar_INCLUDE_DIR which is added to the include paths of any depending executable, i.e. target_include_directories( my_target PUBLIC bar_INCLUDE_DIR ).

Profusive answered 27/4, 2015 at 10:15 Comment(0)
B
6

Context: my project consists of several executables and libraries. For a library, e.g. bar, I'd like to set a variable bar_INCLUDE_DIR which is added to the include paths of any depending executable.

There is a much better way to do this than to set variables in the parent scope. CMake allows a target to specify include directories, preprocessor symbols etc. that depending targets can use. In your case, you can use target_include_directories.

For example:

target_include_directories(my_target PUBLIC my_inc_dir)
Bombay answered 27/4, 2015 at 11:47 Comment(5)
In fact, that's what I using. But since my_target is in a sibling scope relative to a required library bar, I use the detour via the parent scope to forward the location of the include directory.Profusive
You don't need variables to pass on that information. All you need to do is to apply target_include_directories on the library and all other target that use it will automagically get the relevant directories in their include path.Bombay
Aaah, now I understand what you mean. It's much better, indeed. Thanks for your persistence!Profusive
@Profusive The updated documentation says Note that it is not advisable to populate the INSTALL_INTERFACE of the INTERFACE_INCLUDE_DIRECTORIES of a target with paths for dependencies. That would hard-code into installed packages the include directory paths for dependencies as found on the machine the package was made on. So, you should also definitely specify BUILD_INTERFACEDivorce
@Profusive If you manage to make it work for your sample project please post the code somewhere: I tried in a project I have and it wouldn't work, but it can definitely be because it is a complicated projected and there are several other things that my CMake files are doing.Divorce
P
21

I do not see anything that is not consistent with the SET command documentation

If PARENT_SCOPE is present, the variable will be set in the scope above the current scope. Each new directory or function creates a new scope. This command will set the value of a variable into the parent directory or calling function (whichever is applicable to the case at hand).

./bar/CMakeLists.txt

set( BAR "This is bar." PARENT_SCOPE ) #<-- Variable is set only in the PARENT scope
message( STATUS "Variable BAR in ./bar/ = ${BAR}" ) #<--- Still undefined/empty

You can always do:

set( BAR "This is bar." ) #<-- set in this scope
set( BAR ${BAR} PARENT_SCOPE ) #<-- set in the parent scope too

Grep for PARENT_SCOPE in the delivered modules in your installation, for example FindGTK2

if(GTK2_${_var}_FOUND)
   set(GTK2_LIBRARIES ${GTK2_LIBRARIES} ${GTK2_${_var}_LIBRARY})
   set(GTK2_LIBRARIES ${GTK2_LIBRARIES} PARENT_SCOPE)
endif()
Pedlar answered 27/4, 2015 at 11:26 Comment(2)
Well, consider a variable FOO which is defined in the parent scope to begin with, then this variable is also available in the child scope (see the updated example). So why wouldn't BAR be available in the child scope if both live in the same (parent) scope? I would expect the documentation to mention such a behavior.Profusive
That could be possible seen as a side-effectPedlar
D
15

Peter explained well the reason for this behaviour.

A workaround I usually use in this case is to set a cached variable, which will be visible everywhere:

set(BAR "Visible everywhere"
        CACHE INTERNAL ""
)

INTERNAL is to make it not visible from cmake-gui. INTERNAL implies FORCE, making sure it gets updated if you change something for example in your folder structure. The empty string is a description string, that you might want to fill if you believe it's necessary.

Note, though, that the correct approach is attaching properties to targets whenever possible, like using target_incude_directories, and propagate them to other targets by setting dependencies.

Divorce answered 27/4, 2015 at 12:57 Comment(4)
Thanks for pointing out the CACHE option. However, having understood what @Lindydancer meant, I tend to prefer that method.Profusive
This is definitely the wrong approach because it might affect every function and context in a random way. CACHE will clear the original value, FORCE will override the corresponding -D option. Instead of use the cache you have to consider to set a value in both contexts, in a parent and in a child instead of everythere. Otherwise do not use variables at all and use instead properties attached to a target or something else.Retentive
@andry The best approach is indeed to use as much as possible properties associated to a target, and propagate them all by specifying dependencies. This answer address the need (if there exists one, I stated that it is indeed a workaround) to set a global variable visible everywhere, in sibling directories especially.Divorce
FORCE is redundant here, as INTERNAL implies FORCE. See: cmake.org/cmake/help/v3.0/command/set.htmlChessa
B
6

Context: my project consists of several executables and libraries. For a library, e.g. bar, I'd like to set a variable bar_INCLUDE_DIR which is added to the include paths of any depending executable.

There is a much better way to do this than to set variables in the parent scope. CMake allows a target to specify include directories, preprocessor symbols etc. that depending targets can use. In your case, you can use target_include_directories.

For example:

target_include_directories(my_target PUBLIC my_inc_dir)
Bombay answered 27/4, 2015 at 11:47 Comment(5)
In fact, that's what I using. But since my_target is in a sibling scope relative to a required library bar, I use the detour via the parent scope to forward the location of the include directory.Profusive
You don't need variables to pass on that information. All you need to do is to apply target_include_directories on the library and all other target that use it will automagically get the relevant directories in their include path.Bombay
Aaah, now I understand what you mean. It's much better, indeed. Thanks for your persistence!Profusive
@Profusive The updated documentation says Note that it is not advisable to populate the INSTALL_INTERFACE of the INTERFACE_INCLUDE_DIRECTORIES of a target with paths for dependencies. That would hard-code into installed packages the include directory paths for dependencies as found on the machine the package was made on. So, you should also definitely specify BUILD_INTERFACEDivorce
@Profusive If you manage to make it work for your sample project please post the code somewhere: I tried in a project I have and it wouldn't work, but it can definitely be because it is a complicated projected and there are several other things that my CMake files are doing.Divorce
S
2

If you only need to set a variable both in the local and parent scope, a macro can help reduce duplication:

macro(set_local_and_parent NAME VALUE)
  set(${ARGV0} ${ARGV1})
  set(${ARGV0} ${ARGV1} PARENT_SCOPE)
endmacro()
Stoplight answered 30/4, 2021 at 9:15 Comment(1)
What about NAME and VALUE args?Beeswing
R
0

Each variable in the cmake has it's own scope so it is dangerous use case where a variable automatically propagates in a child context, because it can interfere with it from a parent scope!

But you can set just another variable in a child scope to test it later instead of rereading a parent one:

./bar/CMakeLists.txt:

set( BAR "Exists in parent scope only." PARENT_SCOPE )
set( _somerandomid_BAR "Exists in child scope only.")
message( STATUS "Variable BAR in ./bar/ = ${_somerandomid_BAR}" )

Now, if you have loops in your code, then you can test both variables:

foreach(...)
  ...
  # read a variable token name and token value, for example, from a configuration file
  set(my_var_name_token ...)
  set(my_var_value_token ...)
  ...
  # parse a variable name and value tokens into a real name and value
  set(my_var_name ...)
  set(my_var_value ...)
  ...
  if (DEFINED ${my_var_name})
    if (DEFINED local_${my_var_name})
      message("has been defined before and was resetted");
    else()
      message("has been defined before and was not resetted");
    endif()
  else()
    if (DEFINED local_${my_var_name})
      message("has not been defined before and was setted");
    else()
      message("has not been defined before and was not touched");
    endif()
  endif()
  ...
  # sets parsed name and value into cmake context
  set(${my_var_name} "..." PARENT_SCOPE)

  # Do save all values has been setting from this function to differently compare and 
  # validate them versus already existed before:
  # 1. If has been set before, then must be set to the same value, otherwise - error
  # 2. If has not been set before, then should set only once, otherwise - ignore new 
  # value (a constant variable behaviour)
  set(local_${my_var_name} "...")
  ...
endforeach()
Retentive answered 14/2, 2019 at 19:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.