mirror of
https://git.libcamera.org/libcamera/libcamera.git
synced 2025-07-16 08:55:06 +03:00
meson: Move all code generation scripts to utils/codegen/
We have multiple code generation scripts in utils/, mixed with other miscellaneous utilities, as well as a larger code base based on mojom in utils/ipc/. To make code sharing easier between the generator scripts, without creating a mess in the utils/ directory, move all the code generation code to utils/codegen/. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
This commit is contained in:
parent
d3bf27180e
commit
50c92cc7e2
91 changed files with 15 additions and 15 deletions
131
utils/codegen/ipc/mojo/public/tools/bindings/BUILD.gn
Normal file
131
utils/codegen/ipc/mojo/public/tools/bindings/BUILD.gn
Normal file
|
@ -0,0 +1,131 @@
|
|||
# Copyright 2016 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import("//mojo/public/tools/bindings/mojom.gni")
|
||||
import("//third_party/jinja2/jinja2.gni")
|
||||
|
||||
action("precompile_templates") {
|
||||
sources = mojom_generator_sources
|
||||
sources += [
|
||||
"$mojom_generator_root/generators/cpp_templates/cpp_macros.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/enum_serialization_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/feature_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/feature_definition.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_definition.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_feature_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_macros.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-features.h.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-forward.h.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-import-headers.h.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-params-data.h.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-shared-internal.h.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-shared-message-ids.h.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-test-utils.h.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module.cc.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module.h.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/struct_data_view_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/struct_data_view_definition.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/struct_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/struct_definition.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/struct_macros.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/struct_serialization_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/struct_traits_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/struct_traits_definition.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/struct_unserialized_message_context.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/union_data_view_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/union_data_view_definition.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/union_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/union_definition.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/union_serialization_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/union_traits_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/union_traits_definition.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/validation_macros.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/wrapper_class_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/wrapper_class_definition.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/wrapper_class_template_definition.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/wrapper_union_class_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/wrapper_union_class_definition.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/wrapper_union_class_template_definition.tmpl",
|
||||
"$mojom_generator_root/generators/java_templates/constant_definition.tmpl",
|
||||
"$mojom_generator_root/generators/java_templates/constants.java.tmpl",
|
||||
"$mojom_generator_root/generators/java_templates/data_types_definition.tmpl",
|
||||
"$mojom_generator_root/generators/java_templates/enum.java.tmpl",
|
||||
"$mojom_generator_root/generators/java_templates/enum_definition.tmpl",
|
||||
"$mojom_generator_root/generators/java_templates/header.java.tmpl",
|
||||
"$mojom_generator_root/generators/java_templates/interface.java.tmpl",
|
||||
"$mojom_generator_root/generators/java_templates/interface_definition.tmpl",
|
||||
"$mojom_generator_root/generators/java_templates/interface_internal.java.tmpl",
|
||||
"$mojom_generator_root/generators/java_templates/struct.java.tmpl",
|
||||
"$mojom_generator_root/generators/java_templates/union.java.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/enum_definition.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/fuzzing.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/interface_definition.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/lite/enum_definition_for_module.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/lite/interface_definition.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/lite/interface_definition_for_module.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/lite/module_definition.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/lite/mojom-lite.js.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/lite/mojom.m.js.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/lite/struct_definition.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/lite/struct_definition_for_module.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/lite/union_definition.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/lite/union_definition_for_module.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/module.amd.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/module_definition.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/struct_definition.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/union_definition.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/validation_macros.tmpl",
|
||||
"$mojom_generator_root/generators/mojolpm_templates/mojolpm.cc.tmpl",
|
||||
"$mojom_generator_root/generators/mojolpm_templates/mojolpm.h.tmpl",
|
||||
"$mojom_generator_root/generators/mojolpm_templates/mojolpm.proto.tmpl",
|
||||
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_from_proto_macros.tmpl",
|
||||
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl",
|
||||
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl",
|
||||
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl",
|
||||
"$mojom_generator_root/generators/ts_templates/enum_definition.tmpl",
|
||||
"$mojom_generator_root/generators/ts_templates/interface_definition.tmpl",
|
||||
"$mojom_generator_root/generators/ts_templates/module_definition.tmpl",
|
||||
"$mojom_generator_root/generators/ts_templates/struct_definition.tmpl",
|
||||
"$mojom_generator_root/generators/ts_templates/union_definition.tmpl",
|
||||
]
|
||||
script = mojom_generator_script
|
||||
|
||||
inputs = jinja2_sources
|
||||
outputs = [
|
||||
"$target_gen_dir/cpp_templates.zip",
|
||||
"$target_gen_dir/java_templates.zip",
|
||||
"$target_gen_dir/js_templates.zip",
|
||||
"$target_gen_dir/mojolpm_templates.zip",
|
||||
"$target_gen_dir/ts_templates.zip",
|
||||
]
|
||||
args = [
|
||||
"-o",
|
||||
rebase_path(target_gen_dir, root_build_dir),
|
||||
"--use_bundled_pylibs",
|
||||
"precompile",
|
||||
]
|
||||
}
|
||||
|
||||
group("tests") {
|
||||
data = [
|
||||
mojom_generator_script,
|
||||
"checks/mojom_attributes_check_unittest.py",
|
||||
"checks/mojom_interface_feature_check_unittest.py",
|
||||
"checks/mojom_restrictions_checks_unittest.py",
|
||||
"mojom_bindings_generator_unittest.py",
|
||||
"//tools/diagnosis/crbug_1001171.py",
|
||||
"//third_party/markupsafe/",
|
||||
]
|
||||
data += mojom_generator_sources
|
||||
data += jinja2_sources
|
||||
}
|
1014
utils/codegen/ipc/mojo/public/tools/bindings/README.md
Normal file
1014
utils/codegen/ipc/mojo/public/tools/bindings/README.md
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,170 @@
|
|||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Validate mojo attributes are allowed in Chrome before generation."""
|
||||
|
||||
import mojom.generate.check as check
|
||||
import mojom.generate.module as module
|
||||
|
||||
_COMMON_ATTRIBUTES = {
|
||||
'EnableIf',
|
||||
'EnableIfNot',
|
||||
}
|
||||
|
||||
# For struct, union & parameter lists.
|
||||
_COMMON_FIELD_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'MinVersion',
|
||||
'RenamedFrom',
|
||||
}
|
||||
|
||||
# Note: `Default`` goes on the default _value_, not on the enum.
|
||||
# Note: [Stable] without [Extensible] is not allowed.
|
||||
_ENUM_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'Extensible',
|
||||
'Native',
|
||||
'Stable',
|
||||
'RenamedFrom',
|
||||
'Uuid',
|
||||
}
|
||||
|
||||
# TODO(crbug.com/1234883) MinVersion is not needed for EnumVal.
|
||||
_ENUMVAL_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'Default',
|
||||
'MinVersion',
|
||||
}
|
||||
|
||||
_INTERFACE_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'RenamedFrom',
|
||||
'RequireContext',
|
||||
'RuntimeFeature',
|
||||
'ServiceSandbox',
|
||||
'Stable',
|
||||
'Uuid',
|
||||
}
|
||||
|
||||
_METHOD_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'AllowedContext',
|
||||
'MinVersion',
|
||||
'NoInterrupt',
|
||||
'RuntimeFeature',
|
||||
'SupportsUrgent',
|
||||
'Sync',
|
||||
'UnlimitedSize',
|
||||
}
|
||||
|
||||
_MODULE_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'JavaConstantsClassName',
|
||||
'JavaPackage',
|
||||
}
|
||||
|
||||
_PARAMETER_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES
|
||||
|
||||
_STRUCT_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'CustomSerializer',
|
||||
'JavaClassName',
|
||||
'Native',
|
||||
'Stable',
|
||||
'RenamedFrom',
|
||||
'Uuid',
|
||||
}
|
||||
|
||||
_STRUCT_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES
|
||||
|
||||
_UNION_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'Extensible',
|
||||
'Stable',
|
||||
'RenamedFrom',
|
||||
'Uuid',
|
||||
}
|
||||
|
||||
_UNION_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES | {
|
||||
'Default',
|
||||
}
|
||||
|
||||
# TODO(https://crbug.com/1193875) empty this set and remove the allowlist.
|
||||
_STABLE_ONLY_ALLOWLISTED_ENUMS = {
|
||||
'crosapi.mojom.OptionalBool',
|
||||
'crosapi.mojom.TriState',
|
||||
}
|
||||
|
||||
|
||||
class Check(check.Check):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Check, self).__init__(*args, **kwargs)
|
||||
|
||||
def _Respell(self, allowed, attribute):
|
||||
for a in allowed:
|
||||
if a.lower() == attribute.lower():
|
||||
return f" - Did you mean: {a}?"
|
||||
return ""
|
||||
|
||||
def _CheckAttributes(self, context, allowed, attributes):
|
||||
if not attributes:
|
||||
return
|
||||
for attribute in attributes:
|
||||
if not attribute in allowed:
|
||||
# Is there a close misspelling?
|
||||
hint = self._Respell(allowed, attribute)
|
||||
raise check.CheckException(
|
||||
self.module,
|
||||
f"attribute {attribute} not allowed on {context}{hint}")
|
||||
|
||||
def _CheckEnumAttributes(self, enum):
|
||||
if enum.attributes:
|
||||
self._CheckAttributes("enum", _ENUM_ATTRIBUTES, enum.attributes)
|
||||
if 'Stable' in enum.attributes and not 'Extensible' in enum.attributes:
|
||||
full_name = f"{self.module.mojom_namespace}.{enum.mojom_name}"
|
||||
if full_name not in _STABLE_ONLY_ALLOWLISTED_ENUMS:
|
||||
raise check.CheckException(
|
||||
self.module,
|
||||
f"[Extensible] required on [Stable] enum {full_name}")
|
||||
for enumval in enum.fields:
|
||||
self._CheckAttributes("enum value", _ENUMVAL_ATTRIBUTES,
|
||||
enumval.attributes)
|
||||
|
||||
def _CheckInterfaceAttributes(self, interface):
|
||||
self._CheckAttributes("interface", _INTERFACE_ATTRIBUTES,
|
||||
interface.attributes)
|
||||
for method in interface.methods:
|
||||
self._CheckAttributes("method", _METHOD_ATTRIBUTES, method.attributes)
|
||||
for param in method.parameters:
|
||||
self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES,
|
||||
param.attributes)
|
||||
if method.response_parameters:
|
||||
for param in method.response_parameters:
|
||||
self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES,
|
||||
param.attributes)
|
||||
for enum in interface.enums:
|
||||
self._CheckEnumAttributes(enum)
|
||||
|
||||
def _CheckModuleAttributes(self):
|
||||
self._CheckAttributes("module", _MODULE_ATTRIBUTES, self.module.attributes)
|
||||
|
||||
def _CheckStructAttributes(self, struct):
|
||||
self._CheckAttributes("struct", _STRUCT_ATTRIBUTES, struct.attributes)
|
||||
for field in struct.fields:
|
||||
self._CheckAttributes("struct field", _STRUCT_FIELD_ATTRIBUTES,
|
||||
field.attributes)
|
||||
for enum in struct.enums:
|
||||
self._CheckEnumAttributes(enum)
|
||||
|
||||
def _CheckUnionAttributes(self, union):
|
||||
self._CheckAttributes("union", _UNION_ATTRIBUTES, union.attributes)
|
||||
for field in union.fields:
|
||||
self._CheckAttributes("union field", _UNION_FIELD_ATTRIBUTES,
|
||||
field.attributes)
|
||||
|
||||
def CheckModule(self):
|
||||
"""Note that duplicate attributes are forbidden at the parse phase.
|
||||
We also do not need to look at the types of any parameters, as they will be
|
||||
checked where they are defined. Consts do not have attributes so can be
|
||||
skipped."""
|
||||
self._CheckModuleAttributes()
|
||||
for interface in self.module.interfaces:
|
||||
self._CheckInterfaceAttributes(interface)
|
||||
for enum in self.module.enums:
|
||||
self._CheckEnumAttributes(enum)
|
||||
for struct in self.module.structs:
|
||||
self._CheckStructAttributes(struct)
|
||||
for union in self.module.unions:
|
||||
self._CheckUnionAttributes(union)
|
|
@ -0,0 +1,194 @@
|
|||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import unittest
|
||||
|
||||
import mojom.generate.check as check
|
||||
from mojom_bindings_generator import LoadChecks, _Generate
|
||||
from mojom_parser_test_case import MojomParserTestCase
|
||||
|
||||
|
||||
class FakeArgs:
|
||||
"""Fakes args to _Generate - intention is to do just enough to run checks"""
|
||||
|
||||
def __init__(self, tester, files=None):
|
||||
""" `tester` is MojomParserTestCase for paths.
|
||||
`files` will have tester path added."""
|
||||
self.checks_string = 'attributes'
|
||||
self.depth = tester.GetPath('')
|
||||
self.filelist = None
|
||||
self.filename = [tester.GetPath(x) for x in files]
|
||||
self.gen_directories = tester.GetPath('gen')
|
||||
self.generators_string = ''
|
||||
self.import_directories = []
|
||||
self.output_dir = tester.GetPath('out')
|
||||
self.scrambled_message_id_salt_paths = None
|
||||
self.typemaps = []
|
||||
self.variant = 'none'
|
||||
|
||||
|
||||
class MojoBindingsCheckTest(MojomParserTestCase):
|
||||
def _ParseAndGenerate(self, mojoms):
|
||||
self.ParseMojoms(mojoms)
|
||||
args = FakeArgs(self, files=mojoms)
|
||||
_Generate(args, {})
|
||||
|
||||
def _testValid(self, filename, content):
|
||||
self.WriteFile(filename, content)
|
||||
self._ParseAndGenerate([filename])
|
||||
|
||||
def _testThrows(self, filename, content, regexp):
|
||||
mojoms = []
|
||||
self.WriteFile(filename, content)
|
||||
mojoms.append(filename)
|
||||
with self.assertRaisesRegexp(check.CheckException, regexp):
|
||||
self._ParseAndGenerate(mojoms)
|
||||
|
||||
def testLoads(self):
|
||||
"""Validate that the check is registered under the expected name."""
|
||||
check_modules = LoadChecks('attributes')
|
||||
self.assertTrue(check_modules['attributes'])
|
||||
|
||||
def testNoAnnotations(self):
|
||||
# Undecorated mojom should be fine.
|
||||
self._testValid(
|
||||
"a.mojom", """
|
||||
module a;
|
||||
struct Bar { int32 a; };
|
||||
enum Hello { kValue };
|
||||
union Thingy { Bar b; Hello hi; };
|
||||
interface Foo {
|
||||
Foo(int32 a, Hello hi, Thingy t) => (Bar b);
|
||||
};
|
||||
""")
|
||||
|
||||
def testValidAnnotations(self):
|
||||
# Obviously this is meaningless and won't generate, but it should pass
|
||||
# the attribute check's validation.
|
||||
self._testValid(
|
||||
"a.mojom", """
|
||||
[JavaConstantsClassName="FakeClass",JavaPackage="org.chromium.Fake"]
|
||||
module a;
|
||||
[Stable, Extensible]
|
||||
enum Hello { [Default] kValue, kValue2, [MinVersion=2] kValue3 };
|
||||
[Native]
|
||||
enum NativeEnum {};
|
||||
[Stable,Extensible]
|
||||
union Thingy { Bar b; [Default]int32 c; Hello hi; };
|
||||
|
||||
[Stable,RenamedFrom="module.other.Foo",
|
||||
Uuid="4C178401-4B07-4C2E-9255-5401A943D0C7"]
|
||||
struct Structure { Hello hi; };
|
||||
|
||||
[ServiceSandbox=Hello.kValue,RequireContext=Hello.kValue,Stable,
|
||||
Uuid="2F17D7DD-865A-4B1C-9394-9C94E035E82F"]
|
||||
interface Foo {
|
||||
[AllowedContext=Hello.kValue]
|
||||
Foo@0(int32 a) => (int32 b);
|
||||
[MinVersion=2,Sync,UnlimitedSize,NoInterrupt]
|
||||
Bar@1(int32 b, [MinVersion=2]Structure? s) => (bool c);
|
||||
};
|
||||
|
||||
[RuntimeFeature=test.mojom.FeatureName]
|
||||
interface FooFeatureControlled {};
|
||||
|
||||
interface FooMethodFeatureControlled {
|
||||
[RuntimeFeature=test.mojom.FeatureName]
|
||||
MethodWithFeature() => (bool c);
|
||||
};
|
||||
""")
|
||||
|
||||
def testWrongModuleStable(self):
|
||||
contents = """
|
||||
// err: module cannot be Stable
|
||||
[Stable]
|
||||
module a;
|
||||
enum Hello { kValue, kValue2, kValue3 };
|
||||
enum NativeEnum {};
|
||||
struct Structure { Hello hi; };
|
||||
|
||||
interface Foo {
|
||||
Foo(int32 a) => (int32 b);
|
||||
Bar(int32 b, Structure? s) => (bool c);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'attribute Stable not allowed on module')
|
||||
|
||||
def testWrongEnumDefault(self):
|
||||
contents = """
|
||||
module a;
|
||||
// err: default should go on EnumValue not Enum.
|
||||
[Default=kValue]
|
||||
enum Hello { kValue, kValue2, kValue3 };
|
||||
enum NativeEnum {};
|
||||
struct Structure { Hello hi; };
|
||||
|
||||
interface Foo {
|
||||
Foo(int32 a) => (int32 b);
|
||||
Bar(int32 b, Structure? s) => (bool c);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'attribute Default not allowed on enum')
|
||||
|
||||
def testWrongStructMinVersion(self):
|
||||
contents = """
|
||||
module a;
|
||||
enum Hello { kValue, kValue2, kValue3 };
|
||||
enum NativeEnum {};
|
||||
// err: struct cannot have MinVersion.
|
||||
[MinVersion=2]
|
||||
struct Structure { Hello hi; };
|
||||
|
||||
interface Foo {
|
||||
Foo(int32 a) => (int32 b);
|
||||
Bar(int32 b, Structure? s) => (bool c);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'attribute MinVersion not allowed on struct')
|
||||
|
||||
def testWrongMethodRequireContext(self):
|
||||
contents = """
|
||||
module a;
|
||||
enum Hello { kValue, kValue2, kValue3 };
|
||||
enum NativeEnum {};
|
||||
struct Structure { Hello hi; };
|
||||
|
||||
interface Foo {
|
||||
// err: RequireContext is for interfaces.
|
||||
[RequireContext=Hello.kValue]
|
||||
Foo(int32 a) => (int32 b);
|
||||
Bar(int32 b, Structure? s) => (bool c);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'RequireContext not allowed on method')
|
||||
|
||||
def testWrongMethodRequireContext(self):
|
||||
# crbug.com/1230122
|
||||
contents = """
|
||||
module a;
|
||||
interface Foo {
|
||||
// err: sync not Sync.
|
||||
[sync]
|
||||
Foo(int32 a) => (int32 b);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'attribute sync not allowed.*Did you mean: Sync')
|
||||
|
||||
def testStableExtensibleEnum(self):
|
||||
# crbug.com/1193875
|
||||
contents = """
|
||||
module a;
|
||||
[Stable]
|
||||
enum Foo {
|
||||
kDefaultVal,
|
||||
kOtherVal = 2,
|
||||
};
|
||||
"""
|
||||
self._testThrows('a.mojom', contents,
|
||||
'Extensible.*?required.*?Stable.*?enum')
|
|
@ -0,0 +1,34 @@
|
|||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Ensure no duplicate type definitions before generation."""
|
||||
|
||||
import mojom.generate.check as check
|
||||
import mojom.generate.module as module
|
||||
|
||||
|
||||
class Check(check.Check):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Check, self).__init__(*args, **kwargs)
|
||||
|
||||
def CheckModule(self):
|
||||
kinds = dict()
|
||||
for module in self.module.imports:
|
||||
for kind in module.enums + module.structs + module.unions:
|
||||
kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'
|
||||
if kind_name in kinds:
|
||||
previous_module = kinds[kind_name]
|
||||
if previous_module.path != module.path:
|
||||
raise check.CheckException(
|
||||
self.module, f"multiple-definition for type {kind_name}" +
|
||||
f"(defined in both {previous_module} and {module})")
|
||||
kinds[kind_name] = kind.module
|
||||
|
||||
for kind in self.module.enums + self.module.structs + self.module.unions:
|
||||
kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'
|
||||
if kind_name in kinds:
|
||||
previous_module = kinds[kind_name]
|
||||
raise check.CheckException(
|
||||
self.module, f"multiple-definition for type {kind_name}" +
|
||||
f"(previous definition in {previous_module})")
|
||||
return True
|
|
@ -0,0 +1,62 @@
|
|||
# Copyright 2023 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Validate mojo runtime feature guarded interfaces are nullable."""
|
||||
|
||||
import mojom.generate.check as check
|
||||
import mojom.generate.module as module
|
||||
|
||||
|
||||
class Check(check.Check):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Check, self).__init__(*args, **kwargs)
|
||||
|
||||
# `param` is an Interface of some sort.
|
||||
def _CheckNonNullableFeatureGuardedInterface(self, kind):
|
||||
# Only need to validate interface if it has a RuntimeFeature
|
||||
if not kind.kind.runtime_feature:
|
||||
return
|
||||
# Nullable (optional) is ok as the interface expects they might not be sent.
|
||||
if kind.is_nullable:
|
||||
return
|
||||
interface = kind.kind.mojom_name
|
||||
raise check.CheckException(
|
||||
self.module,
|
||||
f"interface {interface} has a RuntimeFeature but is not nullable")
|
||||
|
||||
# `param` can be a lot of things so check if it is a remote/receiver.
|
||||
# Array/Map must be recursed into.
|
||||
def _CheckFieldOrParam(self, kind):
|
||||
if module.IsAnyInterfaceKind(kind):
|
||||
self._CheckNonNullableFeatureGuardedInterface(kind)
|
||||
if module.IsArrayKind(kind):
|
||||
self._CheckFieldOrParam(kind.kind)
|
||||
if module.IsMapKind(kind):
|
||||
self._CheckFieldOrParam(kind.key_kind)
|
||||
self._CheckFieldOrParam(kind.value_kind)
|
||||
|
||||
def _CheckInterfaceFeatures(self, interface):
|
||||
for method in interface.methods:
|
||||
for param in method.parameters:
|
||||
self._CheckFieldOrParam(param.kind)
|
||||
if method.response_parameters:
|
||||
for param in method.response_parameters:
|
||||
self._CheckFieldOrParam(param.kind)
|
||||
|
||||
def _CheckStructFeatures(self, struct):
|
||||
for field in struct.fields:
|
||||
self._CheckFieldOrParam(field.kind)
|
||||
|
||||
def _CheckUnionFeatures(self, union):
|
||||
for field in union.fields:
|
||||
self._CheckFieldOrParam(field.kind)
|
||||
|
||||
def CheckModule(self):
|
||||
"""Validate that any runtime feature guarded interfaces that might be passed
|
||||
over mojo are nullable."""
|
||||
for interface in self.module.interfaces:
|
||||
self._CheckInterfaceFeatures(interface)
|
||||
for struct in self.module.structs:
|
||||
self._CheckStructFeatures(struct)
|
||||
for union in self.module.unions:
|
||||
self._CheckUnionFeatures(union)
|
|
@ -0,0 +1,173 @@
|
|||
# Copyright 2023 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import unittest
|
||||
|
||||
import mojom.generate.check as check
|
||||
from mojom_bindings_generator import LoadChecks, _Generate
|
||||
from mojom_parser_test_case import MojomParserTestCase
|
||||
|
||||
|
||||
class FakeArgs:
|
||||
"""Fakes args to _Generate - intention is to do just enough to run checks"""
|
||||
def __init__(self, tester, files=None):
|
||||
""" `tester` is MojomParserTestCase for paths.
|
||||
`files` will have tester path added."""
|
||||
self.checks_string = 'features'
|
||||
self.depth = tester.GetPath('')
|
||||
self.filelist = None
|
||||
self.filename = [tester.GetPath(x) for x in files]
|
||||
self.gen_directories = tester.GetPath('gen')
|
||||
self.generators_string = ''
|
||||
self.import_directories = []
|
||||
self.output_dir = tester.GetPath('out')
|
||||
self.scrambled_message_id_salt_paths = None
|
||||
self.typemaps = []
|
||||
self.variant = 'none'
|
||||
|
||||
|
||||
class MojoBindingsCheckTest(MojomParserTestCase):
|
||||
def _ParseAndGenerate(self, mojoms):
|
||||
self.ParseMojoms(mojoms)
|
||||
args = FakeArgs(self, files=mojoms)
|
||||
_Generate(args, {})
|
||||
|
||||
def assertValid(self, filename, content):
|
||||
self.WriteFile(filename, content)
|
||||
self._ParseAndGenerate([filename])
|
||||
|
||||
def assertThrows(self, filename, content, regexp):
|
||||
mojoms = []
|
||||
self.WriteFile(filename, content)
|
||||
mojoms.append(filename)
|
||||
with self.assertRaisesRegexp(check.CheckException, regexp):
|
||||
self._ParseAndGenerate(mojoms)
|
||||
|
||||
def testLoads(self):
|
||||
"""Validate that the check is registered under the expected name."""
|
||||
check_modules = LoadChecks('features')
|
||||
self.assertTrue(check_modules['features'])
|
||||
|
||||
def testNullableOk(self):
|
||||
self.assertValid(
|
||||
"a.mojom", """
|
||||
module a;
|
||||
// Scaffolding.
|
||||
feature kFeature {
|
||||
const string name = "Hello";
|
||||
const bool enabled_state = false;
|
||||
};
|
||||
[RuntimeFeature=kFeature]
|
||||
interface Guarded {
|
||||
};
|
||||
|
||||
// Unguarded interfaces should be ok everywhere.
|
||||
interface NotGuarded { };
|
||||
|
||||
// Optional (nullable) interfaces should be ok everywhere:
|
||||
struct Bar {
|
||||
pending_remote<Guarded>? remote;
|
||||
pending_receiver<Guarded>? receiver;
|
||||
};
|
||||
union Thingy {
|
||||
pending_remote<Guarded>? remote;
|
||||
pending_receiver<Guarded>? receiver;
|
||||
};
|
||||
interface Foo {
|
||||
Foo(
|
||||
pending_remote<Guarded>? remote,
|
||||
pending_receiver<Guarded>? receiver,
|
||||
pending_associated_remote<Guarded>? a_remote,
|
||||
pending_associated_receiver<Guarded>? a_receiver,
|
||||
// Unguarded interfaces do not have to be nullable.
|
||||
pending_remote<NotGuarded> remote,
|
||||
pending_receiver<NotGuarded> receiver,
|
||||
pending_associated_remote<NotGuarded> a_remote,
|
||||
pending_associated_receiver<NotGuarded> a_receiver
|
||||
) => (
|
||||
pending_remote<Guarded>? remote,
|
||||
pending_receiver<Guarded>? receiver
|
||||
);
|
||||
Bar(array<pending_remote<Guarded>?> remote)
|
||||
=> (map<string, pending_receiver<Guarded>?> a);
|
||||
};
|
||||
""")
|
||||
|
||||
def testMethodParamsMustBeNullable(self):
|
||||
prelude = """
|
||||
module a;
|
||||
// Scaffolding.
|
||||
feature kFeature {
|
||||
const string name = "Hello";
|
||||
const bool enabled_state = false;
|
||||
};
|
||||
[RuntimeFeature=kFeature]
|
||||
interface Guarded { };
|
||||
"""
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(pending_remote<Guarded> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(bool foo) => (pending_receiver<Guarded> a);
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(pending_receiver<Guarded> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(pending_associated_remote<Guarded> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(pending_associated_receiver<Guarded> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(array<pending_associated_receiver<Guarded>> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(map<string, pending_associated_receiver<Guarded>> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
|
||||
def testStructUnionMembersMustBeNullable(self):
|
||||
prelude = """
|
||||
module a;
|
||||
// Scaffolding.
|
||||
feature kFeature {
|
||||
const string name = "Hello";
|
||||
const bool enabled_state = false;
|
||||
};
|
||||
[RuntimeFeature=kFeature]
|
||||
interface Guarded { };
|
||||
"""
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
struct Trial {
|
||||
pending_remote<Guarded> a;
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
union Trial {
|
||||
pending_remote<Guarded> a;
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
|
@ -0,0 +1,102 @@
|
|||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Validate RequireContext and AllowedContext annotations before generation."""
|
||||
|
||||
import mojom.generate.check as check
|
||||
import mojom.generate.module as module
|
||||
|
||||
|
||||
class Check(check.Check):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.kind_to_interfaces = dict()
|
||||
super(Check, self).__init__(*args, **kwargs)
|
||||
|
||||
def _IsPassedInterface(self, candidate):
|
||||
if isinstance(
|
||||
candidate.kind,
|
||||
(module.PendingReceiver, module.PendingRemote,
|
||||
module.PendingAssociatedReceiver, module.PendingAssociatedRemote)):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _CheckInterface(self, method, param):
|
||||
# |param| is a pending_x<Interface> so need .kind.kind to get Interface.
|
||||
interface = param.kind.kind
|
||||
if interface.require_context:
|
||||
if method.allowed_context is None:
|
||||
raise check.CheckException(
|
||||
self.module, "method `{}` has parameter `{}` which passes interface"
|
||||
" `{}` that requires an AllowedContext annotation but none exists.".
|
||||
format(
|
||||
method.mojom_name,
|
||||
param.mojom_name,
|
||||
interface.mojom_name,
|
||||
))
|
||||
# If a string was provided, or if an enum was not imported, this will
|
||||
# be a string and we cannot validate that it is in range.
|
||||
if not isinstance(method.allowed_context, module.EnumValue):
|
||||
raise check.CheckException(
|
||||
self.module,
|
||||
"method `{}` has AllowedContext={} which is not a valid enum value."
|
||||
.format(method.mojom_name, method.allowed_context))
|
||||
# EnumValue must be from the same enum to be compared.
|
||||
if interface.require_context.enum != method.allowed_context.enum:
|
||||
raise check.CheckException(
|
||||
self.module, "method `{}` has parameter `{}` which passes interface"
|
||||
" `{}` that requires AllowedContext={} but one of kind `{}` was "
|
||||
"provided.".format(
|
||||
method.mojom_name,
|
||||
param.mojom_name,
|
||||
interface.mojom_name,
|
||||
interface.require_context.enum,
|
||||
method.allowed_context.enum,
|
||||
))
|
||||
# RestrictContext enums have most privileged field first (lowest value).
|
||||
interface_value = interface.require_context.field.numeric_value
|
||||
method_value = method.allowed_context.field.numeric_value
|
||||
if interface_value < method_value:
|
||||
raise check.CheckException(
|
||||
self.module, "RequireContext={} > AllowedContext={} for method "
|
||||
"`{}` which passes interface `{}`.".format(
|
||||
interface.require_context.GetSpec(),
|
||||
method.allowed_context.GetSpec(), method.mojom_name,
|
||||
interface.mojom_name))
|
||||
return True
|
||||
|
||||
def _GatherReferencedInterfaces(self, field):
|
||||
key = field.kind.spec
|
||||
# structs/unions can nest themselves so we need to bookkeep.
|
||||
if not key in self.kind_to_interfaces:
|
||||
# Might reference ourselves so have to create the list first.
|
||||
self.kind_to_interfaces[key] = set()
|
||||
for param in field.kind.fields:
|
||||
if self._IsPassedInterface(param):
|
||||
self.kind_to_interfaces[key].add(param)
|
||||
elif isinstance(param.kind, (module.Struct, module.Union)):
|
||||
for iface in self._GatherReferencedInterfaces(param):
|
||||
self.kind_to_interfaces[key].add(iface)
|
||||
return self.kind_to_interfaces[key]
|
||||
|
||||
def _CheckParams(self, method, params):
|
||||
# Note: we have to repeat _CheckParams for each method as each might have
|
||||
# different AllowedContext= attributes. We cannot memoize this function,
|
||||
# but can do so for gathering referenced interfaces as their RequireContext
|
||||
# attributes do not change.
|
||||
for param in params:
|
||||
if self._IsPassedInterface(param):
|
||||
self._CheckInterface(method, param)
|
||||
elif isinstance(param.kind, (module.Struct, module.Union)):
|
||||
for interface in self._GatherReferencedInterfaces(param):
|
||||
self._CheckInterface(method, interface)
|
||||
|
||||
def _CheckMethod(self, method):
|
||||
if method.parameters:
|
||||
self._CheckParams(method, method.parameters)
|
||||
if method.response_parameters:
|
||||
self._CheckParams(method, method.response_parameters)
|
||||
|
||||
def CheckModule(self):
|
||||
for interface in self.module.interfaces:
|
||||
for method in interface.methods:
|
||||
self._CheckMethod(method)
|
|
@ -0,0 +1,254 @@
|
|||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import unittest
|
||||
|
||||
import mojom.generate.check as check
|
||||
from mojom_bindings_generator import LoadChecks, _Generate
|
||||
from mojom_parser_test_case import MojomParserTestCase
|
||||
|
||||
# Mojoms that we will use in multiple tests.
|
||||
basic_mojoms = {
|
||||
'level.mojom':
|
||||
"""
|
||||
module level;
|
||||
enum Level {
|
||||
kHighest,
|
||||
kMiddle,
|
||||
kLowest,
|
||||
};
|
||||
""",
|
||||
'interfaces.mojom':
|
||||
"""
|
||||
module interfaces;
|
||||
import "level.mojom";
|
||||
struct Foo {int32 bar;};
|
||||
[RequireContext=level.Level.kHighest]
|
||||
interface High {
|
||||
DoFoo(Foo foo);
|
||||
};
|
||||
[RequireContext=level.Level.kMiddle]
|
||||
interface Mid {
|
||||
DoFoo(Foo foo);
|
||||
};
|
||||
[RequireContext=level.Level.kLowest]
|
||||
interface Low {
|
||||
DoFoo(Foo foo);
|
||||
};
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
class FakeArgs:
|
||||
"""Fakes args to _Generate - intention is to do just enough to run checks"""
|
||||
|
||||
def __init__(self, tester, files=None):
|
||||
""" `tester` is MojomParserTestCase for paths.
|
||||
`files` will have tester path added."""
|
||||
self.checks_string = 'restrictions'
|
||||
self.depth = tester.GetPath('')
|
||||
self.filelist = None
|
||||
self.filename = [tester.GetPath(x) for x in files]
|
||||
self.gen_directories = tester.GetPath('gen')
|
||||
self.generators_string = ''
|
||||
self.import_directories = []
|
||||
self.output_dir = tester.GetPath('out')
|
||||
self.scrambled_message_id_salt_paths = None
|
||||
self.typemaps = []
|
||||
self.variant = 'none'
|
||||
|
||||
|
||||
class MojoBindingsCheckTest(MojomParserTestCase):
|
||||
def _WriteBasicMojoms(self):
|
||||
for filename, contents in basic_mojoms.items():
|
||||
self.WriteFile(filename, contents)
|
||||
return list(basic_mojoms.keys())
|
||||
|
||||
def _ParseAndGenerate(self, mojoms):
|
||||
self.ParseMojoms(mojoms)
|
||||
args = FakeArgs(self, files=mojoms)
|
||||
_Generate(args, {})
|
||||
|
||||
def testLoads(self):
|
||||
"""Validate that the check is registered under the expected name."""
|
||||
check_modules = LoadChecks('restrictions')
|
||||
self.assertTrue(check_modules['restrictions'])
|
||||
|
||||
def testValidAnnotations(self):
|
||||
mojoms = self._WriteBasicMojoms()
|
||||
|
||||
a = 'a.mojom'
|
||||
self.WriteFile(
|
||||
a, """
|
||||
module a;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
|
||||
interface PassesHigh {
|
||||
[AllowedContext=level.Level.kHighest]
|
||||
DoHigh(pending_receiver<interfaces.High> hi);
|
||||
};
|
||||
interface PassesMedium {
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoMedium(pending_receiver<interfaces.Mid> hi);
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoMediumRem(pending_remote<interfaces.Mid> hi);
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoMediumAssoc(pending_associated_receiver<interfaces.Mid> hi);
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoMediumAssocRem(pending_associated_remote<interfaces.Mid> hi);
|
||||
};
|
||||
interface PassesLow {
|
||||
[AllowedContext=level.Level.kLowest]
|
||||
DoLow(pending_receiver<interfaces.Low> hi);
|
||||
};
|
||||
|
||||
struct One { pending_receiver<interfaces.High> hi; };
|
||||
struct Two { One one; };
|
||||
interface PassesNestedHigh {
|
||||
[AllowedContext=level.Level.kHighest]
|
||||
DoNestedHigh(Two two);
|
||||
};
|
||||
|
||||
// Allowed as PassesHigh is not itself restricted.
|
||||
interface PassesPassesHigh {
|
||||
DoPass(pending_receiver<PassesHigh> hiho);
|
||||
};
|
||||
""")
|
||||
mojoms.append(a)
|
||||
self._ParseAndGenerate(mojoms)
|
||||
|
||||
def _testThrows(self, filename, content, regexp):
|
||||
mojoms = self._WriteBasicMojoms()
|
||||
self.WriteFile(filename, content)
|
||||
mojoms.append(filename)
|
||||
with self.assertRaisesRegexp(check.CheckException, regexp):
|
||||
self._ParseAndGenerate(mojoms)
|
||||
|
||||
def testMissingAnnotation(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
|
||||
interface PassesHigh {
|
||||
// err: missing annotation.
|
||||
DoHigh(pending_receiver<interfaces.High> hi);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
|
||||
|
||||
def testAllowTooLow(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
|
||||
interface PassesHigh {
|
||||
// err: level is worse than required.
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoHigh(pending_receiver<interfaces.High> hi);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')
|
||||
|
||||
def testWrongEnumInAllow(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
enum Blah {
|
||||
kZero,
|
||||
};
|
||||
interface PassesHigh {
|
||||
// err: different enums.
|
||||
[AllowedContext=Blah.kZero]
|
||||
DoHigh(pending_receiver<interfaces.High> hi);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents, 'but one of kind')
|
||||
|
||||
def testNotAnEnumInAllow(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
interface PassesHigh {
|
||||
// err: not an enum.
|
||||
[AllowedContext=doopdedoo.mojom.kWhatever]
|
||||
DoHigh(pending_receiver<interfaces.High> hi);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents, 'not a valid enum value')
|
||||
|
||||
def testMissingAllowedForNestedStructs(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
struct One { pending_receiver<interfaces.High> hi; };
|
||||
struct Two { One one; };
|
||||
interface PassesNestedHigh {
|
||||
// err: missing annotation.
|
||||
DoNestedHigh(Two two);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
|
||||
|
||||
def testMissingAllowedForNestedUnions(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
struct One { pending_receiver<interfaces.High> hi; };
|
||||
struct Two { One one; };
|
||||
union Three {One one; Two two; };
|
||||
interface PassesNestedHigh {
|
||||
// err: missing annotation.
|
||||
DoNestedHigh(Three three);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
|
||||
|
||||
def testMultipleInterfacesThrows(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
struct One { pending_receiver<interfaces.High> hi; };
|
||||
interface PassesMultipleInterfaces {
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoMultiple(
|
||||
pending_remote<interfaces.Mid> mid,
|
||||
pending_receiver<interfaces.High> hi,
|
||||
One one
|
||||
);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')
|
||||
|
||||
def testMultipleInterfacesAllowed(self):
|
||||
"""Multiple interfaces can be passed, all satisfy the level."""
|
||||
mojoms = self._WriteBasicMojoms()
|
||||
|
||||
b = "b.mojom"
|
||||
self.WriteFile(
|
||||
b, """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
struct One { pending_receiver<interfaces.High> hi; };
|
||||
interface PassesMultipleInterfaces {
|
||||
[AllowedContext=level.Level.kHighest]
|
||||
DoMultiple(
|
||||
pending_receiver<interfaces.High> hi,
|
||||
pending_remote<interfaces.Mid> mid,
|
||||
One one
|
||||
);
|
||||
};
|
||||
""")
|
||||
mojoms.append(b)
|
||||
self._ParseAndGenerate(mojoms)
|
55
utils/codegen/ipc/mojo/public/tools/bindings/concatenate-files.py
Executable file
55
utils/codegen/ipc/mojo/public/tools/bindings/concatenate-files.py
Executable file
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2019 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
#
|
||||
# This utility concatenates several files into one. On Unix-like systems
|
||||
# it is equivalent to:
|
||||
# cat file1 file2 file3 ...files... > target
|
||||
#
|
||||
# The reason for writing a separate utility is that 'cat' is not available
|
||||
# on all supported build platforms, but Python is, and hence this provides
|
||||
# us with an easy and uniform way of doing this on all platforms.
|
||||
|
||||
# for py2/py3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import optparse
|
||||
import sys
|
||||
|
||||
|
||||
def Concatenate(filenames):
|
||||
"""Concatenate files.
|
||||
|
||||
Args:
|
||||
files: Array of file names.
|
||||
The last name is the target; all earlier ones are sources.
|
||||
|
||||
Returns:
|
||||
True, if the operation was successful.
|
||||
"""
|
||||
if len(filenames) < 2:
|
||||
print("An error occurred generating %s:\nNothing to do." % filenames[-1])
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(filenames[-1], "wb") as target:
|
||||
for filename in filenames[:-1]:
|
||||
with open(filename, "rb") as current:
|
||||
target.write(current.read())
|
||||
return True
|
||||
except IOError as e:
|
||||
print("An error occurred when writing %s:\n%s" % (filenames[-1], e))
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
parser = optparse.OptionParser()
|
||||
parser.set_usage("""Concatenate several files into one.
|
||||
Equivalent to: cat file1 ... > target.""")
|
||||
(_options, args) = parser.parse_args()
|
||||
sys.exit(0 if Concatenate(args) else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,75 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2018 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""Simple utility which concatenates a set of files into a single output file
|
||||
while also stripping any goog.provide or goog.require lines. This allows us to
|
||||
provide a very primitive sort of "compilation" without any extra toolchain
|
||||
support and without having to modify otherwise compilable sources in the tree
|
||||
which use these directives.
|
||||
|
||||
goog.provide lines are replaced with an equivalent invocation of
|
||||
mojo.internal.exportModule, which accomplishes essentially the same thing in an
|
||||
uncompiled context. A singular exception is made for the 'mojo.internal' export,
|
||||
which is instead replaced with an inlined assignment to initialize the
|
||||
namespace.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import optparse
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
_MOJO_INTERNAL_MODULE_NAME = "mojo.internal"
|
||||
_MOJO_EXPORT_MODULE_SYMBOL = "mojo.internal.exportModule"
|
||||
|
||||
|
||||
def FilterLine(filename, line, output):
|
||||
if line.startswith("goog.require"):
|
||||
return
|
||||
|
||||
if line.startswith("goog.provide"):
|
||||
match = re.match(r"goog.provide\('([^']+)'\);", line)
|
||||
if not match:
|
||||
print("Invalid goog.provide line in %s:\n%s" % (filename, line))
|
||||
sys.exit(1)
|
||||
|
||||
module_name = match.group(1)
|
||||
if module_name == _MOJO_INTERNAL_MODULE_NAME:
|
||||
output.write("self.mojo = { internal: {} };")
|
||||
else:
|
||||
output.write("%s('%s');\n" % (_MOJO_EXPORT_MODULE_SYMBOL, module_name))
|
||||
return
|
||||
|
||||
output.write(line)
|
||||
|
||||
def ConcatenateAndReplaceExports(filenames):
|
||||
if (len(filenames) < 2):
|
||||
print("At least two filenames (one input and the output) are required.")
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(filenames[-1], "w") as target:
|
||||
for filename in filenames[:-1]:
|
||||
with open(filename, "r") as current:
|
||||
for line in current.readlines():
|
||||
FilterLine(filename, line, target)
|
||||
return True
|
||||
except IOError as e:
|
||||
print("Error generating %s\n: %s" % (filenames[-1], e))
|
||||
return False
|
||||
|
||||
def main():
|
||||
parser = optparse.OptionParser()
|
||||
parser.set_usage("""file1 [file2...] outfile
|
||||
Concatenate several files into one, stripping Closure provide and
|
||||
require directives along the way.""")
|
||||
(_, args) = parser.parse_args()
|
||||
sys.exit(0 if ConcatenateAndReplaceExports(args) else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,48 @@
|
|||
# Copyright 2017 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Generates a list of all files in a directory.
|
||||
|
||||
This script takes in a directory and an output file name as input.
|
||||
It then reads the directory and creates a list of all file names
|
||||
in that directory. The list is written to the output file.
|
||||
There is also an option to pass in '-p' or '--pattern'
|
||||
which will check each file name against a regular expression
|
||||
pattern that is passed in. Only files which match the regex
|
||||
will be written to the list.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "mojom"))
|
||||
|
||||
from mojom.generate.generator import WriteFile
|
||||
|
||||
|
||||
def main():
|
||||
parser = OptionParser()
|
||||
parser.add_option('-d', '--directory', help='Read files from DIRECTORY')
|
||||
parser.add_option('-o', '--output', help='Write list to FILE')
|
||||
parser.add_option('-p',
|
||||
'--pattern',
|
||||
help='Only reads files that name matches PATTERN',
|
||||
default=".")
|
||||
(options, _) = parser.parse_args()
|
||||
pattern = re.compile(options.pattern)
|
||||
files = [f for f in os.listdir(options.directory) if pattern.match(f)]
|
||||
|
||||
contents = '\n'.join(f for f in files) + '\n'
|
||||
WriteFile(contents, options.output)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
135
utils/codegen/ipc/mojo/public/tools/bindings/generate_type_mappings.py
Executable file
135
utils/codegen/ipc/mojo/public/tools/bindings/generate_type_mappings.py
Executable file
|
@ -0,0 +1,135 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2016 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Generates a JSON typemap from its command-line arguments and dependencies.
|
||||
|
||||
Each typemap should be specified in an command-line argument of the form
|
||||
key=value, with an argument of "--start-typemap" preceding each typemap.
|
||||
|
||||
For example,
|
||||
generate_type_mappings.py --output=foo.typemap --start-typemap \\
|
||||
public_headers=foo.h traits_headers=foo_traits.h \\
|
||||
type_mappings=mojom.Foo=FooImpl
|
||||
|
||||
generates a foo.typemap containing
|
||||
{
|
||||
"c++": {
|
||||
"mojom.Foo": {
|
||||
"typename": "FooImpl",
|
||||
"traits_headers": [
|
||||
"foo_traits.h"
|
||||
],
|
||||
"public_headers": [
|
||||
"foo.h"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Then,
|
||||
generate_type_mappings.py --dependency foo.typemap --output=bar.typemap \\
|
||||
--start-typemap public_headers=bar.h traits_headers=bar_traits.h \\
|
||||
type_mappings=mojom.Bar=BarImpl
|
||||
|
||||
generates a bar.typemap containing
|
||||
{
|
||||
"c++": {
|
||||
"mojom.Bar": {
|
||||
"typename": "BarImpl",
|
||||
"traits_headers": [
|
||||
"bar_traits.h"
|
||||
],
|
||||
"public_headers": [
|
||||
"bar.h"
|
||||
]
|
||||
},
|
||||
"mojom.Foo": {
|
||||
"typename": "FooImpl",
|
||||
"traits_headers": [
|
||||
"foo_traits.h"
|
||||
],
|
||||
"public_headers": [
|
||||
"foo.h"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "mojom"))
|
||||
|
||||
from mojom.generate.generator import WriteFile
|
||||
|
||||
def ReadTypemap(path):
|
||||
with open(path) as f:
|
||||
return json.load(f)['c++']
|
||||
|
||||
|
||||
def LoadCppTypemapConfig(path):
|
||||
configs = {}
|
||||
with open(path) as f:
|
||||
for config in json.load(f):
|
||||
for entry in config['types']:
|
||||
configs[entry['mojom']] = {
|
||||
'typename': entry['cpp'],
|
||||
'forward_declaration': entry.get('forward_declaration', None),
|
||||
'public_headers': config.get('traits_headers', []),
|
||||
'traits_headers': config.get('traits_private_headers', []),
|
||||
'copyable_pass_by_value': entry.get('copyable_pass_by_value',
|
||||
False),
|
||||
'default_constructible': entry.get('default_constructible', True),
|
||||
'force_serialize': entry.get('force_serialize', False),
|
||||
'hashable': entry.get('hashable', False),
|
||||
'move_only': entry.get('move_only', False),
|
||||
'nullable_is_same_type': entry.get('nullable_is_same_type', False),
|
||||
'non_copyable_non_movable': False,
|
||||
}
|
||||
return configs
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument(
|
||||
'--dependency',
|
||||
type=str,
|
||||
action='append',
|
||||
default=[],
|
||||
help=('A path to another JSON typemap to merge into the output. '
|
||||
'This may be repeated to merge multiple typemaps.'))
|
||||
parser.add_argument(
|
||||
'--cpp-typemap-config',
|
||||
type=str,
|
||||
action='store',
|
||||
dest='cpp_config_path',
|
||||
help=('A path to a single JSON-formatted typemap config as emitted by'
|
||||
'GN when processing a mojom_cpp_typemap build rule.'))
|
||||
parser.add_argument('--output',
|
||||
type=str,
|
||||
required=True,
|
||||
help='The path to which to write the generated JSON.')
|
||||
params, _ = parser.parse_known_args()
|
||||
typemaps = {}
|
||||
if params.cpp_config_path:
|
||||
typemaps = LoadCppTypemapConfig(params.cpp_config_path)
|
||||
missing = [path for path in params.dependency if not os.path.exists(path)]
|
||||
if missing:
|
||||
raise IOError('Missing dependencies: %s' % ', '.join(missing))
|
||||
for path in params.dependency:
|
||||
typemaps.update(ReadTypemap(path))
|
||||
|
||||
WriteFile(json.dumps({'c++': typemaps}, indent=2), params.output)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
47
utils/codegen/ipc/mojo/public/tools/bindings/minify_with_terser.py
Executable file
47
utils/codegen/ipc/mojo/public/tools/bindings/minify_with_terser.py
Executable file
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright 2023 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
#
|
||||
# This utility minifies JS files with terser.
|
||||
#
|
||||
# Instance of 'node' has no 'RunNode' member (no-member)
|
||||
# pylint: disable=no-member
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
_HERE_PATH = os.path.dirname(__file__)
|
||||
_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))
|
||||
_CWD = os.getcwd()
|
||||
sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
|
||||
import node
|
||||
import node_modules
|
||||
|
||||
|
||||
def MinifyFile(input_file, output_file):
|
||||
node.RunNode([
|
||||
node_modules.PathToTerser(), input_file, '--mangle', '--compress',
|
||||
'--comments', 'false', '--output', output_file
|
||||
])
|
||||
|
||||
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--input', required=True)
|
||||
parser.add_argument('--output', required=True)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
# Delete the output file if it already exists. It may be a sym link to the
|
||||
# input, because in non-optimized/pre-Terser builds the input file is copied
|
||||
# to the output location with gn copy().
|
||||
out_path = os.path.join(_CWD, args.output)
|
||||
if (os.path.exists(out_path)):
|
||||
os.remove(out_path)
|
||||
|
||||
MinifyFile(os.path.join(_CWD, args.input), out_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
2118
utils/codegen/ipc/mojo/public/tools/bindings/mojom.gni
Normal file
2118
utils/codegen/ipc/mojo/public/tools/bindings/mojom.gni
Normal file
File diff suppressed because it is too large
Load diff
424
utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
Executable file
424
utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
Executable file
|
@ -0,0 +1,424 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2013 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""The frontend for the Mojo bindings system."""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
|
||||
import hashlib
|
||||
import importlib
|
||||
import json
|
||||
import os
|
||||
import pprint
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
|
||||
# Disable lint check for finding modules:
|
||||
# pylint: disable=F0401
|
||||
|
||||
def _GetDirAbove(dirname):
|
||||
"""Returns the directory "above" this file containing |dirname| (which must
|
||||
also be "above" this file)."""
|
||||
path = os.path.abspath(__file__)
|
||||
while True:
|
||||
path, tail = os.path.split(path)
|
||||
assert tail
|
||||
if tail == dirname:
|
||||
return path
|
||||
|
||||
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "mojom"))
|
||||
|
||||
from mojom.error import Error
|
||||
import mojom.fileutil as fileutil
|
||||
from mojom.generate.module import Module
|
||||
from mojom.generate import template_expander
|
||||
from mojom.generate import translate
|
||||
from mojom.generate.generator import WriteFile
|
||||
|
||||
sys.path.append(
|
||||
os.path.join(_GetDirAbove("mojo"), "tools", "diagnosis"))
|
||||
import crbug_1001171
|
||||
|
||||
|
||||
_BUILTIN_GENERATORS = {
|
||||
"c++": "mojom_cpp_generator",
|
||||
"javascript": "mojom_js_generator",
|
||||
"java": "mojom_java_generator",
|
||||
"mojolpm": "mojom_mojolpm_generator",
|
||||
"typescript": "mojom_ts_generator",
|
||||
}
|
||||
|
||||
_BUILTIN_CHECKS = {
|
||||
"attributes": "mojom_attributes_check",
|
||||
"definitions": "mojom_definitions_check",
|
||||
"features": "mojom_interface_feature_check",
|
||||
"restrictions": "mojom_restrictions_check",
|
||||
}
|
||||
|
||||
|
||||
def LoadGenerators(generators_string):
|
||||
if not generators_string:
|
||||
return {} # No generators.
|
||||
|
||||
generators = {}
|
||||
for generator_name in [s.strip() for s in generators_string.split(",")]:
|
||||
language = generator_name.lower()
|
||||
if language not in _BUILTIN_GENERATORS:
|
||||
print("Unknown generator name %s" % generator_name)
|
||||
sys.exit(1)
|
||||
generator_module = importlib.import_module(
|
||||
"generators.%s" % _BUILTIN_GENERATORS[language])
|
||||
generators[language] = generator_module
|
||||
return generators
|
||||
|
||||
|
||||
def LoadChecks(checks_string):
|
||||
if not checks_string:
|
||||
return {} # No checks.
|
||||
|
||||
checks = {}
|
||||
for check_name in [s.strip() for s in checks_string.split(",")]:
|
||||
check = check_name.lower()
|
||||
if check not in _BUILTIN_CHECKS:
|
||||
print("Unknown check name %s" % check_name)
|
||||
sys.exit(1)
|
||||
check_module = importlib.import_module("checks.%s" % _BUILTIN_CHECKS[check])
|
||||
checks[check] = check_module
|
||||
return checks
|
||||
|
||||
|
||||
def MakeImportStackMessage(imported_filename_stack):
|
||||
"""Make a (human-readable) message listing a chain of imports. (Returned
|
||||
string begins with a newline (if nonempty) and does not end with one.)"""
|
||||
return ''.join(
|
||||
reversed(["\n %s was imported by %s" % (a, b) for (a, b) in \
|
||||
zip(imported_filename_stack[1:], imported_filename_stack)]))
|
||||
|
||||
|
||||
class RelativePath:
|
||||
"""Represents a path relative to the source tree or generated output dir."""
|
||||
|
||||
def __init__(self, path, source_root, output_dir):
|
||||
self.path = path
|
||||
if path.startswith(source_root):
|
||||
self.root = source_root
|
||||
elif path.startswith(output_dir):
|
||||
self.root = output_dir
|
||||
else:
|
||||
raise Exception("Invalid input path %s" % path)
|
||||
|
||||
def relative_path(self):
|
||||
return os.path.relpath(
|
||||
os.path.abspath(self.path), os.path.abspath(self.root))
|
||||
|
||||
|
||||
def _GetModulePath(path, output_dir):
|
||||
return os.path.join(output_dir, path.relative_path() + '-module')
|
||||
|
||||
|
||||
def ScrambleMethodOrdinals(interfaces, salt):
|
||||
already_generated = set()
|
||||
for interface in interfaces:
|
||||
i = 0
|
||||
already_generated.clear()
|
||||
for method in interface.methods:
|
||||
if method.explicit_ordinal is not None:
|
||||
continue
|
||||
while True:
|
||||
i = i + 1
|
||||
if i == 1000000:
|
||||
raise Exception("Could not generate %d method ordinals for %s" %
|
||||
(len(interface.methods), interface.mojom_name))
|
||||
# Generate a scrambled method.ordinal value. The algorithm doesn't have
|
||||
# to be very strong, cryptographically. It just needs to be non-trivial
|
||||
# to guess the results without the secret salt, in order to make it
|
||||
# harder for a compromised process to send fake Mojo messages.
|
||||
sha256 = hashlib.sha256(salt)
|
||||
sha256.update(interface.mojom_name.encode('utf-8'))
|
||||
sha256.update(str(i).encode('utf-8'))
|
||||
# Take the first 4 bytes as a little-endian uint32.
|
||||
ordinal = struct.unpack('<L', sha256.digest()[:4])[0]
|
||||
# Trim to 31 bits, so it always fits into a Java (signed) int.
|
||||
ordinal = ordinal & 0x7fffffff
|
||||
if ordinal in already_generated:
|
||||
continue
|
||||
already_generated.add(ordinal)
|
||||
method.ordinal = ordinal
|
||||
method.ordinal_comment = (
|
||||
'The %s value is based on sha256(salt + "%s%d").' %
|
||||
(ordinal, interface.mojom_name, i))
|
||||
break
|
||||
|
||||
|
||||
def ReadFileContents(filename):
|
||||
with open(filename, 'rb') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
class MojomProcessor:
|
||||
"""Takes parsed mojom modules and generates language bindings from them.
|
||||
|
||||
Attributes:
|
||||
_processed_files: {Dict[str, mojom.generate.module.Module]} Mapping from
|
||||
relative mojom filename paths to the module AST for that mojom file.
|
||||
"""
|
||||
def __init__(self, should_generate):
|
||||
self._should_generate = should_generate
|
||||
self._processed_files = {}
|
||||
self._typemap = {}
|
||||
|
||||
def LoadTypemaps(self, typemaps):
|
||||
# Support some very simple single-line comments in typemap JSON.
|
||||
comment_expr = r"^\s*//.*$"
|
||||
def no_comments(line):
|
||||
return not re.match(comment_expr, line)
|
||||
for filename in typemaps:
|
||||
with open(filename) as f:
|
||||
typemaps = json.loads("".join(filter(no_comments, f.readlines())))
|
||||
for language, typemap in typemaps.items():
|
||||
language_map = self._typemap.get(language, {})
|
||||
language_map.update(typemap)
|
||||
self._typemap[language] = language_map
|
||||
if 'c++' in self._typemap:
|
||||
self._typemap['mojolpm'] = self._typemap['c++']
|
||||
|
||||
def _GenerateModule(self, args, remaining_args, check_modules,
|
||||
generator_modules, rel_filename, imported_filename_stack):
|
||||
# Return the already-generated module.
|
||||
if rel_filename.path in self._processed_files:
|
||||
return self._processed_files[rel_filename.path]
|
||||
|
||||
if rel_filename.path in imported_filename_stack:
|
||||
print("%s: Error: Circular dependency" % rel_filename.path + \
|
||||
MakeImportStackMessage(imported_filename_stack + [rel_filename.path]))
|
||||
sys.exit(1)
|
||||
|
||||
module_path = _GetModulePath(rel_filename, args.output_dir)
|
||||
with open(module_path, 'rb') as f:
|
||||
module = Module.Load(f)
|
||||
|
||||
if args.scrambled_message_id_salt_paths:
|
||||
salt = b''.join(
|
||||
map(ReadFileContents, args.scrambled_message_id_salt_paths))
|
||||
ScrambleMethodOrdinals(module.interfaces, salt)
|
||||
|
||||
if self._should_generate(rel_filename.path):
|
||||
# Run checks on module first.
|
||||
for check_module in check_modules.values():
|
||||
checker = check_module.Check(module)
|
||||
checker.CheckModule()
|
||||
# Then run generation.
|
||||
for language, generator_module in generator_modules.items():
|
||||
generator = generator_module.Generator(
|
||||
module, args.output_dir, typemap=self._typemap.get(language, {}),
|
||||
variant=args.variant, bytecode_path=args.bytecode_path,
|
||||
for_blink=args.for_blink,
|
||||
js_generate_struct_deserializers=\
|
||||
args.js_generate_struct_deserializers,
|
||||
export_attribute=args.export_attribute,
|
||||
export_header=args.export_header,
|
||||
generate_non_variant_code=args.generate_non_variant_code,
|
||||
support_lazy_serialization=args.support_lazy_serialization,
|
||||
disallow_native_types=args.disallow_native_types,
|
||||
disallow_interfaces=args.disallow_interfaces,
|
||||
generate_message_ids=args.generate_message_ids,
|
||||
generate_fuzzing=args.generate_fuzzing,
|
||||
enable_kythe_annotations=args.enable_kythe_annotations,
|
||||
extra_cpp_template_paths=args.extra_cpp_template_paths,
|
||||
generate_extra_cpp_only=args.generate_extra_cpp_only)
|
||||
filtered_args = []
|
||||
if hasattr(generator_module, 'GENERATOR_PREFIX'):
|
||||
prefix = '--' + generator_module.GENERATOR_PREFIX + '_'
|
||||
filtered_args = [arg for arg in remaining_args
|
||||
if arg.startswith(prefix)]
|
||||
generator.GenerateFiles(filtered_args)
|
||||
|
||||
# Save result.
|
||||
self._processed_files[rel_filename.path] = module
|
||||
return module
|
||||
|
||||
|
||||
def _Generate(args, remaining_args):
|
||||
if args.variant == "none":
|
||||
args.variant = None
|
||||
|
||||
for idx, import_dir in enumerate(args.import_directories):
|
||||
tokens = import_dir.split(":")
|
||||
if len(tokens) >= 2:
|
||||
args.import_directories[idx] = RelativePath(tokens[0], tokens[1],
|
||||
args.output_dir)
|
||||
else:
|
||||
args.import_directories[idx] = RelativePath(tokens[0], args.depth,
|
||||
args.output_dir)
|
||||
generator_modules = LoadGenerators(args.generators_string)
|
||||
check_modules = LoadChecks(args.checks_string)
|
||||
|
||||
fileutil.EnsureDirectoryExists(args.output_dir)
|
||||
|
||||
processor = MojomProcessor(lambda filename: filename in args.filename)
|
||||
processor.LoadTypemaps(set(args.typemaps))
|
||||
|
||||
if args.filelist:
|
||||
with open(args.filelist) as f:
|
||||
args.filename.extend(f.read().split())
|
||||
|
||||
for filename in args.filename:
|
||||
processor._GenerateModule(
|
||||
args, remaining_args, check_modules, generator_modules,
|
||||
RelativePath(filename, args.depth, args.output_dir), [])
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def _Precompile(args, _):
|
||||
generator_modules = LoadGenerators(",".join(_BUILTIN_GENERATORS.keys()))
|
||||
|
||||
template_expander.PrecompileTemplates(generator_modules, args.output_dir)
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate bindings from mojom files.")
|
||||
parser.add_argument("--use_bundled_pylibs", action="store_true",
|
||||
help="use Python modules bundled in the SDK")
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output_dir",
|
||||
dest="output_dir",
|
||||
default=".",
|
||||
help="output directory for generated files")
|
||||
|
||||
subparsers = parser.add_subparsers()
|
||||
|
||||
generate_parser = subparsers.add_parser(
|
||||
"generate", description="Generate bindings from mojom files.")
|
||||
generate_parser.add_argument("filename", nargs="*",
|
||||
help="mojom input file")
|
||||
generate_parser.add_argument("--filelist", help="mojom input file list")
|
||||
generate_parser.add_argument("-d", "--depth", dest="depth", default=".",
|
||||
help="depth from source root")
|
||||
generate_parser.add_argument("-g",
|
||||
"--generators",
|
||||
dest="generators_string",
|
||||
metavar="GENERATORS",
|
||||
default="c++,javascript,java,mojolpm",
|
||||
help="comma-separated list of generators")
|
||||
generate_parser.add_argument("-c",
|
||||
"--checks",
|
||||
dest="checks_string",
|
||||
metavar="CHECKS",
|
||||
default=",".join(_BUILTIN_CHECKS.keys()),
|
||||
help="comma-separated list of checks")
|
||||
generate_parser.add_argument(
|
||||
"--gen_dir", dest="gen_directories", action="append", metavar="directory",
|
||||
default=[], help="add a directory to be searched for the syntax trees.")
|
||||
generate_parser.add_argument(
|
||||
"-I", dest="import_directories", action="append", metavar="directory",
|
||||
default=[],
|
||||
help="add a directory to be searched for import files. The depth from "
|
||||
"source root can be specified for each import by appending it after "
|
||||
"a colon")
|
||||
generate_parser.add_argument("--typemap", action="append", metavar="TYPEMAP",
|
||||
default=[], dest="typemaps",
|
||||
help="apply TYPEMAP to generated output")
|
||||
generate_parser.add_argument("--variant", dest="variant", default=None,
|
||||
help="output a named variant of the bindings")
|
||||
generate_parser.add_argument(
|
||||
"--bytecode_path", required=True, help=(
|
||||
"the path from which to load template bytecode; to generate template "
|
||||
"bytecode, run %s precompile BYTECODE_PATH" % os.path.basename(
|
||||
sys.argv[0])))
|
||||
generate_parser.add_argument("--for_blink", action="store_true",
|
||||
help="Use WTF types as generated types for mojo "
|
||||
"string/array/map.")
|
||||
generate_parser.add_argument(
|
||||
"--js_generate_struct_deserializers", action="store_true",
|
||||
help="Generate javascript deserialize methods for structs in "
|
||||
"mojom-lite.js file")
|
||||
generate_parser.add_argument(
|
||||
"--export_attribute", default="",
|
||||
help="Optional attribute to specify on class declaration to export it "
|
||||
"for the component build.")
|
||||
generate_parser.add_argument(
|
||||
"--export_header", default="",
|
||||
help="Optional header to include in the generated headers to support the "
|
||||
"component build.")
|
||||
generate_parser.add_argument(
|
||||
"--generate_non_variant_code", action="store_true",
|
||||
help="Generate code that is shared by different variants.")
|
||||
generate_parser.add_argument(
|
||||
"--scrambled_message_id_salt_path",
|
||||
dest="scrambled_message_id_salt_paths",
|
||||
help="If non-empty, the path to a file whose contents should be used as"
|
||||
"a salt for generating scrambled message IDs. If this switch is specified"
|
||||
"more than once, the contents of all salt files are concatenated to form"
|
||||
"the salt value.", default=[], action="append")
|
||||
generate_parser.add_argument(
|
||||
"--support_lazy_serialization",
|
||||
help="If set, generated bindings will serialize lazily when possible.",
|
||||
action="store_true")
|
||||
generate_parser.add_argument(
|
||||
"--extra_cpp_template_paths",
|
||||
dest="extra_cpp_template_paths",
|
||||
action="append",
|
||||
metavar="path_to_template",
|
||||
default=[],
|
||||
help="Provide a path to a new template (.tmpl) that is used to generate "
|
||||
"additional C++ source/header files ")
|
||||
generate_parser.add_argument(
|
||||
"--generate_extra_cpp_only",
|
||||
help="If set and extra_cpp_template_paths provided, will only generate"
|
||||
"extra_cpp_template related C++ bindings",
|
||||
action="store_true")
|
||||
generate_parser.add_argument(
|
||||
"--disallow_native_types",
|
||||
help="Disallows the [Native] attribute to be specified on structs or "
|
||||
"enums within the mojom file.", action="store_true")
|
||||
generate_parser.add_argument(
|
||||
"--disallow_interfaces",
|
||||
help="Disallows interface definitions within the mojom file. It is an "
|
||||
"error to specify this flag when processing a mojom file which defines "
|
||||
"any interface.", action="store_true")
|
||||
generate_parser.add_argument(
|
||||
"--generate_message_ids",
|
||||
help="Generates only the message IDs header for C++ bindings. Note that "
|
||||
"this flag only matters if --generate_non_variant_code is also "
|
||||
"specified.", action="store_true")
|
||||
generate_parser.add_argument(
|
||||
"--generate_fuzzing",
|
||||
action="store_true",
|
||||
help="Generates additional bindings for fuzzing in JS.")
|
||||
generate_parser.add_argument(
|
||||
"--enable_kythe_annotations",
|
||||
action="store_true",
|
||||
help="Adds annotations for kythe metadata generation.")
|
||||
|
||||
generate_parser.set_defaults(func=_Generate)
|
||||
|
||||
precompile_parser = subparsers.add_parser("precompile",
|
||||
description="Precompile templates for the mojom bindings generator.")
|
||||
precompile_parser.set_defaults(func=_Precompile)
|
||||
|
||||
args, remaining_args = parser.parse_known_args()
|
||||
return args.func(args, remaining_args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with crbug_1001171.DumpStateOnLookupError():
|
||||
ret = main()
|
||||
# Exit without running GC, which can save multiple seconds due to the large
|
||||
# number of object created. But flush is necessary as os._exit doesn't do
|
||||
# that.
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
os._exit(ret)
|
|
@ -0,0 +1,62 @@
|
|||
# Copyright 2014 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import unittest
|
||||
|
||||
from mojom_bindings_generator import MakeImportStackMessage
|
||||
from mojom_bindings_generator import ScrambleMethodOrdinals
|
||||
|
||||
|
||||
class FakeIface:
|
||||
def __init__(self):
|
||||
self.mojom_name = None
|
||||
self.methods = None
|
||||
|
||||
|
||||
class FakeMethod:
|
||||
def __init__(self, explicit_ordinal=None):
|
||||
self.explicit_ordinal = explicit_ordinal
|
||||
self.ordinal = explicit_ordinal
|
||||
self.ordinal_comment = None
|
||||
|
||||
|
||||
class MojoBindingsGeneratorTest(unittest.TestCase):
|
||||
"""Tests mojo_bindings_generator."""
|
||||
|
||||
def testMakeImportStackMessage(self):
|
||||
"""Tests MakeImportStackMessage()."""
|
||||
self.assertEqual(MakeImportStackMessage(["x"]), "")
|
||||
self.assertEqual(MakeImportStackMessage(["x", "y"]),
|
||||
"\n y was imported by x")
|
||||
self.assertEqual(MakeImportStackMessage(["x", "y", "z"]),
|
||||
"\n z was imported by y\n y was imported by x")
|
||||
|
||||
def testScrambleMethodOrdinals(self):
|
||||
"""Tests ScrambleMethodOrdinals()."""
|
||||
interface = FakeIface()
|
||||
interface.mojom_name = 'RendererConfiguration'
|
||||
interface.methods = [
|
||||
FakeMethod(),
|
||||
FakeMethod(),
|
||||
FakeMethod(),
|
||||
FakeMethod(explicit_ordinal=42)
|
||||
]
|
||||
ScrambleMethodOrdinals([interface], "foo".encode('utf-8'))
|
||||
# These next three values are hard-coded. If the generation algorithm
|
||||
# changes from being based on sha256(seed + interface.name + str(i)) then
|
||||
# these numbers will obviously need to change too.
|
||||
#
|
||||
# Note that hashlib.sha256('fooRendererConfiguration1').digest()[:4] is
|
||||
# '\xa5\xbc\xf9\xca' and that hex(1257880741) = '0x4af9bca5'. The
|
||||
# difference in 0x4a vs 0xca is because we only take 31 bits.
|
||||
self.assertEqual(interface.methods[0].ordinal, 1257880741)
|
||||
self.assertEqual(interface.methods[1].ordinal, 631133653)
|
||||
self.assertEqual(interface.methods[2].ordinal, 549336076)
|
||||
|
||||
# Explicit method ordinals should not be scrambled.
|
||||
self.assertEqual(interface.methods[3].ordinal, 42)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
58
utils/codegen/ipc/mojo/public/tools/bindings/validate_typemap_config.py
Executable file
58
utils/codegen/ipc/mojo/public/tools/bindings/validate_typemap_config.py
Executable file
|
@ -0,0 +1,58 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2020 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def CheckCppTypemapConfigs(target_name, config_filename, out_filename):
|
||||
_SUPPORTED_CONFIG_KEYS = set([
|
||||
'types', 'traits_headers', 'traits_private_headers', 'traits_sources',
|
||||
'traits_deps', 'traits_public_deps'
|
||||
])
|
||||
_SUPPORTED_TYPE_KEYS = set([
|
||||
'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable',
|
||||
'move_only', 'nullable_is_same_type', 'forward_declaration',
|
||||
'default_constructible'
|
||||
])
|
||||
with open(config_filename, 'r') as f:
|
||||
for config in json.load(f):
|
||||
for key in config.keys():
|
||||
if key not in _SUPPORTED_CONFIG_KEYS:
|
||||
raise ValueError('Invalid typemap property "%s" when processing %s' %
|
||||
(key, target_name))
|
||||
|
||||
types = config.get('types')
|
||||
if not types:
|
||||
raise ValueError('Typemap for %s must specify at least one type to map'
|
||||
% target_name)
|
||||
|
||||
for entry in types:
|
||||
for key in entry.keys():
|
||||
if key not in _SUPPORTED_TYPE_KEYS:
|
||||
raise IOError(
|
||||
'Invalid type property "%s" in typemap for "%s" on target %s' %
|
||||
(key, entry.get('mojom', '(unknown)'), target_name))
|
||||
|
||||
with open(out_filename, 'w') as f:
|
||||
f.truncate(0)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
_, args = parser.parse_known_args()
|
||||
if len(args) != 3:
|
||||
print('Usage: validate_typemap_config.py target_name config_filename '
|
||||
'stamp_filename')
|
||||
sys.exit(1)
|
||||
|
||||
CheckCppTypemapConfigs(args[0], args[1], args[2])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Add table
Add a link
Reference in a new issue