lists.bbclass,bitbake.conf: use features lists

Message ID 20241216201602.619-1-chris.larson@siemens.com
State New
Headers show
Series lists.bbclass,bitbake.conf: use features lists | expand

Commit Message

chris.larson Dec. 16, 2024, 8:16 p.m. UTC
From: Christopher Larson <kergoth@gmail.com>

The intention behind this commit is to ease and encourage the use of Yocto-style
features variables, beyond our current usage:

- Add a bbclass to ease the handling of list variables in general
- Add default values for the features variables
- Add the features variables to the list variables
- Add a combined features variable

The intention is that a downstream layer will use `bb.utils.contains` or
`bb.utils.contains_any` to enable or disable functionality based on the presence
of defined features, rather than adding new variables in each case.

Signed-off-by: Christopher Larson <kergoth@gmail.com>
---
 meta/classes/lists.bbclass | 97 ++++++++++++++++++++++++++++++++++++++
 meta/conf/bitbake.conf     | 19 ++++++++
 2 files changed, 116 insertions(+)
 create mode 100644 meta/classes/lists.bbclass

Comments

chris.larson Dec. 18, 2024, 4:29 p.m. UTC | #1
Ignore, this, I'll follow-up. Accidentally missed the LIST_VARIABLES handling somehow.
--
Christopher Larson
Siemens AG
http://www.siemens.com/

