Is it possible to set cmake variables with add_subdirectory? - cmake

I am currently using add_subdirectory(${CMAKE_SOURCE_DIR}/ext/glfw) to add glfw to my project.
The problem is that once I do cmake .. && make it always builds all the examples and I don't want that to happen.
In the cmake file from glfw there is
option(GLFW_BUILD_EXAMPLES "Build the GLFW example programs" ON)
Is it possible to set this variable to OFF from my CMakeLists.txt?

Simply do either
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "Build the GLFW example programs")
before the add_subdirectory command or
cmake -DGLFW_BUILD_EXAMPLES:BOOL=OFF ..
on the command line
For example given the following two CMake scripts
cmake_minimum_required(VERSION 2.8)
project(test)
set(OVERRIDE FALSE CACHE BOOL "")
if(OVERRIDE)
message(STATUS "Overriding option")
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "Build the GLFW example programs")
endif()
message(STATUS "OPT BEFORE =${GLFW_BUILD_EXAMPLES}")
add_subdirectory(subdir)
message(STATUS "OPT AFTER=${GLFW_BUILD_EXAMPLES}")
and subdir/CMakeLists.txt
option(GLFW_BUILD_EXAMPLES "Build the GLFW example programs" ON)
you see the following (when run in the build directory
With no command line options, the option() command sets the variable
$ rm -rf *
$ cmake ..
...
-- OPT BEFORE =
-- OPT AFTER=ON
-- Configuring done
-- Generating done
Turning on the over-ride, the set() command overrides the option() command
$ rm -rf *
$ cmake .. -DOVERRIDE=ON
...
-- Overriding option
-- OPT BEFORE =OFF
-- OPT AFTER=OFF
-- Configuring done
-- Generating done
You can also over-ride the option() directly on the command line.
$ rm -rf *
$ cmake .. -DGLFW_BUILD_EXAMPLES=OFF
...
-- OPT BEFORE =OFF
-- OPT AFTER=OFF
-- Configuring done
-- Generating done
Note that I do an rm -rf * in the build directory each time. The value of GLFW_BUILD_EXAMPLES is cached in a file called CMakeCache.txt. CMake will always use the values it finds there before anything set in a set or option command.

Related

How do I get cmake to halt processing on error [duplicate]

I want to disallow people from cluttering our source tree with generated CMake files... and, more importantly, disallow them from stepping on existing Makefiles that are not part of the same build process we're using CMake for. (best not to ask)
The way I have come up with to do this is to have a few lines at the top of my CMakeLists.txt, as follows:
if("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}")
message(SEND_ERROR "In-source builds are not allowed.")
endif("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}")
However, doing it this way seems too verbose. Additionally, if I try an in-source build it still creates the the CMakeFiles/ directory, and the CMakeCache.txt file in the source tree before the error is thrown.
Am I missing a better way to do this?
CMake has two undocumented options:
CMAKE_DISABLE_SOURCE_CHANGES and CMAKE_DISABLE_IN_SOURCE_BUILD
cmake_minimum_required (VERSION 2.8)
# add this options before PROJECT keyword
set(CMAKE_DISABLE_SOURCE_CHANGES ON)
set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)
project (HELLO)
add_executable (hello hello.cxx)
-
andrew#manchester:~/src% cmake .
CMake Error at /usr/local/share/cmake-2.8/Modules/CMakeDetermineSystem.cmake:160 (FILE):
file attempted to write a file: /home/andrew/src/CMakeFiles/CMakeOutput.log
into a source directory.
/home/selivanov/cmake-2.8.8/Source/cmMakefile.cxx
bool cmMakefile::CanIWriteThisFile(const char* fileName)
{
if ( !this->IsOn("CMAKE_DISABLE_SOURCE_CHANGES") )
{
return true;
}
// If we are doing an in-source build, than the test will always fail
if ( cmSystemTools::SameFile(this->GetHomeDirectory(),
this->GetHomeOutputDirectory()) )
{
if ( this->IsOn("CMAKE_DISABLE_IN_SOURCE_BUILD") )
{
return false;
}
return true;
}
// Check if this is subdirectory of the source tree but not a
// subdirectory of a build tree
if ( cmSystemTools::IsSubDirectory(fileName,
this->GetHomeDirectory()) &&
!cmSystemTools::IsSubDirectory(fileName,
this->GetHomeOutputDirectory()) )
{
return false;
}
return true;
}
Include a function like this one. It is similar to what you do with these differences:
It is encapsulated in a function, which is called when you include the PreventInSourceBuilds.cmake module. Your main CMakeLists.txt must include it:
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/CMake)
include(PreventInSourceBuilds)
It uses get_filename_component() with REALPATH parameter that resolves symlinks before comparing the paths.
In case the github link changes, here's the module source code (which should be placed in a PreventInSouceBuilds.cmake, in a directory called CMake, in the above example):
#
# This function will prevent in-source builds
function(AssureOutOfSourceBuilds)
# make sure the user doesn't play dirty with symlinks
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH)
# disallow in-source builds
if("${srcdir}" STREQUAL "${bindir}")
message("######################################################")
message("# ITK should not be configured & built in the ITK source directory")
message("# You must run cmake in a build directory.")
message("# For example:")
message("# mkdir ITK-Sandbox ; cd ITK-sandbox")
message("# git clone http://itk.org/ITK.git # or download & unpack the source tarball")
message("# mkdir ITK-build")
message("# this will create the following directory structure")
message("#")
message("# ITK-Sandbox")
message("# +--ITK")
message("# +--ITK-build")
message("#")
message("# Then you can proceed to configure and build")
message("# by using the following commands")
message("#")
message("# cd ITK-build")
message("# cmake ../ITK # or ccmake, or cmake-gui ")
message("# make")
message("#")
message("# NOTE: Given that you already tried to make an in-source build")
message("# CMake have already created several files & directories")
message("# in your source tree. run 'git status' to find them and")
message("# remove them by doing:")
message("#")
message("# cd ITK-Sandbox/ITK")
message("# git clean -n -d")
message("# git clean -f -d")
message("# git checkout --")
message("#")
message("######################################################")
message(FATAL_ERROR "Quitting configuration")
endif()
endfunction()
AssureOutOfSourceBuilds()
I have a cmake() shell function in my .bashrc/.zshrc similar to this one:
function cmake() {
# Don't invoke cmake from the top-of-tree
if [ -e "CMakeLists.txt" ]
then
echo "CMakeLists.txt file present, cowardly refusing to invoke cmake..."
else
/usr/bin/cmake $*
fi
}
I prefer this low ceremony solution. It got rid of my colleagues' biggest complaint when we switched to CMake, but it doesn't prevent people who really want to do an in-source/top-of-tree build from doing so—they can just invoke /usr/bin/cmake directly (or not use the wrapper function at all). And it's stupid simple.
I think I like your way. The cmake mailing list does a good job at answering these types of questions.
As a side note: you could create a "cmake" executable file in the directory which fails. Depending on whether or not "." is in their path (on linux). You could even symlink /bin/false.
In windows, I am not sure if a file in your current directory is found first or not.
You can configure your .bashrc file like this one
Look at the functions cmakekde and kdebuild. Set BUILD and SRC env. variables and edit these functions according to your needs. This will build only in buildDir rather than srcDir
Just make the directory read-only by the people/processes doing the builds. Have a separate process that checks out to the directory from source control (you are using source control, right?), then makes it read-only.
For those on Linux:
add to top-level CMakeLists.txt:
set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)
create a file 'dotme' in your top-level or add to your .bashrc (globally):
#!/bin/bash
cmk() { if [ ! -e $1/CMakeLists.txt ] || ! grep -q "set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)" $1/CMakeLists.txt;then /usr/bin/cmake $*;else echo "CMAKE_DISABLE_IN_SOURCE_BUILD ON";fi }
alias cmake=cmk
now run:
. ./dotme
when you try to run cmake in the top-level source tree:
$ cmake .
CMAKE_DISABLE_IN_SOURCE_BUILD ON
No CMakeFiles/ or CMakeCache.txt gets generated.
When doing out-of-source build and you need to run cmake first time just call the actual executable:
$ cd build
$ /usr/bin/cmake ..
This is still the best answer for my purposes:
project(myproject)
if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
message(FATAL_ERROR "In-source builds are not allowed")
endif()
or allow the build, but show a warning message:
if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
message(WARNING "In-source builds are not recommended")
endif()
However, there does not appear to be a simple way to avoid CMakeFiles/ and CMakeCache.txt being created in the source directory.

