How can I understand when something is a variable or a value? - cmake

Not sure how to perfectly word this from the title, but I am new to CMake and slowly progressing through the online tutorial.
I am up to Step 4 and sometimes find it confusing when mixing passed values that in my eyes are strings, and thus in all programming languages I expect them to have quotation marks or some sort around them. However sometimes I create new targets with the same names. I will elaborate with an example. I reworded some things from the tutorial to make it a bit more clear for me to see what they actually do.
In the root CMakeLists.txt I have this file,
cmake_minimum_required(VERSION 3.10)
project(My_Project VERSION 1.0)
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
option(USE_MYMATH "Use tutorial provided math implementation" TRUE)
configure_file(src/sqrt.h.in src/sqrt.h)
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
endif()
add_executable(compute_square_root src/sqrt.cxx)
target_link_libraries(compute_square_root PUBLIC ${EXTRA_LIBS} tutorial_compiler_flags)
target_include_directories(compute_square_root PUBLIC "${PROJECT_BINARY_DIR}/src")
Inside of MathFunctions I have
add_library(MathFunctions mysqrt.cxx)
target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
Here is where the confusion can come from. Notice that in
add_subdirectory(MathFunctions)
MathFunctions is kind of treated as a string in my eyes, because it is now looking for the directory current_location/MathFunctions. However inside of of the MathFunctions CMakeLists.txt it now creates a target with the exact same spelling from the line "add_library(MathFunctions mysqrt.cxx)", this is then immediately referenced afterwards from the "target_include_directories(MathFunctions, ...".
Here, target_include_directories is referring to the target MathFunctions we just created. Now, when we leave that CMakeLists.txt we now have another line "list(APPEND EXTRA_LIBS MathFunctions)". Now I some confusion, like, is this MathFunctions referring to the target we just made? Is it a string called "MathFunctions"? In the documentation for target_link_libraries it says that it has to be a target created by add_library so I assume it is referring to the previous add_library(MathFunctions ...) call. I find the scoping weird here too, since we are referring to something that was made from a child, inside a different call.
Do we have certain rules in CMake for this kind of behaviour? THanks

