CMakeMacroListOperations

From KitwarePublic
Revision as of 07:03, 16 January 2009 by Plowman (talk | contribs)
Jump to navigationJump to search

Back

CAUTION: The contents of this page may be obsolete
120px-Old finnish stop sign.svg.png

Lists are an important component of most CMakeLists.txt files. Lists are built automatically from arguments to commands and macros. Thus, we often build lists in variables by calling SET with more than one variable argument. Surprisingly, there are very few CMake commands to handle lists. Here is a compilation of a few basic list manipulation commands.

Update: Since CMake 2.4 most of the issues discussed here can be handled using the native CMake LIST command. However, these were very useful macros for handling lists in previous versions and they still are if you would like to add LIST support to your CMakeLists.txt files and for some reason you can't update your CMake installation. As such, I have maintained the original content as is, and provided a section at the bottom that makes use of the new CMake LIST command to perform the same functionality as the CAR, CDR, LIST_LENGTH, LIST_INDEX, and LIST_CONTAINS macros below. Note that there is a LIST_FILTER macro below that I have not attempted to write using the enhanced LIST command.

CAR and CDR

Our first operations we take from lisp, a programming language built entirely around lists. Two fundamental functions of lisp are car, which returns the first element of a list, and cdr, which returns a list minus the first element. Here we give implementations of car and cdr CMake macros. Although they are not as efficient as their counterparts in lisp, they can still be surprisingly useful.

MACRO(CAR var)
  SET(${var} ${ARGV1})
ENDMACRO(CAR)

MACRO(CDR var junk)
  SET(${var} ${ARGN})
ENDMACRO(CDR)

Here is a simple example of their use.

SET(MYLIST hello world foo bar)

CAR(car ${MYLIST})
CDR(cdr ${MYLIST})
MESSAGE("car: ${car}")
MESSAGE("cdr: ${cdr}")

The output of the command above is

car: hello
cdr: world;foo;bar

Note that the CAR and CDR macros (as well as the rest of the macros in this document) take advantage of CMake's calling convention of expanding lists into arguments. If you quote the list argument, you get a very different result. If you change the previous example to

CAR(car "${MYLIST}")
CDR(cdr "${MYLIST}")
MESSAGE("car: ${car}")
MESSAGE("cdr: ${cdr}")

you get

car: hello;world;foo;bar
cdr: 

LIST_LENGTH

Here is a macro to get the length of a list.

MACRO(LIST_LENGTH var)
  SET(entries)
  FOREACH(e ${ARGN})
    SET(entries "${entries}.")
  ENDFOREACH(e)
  STRING(LENGTH ${entries} ${var})
ENDMACRO(LIST_LENGTH)

If you use the LIST_LENGTH macro as follows

SET(MYLIST hello world foo bar)

LIST_LENGTH(length ${MYLIST})
MESSAGE("length: ${length}")

you get this output:

length: 4

LIST_INDEX

Sometimes you want to get a particular entry of a list. This macro lets you grab the entry in a list (indexed from 1).

MACRO(LIST_INDEX var index)
  SET(list . ${ARGN})
  FOREACH(i RANGE 1 ${index})
    CDR(list ${list})
  ENDFOREACH(i)
  CAR(${var} ${list})
ENDMACRO(LIST_INDEX)

If you use LIST_INDEX with the following commands

SET(MYLIST hello world foo bar)

LIST_INDEX(first 1 ${MYLIST})
LIST_INDEX(second 2 ${MYLIST})
LIST_INDEX(third 3 ${MYLIST})
LIST_INDEX(fourth 4 ${MYLIST})
MESSAGE("first: ${first}")
MESSAGE("second: ${second}")
MESSAGE("third: ${third}")
MESSAGE("fourth: ${fourth}")

you get this output:

first: hello
second: world
third: foo
fourth: bar

LIST_CONTAINS

Sometimes you just want to know if a list contains a particular entry.

MACRO(LIST_CONTAINS var value)
  SET(${var})
  FOREACH (value2 ${ARGN})
    IF (${value} STREQUAL ${value2})
      SET(${var} TRUE)
    ENDIF (${value} STREQUAL ${value2})
  ENDFOREACH (value2)
ENDMACRO(LIST_CONTAINS)

Here are some simple examples of using LIST_CONTAINS.

SET(MYLIST hello world foo bar)

LIST_CONTAINS(contains foo ${MYLIST})
IF (contains)
  MESSAGE("MYLIST contains foo")
ENDIF (contains)

LIST_CONTAINS(contains baz ${MYLIST})
IF (NOT contains)
  MESSAGE("MYLIST does not contain baz")
ENDIF (NOT contains)

The result of the previous examples is:

MYLIST contains foo
MYLIST does not contain baz

LIST_FILTER