How does project(...) affect variables?

I stumbled upon the following nice cmake feature recently:
https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT.html
Save the following snippet as CMakeLists.txt, run mkdir build; cd build:
cmake_minimum_required(VERSION 3.12)
message(STATUS "CMID: ${CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT}")
project( test )
Executing that snippet does not output a true variable:
➜ build /usr/bin/rm -rf *; cmake ../ | grep CMID
-- CMID:
Now if you change that snippet:
cmake_minimum_required(VERSION 3.12)
project( test )
message(STATUS "CMID: ${CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT}")
Execution yields the result, I expect from the documentation:
➜ build /usr/bin/rm -rf *; cmake ../ | grep CMID
-- CMID: 1
So I wonder, how does the relative position of the project( ... ) command change that variable?
project() call sets many CMake variables, and CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT is one of such variables.
So, for many CMake commands and variables' accesses placing them before or after the project() call is crusial.
In most cases, project() call should be before the using of the other commands and variables.
If you are looking for a way of changing default install prefix from the CMakeLists.txt, see that my answer: https://stackoverflow.com/a/39485990/3440745.

cmake cache variables deleted when some change

I have a part of my CMakeLists.txt that sets the C++ standard:
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 11)
endif()
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
This seems to work fine... mostly. I have a build directory, which I set up via:
$ cmake -G Ninja -DCMAKE_CXX_COMPILER=$(which clang++) -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_STANDARD=17 ../gw3
Built my project on clang, fixed a few things, normal work stuff. Then I wanted to check to see if I broke the gcc build, so, in the same directory, I ran:
$ cmake -G Ninja -DCMAKE_CXX_COMPILER=$(which g++) -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_STANDARD=17 ../gw3
This emited:
-- Configuring done
You have changed variables that require your cache to be deleted.
Configure will be re-run and you may have to reset some variables.
The following variables have changed:
CMAKE_CXX_COMPILER= /path/to/bin/g++
CMAKE_CXX_COMPILER= /path/to/bin/g++
CMAKE_CXX_COMPILER= /path/to/bin/g++
CMAKE_CXX_COMPILER= /path/to/bin/g++
... like 20 more times ...
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 7.2.0
... more cmake stuff here ...
At this point though, CMAKE_CXX_STANDARD is set to 11 in the cache! Why? I set it on the command line to 17. If I rereun the same cmake command again (verbatim, just up-arrow, enter), then the CMAKE_CXX_STANDARD variable gets set to 17 as desired. What gives?
The conventional way to avoid this error is to set the compiler via an environmental variable, i.e:
$ CXX=clang++ cmake .. -DCMAKE_CXX_STANDARD=17
# ...
$ cat CMakeCache.txt | grep STANDARD
CMAKE_CXX_STANDARD:STRING=17
$ env CXX=g++ cmake .. -DCMAKE_CXX_STANDARD=17
# ...
$ cat CMakeCache.txt | grep STANDARD
CMAKE_CXX_STANDARD:STRING=17
CMake doesn't like it when you try to change CMAKE_CXX_COMPILER. From the FAQ:
I change CMAKE_C_COMPILER in the GUI but it changes back on the next configure step. Why?
Once a build tree is created with a given compiler it cannot be
changed. There are a variety of implementation reasons for this
policy.

