2 # Copyright (c) Facebook, Inc. and its affiliates.
4 # Helper function for parsing arguments to a CMake function.
6 # This function is very similar to CMake's built-in cmake_parse_arguments()
7 # function, with some improvements:
8 # - This function correctly handles empty arguments. (cmake_parse_arguments()
9 # ignores empty arguments.)
10 # - If a multi-value argument is specified more than once, the subsequent
11 # arguments are appended to the original list rather than replacing it. e.g.
12 # if "SOURCES" is a multi-value argument, and the argument list contains
13 # "SOURCES a b c SOURCES x y z" then the resulting value for SOURCES will be
14 # "a;b;c;x;y;z" rather than "x;y;z"
15 # - This function errors out by default on unrecognized arguments. You can
16 # pass in an extra "ALLOW_UNPARSED_ARGS" argument to make it behave like
17 # cmake_parse_arguments(), and return the unparsed arguments in a
18 # <prefix>_UNPARSED_ARGUMENTS variable instead.
20 # It does look like cmake_parse_arguments() handled empty arguments correctly
21 # from CMake 3.0 through 3.3, but it seems like this was probably broken when
22 # it was turned into a built-in function in CMake 3.4. Here is discussion and
23 # patches that fixed this behavior prior to CMake 3.0:
24 # https://cmake.org/pipermail/cmake-developers/2013-November/020607.html
26 # The one downside to this function over the built-in cmake_parse_arguments()
27 # is that I don't think we can achieve the PARSE_ARGV behavior in a non-builtin
28 # function, so we can't properly handle arguments that contain ";". CMake will
29 # treat the ";" characters as list element separators, and treat it as multiple
32 function(fb_cmake_parse_args PREFIX OPTIONS ONE_VALUE_ARGS MULTI_VALUE_ARGS ARGS)
33 foreach(option IN LISTS ARGN)
34 if ("${option}" STREQUAL "ALLOW_UNPARSED_ARGS")
35 set(ALLOW_UNPARSED_ARGS TRUE)
39 "unknown optional argument for fb_cmake_parse_args(): ${option}"
44 # Define all options as FALSE in the parent scope to start with
45 foreach(var_name IN LISTS OPTIONS)
46 set("${PREFIX}_${var_name}" "FALSE" PARENT_SCOPE)
49 # TODO: We aren't extremely strict about error checking for one-value
50 # arguments here. e.g., we don't complain if a one-value argument is
51 # followed by another option/one-value/multi-value name rather than an
52 # argument. We also don't complain if a one-value argument is the last
53 # argument and isn't followed by a value.
55 list(APPEND all_args ${ONE_VALUE_ARGS})
56 list(APPEND all_args ${MULTI_VALUE_ARGS})
59 foreach(arg IN LISTS ARGS)
60 list(FIND OPTIONS "${arg}" opt_index)
61 if("${opt_index}" EQUAL -1)
62 list(FIND all_args "${arg}" arg_index)
63 if("${arg_index}" EQUAL -1)
64 # This argument does not match an argument name,
65 # must be an argument value
66 if("${current_variable}" STREQUAL "")
67 list(APPEND unparsed_args "${arg}")
69 # Ugh, CMake lists have a pretty fundamental flaw: they cannot
70 # distinguish between an empty list and a list with a single empty
71 # element. We track our own SEEN_VALUES_arg setting to help
72 # distinguish this and behave properly here.
73 if ("${SEEN_${current_variable}}" AND "${${current_variable}}" STREQUAL "")
74 set("${current_variable}" ";${arg}")
76 list(APPEND "${current_variable}" "${arg}")
78 set("SEEN_${current_variable}" TRUE)
81 # We found a single- or multi-value argument name
82 set(current_variable "VALUES_${arg}")
83 set("SEEN_${arg}" TRUE)
86 # We found an option variable
87 set("${PREFIX}_${arg}" "TRUE" PARENT_SCOPE)
92 foreach(arg_name IN LISTS ONE_VALUE_ARGS)
93 if(NOT "${SEEN_${arg_name}}")
94 unset("${PREFIX}_${arg_name}" PARENT_SCOPE)
95 elseif(NOT "${SEEN_VALUES_${arg_name}}")
96 # If the argument was seen but a value wasn't specified, error out.
97 # We require exactly one value to be specified.
99 FATAL_ERROR "argument ${arg_name} was specified without a value"
102 list(LENGTH "VALUES_${arg_name}" num_args)
103 if("${num_args}" EQUAL 0)
104 # We know an argument was specified and that we called list(APPEND).
105 # If CMake thinks the list is empty that means there is really a single
106 # empty element in the list.
107 set("${PREFIX}_${arg_name}" "" PARENT_SCOPE)
108 elseif("${num_args}" EQUAL 1)
109 list(GET "VALUES_${arg_name}" 0 arg_value)
110 set("${PREFIX}_${arg_name}" "${arg_value}" PARENT_SCOPE)
113 FATAL_ERROR "too many arguments specified for ${arg_name}: "
114 "${VALUES_${arg_name}}"
120 foreach(arg_name IN LISTS MULTI_VALUE_ARGS)
121 # If this argument name was never seen, then unset the parent scope
122 if (NOT "${SEEN_${arg_name}}")
123 unset("${PREFIX}_${arg_name}" PARENT_SCOPE)
125 # TODO: Our caller still won't be able to distinguish between an empty
126 # list and a list with a single empty element. We can tell which is
127 # which, but CMake lists don't make it easy to show this to our caller.
128 set("${PREFIX}_${arg_name}" "${VALUES_${arg_name}}" PARENT_SCOPE)
132 # By default we fatal out on unparsed arguments, but return them to the
133 # caller if ALLOW_UNPARSED_ARGS was specified.
134 if (DEFINED unparsed_args)
135 if ("${ALLOW_UNPARSED_ARGS}")
136 set("${PREFIX}_UNPARSED_ARGUMENTS" "${unparsed_args}" PARENT_SCOPE)
138 message(FATAL_ERROR "unrecognized arguments: ${unparsed_args}")