CMAKE : set only if is not a repetition - cmake

I'm using a global variable in cmake using CACHE STRING FORCE trick..
I want this simple test case to work messaging in the end only "dudee" and not "dudeedudee:
cmake_minimum_required(VERSION 2.8.10)
set(DUDEE "dudee")
set(LIB ${DUDEE} CACHE STRING "Description" FORCE)
set(LIB ${LIB} ${DUDEE} CACHE STRING "Description" FORCE)
message(${LIB})
Little explanation: Ok, I know that it is not nice to use global variable but cmake is kind of hell but I cannot track down the scope so PARENT can not work and I have a nice working example in which I set dependencies between different cmake modules and application that I wrote with global variable and it works.
But.. sometimes it happens that some modules call each other and each one is adding the dependencies.. a simple example:
application XXX uses the modules VideoReader, BlobDetector, VideoViewer, BlobTracker. All of those modules import the OpenCV libs dependecies in this way:
set(LIB ${LIB} ${OpenCV_LIBS} CACHE STRING "Description" FORCE)
and in the end I have the opencv libs a lot of time repeated in the ${LIB} variable

In the context of a list of libraries to be used in target_link_libraries, then duplicates shouldn't normally cause any real problems.
However, to remove the duplicates, you should be able to use list(REMOVE_DUPLICATES LIB).

Related

is it possible to make target shared library file name variable according to environment variable in cmake?

I'm not familar with cmake but in CMakeLists.txt we set the target shared library name like this.
add_library( mylib SHARED ${source_list} )
This generates libmylib.so and other settings in CMakeLists.txt are defined for mylib like
about the mylib
and also we can use shell environment variable to do some selective settings like
target_compile_definitions( mylib PRIVATE -DQQQ -D... )
Also it is possible to use shell environment variable to do some selective things.
if(defined env{MYVAR})
set(CMAKE_C_FLAGS "-g -DXYZ")
else()
set(CMAKE_C_FLAGS "-DXYZ")
endif()
I would be happy if I could set the target shared library name as a variable according to the environment variable and use that selected name variable as the shared library name in all other settings. In other words, is it possible to do things like below?
if (defined ENV{FOR_QEMU})
set_name(target_name "simlib_qemu")
else ()
set_name(target_name "simlib")
endif ()
add_library(target_name SHARED ${source_list} )
target_compile_definitions( target_name PRIVATE -DQQQ -D... )
...
You can set the output name of a target to anything you like via:
set_target_properties(target_name PROPERTIES OUTPUT_NAME "whatever")
Then instead of libtarget_name.so, you'll get libwhatever.so. You would continue to refer to the target as target_name in your CMakeLists.txt.
However, since this will only work during configure time anyway, I strongly urge you to use a normal CMake variable instead. You may initialize it from the environment if it is not set, like so:
option(FOR_QEMU "Enable if building with Qemu support" "$ENV{FOR_QEMU}")
add_library(simlib SHARED ${source_list})
target_compile_definitions(simlib PRIVATE -DQQQ -D...)
if (FOR_QEMU)
set_target_properties(target_name PROPERTIES OUTPUT_NAME "simlib_qemu")
endif ()
This way, the CMake variable FOR_QEMU is the de-facto control and it is initialized on the first execution if the matching env-var is set. It will also appear with documentation in the cache, so other developers may query the build system directly for all its configuration points. Bear in mind: CMake is not Make and reading from the environment on every configure is a surprising behavior and generally bad practice.

CMake target collision in multi module project

