diff --git a/meta/classes/features.bbclass b/meta/classes/features.bbclass
new file mode 100644
index 00000000..32964c91
--- /dev/null
+++ b/meta/classes/features.bbclass
@@ -0,0 +1,91 @@
+# Functions to manipulate feature lists
+#
+# This class provides functions to manipulate feature lists. The functions
+# are intended to be used in :remove and :append handlers to remove or append
+# items to a variable. The remove will interpret items prefixed with a '-'
+# as items to be removed. The append function will rely on a mapping of
+# implied features to append the corresponding items. The intention is to
+# provide the ability for a feature to imply enabling of other features,
+# much like a dependency, but without the implication of order.
+#
+# Usage example:
+#
+#  IMAGE_FEATURES ??= ""
+#  IMAGE_FEATURES:remove = "${@remove_prefixed_features('IMAGE_FEATURES', d)}"
+#  IMAGE_FEATURES:prepend =
"${@add_implied_features('IMAGE_FEATURES', 'IMPLIED_IMAGE_FEATURES',
d)} "
+#  IMAGE_FEATURES_DISABLED = "${@' '.join(f for f in
remove_prefixed_features('IMAGE_FEATURES', d).split() if not
f.startswith('-'))}"
+#
+#  IMPLIED_IMAGE_FEATURES[alpha] = "beta"
+#
+#  IMAGE_FEATURES += "alpha theta -theta"
+#
+#  # SOME_VARIBLE="yes" if 'beta' is in IMAGE_FEATURES, "no" otherwise
+#  SOME_VARIABLE = "${@bb.utils.contains('IMAGE_FEATURES', 'beta',
'yes', 'no', d)}"
+#  # SOME_VARIBLE_TWO="yes" if 'beta' and 'alpha' are in
IMAGE_FEATURES, "no" otherwise
+#  SOME_VARIABLE_TWO = "${@bb.utils.contains('IMAGE_FEATURES',
['beta', 'alpha'], 'yes', 'no', d)}"
+#  # SOME_VARIBLE_THREE="yes" if 'beta' or 'theta' are in
IMAGE_FEATURES, "no" otherwise
+#  SOME_VARIABLE_THREE = "${@bb.utils.contains_any('IMAGE_FEATURES',
['beta', 'theta'], 'yes', 'no', d)}"
+
+
+def remove_prefixed_features(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 '-' as
+    items to be removed.
+
+    As an internal note, the 'remove_prefixed_features_internal' flag
is used to
+    avoid infinite recursion.
+    """
+    if d.getVarFlag(var, 'remove_prefixed_features_internal') == '1':
+        return ''
+
+    from collections import Counter
+
+    d.setVarFlag(var, 'remove_prefixed_features_internal', '1')
+    try:
+        value = d.getVar(var)
+        counter = Counter()
+        for v in value.split():
+            if 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_features_internal')
+
+def add_implied_features(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 mapping of implied features
+    to append the corresponding items.
+
+    As an internal note, the 'add_implied_features_internal' flag is used to
+    avoid infinite recursion.
+    """
+    if d.getVarFlag(var, 'add_implied_features_internal') == '1':
+        return ''
+
+    def implied_features(feature, implied_mapping, d, seen=None):
+        """Return the implied features for a given feature."""
+        if seen is None:
+            seen = set()
+        if feature in seen:
+            return ''
+        seen.add(feature)
+        implied = implied_mapping.get(feature, '').split()
+        return ' '.join(implied + [implied_features(f,
implied_mapping, d, seen) for f in implied])
+
+    d.setVarFlag(var, 'add_implied_features_internal', '1')
+    try:
+        value = d.getVar(var)
+        implied_mapping = d.getVarFlags(implied_var)
+        if implied_mapping is None:
+            return ''
+
+        return ' '.join(implied_features(f, implied_mapping, d) for f
in value.split())
+    finally:
+        d.delVarFlag(var, 'add_implied_features_internal')
diff --git a/meta/conf/bitbake.conf b/meta/conf/bitbake.conf
index cda98035..4dedb081 100644
--- a/meta/conf/bitbake.conf
+++ b/meta/conf/bitbake.conf
@@ -172,6 +172,31 @@ BBINCLUDELOGS ??= "yes"
 # Add event handlers for bitbake
 INHERIT += "isar-events sstate"

+# Make features variables available
+INHERIT += "features"
+
+BASE_REPO_FEATURES ??= ""
+BASE_REPO_FEATURES[doc] = "Specifies the list of features for the
base-apt repository."
+BASE_REPO_FEATURES:remove =
"${@remove_prefixed_features('BASE_REPO_FEATURES', d)}"
+BASE_REPO_FEATURES:prepend =
"${@add_implied_features('BASE_REPO_FEATURES',
'IMPLIED_BASE_REPO_FEATURES', d)} "
+
+MACHINE_FEATURES ??= ""
+MACHINE_FEATURES[doc] = "Specifies the list of hardware features the
MACHINE is capable of supporting."
+MACHINE_FEATURES:remove = "${@remove_prefixed_features('MACHINE_FEATURES', d)}"
+MACHINE_FEATURES:prepend =
"${@add_implied_features('MACHINE_FEATURES',
'IMPLIED_MACHINE_FEATURES', d)} "
+
+DISTRO_FEATURES ??= ""
+DISTRO_FEATURES[doc] = "The software support you want in your
distribution for various features."
+DISTRO_FEATURES:remove = "${@remove_prefixed_features('DISTRO_FEATURES', d)}"
+DISTRO_FEATURES:prepend = "${@add_implied_features('DISTRO_FEATURES',
'IMPLIED_DISTRO_FEATURES', d)} "
+
+COMBINED_FEATURES = "${@oe.utils.set_intersect('DISTRO_FEATURES',
