Wrap cache variables of a library when configuring my code in CMake - cmake

I have to build a library that is using another library, how can I make variables of that another library not showing in the CMake-Gui and use instead values that are configured by my CMake script?
For example when my application is built for Mobile, I already have a flag for OpenGL ES, but the SDL "VIDEO_OPENGLES" variable still shows up in the GUI. The problem is that since my build script already have knowledge of what happens, it can happily pre-configure other scripts instead of polluting the Gui of the users that need to compile my code.
Also I want to know what happens if 2 libraries have by accident 2 variables with same name but used in a different fashion.

The key word is INTERNAL property of the variable. Take a look at this example:
Test/CMakeLists.txt:
cmake_minimum_required(VERSION 2.8)
Project(Test)
add_subdirectory(Component1)
add_subdirectory(Component2)
Test/Component1/CMakeLists.txt:
cmake_minimum_required(VERSION 2.8.11)
project(Component1)
set(SOURCES test1.c)
set(Component1_CONF "SomeValue" CACHE STRING "Component1_CONF description")
message(STATUS "Component1_CONF=${Component1_CONF}")
add_library(Component1 ${SOURCES})
target_include_directories(
Component1 INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>"
)
Test/Component2/CMakeLists.txt:
project(Component2)
set(SOURCES test1.c)
set(Component2_CONF "/etc/passwd" CACHE FILEPATH "Component2_CONF description")
add_executable(Component2 ${SOURCES})
target_link_libraries(Component2 Component1)
Actually Component2 can be a library static or shared not an executable, it doesn't matter.
Now if you run cmake-gui on the top directory and run Configure you will see both Component1_CONF and Component2_CONF. But if you change the top-level CMakeLists.txt and add the line which forcibly sets Component1_CONF:
cmake_minimum_required(VERSION 2.8)
Project(Test)
set(Component1_CONF "Other value" CACHE INTERNAL "Component1_CONF forced value" FORCE)
add_subdirectory(Component1)
add_subdirectory(Component2)
you will effectively hide Component1_CONF from cmake-gui and even from command-line (-DComponent1_CONF=Boo) configuration.
Also you should note that usually variables are set only in the current scope (unless PARENT keyword is used). Thus variables set in some directory don't affect variables set in peer subirectories and the parent subdirectory. However if variables are set in the very same scope (say, when you run configuration tests from within the parent CMakeLists.txt) then yes, they could interfere one another. To prevent this CMake uses naming convention which explicitly separates variables from different packages. Most of configuration macros receives "prefix" parameter which specifies the name prefix for variables being set in the macros, so macro users (that is applications CMakeLists.txt) can explicitly separate test variables from different dependent packages.

Related

Share cmake variables between different folders

Suppose I have a project setup like so:
Project
'-> CMakeLists.txt
'-> src
'-> CMakeLists.txt
'-> third_party
'-> CMakeLists.txt
In the CMakeLists.txt of third_party I have a variable set like so:
cmake_path( APPEND ${CMAKE_SOURCE_DIR} "third_party" OUTPUT_VARIABLE PROJECT_THIRD_PARTY_DIR )
In the top level CMakeLists.txt I have both 'third_party' and 'src' included and the third party folder has a particular prebuilt project as well as others that will be built. Is is possible to have access to the variable '${PROJECT_THIRD_PARTY_DIR}' from the src CMakeLists.txt or would I have to move the definition into the master CMakeLists.txt? From what I have found online it seems it can be done by using 'set( .... CACHE INTERNAL )' but I would prefer to use what I thought to be the correct (and newer) features of cmake 3.20 for this path variable.
First of all, you should minimize adding variables to your Cmake path.
Paths can change between operating systems and between different installations of the same operating system, so you should save information with Cmake variables.
Even if you are trying to use new Cmake, sticking with the set() command allows for better back-portability with earlier versions of Cmake. I would stay with the set(<blah name> CACHE INTERNAL ) call and move on.
Another note:
A variable defined in your top level Cmake file is accessible to the lower level CMake files. When the top level Cmake includes another Cmake file, the Cmake files included later also have access to the previous sub-Cmake file variables (with a few exceptions, like the filepath of the previous Cmake file). Ignoring the exceptions, the simple solution to me is to define your 3rd party sources first in your top level Cmake and then the later Cmake files in your own source code can access them.