All command parameters are treated as strings in cmake. Parameters are separated by whitespace unless quoted. The exact effect of a parameter depends on the command.
The following commands have the same effect:
add_subdirectory(MathFunctions)
add_subdirectory("MathFunctions")
In the case of add_library the first parameter is treated as the target name. CMake internally keeps track of targets and stores several pieces of information for them. The target name MathFunctions is entirely unrelated to the name of the subdirectory added via add_subdirectory; you could rename the directory to FooBar and use add_subdirectory(FooBar) and nothing would change.
There are several commands you pass the target name to to modify the properties of the cmake target as well as commands that treat the name of cmake targets specially e.g.:
target_include_directories: the target to modify the [INTERFACE_]INCLUDE_DIRECTORIES property for is passed
target_link_directories: the target to modify the [INTERFACE_]LINK_DIRECTORIES property for is passed
set_target_properties: One or more targets to set properties for are passed
target_link_libraries: The cmake target linked to is passed. Furthermore cmake library targets may be specified as libraries to be linked to the target.
add_test: If you use the name of a cmake target in the COMMAND part, the test logic uses the path to the target instead.
...
As for scope:
Variable values you write are actually set for the current scope only, but reading variables cmake looks into ancestor scopes, unless a variable is found in the current scope. CMake targets are visible in the whole cmake project though from the point of the parsing of the command creating the target(add_library, add_executable, add_custom_target): cmake targets are "global objects". (Exceptions exist for imported libs and alias targets, but that's probably nothing relevant to you right now.)

Related

Expand variable name containing a generator expression in cmake

In the build process, I set directories where I gather the build output of different sub-projects. The directories are set as :
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_LIST_DIR}/../build/bin/debug" )
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_LIST_DIR}/../build/bin/release" )
Now, I'd like to copy some files (a directory of qt plugins) to that directory dependent on the configuration which it is built for.
I tried:
# copy qt plugins
add_custom_command( TARGET mytarget POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${QT_DIR}/../../../plugins"
"${$<UPPER_CASE:CMAKE_RUNTIME_OUTPUT_DIRECTORY_$<CONFIG> >}/plugins"
COMMAND_EXPAND_LISTS)
thus, I try to build a string that equals the variable name and then try to expand that as described here: CMake interpret string as variable. In other words: I would like to have a generator expression that evaluates to the content of CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG or CMAKE_RUNTIME_OUTPUT_DIRECTOR_RELEASE dependent on the current build configuration.
However running cmake with the statement above results in an error:
"CMakeLists.txt:112: error: Syntax error in cmake code at [..] when parsing string ${$<UPPER_CASE:CMAKE_RUNTIME_OUTPUT_DIRECTORY_$<CONFIG> >}/plugins Invalid character ('<') in a variable name: '$'
So my question is, how can I use a generator-expression to access the corresponding variable? (Bonus question: is there another/better way to achieve the same goal?)
So my question is, how can I use a generator-expression to access the corresponding variable?
You cannot. There is currently (CMake <=3.23) no way to expand a variable whose name is determined by the value of a generator expression.
Bonus question: is there another/better way to achieve the same goal?
Yes, and you are almost there! You can use $<TARGET_FILE_DIR:...>:
add_custom_command(
TARGET mytarget POST_BUILD
COMMAND
${CMAKE_COMMAND} -E copy_directory
"${QT_DIR}/../../../plugins"
"$<TARGET_FILE_DIR:mytarget>/plugins"
VERBATIM
)
This works because TARGET_FILE_DIR evaluates to the actual directory containing the executable or library file for mytarget, no matter the active configuration, property values, etc.
Docs: https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#genex:TARGET_FILE_DIR
CMAKE_RUNTIME_OUTPUT_DIRECTORY_<CONFIG> is already relative to the binary directory so you should not try to compute the binary directory in its definition. Also, it supports generator expressions. Thus, the following will be much more robust:
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "bin/$<LOWER_CASE:$<CONFIG>>"
CACHE STRING "Common output directory for runtime artifacts")
This has a bunch of concrete benefits:
No need to set CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG or CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE
This will work for MinSizeRel and RelWithDebInfo, plus any custom configurations one might add down the line.
Since it's defined as a cache variable, it can be overridden for debugging / working around name clashes, etc.
A bit more context for (3): most CMAKE_* variables are intended to be either read-only or user-configurable (i.e. at the command line, from the GUI, etc.). Overriding their defaults via set(CACHE) is a polite compromise. A notable exception to this rule is the collection of Qt codegen flags (CMAKE_AUTO{MOC,RCC,UIC}). These must typically be set for the build to produce usable binaries.

CMake: How to specify a different link script for each target

