I am trying to write a macro which goes through a given list of libraries. However the message call in the macro prints only the first item of the list. What am I doing wrong here?
Code:
macro( FindLibs LIBRARY_NAMES_LIST )
message( "inside ${LIBRARY_NAMES_LIST}" )
endmacro()
set( LIBRARY_NAMES_LIST lib1 lib2 lib3)
message( "outside ${LIBRARY_NAMES_LIST}" )
FindLibs(${LIBRARY_NAMES_LIST})
Output:
message( "outside lib1 lib2 lib3" )
message( "inside lib1" )
Quote the variable as you pass it to the macro:
FindLibs("${LIBRARY_NAMES_LIST}")
The provided answers by others are correct. The best solution is indeed to provide the list in double quotes like this:
FindLibs( "${LIBRARY_NAMES_LIST}" )
But if you really don't want to force the user to use double quotes and also want to see the LIBRARY_NAMES_LIST argument in your macro declaration, here's how I would do it:
macro( FindLibs LIBRARY_NAMES_LIST )
set( _LIBRARY_NAMES_LIST ${LIBRARY_NAMES_LIST} ${ARGN} ) # Merge them together
message( "inside ${_LIBRARY_NAMES_LIST}" )
endmacro()
And it would be used like this (your expectation):
FindLibs( ${LIBRARY_NAMES_LIST} )
This is nice, because it will force the user to provide at least one library. Calling it like
FindLibs()
won't work. CMake will report the following error:
FindLibs Macro invoked with incorrect arguments for macro named: FindLibs
If you are using CMake 2.8.3 or newer, another option is to use the CmakeParseArguments, but it will require you to put a keyword argument in front of your list. But this technique is probably the easiest way to manage more than one list, and provides high flexibility. For that reason, it is very handy to know. It is also my preferred way of doing it. Here's how to do it:
include( CMakeParseArguments )
macro( FindLibs )
set( _OPTIONS_ARGS )
set( _ONE_VALUE_ARGS )
set( _MULTI_VALUE_ARGS NAMES DEPENDS )
cmake_parse_arguments( _FINDLIBS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN} )
# Mandatory
if( _FINDLIBS_NAMES )
message( STATUS "inside NAMES=${_FINDLIBS_NAMES}" )
else()
message( FATAL_ERROR "FindLibs: 'NAMES' argument required." )
endif()
# Optional
if( _FINDLIBS_DEPENDS )
message( STATUS "inside DEPENDS=${_FINDLIBS_DEPENDS}" )
endif()
endmacro()
Unfortunately, you have to do your argument enforcement yourself, but at least it gives you the option to chose which arguments are mandatory, and which ones are not (DEPENDS is optional in my example above).
And it would be used like this:
FindLibs( NAMES ${LIBRARY_NAMES_LIST} )
FindLibs( NAMES ${LIBRARY_NAMES_LIST} DEPENDS ${LIBRARY_DEPENDENCY_LIST} )
# You can change the order
FindLibs( DEPENDS ${LIBRARY_DEPENDENCY_LIST} NAMES ${LIBRARY_NAMES_LIST} )
# You can even build your lists on the fly
FindLibs(
NAMES
zlib
png
jpeg
DEPENDS
otherProject1
otherProject2
)
And if I do this:
FindLibs()
# or that:
FindLibs( DEPENDS ${LIBRARY_DEPENDENCY_LIST} )
Then I would get my custom error message:
error: FindLibs: 'NAMES' argument required.
And here the link to the CMakeParseArguments documentation if you want to learn more about it.
I hope it helps :-)
Your macro should look like this:
macro(FindLibs list_var_name)
message( "inside ${${list_var_name}}" )
endmacro()
and call the macro like this:
FindLibs(LIBRARY_NAMES_LIST)
So inside the macro: ${list_var_name} = LIBRARY_NAMES_LIST, ${${list_var_name}} = ${LIBRARY_NAMES_LIST} = your list.
Another solution could be (somewhat more obscure):
macro(FindLibs)
message( "inside ${ARGN}" )
endmacro()
FindLibs(${LIBRARY_NAMES_LIST})
In the first solution you pass only the name of the list-variable to the macro (one argument). In the second solution you expand the list before calling the macro and pass N parameters (N = length of the list).
According to (almost) current CMake documentation, the ${ARGV} symbol should expand to the entire list of arguments. This should keep things simpler at the calling location.
Related
In CMake, given a list of arguments, how can we check if an argument represents a reserved word?
For a concrete example, consider this wrapper around target_link_libraries(), which modifies library names but not reserved keywords like INTERFACE:
macro(modify_library_names LIB_NAMES TO_APPEND OUTPUT_LIST)
foreach(item ${LIB_NAMES})
if (${item} MATCHES "INTERFACE|PUBLIC|PRIVATE")
list(APPEND ${OUTPUT_LIST} "${item}")
else()
list(APPEND ${OUTPUT_LIST} "${item}${TO_APPEND}")
endif()
endforeach()
endmacro()
function(target_link_libraries_multiplexed BASE-NAME)
set(LINK_ARG_SOFT "")
modify_library_names("${ARGN}" "_soft" LINK_ARG_SOFT)
set(LINK_ARG_HARD "")
modify_library_names("${ARGN}" "_hard" LINK_ARG_HARD)
target_link_libraries(${BASE-NAME}_soft
${LINK_ARG_SOFT}
)
target_link_libraries(${BASE-NAME}_hard
${LINK_ARG_HARD}
)
endfunction()
# Usage:
target_link_libraries_multiplexed(util
PUBLIC
math # links util_soft against math_soft and util_hard against math_hard
)
Is there something like if (RESERVED(item)) that would let us get rid of the hardcoded keyword list on line 3?
For users that are using cmake 3.15 or later and are also using Ninja as a generator, I want to set the new JOB_POOL argument to some large add_custom_command() blocks. For other users, I want to keep my add_custom_command() the same (no JOB_POOL).
In earlier steps, I check the version and the generator and set ${JOB_POOLS} and I also set a variable such that users who should use a pool will see (something like):
For historical reasons, I leave this here, although #Tsyvarev
points out that this is the source of my problem!
The double-quotes are NOT wanted here!
set(USE_POOL "JOB_POOL pool_A")
Users that are not using a pool will not have that variable set.
Now how to leverage that variable in my custom command...?
1.) Generator expressions don't work, just including the text with the previous line...
add_custom_command(
...
$<USE_POOL>
)
2.) I can't seem to simply place the variable in the command, again just including the variable contents on the previous line. For example, when ${JOB_POOL} is set to the string "JOB_POOL pool_A", this code...
For historical reasons, I leave this here, although #Tsyvarev
points out that this is the source of my problem!
Don't use a STRING! No double-quotes!
add_custom_command(
OUTPUT foo
DEPENDS bar
# Comment line here...
${USE_POOL}
COMMAND
${CMAKE_COMMAND} -E ...
)
gives this error...
ninja: error: '../path/to/src/dir/JOB_POOL pool_A', needed by 'path/to/src/dir/foo', missing and no known rule to make it
It simply considers the ${JOB_POOL} string to be another dependency!
3.) I can't use the "APPEND" feature of add_custom_command(). It's just ignored...
if (${USE_POOL})
add_custom_command(
...
APPEND
JOB_POOL pool_A
)
endif()
The only thing that seems to work is to put an "if" around my
entire command, which offends my sensibility since I don't like to duplicate so much code...
if(${USE_POOL})
add_custom_command(
...many lines...
JOB_POOL pool_A
)
else()
add_custom_command(
...many lines...
)
endif()
Do you have a better idea...?
Here's a standalone example for #tsyvarev:
cmake_minimum_required(VERSION 3.15)
project foo
set_property(GLOBAL PROPERTY JOB_POOLS pool_A=2)
# For historical reasons, I leave this here, although #Tsyvarev
# points out that this is the source of my problem!
# Don't use a STRING! No double-quotes!
set(USE_POOL "JOB_POOL pool_A")
add_custom_command(
OUTPUT foo.out
DEPENDS foo.in
${USE_POOL}
COMMAND
${CMAKE_COMMAND} -E copy foo.in foo.out
COMMENT "Converting foo.in -> foo.out"
VERBATIM
)
add_custom_target(foo-out
DEPENDS foo.out
)
% cmake -GNinja .
% ninja foo-out
ninja: error: 'JOB_POOL pool_A', needed by 'foo.out', missing and no known rule to make it
It considers the string to be a dependency... If I move the USE_POOL to after the comment, it considers it part of the comment... If I move it to after the command, it considers it part of the command...
Your JOB_POOL option serves for the user's choice. You may create another variable which contains sequence of related parameters for add_custom_command:
if(JOB_POOL)
set(JOB_POOL_PARAMS JOB_POOL pool_A) # Will add 'JOB_POOL pool_A' sequence of params
else()
set(JOB_POOL_PARAMS) # Will add nothing
endif()
Then use new variable directly in add_custom_command call:
add_custom_command(
...
${JOB_POOL_PARAMS} # Will expand to additional parameters when needed
)
How do I pass project specific variables down to subdirectory? I wonder if there is a "official" way of doing this:
# (CMAKE_BUILD_TYPE is one of None, Debug, Release, RelWithDebInfo)
# ...
# set specific build type for 'my_library'
set( CMAKE_BUILD_TYPE_COPY "${CMAKE_BUILD_TYPE}" )
set( CMAKE_BUILD_TYPE "Release" )
add_subdirectory( "my_library" )
set( CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE_COPY} )
# continue with original build type
# ...
The library/subdirectory my_library should always be buildt with type "Release", main project and other subdirectories should be buildt with type defined by configuration. I am not able to modify the CMakeLists.txtof my_library.
Answering the revised question in your comment (how can I pass different values), so values other then CMAKE_BUILD_TYPE`:
There's no extra machinery for this. If the variable is project specific, you just set the variable:
set(MYLIB_SOME_OPTION OFF)
add_subdirectory(mylib)
If it's more general, you need to revert it:
set(BUILD_SHARED_LIBS_SAVED "${BUILD_SHARED_LIBS}")
set(BUILD_SHARED_LIBS OFF)
add_subdirectory(mylib)
set(BUILD_SHARED_LIBS "${BUILD_SHARED_LIBS_SAVED}")
Or you can put it into a function and you don't need to revert the variables you changed since functions has a scope.
CMake lists are essentially just semicolon-separated strings, but if you pass such a variable to a command, it does get expanded into multiple arguments - for example,
set(FLAGS f1 f2 f3)
# now FLAGS == 'f1;f2;f3'
add_custom_command(
...
COMMAND my_cmd ${FLAGS}
...
)
will correctly call my_cmd f1 f2 f3.
Now if I do this with
set_target_properties(
myTarget PROPERTIES
LINK_FLAGS "${LD_FLAGS}"
)
the expansion does not occur, and I end up with a single LD_FLAG that contains semicolons -- useless, instead of expanding it into a space-separated string.
Is there any way to make it so that when I pass a list to the LINK_FLAGS property (or any property that is), it gets expanded into multiple arguments rather than just one?
I don't think set_target_properties can do the expansion automatically, but you can use string (REPLACE ...) to expand a list into a space separated string:
string (REPLACE ";" " " LD_FLAGS_STR "${LD_FLAGS}")
set_target_properties(
myTarget PROPERTIES
LINK_FLAGS "${LD_FLAGS_STR}"
)
For using a cmake List as list, use
${LD_FLAG}
For using a cmake list as string, (i.e. list items are separated with ';'), use
"${LD_FLAG}"
So in your case, just remove "" should be sufficient.
The set_property command is designed for this
http://www.cmake.org/cmake/help/v3.0/command/set_property.html
set_property(TARGET tgt PROPERTY LINK_FLAGS foo bar)
I use it like strings
set(FLAGS " f1 f2 f3")
Note the space in the front, it allows you to concatenate other sets of flags.
For more complex projects, instead of if-elses you may also do a double expansion trick:
set(GCC_FLAGS " -Wl,--relax")
set(DIAB_FLAGS " -tPPCE500ES:cross")
set(MSVC_FLAGS " /RAINBOW_CRAP)
# ...
# ...LINUX_FLAGS, WINDOWS_FLAGS, etc...
set_target_properties(
myTarget PROPERTIES
LINK_FLAGS "${${COMPILER}_FLAGS} ${${SYSTEM}_FLAGS}"
)
# COMPILER and SYSTEM is set somewhere else, in toolchain files for example
separate_arguments(LD_FLAGS_AS_LIST NATIVE_COMMAND LD_FLAGS)
set_target_properties(
myTarget PROPERTIES
LINK_FLAGS "${LD_FLAGS_AS_LIST}"
)
As of CMake 3.12, you can use the list(JOIN <list> <glue> <output variable>) command described here:
set(FLAGS f1 f2 f3)
# now FLAGS == 'f1;f2;f3'
list(JOIN FLAGS " " FLAGS) # Note: Overwrites FLAGS variable
# now FLAGS == 'f1 f2 f3'
add_custom_command(
...
COMMAND my_cmd ${FLAGS}
...
)
This is a little more direct than using string(REPLACE ...).
In cmake 3.x there are a few approaches
The top level CMakeLists.txt file then looks something like this:
# The name of the included file could be anything,
# it doesn't have to be called CMakeLists.txt
include(foo/CMakeLists.txt)
include(bar/CMakeLists.txt)
add_executable(myApp ${myApp_SOURCES})
with the subdirectory files structured something like this:
list(APPEND myApp_SOURCES
${CMAKE_CURRENT_LIST_DIR}/foo.cpp
${CMAKE_CURRENT_LIST_DIR}/foo_p.cpp
)
Enhanced source file handling with target_sources()
I want to see what the current set of compiler definitions is in my CMake file. Ones automatically specified and the ones I'd added would be great. The COMPILE_DEFINITIONS macro doesn't appear to contain -- despite what the documentation says.
For example, in the below setup the message never includes GUI_BUILD
add_definitions( -DGUI_BUILD )
message( "COMPILE_DEFINITIONS = ${COMPILE_DEFINITIONS}" )
I don't need to see them in their final form, I just want a quick output to help verify that everything has been configured correctly.
You want to extract the COMPILE_DEFINITIONS property from the directory. E.g. use the following:
add_definitions( -DDebug )
get_directory_property( DirDefs DIRECTORY ${CMAKE_SOURCE_DIR} COMPILE_DEFINITIONS )
Then you can simply iterate over them, e.g.:
foreach( d ${DirDefs} )
message( STATUS "Found Define: " ${d} )
endforeach()
message( STATUS "DirDefs: " ${DirDefs} )
Note that definitions may also be associated with targets or source-files instead of directories. And note that they can differ between configurations. Depending on your requirements, you may need to check a large set of different properties.