Where to set CMAKE_CONFIGURATION_TYPES in a project with subprojects

Lets say I have a project with two independent subprojects. If I understood cmake correctly, the idea would be to have one root CMakeLists.txt defining a project(...) and then using add_subdirectory(...) to include the subprojects. Each subproject would have its own CMakeLists.txt defining its own project. This way projects can be build either together (using the root cmake file) or individually (using the subprojects cmake file).
I now would like to change the CMAKE_CONFIGURATION_TYPES. Should I do this in the root CMakeLists.txt or in each subproject, or both?
Changing it in the root would mean that building a subproject individually would offer the wrong configuration types; the other options would duplicate the cmake code. I think I'm missing something here.
Factorize out the code that sets up configuration-dependent settings. Create a file, say, SetUpConfigurations.cmake with this content:
if(NOT SET_UP_CONFIGURATIONS_DONE)
set(SET_UP_CONFIGURATIONS_DONE TRUE)
# No reason to set CMAKE_CONFIGURATION_TYPES if it's not a multiconfig generator
# Also no reason mess with CMAKE_BUILD_TYPE if it's a multiconfig generator.
get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(isMultiConfig)
set(CMAKE_CONFIGURATION_TYPES "Debug;Release;Profile" CACHE STRING "" FORCE)
else()
if(NOT CMAKE_BUILD_TYPE)
message("Defaulting to release build.")
set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
endif()
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY HELPSTRING "Choose the type of build")
# set the valid options for cmake-gui drop-down list
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;Profile")
endif()
# now set up the Profile configuration
set(CMAKE_C_FLAGS_PROFILE "...")
set(CMAKE_CXX_FLAGS_PROFILE "...")
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "...")
endif()
Then include(..) this file at the beginning of the CMakeLists.txt's.
You have two choices about where to put SetUpConfigurations.cmake, it depends on how you organize your projects, repositories:
The quick'n'dirty way: Copy and commit this script into each project that needs it. Its location will be fixed, relative to the CMakeLists.txt of the project. So you can include it, for example, with include(${CMAKE_CURRENT_SOURCE_DIR}/<...>/SetUpConfigurations.cmake)
The disciplined way: Maintain a repository with your custom CMake scripts, like this one. Each time you generate a project with the cmake command, you pass the path to this repository in the CMAKE_MODULE_PATH variable:
cmake -DCMAKE_MODULE_PATH=<dir-of-cmake-script-repo> ...
In this case include the script with include(SetUpConfigurations) (no .cmake extension).
A note about what a multiconfig generator is:
Xcode and Visual Studio are multiconfig generators. They respect the value of CMAKE_CONFIGURATION_TYPES but CMAKE_BUILD_TYPE has no effect since no concrete configuration is defined when the CMakeLists.txt is processed. It will be selected on the IDE's user interface later.
On the other hand, the makefile-style generators are not interested in CMAKE_CONFIGURATION_TYPES. CMAKE_BUILD_TYPE defines the configuration. It is a concrete value when the CMakeLists.txt file is processed but still: never make any decisions based on the value of CMAKE_BUILD_TYPE:
if(CMAKE_BUILD_TYPE STREQUAL "Release") # WRONG!
....
endif()
You project won't work as intended in multiconfig generators.
When use add_subdirectory into subproject dir, you propagate almost all variables into that subproject, which contradicts to "subproject independency".
Instead, it is better to build and install subproject using nested cmake call inside execute_process(). If you want to make some subproject's definitions available for top-level project, you need to "export" this definitions when subproject is installed. This question/answer post describes, how to do that.

How to get debug postfix in executable name