I have multiple targets that are being made in a CMake project. Each target has a different linkscript (LD file). How do I write the CMakeLists.txt file to make this happen? This is for an embedded project with C/C++/ASM files.
This is what I have so far. The problem is that LINKER_SCRIPT is defined globally and not per-target.
# add alpha target
add_executable(alpha.elf ${SOURCES})
target_compile_definitions(alpha.elf PUBLIC -DALPHA_DEFINED)
set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/Linker/alpha.ld)
# add beta target
add_executable(beta.elf ${SOURCES})
target_compile_definitions(beta.elf PUBLIC -DBETA_DEFINED)
set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/Linker/beta.ld)
set(CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -T ${LINKER_SCRIPT}")
The problem is that LINKER_SCRIPT gets overwritten and the last definition is the one used. How can I make this work?
I have tried to define per-target variable using the following, however the output is not as expected. The file gets compiled, however things are not where they should be. For example, the generated HEX file does not start at 0x08000000 which is where it should be defined per the LD file.
set_target_properties(alpha.elf PROPERTIES
CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Map=${PROJECT_BINARY_DIR}/alpha.elf.map -T ${CMAKE_SOURCE_DIR}/Linker/alpha.ld")
The variable LINKER_SCRIPT is just being over-ridden the second time you set it to a value. It doesn't have global scope but sub-directory / function scope. But like any variable it takes on its latest value.
CMAKE_EXE_LINKER_FLAGS is being used for both targets in this case. Splitting this into separate sub-directories may work but I've never tried it.
There are no per-target variables that I am aware of. set_target_properties is used to set properties. Some properties are known to CMake, but the property CMAKE_EXE_LINKER_FLAGS is not one of them. It should just be ignored when generating the build files.
Try using target_link_options to set per-target property LINK_OPTIONS which is known and the option will show up when linking the executable.
For example target_link_options(alpha.elf PRIVATE -T${CMAKE_SOURCE_DIR}/Linker/alpha.ld). I haven't used it with options that have a space in them so that might be a problem.
To re-link if the linker script changes then refer to this answer:
https://stackoverflow.com/a/42138375/1028434

CMake: show all modified variables

I would like to have a command or option to list all the modified cache variables of the current build configuration. While cmake -L[AH] is nice, it is also quite overwhelming and doesn't show which are non-default values.
There seems to be a variable property MODIFIED that sounds exactly like what I'm looking for - but the documentation is not very reassuring:
Internal management property. Do not set or get.
This is an internal cache entry property managed by CMake to track interactive user modification of entries. Ignore it.
This question also didn't help: CMAKE: Print out all accessible variables in a script
There are so many ways you could change or initialize variables in CMake (command line, environment variables, script files, etc.) that you won't be able to cover them all.
I just came up with the following script that covers the command line switches. Put the following file in your CMake project's root folder and you get the modified variables printed:
PreLoad.cmake
set(_file "${CMAKE_BINARY_DIR}/UserModifiedVars.txt")
get_directory_property(_vars CACHE_VARIABLES)
list(FIND _vars "CMAKE_BACKWARDS_COMPATIBILITY" _idx)
if (_idx EQUAL -1)
list(REMOVE_ITEM _vars "CMAKE_COMMAND" "CMAKE_CPACK_COMMAND" "CMAKE_CTEST_COMMAND" "CMAKE_ROOT")
file(WRITE "${_file}" "${_vars}")
else()
file(READ "${_file}" _vars)
endif()
foreach(_var IN LISTS _vars)
message(STATUS "User modified ${_var} = ${${_var}}")
endforeach()
This will load before anything else and therefore can relatively easily identify the user modified variables and store them into a file for later reference.
The CMAKE_BACKWARDS_COMPATIBILITY is a cached variable set by CMake at the end of a configuration run and therefor is used here to identify an already configured CMake project.
Reference
CMake: In which Order are Files parsed (Cache, Toolchain, …)?

CMake: Function is callable while script, defined it, is no longer included

I have three folders in a location, say A,B and C. I have two cmake files in folder A: FindABC.cmake and UseABC.cmake. The former is for finding the libraries and the latter contains a function, say run_command(). CMakelists.txt in folder B and folder C contains the following lines:
find_package(ABC)
include(UseABC)
run_command()
It works as intended. Now If I comment find_package() and include() in CMakelists of folder C, as far as I know, Cmake should give an error telling unknown command - run_command(). But, the controls goes into the function and executes in unpredictable manner.
How come the control goes to the function when the include line is commented? The root CMakelists that lists the sub-directories does not have any find_package or include lines in it.
Edit:
UseABC.cmake:
set(ABC_COMPILE_DEBUG FALSE)
set(ABC_COMPILE_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/abc_gen")
message("USEABC1 - -> " ${ABC_COMPILE_OUTPUT_DIR})
function(run_command)
message("USEABC2 - File recurs -> " ${ABC_COMPILE_OUTPUT_DIR})
file(REMOVE_RECURSE "${ABC_COMPILE_OUTPUT_DIR}")
file(MAKE_DIRECTORY "${ABC_COMPILE_OUTPUT_DIR}")
add_custom_command() #command to be executed
endfunction()
Here, When nothing is commented(find_package and include is not commented in any CMakelists.txt), I get the correct path for the two messages I print.
When I comment include(UseABC) in the second CMakelists.txt, the configuration fails, the first message is not at all printed and the second message gets printed, but does not give the value of the variable. It also deletes all the files in Folder C (but the argument to REMOVE_RECURSE is empty).
If I correctly understand the situation, you have:
CMakeLists.txt:
add_subdirectory(B)
add_subdirectory(C)
B/CMakeLists.txt:
find_package(ABC)
include(UseABC)
In that case run_command function, defined in UseABC.cmake, is accessible in C/CMakeLists.txt, though this script doesn't define it.
In CMake function definitions are global.
By opposite, variable definitions are local to the scope, where they are defined. (Until variables are cached ones, in that case they have global visibility).
That is, variable ABC_COMPILE_DEBUG defined in UseABC.cmake is accessible in
UseABC.cmake script
B/CMakeLists.txt script, because it includes UseABC.cmake one, and include() command doesn't introduce a scope
but it is inaccessible in
CMakeLists.txt script, because add_subdirectory(B) does introduce a scope
C/CMakeLists.txt script
More details about variable's visibility can be found in documentation.

How to create a C #define for a certain target using CMake?

I feel a little stupid right now. After recently converting a few smaller projects to use CMake, I decided to also get rid of a few "Platform_Config.h" files. These files contain a few preprocessing directives like #define USE_NEW_CACHE and control compilation.
How would I 'convert' these defines to be controlled with CMake? Ideally by using these "cache" variables the user can easily edit.
There are two options. You can use the add_definitions method to pass defines as compiler flags: E.g. somewhere in your projects cmakelists.txt:
add_definitions( -DUSE_NEW_CACHE )
CMake will make sure the -D prefix is converted to the right flag for your compiler (/D for msvc and -D for gcc).
Alternatively, check out configure_file. It is more complex, but may be better suited to your original approach with a Platform_Config file.
You can create an input-file, similar to your original Platform_Config.h and add "#cmakedefine" lines to it.
Let's call in Platform_Config.h.in:
// In Platform_Config.h.in
#cmakedefine USE_NEW_CACHE
// end of Platform_Config.h.in
When then running
configure_file( ${CMAKE_SOURCE_DIR}/Platform_Config.h.in ${CMAKE_BINARY_DIR}/common/Platform_Config.h )
it will generate a new Platform_Config file in your build-dir. Those variables in cmake which are also a cmakedefine will be present in the generated file, the other ones will be commented out or undefed.
Of course, you should make sure the actual, generated file is then correctly found when including it in your source files.
option command might provide what you are looking for.
use it with the COMPILE DEFINITIONS property on the target and i think you are done.
To set the property on the target, use the command set target properties
option(DEBUGPRINTS "Prints a lot of debug prints")
target(myProgram ...)
if(DEBUGPRINTS)
set_target_properties(myProgram PROPERTIES COMPILE_DEFINITIONS "DEBUGPRINTS=1")
endif()
edit:
The option i wrote in the example shows up as a checkbox in the CMake GUI.
In case you want to set defines per target: Since 2.8.11 you can use target_compile_definitions.
In earlier versions you probably don't want to use set_target_properties as is, since it overwrites any defines you set previously. Call get_target_property first instead, then merge with previous values. See add_target_definitions here.
Use target_compile_options. Do not quote your define or it not be detected as a define. CMake parses off the /define and adds the actual define to the DefineConstants section of the csproj, if there are quotes it will put the entire quoted string in the AdditionalOptions section of the csproj.
An example from one of my projects that uses generator expressions:
target_compile_options( ${LIBRARY_NAME} PRIVATE
$<${IS_ART_ITERATION_BUILD}:/define:ART_ITERATION_BUILD>
)
An example without generator expressions:
target_compile_options( ${LIBRARY_NAME} PRIVATE
/define:GRAPHICS_VULKAN
)