> -----Original Message-----
> From: chris.larson via isar-users <isar-users@googlegroups.com>
> Sent: Monday, December 16, 2024 1:16 PM
> To: isar-users@googlegroups.com
> Cc: Christopher Larson <kergoth@gmail.com>
> Subject: [PATCH] lists.bbclass,bitbake.conf: use features lists
>
> From: Christopher Larson <kergoth@gmail.com>
>
> The intention behind this commit is to ease and encourage the use of Yocto-style
> features variables, beyond our current usage:
>
> - Add a bbclass to ease the handling of list variables in general
> - Add default values for the features variables
> - Add the features variables to the list variables
> - Add a combined features variable
>
> The intention is that a downstream layer will use `bb.utils.contains` or
> `bb.utils.contains_any` to enable or disable functionality based on the presence of
> defined features, rather than adding new variables in each case.
>
> Signed-off-by: Christopher Larson <kergoth@gmail.com>
> ---
>  meta/classes/lists.bbclass | 97 ++++++++++++++++++++++++++++++++++++++
>  meta/conf/bitbake.conf     | 19 ++++++++
>  2 files changed, 116 insertions(+)
>  create mode 100644 meta/classes/lists.bbclass
>
> diff --git a/meta/classes/lists.bbclass b/meta/classes/lists.bbclass new file mode
> 100644 index 00000000..c872f4bd
> --- /dev/null
> +++ b/meta/classes/lists.bbclass
> @@ -0,0 +1,97 @@
> +# Functions to improve the functionality of bitbake list variables.
> +#
> +# - Add the ability to remove items from a list variable without using :remove.
> +# - Add the ability for a list item to imply the addition of other list items.
> +#
> +
> +# Usage requires either adding the variable name to LIST_VARIABLES, or
> +manually # adding a :remove and a :prepend to each fully supported list variable.
> +#
> +# To remove items from a configured list, simply append the item to be
> +removed # to the variable with a '-' or '~' prefix. For example, to
> +remove 'alpha' from # IMAGE_FEATURES, add '-alpha' to IMAGE_FEATURES.
> +#
> +# To support implied list items, create a mapping of items to be
> +appended to # the variable when a specific item is present. For example, to
> append 'beta'
> +# to IMAGE_FEATURES when 'alpha' is present, configure IMAGE_FEATURES
> +as such, # then set IMPLIED_IMAGE_FEATURES[alpha] = "beta".
> +#
> +# Boilerplate example:
> +#
> +#   # Either this:
> +#   LIST_VARIABLES += "IMAGE_FEATURES"
> +#
> +#   # Or this:
> +#   IMAGE_FEATURES:remove =
> "${@remove_prefixed_items('IMAGE_FEATURES', d)}"
> +#   IMAGE_FEATURES:prepend =
> "${@add_implied_items('IMAGE_FEATURES', 'IMPLIED_IMAGE_FEATURES', d)}
> "
> +#
> +# Usage example:
> +#
> +#   # IMAGE_FEATURES will be "beta alpha" if the following configuration is
> used:
> +#   IMPLIED_IMAGE_FEATURES[alpha] = "beta"
> +#   IMAGE_FEATURES += "alpha"
> +#
> +#   # IMAGE_FEATURES will be "first" if the following configuration is used:
> +#   IMAGE_FEATURES = "first second"
> +#   IMAGE_FEATURES += "-second"
> +
> +
> +def remove_prefixed_items(var, d):
> +    """Return the items to be removed from var with :remove.
> +
> +    This function is intended to be used in a :remove handler to remove
> +    items from a variable. It will interpret items prefixed with a '-'
> +    or '~' as items to be removed.
> +    """
> +    # Use a flag to avoid infinite recursion.
> +    if d.getVarFlag(var, 'remove_prefixed_items_internal') == '1':
> +        return ''
> +
> +    from collections import Counter
> +
> +    d.setVarFlag(var, 'remove_prefixed_items_internal', '1')
> +    try:
> +        value = d.getVar(var)
> +        counter = Counter()
> +        for v in value.split():
> +            if v.startswith('-') or v.startswith('~'):
> +                counter[v[1:]] -= 1
> +                counter[v] -= 1
> +            else:
> +                counter[v] += 1
> +        return ' '.join(v for v, c in counter.items() if c < 1)
> +    finally:
> +        d.delVarFlag(var, 'remove_prefixed_items_internal')
> +
> +
> +def add_implied_items(var, implied_var, d):
> +    """Return the items to be appended due to the presence of other items in var.
> +
> +    This function is intended to be used in a :append handler to append
> +    items from a variable. It will rely on the supplied mapping of implied items
> +    to append the corresponding items.
> +    """
> +    # Use a flag to avoid infinite recursion.
> +    if d.getVarFlag(var, 'add_implied_items_internal') == '1':
> +        return ''
> +
> +    def implied_items(item, implied_mapping, d, seen=None):
> +        """Return the implied items for a given item."""
> +        if seen is None:
> +            seen = set()
> +        if item in seen:
> +            return ''
> +        seen.add(item)
> +        implied = implied_mapping.get(item, '').split()
> +        return ' '.join(implied + [implied_items(f, implied_mapping, d,
> + seen) for f in implied])
> +
> +    d.setVarFlag(var, 'add_implied_items_internal', '1')
> +    try:
> +        value = d.getVar(var)
> +        implied_mapping = d.getVarFlags(implied_var)
> +        if implied_mapping is None:
> +            return ''
> +
> +        return ' '.join(implied_items(f, implied_mapping, d) for f in value.split())
> +    finally:
> +        d.delVarFlag(var, 'add_implied_items_internal')
> diff --git a/meta/conf/bitbake.conf b/meta/conf/bitbake.conf index
> ef408faa..5ab8ced7 100644
> --- a/meta/conf/bitbake.conf
> +++ b/meta/conf/bitbake.conf
> @@ -175,6 +175,25 @@ BBINCLUDELOGS ??= "yes"
>  # Add event handlers for bitbake
>  INHERIT += "isar-events sstate"
>
> +# Make features variables available
> +INHERIT += "lists"
> +
> +LIST_VARIABLES += "BASE_REPO_FEATURES MACHINE_FEATURES
> DISTRO_FEATURES ROOTFS_FEATURES"
> +
> +BASE_REPO_FEATURES ??= ""
> +BASE_REPO_FEATURES[doc] = "Specifies the list of features for the base-apt
> repository."
> +
> +MACHINE_FEATURES ??= ""
> +MACHINE_FEATURES[doc] = "Specifies the list of hardware features the
> MACHINE is capable of supporting."
> +
> +DISTRO_FEATURES ??= ""
> +DISTRO_FEATURES[doc] = "The software support you want in your distribution
> for various features."
> +
> +COMBINED_FEATURES = "${@oe.utils.set_intersect('DISTRO_FEATURES',
> 'MACHINE_FEATURES', d)}"
> +
> +ROOTFS_FEATURES ??= ""
> +ROOTFS_FEATURES[doc] = "The list of features to be included in a root
> filesystem. Typically, you configure this variable in an image recipe or class."
> +
>  # Buildstats requires IMAGE_ROOTFS to be always defined  IMAGE_ROOTFS
> ??= "${WORKDIR}/rootfs"
>  INHERIT += "${@'buildstats' if bb.utils.to_boolean(d.getVar('USE_BUILDSTATS'))
> else ''}"
> --
> 2.47.1.windows.1
>
> --
> You received this message because you are subscribed to the Google Groups
> "isar-users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to
> isar-users+unsubscribe@googlegroups.com.
> To view this discussion visit
> https://groups.goog/
> le.com%2Fd%2Fmsgid%2Fisar-users%2F20241216201602.619-1-
> chris.larson%2540siemens.com&data=05%7C02%7Cchris.larson%40siemens.co
> m%7Cb93bac82bcdf48d0efb708dd1e0e8b96%7C38ae3bcd95794fd4addab42e14
> 95d55a%7C1%7C0%7C638699770030857193%7CUnknown%7CTWFpbGZsb3d
> 8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjo
> iTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=TYJgrABE1gVPkjCAN
> QGPp%2B9yYpHdVmqXbJLlmA%2BFPZU%3D&reserved=0.
chris.larson Dec. 23, 2024, 11:16 p.m. UTC | #2
From: Christopher Larson <chris.larson@siemens.com>


The intention behind this commit is to ease and encourage the use of Yocto-style

features variables, beyond our current usage:



- Add a bbclass to ease the handling of list variables in general

- Add default values for the features variables

