blob: 07b80c7dc322da6852ab78b45cc1fa6e3bd4a41f [file] [log] [blame]
Soby Mathewb4c6df42022-11-09 11:13:29 +00001#
2# SPDX-License-Identifier: BSD-3-Clause
3# SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
4#
5
6#[=======================================================================[.rst:
7ArmConfigOption
8---------------
9
10.. default-domain:: cmake
11
12.. command:: arm_config_option
13
14Create a configuration option with more flexibility than offered by
15:module:`cmake_dependent_option() <module:CMakeDependentOption>`.
16
17.. code:: cmake
18
19 arm_config_option(NAME <name> HELP <help> [TYPE <type>] [DEFAULT <default>]
20 [[STRINGS <strings>...] [FREEFORM]] [ADVANCED]
21 [[DEPENDS <depends>] [ELSE <else>]] [FORCE <force>])
22
23This helper function is intended to simplify some of the complex mechanics
24involved in creating a robust, scalable configuration system for medium to
25large projects. It incorporates basic dependency resolution, overridable default
26values, and stronger typing in order to provide a smoother experience for both
27build system developers and users.
28
29Basics
30^^^^^^
31
32Every configuration option has one of the following types:
33
34- ``BOOL`` for booleans (shows as a toggle option)
35- ``STRING`` for strings (shows as a text box)
36- ``PATH`` for directory paths (shows as a directory chooser)
37- ``FILEPATH`` for file paths (shows as a file chooser)
38
39These are the types supported by the :prop_cache:`TYPE <prop_cache:TYPE>` cache
40entry property. It's important to choose the right type for the option in order
41to provide a consistent user experience.
42
43By default, any configuration option that does not specify a type (by providing
44a value to the ``TYPE`` argument) is ``BOOL``, unless the ``STRINGS`` argument
45has been provided. For example:
46
47.. code:: cmake
48
49 arm_config_option(NAME XYZ ... TYPE BOOL) # BOOL
50 arm_config_option(NAME XYZ ... TYPE STRING) # STRING
51 arm_config_option(NAME XYZ ... TYPE PATH) # PATH
52 arm_config_option(NAME XYZ ... TYPE FILEPATH) # FILEPATH
53
54 arm_config_option(NAME XYZ ...) # BOOL
55 arm_config_option(NAME XYZ ... STRINGS ...) # STRING
56
57Likewise, every configuration option has a (default) default value, dependent on
58its type:
59
60.. code:: cmake
61
62 arm_config_option(NAME XYZ ... TYPE BOOL) # FALSE
63 arm_config_option(NAME XYZ ... TYPE STRING) # ""
64 arm_config_option(NAME XYZ ... TYPE PATH) # ""
65 arm_config_option(NAME XYZ ... TYPE FILEPATH) # ""
66
67 arm_config_option(NAME XYZ ...) # FALSE
68 arm_config_option(NAME XYZ ... STRINGS X Y Z) # "X"
69
70Note that the default value of configuration options with a ``STRINGS`` list
71will use the first element of the list as the default.
72
73The default value can be overridden by providing a value to the ``DEFAULT``
74argument:
75
76.. code:: cmake
77
78 arm_config_option(NAME XYZ ... TYPE BOOL DEFAULT TRUE) # TRUE
79 arm_config_option(NAME XYZ ... TYPE STRING DEFAULT "x") # "x"
80 arm_config_option(NAME XYZ ... TYPE PATH DEFAULT "./x") # "./x"
81 arm_config_option(NAME XYZ ... TYPE FILEPATH DEFAULT "./x.txt") # "./x.txt"
82
83For options with a ``STRINGS`` list, the value provided to the ``DEFAULT``
84argument must exist within the list unless the ``FREEFORM`` argument has been
85provided. Freeform string list options permit values outside of the list:
86
87.. code:: cmake
88
89 arm_config_option(NAME XYZ ... STRINGS X Y Z) # "X"
90 arm_config_option(NAME XYZ ... STRINGS X Y Z DEFAULT Z) # "Z"
91 arm_config_option(NAME XYZ ... STRINGS X Y Z DEFAULT A FREEFORM) # "A"
92 arm_config_option(NAME XYZ ... STRINGS X Y Z DEFAULT A) # ERROR
93
94Configuration options can be marked as "advanced" by using the ``ADVANCED``
95flag. In CMake's user interfaces, this hides the configuration option behind the
96"advanced" toggle:
97
98 arm_config_option(NAME XYZ ...) # Always visible
99 arm_config_option(NAME XYZ ... ADVANCED) # Visible only when requested
100
101Some basic usage examples follow:
102
103.. code:: cmake
104
105 arm_config_option(
106 NAME MYPROJECT_ENABLE_FOO
107 HELP "Enable the foo feature.")
108
109 arm_config_option(
110 NAME MYPROJECT_ENABLE_BAR
111 HELP "Enable the bar feature."
112 ADVANCED)
113
114 arm_config_option(
115 NAME MYPROJECT_BAZ_NAME
116 HELP "Name of the baz."
117 TYPE STRING
118 DEFAULT "Baz")
119
120 arm_config_option(
121 NAME MYPROJECT_BAZ_TYPE
122 HELP "Type of the baz."
123 STRINGS "Surly" "Bewildered" "Aloof")
124
125 if(MYPROJECT_ENABLE_FOO)
126 message(STATUS "The foo feature is enabled!")
127 endif()
128
129 if(MYPROJECT_ENABLE_BAR)
130 message(STATUS "The bar feature is enabled!")
131 endif()
132
133 message(STATUS "The name of the baz is: ${MYPROJECT_BAZ_NAME}!")
134 message(STATUS "The type of the baz is: ${MYPROJECT_BAZ_TYPE}!")
135
136Dependencies
137^^^^^^^^^^^^
138
139Dependencies between options can be modelled using the ``DEPENDS`` argument.
140This argument takes an expression in :ref:`Condition Syntax`, which determines
141whether the option will be shown.
142
143For example, if you have a feature flag ``foo``, and you have a feature flag
144``bar`` that only makes sense if ``foo`` is enabled, you might use:
145
146.. code:: cmake
147
148 arm_config_option(
149 NAME MYPROJECT_ENABLE_FOO
150 HELP "Enable the foo feature.")
151
152 arm_config_option(
153 NAME MYPROJECT_ENABLE_BAR
154 HELP "Enable the bar feature."
155 DEPENDS MYPROJECT_ENABLE_FOO)
156
157Configuration options whose dependencies have not been met are hidden from the
158GUI (that is, the cache variable is given the ``INTERNAL`` type), and the
159default value is restored.
160
161If you need a value *other* than the default to be set if the dependency is not
162met, then use the ``ELSE`` argument:
163
164.. code:: cmake
165
166 arm_config_option(
167 NAME STACK_SIZE
168 HELP "Stack size (in bytes)."
169 TYPE STRING
170 DEFAULT 512)
171
172 arm_config_option(
173 NAME HEAP_SIZE
174 HELP "Heap size (in bytes)."
175 DEFAULT 65536)
176
177 arm_config_option(
178 NAME STACKHEAP_SIZE
179 HELP "Stackheap size."
180 DEFAULT 65536
181 DEPENDS ((STACK_SIZE EQUAL 0) AND (HEAP_SIZE EQUAL 0))
182 ELSE 0)
183
184In some cases you may need to forcibly overwrite the value of a configuration
185option under certain conditions. You can do this using the ``FORCE`` argument
186which, like ``DEPENDS``, accepts :ref:`Condition Syntax`. This is typically only
187useful for augmenting existing cache variables.
188
189In the following example, ``FORCE`` is used to forcibly override the default
190value of :variable:`CMAKE_BUILD_TYPE <variable:CMAKE_BUILD_TYPE>` (``""``) with
191a new default defined by the build system configuration:
192
193.. code:: cmake
194
195 arm_config_option(
196 NAME CMAKE_BUILD_TYPE
197 HELP "Build type."
198 STRINGS "Debug" "RelWithDebInfo" "MinSizeRel" "Release"
199 DEFAULT "MinSizeRel"
200 FORCE NOT CMAKE_BUILD_TYPE)
201
202Detecting Changes
203^^^^^^^^^^^^^^^^^
204
205In some cases it's useful to know whether a configuration option has been
206modified. Any configuration option created with this function has an associated
207``${NAME}_CHANGED`` cache variable, which can be used to detect whether the
208value of ``${NAME}`` has changed between the last configuration run and the
209current one.
210
211For example:
212
213.. code:: cmake
214
215 arm_config_option(
216 NAME ENABLE_FEATURE
217 HELP "Enable the feature.")
218
219 if(ENABLE_FEATURE_CHANGED)
220 message(STATUS "The feature's been toggled!")
221 endif()
222
223Fine-Grained Control
224^^^^^^^^^^^^^^^^^^^^
225
226Additional facilities for fine-grained control over defaults and forced values
227are provided by the :command:`arm_config_option_override`.
228#]=======================================================================]
229
230include_guard()
231
232function(arm_config_option)
233 set(_options "FREEFORM;ADVANCED")
234 set(_single_args "NAME;HELP;TYPE")
235 set(_multi_args "DEFAULT;STRINGS;DEPENDS;ELSE;FORCE")
236
237 cmake_parse_arguments(
238 arg "${_options}" "${_single_args}" "${_multi_args}" ${ARGN})
239
240 if("DEFAULT" IN_LIST arg_KEYWORDS_MISSING_VALUES)
241 set(arg_DEFAULT "")
242 endif()
243
244 #
245 # Attempt to derive the type from the other arguments given. Passing STRINGS
246 # implies a type of STRING, otherwise the type is BOOL.
247 #
248
249 if(NOT DEFINED arg_TYPE)
250 if(DEFINED arg_STRINGS)
251 set(arg_TYPE "STRING")
252 else()
253 set(arg_TYPE "BOOL")
254 endif()
255 endif()
256
257 #
258 # Identify a reasonable default if one has not been provided. For BOOL this
259 # is FALSE. If STRINGS has been provided then we take the first entry in the
260 # list. For any other type we use an empty string.
261 #
262
263 if(NOT DEFINED arg_DEFAULT)
264 if(arg_TYPE MATCHES "BOOL")
265 set(arg_DEFAULT "FALSE")
266 elseif(DEFINED arg_STRINGS)
267 list(GET arg_STRINGS 0 arg_DEFAULT)
268 else()
269 set(arg_DEFAULT "")
270 endif()
271 endif()
272
273 #
274 # If no dependency condition is provided, it is implicitly TRUE.
275 #
276
277 if(NOT DEFINED arg_DEPENDS)
278 set(arg_DEPENDS "TRUE")
279 endif()
280
281 if(${arg_DEPENDS})
282 #
283 # If an internal cache variable exists by this name but the dependency
284 # condition holds, it's because it previously didn't. We need to
285 # forcibly update the variable to make it visible again.
286 #
287
288 if(DEFINED "${arg_NAME}")
289 get_property(type CACHE "${arg_NAME}" PROPERTY TYPE)
290
291 if(type STREQUAL "INTERNAL")
292 set(arg_FORCE TRUE)
293 endif()
294 endif()
295
296 #
297 # If a force variable exists, take on its value and hide the cache
298 # variable. Otherwise, if a default variable exists, just take on its
299 # value.
300 #
301
302 if(DEFINED "${arg_NAME}_FORCE")
303 set(arg_TYPE "INTERNAL")
304 set(arg_DEFAULT "${${arg_NAME}_FORCE}")
305 elseif(DEFINED "${arg_NAME}_INIT")
306 set(arg_DEFAULT "${${arg_NAME}_INIT}")
307 endif()
308 else()
309 #
310 # If the dependency condition doesn't hold, hide the cache variable from
311 # the user.
312 #
313
314 set(arg_TYPE "INTERNAL")
315
316 #
317 # If an else value has been given, now is the time to adopt it.
318 #
319
320 if(DEFINED arg_ELSE)
321 set(arg_DEFAULT "${arg_ELSE}")
322 endif()
323 endif()
324
325 #
326 # Try to detect whether the user has overridden an already
327 # forcibly-overriden variable. We throw an error in this situation to avoid
328 # a split-brain configuration, where the variable expands to two values
329 # depending on which side of this function call you are on.
330 #
331 # This usually happens if the user has defined the value on the command
332 # line, as these options are replaced every time reconfiguration
333 # happens.
334 #
335
336 if((DEFINED "${arg_NAME}") AND
337 (DEFINED "${arg_NAME}_FORCE") AND
338 (NOT "${${arg_NAME}_FORCE}" STREQUAL "${${arg_NAME}}"))
339 set(value "${${arg_NAME}}")
340 unset("${arg_NAME}" CACHE)
341
342 if(${arg_DEPENDS})
343 message(FATAL_ERROR
344 "Overridden configuration option detected!\n"
345
346 "The configuration option `${arg_NAME}` cannot be given "
347 "the value `${value}` because it has been forcibly set to "
348 "`${arg_DEFAULT}`.")
349 else()
350 string(REPLACE ";" " " dependency "${arg_DEPENDS}")
351
352 message(FATAL_ERROR
353 "Impossible configuration detected!\n"
354
355 "The configuration option `${arg_NAME}` cannot be given "
356 "the value `${value}` because it has been forcibly set to "
357 "`${arg_DEFAULT}` due to an unmet dependency:\n"
358
359 "${dependency}")
360 endif()
361 endif()
362
363 #
364 # The official documentation says that `INTERNAL` implies `FORCE`, but this
365 # does not seem to be the case in some situations, so let's be safe.
366 #
367
368 if(arg_TYPE STREQUAL "INTERNAL")
369 set(arg_FORCE TRUE)
370 endif()
371
372 #
373 # If we're being asked to forcibly update the cache variable, append FORCE
374 # to the set() call.
375 #
376
377 if((DEFINED arg_FORCE) AND (${arg_FORCE}))
378 set(force "FORCE")
379 else()
380 unset(force)
381
382 #
383 # Clear the forced-value variable so that we don't accidentally flag
384 # this
385 #
386
387 unset("${arg_NAME}_FORCE" CACHE)
388 endif()
389
390 #
391 # Update the change-tracking variable.
392 #
393
394 set(old "${${arg_NAME}_NEW}")
395 set(new "${${arg_NAME}}")
396
397 if(NOT old STREQUAL new)
398 set(changed TRUE)
399 else()
400 set(changed FALSE)
401 endif()
402
403 set("${arg_NAME}_OLD" "${old}"
404 CACHE INTERNAL "Previous value of ${arg_NAME}." FORCE)
405
406 set("${arg_NAME}_NEW" "${new}"
407 CACHE INTERNAL "Latest value of ${arg_NAME}." FORCE)
408
409 set("${arg_NAME}_CHANGED" ${changed}
410 CACHE INTERNAL "Has ${arg_NAME} just changed?" FORCE)
411
412 #
413 # Create the cache variable.
414 #
415
416 set("${arg_NAME}" "${arg_DEFAULT}"
417 CACHE "${arg_TYPE}" "${arg_HELP}" ${force})
418
419 if(arg_ADVANCED)
420 mark_as_advanced("${arg_NAME}")
421 endif()
422
423 #
424 # If we've been given a list of valid values, update the STRINGS property of
425 # the cache variable with that list.
426 #
427
428 if(DEFINED arg_STRINGS)
429 set_property(CACHE "${arg_NAME}" PROPERTY STRINGS ${arg_STRINGS})
430
431 #
432 # If we haven't been asked to offer a freeform text box, let the user
433 # know if they've provided something out of bounds.
434 #
435
436 if((NOT arg_FREEFORM) AND (NOT "${${arg_NAME}}" IN_LIST arg_STRINGS))
437 set(strings "")
438
439 foreach(string IN LISTS arg_STRINGS)
440 string(APPEND strings "\"${string}\" ")
441 endforeach()
442
443 message(FATAL_ERROR
444 "Invalid value for `${arg_NAME}`!\n"
445
446 "This configuration supports the following values: ${strings}")
447 endif()
448 endif()
449endfunction()