How to instruct CMake to use the build architecture compiler

When using CMake for cross compiling, one generally specifies a toolchain file via the CMAKE_TOOLCHAIN_FILE option. In GNU terminology, one can specify the host architecture toolset using this file. However, one can generally not expect to be able to execute anything built with this toolchain. So often enough, some build tools need to be compiled for the build architecture.
Consider the following setup. I have two source files genfoo.c and bar.c. During build, genfoo.c needs to be compiled and run. Its output needs to be written to foo.h. Then I can compile bar.c, which #include "foo.h". Since CMake defaults to using the host architecture toolchain, the instructions for bar.c are easy. But how do I tell it to use the build architecture toolchain for compiling genfoo.c? Simply saying add_executable(genfoo genfoo.c) will result in using the wrong compiler.
CMake can only handle one compiler at a time. So - if you don't go the long way to set up the other compiler as a new language - you will end up with two configuration cycles.
I see the following approaches to automate this process:
Taking the example "CMake Cross Compiling - Using executables in the build created during the build?" from the CMake pages as a starting point I'll get:
CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(FooBarTest)
# When crosscompiling import the executable targets
if (CMAKE_CROSSCOMPILING)
set(IMPORT_PATH "IMPORTFILE-NOTFOUND" CACHE FILEPATH "Point it to the export file path from a native build")
file(TO_CMAKE_PATH "${IMPORT_PATH}" IMPORT_PATH_CMAKE)
include(${IMPORT_PATH_CMAKE}/genfooTargets.cmake)
# Then use the target name as COMMAND, CMake >= 2.6 knows how to handle this
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/foo.h
COMMAND genfoo
)
add_executable(bar bar.cpp ${CMAKE_CURRENT_BINARY_DIR}/foo.h)
target_include_directories(bar PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
endif()
# Only build the generator if not crosscompiling
if (NOT CMAKE_CROSSCOMPILING)
add_executable(genfoo genfoo.cpp)
export(TARGETS genfoo FILE "${CMAKE_CURRENT_BINARY_DIR}/genfooTargets.cmake")
endif()
Then using a script like:
build.sh
#!/bin/bash
if [ ! -d hostBuild ]; then
cmake -E make_directory hostBuild
cmake -E chdir hostBuild cmake ..
fi
cmake --build hostBuild
if [ ! -d crossBuild ]; then
cmake -E make_directory crossBuild
cmake -E chdir crossBuild cmake .. -DIMPORT_PATH=${PWD}/hostBuild -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake
fi
cmake --build crossBuild
I'll get the desired results by calling ./build.sh.
Splitting the CMakeLists.txt and maybe even replace the export()/include() with something where I know the output path of my build tools e.g. by using CMAKE_RUNTIME_OUTPUT_DIRECTORY would simplify things:
CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(FooBarTest)
# Then use the target name as COMMAND. CMake >= 2.6 knows how to handle this
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/foo.h
COMMAND genfoo
)
add_executable(bar bar.cpp ${CMAKE_CURRENT_BINARY_DIR}/foo.h)
target_include_directories(bar PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
buildTools/CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(BuildTools)
add_executable(genfoo genfoo.cpp)
build.sh
#!/bin/bash
if [ ! -d crossBuild ]; then
cmake -E make_directory crossBuild
cmake -E chdir crossBuild cmake .. -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake
fi
if [ ! -d hostBuild ]; then
cmake -E make_directory hostBuild
cmake -E chdir hostBuild cmake ../buildTools -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${PWD}/crossBuild
fi
cmake --build hostBuild
cmake --build crossBuild
References
Making a CMake library accessible by other CMake packages automatically
CMake build multiple targets in different build directories
How do I make CMake output into a 'bin' dir?
It is possible to do that completely within CMake.
The trick is to run a separate CMake configuring stage within its own space, silently dismissing every crosscompiling setting and using the host's default toolchain, then import the generated outputs into it's parent, crosscompiling build.
First part:
set(host_tools_list wxrc generate_foo)
if(CMAKE_CROSSCOMPILING)
# Pawn off the creation of the host utilities into its own dedicated space
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/host_tools)
file(TO_NATIVE_PATH ${CMAKE_COMMAND} native_cmake_command)
file(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR} native_cmake_current_source_dir)
execute_process(
COMMAND "${native_cmake_command}" "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" "${native_cmake_current_source_dir}"
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/host_tools
)
add_custom_target(host_tools
COMMAND ${CMAKE_COMMAND} --build . --target host_tools --config $<CONFIG>
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/host_tools
)
include(${CMAKE_CURRENT_BINARY_DIR}/host_tools/host_tools.cmake)
foreach(tgt IN ITEMS ${host_tools_list})
add_dependencies(host${tgt} host_tools)
endforeach()
else()
# Add an empty target, host tools are built inplace
add_custom_target(host_tools
DEPENDS ${host_tools_list}
)
endif()
... then you add the usual add_executable and whatever ...
At the end:
if(NOT CMAKE_CROSSCOMPILING)
foreach(tgt IN ITEMS ${host_tools_list})
add_executable(host${tgt} ALIAS ${tgt})
endforeach()
export(TARGETS ${host_tools_list} NAMESPACE host FILE host_tools.cmake)
endif()
When it crosscompiles, it pawns off the creation of the host-run tools into its own dedicated space, and imports the targets as "hostwxrc" and "hostgenerate_foo", with a dependency on generating the host_tools themselves .
When it doesn't crosscompile, it builds wxrc and generate_foo as-is, and aliases them to hostwxrc and hostgenerate_foo.
After this, when you use $<TARGET_FILE:wxrc>, you refer to the wxrc built for the target platform, and $<TARGET_FILE:hostwxrc> refers to the wxrc built for the host platform, regardless whether they are the same or not.