- Add the features variables to the list variables

- Add a combined features variable



The intention is that a downstream layer will use `bb.utils.contains` or

`bb.utils.contains_any` to enable or disable functionality based on the presence

of defined features, rather than adding new variables in each case.



Signed-off-by: Christopher Larson <chris.larson@siemens.com>

---

 meta/classes/lists.bbclass | 105 +++++++++++++++++++++++++++++++++++++

 meta/conf/bitbake.conf     |  19 +++++++

 2 files changed, 124 insertions(+)

 create mode 100644 meta/classes/lists.bbclass



v2 changes:

- Corrected email address.

- Added missing LIST_VARIABLES event handler

- Changed the examples to the more appropriate ROOTFS_FEATURES



diff --git a/meta/classes/lists.bbclass b/meta/classes/lists.bbclass

new file mode 100644

index 00000000..db8f5837

--- /dev/null

+++ b/meta/classes/lists.bbclass

@@ -0,0 +1,105 @@

+# Functions to improve the functionality of bitbake list variables.

+#

+# - Add the ability to remove items from a list variable without using :remove.

+# - Add the ability for a list item to imply the addition of other list items.

+#

+

+# Usage requires either adding the variable name to LIST_VARIABLES, or manually

+# adding a :remove and a :prepend to each fully supported list variable.

+#

+# To remove items from a configured list, simply append the item to be removed

+# to the variable with a '-' or '~' prefix. For example, to remove 'alpha' from

+# ROOTFS_FEATURES, add '-alpha' to ROOTFS_FEATURES.

+#

+# To support implied list items, create a mapping of items to be appended to

+# the variable when a specific item is present. For example, to append 'beta'

+# to ROOTFS_FEATURES when 'alpha' is present, configure ROOTFS_FEATURES as such,

+# then set IMPLIED_ROOTFS_FEATURES[alpha] = "beta".

+#

+# Boilerplate example:

+#

+#   # Either this:

+#   LIST_VARIABLES += "ROOTFS_FEATURES"

+#

+#   # Or this:

+#   ROOTFS_FEATURES:remove = "${@remove_prefixed_items('ROOTFS_FEATURES', d)}"

+#   ROOTFS_FEATURES:prepend = "${@add_implied_items('ROOTFS_FEATURES', 'IMPLIED_ROOTFS_FEATURES', d)} "

+#

+# Usage example:

+#

+#   # ROOTFS_FEATURES will be "beta alpha" if the following configuration is used:

+#   IMPLIED_ROOTFS_FEATURES[alpha] = "beta"

+#   ROOTFS_FEATURES += "alpha"

+#

+#   # ROOTFS_FEATURES will be "first" if the following configuration is used:

+#   ROOTFS_FEATURES = "first second"

+#   ROOTFS_FEATURES += "-second"

+

+python enable_list_variables() {

+    """Enable list variable functionality."""

+    for variable in d.getVar("LIST_VARIABLES").split():

+        d.setVar(variable + ':remove', ' ${@remove_prefixed_items("%s", d)}' % variable)

+        d.setVar(variable + ':prepend', '${@add_implied_items("%s", "IMPLIED_%s", d)} ' % (variable, variable))

+}

+enable_list_variables[eventmask] = "bb.event.ConfigParsed"

+addhandler enable_list_variables

+

+def remove_prefixed_items(var, d):

+    """Return the items to be removed from var with :remove.

+

+    This function is intended to be used in a :remove handler to remove

+    items from a variable. It will interpret items prefixed with a '-'

+    or '~' as items to be removed.

+    """

+    # Use a flag to avoid infinite recursion.

+    if d.getVarFlag(var, 'remove_prefixed_items_internal') == '1':

+        return ''

+

+    from collections import Counter

+

+    d.setVarFlag(var, 'remove_prefixed_items_internal', '1')

+    try:

+        value = d.getVar(var)

+        counter = Counter()

+        for v in value.split():

+            if v.startswith('-') or v.startswith('~'):

+                counter[v[1:]] -= 1

+                counter[v] -= 1

+            else:

+                counter[v] += 1

+        return ' '.join(v for v, c in counter.items() if c < 1)

+    finally:

+        d.delVarFlag(var, 'remove_prefixed_items_internal')

+

+

+def add_implied_items(var, implied_var, d):

+    """Return the items to be appended due to the presence of other items in var.

+

+    This function is intended to be used in a :append handler to append

+    items from a variable. It will rely on the supplied mapping of implied items

+    to append the corresponding items.

+    """

+    # Use a flag to avoid infinite recursion.

+    if d.getVarFlag(var, 'add_implied_items_internal') == '1':

+        return ''

+

+    def implied_items(item, implied_mapping, d, seen=None):

+        """Return the implied items for a given item."""

+        if seen is None:

+            seen = set()

+        if item in seen:

+            return ''

+        seen.add(item)

+        implied = implied_mapping.get(item, '').split()

+        return ' '.join(implied + [implied_items(f, implied_mapping, d, seen) for f in implied])