At the beginning I had a bunch of CMake projects handled separately: each one had its own target for generating documentation called doc. Now I need to wrap all these projects with a super-project: the problem is that super-project compilation fails complaining that exist multiple targets with the same name doc.
The simple solution I thought is to prepend each doc target with the name of the project, but it does not satisfy me.
I would like not to have to use make projectX_doc when compiling a single sub-project and to have a global target make doc for generating the documentation of all projects when compiling super-project.
Are my requests possible? Is there any mechanism to handle target collision?
well each subproject could verify if there are inside a super project with:
if("^${CMAKE_SOURCE_DIR}$" STREQUAL "^${PROJECT_SOURCE_DIR}$")
set(IS_SUBPROJECT FALSE)
else()
set(IS_SUBPROJECT TRUE)
endif()
Thus in your projects CMakeLists.txt you can do:
if (NOT IS_SUBPROJECT)
set(DOC_TGT doc)
else()
set(DOC_TGT ${PROJECT_NAME}_doc)
endif()
add_custom_target(${DOC_TGT} EXCLUDE_FROM_ALL ...
note: you can merge both snippets to avoid IS_SUBPROJECT variable
In your super project CMakeLists.txt:
add_custom_target(doc EXCLUDE_FROM_ALL
DEPENDS projectX_doc projectY_doc...
So when configuring/building each sub project standalone you have
make doc otherwise when you are in your super project target doc become a meta target...
note: You can also use this trick to modify default options etc...
e.g. gflags:
https://github.com/gflags/gflags/blob/master/CMakeLists.txt#L126
https://github.com/gflags/gflags/blob/master/cmake/utils.cmake#L61
https://github.com/gflags/gflags/blob/master/CMakeLists.txt#L163

Using a CMake cache variable "before" it is defined

CMake cache variables can be set from virtually everywhere (see here #Florian's What's the CMake syntax to set and use variables?). I was under the assumption that the set value is visible everywhere, even to CMake lists parsed before, but this isn't the case.
Use case
Module A uses ${CMAKE_MYDEF}.
Module B sets the cache variable CMAKE_MYDEF.
add_subdirectory(A) is called before add_subdirectory(B).
Short example showing the behavior
cmake_minimum_required(VERSION 3.7)
project(test)
add_executable(EXEC test.cpp)
target_compile_definitions(EXEC PRIVATE MYDEF=${CMAKE_MYDEF})
set(CMAKE_MYDEF "MyValue" CACHE STRING "")
Questions
How can I make sure CMAKE_MYDEF has the desired value regardless the order I add module A and module B?
Are there any ways to ensure the CMake configuration step is re-run twice or, if applicable, as long as the cache variables get changed? (This isn't probably a clean solution, but since I'm working with legacy code not everything can be done beautifully.)
Are there alternatives to cache variables to achieve the same result without re-running the CMake configuration by hand?
Is it possible to set compiler definitions in the generation phase (i.e. when all CMake cache variables are known and set)? Using some kind of generator expressions?
Edit: Short example solution
Following #Florian's answer, here the adapted example showing the solution:
cmake_minimum_required(VERSION 3.7)
project(test)
add_executable(EXEC test.cpp)
target_link_libraries(EXEC MyOtherLib)
add_library(MyOtherLib INTERFACE)
set(CMAKE_MYDEF "MyValue" CACHE STRING "")
target_compile_definitions(MyOtherLib INTERFACE MYDEF=${CMAKE_MYDEF})
Yes, I'm fully with #Tsyvarev's answer that CMake's parser works sequentially. So variables - even cached ones - or generator expressions - that can't read variables - are no good here.
I just wanted to add the possibilities you have using target and directory properties depending on the dependencies between A and B:
When A depends on B with e.g.
target_link_libraries(A PUBLIC B)
then a simple
target_compile_definitions(B PUBLIC MYDEF=SOME_DEF)
would propagate the necessary define to A.
When B depends an A and A is already known than it would be
target_link_libraries(B PUBLIC A)
target_compile_definitions(A PUBLIC MYDEF=SOME_OTHER_DEF)
If you're working with sub-directories I would recommend putting the definition in the root CMakeLists.txt globally:
add_definitions(-DMYDEF=GLOBAL_DEF)
Finally the full variant with sub-directories letting B decide what to do:
CMakeLists.txt
cmake_minimum_required(VERSION 3.7)
project(test)
add_subdirectory(A)
add_subdirectory(B)
A\CMakeLists.txt
file(WRITE a.cpp [=[
#include <iostream>
#ifndef MYDEF
#define MYDEF "Hello from A!"
#endif
void a()
{
std::cout << MYDEF << std::endl;
}
]=])
add_library(A a.cpp)
B\CMakeLists.txt
file(WRITE b.cpp [=[
void a();
void main()
{
a();
}
]=])
add_executable(B b.cpp)
target_link_libraries(B A)
if (TARGET "A")
target_compile_definitions(A PRIVATE MYDEF="Hello from B!")
else()
set_property(
DIRECTORY ".."
APPEND PROPERTY
COMPILE_DEFINITIONS "MYDEF=\"Hello from Global!\""
)
endif()
Reference
Is Cmake set variable recursive?
CMake processes scripts sequentially, starting from top-level CMakeLists.txt and executing its lines one by one.
So, if read variable before assigning it, you will get nothing. The only specific of CACHE variable in that scenario is possibility for that variable to be assigned on previous cmake invocation.
Needs for using a variable before its assigning taking a place usually signals about bad design. In many situations (even with legacy code), design can be fixed gracefully.
Forcing CMake to reconfigure the project can be accomplished e.g. by touching current script:
to force a re-configure, one could "cmake -E touch"
the CMAKE_CURRENT_LIST_FILE, somehow during target building
or some such.

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 generator expression TARGET_PROPERTY of external project

I need to use a property from an external project. So far I'm successfully doing that this way:
ExternalProject_Add(
some_ep_name
...
)
# Get source dir
ExternalProject_Get_Property(some_ep_name SOURCE_DIR)
# Set source dir value into less generically named variable
set(SOME_EP_NAME_SOURCE_DIR "${SOURCE_DIR}")
This works, but it seems unnecessarily verbose, and it annoys me a little. I was hoping I could use a generator expression, like so:
"$<TARGET_PROPERTY:some_ep_name,SOURCE_DIR>"
But it seems like this doesn't work. Before I give up, I wanted to check if I was doing something wrong or if anyone knows a better way.
All "properties" of ExternalProject are known at configuration time. So they don't require support of generator expressions, which main intention is usage for values not known at configuration time (but known at build time).
If you found "unnecessarily verbose" having several lines of code for save external project' property into the variable, you may create a macro/function for incorporate all these lines. Then calling the macro/function will use only single line of code:
function(ExternalProject_Property_to_Var VAR eproject prop)
ExternalProject_Get_Property(${eproject} ${eprop})
set(${VAR} ${${eprop}} PARENT_SCOPE)
endfunction()
...
ExternalProject_Property_to_Var(SOME_EP_NAME_SOURCE_DIR some_ep_name SOURCE_DIR)