How to set a CMake option() at command line

I created a CMakeLists.txt that contains the following
project(P4V)
cmake_minimum_required(VERSION 2.6)
option(BUILD_STATIC_LIBS "Build the static library" ON)
option(BUILD_SHARED_LIBS "Build the shared library" ON)
option(BUILD_TESTS "Build test programs" OFF)
include_directories(${CMAKE_SOURCE_DIR}/include)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)
set(CMAKE_BUILD_TYPE Release)
add_subdirectory(src)
if(BUILD_TESTS)
add_subdirectory(tests)
endif(BUILD_TESTS)
By default BUILD_TESTS is OFF, how can I turn it ON without CMake GUI, but with cmake command itself?
I tried the following, but it doesn't work
cmake .. -G %1 -DBUILD_SHARED_LIBS=ON -DBUILD_STATIC_LIBS=ON -DBUILD_TESTS=ON
Delete the CMakeCache.txt file and try this:
cmake -G %1 -DBUILD_SHARED_LIBS=ON -DBUILD_STATIC_LIBS=ON -DBUILD_TESTS=ON ..
You have to enter all your command-line definitions before including the path.
An additional option is to go to your build folder and use the command ccmake .
This is like the GUI but terminal based. This obviously won't help with an installation script but at least it can be run without a UI.
The one warning I have is it won't let you generate sometimes when you have warnings. if that is the case, exit the interface and call cmake .
this works for me:
cmake -D DBUILD_SHARED_LIBS=ON DBUILD_STATIC_LIBS=ON DBUILD_TESTS=ON ..