| # |
| # SPDX-License-Identifier: BSD-3-Clause |
| # SPDX-FileCopyrightText: Copyright TF-RMM Contributors. |
| # |
| |
| #[=======================================================================[.rst: |
| ArmConfigOption |
| --------------- |
| |
| .. default-domain:: cmake |
| |
| .. command:: arm_config_option |
| |
| Create a configuration option with more flexibility than offered by |
| :module:`cmake_dependent_option() <module:CMakeDependentOption>`. |
| |
| .. code:: cmake |
| |
| arm_config_option(NAME <name> HELP <help> [TYPE <type>] [DEFAULT <default>] |
| [[STRINGS <strings>...] [FREEFORM]] [ADVANCED] |
| [[DEPENDS <depends>] [ELSE <else>]] [FORCE <force>]) |
| |
| This helper function is intended to simplify some of the complex mechanics |
| involved in creating a robust, scalable configuration system for medium to |
| large projects. It incorporates basic dependency resolution, overridable default |
| values, and stronger typing in order to provide a smoother experience for both |
| build system developers and users. |
| |
| Basics |
| ^^^^^^ |
| |
| Every configuration option has one of the following types: |
| |
| - ``BOOL`` for booleans (shows as a toggle option) |
| - ``STRING`` for strings (shows as a text box) |
| - ``PATH`` for directory paths (shows as a directory chooser) |
| - ``FILEPATH`` for file paths (shows as a file chooser) |
| |
| These are the types supported by the :prop_cache:`TYPE <prop_cache:TYPE>` cache |
| entry property. It's important to choose the right type for the option in order |
| to provide a consistent user experience. |
| |
| By default, any configuration option that does not specify a type (by providing |
| a value to the ``TYPE`` argument) is ``BOOL``, unless the ``STRINGS`` argument |
| has been provided. For example: |
| |
| .. code:: cmake |
| |
| arm_config_option(NAME XYZ ... TYPE BOOL) # BOOL |
| arm_config_option(NAME XYZ ... TYPE STRING) # STRING |
| arm_config_option(NAME XYZ ... TYPE PATH) # PATH |
| arm_config_option(NAME XYZ ... TYPE FILEPATH) # FILEPATH |
| |
| arm_config_option(NAME XYZ ...) # BOOL |
| arm_config_option(NAME XYZ ... STRINGS ...) # STRING |
| |
| Likewise, every configuration option has a (default) default value, dependent on |
| its type: |
| |
| .. code:: cmake |
| |
| arm_config_option(NAME XYZ ... TYPE BOOL) # FALSE |
| arm_config_option(NAME XYZ ... TYPE STRING) # "" |
| arm_config_option(NAME XYZ ... TYPE PATH) # "" |
| arm_config_option(NAME XYZ ... TYPE FILEPATH) # "" |
| |
| arm_config_option(NAME XYZ ...) # FALSE |
| arm_config_option(NAME XYZ ... STRINGS X Y Z) # "X" |
| |
| Note that the default value of configuration options with a ``STRINGS`` list |
| will use the first element of the list as the default. |
| |
| The default value can be overridden by providing a value to the ``DEFAULT`` |
| argument: |
| |
| .. code:: cmake |
| |
| arm_config_option(NAME XYZ ... TYPE BOOL DEFAULT TRUE) # TRUE |
| arm_config_option(NAME XYZ ... TYPE STRING DEFAULT "x") # "x" |
| arm_config_option(NAME XYZ ... TYPE PATH DEFAULT "./x") # "./x" |
| arm_config_option(NAME XYZ ... TYPE FILEPATH DEFAULT "./x.txt") # "./x.txt" |
| |
| For options with a ``STRINGS`` list, the value provided to the ``DEFAULT`` |
| argument must exist within the list unless the ``FREEFORM`` argument has been |
| provided. Freeform string list options permit values outside of the list: |
| |
| .. code:: cmake |
| |
| arm_config_option(NAME XYZ ... STRINGS X Y Z) # "X" |
| arm_config_option(NAME XYZ ... STRINGS X Y Z DEFAULT Z) # "Z" |
| arm_config_option(NAME XYZ ... STRINGS X Y Z DEFAULT A FREEFORM) # "A" |
| arm_config_option(NAME XYZ ... STRINGS X Y Z DEFAULT A) # ERROR |
| |
| Configuration options can be marked as "advanced" by using the ``ADVANCED`` |
| flag. In CMake's user interfaces, this hides the configuration option behind the |
| "advanced" toggle: |
| |
| arm_config_option(NAME XYZ ...) # Always visible |
| arm_config_option(NAME XYZ ... ADVANCED) # Visible only when requested |
| |
| Some basic usage examples follow: |
| |
| .. code:: cmake |
| |
| arm_config_option( |
| NAME MYPROJECT_ENABLE_FOO |
| HELP "Enable the foo feature.") |
| |
| arm_config_option( |
| NAME MYPROJECT_ENABLE_BAR |
| HELP "Enable the bar feature." |
| ADVANCED) |
| |
| arm_config_option( |
| NAME MYPROJECT_BAZ_NAME |
| HELP "Name of the baz." |
| TYPE STRING |
| DEFAULT "Baz") |
| |
| arm_config_option( |
| NAME MYPROJECT_BAZ_TYPE |
| HELP "Type of the baz." |
| STRINGS "Surly" "Bewildered" "Aloof") |
| |
| if(MYPROJECT_ENABLE_FOO) |
| message(STATUS "The foo feature is enabled!") |
| endif() |
| |
| if(MYPROJECT_ENABLE_BAR) |
| message(STATUS "The bar feature is enabled!") |
| endif() |
| |
| message(STATUS "The name of the baz is: ${MYPROJECT_BAZ_NAME}!") |
| message(STATUS "The type of the baz is: ${MYPROJECT_BAZ_TYPE}!") |
| |
| Dependencies |
| ^^^^^^^^^^^^ |
| |
| Dependencies between options can be modelled using the ``DEPENDS`` argument. |
| This argument takes an expression in :ref:`Condition Syntax`, which determines |
| whether the option will be shown. |
| |
| For example, if you have a feature flag ``foo``, and you have a feature flag |
| ``bar`` that only makes sense if ``foo`` is enabled, you might use: |
| |
| .. code:: cmake |
| |
| arm_config_option( |
| NAME MYPROJECT_ENABLE_FOO |
| HELP "Enable the foo feature.") |
| |
| arm_config_option( |
| NAME MYPROJECT_ENABLE_BAR |
| HELP "Enable the bar feature." |
| DEPENDS MYPROJECT_ENABLE_FOO) |
| |
| Configuration options whose dependencies have not been met are hidden from the |
| GUI (that is, the cache variable is given the ``INTERNAL`` type), and the |
| default value is restored. |
| |
| If you need a value *other* than the default to be set if the dependency is not |
| met, then use the ``ELSE`` argument: |
| |
| .. code:: cmake |
| |
| arm_config_option( |
| NAME STACK_SIZE |
| HELP "Stack size (in bytes)." |
| TYPE STRING |
| DEFAULT 512) |
| |
| arm_config_option( |
| NAME HEAP_SIZE |
| HELP "Heap size (in bytes)." |
| DEFAULT 65536) |
| |
| arm_config_option( |
| NAME STACKHEAP_SIZE |
| HELP "Stackheap size." |
| DEFAULT 65536 |
| DEPENDS ((STACK_SIZE EQUAL 0) AND (HEAP_SIZE EQUAL 0)) |
| ELSE 0) |
| |
| In some cases you may need to forcibly overwrite the value of a configuration |
| option under certain conditions. You can do this using the ``FORCE`` argument |
| which, like ``DEPENDS``, accepts :ref:`Condition Syntax`. This is typically only |
| useful for augmenting existing cache variables. |
| |
| In the following example, ``FORCE`` is used to forcibly override the default |
| value of :variable:`CMAKE_BUILD_TYPE <variable:CMAKE_BUILD_TYPE>` (``""``) with |
| a new default defined by the build system configuration: |
| |
| .. code:: cmake |
| |
| arm_config_option( |
| NAME CMAKE_BUILD_TYPE |
| HELP "Build type." |
| STRINGS "Debug" "RelWithDebInfo" "MinSizeRel" "Release" |
| DEFAULT "MinSizeRel" |
| FORCE NOT CMAKE_BUILD_TYPE) |
| |
| Detecting Changes |
| ^^^^^^^^^^^^^^^^^ |
| |
| In some cases it's useful to know whether a configuration option has been |
| modified. Any configuration option created with this function has an associated |
| ``${NAME}_CHANGED`` cache variable, which can be used to detect whether the |
| value of ``${NAME}`` has changed between the last configuration run and the |
| current one. |
| |
| For example: |
| |
| .. code:: cmake |
| |
| arm_config_option( |
| NAME ENABLE_FEATURE |
| HELP "Enable the feature.") |
| |
| if(ENABLE_FEATURE_CHANGED) |
| message(STATUS "The feature's been toggled!") |
| endif() |
| |
| Fine-Grained Control |
| ^^^^^^^^^^^^^^^^^^^^ |
| |
| Additional facilities for fine-grained control over defaults and forced values |
| are provided by the :command:`arm_config_option_override`. |
| #]=======================================================================] |
| |
| include_guard() |
| |
| function(arm_config_option) |
| set(_options "FREEFORM;ADVANCED") |
| set(_single_args "NAME;HELP;TYPE") |
| set(_multi_args "DEFAULT;STRINGS;DEPENDS;ELSE;FORCE") |
| |
| cmake_parse_arguments( |
| arg "${_options}" "${_single_args}" "${_multi_args}" ${ARGN}) |
| |
| if("DEFAULT" IN_LIST arg_KEYWORDS_MISSING_VALUES) |
| set(arg_DEFAULT "") |
| endif() |
| |
| # |
| # Attempt to derive the type from the other arguments given. Passing STRINGS |
| # implies a type of STRING, otherwise the type is BOOL. |
| # |
| |
| if(NOT DEFINED arg_TYPE) |
| if(DEFINED arg_STRINGS) |
| set(arg_TYPE "STRING") |
| else() |
| set(arg_TYPE "BOOL") |
| endif() |
| endif() |
| |
| # |
| # Identify a reasonable default if one has not been provided. For BOOL this |
| # is FALSE. If STRINGS has been provided then we take the first entry in the |
| # list. For any other type we use an empty string. |
| # |
| |
| if(NOT DEFINED arg_DEFAULT) |
| if(arg_TYPE MATCHES "BOOL") |
| set(arg_DEFAULT "FALSE") |
| elseif(DEFINED arg_STRINGS) |
| list(GET arg_STRINGS 0 arg_DEFAULT) |
| else() |
| set(arg_DEFAULT "") |
| endif() |
| endif() |
| |
| # |
| # If no dependency condition is provided, it is implicitly TRUE. |
| # |
| |
| if(NOT DEFINED arg_DEPENDS) |
| set(arg_DEPENDS "TRUE") |
| endif() |
| |
| if(${arg_DEPENDS}) |
| # |
| # If an internal cache variable exists by this name but the dependency |
| # condition holds, it's because it previously didn't. We need to |
| # forcibly update the variable to make it visible again. |
| # |
| |
| if(DEFINED "${arg_NAME}") |
| get_property(type CACHE "${arg_NAME}" PROPERTY TYPE) |
| |
| if(type STREQUAL "INTERNAL") |
| set(arg_FORCE TRUE) |
| endif() |
| endif() |
| |
| # |
| # If a force variable exists, take on its value and hide the cache |
| # variable. Otherwise, if a default variable exists, just take on its |
| # value. |
| # |
| |
| if(DEFINED "${arg_NAME}_FORCE") |
| set(arg_TYPE "INTERNAL") |
| set(arg_DEFAULT "${${arg_NAME}_FORCE}") |
| elseif(DEFINED "${arg_NAME}_INIT") |
| set(arg_DEFAULT "${${arg_NAME}_INIT}") |
| endif() |
| else() |
| # |
| # If the dependency condition doesn't hold, hide the cache variable from |
| # the user. |
| # |
| |
| set(arg_TYPE "INTERNAL") |
| |
| # |
| # If an else value has been given, now is the time to adopt it. |
| # |
| |
| if(DEFINED arg_ELSE) |
| set(arg_DEFAULT "${arg_ELSE}") |
| endif() |
| endif() |
| |
| # |
| # Try to detect whether the user has overridden an already |
| # forcibly-overriden variable. We throw an error in this situation to avoid |
| # a split-brain configuration, where the variable expands to two values |
| # depending on which side of this function call you are on. |
| # |
| # This usually happens if the user has defined the value on the command |
| # line, as these options are replaced every time reconfiguration |
| # happens. |
| # |
| |
| if((DEFINED "${arg_NAME}") AND |
| (DEFINED "${arg_NAME}_FORCE") AND |
| (NOT "${${arg_NAME}_FORCE}" STREQUAL "${${arg_NAME}}")) |
| set(value "${${arg_NAME}}") |
| unset("${arg_NAME}" CACHE) |
| |
| if(${arg_DEPENDS}) |
| message(FATAL_ERROR |
| "Overridden configuration option detected!\n" |
| |
| "The configuration option `${arg_NAME}` cannot be given " |
| "the value `${value}` because it has been forcibly set to " |
| "`${arg_DEFAULT}`.") |
| else() |
| string(REPLACE ";" " " dependency "${arg_DEPENDS}") |
| |
| message(FATAL_ERROR |
| "Impossible configuration detected!\n" |
| |
| "The configuration option `${arg_NAME}` cannot be given " |
| "the value `${value}` because it has been forcibly set to " |
| "`${arg_DEFAULT}` due to an unmet dependency:\n" |
| |
| "${dependency}") |
| endif() |
| endif() |
| |
| # |
| # The official documentation says that `INTERNAL` implies `FORCE`, but this |
| # does not seem to be the case in some situations, so let's be safe. |
| # |
| |
| if(arg_TYPE STREQUAL "INTERNAL") |
| set(arg_FORCE TRUE) |
| endif() |
| |
| # |
| # If we're being asked to forcibly update the cache variable, append FORCE |
| # to the set() call. |
| # |
| |
| if((DEFINED arg_FORCE) AND (${arg_FORCE})) |
| set(force "FORCE") |
| else() |
| unset(force) |
| |
| # |
| # Clear the forced-value variable so that we don't accidentally flag |
| # this |
| # |
| |
| unset("${arg_NAME}_FORCE" CACHE) |
| endif() |
| |
| # |
| # Update the change-tracking variable. |
| # |
| |
| set(old "${${arg_NAME}_NEW}") |
| set(new "${${arg_NAME}}") |
| |
| if(NOT old STREQUAL new) |
| set(changed TRUE) |
| else() |
| set(changed FALSE) |
| endif() |
| |
| set("${arg_NAME}_OLD" "${old}" |
| CACHE INTERNAL "Previous value of ${arg_NAME}." FORCE) |
| |
| set("${arg_NAME}_NEW" "${new}" |
| CACHE INTERNAL "Latest value of ${arg_NAME}." FORCE) |
| |
| set("${arg_NAME}_CHANGED" ${changed} |
| CACHE INTERNAL "Has ${arg_NAME} just changed?" FORCE) |
| |
| # |
| # Create the cache variable. |
| # |
| |
| set("${arg_NAME}" "${arg_DEFAULT}" |
| CACHE "${arg_TYPE}" "${arg_HELP}" ${force}) |
| |
| if(arg_ADVANCED) |
| mark_as_advanced("${arg_NAME}") |
| endif() |
| |
| # |
| # If we've been given a list of valid values, update the STRINGS property of |
| # the cache variable with that list. |
| # |
| |
| if(DEFINED arg_STRINGS) |
| set_property(CACHE "${arg_NAME}" PROPERTY STRINGS ${arg_STRINGS}) |
| |
| # |
| # If we haven't been asked to offer a freeform text box, let the user |
| # know if they've provided something out of bounds. |
| # |
| |
| if((NOT arg_FREEFORM) AND (NOT "${${arg_NAME}}" IN_LIST arg_STRINGS)) |
| set(strings "") |
| |
| foreach(string IN LISTS arg_STRINGS) |
| string(APPEND strings "\"${string}\" ") |
| endforeach() |
| |
| message(FATAL_ERROR |
| "Invalid value for `${arg_NAME}`!\n" |
| |
| "This configuration supports the following values: ${strings}") |
| endif() |
| endif() |
| endfunction() |