60

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" )
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Paul
  • 2,474
  • 7
  • 33
  • 48

4 Answers4

88

Quote the variable as you pass it to the macro:

FindLibs("${LIBRARY_NAMES_LIST}")
rafalcieslak
  • 915
  • 1
  • 12
  • 25
Jack Kelly
  • 18,264
  • 2
  • 56
  • 81
  • 1
    it works. why we should quote the ${LIBRARY_NAMES_LIST}? – nobody0day Nov 27 '18 at 03:10
  • 4
    Because the macro takes a single argument, and if you do not quote it and the variable expands to multiple words, they will be passed as multiple arguments to the macro. – Jack Kelly Nov 27 '18 at 05:36
18

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 :-)

starball
  • 20,030
  • 7
  • 43
  • 238
mchiasson
  • 2,452
  • 25
  • 27
  • 1
    and the line `cmake_parse_arguments( _FINDLIBS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN} )` shows that Kitware is also having the same struggle with regard to passing lists to a function or macro. :-) – mchiasson May 30 '15 at 14:33
9

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).

msturdy
  • 10,479
  • 11
  • 41
  • 52
tamas.kenez
  • 7,301
  • 4
  • 24
  • 34
  • Just a note: using `${ARGN}` doesn't protect the function from being used without any parameters like this: `FindLibs()`, but it can be easily fixed by doing `list(LENGTH ARGN _ARG_COUNT)` and by making sure that your returned variable `_ARG_COUNT` is greater than zero, and issue a fatal error message if it is not. That is entirely optional, but it is going to be another major difference between the two solution that cmake developers need to be aware. – mchiasson May 30 '15 at 15:09
2

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.

John
  • 7,301
  • 2
  • 16
  • 23
  • 2
    Careful: `${ARGV}` holds all of the arguments provided by the macro, including the ones already defined. For example, if you had this macro: `macro( FindLibs SomeVar1 SomeVar2 )` And use it like this: `FindLibs( "value1" "value2" zlib png jpeg)`, then `${ARGV}` would hold `value1;value2;zlib;png;jpeg`, which may not be what the user would want. If the user only wants to get `"zlib;png;jpeg"`, then the right variable to use is `${ARGN}` instead of `${ARGV}` In the given question, using `${ARGV}` is fine only because it it is the only arguments being received. – mchiasson May 30 '15 at 15:00
  • Also, using `${ARGV}` or `${ARGN}` doesn't protect the function from being used without any parameters like this: `FindLibs()`, but it can be easily fixed by doing `list(LENGTH ARGN _ARG_COUNT)` and make sure `_ARG_COUNT` is greater than zero. – mchiasson May 30 '15 at 15:07