I am using cmake 2.8.12.2. I have set CMAKE_DEBUG_POSTFIX, and it is automatically used with the add_library command. But it is not automatically used with add_executable command. I have discovered that I can set the DEBUG_POSTFIX target property to get a debug postfix into the executable name, but this requires using an additional command.
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
set_target_properties(${PROJECT_NAME} PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
Is the second command explicitly setting the DEBUG_POSTFIX target property required or is there an easier way?
The current cmake documentation for set_target_properties states
There is also a <CONFIG>_OUTPUT_NAME that can set the output name on a per-configuration basis. <CONFIG>_POSTFIX sets a postfix for the real name of the target when it is built under the configuration named by (in upper-case, such as “DEBUG_POSTFIX”). The value of this property is initialized when the target is created to the value of the variable CMAKE_<CONFIG>_POSTFIX (except for executable targets because earlier CMake versions which did not use this variable for executables).
So it seems to highlight the fact that cmake does not use the value of CMAKE_DEBUG_POSTFIX in the name of executables. Therefore
add_executable(myexe ${SOURCE_FILES})
set_target_properties(myexe PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
will use the value of the global variable ${CMAKE_DEBUG_POSTFIX} when building the myexe target for the DEBUG configuration.
Note that one of the commenters to this question was confused by the use of the variable ${PROJECT_NAME}. This variable is automatically set to myexe when using project(myexe). Using ${PROJECT_NAME} is equivalent to myexe and it makes it easier to copy/paste to new CMakeLists.txt.
Just to mention that, if you have in your project many sub-executables, it is worth having a look on CMAKE_EXECUTABLE_SUFFIX. The variable in this case would need to be changed when running CMake for a specific build (Release/Debug), but this suffix is auto-attached to every ADD_EXECUTABLE(...)-call executable name. Tested and verified with CMake 2.8.12.2 and 3.0.2

CMake generated makefile does not expand all `make` variables

I have a projects that consists of modules that are built separately. Some modules use make only, others use cmake.
The top project has a makefile, which is used to trigger the build of the submodules. The makefile at the top defines make variables for pathes to libs, includes, and binaries. The submodules then use these variables.
My problem is that I cannot pass all these make variables to the projects that use cmake.
For the include directory, this work-around in CMakeLists.txt works:
INCLUDE_DIRECTORIES("/$(INCLUDE_DIR)")
The string is passed as is all the way to the generated makefile, and is then expanded by make upon building. The leading slash is needed. Without it, the content of INCLUDE_DIR gets prefixed by the cmake project root (it is then considered a relative path).
This solution does not work for linking:
LINK_DIRECTORIES("/$(LIB_DIR)")
The link command that I see upon building has the literal string -L/$(LIB_DIR). Removing the leading slash gives a warning about relative paths, and the link command has still the unexpanded $LIB_DIR.
How do I make sure that the expansion works for the make variable LIB_DIR passed though cmake to the generated makefile? Since it is make that runs the linker (I think?) I don't see why it wouldn't expand.
Is there another, less ugly solution, solution for fixing it for INCLUDE_DIR as well?
A backup solution is to let the top makefile define CMAKE variables. It should work fine, and is probably what I'll do if I can't make it work otherwise. But it would be nice to just define these variables once and make everything else take them into account.
One option is to use an environment variable to set a CMake variable. For example:
set (INCLUDE_DIR $ENV{INCLUDE_DIR})
then use the CMake variable like other CMake variables:
INCLUDE_DIRECTORIES(${INCLUDE_DIR})
If you want the variable to show up in cmake-gui or ccmake you can put it in the cache using the following:
set(INCLUDE_DIR $ENV{INCLUDE_DIR} CACHE PATH "An environment variable containing a path" FORCE)
for my projects I have wrapped the concept in a cmake function:
function( define_from_environment VariableName PackageName)
if (NOT DEFINED ${VariableName})
message( STATUS "${VariableName}=$ENV{${VariableName}}" )
if (NOT "$ENV{${VariableName}}" STREQUAL "")
set(${VariableName} $ENV{${VariableName}} CACHE PATH "Set the path variable ${VariableName} for ${PackageName}" FORCE)
endif (NOT "$ENV{${VariableName}}" STREQUAL "")
endif(NOT DEFINED ${VariableName})
endfunction( define_from_environment)
An example usage of this CMake function is
define_from_environment(GDCM_DIR GDCM)
which sets the CMake variable GDCM_DIR to a CMake cache variable if the environment variable GDCM_DIR is set.

CMake find_package dependency on subproject

I have the following directory layout:
main_folder
+ static_lib1
+ executable
Both 'static_lib1' and 'executable' have a full CMakeLists so that they can be
built independently.
The 'executable' depends on 'static_lib1'. It uses find_package() to locate 'static_lib1'.
The main_folder contains a CMakeLists that includes both 'static_lib1' and 'executable' via add_subdirectory for conveniently building the whole project in one go.
Everything works fine if I manually build 'static_lib1' and then 'executable'. But when running the CMakeLists from the main folder, I get an error because find_package is unable to find the library files from 'static_lib1' which have not yet been built.
How can I resolve this while keeping the CMakeLists files separate (i.e. without including the static_lib's CMakeLists from the executable's CMakeLists)?
In executable's CMakeLists.txt you can check if you are building stand-alone or as part of project:
if( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR )
# stand-alone build
find_package(static_lib1)
else()
include_directories(../static_lib1)
link_directories(../static_lib1)
...
target_link_libraries(executable static_lib1)
endif()
Switch from a file-based approach to a target-based approach for handling the dependency from executable to static_lib1.
The original problem occurred because executable called find_package for locating static_lib1, which then attempted to fill a variable like STATIC_LIB1_LIBRARY with the paths to the library files by calling find_library. executable then consumes the content of that variable in a target_link_libraries(executable ${STATIC_LIB1_LIBRARY}) call. The problem here is, since those library files only get generated as part of the build, that call to find_library will not be able to find anything.
Building executable needs to support two scenarios here:
Building standalone, where a pre-compiled version of static_lib1 is located somewhere on the disc.
Building from main_folder, where both executable and static_lib1 are part of the same build.
The approach from the question supports scenario 1, but not scenario 2.
Instead of using using a variable to communicate a dependency between the two builds, use a target. The CMakeLists.txt for static_lib1 likely creates a library target like add_library(static_lib1 [...]). In executable we now simply do target_link_libraries(executable PUBLIC static_lib1). This is sufficient to support scenario 2.
To also allow for scenario 1 at the same time, we look at the call to find_package(static_lib1) in the CMakeLists.txt for executable. Instead of providing a variable like before, this call now needs to provide a target static_lib1 for consumption.
So we adapt the find script for static_lib1 to the following behavior:
If a target static_lib1 already exists, there's nothing to be done and the find script can just return (this is scenario 2).
Otherwise, we call find_library to locate the library file on disc (as before in the original approach) and then create a new imported target: add_library(static_lib1 STATIC IMPORTED). We then configure all relevant properties of the static library to that target. For instance, to add the location of the library file, we could do
set_target_properties(static_lib1 PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION ${STATIC_LIB1_LIBRARY}
)
To support multi-config generators like MSVC, instead of setting IMPORTED_LOCATION and IMPORTED_LINK_INTERFACE_LANGUAGES, you will want to set the configuration specific properties like IMPORTED_LOCATION_DEBUG and IMPORTED_LOCATION_RELEASE instead. Since this can get quite tedious to do manually, you can have CMake generate this information (and a bunch of other convenient stuff) for you in a package script. The find mechanism for package scripts works slightly different under the hood, but the code in the CMakeLists.txt for executable will look just the same, a simple call to find_package(static_lib1). The main difference is that this call will then not dispatch to a hand-written find script, but to a package script that was automatically generated by CMake as part of the build process of static_lib1.
I guess I will leave this answer for posterity since only recently I have searched for a solution to this problem and found out that...
Since CMake 3.24 it is possible!
It is possible to override subsequent calls to find_package() with FetchContent_Declare() flag OVERRIDE_FIND_PACKAGE.
Your
add_subdirectory("path/to/static_lib1")
call has to be replaced in main_folder/CMakeLists.txt with:
include(FetchContent)
FetchContent_Declare(
static_lib1
SOURCE_DIR "path/to/static_lib1"
OVERRIDE_FIND_PACKAGE
)
Any calls to find_package(static_lib1) will call FetchContent_MakeAvailable() for you, virtually making it identical to add_subdirectory() call.
You can read more about OVERRIDE_FIND_PACKAGE in CMake documentation.