How to tell whether CMake used initial value for an option? - cmake

Say I have an option "ENABLE_Foo" in CMake:
option(ENABLE_Foo "Tell cmake to enable the package Foo" ON)
I want to detect whether the user specified -DENABLE_Foo=ON or -DENABLE_Foo=OFF, or whether the specified initial value ON is being used.
The reason I want to tell whether the user turned the option on or whether it is turned on by default is because Foo requires an additional package and I want to cover these cases:
1) User did not specify a value for the option ENABLE_Foo:
a) Package Foo is found -> use Foo
b) Package Foo is not found -> silently turn off Foo
2) User specified a value for the option ENABLE_Foo:
a) User said -DENABLE_Foo=ON:
i) Package Foo is found -> use Foo
ii) Package Foo is not found -> fatal error message
b) User said -DENABLE_Foo=OFF -> don't use Foo and don't try to find it
If there is no way to tell whether the option value came from user input or from the initial value, are there other ways to achieve what I have outlined above?

If the user gives -DENABLE_Foo=ON on the command line, an entry for the respective option will be added to the CMake cache. While it is possible to inspect this value before invoking the option command, you cannot distinguish whether the value was originally set by the user on the command line, or by the option command itself on a previous CMake run.
Still, achieving the behavior you described is possible.
The main issue is that you cannot model the configuration options you want with just one flag. You are actually dealing with two different options.
The first is whether support for Foo is desired at all, the second is whether Foo is to be considered an optional or a mandatory dependency. Note that the value of the latter is irrelevant in case support for Foo is disabled. One way to handle this would be to remove the option completely in this case. This allows for the following approach:
if(REQUIRE_Foo)
# REQUIRE_Foo implies ENABLE_Foo
unset(ENABLE_Foo CACHE)
endif()
option(ENABLE_Foo "Tell cmake to enable support for package Foo" ON)
if(ENABLE_Foo)
option(REQUIRE_Foo "Abort the build if Foo is not found." OFF)
find_package(Foo) # assuming that FindFoo is a well-behaving find script,
# which will set Foo_FOUND appropriately
else()
unset(REQUIRE_Foo CACHE)
set(Foo_FOUND FALSE)
endif()
if(REQUIRE_Foo AND NOT Foo_FOUND)
message(FATAL_ERROR "Required library Foo could not be found")
endif()
In case the user wants to require Foo support (your case 2a) they would invoke CMake with -DREQUIRE_Foo=TRUE. In case they want to disable Foo completely (your case 2b) they would use -DENABLE_Foo=FALSE. If the user specifies nothing, you get the behavior from your case 1.
Assuming that the rest of the build system is already prepared to handle the case where Foo was not found (case 1b from your question), this should just work without further changes.
Note that even if you could detect whether the flag was set by the user, it would still be undesirable to implement the original behavior from your question.
Assuming we could detect it, the initial value of REQUIRE_Foo in the above code would be set to true if and only if ENABLE_Foo=ON was set on the command line. Doing this implicitly without also adding the REQUIRE_Foo to the set of configuration options is problematic. A user would experience different behaviors on CMake runs even though the build options are the same. You should avoid magical behavior like this. It will only confuse users and give you a hard time debugging failing CMake runs.
Always make build options that depend on configuration given by the user visible to the user.

Related

How to set value by code to a CACHE variable defined by 3d party CMake?