# LIST_FILTER(<list> <regexp_var> [<regexp_var> ...]
#              [OUTPUT_VARIABLE <variable>])
# Removes items from <list> which do not match any of the specified
# regular expressions. An optional argument OUTPUT_VARIABLE
# specifies a variable in which to store the matched items instead of
# updating <list>
# As regular expressions can not be given to macros (see bug #5389), we pass
# variable names whose content is the regular expressions.
# Note that this macro requires PARSE_ARGUMENTS macro, available here:
# http://www.cmake.org/Wiki/CMakeMacroParseArguments
MACRO(LIST_FILTER)
  PARSE_ARGUMENTS(LIST_FILTER "OUTPUT_VARIABLE" "" ${ARGV})
  # Check arguments.
  LIST(LENGTH LIST_FILTER_DEFAULT_ARGS LIST_FILTER_default_length)
  IF(${LIST_FILTER_default_length} EQUAL 0)
    MESSAGE(FATAL_ERROR "LIST_FILTER: missing list variable.")
  ENDIF(${LIST_FILTER_default_length} EQUAL 0)
  IF(${LIST_FILTER_default_length} EQUAL 1)
    MESSAGE(FATAL_ERROR "LIST_FILTER: missing regular expression variable.")
  ENDIF(${LIST_FILTER_default_length} EQUAL 1)
  # Reset output variable
  IF(NOT LIST_FILTER_OUTPUT_VARIABLE)
    SET(LIST_FILTER_OUTPUT_VARIABLE "LIST_FILTER_internal_output")
  ENDIF(NOT LIST_FILTER_OUTPUT_VARIABLE)
  SET(${LIST_FILTER_OUTPUT_VARIABLE})
  # Extract input list from arguments
  LIST(GET LIST_FILTER_DEFAULT_ARGS 0 LIST_FILTER_input_list)
  LIST(REMOVE_AT LIST_FILTER_DEFAULT_ARGS 0)
  FOREACH(LIST_FILTER_item ${${LIST_FILTER_input_list}})
    FOREACH(LIST_FILTER_regexp_var ${LIST_FILTER_DEFAULT_ARGS})
      FOREACH(LIST_FILTER_regexp ${${LIST_FILTER_regexp_var}})
        IF(${LIST_FILTER_item} MATCHES ${LIST_FILTER_regexp})
          LIST(APPEND ${LIST_FILTER_OUTPUT_VARIABLE} ${LIST_FILTER_item})
        ENDIF(${LIST_FILTER_item} MATCHES ${LIST_FILTER_regexp})
      ENDFOREACH(LIST_FILTER_regexp ${${LIST_FILTER_regexp_var}})
    ENDFOREACH(LIST_FILTER_regexp_var)
  ENDFOREACH(LIST_FILTER_item)
  # If OUTPUT_VARIABLE is not specified, overwrite the input list.
  IF(${LIST_FILTER_OUTPUT_VARIABLE} STREQUAL "LIST_FILTER_internal_output")
    SET(${LIST_FILTER_input_list} ${${LIST_FILTER_OUTPUT_VARIABLE}})
  ENDIF(${LIST_FILTER_OUTPUT_VARIABLE} STREQUAL "LIST_FILTER_internal_output")
ENDMACRO(LIST_FILTER)

Here is a use-case of the two signatures:

SET(sources foo.c foo.cc foo.cpp foo.cxx foo.h foo.hh foo.hxx foo.hpp foo.hcc)
SET(re_header ".+\\.h$" ".+\\.hh$")
SET(re_inline ".+\\.hxx$" ".+\\.hpp$")
SET(re_impl ".+\\.c+" ".+\\.hcc$")
LIST_FILTER(sources re_header re_inline OUTPUT_VARIABLE headers)
LIST_FILTER(sources re_impl)
FOREACH(var sources headers)
  MESSAGE(STATUS "content of variable \"${var}\":")
  FOREACH(elt ${${var}})
    MESSAGE(STATUS "  ${elt}")
  ENDFOREACH(elt)
ENDFOREACH(var)

Then you get the following output:

-- content of variable "sources":
--   foo.c
--   foo.cc
--   foo.cpp
--   foo.cxx
--   foo.hcc
-- content of variable "headers":
--   foo.h
--   foo.hh
--   foo.hxx
--   foo.hpp

Note that LIST_FILTER requires the macro PARSE_ARGUMENTS.

Using CMake LIST Command

The following code is based on the macros presented above:

SET(MYLIST hello world foo bar)

CAR(car ${MYLIST})

CDR(cdr ${MYLIST})

LIST_LENGTH(length ${MYLIST})

LIST_INDEX(first 1 ${MYLIST})
LIST_INDEX(second 2 ${MYLIST})
LIST_INDEX(third 3 ${MYLIST})
LIST_INDEX(fourth 4 ${MYLIST})

LIST_CONTAINS(contains_foo foo ${MYLIST})
LIST_CONTAINS(contains_baz baz ${MYLIST})

The previous code could have been implemented using the CMake LIST command (CMake 2.4+) as shown below. Note, however, that the LIST(FIND ...) is available in CMake 2.4.7+.

SET(MYLIST hello world foo bar)

LIST(GET MYLIST 0 car)

SET(cdr ${MYLIST})
LIST(REMOVE_AT cdr 0)

LIST(LENGTH MYLIST length)

LIST(GET MYLIST 0 first)
LIST(GET MYLIST 1 second)
LIST(GET MYLIST 2 third)
LIST(GET MYLIST 3 fourth)

LIST(FIND MYLIST foo contains_foo)
LIST(FIND MYLIST baz contains_baz)

The output of the variables in both cases is:

MESSAGE("car: ${car}")
MESSAGE("cdr: ${cdr}")
MESSAGE("length: ${length}")
MESSAGE("first: ${first}")
MESSAGE("second: ${second}")
MESSAGE("third: ${third}")
MESSAGE("fourth: ${fourth}")
MESSAGE("contains_foo: ${contains_foo}")
MESSAGE("contains_baz: ${contains_baz}")
--
car: hello
cdr: world;foo;bar
length: 4
first: hello
second: world
third: foo
fourth: bar
contains_foo:  2  # for LIST_CONTAINS this is TRUE
contains_baz: -1  # for LIST_CONTAINS this is empty; which evaluates to false

Note that the CMake LIST command also provides additional flexibility, such as APPEND, INSERT, SORT, REVERSE, etc. Type

cmake --help-command LIST

for the current usage information.


Back



CMake: [Welcome | Site Map]