1

I want to pass configuration options (some STRINGs and BOOLs) to a subdirectory with its own CMake project. What is the correct way to do this since all three methods I have found, have major flaws?

  1. set(CACHE): You need to use set(CACHE) in the parent project as well, since it cache variables and normal variables don't interact in any logical manner (and it changes depending on the policy). Also you have to set the variable description in the the parent project, which also makes no sense.
  2. set(): Not an option since you cannot set any defaults except with a lot of if-statements.
  3. option(): Useless since you can only use BOOLs with it.

Is there something that I am missing? How do big projects handle this kind of problem?

Thanks in advance :)

1
  • "since all three methods I have found, have major flaws?" - I know no problem, where its solutions has no flaws. Each solution has its own restrictions. As for "you have to set the variable description in the the parent project, which also makes no sense." - I don't find it as a "major flaw" at all. That description could be informative like "Select variant of <subproject> which is needed by <superproject>". Commented Jul 28, 2022 at 14:57

3 Answers 3

2

What is the correct way

add_subdirectory inherits all variables. Just set() the variable.

Is there something that I am missing?

option() and set(.... CACHE) are for configuration options and/or cache variables. A variable doesn't have to be in cache to be visible in a subdirectory. Just set() it normally. Set a variable to be in cache, if you have a specific reason for it, like you want the variable to be preserved between configuration runs, or it should show up in ccmake and gui apps. That's what CACHE variables are for.

How do big projects handle this kind of problem?

They set() the variables. For example kwsys in cmake sources.

Sign up to request clarification or add additional context in comments.

4 Comments

How do you handle default values? Is there a more efficient way than if?
gitlab.kitware.com/cmake/cmake/-/blob/master/Source/kwsys/… Is there a more efficient way than if? Your overall post is odd for me. You have a solution, right in front of you, but you seem not to take it because "it's a lot of if statements". I can only recommend to just write those if statements. CMake as a language is verbose. if is very efficient, I think it is the most efficient way to check something in CMake.
I'm currently learning CMake and may have gotten a bit frustrated with CMake the last two weeks. I really struggle to find good examples of "modern CMake" and how to correctly structure a project and so many features are hidden or not well thought out like this example. Like the CMake Devs were this close to providing a useful parameter passing mechanism, but no... Implicit variables and a pile of if statements it is... option and set(CACHE) with tiny fixes could be one-line-solutions to this extremely common problem.
You explained what cache variables are for in CMake, but isn't another main reason if you don't want the child list/CMakeLists/directory to modify the value? Without cache the child inherits the value and then sets its default, like option(SOME_OPTION ON), and that can override your variable, right?
2

Cache variables and normal variables interact logically as of CMake 3.21 with CMP0126 enabled.

In your project, just write:

cmake_minimum_required(VERSION 3.21) project(example) ... # include the next line if subdir is 3rd party set(CMAKE_POLICY_DEFAULT_CMP0126 ON) set(subdir_SOME_OPTION "value") add_subdirectory(subdir) 

The variable CMAKE_POLICY_DEFAULT_CMP0126 will enable sane cache interactions with normal variables, even when the subproject's cmake_minimum_required version is set below 3.21. In short, with CMP0126 normal variables always "win" over cache variables.

If you want subdir_SOME_OPTION to remain user-overridable, then you will need to make it into a cache variable with the same type and docstring as the subproject. A bit annoying, yes, but not too much of a burden. Alternatively, you can re-export the option with your own cache variable like example_SOME_OPTION and then set(subdir_SOME_OPTION "${example_SOME_OPTION}") if you have some use for this level of indirection.

That would look like this:

cmake_minimum_required(VERSION 3.21) project(example) set(CMAKE_POLICY_DEFAULT_CMP0126 ON) set(example_SOME_OPTION "default-value" CACHE STRING "documentation") set(subdir_SOME_OPTION "${example_SOME_OPTION}") add_subdirectory(subdir) 

10 Comments

@SkryptX - I can't disagree with you on any of that. One day more projects will be on CMake 3.21+ and we won't have to think about CMP0126 anymore. (btw - I was the one who complained enough to actually get CMP0126 added and implemented).
@SkryptX - unfortunately the ship has completely sailed on that. I don't think CMake can be completely fixed through policies. CMake's declarative core (targets with properties that can contain generator expressions) is actually pretty decent, but the configuration language was designed (as far as I can tell) without regard to any prior art, is terrible, and has remained essentially unchanged since it was introduced. Fixing the scoping rules, function calling convention, core data structures, etc. would be tantamount to a rewrite.
@SkryptX I'm pretty sure the old behaviour for the policy was never intended and was a bug in the first place.
@AlexReinking Nice to meet someone that sees it the same way as I do :) Maybe we get CMake 4 with all these things... It took Python only 10 short years to transition from 2 to 3 :D
I'm sorry it has been late yesterday. I've had PATH and INTERNAL attributes mixed up in my code. Please ignore my rant. ;)
|
1

All three methods are usable depending on the way how subproject processes its parameter and how the outer project's setting is intended to interact with the user.

  1. Inner project works with the CACHE variable: creates it using set(CACHE) or option():

    # Inner project set(FOO_TOOL_PATH "/inner/path" PATH "Path to foo tool") option(ENABLE_BAR "Whether need to use bar" FALSE) 

    In that case the outer project could set CACHE variable too:

    # Outer project, user cannot modify the value. set(FOO_TOOL_PATH "/outer/path" INTERNAL "Path to foo tool, selected by <me>") # Use `set` because we need to use INTERNAL keyword and 'option()' doesn't support it. set(ENABLE_BAR TRUE INTERNAL "Need to use bar") 

    or, if it wants the value to be modifiable by the user,

    # Outer project, user may modify that value. set(FOO_TOOL_PATH "/outer/path" PATH "Path to foo tool") option(ENABLE_BAR "Whether need to use bar" TRUE) 
  2. Inner projects works with normal (non-CACHE) variable: checks its existence using if and, if unset, possibly creates using set():

    # Inner project if(NOT FOO_TOOL_PATH) set(FOO_TOOL_PATH "/inner/path") endif() 

    If the outer project wants to assign fixed value to the variable, then the variable could be defined using plain set():

    # Outer project, user cannot modify the value. set(FOO_TOOL_PATH "/outer/path") 

    If the outer project wants the value to be modifiable by the user, then it could use either if plus set(), like the inner project

    # Outer project, user may modify that value. if(NOT FOO_TOOL_PATH) set(FOO_TOOL_PATH "/outer/path") endif() 

    or it could create a CACHE variable:

    # Outer project, user may modify that value. set(FOO_TOOL_PATH "/outer/path" CACHE PATH "Path to foo tool") 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.