This question already has an answer here:
Using FetchContent_Declare together with CMAKE_ARGS
(1 answer)
Closed 2 years ago.
Context
I have a super build CMake project which integrate a foo CMake project (think of FetchContent() or add_subdirectory()).
I want to be able to change the value of a cached variable inside foo (i.e. don't like the default value)
question: How I can do that ?
Here a simple test.
note: I also test for an "option()" to see how smooth it is in this case.
Protocol
layout
CMakeLists.txt:
# This is a super build project
cmake_minimum_required(VERSION 3.0)
project(SuperBuild VERSION 1.0 LANGUAGES CXX)
# Trying to override the FOO option of "third party" foo
set(FOO OFF)
# Trying to override the BAR cache variable of "third party" foo
set(BAR "foo")
# Incorporate the Foo third party project.
# Should be done using FetchContent(GIT_REPOSITORY .../foo.git) but, here
# I used add_subdirectory(foo) to keep it simple...
add_subdirectory(foo)
foo/CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
# option() honors normal variables.
# without this policy, cmake will "clear" the super build variable value
# and will use the option defualt value -_-
# see: https://cmake.org/cmake/help/git-stage/policy/CMP0077.html
if(POLICY CMP0077)
cmake_policy(SET CMP0077 NEW)
endif()
# An option ON by default
option(FOO "My FOO option" ON)
message(WARNING "FOO: ${FOO}")
# A string variable cached with default "bar"
# we can override it on command line using for example -DBAR:STRING="foo"
# but here since we are in a super build we want to override it in the super
# build CMakeLists.txt directly
set(BAR "bar" CACHE STRING "My cache entry with default 'bar'")
message(WARNING "BAR: ${BAR}")
Test 1
echo "First run"
cmake -S. -Bbuild
echo "Second run"
cmake -S. -Bbuild
Observed
First run
-- The CXX compiler identification is GNU 10.1.0
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ - works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
CMake Warning at foo/CMakeLists.txt:13 (message):
FOO: OFF
CMake Warning at foo/CMakeLists.txt:20 (message):
BAR: bar
-- Configuring done
-- Generating done
-- Build files have been written to: build
Second run
CMake Warning at foo/CMakeLists.txt:13 (message):
FOO: OFF
CMake Warning at foo/CMakeLists.txt:20 (message):
BAR: foo
-- Configuring done
-- Generating done
-- Build files have been written to: build
So first run, option FOO is correctly override by the super build variable thanks to CMP0077, while BAR cache variable is set to its default value.
During the second run, strangely (any explanation welcome, IMHO is BAR is already in the cache so the super build set will update it) BAR take the value of the super build...
Side Test
From scratch, just test we can override value in the cmdline (not my case but still interesting)
rm -rf build
cmake -S. -Bbuild -DBAR:STRING=plop
CMake Warning at foo/CMakeLists.txt:13 (message):
FOO: OFF
CMake Warning at foo/CMakeLists.txt:20 (message):
BAR: plop
-- Configuring done
-- Generating done
-- Build files have been written to: build
cmake -S. -Bbuild -DBAR:STRING=plop
CMake Warning at foo/CMakeLists.txt:13 (message):
FOO: OFF
CMake Warning at foo/CMakeLists.txt:20 (message):
BAR: plop
-- Configuring done
-- Generating done
-- Build files have been written to: build
ps: Sorry for the non syntax highlighting, contrary to github, SO doesn't recognize cmake...
One possible workaround
In the super build I can use:
set(BAR "foo" CACHE STRING "Override foo default value")
cons:
need a "docstring"
need to know the underlying variable type
expose it to super build user ?
ugly IMHO, see option() and variable interaction:
If <variable> is already set as a normal or cache variable, then the command does nothing (see policy CMP0077).
ref: https://cmake.org/cmake/help/latest/command/option.html
EDIT: As pointed out by Tsyvarev we can use:
set(BAR "foo" CACHE INTERNAL "")
which should fix 1,2,3 and mostly 4.
note: to backup and reapply previous cached variable value please take a look at the duplicate answer...
Related
Scenario
I'm in the process of debugging a CMake script.
In order to produce usable output for me to inspect, I use the following invocation:
cmake -S . -B build -DENABLE_MODULE_A=OFF -DENABLE_MODULE_B=OFF -DENABLE_MODULE_C=OFF --trace-source=CMakeLists.txt --trace-expand |& grep VARIABLE_IM_INTERESTED_IN > build/testout.utf8
Problem
This approach is sub-par, because I'm really only interested in one variable that is expanded in the CMake script in question. grep-ing for that after running it in trace-mode seems to be quite cumbersome, especially since the variable gets expanded multiple times, however I'm interested only in the last, full expansion of said variable.
Desired result
Ideally, there is a command line option for CMake, to print/expand only the one variable I'm interested in. I can filter for the last occurrence of that variable with external tools (for instance tail), but grepping the entire trace output for just one file, seems excessive. In essence I'm looking for a command line option to inspect an arbitrary variable, instead of tracing the entirety of the CMake script.
It is however, not an option to change the CMakeFiles.txt such that this one variable is printed. That is for external reasons, of which I have no control of.
Use variable_watch and CMAKE_PROJECT_INCLUDE_BEFORE as a debugging tool. Create a file called watch_var.cmake with the following contents:
cmake_minimum_required(VERSION 3.24)
variable_watch("${watch_var}")
Now here's an example project:
cmake_minimum_required(VERSION 3.24)
project(example)
set(VARIABLE_IM_INTERESTED_IN foo)
string(APPEND VARIABLE_IM_INTERESTED_IN " bar")
At the command line:
$ cmake -G Ninja -S . -B build -DCMAKE_PROJECT_INCLUDE_BEFORE=$PWD/watch_var.cmake -Dwatch_var=VARIABLE_IM_INTERESTED_IN
-- The C compiler identification is GNU 10.2.1
-- The CXX compiler identification is GNU 10.2.1
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
CMake Debug Log at CMakeLists.txt:4 (set):
Variable "VARIABLE_IM_INTERESTED_IN" was accessed using MODIFIED_ACCESS
with value "foo".
CMake Debug Log at CMakeLists.txt:5 (string):
Variable "VARIABLE_IM_INTERESTED_IN" was accessed using READ_ACCESS with
value "foo".
CMake Debug Log at CMakeLists.txt:5 (string):
Variable "VARIABLE_IM_INTERESTED_IN" was accessed using MODIFIED_ACCESS
with value "foo bar".
-- Configuring done
-- Generating done
-- Build files have been written to: /home/reinking/test2/build
Docs: https://cmake.org/cmake/help/latest/command/variable_watch.html
I am currently trying to set up a CMake-based build system for a small C library of mine. Since this library depends on libacl, CMake should verify that it is present on the system, or fail otherwise.
Since there is no predefined FindACL module, I used the one from the KDE project:
[...] check_include_files [...] find_library [...]
if (ACL_HEADERS_FOUND AND ACL_LIBS AND ATTR_LIBS)
set(ACL_FOUND TRUE)
set(ACL_LIBS ${ACL_LIBS} ${ATTR_LIBS})
message(STATUS "Found ACL support: ${ACL_LIBS}")
endif (ACL_HEADERS_FOUND AND ACL_LIBS AND ATTR_LIBS)
I call it using the following (minimal) CMakeLists.txt:
cmake_minimum_required(VERSION 3.12)
project(
cmaketest
VERSION 1.0
DESCRIPTION "cmake test"
LANGUAGES C
)
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
find_package(ACL REQUIRED) # ACL_LIBS
It correctly detects that my system is lacking the libacl includes, but does not stop processing, even though the manual states that
The REQUIRED option stops processing with an error message if the package cannot be found.
Do I have to explicitly check whether ACL_FOUND is set, through an if statement?
I am using CMake 3.13.4. Terminal output:
-- The C compiler identification is GNU 8.3.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Looking for include file attr/libattr.h
-- Looking for include file attr/libattr.h - not found
-- Looking for include file sys/xattr.h
-- Looking for include file sys/xattr.h - found
-- Looking for include file sys/acl.h
-- Looking for include file sys/acl.h - not found
-- Looking for include file acl/libacl.h
-- Looking for include file acl/libacl.h - not found
-- Configuring done
-- Generating done
-- Build files have been written to: [redacted]/build
It seems that since some time CMake expects a FindXXX.cmake script (used in MODULE mode of find_package) to check REQUIRED option and emit an error if needed. Such change in CMake behavior makes old legacy "Find" scripts, like FindACL.cmake, to be broken.
Indirect confirmation of that can be found in this "Common bug in find module", described in CMake wiki:
The module does not check _FIND_REQUIRED or
_FIND_QUIETLY - and thus the find_package arguments QUIET
and REQUIRED will have no effect
So, FindACL.cmake script could be rewritten as follows:
# FindACL.cmake
# ...
if (ACL_HEADERS_FOUND AND ACL_LIBS AND ATTR_LIBS)
set(ACL_FOUND TRUE)
set(ACL_LIBS ${ACL_LIBS} ${ATTR_LIBS})
message(STATUS "Found ACL support: ${ACL_LIBS}")
elif (ACL_FIND_REQUIRED)
# ACL hasn't been found but REQUIRED. Emit an error.
message(FATAL_ERROR "Cannot find ACL")
endif (ACL_HEADERS_FOUND AND ACL_LIBS AND ATTR_LIBS)
Actually, most "Find" scripts use the special helper which sets result of find_package and cares about its options. FindACL.cmake could also use this helper:
# FindACL.cmake
# ...
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(ACL
# Use default message on fail.
DEFAULT_MSG
# Variables which should be true-evaluated for assume the package to be found
ACL_HEADERS_FOUND ACL_LIBS ATTR_LIBS
)
The things are different for XXXConfig.cmake scripts, which are processed in CONFIG mode of find_package. These scripts should only set XXX_FOUND variable to false, and CMake will handle REQUIRED option by itself.
I'm having trouble setting a configuration variable via the command line. I can't determine it from the system, so I expect the user to specify:
cmake -DCMAKE_TOOLCHAIN_FILE=../android.toolchain -DANDROID_ABI:STRING="arm64" ..
Inside my android.toolchain, I have the following:
message(STATUS "Android ABI: ${ANDROID_ABI}")
if( "${ANDROID_ABI}" STREQUAL "" )
message(FATAL_ERROR "Please specifiy ABI at cmake call -DANDROID_ABI:STRING=armeabi or -DANDROID_ABI:STRING=arm64")
endif()
No matter what, it fails at this line EVEN THOUGH it prints out the correct arm64:
-- Android ABI: arm64
CMake Error at yaml-cpp/android.toolchain:45 (message):
Please specifiy ABI at cmake call -DANDROID_ABI:STRING=armeabi or -DANDROID_ABI:STRING=arm64
Could anyone direct me to what I'm doing wrong?
I think this has to do with:
-D adds a cache variable instead of a normal variable
This is in a toolchain file... it seems to ignore cache variables
Any thoughts or suggestions?
I don't pretend to fully understand what's going on behind the scenes, but here's a workaround that works for me:
# Problem: CMake runs toolchain files multiple times, but can't read cache variables on some runs.
# Workaround: On first run (in which cache variables are always accessible), set an intermediary environment variable.
if (FOO)
# Environment variables are always preserved.
set(ENV{_FOO} "${FOO}")
else ()
set(FOO "$ENV{_FOO}")
endif ()
CMake 3.6 introduces variable CMAKE_TRY_COMPILE_PLATFORM_VARIABLES which contains a list of variables, automatically passed from the main project to the project, created with try_compile.
A toolchain may add its variables to that list, so they could be extracted in a subproject:
message(STATUS "Android ABI: ${ANDROID_ABI}")
if( "${ANDROID_ABI}" STREQUAL "" )
message(FATAL_ERROR "Please specifiy ABI at cmake call -DANDROID_ABI:STRING=armeabi or -DANDROID_ABI:STRING=arm64")
endif()
# propagate the variable into "inner" subprojects.
list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES "ANDROID_ABI")
Caveats:
This approach affects only to source flow of try_compile command. It won't work when try_compile is used for create fully-fledged CMake project with signature
try_compile(<resultVar> <bindir> <srcdir> <projectName> ...)
(Approach with setting environment variable, as described in the #sorbet answer, works perfectly in this case.)
This approach won't work for a subproject, created with ExternalProject_Add.
(Approach with setting environment variable fails in that case too.)
I am not asking about the various available third-party modules that support Cppcheck in one way or the other.
With CMake 3.10, CMake seems to have gained some official Cppcheck support. See CMAKE_<LANG>_CPPCHECK.
Unfortunately the documentation how to use this variable is a bit sparse. Is there a good example of how Cppcheck is supposed to be used with CMake 3.10 (or later)?
An simple example would be - if you have cppcheck in your PATH and you are not specifying additional parameters - the following by setting global CMAKE_<LANG>_CPPCHECK variable:
cmake_minimum_required(VERSION 3.10)
project(CppCheckTest)
file(
WRITE "main.cpp"
[=[
int main()
{
char a[10];
a[10] = 0;
return 0;
}
]=]
)
set(CMAKE_CXX_CPPCHECK "cppcheck")
add_executable(${PROJECT_NAME} "main.cpp")
The files to scan are added automatically to the cppcheck command line. So the above example gives the following output (gcc and cppcheck on Linux system):
# make
Scanning dependencies of target CppCheckTest
[ 50%] Building CXX object CMakeFiles/CppCheckTest.dir/main.cpp.o
Checking .../CppCheckTest/main.cpp...
Warning: cppcheck reported diagnostics:
[/mnt/c/temp/StackOverflow/CppCheckTest/main.cpp:4]: (error) Array 'a[10]' accessed at index 10, which is out of bounds.
[100%] Linking CXX executable CppCheckTest
[100%] Built target CppCheckTest
You could give cppcheck a try in an existing project by simply setting the CMAKE_CXX_CPPCHECK variable via the cmake command line:
# cmake -DCMAKE_CXX_CPPCHECK:FILEPATH=cppcheck ..
A more "daily life" example would probably for you to include something like the following code snippet in your CMakeList.txt:
find_program(CMAKE_CXX_CPPCHECK NAMES cppcheck)
if (CMAKE_CXX_CPPCHECK)
list(
APPEND CMAKE_CXX_CPPCHECK
"--enable=warning"
"--inconclusive"
"--force"
"--inline-suppr"
"--suppressions-list=${CMAKE_SOURCE_DIR}/CppCheckSuppressions.txt"
)
endif()
References
CMake Commit: Add properties to run cppcheck along with the compiler
<LANG>_CPPCHECK target property
This property is supported only when <LANG> is C or CXX.
Specify a ;-list containing a command line for the cppcheck static analysis tool. The Makefile Generators and the Ninja generator will run cppcheck along with the compiler and report any problems.
This property is initialized by the value of the CMAKE_<LANG>_CPPCHECK variable if it is set when a target is created.
I'm having trouble setting a configuration variable via the command line. I can't determine it from the system, so I expect the user to specify:
cmake -DCMAKE_TOOLCHAIN_FILE=../android.toolchain -DANDROID_ABI:STRING="arm64" ..
Inside my android.toolchain, I have the following:
message(STATUS "Android ABI: ${ANDROID_ABI}")
if( "${ANDROID_ABI}" STREQUAL "" )
message(FATAL_ERROR "Please specifiy ABI at cmake call -DANDROID_ABI:STRING=armeabi or -DANDROID_ABI:STRING=arm64")
endif()
No matter what, it fails at this line EVEN THOUGH it prints out the correct arm64:
-- Android ABI: arm64
CMake Error at yaml-cpp/android.toolchain:45 (message):
Please specifiy ABI at cmake call -DANDROID_ABI:STRING=armeabi or -DANDROID_ABI:STRING=arm64
Could anyone direct me to what I'm doing wrong?
I think this has to do with:
-D adds a cache variable instead of a normal variable
This is in a toolchain file... it seems to ignore cache variables
Any thoughts or suggestions?
I don't pretend to fully understand what's going on behind the scenes, but here's a workaround that works for me:
# Problem: CMake runs toolchain files multiple times, but can't read cache variables on some runs.
# Workaround: On first run (in which cache variables are always accessible), set an intermediary environment variable.
if (FOO)
# Environment variables are always preserved.
set(ENV{_FOO} "${FOO}")
else ()
set(FOO "$ENV{_FOO}")
endif ()
CMake 3.6 introduces variable CMAKE_TRY_COMPILE_PLATFORM_VARIABLES which contains a list of variables, automatically passed from the main project to the project, created with try_compile.
A toolchain may add its variables to that list, so they could be extracted in a subproject:
message(STATUS "Android ABI: ${ANDROID_ABI}")
if( "${ANDROID_ABI}" STREQUAL "" )
message(FATAL_ERROR "Please specifiy ABI at cmake call -DANDROID_ABI:STRING=armeabi or -DANDROID_ABI:STRING=arm64")
endif()
# propagate the variable into "inner" subprojects.
list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES "ANDROID_ABI")
Caveats:
This approach affects only to source flow of try_compile command. It won't work when try_compile is used for create fully-fledged CMake project with signature
try_compile(<resultVar> <bindir> <srcdir> <projectName> ...)
(Approach with setting environment variable, as described in the #sorbet answer, works perfectly in this case.)
This approach won't work for a subproject, created with ExternalProject_Add.
(Approach with setting environment variable fails in that case too.)