Why is a variable value not available after add_subdirectory-ing a CMakeLists.txt that defines it? How can I make it so?
Asked Answered
S

2

101

I have a CMakeLists.txt in my project root and one in my /src folder. The one in the /src folder only contains a variable with the .cpp files (set (SOURCEFILES main.cpp foo.cpp)) and in the root CMakeLists.txt I do add_subdirectory(src) and later I do add_executable(MyApp ${SOURCEFILES}).

But cmake gives me the error

add_executable called with incorrect number of arguments, no sources provided

I read that CMake only knows global variables, but that's obviously not the case... What am I missing about how variable scoping works in CMake that explains this? And knowing that, how do I get CMake to see the variable?

Sharpedged answered 31/7, 2011 at 18:29 Comment(0)
S
155

As mentioned in the documentation of the set command, each directory added with add_subdirectory or each function declared with function creates a new scope.

The new child scope inherits all variable definitions from its parent scope. Variable assignments in the new child scope with the set command will only be visible in the child scope unless the PARENT_SCOPE option is used.

To make the SOURCEFILES assignment visible in the root folder of your project, try:

set (SOURCEFILES main.cpp foo.cpp PARENT_SCOPE) 
Sideline answered 31/7, 2011 at 18:45 Comment(7)
Thanks, this solved it. But another note: in the sub directory you have now to use ${CMAKE_CURRENT_SOURCE_DIR}/foo.cpp instead of only foo.cpp because cmake is now in the parent scope.Sharpedged
Alternatively, you could set the variable in the parent CMakeLists.txt file before the add_subdirectory call, and have its definition inherit down to the child CMakeLists file. Again, you'll need to qualify the names of the files if you need to refer to them from multiple CMakeLists files.Spectre
You'll need to create the variable also in the current-scope to use its values in the sub-dir/function: set(VAR value PARENT_SCOPE); set(VAR ${VAR});Cacodyl
@PatrickB. in my case set(VAR value PARENT_SCOPE); set(VAR ${VAR}); is not working, however, set(VAR value); set(VAR ${VAR} PARENT_SCOPE); works.Bughouse
Additonaly you can access from parent dir to specific variable in suddir like this get_directory_property(VAR1 DIRECTORY subdir1 DEFINITION VAR1)Cougar
It is not quite fully correct. For example, ARGV0..N parameters might still contains old values on a moment BEFORE a add_subdirectory call (which means the add_subdirectory in that case is not quite a function), in case where u hooked up the add_subdirectory through the another function and call into _add_subdirectory. I think, this is important in case of fully compatible implementation.Kerin
how about target_... commands? I mean this discussion is about just functions and set, but what is the scope of other commands like target_link_library()? can we add a library in a subdirectory and then target_link in the parent?Unshapen
F
1

Variables in CMake are directory, function, and block scoped. add_subdirectory creates a new directory "child scope". You can set a variable in the parent scope of a given scope by using the PARENT_SCOPE argument of the set command.

In your specific use-case with trying to set source files for a target, if your cmake_minimum_version is greater than or equal to 3.1, you can (and probably should) instead be using target_sources to add sources to a target after the target has been defined/declared.

Note that if you want a variable to be set at a given scope and the parent scope, you need to call set twice- once to set it at that scope using set(...), and once to set it at its parent scope using set(... PARENT_SCOPE).

Think of each scope as inheriting copies of all the CMake variables of the parent scope. If you know how environment variables with OS processes work, some of that kind of intuition can be pretty useful here (not a perfect analogy since processes can't affect the environments of their parent processes).

To pass a variable value up multiple scopes, you need to do set(... PARENT_SCOPE) in a chain to pass it up each scope. So if possible, it's much less hassle to just set the value of a variable at the highest scope that it's needed in.

If a parent directory scope isn't guaranteed to exist, you can check that CMAKE_SOURCE_DIR is not equal to the CMAKE_CURRENT_SOURCE_DIR, like so:

if(NOT ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}"))
  set(foo "hello world!" PARENT_SCOPE)
endif()

You can also set global properties using set_property(GLOBAL ...). The way of using properties is a little bit clunky with having to read it into a variable and then modify the variable and then write the variable back to the property if you want to do modifications, but it might be more preferable than doing a long chain of set(... PARENT_SCOPE) if you really can't just set the variable at the highest scope.

Foreshadow answered 9/4, 2023 at 8:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.