+

+    d.setVarFlag(var, 'add_implied_items_internal', '1')

+    try:

+        value = d.getVar(var)

+        implied_mapping = d.getVarFlags(implied_var)

+        if implied_mapping is None:

+            return ''

+

+        return ' '.join(implied_items(f, implied_mapping, d) for f in value.split())

+    finally:

+        d.delVarFlag(var, 'add_implied_items_internal')

diff --git a/meta/conf/bitbake.conf b/meta/conf/bitbake.conf

index cda98035..9f3b8a4e 100644

--- a/meta/conf/bitbake.conf

+++ b/meta/conf/bitbake.conf

@@ -172,6 +172,25 @@ BBINCLUDELOGS ??= "yes"

 # Add event handlers for bitbake

 INHERIT += "isar-events sstate"

 

+# Make features variables available

+INHERIT += "lists"

+

+LIST_VARIABLES += "BASE_REPO_FEATURES MACHINE_FEATURES DISTRO_FEATURES ROOTFS_FEATURES"

+

+BASE_REPO_FEATURES ??= ""

+BASE_REPO_FEATURES[doc] = "Specifies the list of features for the base-apt repository."

+

+MACHINE_FEATURES ??= ""

+MACHINE_FEATURES[doc] = "Specifies the list of hardware features the MACHINE is capable of supporting."

+

+DISTRO_FEATURES ??= ""

+DISTRO_FEATURES[doc] = "The software support you want in your distribution for various features."

+

+COMBINED_FEATURES = "${@oe.utils.set_intersect('DISTRO_FEATURES', 'MACHINE_FEATURES', d)}"

+

+ROOTFS_FEATURES ??= ""

+ROOTFS_FEATURES[doc] = "The list of features to be included in a root filesystem. Typically, you configure this variable in an image recipe or class."

+

 # Buildstats requires IMAGE_ROOTFS to be always defined

 IMAGE_ROOTFS ??= "${WORKDIR}/rootfs"

 INHERIT += "${@'buildstats' if bb.utils.to_boolean(d.getVar('USE_BUILDSTATS')) else ''}"
Uladzimir Bely Dec. 26, 2024, 7:23 a.m. UTC | #3
On Mon, 2024-12-23 at 16:16 -0700, chris.larson via isar-users wrote:
> From: Christopher Larson <chris.larson@siemens.com>
> 
> 
> The intention behind this commit is to ease and encourage the use of
> Yocto-style
> 
> features variables, beyond our current usage:
> 
> 
> 
> - Add a bbclass to ease the handling of list variables in general
> 
> - Add default values for the features variables
> 
> - Add the features variables to the list variables
> 
> - Add a combined features variable
> 
> 
> 
> The intention is that a downstream layer will use `bb.utils.contains`
> or
> 
> `bb.utils.contains_any` to enable or disable functionality based on
> the presence
> 
> of defined features, rather than adding new variables in each case.
> 
> 
> 
> Signed-off-by: Christopher Larson <chris.larson@siemens.com>
> 
> ---
> 
>  meta/classes/lists.bbclass | 105
> +++++++++++++++++++++++++++++++++++++
> 
>  meta/conf/bitbake.conf     |  19 +++++++
> 
>  2 files changed, 124 insertions(+)
> 
>  create mode 100644 meta/classes/lists.bbclass
> 
> 
> 
> v2 changes:
> 
> - Corrected email address.
> 
> - Added missing LIST_VARIABLES event handler
> 
> - Changed the examples to the more appropriate ROOTFS_FEATURES
> 
> 

Hello.

Could you please resend properly formatted [PATCH v2]? Current one is
broken due to multiple extra empty lines and is not recognized as a
patch.

