Soby Mathew | b4c6df4 | 2022-11-09 11:13:29 +0000 | [diff] [blame] | 1 | # |
| 2 | # SPDX-License-Identifier: BSD-3-Clause |
| 3 | # SPDX-FileCopyrightText: Copyright TF-RMM Contributors. |
| 4 | # |
| 5 | |
| 6 | #[=======================================================================[.rst: |
| 7 | ArmConfigOption |
| 8 | --------------- |
| 9 | |
| 10 | .. default-domain:: cmake |
| 11 | |
| 12 | .. command:: arm_config_option |
| 13 | |
| 14 | Create 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 | |
| 23 | This helper function is intended to simplify some of the complex mechanics |
| 24 | involved in creating a robust, scalable configuration system for medium to |
| 25 | large projects. It incorporates basic dependency resolution, overridable default |
| 26 | values, and stronger typing in order to provide a smoother experience for both |
| 27 | build system developers and users. |
| 28 | |
| 29 | Basics |
| 30 | ^^^^^^ |
| 31 | |
| 32 | Every 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 | |
| 39 | These are the types supported by the :prop_cache:`TYPE <prop_cache:TYPE>` cache |
| 40 | entry property. It's important to choose the right type for the option in order |
| 41 | to provide a consistent user experience. |
| 42 | |
| 43 | By default, any configuration option that does not specify a type (by providing |
| 44 | a value to the ``TYPE`` argument) is ``BOOL``, unless the ``STRINGS`` argument |
| 45 | has 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 | |
| 57 | Likewise, every configuration option has a (default) default value, dependent on |
| 58 | its 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 | |
| 70 | Note that the default value of configuration options with a ``STRINGS`` list |
| 71 | will use the first element of the list as the default. |
| 72 | |
| 73 | The default value can be overridden by providing a value to the ``DEFAULT`` |
| 74 | argument: |
| 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 | |
| 83 | For options with a ``STRINGS`` list, the value provided to the ``DEFAULT`` |
| 84 | argument must exist within the list unless the ``FREEFORM`` argument has been |
| 85 | provided. 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 | |
| 94 | Configuration options can be marked as "advanced" by using the ``ADVANCED`` |
| 95 | flag. 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 | |
| 101 | Some 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 | |
| 136 | Dependencies |
| 137 | ^^^^^^^^^^^^ |
| 138 | |
| 139 | Dependencies between options can be modelled using the ``DEPENDS`` argument. |
| 140 | This argument takes an expression in :ref:`Condition Syntax`, which determines |
| 141 | whether the option will be shown. |
| 142 | |
| 143 | For 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 | |
| 157 | Configuration options whose dependencies have not been met are hidden from the |
| 158 | GUI (that is, the cache variable is given the ``INTERNAL`` type), and the |
| 159 | default value is restored. |
| 160 | |
| 161 | If you need a value *other* than the default to be set if the dependency is not |
| 162 | met, 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 | |
| 184 | In some cases you may need to forcibly overwrite the value of a configuration |
| 185 | option under certain conditions. You can do this using the ``FORCE`` argument |
| 186 | which, like ``DEPENDS``, accepts :ref:`Condition Syntax`. This is typically only |
| 187 | useful for augmenting existing cache variables. |
| 188 | |
| 189 | In the following example, ``FORCE`` is used to forcibly override the default |
| 190 | value of :variable:`CMAKE_BUILD_TYPE <variable:CMAKE_BUILD_TYPE>` (``""``) with |
| 191 | a 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 | |
| 202 | Detecting Changes |
| 203 | ^^^^^^^^^^^^^^^^^ |
| 204 | |
| 205 | In some cases it's useful to know whether a configuration option has been |
| 206 | modified. Any configuration option created with this function has an associated |
| 207 | ``${NAME}_CHANGED`` cache variable, which can be used to detect whether the |
| 208 | value of ``${NAME}`` has changed between the last configuration run and the |
| 209 | current one. |
| 210 | |
| 211 | For 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 | |
| 223 | Fine-Grained Control |
| 224 | ^^^^^^^^^^^^^^^^^^^^ |
| 225 | |
| 226 | Additional facilities for fine-grained control over defaults and forced values |
| 227 | are provided by the :command:`arm_config_option_override`. |
| 228 | #]=======================================================================] |
| 229 | |
| 230 | include_guard() |
| 231 | |
| 232 | function(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() |
| 449 | endfunction() |