Allow multiple conditions in mojom's EnableIf attribute
Before this CL mojom EnableIf attributes could only test a single feature per definition, requiring work in build.gn files to allow for multiple-os enablement: ```mojom [EnableIf=is_chromeos_or_linux] struct Foo { ... } ``` ```gn mojom("foo") { ... if (is_chromeos | is_linux) { enabled_features += "is_chromeos_or_linux" } } ``` After this CL a pipe `|` or ampersand `&` can now be used to allow multiple conditions to be tested for `EnableIf` and `EnableIfNot` attributes. If `|` is used then any listed feature being enabled will make the condition true (so an EnableIf attribute will allow the mojom node to be defined). If `&` is supplied every listed feature must be present in enabled_features for the condition to be true. This will allow simplification of the build setup for many mojoms where features are introduced to allow multiple platforms to be specified: ```mojom [EnableIf=is_chromeos|is_linux] struct Foo { ... } ``` This change is backwards-compatible as previously the `|` and `&` characters could not be included in an attribute's value field and would result in a parse error. No other attributes make use of these facilities, and if `&` or `|` are provided this will likely result in a python error during mojom generation. Mixed `&` and `|` are prevented by the parsing rules. Tests: mojo/public/tools/run_all_python_unittests.py Bug: 378692747 Change-Id: I4873a3ced25e60796a63889153ae8ef457c2465b Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6014778 Reviewed-by: Oksana Zhuravlova <oksamyt@chromium.org> Commit-Queue: Alex Gough <ajgo@chromium.org> Cr-Commit-Position: refs/heads/main@{#1382566}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
11e78cd74a
commit
899cca9d77
mojo/public/tools
bindings
mojom
@ -512,11 +512,13 @@ interesting attributes supported today.
|
||||
matching `value` in the list of `enabled_features`, the definition will be
|
||||
disabled. This is useful for mojom definitions that only make sense on one
|
||||
platform. Note that the `EnableIf` attribute can only be set once per
|
||||
definition and cannot be set at the same time as `EnableIfNot`. Also be aware
|
||||
that only one condition can be tested, `EnableIf=value,xyz` introduces a new
|
||||
`xyz` attribute. `xyz` is not part of the `EnableIf` condition that depends
|
||||
only on the feature `value`. Complex conditions can be introduced via
|
||||
enabled_features in `build.gn` files.
|
||||
definition and cannot be set at the same time as `EnableIfNot`. Multiple
|
||||
conditions can be tested using `|` (any, e.g. `EnableIf=is_win|is_linux`) and
|
||||
`&` (all, e.g. `Enableif=is_official_build&is_win`). You cannot mix `&` and
|
||||
`|` in one condition. More complex conditions can be introduced by defining
|
||||
your own features via `enabled_features` in `build.gn` files. Also be aware
|
||||
that a comma introduces a new attribute, so `EnableIf=value,xyz` means
|
||||
`EnableIf=value` and applies the `xyz` attribute.
|
||||
|
||||
* **`[EnableIfNot=value]`**:
|
||||
The `EnableIfNot` attribute is used to conditionally enable definitions when
|
||||
@ -524,7 +526,10 @@ interesting attributes supported today.
|
||||
matching `value` in the list of `enabled_features`, the definition will be
|
||||
disabled. This is useful for mojom definitions that only make sense on all but
|
||||
one platform. Note that the `EnableIfNot` attribute can only be set once per
|
||||
definition and cannot be set at the same time as `EnableIf`.
|
||||
definition and cannot be set at the same time as `EnableIf`. Multiple
|
||||
conditions can be tested using `|` (any, e.g. `EnableIfNot=is_win|is_linux`)
|
||||
and `&` (all, e.g. `EnableifNot=is_official_build&is_win`). You cannot mix `&`
|
||||
and `|` in one condition.
|
||||
|
||||
* **`[ServiceSandbox=value]`**:
|
||||
The `ServiceSandbox` attribute is used in Chromium to tag which sandbox a
|
||||
|
@ -203,11 +203,29 @@ class Attribute(NodeBase):
|
||||
|
||||
|
||||
class AttributeList(NodeListBase):
|
||||
"""Represents a list attributes."""
|
||||
"""Represents a list of attributes."""
|
||||
|
||||
_list_item_type = Attribute
|
||||
|
||||
|
||||
class AttributeValueOrList(NodeListBase):
|
||||
"""Represents a list of pipe delimited attribute values."""
|
||||
|
||||
def __str__(self):
|
||||
return '|'.join(item.name for item in self)
|
||||
|
||||
_list_item_type = Name
|
||||
|
||||
|
||||
class AttributeValueAndList(NodeListBase):
|
||||
"""Represents a list of ampersand delimited attribute values."""
|
||||
|
||||
def __str__(self):
|
||||
return '&'.join(item.name for item in self)
|
||||
|
||||
_list_item_type = Name
|
||||
|
||||
|
||||
class Const(Definition):
|
||||
"""Represents a const definition."""
|
||||
|
||||
@ -226,6 +244,11 @@ class Const(Definition):
|
||||
self.typename == other.typename and \
|
||||
self.value == other.value
|
||||
|
||||
def __repr__(self):
|
||||
return "Const(typename=%s, attribute_list=%s, value=%s)" % (
|
||||
self.typename, self.attribute_list, self.value)
|
||||
|
||||
|
||||
|
||||
class Enum(Definition):
|
||||
"""Represents an enum definition."""
|
||||
|
@ -27,24 +27,52 @@ def _IsEnabled(definition, enabled_features):
|
||||
if not definition.attribute_list:
|
||||
return True
|
||||
|
||||
already_defined = False
|
||||
for a in definition.attribute_list:
|
||||
if a.key.name == 'EnableIf' or a.key.name == 'EnableIfNot':
|
||||
if already_defined:
|
||||
has_condition = False
|
||||
condition = None # EnableIf or EnableIfNot
|
||||
value = None
|
||||
for attribute in definition.attribute_list:
|
||||
if attribute.key.name == 'EnableIf' or attribute.key.name == 'EnableIfNot':
|
||||
if has_condition:
|
||||
raise EnableIfError(
|
||||
definition.filename,
|
||||
"EnableIf/EnableIfNot attribute may only be set once per field.",
|
||||
definition.start.line)
|
||||
already_defined = True
|
||||
condition = attribute.key.name
|
||||
value = attribute.value
|
||||
has_condition = True
|
||||
|
||||
for attribute in definition.attribute_list:
|
||||
if (attribute.key.name == 'EnableIf'
|
||||
and attribute.value.name not in enabled_features):
|
||||
return False
|
||||
if (attribute.key.name == 'EnableIfNot'
|
||||
and attribute.value.name in enabled_features):
|
||||
return False
|
||||
return True
|
||||
# No EnableIf/EnableIfNot to filter by, so item is defined.
|
||||
if not has_condition:
|
||||
return True
|
||||
|
||||
# Common case is to have a single attribute value so shortcut that:
|
||||
if not isinstance(value, ast.NodeListBase):
|
||||
if condition == 'EnableIf':
|
||||
if value.name not in enabled_features:
|
||||
return False
|
||||
if condition == 'EnableIfNot':
|
||||
if value.name in enabled_features:
|
||||
return False
|
||||
return True
|
||||
|
||||
condition_met = False
|
||||
if isinstance(value, ast.AttributeValueOrList):
|
||||
for item in value:
|
||||
if item.name in enabled_features:
|
||||
condition_met = True
|
||||
break
|
||||
elif isinstance(value, ast.AttributeValueAndList):
|
||||
for item in value:
|
||||
if item.name in enabled_features:
|
||||
condition_met = True
|
||||
continue
|
||||
condition_met = False
|
||||
break
|
||||
|
||||
if condition == 'EnableIf':
|
||||
return condition_met
|
||||
|
||||
return not condition_met
|
||||
|
||||
|
||||
def _FilterDisabledFromNodeList(node_list, enabled_features):
|
||||
|
@ -372,5 +372,54 @@ class ConditionalFeaturesTest(unittest.TestCase):
|
||||
conditional_features.RemoveDisabledDefinitions,
|
||||
definition, ENABLED_FEATURES)
|
||||
|
||||
def testMultipleOrFeatures(self):
|
||||
mojom_source = """
|
||||
feature Foo {
|
||||
const string name = "FooFeature";
|
||||
[EnableIf=red|yellow]
|
||||
const bool default_state = false;
|
||||
[EnableIf=yellow|purple]
|
||||
const bool default_state = true;
|
||||
};
|
||||
"""
|
||||
expected_source = """
|
||||
feature Foo {
|
||||
const string name = "FooFeature";
|
||||
[EnableIf=red|yellow]
|
||||
const bool default_state = false;
|
||||
};
|
||||
"""
|
||||
self.parseAndAssertEqual(mojom_source, expected_source)
|
||||
|
||||
def testMultipleAndFeatures(self):
|
||||
mojom_source = """
|
||||
feature Foo {
|
||||
const string name = "FooFeature";
|
||||
[EnableIf=red&blue]
|
||||
const bool default_state = false;
|
||||
[EnableIf=yellow&purple]
|
||||
const bool default_state = true;
|
||||
};
|
||||
"""
|
||||
expected_source = """
|
||||
feature Foo {
|
||||
const string name = "FooFeature";
|
||||
[EnableIf=red&blue]
|
||||
const bool default_state = false;
|
||||
};
|
||||
"""
|
||||
self.parseAndAssertEqual(mojom_source, expected_source)
|
||||
|
||||
def testMixedAndOrInEnableIf(self):
|
||||
source = """
|
||||
enum Foo {
|
||||
[EnableIf=red&blue|yellow]
|
||||
kBarValue = 5,
|
||||
};
|
||||
"""
|
||||
# some other error, but some error!
|
||||
self.assertRaises(parser.ParseError, parser.Parse, source, "myfile.mojom")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -101,6 +101,8 @@ class Lexer:
|
||||
'RANGLE', # < >
|
||||
'SEMI', # ;
|
||||
'COMMA',
|
||||
'PIPE', # |
|
||||
'AMPERSAND', # &
|
||||
'DOT' # , .
|
||||
)
|
||||
|
||||
@ -190,6 +192,8 @@ class Lexer:
|
||||
t_COMMA = r','
|
||||
t_DOT = r'\.'
|
||||
t_SEMI = r';'
|
||||
t_PIPE = r'\|'
|
||||
t_AMPERSAND = r'&'
|
||||
|
||||
t_STRING_LITERAL = string_literal
|
||||
|
||||
|
@ -174,6 +174,8 @@ class Parser:
|
||||
|
||||
def p_attribute_2(self, p):
|
||||
"""attribute : name EQUALS evaled_literal
|
||||
| name EQUALS nonempty_pipe_delimited_names
|
||||
| name EQUALS nonempty_amps_delimited_names
|
||||
| name EQUALS name"""
|
||||
p[0] = ast.Attribute(p[1], p[3])
|
||||
self._set_lexstate(p, 1, 3)
|
||||
@ -195,6 +197,24 @@ class Parser:
|
||||
else:
|
||||
p[0] = eval(p[1].value)
|
||||
|
||||
def p_nonempty_pipe_delimited_names_1(self, p):
|
||||
"""nonempty_pipe_delimited_names : name"""
|
||||
p[0] = ast.AttributeValueOrList(p[1])
|
||||
|
||||
def p_nonempty_pipe_delimited_names_2(self, p):
|
||||
"""nonempty_pipe_delimited_names : nonempty_pipe_delimited_names PIPE name"""
|
||||
p[0] = p[1]
|
||||
p[0].Append(p[3])
|
||||
|
||||
def p_nonempty_amps_delimited_names_1(self, p):
|
||||
"""nonempty_amps_delimited_names : name"""
|
||||
p[0] = ast.AttributeValueAndList(p[1])
|
||||
|
||||
def p_nonempty_amps_delimited_names_2(self, p):
|
||||
"""nonempty_amps_delimited_names : nonempty_amps_delimited_names AMPERSAND name"""
|
||||
p[0] = p[1]
|
||||
p[0].Append(p[3])
|
||||
|
||||
def p_struct_1(self, p):
|
||||
"""struct : attribute_section STRUCT name LBRACE struct_body RBRACE SEMI"""
|
||||
p[0] = ast.Struct(p[3], p[1], p[5])
|
||||
|
Reference in New Issue
Block a user