In my project, the CMakeLists includes other cmake files from a library and those dependencies need some cache variables to be configured by user values.
It is all working well if I define those values from the command line with the cmake command:
-DTHIRDPARTY_FRAMEWORK_ROOT="$thirdpartyFrameworkPath"
But can I define (= hardcode) such values in my own CMakeLists file?
To avoid my own users to do it when they configure my project (some values of the 3d party configuration are constant in my project), and make my own cmake interface simpler.
I tried to simply set the variable with a value, but it is both defined and used in the included cmake so it gets overwritten with their default value just before being used.
Using set(... FORCE) seems to work but it does not look clean to me, and might lead to confusing errors if they rename or change the type of the variables on their side. It also forces me to add a type and a doc string because of the set(... CACHE ...) syntax.
Is there a better way to do this?
Setting CACHE INTERNAL variable is a proper way for hardcode a parameter of the inner project in the outer one:
set(THIRDPARTY_FRAMEWORK_ROOT CACHE INTERNAL "Hardcoded root for 'thirdparty'" <value>)
INTERNAL type makes sure that this setting will overwrite the option (FORCE doesn't need) and makes sure that the option won't be shown for a "normal" user.
Since the parameter is not intended to be changed by a user, its real type is meaningless, so there is no needs for it to coincide with the one set in the inner project.
As for description, you could set it to be empty (the parameter is not shown to the normal user, remember?). Alternatively, in the description you could explain why do you set the variable in the outer project. So an "advanced" user will see your description.

Parallel execution of cmake functions?

When running cmake on a large project, can it call a given cmake function in parallel?
What I am concerned about is that when this (user defined) function stores something in the cache then that would collide with the parallel execution; how does cmake deal with this when functions are indeed executed in parallel, or do I have to make sure that even invocations of the same function will never use the same (cached) variable name with different values?
EDIT (see comments):
I wrote the following function,
function(CW_SYS_CACHELINE_SIZE)
if (NOT DEFINED CACHE{cw_cv_sys_cacheline_size})
set(CMAKE_TRY_COMPILE_CONFIGURATION "Release")
try_run(RUN_WORKS
COMPILE_WORKS
${CMAKE_CURRENT_BINARY_DIR}/cw_utils_sys_cacheline_size
${CW_SYS_CACHELINE_SIZE_MODULE_PATH}/CW_SYS_CACHELINE_SIZE.c
COMPILE_OUTPUT_VARIABLE COMPILE_OUTPUT
RUN_OUTPUT_VARIABLE RUN_OUTPUT)
if (NOT COMPILE_WORKS)
message(FATAL_ERROR "Failed to compile test program
CW_SYS_CACHELINE_SIZE.c: ${COMPILE_OUTPUT}")
elseif (NOT RUN_WORKS EQUAL 0)
message(FATAL_ERROR "Failed to run test program CW_SYS_CACHELINE_SIZE.c: ${RUN_OUTPUT}")
else ()
set(cw_cv_sys_cacheline_size ${RUN_OUTPUT} CACHE INTERNAL "")
endif ()
endif()
endfunction()
Since this function doesn't take arguments, the internal values of the used variables are expected to be the same every time; but I have other very similar functions that do almost the exact same thing, except that they compile and test a different .c file.
So, each of those functions can have a different value of RUN_WORKS, which is (as it turns out) written to the cache as INTERNAL. Suppose that I am not terminating the program but doing something else with the value of RUN_WORKS here. Then it could be that I first run this function where RUN_WORKS is true, and then run the other function which sets RUN_WORKS to false.
If then I run cmake again (or inadvertently call the first function again),
then it has no way of knowing if the current value of RUN_WORKS in the cache has anything to do with a previous run of THIS function (and/or with the same arguments, if it has some). So, it CANNOT use the value of the cached variable. BUT - cmake is caching it. This worries me; why would it be caching anything if it isn't planning to reuse its value later? I'd feel a lot better if it didn't cache these variables.
So, my reasoning was: if anything is being cached (INTERNAL or not) I must make sure it is 100% unique (aka, that cmake will always write the same value to the same variable, or it wouldn't have made sense to cache it in the first place). But how to do that with this RUN_WORKS variable? I'm just horribly confused about how to write safe code like this :/
... can it call a given CMake function in parallel?
No, configuration stage of CMake, when it processes CMakeLists.txt and, among other things, executes its functions, is performed strictly sequentially.
Only build stage, when libraries and executables are compiled and COMMAND's (in add_custom_command/add_custom_target) are executed can be performed in parallel.
Moreover, during the build stage CMake isn't running at all: this stage is controlled by the build tool, selected as a generator for CMake.

How to use condition in cmake generator expression

I would like to compile a library as shared or static based on other variable eg:
add_library(MyLibrary $<$<IF:${BUILD_SHARED_LIBS},SHARED,STATIC> ${SOURCES})
For clarity I expect this to be equivalent with the following:
if(BUILD_SHARED_LIBS)
add_library(MyLibrary SHARED ${SOURCES})
elseif()
add_library(MyLibrary STATIC ${SOURCES})
endif()
AFAIK, you cannot do that with generator expressions (no way to query that aspect according to doc), since BUILD_SHARED_LIBS is there for exactly that reason; to allow you to select its value during configuration (using the -D command line option). This will work only if you do not set the library type explicitly as in your code, but like this
add_library(MyLibrary ${SOURCES})
Actually, that is the recommended practice. If you need to influence its value in association with some other condition, you can override it with the usual if()/else() logic, making sure that you print at least an informative message() for the user.
However, an even better approach would be to push those decisions to the user (via options) and check for the illegal combinations, issuing a message(FATAL_ERROR). Even if that condition is determined automatically, this is still a tactic that has merit.

Why do I need FORCE to override a CMake option?

I want to override an option, which formely was set to OFF. I am using
SET(USE_OPTION ON CACHE BOOL "Override option" FORCE)
Just for my curiosity: Why do I need 'FORCE' (without it, the set() does not work)
Options are variables meant to be changeable by the user after the configuration step and before the actual build enviroment generation through tools like the cmake-gui, ccmake or through the -D command line switch.
That's why they are cached in the first place. The choice the user has done has to be persisted. So
option(USE_OPTION "My option" ON)
is an equivalent to
set(USE_OPTION ON CACHE BOOL "My option")
So to change or force an "user definable value" / cached variable you have to FORCE it.
Or hide/overwrite it for the current variable scope with a non-cached variable:
set(USE_OPTION ON)
Reference
What's the CMake syntax to set and use variables?
From the docs:
If CACHE is present, then the is put in the cache instead,
unless it is already in the cache
I assume the previous option was also CACHE.
If FORCE is specified, the value of the cache variable is set, even
if the variable is already in the cache.
So, if you don't specify FORCE it doesn't get added to the cache as per above.

CMake-gui toggle visibility of variable

I have a CMake setup where the accessibilty of one variable will depend whether another one is set or not. Small snippet:
option(build-compiler "Build the Nap Compiler" ON)
set(include_interrupt_dirs CACHE INTERNAL "interrupts/intr_4" FORCE)
if(build-compiler)
option(enable-runtime-compilation
"Build in the runtime code compilation link in intr_2 & intr_3)" ON)
if(enable-runtime-compilation)
list(APPEND include_interrupt_dirs "interrupts/intr_2" "interrupts/intr_3" )
endif()
endif()
I use cmake-gui for configuring the project, and what I would like to achieve is:
if the user selects the build-compiler the enable-runtime-compilation should also be presented. This part is done.
if the user deselects the build-compiler the enable-runtime-compilation should be hidden from the GUI. This is not working.
Do you have any idea how to make it work?
Using unset(var [CACHE]) is subtly tricky. If you just unset the variable, it will remain in the cache (although it will not be visible to the script, it will still be visible to the user). If you also delete it from the cache, then you lose the value that was there.
In my use case, I wanted to hide variables based on some condition. I found it that deleting the variables from the cache might get confusing as when reinstated, they would return to their default state rather than to what the user might have set before.
I prefer to hide the variables using mark_as_advanced(FORCE var) and unhide using mark_as_advanced(CLEAR var). It does exactly what you need-it hides the variable from the GUI but it still exists in the cache. You might use this together with the "soft" unset (the one without CACHE) to make sure that the hidden variable is not still being used in the configuration.
Additionally, there is CMakeDependentOption which is intended specifically for this use case (an option that is only available if some set of condition evaluates as true). This is apparently available since CMake 3.0.2.
You can use unset(var CACHE) to remove the variable from the cache:
if(build-compiler)
option(enable-runtime-compilation
"Build in the runtime code compilation link in intr_2 & intr_3)" ON)
if(enable-runtime-compilation)
list(APPEND include_interrupt_dirs "interrupts/intr_2" "interrupts/intr_3" )
endif()
else()
unset(enable-runtime-compilation CACHE)
endif()