> 
> diff --git a/meta/classes/lists.bbclass b/meta/classes/lists.bbclass
> 
> new file mode 100644
> 
> index 00000000..db8f5837
> 
> --- /dev/null
> 
> +++ b/meta/classes/lists.bbclass
> 
> @@ -0,0 +1,105 @@
> 
> +# Functions to improve the functionality of bitbake list variables.
> 
> +#
> 
> +# - Add the ability to remove items from a list variable without
> using :remove.
> 
> +# - Add the ability for a list item to imply the addition of other
> list items.
> 
> +#
> 
> +
> 
> +# Usage requires either adding the variable name to LIST_VARIABLES,
> or manually
> 
> +# adding a :remove and a :prepend to each fully supported list
> variable.
> 
> +#
> 
> +# To remove items from a configured list, simply append the item to
> be removed
> 
> +# to the variable with a '-' or '~' prefix. For example, to remove
> 'alpha' from
> 
> +# ROOTFS_FEATURES, add '-alpha' to ROOTFS_FEATURES.
> 
> +#
> 
> +# To support implied list items, create a mapping of items to be
> appended to
> 
> +# the variable when a specific item is present. For example, to
> append 'beta'
> 
> +# to ROOTFS_FEATURES when 'alpha' is present, configure
> ROOTFS_FEATURES as such,
> 
> +# then set IMPLIED_ROOTFS_FEATURES[alpha] = "beta".
> 
> +#
> 
> +# Boilerplate example:
> 
> +#
> 
> +#   # Either this:
> 
> +#   LIST_VARIABLES += "ROOTFS_FEATURES"
> 
> +#
> 
> +#   # Or this:
> 
> +#   ROOTFS_FEATURES:remove =
> "${@remove_prefixed_items('ROOTFS_FEATURES', d)}"
> 
> +#   ROOTFS_FEATURES:prepend =
> "${@add_implied_items('ROOTFS_FEATURES', 'IMPLIED_ROOTFS_FEATURES',
> d)} "
> 
> +#
> 
> +# Usage example:
> 
> +#
> 
> +#   # ROOTFS_FEATURES will be "beta alpha" if the following
> configuration is used:
> 
> +#   IMPLIED_ROOTFS_FEATURES[alpha] = "beta"
> 
> +#   ROOTFS_FEATURES += "alpha"
> 
> +#
> 
> +#   # ROOTFS_FEATURES will be "first" if the following configuration
> is used:
> 
> +#   ROOTFS_FEATURES = "first second"
> 
> +#   ROOTFS_FEATURES += "-second"
> 
> +
> 
> +python enable_list_variables() {
> 
> +    """Enable list variable functionality."""
> 
> +    for variable in d.getVar("LIST_VARIABLES").split():
> 
> +        d.setVar(variable + ':remove', '
> ${@remove_prefixed_items("%s", d)}' % variable)
> 
> +        d.setVar(variable + ':prepend', '${@add_implied_items("%s",
> "IMPLIED_%s", d)} ' % (variable, variable))
> 
> +}
> 
> +enable_list_variables[eventmask] = "bb.event.ConfigParsed"
> 
> +addhandler enable_list_variables
> 
> +
> 
> +def remove_prefixed_items(var, d):
> 
> +    """Return the items to be removed from var with :remove.
> 
> +
> 
> +    This function is intended to be used in a :remove handler to
> remove
> 
> +    items from a variable. It will interpret items prefixed with a
> '-'
> 
> +    or '~' as items to be removed.
> 
> +    """
> 
> +    # Use a flag to avoid infinite recursion.
> 
> +    if d.getVarFlag(var, 'remove_prefixed_items_internal') == '1':
> 
> +        return ''
> 
> +
> 
> +    from collections import Counter
> 
> +
> 
> +    d.setVarFlag(var, 'remove_prefixed_items_internal', '1')
> 
> +    try:
> 
> +        value = d.getVar(var)
> 
> +        counter = Counter()
> 
> +        for v in value.split():
> 
> +            if v.startswith('-') or v.startswith('~'):
> 
> +                counter[v[1:]] -= 1
> 
> +                counter[v] -= 1
> 
> +            else:
> 
> +                counter[v] += 1
> 
> +        return ' '.join(v for v, c in counter.items() if c < 1)
> 
> +    finally:
> 
> +        d.delVarFlag(var, 'remove_prefixed_items_internal')
> 
> +
> 
> +
> 
> +def add_implied_items(var, implied_var, d):
> 
> +    """Return the items to be appended due to the presence of other
> items in var.
> 
> +
> 
> +    This function is intended to be used in a :append handler to
> append
> 
> +    items from a variable. It will rely on the supplied mapping of
> implied items
> 
> +    to append the corresponding items.
> 
> +    """
> 
> +    # Use a flag to avoid infinite recursion.
> 
> +    if d.getVarFlag(var, 'add_implied_items_internal') == '1':
> 
> +        return ''
> 
> +
> 
> +    def implied_items(item, implied_mapping, d, seen=None):
> 
> +        """Return the implied items for a given item."""
> 
> +        if seen is None:
> 
> +            seen = set()
> 
> +        if item in seen:
> 
> +            return ''
> 
> +        seen.add(item)
> 
> +        implied = implied_mapping.get(item, '').split()
> 
> +        return ' '.join(implied + [implied_items(f, implied_mapping,
> d, seen) for f in implied])
> 
> +
> 
> +    d.setVarFlag(var, 'add_implied_items_internal', '1')
> 
> +    try:
> 
> +        value = d.getVar(var)
> 
> +        implied_mapping = d.getVarFlags(implied_var)
> 
> +        if implied_mapping is None:
> 
> +            return ''
> 
> +
> 
> +        return ' '.join(implied_items(f, implied_mapping, d) for f
> in value.split())
> 
> +    finally:
> 
> +        d.delVarFlag(var, 'add_implied_items_internal')
> 
> diff --git a/meta/conf/bitbake.conf b/meta/conf/bitbake.conf
> 
> index cda98035..9f3b8a4e 100644
> 
> --- a/meta/conf/bitbake.conf
> 
> +++ b/meta/conf/bitbake.conf
> 
> @@ -172,6 +172,25 @@ BBINCLUDELOGS ??= "yes"
> 
>  # Add event handlers for bitbake
> 
>  INHERIT += "isar-events sstate"
> 
>  
> 
> +# Make features variables available
> 
> +INHERIT += "lists"
> 
> +
> 
> +LIST_VARIABLES += "BASE_REPO_FEATURES MACHINE_FEATURES
> DISTRO_FEATURES ROOTFS_FEATURES"
> 
> +
> 
> +BASE_REPO_FEATURES ??= ""
> 
> +BASE_REPO_FEATURES[doc] = "Specifies the list of features for the
> base-apt repository."
> 
> +
> 
> +MACHINE_FEATURES ??= ""
> 
> +MACHINE_FEATURES[doc] = "Specifies the list of hardware features the
> MACHINE is capable of supporting."
> 
> +
> 
> +DISTRO_FEATURES ??= ""
> 
> +DISTRO_FEATURES[doc] = "The software support you want in your
> distribution for various features."
> 
> +
> 
> +COMBINED_FEATURES = "${@oe.utils.set_intersect('DISTRO_FEATURES',
> 'MACHINE_FEATURES', d)}"
> 
> +
> 
> +ROOTFS_FEATURES ??= ""
> 
> +ROOTFS_FEATURES[doc] = "The list of features to be included in a
> root filesystem. Typically, you configure this variable in an image
> recipe or class."
> 
> +
> 
>  # Buildstats requires IMAGE_ROOTFS to be always defined
> 
>  IMAGE_ROOTFS ??= "${WORKDIR}/rootfs"
> 
>  INHERIT += "${@'buildstats' if
> bb.utils.to_boolean(d.getVar('USE_BUILDSTATS')) else ''}"
> 
> -- 
> 
> 2.47.1
> 
> 
>
chris.larson Dec. 27, 2024, 9:55 p.m. UTC | #4
From: Christopher Larson <chris.larson@siemens.com>


The intention behind this commit is to ease and encourage the use of Yocto-style

features variables, beyond our current usage:



- Add a bbclass to ease the handling of list variables in general

- Add default values for the features variables

- Add the features variables to the list variables

- Add a combined features variable



The intention is that a downstream layer will use `bb.utils.contains` or

`bb.utils.contains_any` to enable or disable functionality based on the presence

of defined features, rather than adding new variables in each case.



Signed-off-by: Christopher Larson <chris.larson@siemens.com>

---

 meta/classes/lists.bbclass | 105 +++++++++++++++++++++++++++++++++++++

 meta/conf/bitbake.conf     |  19 +++++++

 2 files changed, 124 insertions(+)

 create mode 100644 meta/classes/lists.bbclass



v2 changes:

- Corrected email address.

- Added missing LIST_VARIABLES event handler

- Changed the examples to the more appropriate ROOTFS_FEATURES



diff --git a/meta/classes/lists.bbclass b/meta/classes/lists.bbclass

new file mode 100644

index 00000000..db8f5837

--- /dev/null

+++ b/meta/classes/lists.bbclass

@@ -0,0 +1,105 @@

+# Functions to improve the functionality of bitbake list variables.

+#

+# - Add the ability to remove items from a list variable without using :remove.

+# - Add the ability for a list item to imply the addition of other list items.

+#

+

+# Usage requires either adding the variable name to LIST_VARIABLES, or manually

+# adding a :remove and a :prepend to each fully supported list variable.

+#

+# To remove items from a configured list, simply append the item to be removed

+# to the variable with a '-' or '~' prefix. For example, to remove 'alpha' from

+# ROOTFS_FEATURES, add '-alpha' to ROOTFS_FEATURES.

+#

+# To support implied list items, create a mapping of items to be appended to

+# the variable when a specific item is present. For example, to append 'beta'

+# to ROOTFS_FEATURES when 'alpha' is present, configure ROOTFS_FEATURES as such,

+# then set IMPLIED_ROOTFS_FEATURES[alpha] = "beta".

+#

+# Boilerplate example:

+#

+#   # Either this:

+#   LIST_VARIABLES += "ROOTFS_FEATURES"

+#

+#   # Or this:

+#   ROOTFS_FEATURES:remove = "${@remove_prefixed_items('ROOTFS_FEATURES', d)}"

+#   ROOTFS_FEATURES:prepend = "${@add_implied_items('ROOTFS_FEATURES', 'IMPLIED_ROOTFS_FEATURES', d)} "

+#

+# Usage example:

+#

+#   # ROOTFS_FEATURES will be "beta alpha" if the following configuration is used:

+#   IMPLIED_ROOTFS_FEATURES[alpha] = "beta"

+#   ROOTFS_FEATURES += "alpha"

+#

+#   # ROOTFS_FEATURES will be "first" if the following configuration is used:

+#   ROOTFS_FEATURES = "first second"

+#   ROOTFS_FEATURES += "-second"

+

+python enable_list_variables() {

+    """Enable list variable functionality."""

+    for variable in d.getVar("LIST_VARIABLES").split():

+        d.setVar(variable + ':remove', ' ${@remove_prefixed_items("%s", d)}' % variable)

+        d.setVar(variable + ':prepend', '${@add_implied_items("%s", "IMPLIED_%s", d)} ' % (variable, variable))

+}

+enable_list_variables[eventmask] = "bb.event.ConfigParsed"

+addhandler enable_list_variables

+

+def remove_prefixed_items(var, d):

+    """Return the items to be removed from var with :remove.

+

+    This function is intended to be used in a :remove handler to remove

+    items from a variable. It will interpret items prefixed with a '-'

+    or '~' as items to be removed.

+    """

+    # Use a flag to avoid infinite recursion.

+    if d.getVarFlag(var, 'remove_prefixed_items_internal') == '1':

+        return ''

+

+    from collections import Counter

+

+    d.setVarFlag(var, 'remove_prefixed_items_internal', '1')

+    try:

+        value = d.getVar(var)

+        counter = Counter()

+        for v in value.split():

+            if v.startswith('-') or v.startswith('~'):

+                counter[v[1:]] -= 1

+                counter[v] -= 1

+            else:

+                counter[v] += 1

+        return ' '.join(v for v, c in counter.items() if c < 1)

+    finally:

+        d.delVarFlag(var, 'remove_prefixed_items_internal')

+

+

+def add_implied_items(var, implied_var, d):

+    """Return the items to be appended due to the presence of other items in var.

+

+    This function is intended to be used in a :append handler to append

+    items from a variable. It will rely on the supplied mapping of implied items

+    to append the corresponding items.

+    """

+    # Use a flag to avoid infinite recursion.

+    if d.getVarFlag(var, 'add_implied_items_internal') == '1':

+        return ''

+

+    def implied_items(item, implied_mapping, d, seen=None):

+        """Return the implied items for a given item."""

+        if seen is None:

+            seen = set()

+        if item in seen:

+            return ''

+        seen.add(item)

+        implied = implied_mapping.get(item, '').split()

+        return ' '.join(implied + [implied_items(f, implied_mapping, d, seen) for f in implied])

+

+    d.setVarFlag(var, 'add_implied_items_internal', '1')

+    try:

+        value = d.getVar(var)

+        implied_mapping = d.getVarFlags(implied_var)

+        if implied_mapping is None:

+            return ''

+

+        return ' '.join(implied_items(f, implied_mapping, d) for f in value.split())

+    finally:

+        d.delVarFlag(var, 'add_implied_items_internal')

diff --git a/meta/conf/bitbake.conf b/meta/conf/bitbake.conf

index cda98035..9f3b8a4e 100644

--- a/meta/conf/bitbake.conf

+++ b/meta/conf/bitbake.conf

@@ -172,6 +172,25 @@ BBINCLUDELOGS ??= "yes"

 # Add event handlers for bitbake

 INHERIT += "isar-events sstate"

 

+# Make features variables available

+INHERIT += "lists"

+

+LIST_VARIABLES += "BASE_REPO_FEATURES MACHINE_FEATURES DISTRO_FEATURES ROOTFS_FEATURES"

+

+BASE_REPO_FEATURES ??= ""

+BASE_REPO_FEATURES[doc] = "Specifies the list of features for the base-apt repository."

+

+MACHINE_FEATURES ??= ""

+MACHINE_FEATURES[doc] = "Specifies the list of hardware features the MACHINE is capable of supporting."

+

+DISTRO_FEATURES ??= ""

+DISTRO_FEATURES[doc] = "The software support you want in your distribution for various features."

+

+COMBINED_FEATURES = "${@oe.utils.set_intersect('DISTRO_FEATURES', 'MACHINE_FEATURES', d)}"

+

+ROOTFS_FEATURES ??= ""

+ROOTFS_FEATURES[doc] = "The list of features to be included in a root filesystem. Typically, you configure this variable in an image recipe or class."

+

 # Buildstats requires IMAGE_ROOTFS to be always defined

 IMAGE_ROOTFS ??= "${WORKDIR}/rootfs"

 INHERIT += "${@'buildstats' if bb.utils.to_boolean(d.getVar('USE_BUILDSTATS')) else ''}"

Patch

diff --git a/meta/classes/lists.bbclass b/meta/classes/lists.bbclass
new file mode 100644
index 00000000..c872f4bd
--- /dev/null
+++ b/meta/classes/lists.bbclass
@@ -0,0 +1,97 @@ 
+# Functions to improve the functionality of bitbake list variables.
+#
+# - Add the ability to remove items from a list variable without using :remove.
+# - Add the ability for a list item to imply the addition of other list items.
+#
+
+# Usage requires either adding the variable name to LIST_VARIABLES, or manually
+# adding a :remove and a :prepend to each fully supported list variable.
+#
+# To remove items from a configured list, simply append the item to be removed
+# to the variable with a '-' or '~' prefix. For example, to remove 'alpha' from
+# IMAGE_FEATURES, add '-alpha' to IMAGE_FEATURES.
+#
+# To support implied list items, create a mapping of items to be appended to
+# the variable when a specific item is present. For example, to append 'beta'
+# to IMAGE_FEATURES when 'alpha' is present, configure IMAGE_FEATURES as such,
+# then set IMPLIED_IMAGE_FEATURES[alpha] = "beta".
+#
+# Boilerplate example:
+#
+#   # Either this:
+#   LIST_VARIABLES += "IMAGE_FEATURES"
+#
+#   # Or this:
+#   IMAGE_FEATURES:remove = "${@remove_prefixed_items('IMAGE_FEATURES', d)}"
+#   IMAGE_FEATURES:prepend = "${@add_implied_items('IMAGE_FEATURES', 'IMPLIED_IMAGE_FEATURES', d)} "
+#
+# Usage example:
+#
+#   # IMAGE_FEATURES will be "beta alpha" if the following configuration is used:
+#   IMPLIED_IMAGE_FEATURES[alpha] = "beta"
+#   IMAGE_FEATURES += "alpha"
+#
+#   # IMAGE_FEATURES will be "first" if the following configuration is used:
+#   IMAGE_FEATURES = "first second"
+#   IMAGE_FEATURES += "-second"
+
+
+def remove_prefixed_items(var, d):
+    """Return the items to be removed from var with :remove.
+
+    This function is intended to be used in a :remove handler to remove
+    items from a variable. It will interpret items prefixed with a '-'
+    or '~' as items to be removed.
+    """
+    # Use a flag to avoid infinite recursion.
+    if d.getVarFlag(var, 'remove_prefixed_items_internal') == '1':
+        return ''
+
+    from collections import Counter
+
+    d.setVarFlag(var, 'remove_prefixed_items_internal', '1')
+    try:
+        value = d.getVar(var)
+        counter = Counter()
+        for v in value.split():
+            if v.startswith('-') or v.startswith('~'):
+                counter[v[1:]] -= 1
+                counter[v] -= 1
+            else:
+                counter[v] += 1
+        return ' '.join(v for v, c in counter.items() if c < 1)
+    finally:
+        d.delVarFlag(var, 'remove_prefixed_items_internal')
+
+
+def add_implied_items(var, implied_var, d):
+    """Return the items to be appended due to the presence of other items in var.
+
+    This function is intended to be used in a :append handler to append
+    items from a variable. It will rely on the supplied mapping of implied items
+    to append the corresponding items.
+    """
+    # Use a flag to avoid infinite recursion.
+    if d.getVarFlag(var, 'add_implied_items_internal') == '1':
+        return ''
+
+    def implied_items(item, implied_mapping, d, seen=None):
+        """Return the implied items for a given item."""
+        if seen is None:
+            seen = set()
+        if item in seen:
+            return ''
+        seen.add(item)
+        implied = implied_mapping.get(item, '').split()
+        return ' '.join(implied + [implied_items(f, implied_mapping, d, seen) for f in implied])
+
+    d.setVarFlag(var, 'add_implied_items_internal', '1')
+    try:
+        value = d.getVar(var)
+        implied_mapping = d.getVarFlags(implied_var)
+        if implied_mapping is None:
+            return ''
+
+        return ' '.join(implied_items(f, implied_mapping, d) for f in value.split())
+    finally:
+        d.delVarFlag(var, 'add_implied_items_internal')
diff --git a/meta/conf/bitbake.conf b/meta/conf/bitbake.conf
index ef408faa..5ab8ced7 100644
--- a/meta/conf/bitbake.conf
+++ b/meta/conf/bitbake.conf
@@ -175,6 +175,25 @@  BBINCLUDELOGS ??= "yes"
 # Add event handlers for bitbake
 INHERIT += "isar-events sstate"
 
+# Make features variables available
+INHERIT += "lists"
+
+LIST_VARIABLES += "BASE_REPO_FEATURES MACHINE_FEATURES DISTRO_FEATURES ROOTFS_FEATURES"
+
+BASE_REPO_FEATURES ??= ""
+BASE_REPO_FEATURES[doc] = "Specifies the list of features for the base-apt repository."
+
+MACHINE_FEATURES ??= ""
+MACHINE_FEATURES[doc] = "Specifies the list of hardware features the MACHINE is capable of supporting."
+
+DISTRO_FEATURES ??= ""
+DISTRO_FEATURES[doc] = "The software support you want in your distribution for various features."
+
+COMBINED_FEATURES = "${@oe.utils.set_intersect('DISTRO_FEATURES', 'MACHINE_FEATURES', d)}"
+
+ROOTFS_FEATURES ??= ""
+ROOTFS_FEATURES[doc] = "The list of features to be included in a root filesystem. Typically, you configure this variable in an image recipe or class."
+
 # Buildstats requires IMAGE_ROOTFS to be always defined
 IMAGE_ROOTFS ??= "${WORKDIR}/rootfs"
 INHERIT += "${@'buildstats' if bb.utils.to_boolean(d.getVar('USE_BUILDSTATS')) else ''}"