[libcamera-devel] [PATCH 8/8] utils: ipc: Update mojo
Milan Zamazal
mzamazal at redhat.com
Fri Jan 5 14:04:37 CET 2024
Laurent Pinchart <laurent.pinchart at ideasonboard.com> writes:
> Update mojo from commit
>
> 9be4263648d7d1a04bb78be75df53f56449a5e3a "Updating trunk VERSION from 6225.0 to 6226.0"
>
> from the Chromium repository.
I don't know which one but when fetching the required components from
chromium.googlesource.com manually and applying update-mojo.sh, I get the same
tree. I also checked it compiles in my environment, where it previously failed
due to the Python incompatibilities. So the change looks all right to me.
Thank you for fixing this.
> The update-mojo.sh script was used for this update.
>
> Bug: https://bugs.libcamera.org/show_bug.cgi?id=206
> Signed-off-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
Reviewed-by: Milan Zamazal <mzamazal at redhat.com>
> ---
> utils/ipc/mojo/README | 2 +-
> utils/ipc/mojo/public/LICENSE | 2 +-
> utils/ipc/mojo/public/tools/BUILD.gn | 8 +-
> utils/ipc/mojo/public/tools/bindings/BUILD.gn | 36 +-
> .../ipc/mojo/public/tools/bindings/README.md | 239 +++--
> .../public/tools/bindings/checks/__init__.py | 0
> .../bindings/checks/mojom_attributes_check.py | 170 ++++
> .../checks/mojom_attributes_check_unittest.py | 194 ++++
> .../checks/mojom_definitions_check.py | 34 +
> .../checks/mojom_interface_feature_check.py | 62 ++
> .../mojom_interface_feature_check_unittest.py | 173 ++++
> .../checks/mojom_restrictions_check.py | 102 +++
> .../mojom_restrictions_checks_unittest.py | 254 ++++++
> .../chromium_bindings_configuration.gni | 51 --
> .../tools/bindings/compile_typescript.py | 27 -
> .../tools/bindings/concatenate-files.py | 5 +-
> ...concatenate_and_replace_closure_exports.py | 10 +-
> .../bindings/format_typemap_generator_args.py | 36 -
> .../tools/bindings/gen_data_files_list.py | 2 +-
> .../tools/bindings/generate_type_mappings.py | 4 +-
> .../tools/bindings/minify_with_terser.py | 47 +
> .../ipc/mojo/public/tools/bindings/mojom.gni | 853 ++++++++++--------
> .../bindings/mojom_bindings_generator.py | 62 +-
> .../mojom_bindings_generator_unittest.py | 6 +-
> .../tools/bindings/mojom_types_downgrader.py | 119 ---
> .../tools/bindings/validate_typemap_config.py | 5 +-
> utils/ipc/mojo/public/tools/mojom/BUILD.gn | 18 +
> .../mojom/check_stable_mojom_compatibility.py | 69 +-
> ...eck_stable_mojom_compatibility_unittest.py | 87 +-
> .../mojo/public/tools/mojom/const_unittest.py | 2 +-
> .../mojo/public/tools/mojom/enum_unittest.py | 30 +-
> .../public/tools/mojom/feature_unittest.py | 84 ++
> .../mojo/public/tools/mojom/mojom/BUILD.gn | 3 +-
> .../mojo/public/tools/mojom/mojom/error.py | 2 +-
> .../mojo/public/tools/mojom/mojom/fileutil.py | 3 +-
> .../tools/mojom/mojom/fileutil_unittest.py | 7 +-
> .../tools/mojom/mojom/generate/check.py | 26 +
> .../mojom/mojom/generate/constant_resolver.py | 93 --
> .../tools/mojom/mojom/generate/generator.py | 11 +-
> .../mojom/generate/generator_unittest.py | 9 +-
> .../tools/mojom/mojom/generate/module.py | 783 +++++++++++-----
> .../mojom/mojom/generate/module_unittest.py | 2 +-
> .../public/tools/mojom/mojom/generate/pack.py | 151 +++-
> .../mojom/mojom/generate/pack_unittest.py | 30 +-
> .../mojom/mojom/generate/template_expander.py | 2 +-
> .../tools/mojom/mojom/generate/translate.py | 464 +++++++++-
> .../mojom/generate/translate_unittest.py | 82 +-
> .../public/tools/mojom/mojom/parse/ast.py | 145 +--
> .../tools/mojom/mojom/parse/ast_unittest.py | 12 +-
> .../mojom/mojom/parse/conditional_features.py | 21 +-
> .../parse/conditional_features_unittest.py | 155 +++-
> .../public/tools/mojom/mojom/parse/lexer.py | 8 +-
> .../tools/mojom/mojom/parse/lexer_unittest.py | 10 +-
> .../public/tools/mojom/mojom/parse/parser.py | 108 ++-
> .../mojom/mojom/parse/parser_unittest.py | 39 +-
> .../mojo/public/tools/mojom/mojom_parser.py | 119 ++-
> .../tools/mojom/mojom_parser_test_case.py | 6 +-
> .../tools/mojom/mojom_parser_unittest.py | 31 +-
> .../tools/mojom/stable_attribute_unittest.py | 2 +-
> .../mojo/public/tools/mojom/union_unittest.py | 44 +
> .../mojom/version_compatibility_unittest.py | 73 +-
> .../public/tools/run_all_python_unittests.py | 8 +-
> utils/ipc/tools/README | 2 +-
> utils/ipc/tools/diagnosis/crbug_1001171.py | 2 +-
> 64 files changed, 3830 insertions(+), 1416 deletions(-)
> create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/__init__.py
> create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py
> create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
> create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py
> create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py
> create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py
> create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py
> create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py
> delete mode 100644 utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni
> delete mode 100644 utils/ipc/mojo/public/tools/bindings/compile_typescript.py
> delete mode 100755 utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py
> create mode 100755 utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
> delete mode 100755 utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py
> create mode 100644 utils/ipc/mojo/public/tools/mojom/BUILD.gn
> create mode 100644 utils/ipc/mojo/public/tools/mojom/feature_unittest.py
> create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
> delete mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py
> create mode 100644 utils/ipc/mojo/public/tools/mojom/union_unittest.py
>
> diff --git a/utils/ipc/mojo/README b/utils/ipc/mojo/README
> index d5c24fc3b5cb..961cabd222e3 100644
> --- a/utils/ipc/mojo/README
> +++ b/utils/ipc/mojo/README
> @@ -1,4 +1,4 @@
> # SPDX-License-Identifier: CC0-1.0
>
> -Files in this directory are imported from 9c138d992bfc of Chromium. Do not
> +Files in this directory are imported from 9be4263648d7 of Chromium. Do not
> modify them manually.
> diff --git a/utils/ipc/mojo/public/LICENSE b/utils/ipc/mojo/public/LICENSE
> index 972bb2edb099..513e8a6a0a1d 100644
> --- a/utils/ipc/mojo/public/LICENSE
> +++ b/utils/ipc/mojo/public/LICENSE
> @@ -1,4 +1,4 @@
> -// Copyright 2014 The Chromium Authors. All rights reserved.
> +// Copyright 2014 The Chromium Authors
> //
> // Redistribution and use in source and binary forms, with or without
> // modification, are permitted provided that the following conditions are
> diff --git a/utils/ipc/mojo/public/tools/BUILD.gn b/utils/ipc/mojo/public/tools/BUILD.gn
> index eb6391a65400..5328a34a3935 100644
> --- a/utils/ipc/mojo/public/tools/BUILD.gn
> +++ b/utils/ipc/mojo/public/tools/BUILD.gn
> @@ -1,4 +1,4 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# 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.
>
> @@ -10,7 +10,11 @@ group("mojo_python_unittests") {
> "run_all_python_unittests.py",
> "//testing/scripts/run_isolated_script_test.py",
> ]
> - deps = [ "//mojo/public/tools/mojom/mojom:tests" ]
> + deps = [
> + "//mojo/public/tools/bindings:tests",
> + "//mojo/public/tools/mojom:tests",
> + "//mojo/public/tools/mojom/mojom:tests",
> + ]
> data_deps = [
> "//testing:test_scripts_shared",
> "//third_party/catapult/third_party/typ/",
> diff --git a/utils/ipc/mojo/public/tools/bindings/BUILD.gn b/utils/ipc/mojo/public/tools/bindings/BUILD.gn
> index 3e2425327490..eeca73ea3d56 100644
> --- a/utils/ipc/mojo/public/tools/bindings/BUILD.gn
> +++ b/utils/ipc/mojo/public/tools/bindings/BUILD.gn
> @@ -1,24 +1,27 @@
> -# Copyright 2016 The Chromium Authors. All rights reserved.
> +# 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("//build/config/python.gni")
> import("//mojo/public/tools/bindings/mojom.gni")
> import("//third_party/jinja2/jinja2.gni")
>
> -# TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> -python2_action("precompile_templates") {
> +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",
> @@ -26,7 +29,6 @@ python2_action("precompile_templates") {
> "$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.cc.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",
> @@ -65,9 +67,6 @@ python2_action("precompile_templates") {
> "$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/externs/interface_definition.tmpl",
> - "$mojom_generator_root/generators/js_templates/externs/module.externs.tmpl",
> - "$mojom_generator_root/generators/js_templates/externs/struct_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",
> @@ -93,8 +92,11 @@ python2_action("precompile_templates") {
> "$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/mojom.tmpl",
> + "$mojom_generator_root/generators/ts_templates/struct_definition.tmpl",
> + "$mojom_generator_root/generators/ts_templates/union_definition.tmpl",
> ]
> script = mojom_generator_script
>
> @@ -102,8 +104,8 @@ python2_action("precompile_templates") {
> outputs = [
> "$target_gen_dir/cpp_templates.zip",
> "$target_gen_dir/java_templates.zip",
> - "$target_gen_dir/mojolpm_templates.zip",
> "$target_gen_dir/js_templates.zip",
> + "$target_gen_dir/mojolpm_templates.zip",
> "$target_gen_dir/ts_templates.zip",
> ]
> args = [
> @@ -113,3 +115,17 @@ python2_action("precompile_templates") {
> "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
> +}
> diff --git a/utils/ipc/mojo/public/tools/bindings/README.md b/utils/ipc/mojo/public/tools/bindings/README.md
> index 438824502280..b27b2d01b3fc 100644
> --- a/utils/ipc/mojo/public/tools/bindings/README.md
> +++ b/utils/ipc/mojo/public/tools/bindings/README.md
> @@ -96,7 +96,7 @@ for message parameters.
> | `string` | UTF-8 encoded string.
> | `array<T>` | Array of any Mojom type *T*; for example, `array<uint8>` or `array<array<string>>`.
> | `array<T, N>` | Fixed-length array of any Mojom type *T*. The parameter *N* must be an integral constant.
> -| `map<S, T>` | Associated array maping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type.
> +| `map<S, T>` | Associated array mapping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type.
> | `handle` | Generic Mojo handle. May be any type of handle, including a wrapped native platform handle.
> | `handle<message_pipe>` | Generic message pipe handle.
> | `handle<shared_buffer>` | Shared buffer handle.
> @@ -188,8 +188,8 @@ struct StringPair {
> };
>
> enum AnEnum {
> - YES,
> - NO
> + kYes,
> + kNo
> };
>
> interface SampleInterface {
> @@ -209,7 +209,7 @@ struct AllTheThings {
> uint64 unsigned_64bit_value;
> float float_value_32bit;
> double float_value_64bit;
> - AnEnum enum_value = AnEnum.YES;
> + AnEnum enum_value = AnEnum.kYes;
>
> // Strings may be nullable.
> string? maybe_a_string_maybe_not;
> @@ -300,14 +300,14 @@ within a module or nested within the namespace of some struct or interface:
> module business.mojom;
>
> enum Department {
> - SALES = 0,
> - DEV,
> + kSales = 0,
> + kDev,
> };
>
> struct Employee {
> enum Type {
> - FULL_TIME,
> - PART_TIME,
> + kFullTime,
> + kPartTime,
> };
>
> Type type;
> @@ -315,6 +315,9 @@ struct Employee {
> };
> ```
>
> +C++ constant-style enum value names are preferred as specified in the
> +[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Enumerator_Names).
> +
> Similar to C-style enums, individual values may be explicitly assigned within an
> enum definition. By default, values are based at zero and increment by
> 1 sequentially.
> @@ -336,8 +339,8 @@ struct Employee {
> const uint64 kInvalidId = 0;
>
> enum Type {
> - FULL_TIME,
> - PART_TIME,
> + kFullTime,
> + kPartTime,
> };
>
> uint64 id = kInvalidId;
> @@ -348,6 +351,37 @@ struct Employee {
> The effect of nested definitions on generated bindings varies depending on the
> target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages).
>
> +### Features
> +
> +Features can be declared with a `name` and `default_state` and can be attached
> +in mojo to interfaces or methods using the `RuntimeFeature` attribute. If the
> +feature is disabled at runtime, the method will crash and the interface will
> +refuse to be bound / instantiated. Features cannot be serialized to be sent over
> +IPC at this time.
> +
> +```
> +module experimental.mojom;
> +
> +feature kUseElevators {
> + const string name = "UseElevators";
> + const bool default_state = false;
> +}
> +
> +[RuntimeFeature=kUseElevators]
> +interface Elevator {
> + // This interface cannot be bound or called if the feature is disabled.
> +}
> +
> +interface Building {
> + // This method cannot be called if the feature is disabled.
> + [RuntimeFeature=kUseElevators]
> + CallElevator(int floor);
> +
> + // This method can be called.
> + RingDoorbell(int volume);
> +}
> +```
> +
> ### Interfaces
>
> An **interface** is a logical bundle of parameterized request messages. Each
> @@ -396,20 +430,33 @@ interesting attributes supported today.
> extreme caution, because it can lead to deadlocks otherwise.
>
> * **`[Default]`**:
> - The `Default` attribute may be used to specify an enumerator value that
> - will be used if an `Extensible` enumeration does not deserialize to a known
> - value on the receiver side, i.e. the sender is using a newer version of the
> - enum. This allows unknown values to be mapped to a well-defined value that can
> - be appropriately handled.
> + The `Default` attribute may be used to specify an enumerator value or union
> + field that will be used if an `Extensible` enumeration or union does not
> + deserialize to a known value on the receiver side, i.e. the sender is using a
> + newer version of the enum or union. This allows unknown values to be mapped to
> + a well-defined value that can be appropriately handled.
> +
> + Note: The `Default` field for a union must be of nullable or integral type.
> + When a union is defaulted to this field, the field takes on the default value
> + for its type: null for nullable types, and zero/false for integral types.
>
> * **`[Extensible]`**:
> - The `Extensible` attribute may be specified for any enum definition. This
> - essentially disables builtin range validation when receiving values of the
> - enum type in a message, allowing older bindings to tolerate unrecognized
> - values from newer versions of the enum.
> + The `Extensible` attribute may be specified for any enum or union definition.
> + For enums, this essentially disables builtin range validation when receiving
> + values of the enum type in a message, allowing older bindings to tolerate
> + unrecognized values from newer versions of the enum.
>
> - Note: in the future, an `Extensible` enumeration will require that a `Default`
> - enumerator value also be specified.
> + If an enum value within an extensible enum definition is affixed with the
> + `Default` attribute, out-of-range values for the enum will deserialize to that
> + default value. Only one enum value may be designated as the `Default`.
> +
> + Similarly, a union marked `Extensible` will deserialize to its `Default` field
> + when an unrecognized field is received. Extensible unions MUST specify exactly
> + one `Default` field, and the field must be of nullable or integral type. When
> + defaulted to this field, the value is always null/zero/false as appropriate.
> +
> + An `Extensible` enumeration REQUIRES that a `Default` value be specified,
> + so all new extensible enums should specify one.
>
> * **`[Native]`**:
> The `Native` attribute may be specified for an empty struct declaration to
> @@ -422,7 +469,10 @@ interesting attributes supported today.
> * **`[MinVersion=N]`**:
> The `MinVersion` attribute is used to specify the version at which a given
> field, enum value, interface method, or method parameter was introduced.
> - See [Versioning](#Versioning) for more details.
> + See [Versioning](#Versioning) for more details. `MinVersion` does not apply
> + to interfaces, structs or enums, but to the fields of those types.
> + `MinVersion` is not a module-global value, but it is ok to pretend it is by
> + skipping versions when adding fields or parameters.
>
> * **`[Stable]`**:
> The `Stable` attribute specifies that a given mojom type or interface
> @@ -442,13 +492,73 @@ interesting attributes supported today.
> string representation as specified by RFC 4122. New UUIDs can be generated
> with common tools such as `uuidgen`.
>
> +* **`[RuntimeFeature=feature]`**
> + The `RuntimeFeature` attribute should reference a mojo `feature`. If this
> + feature is enabled (e.g. using `--enable-features={feature.name}`) then the
> + interface behaves entirely as expected. If the feature is not enabled the
> + interface cannot be bound to a concrete receiver or remote - attempting to do
> + so will result in the receiver or remote being reset() to an unbound state.
> + Note that this is a different concept to the build-time `EnableIf` directive.
> + `RuntimeFeature` is currently only supported for C++ bindings and has no
> + effect for, say, Java or TypeScript bindings (see https://crbug.com/1278253).
> +
> * **`[EnableIf=value]`**:
> The `EnableIf` attribute is used to conditionally enable definitions when the
> mojom is parsed. If the `mojom` target in the GN file does not include the
> 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.
> + 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.
> +
> +* **`[EnableIfNot=value]`**:
> + The `EnableIfNot` attribute is used to conditionally enable definitions when
> + the mojom is parsed. If the `mojom` target in the GN file includes the
> + 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`.
> +
> +* **`[ServiceSandbox=value]`**:
> + The `ServiceSandbox` attribute is used in Chromium to tag which sandbox a
> + service hosting an implementation of interface will be launched in. This only
> + applies to `C++` bindings. `value` should match a constant defined in an
> + imported `sandbox.mojom.Sandbox` enum (for Chromium this is
> + `//sandbox/policy/mojom/sandbox.mojom`), such as `kService`.
> +
> +* **`[RequireContext=enum]`**:
> + The `RequireContext` attribute is used in Chromium to tag interfaces that
> + should be passed (as remotes or receivers) only to privileged process
> + contexts. The process context must be an enum that is imported into the
> + mojom that defines the tagged interface. `RequireContext` may be used in
> + future to DCHECK or CHECK if remotes are made available in contexts that
> + conflict with the one provided in the interface definition. Process contexts
> + are not the same as the sandbox a process is running in, but will reflect
> + the set of capabilities provided to the service.
> +
> +* **`[AllowedContext=enum]`**:
> + The `AllowedContext` attribute is used in Chromium to tag methods that pass
> + remotes or receivers of interfaces that are marked with a `RequireContext`
> + attribute. The enum provided on the method must be equal or better (lower
> + numerically) than the one required on the interface being passed. At present
> + failing to specify an adequate `AllowedContext` value will cause mojom
> + generation to fail at compile time. In future DCHECKs or CHECKs might be
> + added to enforce that method is only called from a process context that meets
> + the given `AllowedContext` value. The enum must of the same type as that
> + specified in the interface's `RequireContext` attribute. Adding an
> + `AllowedContext` attribute to a method is a strong indication that you need
> + a detailed security review of your design - please reach out to the security
> + team.
> +
> +* **`[SupportsUrgent]`**:
> + The `SupportsUrgent` attribute is used in conjunction with
> + `mojo::UrgentMessageScope` in Chromium to tag messages as having high
> + priority. The IPC layer notifies the underlying scheduler upon both receiving
> + and processing an urgent message. At present, this attribute only affects
> + channel associated messages in the renderer process.
>
> ## Generated Code For Target Languages
>
> @@ -495,9 +605,9 @@ values. For example if a Mojom declares the enum:
>
> ``` cpp
> enum AdvancedBoolean {
> - TRUE = 0,
> - FALSE = 1,
> - FILE_NOT_FOUND = 2,
> + kTrue = 0,
> + kFalse = 1,
> + kFileNotFound = 2,
> };
> ```
>
> @@ -550,10 +660,16 @@ See the documentation for
>
> *** note
> **NOTE:** You don't need to worry about versioning if you don't care about
> -backwards compatibility. Specifically, all parts of Chrome are updated
> -atomically today and there is not yet any possibility of any two Chrome
> -processes communicating with two different versions of any given Mojom
> -interface.
> +backwards compatibility. Today, all parts of the Chrome browser are
> +updated atomically and there is not yet any possibility of any two
> +Chrome processes communicating with two different versions of any given Mojom
> +interface. On Chrome OS, there are several places where versioning is required.
> +For example,
> +[ARC++](https://developer.android.com/chrome-os/intro)
> +uses versioned mojo to send IPC to the Android container.
> +Likewise, the
> +[Lacros](/docs/lacros.md)
> +browser uses versioned mojo to talk to the ash system UI.
> ***
>
> Services extend their interfaces to support new features over time, and clients
> @@ -593,8 +709,8 @@ struct Employee {
>
> *** note
> **NOTE:** Mojo object or handle types added with a `MinVersion` **MUST** be
> -optional (nullable). See [Primitive Types](#Primitive-Types) for details on
> -nullable values.
> +optional (nullable) or primitive. See [Primitive Types](#Primitive-Types) for
> +details on nullable values.
> ***
>
> By default, fields belong to version 0. New fields must be appended to the
> @@ -624,10 +740,10 @@ the following hard constraints:
> * For any given struct or interface, if any field or method explicitly specifies
> an ordinal value, all fields or methods must explicitly specify an ordinal
> value.
> -* For an *N*-field struct or *N*-method interface, the set of explicitly
> - assigned ordinal values must be limited to the range *[0, N-1]*. Interfaces
> - should include placeholder methods to fill the ordinal positions of removed
> - methods (for example "Unused_Message_7 at 7()" or "RemovedMessage at 42()", etc).
> +* For an *N*-field struct, the set of explicitly assigned ordinal values must be
> + limited to the range *[0, N-1]*. Structs should include placeholder fields
> + to fill the ordinal positions of removed fields (for example "Unused_Field"
> + or "RemovedField", etc).
>
> You may reorder fields, but you must ensure that the ordinal values of existing
> fields remain unchanged. For example, the following struct remains
> @@ -652,6 +768,24 @@ There are two dimensions on which an interface can be extended
> that the version number is scoped to the whole interface rather than to any
> individual parameter list.
>
> +``` cpp
> +// Old version:
> +interface HumanResourceDatabase {
> + QueryEmployee(uint64 id) => (Employee? employee);
> +};
> +
> +// New version:
> +interface HumanResourceDatabase {
> + QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)
> + => (Employee? employee,
> + [MinVersion=1] array<uint8>? finger_print);
> +};
> +```
> +
> +Similar to [versioned structs](#Versioned-Structs), when you pass the parameter
> +list of a request or response method to a destination using an older version of
> +an interface, unrecognized fields are silently discarded.
> +
> Please note that adding a response to a message which did not previously
> expect a response is a not a backwards-compatible change.
>
> @@ -664,17 +798,12 @@ For example:
> ``` cpp
> // Old version:
> interface HumanResourceDatabase {
> - AddEmployee(Employee employee) => (bool success);
> QueryEmployee(uint64 id) => (Employee? employee);
> };
>
> // New version:
> interface HumanResourceDatabase {
> - AddEmployee(Employee employee) => (bool success);
> -
> - QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)
> - => (Employee? employee,
> - [MinVersion=1] array<uint8>? finger_print);
> + QueryEmployee(uint64 id) => (Employee? employee);
>
> [MinVersion=1]
> AttachFingerPrint(uint64 id, array<uint8> finger_print)
> @@ -682,10 +811,7 @@ interface HumanResourceDatabase {
> };
> ```
>
> -Similar to [versioned structs](#Versioned-Structs), when you pass the parameter
> -list of a request or response method to a destination using an older version of
> -an interface, unrecognized fields are silently discarded. However, if the method
> -call itself is not recognized, it is considered a validation error and the
> +If a method call is not recognized, it is considered a validation error and the
> receiver will close its end of the interface pipe. For example, if a client on
> version 1 of the above interface sends an `AttachFingerPrint` request to an
> implementation of version 0, the client will be disconnected.
> @@ -712,8 +838,8 @@ If you want an enum to be extensible in the future, you can apply the
> ``` cpp
> [Extensible]
> enum Department {
> - SALES,
> - DEV,
> + kSales,
> + kDev,
> };
> ```
>
> @@ -722,9 +848,9 @@ And later you can extend this enum without breaking backwards compatibility:
> ``` cpp
> [Extensible]
> enum Department {
> - SALES,
> - DEV,
> - [MinVersion=1] RESEARCH,
> + kSales,
> + kDev,
> + [MinVersion=1] kResearch,
> };
> ```
>
> @@ -782,7 +908,7 @@ Statement = ModuleStatement | ImportStatement | Definition
>
> ModuleStatement = AttributeSection "module" Identifier ";"
> ImportStatement = "import" StringLiteral ";"
> -Definition = Struct Union Interface Enum Const
> +Definition = Struct Union Interface Enum Feature Const
>
> AttributeSection = <empty> | "[" AttributeList "]"
> AttributeList = <empty> | NonEmptyAttributeList
> @@ -809,7 +935,7 @@ InterfaceBody = <empty>
> | InterfaceBody Const
> | InterfaceBody Enum
> | InterfaceBody Method
> -Method = AttributeSection Name Ordinal "(" ParamterList ")" Response ";"
> +Method = AttributeSection Name Ordinal "(" ParameterList ")" Response ";"
> ParameterList = <empty> | NonEmptyParameterList
> NonEmptyParameterList = Parameter
> | Parameter "," NonEmptyParameterList
> @@ -847,6 +973,13 @@ EnumValue = AttributeSection Name
> | AttributeSection Name "=" Integer
> | AttributeSection Name "=" Identifier
>
> +; Note: `feature` is a weak keyword and can appear as, say, a struct field name.
> +Feature = AttributeSection "feature" Name "{" FeatureBody "}" ";"
> + | AttributeSection "feature" Name ";"
> +FeatureBody = <empty>
> + | FeatureBody FeatureField
> +FeatureField = AttributeSection TypeSpec Name Default ";"
> +
> Const = "const" TypeSpec Name "=" Constant ";"
>
> Constant = Literal | Identifier ";"
> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/__init__.py b/utils/ipc/mojo/public/tools/bindings/checks/__init__.py
> new file mode 100644
> index 000000000000..e69de29bb2d1
> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py
> new file mode 100644
> index 000000000000..e6e4f2c9392b
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py
> @@ -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)
> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
> new file mode 100644
> index 000000000000..f1a50a4ab508
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
> @@ -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 at 0(int32 a) => (int32 b);
> + [MinVersion=2,Sync,UnlimitedSize,NoInterrupt]
> + Bar at 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')
> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py
> new file mode 100644
> index 000000000000..702d41c30fc1
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py
> @@ -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
> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py
> new file mode 100644
> index 000000000000..07f51a64feaf
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py
> @@ -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)
> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py
> new file mode 100644
> index 000000000000..e96152fdd0ef
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py
> @@ -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')
> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py
> new file mode 100644
> index 000000000000..d570e26cea68
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py
> @@ -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)
> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py
> new file mode 100644
> index 000000000000..a6cd71e2f1f1
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py
> @@ -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)
> diff --git a/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni
> deleted file mode 100644
> index d8a138744856..000000000000
> --- a/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni
> +++ /dev/null
> @@ -1,51 +0,0 @@
> -# Copyright 2016 The Chromium Authors. All rights reserved.
> -# Use of this source code is governed by a BSD-style license that can be
> -# found in the LICENSE file.
> -
> -_typemap_imports = [
> - "//chrome/chrome_cleaner/mojom/typemaps/typemaps.gni",
> - "//chrome/common/importer/typemaps.gni",
> - "//chrome/common/media_router/mojom/typemaps.gni",
> - "//chrome/typemaps.gni",
> - "//chromecast/typemaps.gni",
> - "//chromeos/typemaps.gni",
> - "//chromeos/components/multidevice/mojom/typemaps.gni",
> - "//chromeos/services/cros_healthd/public/mojom/typemaps.gni",
> - "//chromeos/services/device_sync/public/mojom/typemaps.gni",
> - "//chromeos/services/network_config/public/mojom/typemaps.gni",
> - "//chromeos/services/secure_channel/public/mojom/typemaps.gni",
> - "//components/arc/mojom/typemaps.gni",
> - "//components/chromeos_camera/common/typemaps.gni",
> - "//components/services/storage/public/cpp/filesystem/typemaps.gni",
> - "//components/sync/mojom/typemaps.gni",
> - "//components/typemaps.gni",
> - "//content/browser/typemaps.gni",
> - "//content/public/common/typemaps.gni",
> - "//sandbox/mac/mojom/typemaps.gni",
> - "//services/media_session/public/cpp/typemaps.gni",
> - "//services/proxy_resolver/public/cpp/typemaps.gni",
> - "//services/resource_coordinator/public/cpp/typemaps.gni",
> - "//services/service_manager/public/cpp/typemaps.gni",
> - "//services/tracing/public/mojom/typemaps.gni",
> -]
> -
> -_typemaps = []
> -foreach(typemap_import, _typemap_imports) {
> - # Avoid reassignment error by assigning to empty scope first.
> - _imported = {
> - }
> - _imported = read_file(typemap_import, "scope")
> - _typemaps += _imported.typemaps
> -}
> -
> -typemaps = []
> -foreach(typemap, _typemaps) {
> - typemaps += [
> - {
> - filename = typemap
> - config = read_file(typemap, "scope")
> - },
> - ]
> -}
> -
> -component_macro_suffix = ""
> diff --git a/utils/ipc/mojo/public/tools/bindings/compile_typescript.py b/utils/ipc/mojo/public/tools/bindings/compile_typescript.py
> deleted file mode 100644
> index a978901bc033..000000000000
> --- a/utils/ipc/mojo/public/tools/bindings/compile_typescript.py
> +++ /dev/null
> @@ -1,27 +0,0 @@
> -# Copyright 2019 The Chromium Authors. All rights reserved.
> -# Use of this source code is governed by a BSD-style license that can be
> -# found in the LICENSE file.
> -
> -import os
> -import sys
> -import argparse
> -
> -_HERE_PATH = os.path.dirname(__file__)
> -_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))
> -
> -sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
> -import node
> -import node_modules
> -
> -def main(argv):
> - parser = argparse.ArgumentParser()
> - parser.add_argument('--tsconfig_path', required=True)
> - args = parser.parse_args(argv)
> -
> - result = node.RunNode([node_modules.PathToTypescript()] +
> - ['--project', args.tsconfig_path])
> - if len(result) != 0:
> - raise RuntimeError('Failed to compile Typescript: \n%s' % result)
> -
> -if __name__ == '__main__':
> - main(sys.argv[1:])
> diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
> index 48bc66fd0f6e..4dd26d4aea8b 100755
> --- a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
> +++ b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
> @@ -1,5 +1,5 @@
> #!/usr/bin/env python
> -# Copyright 2019 The Chromium Authors. All rights reserved.
> +# 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.
> #
> @@ -15,6 +15,7 @@
> from __future__ import print_function
>
> import optparse
> +import sys
>
>
> def Concatenate(filenames):
> @@ -47,7 +48,7 @@ def main():
> parser.set_usage("""Concatenate several files into one.
> Equivalent to: cat file1 ... > target.""")
> (_options, args) = parser.parse_args()
> - exit(0 if Concatenate(args) else 1)
> + sys.exit(0 if Concatenate(args) else 1)
>
>
> if __name__ == "__main__":
> diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py b/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py
> index be8985cedc99..7d56c9f962c3 100755
> --- a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py
> +++ b/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py
> @@ -1,5 +1,5 @@
> #!/usr/bin/env python
> -# Copyright 2018 The Chromium Authors. All rights reserved.
> +# 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.
>
> @@ -20,6 +20,7 @@ from __future__ import print_function
>
> import optparse
> import re
> +import sys
>
>
> _MOJO_INTERNAL_MODULE_NAME = "mojo.internal"
> @@ -31,10 +32,10 @@ def FilterLine(filename, line, output):
> return
>
> if line.startswith("goog.provide"):
> - match = re.match("goog.provide\('([^']+)'\);", line)
> + match = re.match(r"goog.provide\('([^']+)'\);", line)
> if not match:
> print("Invalid goog.provide line in %s:\n%s" % (filename, line))
> - exit(1)
> + sys.exit(1)
>
> module_name = match.group(1)
> if module_name == _MOJO_INTERNAL_MODULE_NAME:
> @@ -67,7 +68,8 @@ def main():
> Concatenate several files into one, stripping Closure provide and
> require directives along the way.""")
> (_, args) = parser.parse_args()
> - exit(0 if ConcatenateAndReplaceExports(args) else 1)
> + sys.exit(0 if ConcatenateAndReplaceExports(args) else 1)
> +
>
> if __name__ == "__main__":
> main()
> diff --git a/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py b/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py
> deleted file mode 100755
> index 7ac4af5faef1..000000000000
> --- a/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py
> +++ /dev/null
> @@ -1,36 +0,0 @@
> -#!/usr/bin/env python
> -# Copyright 2016 The Chromium Authors. All rights reserved.
> -# Use of this source code is governed by a BSD-style license that can be
> -# found in the LICENSE file.
> -
> -from __future__ import print_function
> -
> -import sys
> -
> -# This utility converts mojom dependencies into their corresponding typemap
> -# paths and formats them to be consumed by generate_type_mappings.py.
> -
> -
> -def FormatTypemap(typemap_filename):
> - # A simple typemap is valid Python with a minor alteration.
> - with open(typemap_filename) as f:
> - typemap_content = f.read().replace('=\n', '=')
> - typemap = {}
> - exec typemap_content in typemap
> -
> - for header in typemap.get('public_headers', []):
> - yield 'public_headers=%s' % header
> - for header in typemap.get('traits_headers', []):
> - yield 'traits_headers=%s' % header
> - for header in typemap.get('type_mappings', []):
> - yield 'type_mappings=%s' % header
> -
> -
> -def main():
> - typemaps = sys.argv[1:]
> - print(' '.join('--start-typemap %s' % ' '.join(FormatTypemap(typemap))
> - for typemap in typemaps))
> -
> -
> -if __name__ == '__main__':
> - sys.exit(main())
> diff --git a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py
> index 8b78d0924185..c6daff034f7c 100644
> --- a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py
> +++ b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py
> @@ -1,4 +1,4 @@
> -# Copyright 2017 The Chromium Authors. All rights reserved.
> +# 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.
> diff --git a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
> index a009664945fe..4a53e2bffe1e 100755
> --- a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
> +++ b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
> @@ -1,5 +1,5 @@
> #!/usr/bin/env python
> -# Copyright 2016 The Chromium Authors. All rights reserved.
> +# 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.
> @@ -82,10 +82,12 @@ def LoadCppTypemapConfig(path):
> 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),
> diff --git a/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
> new file mode 100755
> index 000000000000..cefee7a401a6
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
> @@ -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:])
> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom.gni b/utils/ipc/mojo/public/tools/bindings/mojom.gni
> index fe2a1da3750a..3f6e54e0a315 100644
> --- a/utils/ipc/mojo/public/tools/bindings/mojom.gni
> +++ b/utils/ipc/mojo/public/tools/bindings/mojom.gni
> @@ -1,25 +1,28 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# 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("//build/config/python.gni")
> import("//third_party/closure_compiler/closure_args.gni")
> import("//third_party/closure_compiler/compile_js.gni")
> import("//third_party/protobuf/proto_library.gni")
> +import("//ui/webui/resources/tools/generate_grd.gni")
> import("//ui/webui/webui_features.gni")
>
> +import("//build/config/cast.gni")
> +
> # TODO(rockot): Maybe we can factor these dependencies out of //mojo. They're
> # used to conditionally enable message ID scrambling in a way which is
> # consistent across toolchains and which is affected by branded vs non-branded
> # Chrome builds. Ideally we could create some generic knobs here that could be
> # flipped elsewhere though.
> import("//build/config/chrome_build.gni")
> -import("//build/config/chromecast_build.gni")
> import("//build/config/chromeos/ui_mode.gni")
> +import("//build/config/features.gni")
> import("//build/config/nacl/config.gni")
> import("//build/toolchain/kythe.gni")
> import("//components/nacl/features.gni")
> import("//third_party/jinja2/jinja2.gni")
> +import("//third_party/ply/ply.gni")
> import("//tools/ipc_fuzzer/ipc_fuzzer.gni")
> declare_args() {
> # Indicates whether typemapping should be supported in this build
> @@ -34,21 +37,30 @@ declare_args() {
>
> # Controls message ID scrambling behavior. If |true|, message IDs are
> # scrambled (i.e. randomized based on the contents of //chrome/VERSION) on
> - # non-Chrome OS desktop platforms. Set to |false| to disable message ID
> - # scrambling on all platforms.
> - enable_mojom_message_id_scrambling = true
> -
> - # Enables Closure compilation of generated JS lite bindings. In environments
> - # where compilation is supported, any mojom target "foo" will also have a
> - # corresponding "foo_js_library_for_compile" target generated.
> - enable_mojom_closure_compile = enable_js_type_check && optimize_webui
> -
> - # Enables generating Typescript bindings and compiling them to JS bindings.
> - enable_typescript_bindings = false
> + # non-Chrome OS desktop platforms. Enabled on official builds by default.
> + # Set to |true| to enable message ID scrambling on a specific build.
> + # See also `enable_scrambled_message_ids` below for more details.
> + enable_mojom_message_id_scrambling = is_official_build
>
> # Enables generating javascript fuzzing-related code and the bindings for the
> # MojoLPM fuzzer targets. Off by default.
> enable_mojom_fuzzer = false
> +
> + # Enables Closure compilation of generated JS lite bindings. In environments
> + # where compilation is supported, any mojom target "foo" will also have a
> + # corresponding "foo_js_library_for_compile" target generated.
> + if (is_chromeos_ash) {
> + enable_mojom_closure_compile = enable_js_type_check && optimize_webui
> + }
> +}
> +
> +# Closure libraries are needed for mojom_closure_compile, and when
> +# js_type_check is enabled on Ash.
> +if (is_chromeos_ash) {
> + generate_mojom_closure_libraries =
> + enable_mojom_closure_compile || enable_js_type_check
> +} else {
> + generate_mojom_closure_libraries = false
> }
>
> # NOTE: We would like to avoid scrambling message IDs where it doesn't add
> @@ -69,9 +81,8 @@ declare_args() {
> # lacros-chrome switches to target_os="chromeos"
> enable_scrambled_message_ids =
> enable_mojom_message_id_scrambling &&
> - (is_mac || is_win ||
> - (is_linux && !is_chromeos_ash && !is_chromecast && !is_chromeos_lacros) ||
> - ((enable_nacl || is_nacl || is_nacl_nonsfi) &&
> + (is_mac || is_win || (is_linux && !is_castos) ||
> + ((enable_nacl || is_nacl) &&
> (target_os != "chromeos" && !chromeos_is_browser_only)))
>
> _mojom_tools_root = "//mojo/public/tools"
> @@ -80,7 +91,9 @@ mojom_parser_script = "$_mojom_tools_root/mojom/mojom_parser.py"
> mojom_parser_sources = [
> "$_mojom_library_root/__init__.py",
> "$_mojom_library_root/error.py",
> + "$_mojom_library_root/fileutil.py",
> "$_mojom_library_root/generate/__init__.py",
> + "$_mojom_library_root/generate/check.py",
> "$_mojom_library_root/generate/generator.py",
> "$_mojom_library_root/generate/module.py",
> "$_mojom_library_root/generate/pack.py",
> @@ -88,21 +101,32 @@ mojom_parser_sources = [
> "$_mojom_library_root/generate/translate.py",
> "$_mojom_library_root/parse/__init__.py",
> "$_mojom_library_root/parse/ast.py",
> + "$_mojom_library_root/parse/conditional_features.py",
> "$_mojom_library_root/parse/lexer.py",
> "$_mojom_library_root/parse/parser.py",
> + "//tools/diagnosis/crbug_1001171.py",
> ]
>
> mojom_generator_root = "$_mojom_tools_root/bindings"
> mojom_generator_script = "$mojom_generator_root/mojom_bindings_generator.py"
> mojom_generator_sources =
> mojom_parser_sources + [
> + "$mojom_generator_root/checks/__init__.py",
> + "$mojom_generator_root/checks/mojom_attributes_check.py",
> + "$mojom_generator_root/checks/mojom_definitions_check.py",
> + "$mojom_generator_root/checks/mojom_interface_feature_check.py",
> + "$mojom_generator_root/checks/mojom_restrictions_check.py",
> + "$mojom_generator_root/generators/__init__.py",
> "$mojom_generator_root/generators/cpp_util.py",
> "$mojom_generator_root/generators/mojom_cpp_generator.py",
> "$mojom_generator_root/generators/mojom_java_generator.py",
> - "$mojom_generator_root/generators/mojom_mojolpm_generator.py",
> "$mojom_generator_root/generators/mojom_js_generator.py",
> + "$mojom_generator_root/generators/mojom_mojolpm_generator.py",
> "$mojom_generator_root/generators/mojom_ts_generator.py",
> "$mojom_generator_script",
> + "//build/action_helpers.py",
> + "//build/gn_helpers.py",
> + "//build/zip_helpers.py",
> ]
>
> if (enable_scrambled_message_ids) {
> @@ -243,12 +267,16 @@ if (enable_scrambled_message_ids) {
> # |cpp_only| is set to true, it overrides this to prevent generation of
> # Java bindings.
> #
> -# enable_fuzzing (optional)
> +# enable_js_fuzzing (optional)
> +# Enables generation of javascript fuzzing sources for the target if the
> +# global build arg |enable_mojom_fuzzer| is also set to |true|.
> +# Defaults to |true|. If JS fuzzing generation is enabled for a target,
> +# the target will always generate JS bindings even if |cpp_only| is set to
> +# |true|. See note above.
> +#
> +# enable_mojolpm_fuzzing (optional)
> # Enables generation of fuzzing sources for the target if the global build
> -# arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|. If
> -# fuzzing generation is enabled for a target, the target will always
> -# generate JS bindings even if |cpp_only| is set to |true|. See note
> -# above.
> +# arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|.
> #
> # support_lazy_serialization (optional)
> # If set to |true|, generated C++ bindings will effectively prefer to
> @@ -310,8 +338,15 @@ if (enable_scrambled_message_ids) {
> # correct dependency order. Note that this only has an effect if
> # the |enable_mojom_closure_compile| global arg is set to |true| as well.
> #
> -# use_typescript_sources (optional)
> -# Uses the Typescript generator to generate JavaScript bindings.
> +# generate_webui_js_bindings (optional)
> +# Generate WebUI bindings in JavaScript rather than TypeScript. Defaults
> +# to false. ChromeOS only parameter.
> +#
> +# generate_legacy_js_bindings (optional)
> +# Generate js_data_deps target containing legacy JavaScript bindings files
> +# for Blink tests and other non-WebUI users when generating TypeScript
> +# bindings for WebUI. Ignored if generate_webui_js_bindings is set to
> +# true.
> #
> # js_generate_struct_deserializers (optional)
> # Generates JS deerialize methods for structs.
> @@ -323,17 +358,23 @@ if (enable_scrambled_message_ids) {
> # webui_module_path (optional)
> # The path or URL at which modules generated by this target will be
> # accessible to WebUI pages. This may either be an absolute path or
> -# a full URL path starting with "chrome://resources/mojo".
> +# a full URL path starting with "chrome://resources/mojo". If this path
> +# is not specified, WebUI bindings will not be generated.
> #
> # If an absolute path, a WebUI page may only import these modules if
> -# they are manually packaged and mapped independently by that page's
> -# WebUIDataSource. The mapped path must match the path given here.
> +# they are added to that page's data source (usually by adding the
> +# modules to the mojo_files list for build_webui(), or by listing the
> +# files as inputs to the page's ts_library() and/or generate_grd() build
> +# steps.
> #
> # If this is is instead a URL string starting with
> -# "chrome://resources/mojo", the generated resources must be added to
> -# content_resources.grd and registered with
> -# content::SharedResourcesDataSource with a corresponding path, at which
> -# point they will be made available to all WebUI pages at the given URL.
> +# "chrome://resources/mojo", the resulting bindings files should
> +# be added to one of the lists in ui/webui/resources/mojo/BUILD.gn,
> +# at which point they will be made available to all WebUI pages at the
> +# given URL.
> +#
> +# Note: WebUI module bindings are generated in TypeScript by default,
> +# unless |generate_webui_js_bindings| is specified as true.
> #
> # The following parameters are used to support the component build. They are
> # needed so that bindings which are linked with a component can use the same
> @@ -402,16 +443,41 @@ if (enable_scrambled_message_ids) {
> # should be mapped in generated bindings. This is a string like
> # "::base::Value" or "std::vector<::base::Value>".
> #
> -# move_only (optional)
> -# A boolean value (default false) which indicates whether the C++
> -# type is move-only. If true, generated bindings will pass the type
> -# by value and use std::move() at call sites.
> -#
> # copyable_pass_by_value (optional)
> # A boolean value (default false) which effectively indicates
> # whether the C++ type is very cheap to copy. If so, generated
> # bindings will pass by value but not use std::move() at call sites.
> #
> +# default_constructible (optional)
> +# A boolean value (default true) which indicates whether the C++
> +# type is default constructible. If a C++ type is not default
> +# constructible (e.g. the implementor of the type prefers not to
> +# publicly expose a default constructor that creates an object in an
> +# invalid state), Mojo will instead construct C++ type with an
> +# argument of the type `mojo::DefaultConstruct::Tag` (essentially a
> +# passkey-like type specifically for this use case).
> +#
> +# force_serialize (optional)
> +# A boolean value (default false) which disables lazy serialization
> +# of the typemapped type if lazy serialization is enabled for the
> +# mojom target applying this typemap.
> +#
> +# forward_declaration (optional)
> +# A forward declaration of the C++ type, which bindings that don't
> +# need the full type definition can use to reduce the size of
> +# the generated code. This is a string like
> +# "namespace base { class Value; }".
> +#
> +# hashable (optional)
> +# A boolean value (default false) indicating whether the C++ type is
> +# hashable. Set to true if true AND needed (i.e. you need to use the
> +# type as the key of a mojom map).
> +#
> +# move_only (optional)
> +# A boolean value (default false) which indicates whether the C++
> +# type is move-only. If true, generated bindings will pass the type
> +# by value and use std::move() at call sites.
> +#
> # nullable_is_same_type (optional)
> # A boolean value (default false) which indicates that the C++ type
> # has some baked-in semantic notion of a "null" state. If true, the
> @@ -421,16 +487,6 @@ if (enable_scrambled_message_ids) {
> # type with absl::optional, and null values are simply
> # absl::nullopt.
> #
> -# hashable (optional)
> -# A boolean value (default false) indicating whether the C++ type is
> -# hashable. Set to true if true AND needed (i.e. you need to use the
> -# type as the key of a mojom map).
> -#
> -# force_serialize (optional)
> -# A boolean value (default false) which disables lazy serialization
> -# of the typemapped type if lazy serialization is enabled for the
> -# mojom target applying this typemap.
> -#
> # Additional typemap scope parameters:
> #
> # traits_headers (optional)
> @@ -621,20 +677,26 @@ template("mojom") {
> build_metadata_filename = "$target_gen_dir/$target_name.build_metadata"
> build_metadata = {
> }
> - build_metadata.sources = rebase_path(sources_list)
> + build_metadata.sources = rebase_path(sources_list, target_gen_dir)
> build_metadata.deps = []
> foreach(dep, all_deps) {
> dep_target_gen_dir = get_label_info(dep, "target_gen_dir")
> dep_name = get_label_info(dep, "name")
> build_metadata.deps +=
> - [ rebase_path("$dep_target_gen_dir/$dep_name.build_metadata") ]
> + [ rebase_path("$dep_target_gen_dir/$dep_name.build_metadata",
> + target_gen_dir) ]
> }
> write_file(build_metadata_filename, build_metadata, "json")
>
> - generate_fuzzing =
> - (!defined(invoker.enable_fuzzing) || invoker.enable_fuzzing) &&
> + generate_js_fuzzing =
> + (!defined(invoker.enable_js_fuzzing) || invoker.enable_js_fuzzing) &&
> enable_mojom_fuzzer && (!defined(invoker.testonly) || !invoker.testonly)
>
> + generate_mojolpm_fuzzing =
> + (!defined(invoker.enable_mojolpm_fuzzing) ||
> + invoker.enable_mojolpm_fuzzing) && enable_mojom_fuzzer &&
> + (!defined(invoker.testonly) || !invoker.testonly)
> +
> parser_target_name = "${target_name}__parser"
> parser_deps = []
> foreach(dep, all_deps) {
> @@ -665,30 +727,34 @@ template("mojom") {
> "is_chromeos",
> "is_chromeos_ash",
> ]
> + } else if (is_chromeos_lacros) {
> + enabled_features += [
> + "is_chromeos",
> + "is_chromeos_lacros",
> + ]
> } else if (is_fuchsia) {
> enabled_features += [ "is_fuchsia" ]
> } else if (is_ios) {
> enabled_features += [ "is_ios" ]
> - } else if (is_linux || is_chromeos_lacros) {
> + } else if (is_linux) {
> enabled_features += [ "is_linux" ]
> - if (is_chromeos_lacros) {
> - enabled_features += [
> - "is_chromeos",
> - "is_chromeos_lacros",
> - ]
> - }
> } else if (is_mac) {
> enabled_features += [ "is_mac" ]
> } else if (is_win) {
> enabled_features += [ "is_win" ]
> }
>
> - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> - python2_action(parser_target_name) {
> + if (is_apple) {
> + enabled_features += [ "is_apple" ]
> + }
> +
> + action(parser_target_name) {
> + allow_remote = true
> + custom_processor = "mojom_parser"
> script = mojom_parser_script
> - inputs = mojom_parser_sources + [ build_metadata_filename ]
> + inputs = mojom_parser_sources + ply_sources + [ build_metadata_filename ]
> sources = sources_list
> - deps = parser_deps
> + public_deps = parser_deps
> outputs = []
> foreach(base_path, output_file_base_paths) {
> filename = get_path_info(base_path, "file")
> @@ -698,31 +764,35 @@ template("mojom") {
>
> filelist = []
> foreach(source, sources_list) {
> - filelist += [ rebase_path(source) ]
> + filelist += [ rebase_path(source, root_build_dir) ]
> }
> - response_file_contents = filelist
> +
> + # Workaround for https://github.com/ninja-build/ninja/issues/1966.
> + rsp_file = "$target_gen_dir/${target_name}.rsp"
> + write_file(rsp_file, filelist)
> + inputs += [ rsp_file ]
>
> args = [
> # Resolve relative input mojom paths against both the root src dir and
> # the root gen dir.
> "--input-root",
> - rebase_path("//."),
> + rebase_path("//.", root_build_dir),
> "--input-root",
> - rebase_path(root_gen_dir),
> + rebase_path(root_gen_dir, root_build_dir),
>
> "--output-root",
> - rebase_path(root_gen_dir),
> + rebase_path(root_gen_dir, root_build_dir),
>
> - "--mojom-file-list={{response_file_name}}",
> + "--mojom-file-list=" + rebase_path(rsp_file, root_build_dir),
>
> "--check-imports",
> - rebase_path(build_metadata_filename),
> + rebase_path(build_metadata_filename, root_build_dir),
> ]
>
> if (defined(invoker.input_root_override)) {
> args += [
> "--input-root",
> - rebase_path(invoker.input_root_override),
> + rebase_path(invoker.input_root_override, root_build_dir),
> ]
> }
>
> @@ -738,6 +808,13 @@ template("mojom") {
> "--add-module-metadata",
> "webui_module_path=${invoker.webui_module_path}",
> ]
> + if (defined(invoker.generate_webui_js_bindings) &&
> + invoker.generate_webui_js_bindings) {
> + args += [
> + "--add-module-metadata",
> + "generate_webui_js=True",
> + ]
> + }
> }
> }
> }
> @@ -819,11 +896,12 @@ template("mojom") {
> }
> }
>
> - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> - python2_action(generator_cpp_message_ids_target_name) {
> + action(generator_cpp_message_ids_target_name) {
> + allow_remote = true
> script = mojom_generator_script
> inputs = mojom_generator_sources + jinja2_sources
> - sources = sources_list
> + sources = sources_list +
> + [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ]
> deps = [
> ":$parser_target_name",
> "//mojo/public/tools/bindings:precompile_templates",
> @@ -835,16 +913,22 @@ template("mojom") {
> args = common_generator_args
> filelist = []
> foreach(source, sources_list) {
> - filelist += [ rebase_path("$source", root_build_dir) ]
> + filelist += [ rebase_path(source, root_build_dir) ]
> }
> foreach(base_path, output_file_base_paths) {
> + filename = get_path_info(base_path, "file")
> + dirname = get_path_info(base_path, "dir")
> + inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
> outputs += [ "$root_gen_dir/$base_path-shared-message-ids.h" ]
> }
>
> - response_file_contents = filelist
> + # Workaround for https://github.com/ninja-build/ninja/issues/1966.
> + rsp_file = "$target_gen_dir/${target_name}.rsp"
> + write_file(rsp_file, filelist)
> + inputs += [ rsp_file ]
>
> args += [
> - "--filelist={{response_file_name}}",
> + "--filelist=" + rebase_path(rsp_file, root_build_dir),
> "--generate_non_variant_code",
> "--generate_message_ids",
> "-g",
> @@ -860,12 +944,13 @@ template("mojom") {
>
> generator_shared_target_name = "${target_name}_shared__generator"
>
> - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> - python2_action(generator_shared_target_name) {
> + action(generator_shared_target_name) {
> + allow_remote = true
> visibility = [ ":*" ]
> script = mojom_generator_script
> inputs = mojom_generator_sources + jinja2_sources
> - sources = sources_list
> + sources = sources_list +
> + [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ]
> deps = [
> ":$parser_target_name",
> "//mojo/public/tools/bindings:precompile_templates",
> @@ -878,10 +963,16 @@ template("mojom") {
> args = common_generator_args
> filelist = []
> foreach(source, sources_list) {
> - filelist += [ rebase_path("$source", root_build_dir) ]
> + filelist += [ rebase_path(source, root_build_dir) ]
> }
> foreach(base_path, output_file_base_paths) {
> + # Need the mojom-module as an input to this action.
> + filename = get_path_info(base_path, "file")
> + dirname = get_path_info(base_path, "dir")
> + inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
> +
> outputs += [
> + "$root_gen_dir/$base_path-features.h",
> "$root_gen_dir/$base_path-params-data.h",
> "$root_gen_dir/$base_path-shared-internal.h",
> "$root_gen_dir/$base_path-shared.cc",
> @@ -889,10 +980,13 @@ template("mojom") {
> ]
> }
>
> - response_file_contents = filelist
> + # Workaround for https://github.com/ninja-build/ninja/issues/1966.
> + rsp_file = "$target_gen_dir/${target_name}.rsp"
> + write_file(rsp_file, filelist)
> + inputs += [ rsp_file ]
>
> args += [
> - "--filelist={{response_file_name}}",
> + "--filelist=" + rebase_path(rsp_file, root_build_dir),
> "--generate_non_variant_code",
> "-g",
> "c++",
> @@ -923,12 +1017,14 @@ template("mojom") {
> if (defined(invoker.testonly)) {
> testonly = invoker.testonly
> }
> + configs += [ "//build/config/compiler:wexit_time_destructors" ]
> deps = []
> public_deps = []
> if (output_file_base_paths != []) {
> sources = []
> foreach(base_path, output_file_base_paths) {
> sources += [
> + "$root_gen_dir/$base_path-features.h",
> "$root_gen_dir/$base_path-params-data.h",
> "$root_gen_dir/$base_path-shared-internal.h",
> "$root_gen_dir/$base_path-shared.cc",
> @@ -972,7 +1068,7 @@ template("mojom") {
> }
> }
>
> - if (generate_fuzzing) {
> + if (generate_mojolpm_fuzzing) {
> # This block generates the proto files used for the MojoLPM fuzzer,
> # and the corresponding proto targets that will be linked in the fuzzer
> # targets. These are independent of the typemappings, and can be done
> @@ -981,11 +1077,15 @@ template("mojom") {
> generator_mojolpm_proto_target_name =
> "${target_name}_mojolpm_proto_generator"
>
> - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> - python2_action(generator_mojolpm_proto_target_name) {
> + action(generator_mojolpm_proto_target_name) {
> + allow_remote = true
> script = mojom_generator_script
> inputs = mojom_generator_sources + jinja2_sources
> - sources = invoker.sources
> + sources =
> + invoker.sources + [
> + "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip",
> + "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip",
> + ]
> deps = [
> ":$parser_target_name",
> "//mojo/public/tools/bindings:precompile_templates",
> @@ -994,15 +1094,37 @@ template("mojom") {
> outputs = []
> args = common_generator_args
> filelist = []
> - foreach(source, invoker.sources) {
> - filelist += [ rebase_path("$source", root_build_dir) ]
> +
> + # Split the input into generated and non-generated source files. They
> + # need to be processed separately.
> + gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
> + non_gen_sources =
> + filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
> + gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ])
> +
> + foreach(source, non_gen_sources) {
> + filelist += [ rebase_path(source, root_build_dir) ]
> + inputs += [ "$target_gen_dir/$source-module" ]
> outputs += [ "$target_gen_dir/$source.mojolpm.proto" ]
> }
>
> - response_file_contents = filelist
> + foreach(source, gen_sources) {
> + filelist += [ rebase_path(source, root_build_dir) ]
> +
> + # For generated files, we assume they're in the target_gen_dir or a
> + # sub-folder of it. Rebase the path so we can get the relative location.
> + source_file = rebase_path(source, target_gen_dir)
> + inputs += [ "$target_gen_dir/$source_file-module" ]
> + outputs += [ "$target_gen_dir/$source_file.mojolpm.proto" ]
> + }
> +
> + # Workaround for https://github.com/ninja-build/ninja/issues/1966.
> + rsp_file = "$target_gen_dir/${target_name}.rsp"
> + write_file(rsp_file, filelist)
> + inputs += [ rsp_file ]
>
> args += [
> - "--filelist={{response_file_name}}",
> + "--filelist=" + rebase_path(rsp_file, root_build_dir),
> "--generate_non_variant_code",
> "-g",
> "mojolpm",
> @@ -1014,9 +1136,20 @@ template("mojom") {
> proto_library(mojolpm_proto_target_name) {
> testonly = true
> generate_python = false
> +
> + # Split the input into generated and non-generated source files. They
> + # need to be processed separately.
> + gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
> + non_gen_sources =
> + filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
> + gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ])
> sources = process_file_template(
> - invoker.sources,
> + non_gen_sources,
> [ "{{source_gen_dir}}/{{source_file_part}}.mojolpm.proto" ])
> + sources += process_file_template(
> + gen_sources,
> + [ "{{source_dir}}/{{source_file_part}}.mojolpm.proto" ])
> +
> import_dirs = [ "//" ]
> proto_in_dir = "${root_gen_dir}"
> proto_out_dir = "."
> @@ -1055,7 +1188,7 @@ template("mojom") {
> component_macro_suffix = ""
> }
> if ((!defined(invoker.disable_variants) || !invoker.disable_variants) &&
> - !is_ios) {
> + use_blink) {
> blink_variant = {
> variant = "blink"
> component_macro_suffix = "_BLINK"
> @@ -1149,39 +1282,6 @@ template("mojom") {
> "${bindings_configuration.component_macro_suffix}_IMPL" ]
> }
>
> - export_args = []
> - export_args_overridden = false
> - if (defined(bindings_configuration.for_blink) &&
> - bindings_configuration.for_blink) {
> - if (defined(invoker.export_class_attribute_blink)) {
> - export_args_overridden = true
> - export_args += [
> - "--export_attribute",
> - invoker.export_class_attribute_blink,
> - "--export_header",
> - invoker.export_header_blink,
> - ]
> - }
> - } else if (defined(invoker.export_class_attribute)) {
> - export_args_overridden = true
> - export_args += [
> - "--export_attribute",
> - invoker.export_class_attribute,
> - "--export_header",
> - invoker.export_header,
> - ]
> - }
> -
> - if (!export_args_overridden && defined(invoker.component_macro_prefix)) {
> - export_args += [
> - "--export_attribute",
> - "COMPONENT_EXPORT(${invoker.component_macro_prefix}" +
> - "${bindings_configuration.component_macro_suffix})",
> - "--export_header",
> - "base/component_export.h",
> - ]
> - }
> -
> generate_java = false
> if (!cpp_only && defined(invoker.generate_java)) {
> generate_java = invoker.generate_java
> @@ -1190,6 +1290,38 @@ template("mojom") {
> type_mappings_path =
> "$target_gen_dir/${target_name}${variant_suffix}__type_mappings"
> if (sources_list != []) {
> + export_args = []
> + export_args_overridden = false
> + if (defined(bindings_configuration.for_blink) &&
> + bindings_configuration.for_blink) {
> + if (defined(invoker.export_class_attribute_blink)) {
> + export_args_overridden = true
> + export_args += [
> + "--export_attribute",
> + invoker.export_class_attribute_blink,
> + "--export_header",
> + invoker.export_header_blink,
> + ]
> + }
> + } else if (defined(invoker.export_class_attribute)) {
> + export_args_overridden = true
> + export_args += [
> + "--export_attribute",
> + invoker.export_class_attribute,
> + "--export_header",
> + invoker.export_header,
> + ]
> + }
> + if (!export_args_overridden && defined(invoker.component_macro_prefix)) {
> + export_args += [
> + "--export_attribute",
> + "COMPONENT_EXPORT(${invoker.component_macro_prefix}" +
> + "${bindings_configuration.component_macro_suffix})",
> + "--export_header",
> + "base/component_export.h",
> + ]
> + }
> +
> generator_cpp_output_suffixes = []
> variant_dash_suffix = ""
> if (defined(variant)) {
> @@ -1198,7 +1330,6 @@ template("mojom") {
> generator_cpp_output_suffixes += [
> "${variant_dash_suffix}-forward.h",
> "${variant_dash_suffix}-import-headers.h",
> - "${variant_dash_suffix}-test-utils.cc",
> "${variant_dash_suffix}-test-utils.h",
> "${variant_dash_suffix}.cc",
> "${variant_dash_suffix}.h",
> @@ -1207,16 +1338,28 @@ template("mojom") {
> generator_target_name = "${target_name}${variant_suffix}__generator"
>
> # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> - python2_action(generator_target_name) {
> + action(generator_target_name) {
> + allow_remote = true
> visibility = [ ":*" ]
> script = mojom_generator_script
> inputs = mojom_generator_sources + jinja2_sources
> - sources = sources_list
> + sources =
> + sources_list + [
> + "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip",
> + type_mappings_path,
> + ]
> + if (generate_mojolpm_fuzzing &&
> + !defined(bindings_configuration.variant)) {
> + sources += [
> + "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip",
> + ]
> + }
> deps = [
> ":$parser_target_name",
> ":$type_mappings_target_name",
> "//mojo/public/tools/bindings:precompile_templates",
> ]
> +
> if (defined(invoker.parser_deps)) {
> deps += invoker.parser_deps
> }
> @@ -1224,18 +1367,22 @@ template("mojom") {
> args = common_generator_args + export_args
> filelist = []
> foreach(source, sources_list) {
> - filelist += [ rebase_path("$source", root_build_dir) ]
> + filelist += [ rebase_path(source, root_build_dir) ]
> }
> foreach(base_path, output_file_base_paths) {
> + filename = get_path_info(base_path, "file")
> + dirname = get_path_info(base_path, "dir")
> + inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
> +
> outputs += [
> "$root_gen_dir/${base_path}${variant_dash_suffix}-forward.h",
> "$root_gen_dir/${base_path}${variant_dash_suffix}-import-headers.h",
> - "$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.cc",
> "$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.h",
> "$root_gen_dir/${base_path}${variant_dash_suffix}.cc",
> "$root_gen_dir/${base_path}${variant_dash_suffix}.h",
> ]
> - if (generate_fuzzing && !defined(bindings_configuration.variant)) {
> + if (generate_mojolpm_fuzzing &&
> + !defined(bindings_configuration.variant)) {
> outputs += [
> "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.cc",
> "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.h",
> @@ -1243,14 +1390,17 @@ template("mojom") {
> }
> }
>
> - response_file_contents = filelist
> -
> + # Workaround for https://github.com/ninja-build/ninja/issues/1966.
> + rsp_file = "$target_gen_dir/${target_name}.rsp"
> + write_file(rsp_file, filelist)
> + inputs += [ rsp_file ]
> args += [
> - "--filelist={{response_file_name}}",
> + "--filelist=" + rebase_path("$rsp_file", root_build_dir),
> "-g",
> ]
>
> - if (generate_fuzzing && !defined(bindings_configuration.variant)) {
> + if (generate_mojolpm_fuzzing &&
> + !defined(bindings_configuration.variant)) {
> args += [ "c++,mojolpm" ]
> } else {
> args += [ "c++" ]
> @@ -1294,6 +1444,8 @@ template("mojom") {
> "--extra_cpp_template_paths",
> rebase_path(extra_cpp_template, root_build_dir),
> ]
> + inputs += [ extra_cpp_template ]
> +
> assert(
> get_path_info(extra_cpp_template, "extension") == "tmpl",
> "--extra_cpp_template_paths only accepts template files ending in extension .tmpl")
> @@ -1306,62 +1458,6 @@ template("mojom") {
> }
> }
>
> - if (generate_fuzzing && !defined(variant)) {
> - # This block contains the C++ targets for the MojoLPM fuzzer, we need to
> - # do this here so that we can use the typemap configuration for the
> - # empty-variant Mojo target.
> -
> - mojolpm_target_name = "${target_name}_mojolpm"
> - mojolpm_generator_target_name = "${target_name}__generator"
> - source_set(mojolpm_target_name) {
> - # There are still a few missing header dependencies between mojo targets
> - # with typemaps and the dependencies of their typemap headers. It would
> - # be good to enable include checking for these in the future though.
> - check_includes = false
> - testonly = true
> - if (defined(invoker.sources)) {
> - sources = process_file_template(
> - invoker.sources,
> - [
> - "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc",
> - "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h",
> - ])
> - deps = []
> - } else {
> - sources = []
> - deps = []
> - }
> -
> - public_deps = [
> - ":$generator_shared_target_name",
> -
> - # NB: hardcoded dependency on the no-variant variant generator, since
> - # mojolpm only uses the no-variant type.
> - ":$mojolpm_generator_target_name",
> - ":$mojolpm_proto_target_name",
> - "//base",
> - "//mojo/public/tools/fuzzers:mojolpm",
> - ]
> -
> - foreach(d, all_deps) {
> - # Resolve the name, so that a target //mojo/something becomes
> - # //mojo/something:something and we can append variant_suffix to
> - # get the cpp dependency name.
> - full_name = get_label_info("$d", "label_no_toolchain")
> - public_deps += [ "${full_name}_mojolpm" ]
> - }
> -
> - foreach(config, cpp_typemap_configs) {
> - if (defined(config.traits_deps)) {
> - deps += config.traits_deps
> - }
> - if (defined(config.traits_public_deps)) {
> - public_deps += config.traits_public_deps
> - }
> - }
> - }
> - }
> -
> # Write the typemapping configuration for this target out to a file to be
> # validated by a Python script. This helps catch mistakes that can't
> # be caught by logic in GN.
> @@ -1389,20 +1485,20 @@ template("mojom") {
> write_file(_typemap_config_filename, _rebased_typemap_configs, "json")
> _mojom_target_name = target_name
>
> - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> - python2_action(_typemap_validator_target_name) {
> + action(_typemap_validator_target_name) {
> + allow_remote = true
> script = "$mojom_generator_root/validate_typemap_config.py"
> inputs = [ _typemap_config_filename ]
> outputs = [ _typemap_stamp_filename ]
> args = [
> get_label_info(_mojom_target_name, "label_no_toolchain"),
> - rebase_path(_typemap_config_filename),
> - rebase_path(_typemap_stamp_filename),
> + rebase_path(_typemap_config_filename, root_build_dir),
> + rebase_path(_typemap_stamp_filename, root_build_dir),
> ]
> }
>
> - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> - python2_action(type_mappings_target_name) {
> + action(type_mappings_target_name) {
> + allow_remote = true
> inputs =
> mojom_generator_sources + jinja2_sources + [ _typemap_stamp_filename ]
> outputs = [ type_mappings_path ]
> @@ -1413,6 +1509,7 @@ template("mojom") {
> rebase_path(type_mappings_path, root_build_dir),
> ]
>
> + sources = []
> foreach(d, all_deps) {
> name = get_label_info(d, "label_no_toolchain")
> toolchain = get_label_info(d, "toolchain")
> @@ -1422,12 +1519,11 @@ template("mojom") {
> dependency_output_dir =
> get_label_info(dependency_output, "target_gen_dir")
> dependency_name = get_label_info(dependency_output, "name")
> - dependency_path =
> - rebase_path("$dependency_output_dir/${dependency_name}",
> - root_build_dir)
> + dependency_path = "$dependency_output_dir/${dependency_name}"
> + sources += [ dependency_path ]
> args += [
> "--dependency",
> - dependency_path,
> + rebase_path(dependency_path, root_build_dir),
> ]
> }
>
> @@ -1485,11 +1581,15 @@ template("mojom") {
> if (defined(output_name_override)) {
> output_name = output_name_override
> }
> - visibility = output_visibility + [ ":$output_target_name" ]
> + visibility = output_visibility + [
> + ":$output_target_name",
> + ":${target_name}_mojolpm",
> + ]
> if (defined(invoker.testonly)) {
> testonly = invoker.testonly
> }
> defines = export_defines
> + configs += [ "//build/config/compiler:wexit_time_destructors" ]
> configs += extra_configs
> if (output_file_base_paths != []) {
> sources = []
> @@ -1578,13 +1678,81 @@ template("mojom") {
> }
> }
>
> + if (generate_mojolpm_fuzzing && !defined(variant)) {
> + # This block contains the C++ targets for the MojoLPM fuzzer, we need to
> + # do this here so that we can use the typemap configuration for the
> + # empty-variant Mojo target.
> +
> + mojolpm_target_name = "${target_name}_mojolpm"
> + mojolpm_generator_target_name = "${target_name}__generator"
> + source_set(mojolpm_target_name) {
> + # There are still a few missing header dependencies between mojo targets
> + # with typemaps and the dependencies of their typemap headers. It would
> + # be good to enable include checking for these in the future though.
> + check_includes = false
> + testonly = true
> + if (defined(invoker.sources)) {
> + # Split the input into generated and non-generated source files. They
> + # need to be processed separately.
> + gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
> + non_gen_sources =
> + filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
> + gen_sources =
> + filter_include(invoker.sources, [ gen_dir_path_wildcard ])
> + sources = process_file_template(
> + non_gen_sources,
> + [
> + "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc",
> + "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h",
> + ])
> + sources += process_file_template(
> + gen_sources,
> + [
> + "{{source_dir}}/{{source_file_part}}-mojolpm.cc",
> + "{{source_dir}}/{{source_file_part}}-mojolpm.h",
> + ])
> + deps = [ ":$output_target_name" ]
> + } else {
> + sources = []
> + deps = []
> + }
> +
> + public_deps = [
> + ":$generator_shared_target_name",
> +
> + # NB: hardcoded dependency on the no-variant variant generator, since
> + # mojolpm only uses the no-variant type.
> + ":$mojolpm_generator_target_name",
> + ":$mojolpm_proto_target_name",
> + "//base",
> + "//mojo/public/tools/fuzzers:mojolpm",
> + ]
> +
> + foreach(d, all_deps) {
> + # Resolve the name, so that a target //mojo/something becomes
> + # //mojo/something:something and we can append variant_suffix to
> + # get the cpp dependency name.
> + full_name = get_label_info("$d", "label_no_toolchain")
> + public_deps += [ "${full_name}_mojolpm" ]
> + }
> +
> + foreach(config, cpp_typemap_configs) {
> + if (defined(config.traits_deps)) {
> + deps += config.traits_deps
> + }
> + if (defined(config.traits_public_deps)) {
> + public_deps += config.traits_public_deps
> + }
> + }
> + }
> + }
> +
> if (generate_java && is_android) {
> import("//build/config/android/rules.gni")
>
> java_generator_target_name = target_name + "_java__generator"
> if (sources_list != []) {
> - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> - python2_action(java_generator_target_name) {
> + action(java_generator_target_name) {
> script = mojom_generator_script
> inputs = mojom_generator_sources + jinja2_sources
> sources = sources_list
> @@ -1597,7 +1765,7 @@ template("mojom") {
> args = common_generator_args
> filelist = []
> foreach(source, sources_list) {
> - filelist += [ rebase_path("$source", root_build_dir) ]
> + filelist += [ rebase_path(source, root_build_dir) ]
> }
> foreach(base_path, output_file_base_paths) {
> outputs += [ "$root_gen_dir/$base_path.srcjar" ]
> @@ -1624,8 +1792,7 @@ template("mojom") {
>
> java_srcjar_target_name = target_name + "_java_sources"
>
> - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> - python2_action(java_srcjar_target_name) {
> + action(java_srcjar_target_name) {
> script = "//build/android/gyp/zip.py"
> inputs = []
> if (output_file_base_paths != []) {
> @@ -1651,7 +1818,6 @@ template("mojom") {
> android_library(java_target_name) {
> forward_variables_from(invoker, [ "enable_bytecode_checks" ])
> deps = [
> - "//base:base_java",
> "//mojo/public/java:bindings_java",
> "//mojo/public/java:system_java",
> "//third_party/androidx:androidx_annotation_annotation_java",
> @@ -1673,21 +1839,36 @@ template("mojom") {
> }
> }
>
> - use_typescript_for_target =
> - enable_typescript_bindings && defined(invoker.use_typescript_sources) &&
> - invoker.use_typescript_sources
> -
> - if (!use_typescript_for_target && defined(invoker.use_typescript_sources)) {
> - not_needed(invoker, [ "use_typescript_sources" ])
> + if (defined(invoker.generate_webui_js_bindings)) {
> + assert(is_chromeos_ash,
> + "generate_webui_js_bindings can only be used on ChromeOS Ash")
> + assert(invoker.generate_webui_js_bindings,
> + "generate_webui_js_bindings should be set to true or removed")
> }
>
> - if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&
> - !use_typescript_for_target) {
> + use_typescript_for_target = defined(invoker.webui_module_path) &&
> + !defined(invoker.generate_webui_js_bindings)
> +
> + generate_legacy_js = !use_typescript_for_target ||
> + (defined(invoker.generate_legacy_js_bindings) &&
> + invoker.generate_legacy_js_bindings)
> +
> + if (!use_typescript_for_target &&
> + defined(invoker.generate_legacy_js_bindings)) {
> + not_needed(invoker, [ "generate_legacy_js_bindings" ])
> + }
> +
> + # Targets needed by both TS and JS bindings targets. These are needed
> + # unconditionally for JS bindings targets, and are needed for TS bindings
> + # targets when generate_legacy_js_bindings is true. This option is provided
> + # since the legacy bindings are needed by Blink tests and non-Chromium users,
> + # which are not expected to migrate to modules or TypeScript.
> + if (generate_legacy_js && (generate_js_fuzzing ||
> + !defined(invoker.cpp_only) || !invoker.cpp_only)) {
> if (sources_list != []) {
> generator_js_target_name = "${target_name}_js__generator"
>
> - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> - python2_action(generator_js_target_name) {
> + action(generator_js_target_name) {
> script = mojom_generator_script
> inputs = mojom_generator_sources + jinja2_sources
> sources = sources_list
> @@ -1702,19 +1883,18 @@ template("mojom") {
> args = common_generator_args
> filelist = []
> foreach(source, sources_list) {
> - filelist += [ rebase_path("$source", root_build_dir) ]
> + filelist += [ rebase_path(source, root_build_dir) ]
> }
> foreach(base_path, output_file_base_paths) {
> outputs += [
> "$root_gen_dir/$base_path.js",
> - "$root_gen_dir/$base_path.externs.js",
> "$root_gen_dir/$base_path.m.js",
> "$root_gen_dir/$base_path-lite.js",
> - "$root_gen_dir/$base_path.html",
> "$root_gen_dir/$base_path-lite-for-compile.js",
> ]
>
> - if (defined(invoker.webui_module_path)) {
> + if (defined(invoker.webui_module_path) &&
> + !use_typescript_for_target) {
> outputs += [ "$root_gen_dir/mojom-webui/$base_path-webui.js" ]
> }
> }
> @@ -1725,7 +1905,6 @@ template("mojom") {
> "--filelist={{response_file_name}}",
> "-g",
> "javascript",
> - "--js_bindings_mode=new",
> ]
>
> if (defined(invoker.js_generate_struct_deserializers) &&
> @@ -1739,7 +1918,7 @@ template("mojom") {
> args += message_scrambling_args
> }
>
> - if (generate_fuzzing) {
> + if (generate_js_fuzzing) {
> args += [ "--generate_fuzzing" ]
> }
> }
> @@ -1783,31 +1962,13 @@ template("mojom") {
> data_deps += [ "${full_name}_js_data_deps" ]
> }
> }
> + }
>
> - js_library_target_name = "${target_name}_js_library"
> - if (sources_list != []) {
> - js_library(js_library_target_name) {
> - extra_public_deps = [ ":$generator_js_target_name" ]
> - sources = []
> - foreach(base_path, output_file_base_paths) {
> - sources += [ "$root_gen_dir/${base_path}-lite.js" ]
> - }
> - externs_list = [
> - "${externs_path}/mojo_core.js",
> - "${externs_path}/pending.js",
> - ]
> -
> - deps = []
> - foreach(d, all_deps) {
> - full_name = get_label_info(d, "label_no_toolchain")
> - deps += [ "${full_name}_js_library" ]
> - }
> - }
> - } else {
> - group(js_library_target_name) {
> - }
> - }
> -
> + # js_library() closure compiler targets, primarily used on ChromeOS. Only
> + # generate these targets if the mojom target is not C++ only and is not using
> + # TypeScript.
> + if (generate_mojom_closure_libraries &&
> + (!defined(invoker.cpp_only) || !invoker.cpp_only) && generate_legacy_js) {
> js_library_for_compile_target_name = "${target_name}_js_library_for_compile"
> if (sources_list != []) {
> js_library(js_library_for_compile_target_name) {
> @@ -1834,35 +1995,9 @@ template("mojom") {
> }
> }
>
> - js_modules_target_name = "${target_name}_js_modules"
> - if (sources_list != []) {
> - js_library(js_modules_target_name) {
> - extra_public_deps = [ ":$generator_js_target_name" ]
> - sources = []
> - foreach(base_path, output_file_base_paths) {
> - sources += [ "$root_gen_dir/${base_path}.m.js" ]
> - }
> - externs_list = [
> - "${externs_path}/mojo_core.js",
> - "${externs_path}/pending.js",
> - ]
> - if (defined(invoker.disallow_native_types) &&
> - invoker.disallow_native_types) {
> - deps = []
> - } else {
> - deps = [ "//mojo/public/js:bindings_uncompiled" ]
> - }
> - foreach(d, all_deps) {
> - full_name = get_label_info(d, "label_no_toolchain")
> - deps += [ "${full_name}_js_modules" ]
> - }
> - }
> - } else {
> - group(js_modules_target_name) {
> - }
> - }
> -
> - if (defined(invoker.webui_module_path)) {
> + # WebUI specific closure targets, not needed by targets that are generating
> + # TypeScript WebUI bindings or by legacy-only targets.
> + if (defined(invoker.webui_module_path) && !use_typescript_for_target) {
> webui_js_target_name = "${target_name}_webui_js"
> if (sources_list != []) {
> js_library(webui_js_target_name) {
> @@ -1890,46 +2025,38 @@ template("mojom") {
> group(webui_js_target_name) {
> }
> }
> +
> + webui_grdp_target_name = "${target_name}_webui_grdp"
> + out_grd = "$target_gen_dir/${target_name}_webui_resources.grdp"
> + grd_prefix = "${target_name}_webui"
> + generate_grd(webui_grdp_target_name) {
> + grd_prefix = grd_prefix
> + out_grd = out_grd
> +
> + deps = [ ":$webui_js_target_name" ]
> +
> + input_files = []
> + foreach(base_path, output_file_base_paths) {
> + input_files += [ "${base_path}-webui.js" ]
> + }
> +
> + input_files_base_dir =
> + rebase_path("$root_gen_dir/mojom-webui", "$root_build_dir")
> + }
> }
> }
> - if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&
> - use_typescript_for_target) {
> - generator_js_target_names = []
> - source_filelist = []
> - foreach(source, sources_list) {
> - source_filelist += [ rebase_path("$source", root_build_dir) ]
> - }
> -
> - dependency_types = [
> - {
> - name = "regular"
> - ts_extension = ".ts"
> - js_extension = ".js"
> - },
> - {
> - name = "es_modules"
> - ts_extension = ".m.ts"
> - js_extension = ".m.js"
> - },
> - ]
> -
> - foreach(dependency_type, dependency_types) {
> - ts_outputs = []
> - js_outputs = []
> -
> - foreach(base_path, output_file_base_paths) {
> - ts_outputs +=
> - [ "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}" ]
> - js_outputs +=
> - [ "$root_gen_dir/$base_path-lite${dependency_type.js_extension}" ]
> + if ((generate_js_fuzzing || !defined(invoker.cpp_only) ||
> + !invoker.cpp_only) && use_typescript_for_target) {
> + if (sources_list != []) {
> + source_filelist = []
> + foreach(source, sources_list) {
> + source_filelist += [ rebase_path(source, root_build_dir) ]
> }
>
> # Generate Typescript bindings.
> - generator_ts_target_name =
> - "${target_name}_${dependency_type.name}__ts__generator"
> + generator_ts_target_name = "${target_name}_ts__generator"
>
> - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> - python2_action(generator_ts_target_name) {
> + action(generator_ts_target_name) {
> script = mojom_generator_script
> inputs = mojom_generator_sources + jinja2_sources
> sources = sources_list
> @@ -1938,7 +2065,10 @@ template("mojom") {
> "//mojo/public/tools/bindings:precompile_templates",
> ]
>
> - outputs = ts_outputs
> + outputs = []
> + foreach(base_path, output_file_base_paths) {
> + outputs += [ "$root_gen_dir/$base_path-webui.ts" ]
> + }
> args = common_generator_args
> response_file_contents = source_filelist
>
> @@ -1948,98 +2078,21 @@ template("mojom") {
> "typescript",
> ]
>
> - if (dependency_type.name == "es_modules") {
> - args += [ "--ts_use_es_modules" ]
> + if (!defined(invoker.scramble_message_ids) ||
> + invoker.scramble_message_ids) {
> + inputs += message_scrambling_inputs
> + args += message_scrambling_args
> }
>
> - # TODO(crbug.com/1007587): Support scramble_message_ids.
> + if (defined(invoker.js_generate_struct_deserializers) &&
> + invoker.js_generate_struct_deserializers) {
> + args += [ "--js_generate_struct_deserializers" ]
> + }
> +
> + # TODO(crbug.com/1007587): Support scramble_message_ids if above is
> + # insufficient.
> # TODO(crbug.com/1007591): Support generate_fuzzing.
> }
> -
> - # Create tsconfig.json for the generated Typescript.
> - tsconfig_filename =
> - "$target_gen_dir/$target_name-${dependency_type.name}-tsconfig.json"
> - tsconfig = {
> - }
> - tsconfig.compilerOptions = {
> - composite = true
> - target = "es6"
> - module = "es6"
> - lib = [
> - "es6",
> - "esnext.bigint",
> - ]
> - strict = true
> - }
> - tsconfig.files = []
> - foreach(base_path, output_file_base_paths) {
> - tsconfig.files += [ rebase_path(
> - "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}",
> - target_gen_dir,
> - root_gen_dir) ]
> - }
> - tsconfig.references = []
> -
> - # Get tsconfigs for deps.
> - foreach(d, all_deps) {
> - dep_target_gen_dir = rebase_path(get_label_info(d, "target_gen_dir"))
> - dep_name = get_label_info(d, "name")
> - reference = {
> - }
> - reference.path = "$dep_target_gen_dir/$dep_name-${dependency_type.name}-tsconfig.json"
> - tsconfig.references += [ reference ]
> - }
> - write_file(tsconfig_filename, tsconfig, "json")
> -
> - # Compile previously generated Typescript to Javascript.
> - generator_js_target_name =
> - "${target_name}_${dependency_type.name}__js__generator"
> - generator_js_target_names += [ generator_js_target_name ]
> -
> - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> - python2_action(generator_js_target_name) {
> - script = "$mojom_generator_root/compile_typescript.py"
> - sources = ts_outputs
> - outputs = js_outputs
> - public_deps = [ ":$generator_ts_target_name" ]
> - foreach(d, all_deps) {
> - full_name = get_label_info(d, "label_no_toolchain")
> - public_deps +=
> - [ "${full_name}_${dependency_type.name}__js__generator" ]
> - }
> -
> - absolute_tsconfig_path =
> - rebase_path(tsconfig_filename, "", target_gen_dir)
> - args = [ "--tsconfig_path=$absolute_tsconfig_path" ]
> - }
> - }
> -
> - js_target_name = target_name + "_js"
> - group(js_target_name) {
> - public_deps = []
> - if (sources_list != []) {
> - foreach(generator_js_target_name, generator_js_target_names) {
> - public_deps += [ ":$generator_js_target_name" ]
> - }
> - }
> -
> - foreach(d, all_deps) {
> - full_name = get_label_info(d, "label_no_toolchain")
> - public_deps += [ "${full_name}_js" ]
> - }
> - }
> -
> - group(js_data_deps_target_name) {
> - data = js_outputs
> - deps = []
> - foreach(generator_js_target_name, generator_js_target_names) {
> - deps += [ ":$generator_js_target_name" ]
> - }
> - data_deps = []
> - foreach(d, all_deps) {
> - full_name = get_label_info(d, "label_no_toolchain")
> - data_deps += [ "${full_name}_js_data_deps" ]
> - }
> }
> }
> }
> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
> index da9efc71cefd..8c641c2aa8a6 100755
> --- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
> +++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
> @@ -1,5 +1,5 @@
> #!/usr/bin/env python
> -# Copyright 2013 The Chromium Authors. All rights reserved.
> +# 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.
>
> @@ -57,10 +57,17 @@ _BUILTIN_GENERATORS = {
> "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.
> + return {} # No generators.
>
> generators = {}
> for generator_name in [s.strip() for s in generators_string.split(",")]:
> @@ -74,6 +81,21 @@ def LoadGenerators(generators_string):
> 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.)"""
> @@ -82,7 +104,7 @@ def MakeImportStackMessage(imported_filename_stack):
> zip(imported_filename_stack[1:], imported_filename_stack)]))
>
>
> -class RelativePath(object):
> +class RelativePath:
> """Represents a path relative to the source tree or generated output dir."""
>
> def __init__(self, path, source_root, output_dir):
> @@ -142,7 +164,7 @@ def ReadFileContents(filename):
> return f.read()
>
>
> -class MojomProcessor(object):
> +class MojomProcessor:
> """Takes parsed mojom modules and generates language bindings from them.
>
> Attributes:
> @@ -169,8 +191,8 @@ class MojomProcessor(object):
> if 'c++' in self._typemap:
> self._typemap['mojolpm'] = self._typemap['c++']
>
> - def _GenerateModule(self, args, remaining_args, generator_modules,
> - rel_filename, imported_filename_stack):
> + 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]
> @@ -190,12 +212,16 @@ class MojomProcessor(object):
> 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_bindings_mode=args.js_bindings_mode,
> js_generate_struct_deserializers=\
> args.js_generate_struct_deserializers,
> export_attribute=args.export_attribute,
> @@ -234,6 +260,7 @@ def _Generate(args, remaining_args):
> 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)
>
> @@ -246,7 +273,7 @@ def _Generate(args, remaining_args):
>
> for filename in args.filename:
> processor._GenerateModule(
> - args, remaining_args, generator_modules,
> + args, remaining_args, check_modules, generator_modules,
> RelativePath(filename, args.depth, args.output_dir), [])
>
> return 0
> @@ -286,6 +313,12 @@ def main():
> 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.")
> @@ -308,11 +341,6 @@ def main():
> 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_bindings_mode", choices=["new", "old"], default="old",
> - help="This option only affects the JavaScript bindings. The value could "
> - "be \"new\" to generate new-style lite JS bindings in addition to the "
> - "old, or \"old\" to only generate old bindings.")
> generate_parser.add_argument(
> "--js_generate_struct_deserializers", action="store_true",
> help="Generate javascript deserialize methods for structs in "
> @@ -387,4 +415,10 @@ def main():
>
> if __name__ == "__main__":
> with crbug_1001171.DumpStateOnLookupError():
> - sys.exit(main())
> + 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)
> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
> index bddbe3f4c580..761922b6777e 100644
> --- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
> +++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# 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.
>
> @@ -8,13 +8,13 @@ from mojom_bindings_generator import MakeImportStackMessage
> from mojom_bindings_generator import ScrambleMethodOrdinals
>
>
> -class FakeIface(object):
> +class FakeIface:
> def __init__(self):
> self.mojom_name = None
> self.methods = None
>
>
> -class FakeMethod(object):
> +class FakeMethod:
> def __init__(self, explicit_ordinal=None):
> self.explicit_ordinal = explicit_ordinal
> self.ordinal = explicit_ordinal
> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py b/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py
> deleted file mode 100755
> index 15f0e3bac1f7..000000000000
> --- a/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py
> +++ /dev/null
> @@ -1,119 +0,0 @@
> -#!/usr/bin/env python
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> -# Use of this source code is governed by a BSD-style license that can be
> -# found in the LICENSE file.
> -"""Downgrades *.mojom files to the old mojo types for remotes and receivers."""
> -
> -import argparse
> -import fnmatch
> -import os
> -import re
> -import shutil
> -import sys
> -import tempfile
> -
> -# List of patterns and replacements to match and use against the contents of a
> -# mojo file. Each replacement string will be used with Python string's format()
> -# function, so the '{}' substring is used to mark where the mojo type should go.
> -_MOJO_REPLACEMENTS = {
> - r'pending_remote': r'{}',
> - r'pending_receiver': r'{}&',
> - r'pending_associated_remote': r'associated {}',
> - r'pending_associated_receiver': r'associated {}&',
> -}
> -
> -# Pre-compiled regular expression that matches against any of the replacements.
> -_REGEXP_PATTERN = re.compile(
> - r'|'.join(
> - ['{}\s*<\s*(.*?)\s*>'.format(k) for k in _MOJO_REPLACEMENTS.keys()]),
> - flags=re.DOTALL)
> -
> -
> -def ReplaceFunction(match_object):
> - """Returns the right replacement for the string matched against the regexp."""
> - for index, (match, repl) in enumerate(_MOJO_REPLACEMENTS.items(), 1):
> - if match_object.group(0).startswith(match):
> - return repl.format(match_object.group(index))
> -
> -
> -def DowngradeFile(path, output_dir=None):
> - """Downgrades the mojom file specified by |path| to the old mojo types.
> -
> - Optionally pass |output_dir| to place the result under a separate output
> - directory, preserving the relative path to the file included in |path|.
> - """
> - # Use a temporary file to dump the new contents after replacing the patterns.
> - with open(path) as src_mojo_file:
> - with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp_mojo_file:
> - tmp_contents = _REGEXP_PATTERN.sub(ReplaceFunction, src_mojo_file.read())
> - tmp_mojo_file.write(tmp_contents)
> -
> - # Files should be placed in the desired output directory
> - if output_dir:
> - output_filepath = os.path.join(output_dir, os.path.basename(path))
> - if not os.path.exists(output_dir):
> - os.makedirs(output_dir)
> - else:
> - output_filepath = path
> -
> - # Write the new contents preserving the original file's attributes.
> - shutil.copystat(path, tmp_mojo_file.name)
> - shutil.move(tmp_mojo_file.name, output_filepath)
> -
> - # Make sure to "touch" the new file so that access, modify and change times
> - # are always newer than the source file's, otherwise Modify time will be kept
> - # as per the call to shutil.copystat(), causing unnecessary generations of the
> - # output file in subsequent builds due to ninja considering it dirty.
> - os.utime(output_filepath, None)
> -
> -
> -def DowngradeDirectory(path, output_dir=None):
> - """Downgrades mojom files inside directory |path| to the old mojo types.
> -
> - Optionally pass |output_dir| to place the result under a separate output
> - directory, preserving the relative path to the file included in |path|.
> - """
> - # We don't have recursive glob.glob() nor pathlib.Path.rglob() in Python 2.7
> - mojom_filepaths = []
> - for dir_path, _, filenames in os.walk(path):
> - for filename in fnmatch.filter(filenames, "*mojom"):
> - mojom_filepaths.append(os.path.join(dir_path, filename))
> -
> - for path in mojom_filepaths:
> - absolute_dirpath = os.path.dirname(os.path.abspath(path))
> - if output_dir:
> - dest_dirpath = output_dir + absolute_dirpath
> - else:
> - dest_dirpath = absolute_dirpath
> - DowngradeFile(path, dest_dirpath)
> -
> -
> -def DowngradePath(src_path, output_dir=None):
> - """Downgrades the mojom files pointed by |src_path| to the old mojo types.
> -
> - Optionally pass |output_dir| to place the result under a separate output
> - directory, preserving the relative path to the file included in |path|.
> - """
> - if os.path.isdir(src_path):
> - DowngradeDirectory(src_path, output_dir)
> - elif os.path.isfile(src_path):
> - DowngradeFile(src_path, output_dir)
> - else:
> - print(">>> {} not pointing to a valid file or directory".format(src_path))
> - sys.exit(1)
> -
> -
> -def main():
> - parser = argparse.ArgumentParser(
> - description="Downgrade *.mojom files to use the old mojo types.")
> - parser.add_argument(
> - "srcpath", help="path to the file or directory to apply the conversion")
> - parser.add_argument(
> - "--outdir", help="the directory to place the converted file(s) under")
> - args = parser.parse_args()
> -
> - DowngradePath(args.srcpath, args.outdir)
> -
> -
> -if __name__ == "__main__":
> - sys.exit(main())
> diff --git a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
> index f1783d59b951..6bb7a209310e 100755
> --- a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
> +++ b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
> @@ -1,5 +1,5 @@
> #!/usr/bin/env python
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# 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.
>
> @@ -17,7 +17,8 @@ def CheckCppTypemapConfigs(target_name, config_filename, out_filename):
> ])
> _SUPPORTED_TYPE_KEYS = set([
> 'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable',
> - 'move_only', 'nullable_is_same_type'
> + 'move_only', 'nullable_is_same_type', 'forward_declaration',
> + 'default_constructible'
> ])
> with open(config_filename, 'r') as f:
> for config in json.load(f):
> diff --git a/utils/ipc/mojo/public/tools/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/BUILD.gn
> new file mode 100644
> index 000000000000..eafb95a18ad1
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/mojom/BUILD.gn
> @@ -0,0 +1,18 @@
> +# 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.
> +
> +group("tests") {
> + data = [
> + "check_stable_mojom_compatibility_unittest.py",
> + "check_stable_mojom_compatibility.py",
> + "const_unittest.py",
> + "enum_unittest.py",
> + "feature_unittest.py",
> + "mojom_parser_test_case.py",
> + "mojom_parser_unittest.py",
> + "mojom_parser.py",
> + "stable_attribute_unittest.py",
> + "version_compatibility_unittest.py",
> + ]
> +}
> diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py
> index 08bd672f5ba5..35cd1cfde155 100755
> --- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py
> +++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py
> @@ -1,5 +1,5 @@
> -#!/usr/bin/env python
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +#!/usr/bin/env python3
> +# 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.
> """Verifies backward-compatibility of mojom type changes.
> @@ -12,20 +12,18 @@ This can be used e.g. by a presubmit check to prevent developers from making
> breaking changes to stable mojoms."""
>
> import argparse
> -import errno
> import io
> import json
> import os
> import os.path
> -import shutil
> -import six
> import sys
> -import tempfile
>
> from mojom.generate import module
> from mojom.generate import translate
> from mojom.parse import parser
>
> +# pylint: disable=raise-missing-from
> +
>
> class ParseError(Exception):
> pass
> @@ -41,6 +39,8 @@ def _ValidateDelta(root, delta):
> transitive closure of a mojom's input dependencies all at once.
> """
>
> + translate.is_running_backwards_compatibility_check_hack = True
> +
> # First build a map of all files covered by the delta
> affected_files = set()
> old_files = {}
> @@ -73,11 +73,35 @@ def _ValidateDelta(root, delta):
> try:
> ast = parser.Parse(contents, mojom)
> except Exception as e:
> - six.reraise(
> - ParseError,
> - 'encountered exception {0} while parsing {1}'.format(e, mojom),
> - sys.exc_info()[2])
> + raise ParseError('encountered exception {0} while parsing {1}'.format(
> + e, mojom))
> +
> + # Files which are generated at compile time can't be checked by this script
> + # (at the moment) since they may not exist in the output directory.
> + generated_files_to_skip = {
> + ('third_party/blink/public/mojom/runtime_feature_state/'
> + 'runtime_feature.mojom'),
> + ('third_party/blink/public/mojom/origin_trial_feature/'
> + 'origin_trial_feature.mojom'),
> + }
> +
> + ast.import_list.items = [
> + x for x in ast.import_list.items
> + if x.import_filename not in generated_files_to_skip
> + ]
> +
> for imp in ast.import_list:
> + if (not file_overrides.get(imp.import_filename)
> + and not os.path.exists(os.path.join(root, imp.import_filename))):
> + # Speculatively construct a path prefix to locate the import_filename
> + mojom_path = os.path.dirname(os.path.normpath(mojom)).split(os.sep)
> + test_prefix = ''
> + for path_component in mojom_path:
> + test_prefix = os.path.join(test_prefix, path_component)
> + test_import_filename = os.path.join(test_prefix, imp.import_filename)
> + if os.path.exists(os.path.join(root, test_import_filename)):
> + imp.import_filename = test_import_filename
> + break
> parseMojom(imp.import_filename, file_overrides, override_modules)
>
> # Now that the transitive set of dependencies has been imported and parsed
> @@ -89,10 +113,10 @@ def _ValidateDelta(root, delta):
> modules[mojom] = translate.OrderedModule(ast, mojom, all_modules)
>
> old_modules = {}
> - for mojom in old_files.keys():
> + for mojom in old_files:
> parseMojom(mojom, old_files, old_modules)
> new_modules = {}
> - for mojom in new_files.keys():
> + for mojom in new_files:
> parseMojom(mojom, new_files, new_modules)
>
> # At this point we have a complete set of translated Modules from both the
> @@ -132,12 +156,21 @@ def _ValidateDelta(root, delta):
> 'can be deleted by a subsequent change.' % qualified_name)
>
> checker = module.BackwardCompatibilityChecker()
> - if not checker.IsBackwardCompatible(new_types[new_name], kind):
> - raise Exception('Stable type %s appears to have changed in a way which '
> - 'breaks backward-compatibility. Please fix!\n\nIf you '
> - 'believe this assessment to be incorrect, please file a '
> - 'Chromium bug against the "Internals>Mojo>Bindings" '
> - 'component.' % qualified_name)
> + try:
> + if not checker.IsBackwardCompatible(new_types[new_name], kind):
> + raise Exception(
> + 'Stable type %s appears to have changed in a way which '
> + 'breaks backward-compatibility. Please fix!\n\nIf you '
> + 'believe this assessment to be incorrect, please file a '
> + 'Chromium bug against the "Internals>Mojo>Bindings" '
> + 'component.' % qualified_name)
> + except Exception as e:
> + raise Exception(
> + 'Stable type %s appears to have changed in a way which '
> + 'breaks backward-compatibility: \n\n%s.\nPlease fix!\n\nIf you '
> + 'believe this assessment to be incorrect, please file a '
> + 'Chromium bug against the "Internals>Mojo>Bindings" '
> + 'component.' % (qualified_name, e))
>
>
> def Run(command_line, delta=None):
> diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py
> index 9f51ea7751fb..06769c9533c3 100755
> --- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py
> @@ -1,5 +1,5 @@
> -#!/usr/bin/env python
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +#!/usr/bin/env python3
> +# 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.
>
> @@ -15,7 +15,7 @@ import check_stable_mojom_compatibility
> from mojom.generate import module
>
>
> -class Change(object):
> +class Change:
> """Helper to clearly define a mojom file delta to be analyzed."""
>
> def __init__(self, filename, old=None, new=None):
> @@ -28,7 +28,7 @@ class Change(object):
>
> class UnchangedFile(Change):
> def __init__(self, filename, contents):
> - super(UnchangedFile, self).__init__(filename, old=contents, new=contents)
> + super().__init__(filename, old=contents, new=contents)
>
>
> class CheckStableMojomCompatibilityTest(unittest.TestCase):
> @@ -258,3 +258,82 @@ class CheckStableMojomCompatibilityTest(unittest.TestCase):
> [Stable] struct T { foo.S s; int32 x; };
> """)
> ])
> +
> + def testWithPartialImport(self):
> + """The compatibility checking tool correctly parses imports with partial
> + paths."""
> + self.assertBackwardCompatible([
> + UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
> + Change('foo/bar.mojom',
> + old="""\
> + module bar;
> + import "foo/foo.mojom";
> + [Stable] struct T { foo.S s; };
> + """,
> + new="""\
> + module bar;
> + import "foo.mojom";
> + [Stable] struct T { foo.S s; };
> + """)
> + ])
> +
> + self.assertBackwardCompatible([
> + UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
> + Change('foo/bar.mojom',
> + old="""\
> + module bar;
> + import "foo.mojom";
> + [Stable] struct T { foo.S s; };
> + """,
> + new="""\
> + module bar;
> + import "foo/foo.mojom";
> + [Stable] struct T { foo.S s; };
> + """)
> + ])
> +
> + self.assertNotBackwardCompatible([
> + UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
> + Change('bar/bar.mojom',
> + old="""\
> + module bar;
> + import "foo/foo.mojom";
> + [Stable] struct T { foo.S s; };
> + """,
> + new="""\
> + module bar;
> + import "foo.mojom";
> + [Stable] struct T { foo.S s; };
> + """)
> + ])
> +
> + self.assertNotBackwardCompatible([
> + UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
> + Change('bar/bar.mojom',
> + old="""\
> + module bar;
> + import "foo.mojom";
> + [Stable] struct T { foo.S s; };
> + """,
> + new="""\
> + module bar;
> + import "foo/foo.mojom";
> + [Stable] struct T { foo.S s; };
> + """)
> + ])
> +
> + def testNewEnumDefault(self):
> + # Should be backwards compatible since it does not affect the wire format.
> + # This specific case also checks that the backwards compatibility checker
> + # does not throw an error due to the older version of the enum not
> + # specifying [Default].
> + self.assertBackwardCompatible([
> + Change('foo/foo.mojom',
> + old='[Extensible] enum E { One };',
> + new='[Extensible] enum E { [Default] One };')
> + ])
> + self.assertBackwardCompatible([
> + Change('foo/foo.mojom',
> + old='[Extensible] enum E { [Default] One, Two, };',
> + new='[Extensible] enum E { One, [Default] Two, };')
> + ])
> diff --git a/utils/ipc/mojo/public/tools/mojom/const_unittest.py b/utils/ipc/mojo/public/tools/mojom/const_unittest.py
> index cb42dfac3556..e8ed36a7a5bf 100644
> --- a/utils/ipc/mojo/public/tools/mojom/const_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/const_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# 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.
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
> index d9005078671e..9269cde526b4 100644
> --- a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# 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.
>
> @@ -90,3 +90,31 @@ class EnumTest(MojomParserTestCase):
> self.assertEqual('F', b.enums[0].mojom_name)
> self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name)
> self.assertEqual(37, b.enums[0].fields[0].numeric_value)
> +
> + def testEnumAttributesAreEnums(self):
> + """Verifies that enum values in attributes are really enum types."""
> + a_mojom = 'a.mojom'
> + self.WriteFile(a_mojom, 'module a; enum E { kFoo, kBar };')
> + b_mojom = 'b.mojom'
> + self.WriteFile(
> + b_mojom, 'module b;'
> + 'import "a.mojom";'
> + '[MooCow=a.E.kFoo]'
> + 'interface Foo { Foo(); };')
> + self.ParseMojoms([a_mojom, b_mojom])
> + b = self.LoadModule(b_mojom)
> + self.assertEqual(b.interfaces[0].attributes['MooCow'].mojom_name, 'kFoo')
> +
> + def testConstantAttributes(self):
> + """Verifies that constants as attributes are translated to the constant."""
> + a_mojom = 'a.mojom'
> + self.WriteFile(
> + a_mojom, 'module a;'
> + 'enum E { kFoo, kBar };'
> + 'const E kB = E.kFoo;'
> + '[Attr=kB] interface Hello { Foo(); };')
> + self.ParseMojoms([a_mojom])
> + a = self.LoadModule(a_mojom)
> + self.assertEqual(a.interfaces[0].attributes['Attr'].mojom_name, 'kB')
> + self.assertEquals(a.interfaces[0].attributes['Attr'].value.mojom_name,
> + 'kFoo')
> diff --git a/utils/ipc/mojo/public/tools/mojom/feature_unittest.py b/utils/ipc/mojo/public/tools/mojom/feature_unittest.py
> new file mode 100644
> index 000000000000..5f014e1cfe9a
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/mojom/feature_unittest.py
> @@ -0,0 +1,84 @@
> +# 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.
> +
> +from mojom_parser_test_case import MojomParserTestCase
> +
> +
> +class FeatureTest(MojomParserTestCase):
> + """Tests feature parsing behavior."""
> + def testFeatureOff(self):
> + """Verifies basic parsing of feature types."""
> + types = self.ExtractTypes("""
> + // e.g. BASE_DECLARE_FEATURE(kFeature);
> + [AttributeOne=ValueOne]
> + feature kFeature {
> + // BASE_FEATURE(kFeature,"MyFeature",
> + // base::FEATURE_DISABLED_BY_DEFAULT);
> + const string name = "MyFeature";
> + const bool default_state = false;
> + };
> + """)
> + self.assertEqual('name', types['kFeature'].constants[0].mojom_name)
> + self.assertEqual('"MyFeature"', types['kFeature'].constants[0].value)
> + self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)
> + self.assertEqual('false', types['kFeature'].constants[1].value)
> +
> + def testFeatureOn(self):
> + """Verifies basic parsing of feature types."""
> + types = self.ExtractTypes("""
> + // e.g. BASE_DECLARE_FEATURE(kFeature);
> + feature kFeature {
> + // BASE_FEATURE(kFeature,"MyFeature",
> + // base::FEATURE_ENABLED_BY_DEFAULT);
> + const string name = "MyFeature";
> + const bool default_state = true;
> + };
> + """)
> + self.assertEqual('name', types['kFeature'].constants[0].mojom_name)
> + self.assertEqual('"MyFeature"', types['kFeature'].constants[0].value)
> + self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)
> + self.assertEqual('true', types['kFeature'].constants[1].value)
> +
> + def testFeatureWeakKeyword(self):
> + """Verifies that `feature` is a weak keyword."""
> + types = self.ExtractTypes("""
> + // e.g. BASE_DECLARE_FEATURE(kFeature);
> + [AttributeOne=ValueOne]
> + feature kFeature {
> + // BASE_FEATURE(kFeature,"MyFeature",
> + // base::FEATURE_DISABLED_BY_DEFAULT);
> + const string name = "MyFeature";
> + const bool default_state = false;
> + };
> + struct MyStruct {
> + bool feature = true;
> + };
> + interface InterfaceName {
> + Method(string feature) => (int32 feature);
> + };
> + """)
> + self.assertEqual('name', types['kFeature'].constants[0].mojom_name)
> + self.assertEqual('"MyFeature"', types['kFeature'].constants[0].value)
> + self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)
> + self.assertEqual('false', types['kFeature'].constants[1].value)
> +
> + def testFeatureAttributesAreFeatures(self):
> + """Verifies that feature values in attributes are really feature types."""
> + a_mojom = 'a.mojom'
> + self.WriteFile(
> + a_mojom, 'module a;'
> + 'feature F { const string name = "f";'
> + 'const bool default_state = false; };')
> + b_mojom = 'b.mojom'
> + self.WriteFile(
> + b_mojom, 'module b;'
> + 'import "a.mojom";'
> + 'feature G'
> + '{const string name = "g"; const bool default_state = false;};'
> + '[Attri=a.F] interface Foo { Foo(); };'
> + '[Boink=G] interface Bar {};')
> + self.ParseMojoms([a_mojom, b_mojom])
> + b = self.LoadModule(b_mojom)
> + self.assertEqual(b.interfaces[0].attributes['Attri'].mojom_name, 'F')
> + self.assertEqual(b.interfaces[1].attributes['Boink'].mojom_name, 'G')
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
> index 51facc0ccb84..a0edf0eb5d2c 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
> @@ -1,4 +1,4 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# 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.
>
> @@ -8,6 +8,7 @@ group("mojom") {
> "error.py",
> "fileutil.py",
> "generate/__init__.py",
> + "generate/check.py",
> "generate/generator.py",
> "generate/module.py",
> "generate/pack.py",
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/error.py b/utils/ipc/mojo/public/tools/mojom/mojom/error.py
> index 8a1e03da5c1d..dd53b835a49e 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/error.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/error.py
> @@ -1,4 +1,4 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# 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.
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
> index bf626f54798c..124f12c134b9 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
> @@ -1,9 +1,8 @@
> -# Copyright 2015 The Chromium Authors. All rights reserved.
> +# Copyright 2015 The Chromium Authors
> # Use of this source code is governed by a BSD-style license that can be
> # found in the LICENSE file.
>
> import errno
> -import imp
> import os.path
> import sys
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
> index ff5753a29114..c93d22898d29 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
> @@ -1,20 +1,17 @@
> -# Copyright 2015 The Chromium Authors. All rights reserved.
> +# Copyright 2015 The Chromium Authors
> # Use of this source code is governed by a BSD-style license that can be
> # found in the LICENSE file.
>
> -import imp
> import os.path
> import shutil
> -import sys
> import tempfile
> import unittest
>
> from mojom import fileutil
>
> -
> class FileUtilTest(unittest.TestCase):
> def testEnsureDirectoryExists(self):
> - """Test that EnsureDirectoryExists fuctions correctly."""
> + """Test that EnsureDirectoryExists functions correctly."""
>
> temp_dir = tempfile.mkdtemp()
> try:
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
> new file mode 100644
> index 000000000000..1efe2022342b
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
> @@ -0,0 +1,26 @@
> +# 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.
> +"""Code shared by the various pre-generation mojom checkers."""
> +
> +
> +class CheckException(Exception):
> + def __init__(self, module, message):
> + self.module = module
> + self.message = message
> + super().__init__(self.message)
> +
> + def __str__(self):
> + return "Failed mojo pre-generation check for {}:\n{}".format(
> + self.module.path, self.message)
> +
> +
> +class Check:
> + def __init__(self, module):
> + self.module = module
> +
> + def CheckModule(self):
> + """ Subclass should return True if its Checks pass, and throw an
> + exception otherwise. CheckModule will be called immediately before
> + mojom.generate.Generator.GenerateFiles()"""
> + raise NotImplementedError("Subclasses must override/implement this method")
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py
> deleted file mode 100644
> index 0dfd996e355d..000000000000
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py
> +++ /dev/null
> @@ -1,93 +0,0 @@
> -# Copyright 2015 The Chromium Authors. All rights reserved.
> -# Use of this source code is governed by a BSD-style license that can be
> -# found in the LICENSE file.
> -"""Resolves the values used for constants and enums."""
> -
> -from itertools import ifilter
> -
> -from mojom.generate import module as mojom
> -
> -
> -def ResolveConstants(module, expression_to_text):
> - in_progress = set()
> - computed = set()
> -
> - def GetResolvedValue(named_value):
> - assert isinstance(named_value, (mojom.EnumValue, mojom.ConstantValue))
> - if isinstance(named_value, mojom.EnumValue):
> - field = next(
> - ifilter(lambda field: field.name == named_value.name,
> - named_value.enum.fields), None)
> - if not field:
> - raise RuntimeError(
> - 'Unable to get computed value for field %s of enum %s' %
> - (named_value.name, named_value.enum.name))
> - if field not in computed:
> - ResolveEnum(named_value.enum)
> - return field.resolved_value
> - else:
> - ResolveConstant(named_value.constant)
> - named_value.resolved_value = named_value.constant.resolved_value
> - return named_value.resolved_value
> -
> - def ResolveConstant(constant):
> - if constant in computed:
> - return
> - if constant in in_progress:
> - raise RuntimeError('Circular dependency for constant: %s' % constant.name)
> - in_progress.add(constant)
> - if isinstance(constant.value, (mojom.EnumValue, mojom.ConstantValue)):
> - resolved_value = GetResolvedValue(constant.value)
> - else:
> - resolved_value = expression_to_text(constant.value)
> - constant.resolved_value = resolved_value
> - in_progress.remove(constant)
> - computed.add(constant)
> -
> - def ResolveEnum(enum):
> - def ResolveEnumField(enum, field, default_value):
> - if field in computed:
> - return
> - if field in in_progress:
> - raise RuntimeError('Circular dependency for enum: %s' % enum.name)
> - in_progress.add(field)
> - if field.value:
> - if isinstance(field.value, mojom.EnumValue):
> - resolved_value = GetResolvedValue(field.value)
> - elif isinstance(field.value, str):
> - resolved_value = int(field.value, 0)
> - else:
> - raise RuntimeError('Unexpected value: %s' % field.value)
> - else:
> - resolved_value = default_value
> - field.resolved_value = resolved_value
> - in_progress.remove(field)
> - computed.add(field)
> -
> - current_value = 0
> - for field in enum.fields:
> - ResolveEnumField(enum, field, current_value)
> - current_value = field.resolved_value + 1
> -
> - for constant in module.constants:
> - ResolveConstant(constant)
> -
> - for enum in module.enums:
> - ResolveEnum(enum)
> -
> - for struct in module.structs:
> - for constant in struct.constants:
> - ResolveConstant(constant)
> - for enum in struct.enums:
> - ResolveEnum(enum)
> - for field in struct.fields:
> - if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)):
> - field.default.resolved_value = GetResolvedValue(field.default)
> -
> - for interface in module.interfaces:
> - for constant in interface.constants:
> - ResolveConstant(constant)
> - for enum in interface.enums:
> - ResolveEnum(enum)
> -
> - return module
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
> index 4a1c73fcf82f..96fe3a2d0920 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
> @@ -1,4 +1,4 @@
> -# Copyright 2013 The Chromium Authors. All rights reserved.
> +# 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.
> """Code shared by the various language-specific code generators."""
> @@ -97,7 +97,7 @@ def ToLowerSnakeCase(identifier):
> return _ToSnakeCase(identifier, upper=False)
>
>
> -class Stylizer(object):
> +class Stylizer:
> """Stylizers specify naming rules to map mojom names to names in generated
> code. For example, if you would like method_name in mojom to be mapped to
> MethodName in the generated code, you need to define a subclass of Stylizer
> @@ -130,6 +130,9 @@ class Stylizer(object):
> def StylizeEnum(self, mojom_name):
> return mojom_name
>
> + def StylizeFeature(self, mojom_name):
> + return mojom_name
> +
> def StylizeModule(self, mojom_namespace):
> return mojom_namespace
>
> @@ -233,7 +236,7 @@ def AddComputedData(module):
> _AddInterfaceComputedData(interface)
>
>
> -class Generator(object):
> +class Generator:
> # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all
> # files to stdout.
> def __init__(self,
> @@ -243,7 +246,6 @@ class Generator(object):
> variant=None,
> bytecode_path=None,
> for_blink=False,
> - js_bindings_mode="new",
> js_generate_struct_deserializers=False,
> export_attribute=None,
> export_header=None,
> @@ -262,7 +264,6 @@ class Generator(object):
> self.variant = variant
> self.bytecode_path = bytecode_path
> self.for_blink = for_blink
> - self.js_bindings_mode = js_bindings_mode
> self.js_generate_struct_deserializers = js_generate_struct_deserializers
> self.export_attribute = export_attribute
> self.export_header = export_header
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
> index 32c884a8c02e..7143e07c4d7e 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
> @@ -1,13 +1,12 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# 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 imp
> +import importlib.util
> import os.path
> import sys
> import unittest
>
> -
> def _GetDirAbove(dirname):
> """Returns the directory "above" this file containing |dirname| (which must
> also be "above" this file)."""
> @@ -20,12 +19,11 @@ def _GetDirAbove(dirname):
>
>
> try:
> - imp.find_module("mojom")
> + importlib.util.find_spec("mojom")
> except ImportError:
> sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
> from mojom.generate import generator
>
> -
> class StringManipulationTest(unittest.TestCase):
> """generator contains some string utilities, this tests only those."""
>
> @@ -69,6 +67,5 @@ class StringManipulationTest(unittest.TestCase):
> self.assertEquals("SNAKE_D3D11_CASE",
> generator.ToUpperSnakeCase("snakeD3d11Case"))
>
> -
> if __name__ == "__main__":
> unittest.main()
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
> index 9bdb28e05f65..ca71059d53b1 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
> @@ -1,4 +1,4 @@
> -# Copyright 2013 The Chromium Authors. All rights reserved.
> +# 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.
>
> @@ -12,15 +12,14 @@
> # method = interface.AddMethod('Tat', 0)
> # method.AddParameter('baz', 0, mojom.INT32)
>
> -import sys
> -if sys.version_info.major == 2:
> - import cPickle as pickle
> -else:
> - import pickle
> +import pickle
> +from collections import OrderedDict
> from uuid import UUID
>
> +# pylint: disable=raise-missing-from
>
> -class BackwardCompatibilityChecker(object):
> +
> +class BackwardCompatibilityChecker:
> """Used for memoization while recursively checking two type definitions for
> backward-compatibility."""
>
> @@ -64,23 +63,20 @@ def Repr(obj, as_ref=True):
> return obj.Repr(as_ref=as_ref)
> # Since we cannot implement Repr for existing container types, we
> # handle them here.
> - elif isinstance(obj, list):
> + if isinstance(obj, list):
> if not obj:
> return '[]'
> - else:
> - return ('[\n%s\n]' % (',\n'.join(
> - ' %s' % Repr(elem, as_ref).replace('\n', '\n ')
> - for elem in obj)))
> - elif isinstance(obj, dict):
> + return ('[\n%s\n]' %
> + (',\n'.join(' %s' % Repr(elem, as_ref).replace('\n', '\n ')
> + for elem in obj)))
> + if isinstance(obj, dict):
> if not obj:
> return '{}'
> - else:
> - return ('{\n%s\n}' % (',\n'.join(
> - ' %s: %s' % (Repr(key, as_ref).replace('\n', '\n '),
> - Repr(val, as_ref).replace('\n', '\n '))
> - for key, val in obj.items())))
> - else:
> - return repr(obj)
> + return ('{\n%s\n}' % (',\n'.join(' %s: %s' %
> + (Repr(key, as_ref).replace('\n', '\n '),
> + Repr(val, as_ref).replace('\n', '\n '))
> + for key, val in obj.items())))
> + return repr(obj)
>
>
> def GenericRepr(obj, names):
> @@ -104,7 +100,7 @@ def GenericRepr(obj, names):
> ReprIndent(name, as_ref) for (name, as_ref) in names.items()))
>
>
> -class Kind(object):
> +class Kind:
> """Kind represents a type (e.g. int8, string).
>
> Attributes:
> @@ -112,16 +108,43 @@ class Kind(object):
> module: {Module} The defining module. Set to None for built-in types.
> parent_kind: The enclosing type. For example, an enum defined
> inside an interface has that interface as its parent. May be None.
> + is_nullable: True if the type is nullable.
> """
>
> - def __init__(self, spec=None, module=None):
> + def __init__(self, spec=None, is_nullable=False, module=None):
> self.spec = spec
> self.module = module
> self.parent_kind = None
> + self.is_nullable = is_nullable
> + self.shared_definition = {}
> +
> + @classmethod
> + def AddSharedProperty(cls, name):
> + """Adds a property |name| to |cls|, which accesses the corresponding item in
> + |shared_definition|.
> +
> + The reason of adding such indirection is to enable sharing definition
> + between a reference kind and its nullable variation. For example:
> + a = Struct('test_struct_1')
> + b = a.MakeNullableKind()
> + a.name = 'test_struct_2'
> + print(b.name) # Outputs 'test_struct_2'.
> + """
> + def Get(self):
> + try:
> + return self.shared_definition[name]
> + except KeyError: # Must raise AttributeError if property doesn't exist.
> + raise AttributeError
> +
> + def Set(self, value):
> + self.shared_definition[name] = value
> +
> + setattr(cls, name, property(Get, Set))
>
> def Repr(self, as_ref=True):
> # pylint: disable=unused-argument
> - return '<%s spec=%r>' % (self.__class__.__name__, self.spec)
> + return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec,
> + self.is_nullable)
>
> def __repr__(self):
> # Gives us a decent __repr__ for all kinds.
> @@ -130,7 +153,8 @@ class Kind(object):
> def __eq__(self, rhs):
> # pylint: disable=unidiomatic-typecheck
> return (type(self) == type(rhs)
> - and (self.spec, self.parent_kind) == (rhs.spec, rhs.parent_kind))
> + and (self.spec, self.parent_kind, self.is_nullable)
> + == (rhs.spec, rhs.parent_kind, rhs.is_nullable))
>
> def __hash__(self):
> # TODO(crbug.com/1060471): Remove this and other __hash__ methods on Kind
> @@ -138,32 +162,113 @@ class Kind(object):
> # some primitive Kinds as dict keys. The default hash (object identity)
> # breaks these dicts when a pickled Module instance is unpickled and used
> # during a subsequent run of the parser.
> - return hash((self.spec, self.parent_kind))
> + return hash((self.spec, self.parent_kind, self.is_nullable))
>
> # pylint: disable=unused-argument
> def IsBackwardCompatible(self, rhs, checker):
> return self == rhs
>
>
> +class ValueKind(Kind):
> + """ValueKind represents values that aren't reference kinds.
> +
> + The primary difference is the wire representation for nullable value kinds
> + still reserves space for the value type itself, even if that value itself
> + is logically null.
> + """
> + def __init__(self, spec=None, is_nullable=False, module=None):
> + assert spec is None or is_nullable == spec.startswith('?')
> + Kind.__init__(self, spec, is_nullable, module)
> +
> + def MakeNullableKind(self):
> + assert not self.is_nullable
> +
> + if self == BOOL:
> + return NULLABLE_BOOL
> + if self == INT8:
> + return NULLABLE_INT8
> + if self == INT16:
> + return NULLABLE_INT16
> + if self == INT32:
> + return NULLABLE_INT32
> + if self == INT64:
> + return NULLABLE_INT64
> + if self == UINT8:
> + return NULLABLE_UINT8
> + if self == UINT16:
> + return NULLABLE_UINT16
> + if self == UINT32:
> + return NULLABLE_UINT32
> + if self == UINT64:
> + return NULLABLE_UINT64
> + if self == FLOAT:
> + return NULLABLE_FLOAT
> + if self == DOUBLE:
> + return NULLABLE_DOUBLE
> +
> + nullable_kind = type(self)()
> + nullable_kind.shared_definition = self.shared_definition
> + if self.spec is not None:
> + nullable_kind.spec = '?' + self.spec
> + nullable_kind.is_nullable = True
> + nullable_kind.parent_kind = self.parent_kind
> + nullable_kind.module = self.module
> +
> + return nullable_kind
> +
> + def MakeUnnullableKind(self):
> + assert self.is_nullable
> +
> + if self == NULLABLE_BOOL:
> + return BOOL
> + if self == NULLABLE_INT8:
> + return INT8
> + if self == NULLABLE_INT16:
> + return INT16
> + if self == NULLABLE_INT32:
> + return INT32
> + if self == NULLABLE_INT64:
> + return INT64
> + if self == NULLABLE_UINT8:
> + return UINT8
> + if self == NULLABLE_UINT16:
> + return UINT16
> + if self == NULLABLE_UINT32:
> + return UINT32
> + if self == NULLABLE_UINT64:
> + return UINT64
> + if self == NULLABLE_FLOAT:
> + return FLOAT
> + if self == NULLABLE_DOUBLE:
> + return DOUBLE
> +
> + nullable_kind = type(self)()
> + nullable_kind.shared_definition = self.shared_definition
> + if self.spec is not None:
> + nullable_kind.spec = self.spec[1:]
> + nullable_kind.is_nullable = False
> + nullable_kind.parent_kind = self.parent_kind
> + nullable_kind.module = self.module
> +
> + return nullable_kind
> +
> + def __eq__(self, rhs):
> + return (isinstance(rhs, ValueKind) and super().__eq__(rhs))
> +
> + def __hash__(self): # pylint: disable=useless-super-delegation
> + return super().__hash__()
> +
> +
> class ReferenceKind(Kind):
> """ReferenceKind represents pointer and handle types.
>
> A type is nullable if null (for pointer types) or invalid handle (for handle
> types) is a legal value for the type.
> -
> - Attributes:
> - is_nullable: True if the type is nullable.
> """
>
> def __init__(self, spec=None, is_nullable=False, module=None):
> assert spec is None or is_nullable == spec.startswith('?')
> - Kind.__init__(self, spec, module)
> - self.is_nullable = is_nullable
> - self.shared_definition = {}
> -
> - def Repr(self, as_ref=True):
> - return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec,
> - self.is_nullable)
> + Kind.__init__(self, spec, is_nullable, module)
>
> def MakeNullableKind(self):
> assert not self.is_nullable
> @@ -193,55 +298,65 @@ class ReferenceKind(Kind):
>
> return nullable_kind
>
> - @classmethod
> - def AddSharedProperty(cls, name):
> - """Adds a property |name| to |cls|, which accesses the corresponding item in
> - |shared_definition|.
> + def MakeUnnullableKind(self):
> + assert self.is_nullable
>
> - The reason of adding such indirection is to enable sharing definition
> - between a reference kind and its nullable variation. For example:
> - a = Struct('test_struct_1')
> - b = a.MakeNullableKind()
> - a.name = 'test_struct_2'
> - print(b.name) # Outputs 'test_struct_2'.
> - """
> + if self == NULLABLE_STRING:
> + return STRING
> + if self == NULLABLE_HANDLE:
> + return HANDLE
> + if self == NULLABLE_DCPIPE:
> + return DCPIPE
> + if self == NULLABLE_DPPIPE:
> + return DPPIPE
> + if self == NULLABLE_MSGPIPE:
> + return MSGPIPE
> + if self == NULLABLE_SHAREDBUFFER:
> + return SHAREDBUFFER
> + if self == NULLABLE_PLATFORMHANDLE:
> + return PLATFORMHANDLE
>
> - def Get(self):
> - try:
> - return self.shared_definition[name]
> - except KeyError: # Must raise AttributeError if property doesn't exist.
> - raise AttributeError
> + unnullable_kind = type(self)()
> + unnullable_kind.shared_definition = self.shared_definition
> + if self.spec is not None:
> + assert self.spec[0] == '?'
> + unnullable_kind.spec = self.spec[1:]
> + unnullable_kind.is_nullable = False
> + unnullable_kind.parent_kind = self.parent_kind
> + unnullable_kind.module = self.module
>
> - def Set(self, value):
> - self.shared_definition[name] = value
> -
> - setattr(cls, name, property(Get, Set))
> + return unnullable_kind
>
> def __eq__(self, rhs):
> - return (isinstance(rhs, ReferenceKind)
> - and super(ReferenceKind, self).__eq__(rhs)
> - and self.is_nullable == rhs.is_nullable)
> + return (isinstance(rhs, ReferenceKind) and super().__eq__(rhs))
>
> - def __hash__(self):
> - return hash((super(ReferenceKind, self).__hash__(), self.is_nullable))
> -
> - def IsBackwardCompatible(self, rhs, checker):
> - return (super(ReferenceKind, self).IsBackwardCompatible(rhs, checker)
> - and self.is_nullable == rhs.is_nullable)
> + def __hash__(self): # pylint: disable=useless-super-delegation
> + return super().__hash__()
>
>
> # Initialize the set of primitive types. These can be accessed by clients.
> -BOOL = Kind('b')
> -INT8 = Kind('i8')
> -INT16 = Kind('i16')
> -INT32 = Kind('i32')
> -INT64 = Kind('i64')
> -UINT8 = Kind('u8')
> -UINT16 = Kind('u16')
> -UINT32 = Kind('u32')
> -UINT64 = Kind('u64')
> -FLOAT = Kind('f')
> -DOUBLE = Kind('d')
> +BOOL = ValueKind('b')
> +INT8 = ValueKind('i8')
> +INT16 = ValueKind('i16')
> +INT32 = ValueKind('i32')
> +INT64 = ValueKind('i64')
> +UINT8 = ValueKind('u8')
> +UINT16 = ValueKind('u16')
> +UINT32 = ValueKind('u32')
> +UINT64 = ValueKind('u64')
> +FLOAT = ValueKind('f')
> +DOUBLE = ValueKind('d')
> +NULLABLE_BOOL = ValueKind('?b', True)
> +NULLABLE_INT8 = ValueKind('?i8', True)
> +NULLABLE_INT16 = ValueKind('?i16', True)
> +NULLABLE_INT32 = ValueKind('?i32', True)
> +NULLABLE_INT64 = ValueKind('?i64', True)
> +NULLABLE_UINT8 = ValueKind('?u8', True)
> +NULLABLE_UINT16 = ValueKind('?u16', True)
> +NULLABLE_UINT32 = ValueKind('?u32', True)
> +NULLABLE_UINT64 = ValueKind('?u64', True)
> +NULLABLE_FLOAT = ValueKind('?f', True)
> +NULLABLE_DOUBLE = ValueKind('?d', True)
> STRING = ReferenceKind('s')
> HANDLE = ReferenceKind('h')
> DCPIPE = ReferenceKind('h:d:c')
> @@ -270,6 +385,17 @@ PRIMITIVES = (
> UINT64,
> FLOAT,
> DOUBLE,
> + NULLABLE_BOOL,
> + NULLABLE_INT8,
> + NULLABLE_INT16,
> + NULLABLE_INT32,
> + NULLABLE_INT64,
> + NULLABLE_UINT8,
> + NULLABLE_UINT16,
> + NULLABLE_UINT32,
> + NULLABLE_UINT64,
> + NULLABLE_FLOAT,
> + NULLABLE_DOUBLE,
> STRING,
> HANDLE,
> DCPIPE,
> @@ -291,12 +417,17 @@ ATTRIBUTE_DEFAULT = 'Default'
> ATTRIBUTE_EXTENSIBLE = 'Extensible'
> ATTRIBUTE_NO_INTERRUPT = 'NoInterrupt'
> ATTRIBUTE_STABLE = 'Stable'
> +ATTRIBUTE_SUPPORTS_URGENT = 'SupportsUrgent'
> ATTRIBUTE_SYNC = 'Sync'
> ATTRIBUTE_UNLIMITED_SIZE = 'UnlimitedSize'
> ATTRIBUTE_UUID = 'Uuid'
> +ATTRIBUTE_SERVICE_SANDBOX = 'ServiceSandbox'
> +ATTRIBUTE_REQUIRE_CONTEXT = 'RequireContext'
> +ATTRIBUTE_ALLOWED_CONTEXT = 'AllowedContext'
> +ATTRIBUTE_RUNTIME_FEATURE = 'RuntimeFeature'
>
>
> -class NamedValue(object):
> +class NamedValue:
> def __init__(self, module, parent_kind, mojom_name):
> self.module = module
> self.parent_kind = parent_kind
> @@ -316,7 +447,7 @@ class NamedValue(object):
> return hash((self.parent_kind, self.mojom_name))
>
>
> -class BuiltinValue(object):
> +class BuiltinValue:
> def __init__(self, value):
> self.value = value
>
> @@ -350,7 +481,7 @@ class EnumValue(NamedValue):
> return self.field.name
>
>
> -class Constant(object):
> +class Constant:
> def __init__(self, mojom_name=None, kind=None, value=None, parent_kind=None):
> self.mojom_name = mojom_name
> self.name = None
> @@ -368,7 +499,7 @@ class Constant(object):
> rhs.parent_kind))
>
>
> -class Field(object):
> +class Field:
> def __init__(self,
> mojom_name=None,
> kind=None,
> @@ -414,7 +545,18 @@ class StructField(Field):
>
>
> class UnionField(Field):
> - pass
> + def __init__(self,
> + mojom_name=None,
> + kind=None,
> + ordinal=None,
> + default=None,
> + attributes=None):
> + Field.__init__(self, mojom_name, kind, ordinal, default, attributes)
> +
> + @property
> + def is_default(self):
> + return self.attributes.get(ATTRIBUTE_DEFAULT, False) \
> + if self.attributes else False
>
>
> def _IsFieldBackwardCompatible(new_field, old_field, checker):
> @@ -424,6 +566,38 @@ def _IsFieldBackwardCompatible(new_field, old_field, checker):
> return checker.IsBackwardCompatible(new_field.kind, old_field.kind)
>
>
> +class Feature(ReferenceKind):
> + """A runtime enabled feature defined from mojom.
> +
> + Attributes:
> + mojom_name: {str} The name of the feature type as defined in mojom.
> + name: {str} The stylized name. (Note: not the "name" used by FeatureList.)
> + constants: {List[Constant]} The constants defined in the feature scope.
> + attributes: {dict} Additional information about the feature.
> + """
> +
> + Kind.AddSharedProperty('mojom_name')
> + Kind.AddSharedProperty('name')
> + Kind.AddSharedProperty('constants')
> + Kind.AddSharedProperty('attributes')
> +
> + def __init__(self, mojom_name=None, module=None, attributes=None):
> + if mojom_name is not None:
> + spec = 'x:' + mojom_name
> + else:
> + spec = None
> + ReferenceKind.__init__(self, spec, False, module)
> + self.mojom_name = mojom_name
> + self.name = None
> + self.constants = []
> + self.attributes = attributes
> +
> + def Stylize(self, stylizer):
> + self.name = stylizer.StylizeFeature(self.mojom_name)
> + for constant in self.constants:
> + constant.Stylize(stylizer)
> +
> +
> class Struct(ReferenceKind):
> """A struct with typed fields.
>
> @@ -441,14 +615,14 @@ class Struct(ReferenceKind):
> if it's a native struct.
> """
>
> - ReferenceKind.AddSharedProperty('mojom_name')
> - ReferenceKind.AddSharedProperty('name')
> - ReferenceKind.AddSharedProperty('native_only')
> - ReferenceKind.AddSharedProperty('custom_serializer')
> - ReferenceKind.AddSharedProperty('fields')
> - ReferenceKind.AddSharedProperty('enums')
> - ReferenceKind.AddSharedProperty('constants')
> - ReferenceKind.AddSharedProperty('attributes')
> + Kind.AddSharedProperty('mojom_name')
> + Kind.AddSharedProperty('name')
> + Kind.AddSharedProperty('native_only')
> + Kind.AddSharedProperty('custom_serializer')
> + Kind.AddSharedProperty('fields')
> + Kind.AddSharedProperty('enums')
> + Kind.AddSharedProperty('constants')
> + Kind.AddSharedProperty('attributes')
>
> def __init__(self, mojom_name=None, module=None, attributes=None):
> if mojom_name is not None:
> @@ -470,12 +644,11 @@ class Struct(ReferenceKind):
> return '<%s mojom_name=%r module=%s>' % (self.__class__.__name__,
> self.mojom_name,
> Repr(self.module, as_ref=True))
> - else:
> - return GenericRepr(self, {
> - 'mojom_name': False,
> - 'fields': False,
> - 'module': True
> - })
> + return GenericRepr(self, {
> + 'mojom_name': False,
> + 'fields': False,
> + 'module': True
> + })
>
> def AddField(self,
> mojom_name,
> @@ -496,13 +669,13 @@ class Struct(ReferenceKind):
> for constant in self.constants:
> constant.Stylize(stylizer)
>
> - def IsBackwardCompatible(self, older_struct, checker):
> - """This struct is backward-compatible with older_struct if and only if all
> - of the following conditions hold:
> + def IsBackwardCompatible(self, rhs, checker):
> + """This struct is backward-compatible with rhs (older_struct) if and only if
> + all of the following conditions hold:
> - Any newly added field is tagged with a [MinVersion] attribute specifying
> a version number greater than all previously used [MinVersion]
> attributes within the struct.
> - - All fields present in older_struct remain present in the new struct,
> + - All fields present in rhs remain present in the new struct,
> with the same ordinal position, same optional or non-optional status,
> same (or backward-compatible) type and where applicable, the same
> [MinVersion] attribute value.
> @@ -521,7 +694,7 @@ class Struct(ReferenceKind):
> return fields_by_ordinal
>
> new_fields = buildOrdinalFieldMap(self)
> - old_fields = buildOrdinalFieldMap(older_struct)
> + old_fields = buildOrdinalFieldMap(rhs)
> if len(new_fields) < len(old_fields):
> # At least one field was removed, which is not OK.
> return False
> @@ -574,11 +747,18 @@ class Struct(ReferenceKind):
> prefix = self.module.GetNamespacePrefix()
> return '%s%s' % (prefix, self.mojom_name)
>
> + def _tuple(self):
> + return (self.mojom_name, self.native_only, self.fields, self.constants,
> + self.attributes)
> +
> def __eq__(self, rhs):
> - return (isinstance(rhs, Struct) and
> - (self.mojom_name, self.native_only, self.fields, self.constants,
> - self.attributes) == (rhs.mojom_name, rhs.native_only, rhs.fields,
> - rhs.constants, rhs.attributes))
> + return isinstance(rhs, Struct) and self._tuple() == rhs._tuple()
> +
> + def __lt__(self, rhs):
> + if not isinstance(self, type(rhs)):
> + return str(type(self)) < str(type(rhs))
> +
> + return self._tuple() < rhs._tuple()
>
> def __hash__(self):
> return id(self)
> @@ -595,10 +775,11 @@ class Union(ReferenceKind):
> which Java class name to use to represent it in the generated
> bindings.
> """
> - ReferenceKind.AddSharedProperty('mojom_name')
> - ReferenceKind.AddSharedProperty('name')
> - ReferenceKind.AddSharedProperty('fields')
> - ReferenceKind.AddSharedProperty('attributes')
> + Kind.AddSharedProperty('mojom_name')
> + Kind.AddSharedProperty('name')
> + Kind.AddSharedProperty('fields')
> + Kind.AddSharedProperty('attributes')
> + Kind.AddSharedProperty('default_field')
>
> def __init__(self, mojom_name=None, module=None, attributes=None):
> if mojom_name is not None:
> @@ -610,14 +791,14 @@ class Union(ReferenceKind):
> self.name = None
> self.fields = []
> self.attributes = attributes
> + self.default_field = None
>
> def Repr(self, as_ref=True):
> if as_ref:
> return '<%s spec=%r is_nullable=%r fields=%s>' % (
> self.__class__.__name__, self.spec, self.is_nullable, Repr(
> self.fields))
> - else:
> - return GenericRepr(self, {'fields': True, 'is_nullable': False})
> + return GenericRepr(self, {'fields': True, 'is_nullable': False})
>
> def AddField(self, mojom_name, kind, ordinal=None, attributes=None):
> field = UnionField(mojom_name, kind, ordinal, None, attributes)
> @@ -629,13 +810,13 @@ class Union(ReferenceKind):
> for field in self.fields:
> field.Stylize(stylizer)
>
> - def IsBackwardCompatible(self, older_union, checker):
> - """This union is backward-compatible with older_union if and only if all
> - of the following conditions hold:
> + def IsBackwardCompatible(self, rhs, checker):
> + """This union is backward-compatible with rhs (older_union) if and only if
> + all of the following conditions hold:
> - Any newly added field is tagged with a [MinVersion] attribute specifying
> a version number greater than all previously used [MinVersion]
> attributes within the union.
> - - All fields present in older_union remain present in the new union,
> + - All fields present in rhs remain present in the new union,
> with the same ordinal value, same optional or non-optional status,
> same (or backward-compatible) type, and where applicable, the same
> [MinVersion] attribute value.
> @@ -651,7 +832,7 @@ class Union(ReferenceKind):
> return fields_by_ordinal
>
> new_fields = buildOrdinalFieldMap(self)
> - old_fields = buildOrdinalFieldMap(older_union)
> + old_fields = buildOrdinalFieldMap(rhs)
> if len(new_fields) < len(old_fields):
> # At least one field was removed, which is not OK.
> return False
> @@ -677,6 +858,11 @@ class Union(ReferenceKind):
>
> return True
>
> + @property
> + def extensible(self):
> + return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \
> + if self.attributes else False
> +
> @property
> def stable(self):
> return self.attributes.get(ATTRIBUTE_STABLE, False) \
> @@ -690,10 +876,17 @@ class Union(ReferenceKind):
> prefix = self.module.GetNamespacePrefix()
> return '%s%s' % (prefix, self.mojom_name)
>
> + def _tuple(self):
> + return (self.mojom_name, self.fields, self.attributes)
> +
> def __eq__(self, rhs):
> - return (isinstance(rhs, Union) and
> - (self.mojom_name, self.fields,
> - self.attributes) == (rhs.mojom_name, rhs.fields, rhs.attributes))
> + return isinstance(rhs, Union) and self._tuple() == rhs._tuple()
> +
> + def __lt__(self, rhs):
> + if not isinstance(self, type(rhs)):
> + return str(type(self)) < str(type(rhs))
> +
> + return self._tuple() < rhs._tuple()
>
> def __hash__(self):
> return id(self)
> @@ -707,8 +900,8 @@ class Array(ReferenceKind):
> length: The number of elements. None if unknown.
> """
>
> - ReferenceKind.AddSharedProperty('kind')
> - ReferenceKind.AddSharedProperty('length')
> + Kind.AddSharedProperty('kind')
> + Kind.AddSharedProperty('length')
>
> def __init__(self, kind=None, length=None):
> if kind is not None:
> @@ -728,12 +921,11 @@ class Array(ReferenceKind):
> return '<%s spec=%r is_nullable=%r kind=%s length=%r>' % (
> self.__class__.__name__, self.spec, self.is_nullable, Repr(
> self.kind), self.length)
> - else:
> - return GenericRepr(self, {
> - 'kind': True,
> - 'length': False,
> - 'is_nullable': False
> - })
> + return GenericRepr(self, {
> + 'kind': True,
> + 'length': False,
> + 'is_nullable': False
> + })
>
> def __eq__(self, rhs):
> return (isinstance(rhs, Array)
> @@ -754,8 +946,8 @@ class Map(ReferenceKind):
> key_kind: {Kind} The type of the keys. May be None.
> value_kind: {Kind} The type of the elements. May be None.
> """
> - ReferenceKind.AddSharedProperty('key_kind')
> - ReferenceKind.AddSharedProperty('value_kind')
> + Kind.AddSharedProperty('key_kind')
> + Kind.AddSharedProperty('value_kind')
>
> def __init__(self, key_kind=None, value_kind=None):
> if (key_kind is not None and value_kind is not None):
> @@ -780,8 +972,7 @@ class Map(ReferenceKind):
> return '<%s spec=%r is_nullable=%r key_kind=%s value_kind=%s>' % (
> self.__class__.__name__, self.spec, self.is_nullable,
> Repr(self.key_kind), Repr(self.value_kind))
> - else:
> - return GenericRepr(self, {'key_kind': True, 'value_kind': True})
> + return GenericRepr(self, {'key_kind': True, 'value_kind': True})
>
> def __eq__(self, rhs):
> return (isinstance(rhs, Map) and
> @@ -797,7 +988,7 @@ class Map(ReferenceKind):
>
>
> class PendingRemote(ReferenceKind):
> - ReferenceKind.AddSharedProperty('kind')
> + Kind.AddSharedProperty('kind')
>
> def __init__(self, kind=None):
> if kind is not None:
> @@ -822,7 +1013,7 @@ class PendingRemote(ReferenceKind):
>
>
> class PendingReceiver(ReferenceKind):
> - ReferenceKind.AddSharedProperty('kind')
> + Kind.AddSharedProperty('kind')
>
> def __init__(self, kind=None):
> if kind is not None:
> @@ -847,7 +1038,7 @@ class PendingReceiver(ReferenceKind):
>
>
> class PendingAssociatedRemote(ReferenceKind):
> - ReferenceKind.AddSharedProperty('kind')
> + Kind.AddSharedProperty('kind')
>
> def __init__(self, kind=None):
> if kind is not None:
> @@ -873,7 +1064,7 @@ class PendingAssociatedRemote(ReferenceKind):
>
>
> class PendingAssociatedReceiver(ReferenceKind):
> - ReferenceKind.AddSharedProperty('kind')
> + Kind.AddSharedProperty('kind')
>
> def __init__(self, kind=None):
> if kind is not None:
> @@ -899,7 +1090,7 @@ class PendingAssociatedReceiver(ReferenceKind):
>
>
> class InterfaceRequest(ReferenceKind):
> - ReferenceKind.AddSharedProperty('kind')
> + Kind.AddSharedProperty('kind')
>
> def __init__(self, kind=None):
> if kind is not None:
> @@ -923,7 +1114,7 @@ class InterfaceRequest(ReferenceKind):
>
>
> class AssociatedInterfaceRequest(ReferenceKind):
> - ReferenceKind.AddSharedProperty('kind')
> + Kind.AddSharedProperty('kind')
>
> def __init__(self, kind=None):
> if kind is not None:
> @@ -949,7 +1140,7 @@ class AssociatedInterfaceRequest(ReferenceKind):
> self.kind, rhs.kind)
>
>
> -class Parameter(object):
> +class Parameter:
> def __init__(self,
> mojom_name=None,
> kind=None,
> @@ -983,7 +1174,7 @@ class Parameter(object):
> rhs.default, rhs.attributes))
>
>
> -class Method(object):
> +class Method:
> def __init__(self, interface, mojom_name, ordinal=None, attributes=None):
> self.interface = interface
> self.mojom_name = mojom_name
> @@ -999,12 +1190,11 @@ class Method(object):
> def Repr(self, as_ref=True):
> if as_ref:
> return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)
> - else:
> - return GenericRepr(self, {
> - 'mojom_name': False,
> - 'parameters': True,
> - 'response_parameters': True
> - })
> + return GenericRepr(self, {
> + 'mojom_name': False,
> + 'parameters': True,
> + 'response_parameters': True
> + })
>
> def AddParameter(self,
> mojom_name,
> @@ -1061,21 +1251,49 @@ class Method(object):
> return self.attributes.get(ATTRIBUTE_UNLIMITED_SIZE) \
> if self.attributes else False
>
> + @property
> + def allowed_context(self):
> + return self.attributes.get(ATTRIBUTE_ALLOWED_CONTEXT) \
> + if self.attributes else None
> +
> + @property
> + def supports_urgent(self):
> + return self.attributes.get(ATTRIBUTE_SUPPORTS_URGENT) \
> + if self.attributes else None
> +
> + @property
> + def runtime_feature(self):
> + if not self.attributes:
> + return None
> + runtime_feature = self.attributes.get(ATTRIBUTE_RUNTIME_FEATURE, None)
> + if runtime_feature is None:
> + return None
> + if not isinstance(runtime_feature, Feature):
> + raise Exception("RuntimeFeature attribute on %s must be a feature." %
> + self.name)
> + return runtime_feature
> +
> + def _tuple(self):
> + return (self.mojom_name, self.ordinal, self.parameters,
> + self.response_parameters, self.attributes)
> +
> def __eq__(self, rhs):
> - return (isinstance(rhs, Method) and
> - (self.mojom_name, self.ordinal, self.parameters,
> - self.response_parameters,
> - self.attributes) == (rhs.mojom_name, rhs.ordinal, rhs.parameters,
> - rhs.response_parameters, rhs.attributes))
> + return isinstance(rhs, Method) and self._tuple() == rhs._tuple()
> +
> + def __lt__(self, rhs):
> + if not isinstance(self, type(rhs)):
> + return str(type(self)) < str(type(rhs))
> +
> + return self._tuple() < rhs._tuple()
>
>
> class Interface(ReferenceKind):
> - ReferenceKind.AddSharedProperty('mojom_name')
> - ReferenceKind.AddSharedProperty('name')
> - ReferenceKind.AddSharedProperty('methods')
> - ReferenceKind.AddSharedProperty('enums')
> - ReferenceKind.AddSharedProperty('constants')
> - ReferenceKind.AddSharedProperty('attributes')
> + Kind.AddSharedProperty('mojom_name')
> + Kind.AddSharedProperty('name')
> + Kind.AddSharedProperty('methods')
> + Kind.AddSharedProperty('enums')
> + Kind.AddSharedProperty('constants')
> + Kind.AddSharedProperty('attributes')
>
> def __init__(self, mojom_name=None, module=None, attributes=None):
> if mojom_name is not None:
> @@ -1093,12 +1311,11 @@ class Interface(ReferenceKind):
> def Repr(self, as_ref=True):
> if as_ref:
> return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)
> - else:
> - return GenericRepr(self, {
> - 'mojom_name': False,
> - 'attributes': False,
> - 'methods': False
> - })
> + return GenericRepr(self, {
> + 'mojom_name': False,
> + 'attributes': False,
> + 'methods': False
> + })
>
> def AddMethod(self, mojom_name, ordinal=None, attributes=None):
> method = Method(self, mojom_name, ordinal, attributes)
> @@ -1114,10 +1331,10 @@ class Interface(ReferenceKind):
> for constant in self.constants:
> constant.Stylize(stylizer)
>
> - def IsBackwardCompatible(self, older_interface, checker):
> - """This interface is backward-compatible with older_interface if and only
> - if all of the following conditions hold:
> - - All defined methods in older_interface (when identified by ordinal) have
> + def IsBackwardCompatible(self, rhs, checker):
> + """This interface is backward-compatible with rhs (older_interface) if and
> + only if all of the following conditions hold:
> + - All defined methods in rhs (when identified by ordinal) have
> backward-compatible definitions in this interface. For each method this
> means:
> - The parameter list is backward-compatible, according to backward-
> @@ -1131,7 +1348,7 @@ class Interface(ReferenceKind):
> rules for structs.
> - All newly introduced methods in this interface have a [MinVersion]
> attribute specifying a version greater than any method in
> - older_interface.
> + rhs.
> """
>
> def buildOrdinalMethodMap(interface):
> @@ -1144,7 +1361,7 @@ class Interface(ReferenceKind):
> return methods_by_ordinal
>
> new_methods = buildOrdinalMethodMap(self)
> - old_methods = buildOrdinalMethodMap(older_interface)
> + old_methods = buildOrdinalMethodMap(rhs)
> max_old_min_version = 0
> for ordinal, old_method in old_methods.items():
> new_method = new_methods.get(ordinal)
> @@ -1186,6 +1403,39 @@ class Interface(ReferenceKind):
>
> return True
>
> + @property
> + def service_sandbox(self):
> + if not self.attributes:
> + return None
> + service_sandbox = self.attributes.get(ATTRIBUTE_SERVICE_SANDBOX, None)
> + if service_sandbox is None:
> + return None
> + # Constants are only allowed to refer to an enum here, so replace.
> + if isinstance(service_sandbox, Constant):
> + service_sandbox = service_sandbox.value
> + if not isinstance(service_sandbox, EnumValue):
> + raise Exception("ServiceSandbox attribute on %s must be an enum value." %
> + self.module.name)
> + return service_sandbox
> +
> + @property
> + def runtime_feature(self):
> + if not self.attributes:
> + return None
> + runtime_feature = self.attributes.get(ATTRIBUTE_RUNTIME_FEATURE, None)
> + if runtime_feature is None:
> + return None
> + if not isinstance(runtime_feature, Feature):
> + raise Exception("RuntimeFeature attribute on %s must be a feature." %
> + self.name)
> + return runtime_feature
> +
> + @property
> + def require_context(self):
> + if not self.attributes:
> + return None
> + return self.attributes.get(ATTRIBUTE_REQUIRE_CONTEXT, None)
> +
> @property
> def stable(self):
> return self.attributes.get(ATTRIBUTE_STABLE, False) \
> @@ -1199,11 +1449,18 @@ class Interface(ReferenceKind):
> prefix = self.module.GetNamespacePrefix()
> return '%s%s' % (prefix, self.mojom_name)
>
> + def _tuple(self):
> + return (self.mojom_name, self.methods, self.enums, self.constants,
> + self.attributes)
> +
> def __eq__(self, rhs):
> - return (isinstance(rhs, Interface)
> - and (self.mojom_name, self.methods, self.enums, self.constants,
> - self.attributes) == (rhs.mojom_name, rhs.methods, rhs.enums,
> - rhs.constants, rhs.attributes))
> + return isinstance(rhs, Interface) and self._tuple() == rhs._tuple()
> +
> + def __lt__(self, rhs):
> + if not isinstance(self, type(rhs)):
> + return str(type(self)) < str(type(rhs))
> +
> + return self._tuple() < rhs._tuple()
>
> @property
> def uuid(self):
> @@ -1224,7 +1481,7 @@ class Interface(ReferenceKind):
>
>
> class AssociatedInterface(ReferenceKind):
> - ReferenceKind.AddSharedProperty('kind')
> + Kind.AddSharedProperty('kind')
>
> def __init__(self, kind=None):
> if kind is not None:
> @@ -1249,7 +1506,7 @@ class AssociatedInterface(ReferenceKind):
> self.kind, rhs.kind)
>
>
> -class EnumField(object):
> +class EnumField:
> def __init__(self,
> mojom_name=None,
> value=None,
> @@ -1281,16 +1538,25 @@ class EnumField(object):
> rhs.attributes, rhs.numeric_value))
>
>
> -class Enum(Kind):
> +class Enum(ValueKind):
> + Kind.AddSharedProperty('mojom_name')
> + Kind.AddSharedProperty('name')
> + Kind.AddSharedProperty('native_only')
> + Kind.AddSharedProperty('fields')
> + Kind.AddSharedProperty('attributes')
> + Kind.AddSharedProperty('min_value')
> + Kind.AddSharedProperty('max_value')
> + Kind.AddSharedProperty('default_field')
> +
> def __init__(self, mojom_name=None, module=None, attributes=None):
> - self.mojom_name = mojom_name
> - self.name = None
> - self.native_only = False
> if mojom_name is not None:
> spec = 'x:' + mojom_name
> else:
> spec = None
> - Kind.__init__(self, spec, module)
> + ValueKind.__init__(self, spec, False, module)
> + self.mojom_name = mojom_name
> + self.name = None
> + self.native_only = False
> self.fields = []
> self.attributes = attributes
> self.min_value = None
> @@ -1300,8 +1566,7 @@ class Enum(Kind):
> def Repr(self, as_ref=True):
> if as_ref:
> return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)
> - else:
> - return GenericRepr(self, {'mojom_name': False, 'fields': False})
> + return GenericRepr(self, {'mojom_name': False, 'fields': False})
>
> def Stylize(self, stylizer):
> self.name = stylizer.StylizeEnum(self.mojom_name)
> @@ -1327,14 +1592,14 @@ class Enum(Kind):
> return '%s%s' % (prefix, self.mojom_name)
>
> # pylint: disable=unused-argument
> - def IsBackwardCompatible(self, older_enum, checker):
> - """This enum is backward-compatible with older_enum if and only if one of
> - the following conditions holds:
> + def IsBackwardCompatible(self, rhs, checker):
> + """This enum is backward-compatible with rhs (older_enum) if and only if one
> + of the following conditions holds:
> - Neither enum is [Extensible] and both have the exact same set of valid
> numeric values. Field names and aliases for the same numeric value do
> not affect compatibility.
> - - older_enum is [Extensible], and for every version defined by
> - older_enum, this enum has the exact same set of valid numeric values.
> + - rhs is [Extensible], and for every version defined by
> + rhs, this enum has the exact same set of valid numeric values.
> """
>
> def buildVersionFieldMap(enum):
> @@ -1345,32 +1610,49 @@ class Enum(Kind):
> fields_by_min_version[field.min_version].add(field.numeric_value)
> return fields_by_min_version
>
> - old_fields = buildVersionFieldMap(older_enum)
> + old_fields = buildVersionFieldMap(rhs)
> new_fields = buildVersionFieldMap(self)
>
> - if new_fields.keys() != old_fields.keys() and not older_enum.extensible:
> - return False
> + if new_fields.keys() != old_fields.keys() and not rhs.extensible:
> + raise Exception("Non-extensible enum cannot be modified")
>
> for min_version, valid_values in old_fields.items():
> - if (min_version not in new_fields
> - or new_fields[min_version] != valid_values):
> - return False
> + if min_version not in new_fields:
> + raise Exception('New values added to an extensible enum '
> + 'do not specify MinVersion: %s' % new_fields)
>
> + if (new_fields[min_version] != valid_values):
> + if (len(new_fields[min_version]) < len(valid_values)):
> + raise Exception('Removing values for an existing MinVersion %s '
> + 'is not allowed' % min_version)
> +
> + raise Exception(
> + 'New values don\'t match old values'
> + 'for an existing MinVersion %s,'
> + ' please specify MinVersion equal to "Next version" '
> + 'in the enum description'
> + ' for the following values:\n%s' %
> + (min_version, new_fields[min_version].difference(valid_values)))
> return True
>
> + def _tuple(self):
> + return (self.mojom_name, self.native_only, self.fields, self.attributes,
> + self.min_value, self.max_value, self.default_field)
> +
> def __eq__(self, rhs):
> - return (isinstance(rhs, Enum) and
> - (self.mojom_name, self.native_only, self.fields, self.attributes,
> - self.min_value, self.max_value,
> - self.default_field) == (rhs.mojom_name, rhs.native_only,
> - rhs.fields, rhs.attributes, rhs.min_value,
> - rhs.max_value, rhs.default_field))
> + return isinstance(rhs, Enum) and self._tuple() == rhs._tuple()
> +
> + def __lt__(self, rhs):
> + if not isinstance(self, type(rhs)):
> + return str(type(self)) < str(type(rhs))
> +
> + return self._tuple() < rhs._tuple()
>
> def __hash__(self):
> return id(self)
>
>
> -class Module(object):
> +class Module:
> def __init__(self, path=None, mojom_namespace=None, attributes=None):
> self.path = path
> self.mojom_namespace = mojom_namespace
> @@ -1379,24 +1661,26 @@ class Module(object):
> self.unions = []
> self.interfaces = []
> self.enums = []
> + self.features = []
> self.constants = []
> - self.kinds = {}
> + self.kinds = OrderedDict()
> self.attributes = attributes
> self.imports = []
> - self.imported_kinds = {}
> - self.metadata = {}
> + self.imported_kinds = OrderedDict()
> + self.metadata = OrderedDict()
>
> def __repr__(self):
> # Gives us a decent __repr__ for modules.
> return self.Repr()
>
> def __eq__(self, rhs):
> - return (isinstance(rhs, Module) and
> - (self.path, self.attributes, self.mojom_namespace, self.imports,
> - self.constants, self.enums, self.structs, self.unions,
> - self.interfaces) == (rhs.path, rhs.attributes, rhs.mojom_namespace,
> - rhs.imports, rhs.constants, rhs.enums,
> - rhs.structs, rhs.unions, rhs.interfaces))
> + return (isinstance(rhs, Module)
> + and (self.path, self.attributes, self.mojom_namespace, self.imports,
> + self.constants, self.enums, self.structs, self.unions,
> + self.interfaces, self.features)
> + == (rhs.path, rhs.attributes, rhs.mojom_namespace, rhs.imports,
> + rhs.constants, rhs.enums, rhs.structs, rhs.unions,
> + rhs.interfaces, rhs.features))
>
> def __hash__(self):
> return id(self)
> @@ -1405,16 +1689,16 @@ class Module(object):
> if as_ref:
> return '<%s path=%r mojom_namespace=%r>' % (
> self.__class__.__name__, self.path, self.mojom_namespace)
> - else:
> - return GenericRepr(
> - self, {
> - 'path': False,
> - 'mojom_namespace': False,
> - 'attributes': False,
> - 'structs': False,
> - 'interfaces': False,
> - 'unions': False
> - })
> + return GenericRepr(
> + self, {
> + 'path': False,
> + 'mojom_namespace': False,
> + 'attributes': False,
> + 'structs': False,
> + 'interfaces': False,
> + 'unions': False,
> + 'features': False,
> + })
>
> def GetNamespacePrefix(self):
> return '%s.' % self.mojom_namespace if self.mojom_namespace else ''
> @@ -1434,6 +1718,11 @@ class Module(object):
> self.unions.append(union)
> return union
>
> + def AddFeature(self, mojom_name, attributes=None):
> + feature = Feature(mojom_name, self, attributes)
> + self.features.append(feature)
> + return feature
> +
> def Stylize(self, stylizer):
> self.namespace = stylizer.StylizeModule(self.mojom_namespace)
> for struct in self.structs:
> @@ -1446,12 +1735,14 @@ class Module(object):
> enum.Stylize(stylizer)
> for constant in self.constants:
> constant.Stylize(stylizer)
> + for feature in self.features:
> + feature.Stylize(stylizer)
>
> for imported_module in self.imports:
> imported_module.Stylize(stylizer)
>
> def Dump(self, f):
> - pickle.dump(self, f, 2)
> + pickle.dump(self, f)
>
> @classmethod
> def Load(cls, f):
> @@ -1461,15 +1752,15 @@ class Module(object):
>
>
> def IsBoolKind(kind):
> - return kind.spec == BOOL.spec
> + return kind.spec == BOOL.spec or kind.spec == NULLABLE_BOOL.spec
>
>
> def IsFloatKind(kind):
> - return kind.spec == FLOAT.spec
> + return kind.spec == FLOAT.spec or kind.spec == NULLABLE_FLOAT.spec
>
>
> def IsDoubleKind(kind):
> - return kind.spec == DOUBLE.spec
> + return kind.spec == DOUBLE.spec or kind.spec == NULLABLE_DOUBLE.spec
>
>
> def IsIntegralKind(kind):
> @@ -1477,7 +1768,14 @@ def IsIntegralKind(kind):
> or kind.spec == INT16.spec or kind.spec == INT32.spec
> or kind.spec == INT64.spec or kind.spec == UINT8.spec
> or kind.spec == UINT16.spec or kind.spec == UINT32.spec
> - or kind.spec == UINT64.spec)
> + or kind.spec == UINT64.spec or kind.spec == NULLABLE_BOOL.spec
> + or kind.spec == NULLABLE_INT8.spec or kind.spec == NULLABLE_INT16.spec
> + or kind.spec == NULLABLE_INT32.spec
> + or kind.spec == NULLABLE_INT64.spec
> + or kind.spec == NULLABLE_UINT8.spec
> + or kind.spec == NULLABLE_UINT16.spec
> + or kind.spec == NULLABLE_UINT32.spec
> + or kind.spec == NULLABLE_UINT64.spec)
>
>
> def IsStringKind(kind):
> @@ -1522,6 +1820,10 @@ def IsArrayKind(kind):
> return isinstance(kind, Array)
>
>
> +def IsFeatureKind(kind):
> + return isinstance(kind, Feature)
> +
> +
> def IsInterfaceKind(kind):
> return isinstance(kind, Interface)
>
> @@ -1558,12 +1860,16 @@ def IsEnumKind(kind):
> return isinstance(kind, Enum)
>
>
> +def IsValueKind(kind):
> + return isinstance(kind, ValueKind)
> +
> +
> def IsReferenceKind(kind):
> return isinstance(kind, ReferenceKind)
>
>
> def IsNullableKind(kind):
> - return IsReferenceKind(kind) and kind.is_nullable
> + return kind.is_nullable
>
>
> def IsMapKind(kind):
> @@ -1664,11 +1970,8 @@ def MethodPassesInterfaces(method):
> return _AnyMethodParameterRecursive(method, IsInterfaceKind)
>
>
> -def HasSyncMethods(interface):
> - for method in interface.methods:
> - if method.sync:
> - return True
> - return False
> +def GetSyncMethodOrdinals(interface):
> + return [method.ordinal for method in interface.methods if method.sync]
>
>
> def HasUninterruptableMethods(interface):
> @@ -1700,18 +2003,17 @@ def ContainsHandlesOrInterfaces(kind):
> checked.add(kind.spec)
> if IsStructKind(kind):
> return any(Check(field.kind) for field in kind.fields)
> - elif IsUnionKind(kind):
> + if IsUnionKind(kind):
> return any(Check(field.kind) for field in kind.fields)
> - elif IsAnyHandleKind(kind):
> + if IsAnyHandleKind(kind):
> return True
> - elif IsAnyInterfaceKind(kind):
> + if IsAnyInterfaceKind(kind):
> return True
> - elif IsArrayKind(kind):
> + if IsArrayKind(kind):
> return Check(kind.kind)
> - elif IsMapKind(kind):
> + if IsMapKind(kind):
> return Check(kind.key_kind) or Check(kind.value_kind)
> - else:
> - return False
> + return False
>
> return Check(kind)
>
> @@ -1738,21 +2040,20 @@ def ContainsNativeTypes(kind):
> checked.add(kind.spec)
> if IsEnumKind(kind):
> return kind.native_only
> - elif IsStructKind(kind):
> + if IsStructKind(kind):
> if kind.native_only:
> return True
> if any(enum.native_only for enum in kind.enums):
> return True
> return any(Check(field.kind) for field in kind.fields)
> - elif IsUnionKind(kind):
> + if IsUnionKind(kind):
> return any(Check(field.kind) for field in kind.fields)
> - elif IsInterfaceKind(kind):
> + if IsInterfaceKind(kind):
> return any(enum.native_only for enum in kind.enums)
> - elif IsArrayKind(kind):
> + if IsArrayKind(kind):
> return Check(kind.kind)
> - elif IsMapKind(kind):
> + if IsMapKind(kind):
> return Check(kind.key_kind) or Check(kind.value_kind)
> - else:
> - return False
> + return False
>
> return Check(kind)
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py
> index e8fd4936ce82..2a4e852c54d0 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# 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.
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
> index 88b77c9887c5..612404260f75 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
> @@ -1,7 +1,8 @@
> -# Copyright 2013 The Chromium Authors. All rights reserved.
> +# 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.
>
> +import copy
> from mojom.generate import module as mojom
>
> # This module provides a mechanism for determining the packed order and offsets
> @@ -15,7 +16,7 @@ from mojom.generate import module as mojom
> HEADER_SIZE = 8
>
>
> -class PackedField(object):
> +class PackedField:
> kind_to_size = {
> mojom.BOOL: 1,
> mojom.INT8: 1,
> @@ -75,18 +76,55 @@ class PackedField(object):
> return 8
> return cls.GetSizeForKind(kind)
>
> - def __init__(self, field, index, ordinal):
> + def __init__(self,
> + field,
> + index,
> + ordinal,
> + original_field=None,
> + sub_ordinal=None,
> + linked_value_packed_field=None):
> """
> Args:
> field: the original field.
> index: the position of the original field in the struct.
> ordinal: the ordinal of the field for serialization.
> + original_field: See below.
> + sub_ordinal: See below.
> + linked_value_packed_field: See below.
> +
> + original_field, sub_ordinal, and linked_value_packed_field are used to
> + support nullable ValueKind fields. For legacy reasons, nullable ValueKind
> + fields actually generate two PackedFields. This allows:
> +
> + - backwards compatibility prior to Mojo support for nullable ValueKinds.
> + - correct packing of fields for the aforementioned backwards compatibility.
> +
> + When translating Fields to PackedFields, the original field is turned into
> + two PackedFields: the first PackedField always has type mojom.BOOL, while
> + the second PackedField has the non-nullable version of the field's kind.
> +
> + When constructing these PackedFields, original_field references the field
> + as defined in the mojom; the name as defined in the mojom will be used for
> + all layers above the wire/data layer.
> +
> + sub_ordinal is used to sort the two PackedFields correctly with respect to
> + each other: the first mojom.BOOL field always has sub_ordinal 0, while the
> + second field always has sub_ordinal 1.
> +
> + Finally, linked_value_packed_field is used by the serialization and
> + deserialization helpers, which generally just iterate over a PackedStruct's
> + PackedField's in ordinal order. This allows the helpers to easily reference
> + any related PackedFields rather than having to lookup related PackedFields
> + by index while iterating.
> """
> self.field = field
> self.index = index
> self.ordinal = ordinal
> - self.size = self.GetSizeForKind(field.kind)
> - self.alignment = self.GetAlignmentForKind(field.kind)
> + self.original_field = original_field
> + self.sub_ordinal = sub_ordinal
> + self.linked_value_packed_field = linked_value_packed_field
> + self.size = self.GetSizeForKind(self.field.kind)
> + self.alignment = self.GetAlignmentForKind(self.field.kind)
> self.offset = None
> self.bit = None
> self.min_version = None
> @@ -120,7 +158,33 @@ def GetPayloadSizeUpToField(field):
> return offset + pad
>
>
> -class PackedStruct(object):
> +def IsNullableValueKindPackedField(field):
> + """Returns true if `field` is derived from a nullable ValueKind field.
> +
> + Nullable ValueKind fields often require special handling in the bindings due
> + to the way the implementation is constrained for wire compatibility.
> + """
> + assert isinstance(field, PackedField)
> + return field.sub_ordinal is not None
> +
> +
> +def IsPrimaryNullableValueKindPackedField(field):
> + """Returns true if `field` is derived from a nullable ValueKind mojom field
> + and is the "primary" field.
> +
> + The primary field is a bool PackedField that controls if the field should be
> + considered as present or not; it will have a reference to the PackedField that
> + holds the actual value representation if considered present.
> +
> + Bindings code that translates between the wire protocol and the higher layers
> + can use this to simplify mapping multiple PackedFields to the single field
> + that is logically exposed to bindings consumers.
> + """
> + assert isinstance(field, PackedField)
> + return field.linked_value_packed_field is not None
> +
> +
> +class PackedStruct:
> def __init__(self, struct):
> self.struct = struct
> # |packed_fields| contains all the fields, in increasing offset order.
> @@ -139,9 +203,41 @@ class PackedStruct(object):
> for index, field in enumerate(struct.fields):
> if field.ordinal is not None:
> ordinal = field.ordinal
> - src_fields.append(PackedField(field, index, ordinal))
> + # Nullable value types are a bit weird: they generate two PackedFields
> + # despite being a single ValueKind. This is for wire compatibility to
> + # ease the transition from legacy mojom syntax where nullable value types
> + # were not supported.
> + if isinstance(field.kind, mojom.ValueKind) and field.kind.is_nullable:
> + # The suffixes intentionally use Unicode codepoints which are considered
> + # valid C++/Java/JavaScript identifiers, yet are unlikely to be used in
> + # actual user code.
> + has_value_field = copy.copy(field)
> + has_value_field.name = f'{field.mojom_name}_$flag'
> + has_value_field.kind = mojom.BOOL
> +
> + value_field = copy.copy(field)
> + value_field.name = f'{field.mojom_name}_$value'
> + value_field.kind = field.kind.MakeUnnullableKind()
> +
> + value_packed_field = PackedField(value_field,
> + index,
> + ordinal,
> + original_field=field,
> + sub_ordinal=1,
> + linked_value_packed_field=None)
> + has_value_packed_field = PackedField(
> + has_value_field,
> + index,
> + ordinal,
> + original_field=field,
> + sub_ordinal=0,
> + linked_value_packed_field=value_packed_field)
> + src_fields.append(has_value_packed_field)
> + src_fields.append(value_packed_field)
> + else:
> + src_fields.append(PackedField(field, index, ordinal))
> ordinal += 1
> - src_fields.sort(key=lambda field: field.ordinal)
> + src_fields.sort(key=lambda field: (field.ordinal, field.sub_ordinal))
>
> # Set |min_version| for each field.
> next_min_version = 0
> @@ -156,10 +252,11 @@ class PackedStruct(object):
> if (packed_field.min_version != 0
> and mojom.IsReferenceKind(packed_field.field.kind)
> and not packed_field.field.kind.is_nullable):
> - raise Exception("Non-nullable fields are only allowed in version 0 of "
> - "a struct. %s.%s is defined with [MinVersion=%d]." %
> - (self.struct.name, packed_field.field.name,
> - packed_field.min_version))
> + raise Exception(
> + "Non-nullable reference fields are only allowed in version 0 of a "
> + "struct. %s.%s is defined with [MinVersion=%d]." %
> + (self.struct.name, packed_field.field.name,
> + packed_field.min_version))
>
> src_field = src_fields[0]
> src_field.offset = 0
> @@ -186,7 +283,7 @@ class PackedStruct(object):
> dst_fields.append(src_field)
>
>
> -class ByteInfo(object):
> +class ByteInfo:
> def __init__(self):
> self.is_padding = False
> self.packed_fields = []
> @@ -214,10 +311,11 @@ def GetByteLayout(packed_struct):
> return byte_info
>
>
> -class VersionInfo(object):
> - def __init__(self, version, num_fields, num_bytes):
> +class VersionInfo:
> + def __init__(self, version, num_fields, num_packed_fields, num_bytes):
> self.version = version
> self.num_fields = num_fields
> + self.num_packed_fields = num_packed_fields
> self.num_bytes = num_bytes
>
>
> @@ -235,24 +333,35 @@ def GetVersionInfo(packed_struct):
> versions = []
> last_version = 0
> last_num_fields = 0
> + last_num_packed_fields = 0
> last_payload_size = 0
>
> for packed_field in packed_struct.packed_fields_in_ordinal_order:
> if packed_field.min_version != last_version:
> versions.append(
> - VersionInfo(last_version, last_num_fields,
> + VersionInfo(last_version, last_num_fields, last_num_packed_fields,
> last_payload_size + HEADER_SIZE))
> last_version = packed_field.min_version
>
> - last_num_fields += 1
> + # Nullable numeric fields (e.g. `int32?`) expand to two packed fields, so to
> + # avoid double-counting, only increment if the field is:
> + # - not used for representing a nullable value kind field, or
> + # - the primary field representing the nullable value kind field.
> + last_num_fields += 1 if (
> + not IsNullableValueKindPackedField(packed_field)
> + or IsPrimaryNullableValueKindPackedField(packed_field)) else 0
> +
> + last_num_packed_fields += 1
> +
> # The fields are iterated in ordinal order here. However, the size of a
> # version is determined by the last field of that version in pack order,
> # instead of ordinal order. Therefore, we need to calculate the max value.
> - last_payload_size = max(
> - GetPayloadSizeUpToField(packed_field), last_payload_size)
> + last_payload_size = max(GetPayloadSizeUpToField(packed_field),
> + last_payload_size)
>
> - assert len(versions) == 0 or last_num_fields != versions[-1].num_fields
> + assert len(
> + versions) == 0 or last_num_packed_fields != versions[-1].num_packed_fields
> versions.append(
> - VersionInfo(last_version, last_num_fields,
> + VersionInfo(last_version, last_num_fields, last_num_packed_fields,
> last_payload_size + HEADER_SIZE))
> return versions
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py
> index 98c705add32b..7d8e4e019719 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2013 The Chromium Authors. All rights reserved.
> +# 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.
>
> @@ -205,6 +205,34 @@ class PackTest(unittest.TestCase):
> self.assertEqual(4, versions[2].num_fields)
> self.assertEqual(32, versions[2].num_bytes)
>
> + def testGetVersionInfoPackedStruct(self):
> + """Tests that pack.GetVersionInfo() correctly sets version, num_fields,
> + and num_packed_fields for a packed struct.
> + """
> + struct = mojom.Struct('test')
> + struct.AddField('field_0', mojom.BOOL, ordinal=0)
> + struct.AddField('field_1',
> + mojom.NULLABLE_BOOL,
> + ordinal=1,
> + attributes={'MinVersion': 1})
> + struct.AddField('field_2',
> + mojom.NULLABLE_BOOL,
> + ordinal=2,
> + attributes={'MinVersion': 2})
> + ps = pack.PackedStruct(struct)
> + versions = pack.GetVersionInfo(ps)
> +
> + self.assertEqual(3, len(versions))
> + self.assertEqual(0, versions[0].version)
> + self.assertEqual(1, versions[1].version)
> + self.assertEqual(2, versions[2].version)
> + self.assertEqual(1, versions[0].num_fields)
> + self.assertEqual(2, versions[1].num_fields)
> + self.assertEqual(3, versions[2].num_fields)
> + self.assertEqual(1, versions[0].num_packed_fields)
> + self.assertEqual(3, versions[1].num_packed_fields)
> + self.assertEqual(5, versions[2].num_packed_fields)
> +
> def testInterfaceAlignment(self):
> """Tests that interfaces are aligned on 4-byte boundaries, although the size
> of an interface is 8 bytes.
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py
> index 0da900585cea..807e2a4fdfc0 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py
> @@ -1,4 +1,4 @@
> -# Copyright 2013 The Chromium Authors. All rights reserved.
> +# 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.
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
> index 7580b78002e6..83bb297f5d44 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
> @@ -1,4 +1,4 @@
> -# Copyright 2013 The Chromium Authors. All rights reserved.
> +# 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.
> """Convert parse tree to AST.
> @@ -12,17 +12,294 @@ already been parsed and converted to ASTs before.
> import itertools
> import os
> import re
> -import sys
>
> +from collections import OrderedDict
> from mojom.generate import generator
> from mojom.generate import module as mojom
> from mojom.parse import ast
>
>
> -def _IsStrOrUnicode(x):
> - if sys.version_info[0] < 3:
> - return isinstance(x, (unicode, str))
> - return isinstance(x, str)
> +is_running_backwards_compatibility_check_hack = False
> +
> +### DO NOT ADD ENTRIES TO THIS LIST. ###
> +_EXTENSIBLE_ENUMS_MISSING_DEFAULT = (
> + 'x:arc.keymaster.mojom.Algorithm',
> + 'x:arc.keymaster.mojom.Digest',
> + 'x:arc.keymaster.mojom.SignatureResult',
> + 'x:arc.mojom.AccessibilityActionType',
> + 'x:arc.mojom.AccessibilityBooleanProperty',
> + 'x:arc.mojom.AccessibilityEventIntListProperty',
> + 'x:arc.mojom.AccessibilityEventIntProperty',
> + 'x:arc.mojom.AccessibilityEventStringProperty',
> + 'x:arc.mojom.AccessibilityEventType',
> + 'x:arc.mojom.AccessibilityFilterType',
> + 'x:arc.mojom.AccessibilityIntListProperty',
> + 'x:arc.mojom.AccessibilityIntProperty',
> + 'x:arc.mojom.AccessibilityLiveRegionType',
> + 'x:arc.mojom.AccessibilityNotificationStateType',
> + 'x:arc.mojom.AccessibilityRangeType',
> + 'x:arc.mojom.AccessibilitySelectionMode',
> + 'x:arc.mojom.AccessibilityStringListProperty',
> + 'x:arc.mojom.AccessibilityStringProperty',
> + 'x:arc.mojom.AccessibilityWindowBooleanProperty',
> + 'x:arc.mojom.AccessibilityWindowIntListProperty',
> + 'x:arc.mojom.AccessibilityWindowIntProperty',
> + 'x:arc.mojom.AccessibilityWindowStringProperty',
> + 'x:arc.mojom.AccessibilityWindowType',
> + 'x:arc.mojom.AccountCheckStatus',
> + 'x:arc.mojom.AccountUpdateType',
> + 'x:arc.mojom.ActionType',
> + 'x:arc.mojom.Algorithm',
> + 'x:arc.mojom.AndroidIdSource',
> + 'x:arc.mojom.AnrSource',
> + 'x:arc.mojom.AnrType',
> + 'x:arc.mojom.AppDiscoveryRequestState',
> + 'x:arc.mojom.AppKillType',
> + 'x:arc.mojom.AppPermission',
> + 'x:arc.mojom.AppPermissionGroup',
> + 'x:arc.mojom.AppReinstallState',
> + 'x:arc.mojom.AppShortcutItemType',
> + 'x:arc.mojom.ArcAuthCodeStatus',
> + 'x:arc.mojom.ArcClipboardDragDropEvent',
> + 'x:arc.mojom.ArcCorePriAbiMigEvent',
> + 'x:arc.mojom.ArcDnsQuery',
> + 'x:arc.mojom.ArcImageCopyPasteCompatAction',
> + 'x:arc.mojom.ArcNetworkError',
> + 'x:arc.mojom.ArcNetworkEvent',
> + 'x:arc.mojom.ArcNotificationEvent',
> + 'x:arc.mojom.ArcNotificationExpandState',
> + 'x:arc.mojom.ArcNotificationPriority',
> + 'x:arc.mojom.ArcNotificationRemoteInputState',
> + 'x:arc.mojom.ArcNotificationShownContents',
> + 'x:arc.mojom.ArcNotificationStyle',
> + 'x:arc.mojom.ArcNotificationType',
> + 'x:arc.mojom.ArcPipEvent',
> + 'x:arc.mojom.ArcResizeLockState',
> + 'x:arc.mojom.ArcSignInSuccess',
> + 'x:arc.mojom.ArcTimerResult',
> + 'x:arc.mojom.AudioSwitch',
> + 'x:arc.mojom.BluetoothAclState',
> + 'x:arc.mojom.BluetoothAdapterState',
> + 'x:arc.mojom.BluetoothAdvertisingDataType',
> + 'x:arc.mojom.BluetoothBondState',
> + 'x:arc.mojom.BluetoothDeviceType',
> + 'x:arc.mojom.BluetoothDiscoveryState',
> + 'x:arc.mojom.BluetoothGattDBAttributeType',
> + 'x:arc.mojom.BluetoothGattStatus',
> + 'x:arc.mojom.BluetoothPropertyType',
> + 'x:arc.mojom.BluetoothScanMode',
> + 'x:arc.mojom.BluetoothSdpAttributeType',
> + 'x:arc.mojom.BluetoothSocketType',
> + 'x:arc.mojom.BluetoothStatus',
> + 'x:arc.mojom.BootType',
> + 'x:arc.mojom.CaptionTextShadowType',
> + 'x:arc.mojom.ChangeType',
> + 'x:arc.mojom.ChromeAccountType',
> + 'x:arc.mojom.ChromeApp',
> + 'x:arc.mojom.ChromePage',
> + 'x:arc.mojom.ClockId',
> + 'x:arc.mojom.CloudProvisionFlowError',
> + 'x:arc.mojom.CommandResultType',
> + 'x:arc.mojom.CompanionLibApiId',
> + 'x:arc.mojom.ConnectionStateType',
> + 'x:arc.mojom.ContentChangeType',
> + 'x:arc.mojom.CpuRestrictionState',
> + 'x:arc.mojom.CursorCoordinateSpace',
> + 'x:arc.mojom.DataRestoreStatus',
> + 'x:arc.mojom.DecoderStatus',
> + 'x:arc.mojom.DeviceType',
> + 'x:arc.mojom.Digest',
> + 'x:arc.mojom.DisplayWakeLockType',
> + 'x:arc.mojom.EapMethod',
> + 'x:arc.mojom.EapPhase2Method',
> + 'x:arc.mojom.FileSelectorEventType',
> + 'x:arc.mojom.GMSCheckInError',
> + 'x:arc.mojom.GMSSignInError',
> + 'x:arc.mojom.GeneralSignInError',
> + 'x:arc.mojom.GetNetworksRequestType',
> + 'x:arc.mojom.HalPixelFormat',
> + 'x:arc.mojom.IPAddressType',
> + 'x:arc.mojom.InstallErrorReason',
> + 'x:arc.mojom.KeyFormat',
> + 'x:arc.mojom.KeyManagement',
> + 'x:arc.mojom.KeyPurpose',
> + 'x:arc.mojom.KeymasterError',
> + 'x:arc.mojom.MainAccountHashMigrationStatus',
> + 'x:arc.mojom.MainAccountResolutionStatus',
> + 'x:arc.mojom.ManagementChangeStatus',
> + 'x:arc.mojom.ManagementState',
> + 'x:arc.mojom.MessageCenterVisibility',
> + 'x:arc.mojom.MetricsType',
> + 'x:arc.mojom.MountEvent',
> + 'x:arc.mojom.NativeBridgeType',
> + 'x:arc.mojom.NetworkResult',
> + 'x:arc.mojom.NetworkType',
> + 'x:arc.mojom.OemCryptoAlgorithm',
> + 'x:arc.mojom.OemCryptoCipherMode',
> + 'x:arc.mojom.OemCryptoHdcpCapability',
> + 'x:arc.mojom.OemCryptoLicenseType',
> + 'x:arc.mojom.OemCryptoPrivateKey',
> + 'x:arc.mojom.OemCryptoProvisioningMethod',
> + 'x:arc.mojom.OemCryptoResult',
> + 'x:arc.mojom.OemCryptoRsaPaddingScheme',
> + 'x:arc.mojom.OemCryptoUsageEntryStatus',
> + 'x:arc.mojom.Padding',
> + 'x:arc.mojom.PaiFlowState',
> + 'x:arc.mojom.PatternType',
> + 'x:arc.mojom.PressureLevel',
> + 'x:arc.mojom.PrintColorMode',
> + 'x:arc.mojom.PrintContentType',
> + 'x:arc.mojom.PrintDuplexMode',
> + 'x:arc.mojom.PrinterStatus',
> + 'x:arc.mojom.ProcessState',
> + 'x:arc.mojom.PurchaseState',
> + 'x:arc.mojom.ReauthReason',
> + 'x:arc.mojom.ScaleFactor',
> + 'x:arc.mojom.SecurityType',
> + 'x:arc.mojom.SegmentStyle',
> + 'x:arc.mojom.SelectFilesActionType',
> + 'x:arc.mojom.SetNativeChromeVoxResponse',
> + 'x:arc.mojom.ShowPackageInfoPage',
> + 'x:arc.mojom.SpanType',
> + 'x:arc.mojom.SupportedLinkChangeSource',
> + 'x:arc.mojom.TetheringClientState',
> + 'x:arc.mojom.TextInputType',
> + 'x:arc.mojom.TtsEventType',
> + 'x:arc.mojom.VideoCodecProfile',
> + 'x:arc.mojom.VideoDecodeAccelerator.Result',
> + 'x:arc.mojom.VideoEncodeAccelerator.Error',
> + 'x:arc.mojom.VideoFrameStorageType',
> + 'x:arc.mojom.VideoPixelFormat',
> + 'x:arc.mojom.WakefulnessMode',
> + 'x:arc.mojom.WebApkInstallResult',
> + 'x:ash.ime.mojom.InputFieldType',
> + 'x:ash.ime.mojom.PersonalizationMode',
> + 'x:ash.language.mojom.FeatureId',
> + 'x:blink.mojom.ScrollRestorationType',
> + 'x:chromeos.cdm.mojom.CdmKeyStatus',
> + 'x:chromeos.cdm.mojom.CdmMessageType',
> + 'x:chromeos.cdm.mojom.CdmSessionType',
> + 'x:chromeos.cdm.mojom.DecryptStatus',
> + 'x:chromeos.cdm.mojom.EmeInitDataType',
> + 'x:chromeos.cdm.mojom.EncryptionScheme',
> + 'x:chromeos.cdm.mojom.HdcpVersion',
> + 'x:chromeos.cdm.mojom.OutputProtection.LinkType',
> + 'x:chromeos.cdm.mojom.OutputProtection.ProtectionType',
> + 'x:chromeos.cdm.mojom.PromiseException',
> + 'x:chromeos.cfm.mojom.EnqueuePriority',
> + 'x:chromeos.cfm.mojom.LoggerErrorCode',
> + 'x:chromeos.cfm.mojom.LoggerState',
> + 'x:chromeos.cros_healthd.mojom.CryptoAlgorithm',
> + 'x:chromeos.cros_healthd.mojom.EncryptionState',
> + 'x:chromeos.machine_learning.mojom.AnnotationUsecase',
> + 'x:chromeos.machine_learning.mojom.BuiltinModelId',
> + 'x:chromeos.machine_learning.mojom.CreateGraphExecutorResult',
> + 'x:chromeos.machine_learning.mojom.DocumentScannerResultStatus',
> + 'x:chromeos.machine_learning.mojom.EndpointReason',
> + 'x:chromeos.machine_learning.mojom.EndpointerType',
> + 'x:chromeos.machine_learning.mojom.ExecuteResult',
> + 'x:chromeos.machine_learning.mojom.GrammarCheckerResult.Status',
> + 'x:chromeos.machine_learning.mojom.HandwritingRecognizerResult.Status',
> + 'x:chromeos.machine_learning.mojom.LoadHandwritingModelResult',
> + 'x:chromeos.machine_learning.mojom.LoadModelResult',
> + 'x:chromeos.machine_learning.mojom.Rotation',
> + 'x:chromeos.network_config.mojom.ConnectionStateType',
> + 'x:chromeos.network_config.mojom.DeviceStateType',
> + 'x:chromeos.network_config.mojom.IPConfigType',
> + 'x:chromeos.network_config.mojom.NetworkType',
> + 'x:chromeos.network_config.mojom.OncSource',
> + 'x:chromeos.network_config.mojom.PolicySource',
> + 'x:chromeos.network_config.mojom.PortalState',
> + 'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdEvent',
> + 'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestHttpMethod',
> + 'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestStatus',
> + 'x:cros.mojom.CameraClientType',
> + 'x:cros.mojom.CameraMetadataSectionStart',
> + 'x:cros.mojom.CameraMetadataTag',
> + 'x:cros.mojom.HalPixelFormat',
> + 'x:crosapi.mojom.AllowedPaths',
> + 'x:crosapi.mojom.BrowserAppInstanceType',
> + 'x:crosapi.mojom.CreationResult',
> + 'x:crosapi.mojom.DeviceAccessResultCode',
> + 'x:crosapi.mojom.DeviceMode',
> + 'x:crosapi.mojom.DlpRestrictionLevel',
> + 'x:crosapi.mojom.ExoImeSupport',
> + 'x:crosapi.mojom.FullscreenVisibility',
> + 'x:crosapi.mojom.GoogleServiceAuthError.State',
> + 'x:crosapi.mojom.IsInstallableResult',
> + 'x:crosapi.mojom.KeyTag',
> + 'x:crosapi.mojom.KeystoreSigningAlgorithmName',
> + 'x:crosapi.mojom.KeystoreType',
> + 'x:crosapi.mojom.LacrosFeedbackSource',
> + 'x:crosapi.mojom.MemoryPressureLevel',
> + 'x:crosapi.mojom.MetricsReportingManaged',
> + 'x:crosapi.mojom.NotificationType',
> + 'x:crosapi.mojom.OndeviceHandwritingSupport',
> + 'x:crosapi.mojom.OpenResult',
> + 'x:crosapi.mojom.PolicyDomain',
> + 'x:crosapi.mojom.RegistrationCodeType',
> + 'x:crosapi.mojom.ScaleFactor',
> + 'x:crosapi.mojom.SearchResult.OptionalBool',
> + 'x:crosapi.mojom.SelectFileDialogType',
> + 'x:crosapi.mojom.SelectFileResult',
> + 'x:crosapi.mojom.SharesheetResult',
> + 'x:crosapi.mojom.TouchEventType',
> + 'x:crosapi.mojom.VideoRotation',
> + 'x:crosapi.mojom.WallpaperLayout',
> + 'x:crosapi.mojom.WebAppInstallResultCode',
> + 'x:crosapi.mojom.WebAppUninstallResultCode',
> + 'x:device.mojom.HidBusType',
> + 'x:device.mojom.WakeLockReason',
> + 'x:device.mojom.WakeLockType',
> + 'x:drivefs.mojom.DialogReason.Type',
> + 'x:drivefs.mojom.DriveError.Type',
> + 'x:drivefs.mojom.DriveFsDelegate.ExtensionConnectionStatus',
> + 'x:drivefs.mojom.FileMetadata.CanPinStatus',
> + 'x:drivefs.mojom.FileMetadata.Type',
> + 'x:drivefs.mojom.ItemEventReason',
> + 'x:drivefs.mojom.MirrorPathStatus',
> + 'x:drivefs.mojom.MirrorSyncStatus',
> + 'x:drivefs.mojom.QueryParameters.SortField',
> + 'x:fuzz.mojom.FuzzEnum',
> + 'x:media.mojom.FillLightMode',
> + 'x:media.mojom.MeteringMode',
> + 'x:media.mojom.PowerLineFrequency',
> + 'x:media.mojom.RedEyeReduction',
> + 'x:media.mojom.ResolutionChangePolicy',
> + 'x:media.mojom.VideoCaptureApi',
> + 'x:media.mojom.VideoCaptureBufferType',
> + 'x:media.mojom.VideoCaptureError',
> + 'x:media.mojom.VideoCaptureFrameDropReason',
> + 'x:media.mojom.VideoCapturePixelFormat',
> + 'x:media.mojom.VideoCaptureTransportType',
> + 'x:media.mojom.VideoFacingMode',
> + 'x:media_session.mojom.AudioFocusType',
> + 'x:media_session.mojom.CameraState',
> + 'x:media_session.mojom.EnforcementMode',
> + 'x:media_session.mojom.MediaAudioVideoState',
> + 'x:media_session.mojom.MediaImageBitmapColorType',
> + 'x:media_session.mojom.MediaPictureInPictureState',
> + 'x:media_session.mojom.MediaPlaybackState',
> + 'x:media_session.mojom.MediaSession.SuspendType',
> + 'x:media_session.mojom.MediaSessionAction',
> + 'x:media_session.mojom.MediaSessionImageType',
> + 'x:media_session.mojom.MediaSessionInfo.SessionState',
> + 'x:media_session.mojom.MicrophoneState',
> + 'x:ml.model_loader.mojom.ComputeResult',
> + 'x:ml.model_loader.mojom.CreateModelLoaderResult',
> + 'x:ml.model_loader.mojom.LoadModelResult',
> + 'x:mojo.test.AnExtensibleEnum',
> + 'x:mojo.test.EnumB',
> + 'x:mojo.test.ExtensibleEmptyEnum',
> + 'x:mojo.test.enum_default_unittest.mojom.ExtensibleEnumWithoutDefault',
> + 'x:network.mojom.WebSandboxFlags',
> + 'x:payments.mojom.BillingResponseCode',
> + 'x:payments.mojom.CreateDigitalGoodsResponseCode',
> + 'x:payments.mojom.ItemType',
> + 'x:printing.mojom.PrinterType',
> + 'x:ui.mojom.KeyboardCode',
> +)
> +### DO NOT ADD ENTRIES TO THIS LIST. ###
>
>
> def _DuplicateName(values):
> @@ -98,12 +375,6 @@ def _MapKind(kind):
> }
> if kind.endswith('?'):
> base_kind = _MapKind(kind[0:-1])
> - # NOTE: This doesn't rule out enum types. Those will be detected later, when
> - # cross-reference is established.
> - reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso', 'rmt', 'rcv',
> - 'rma', 'rca')
> - if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds:
> - raise Exception('A type (spec "%s") cannot be made nullable' % base_kind)
> return '?' + base_kind
> if kind.endswith('}'):
> lbracket = kind.rfind('{')
> @@ -113,8 +384,6 @@ def _MapKind(kind):
> lbracket = kind.rfind('[')
> typename = kind[0:lbracket]
> return 'a' + kind[lbracket + 1:-1] + ':' + _MapKind(typename)
> - if kind.endswith('&'):
> - return 'r:' + _MapKind(kind[0:-1])
> if kind.startswith('asso<'):
> assert kind.endswith('>')
> return 'asso:' + _MapKind(kind[5:-1])
> @@ -135,13 +404,45 @@ def _MapKind(kind):
> return 'x:' + kind
>
>
> -def _AttributeListToDict(attribute_list):
> +def _MapAttributeValue(module, kind, value):
> + # True/False/None
> + if value is None:
> + return value
> + if not isinstance(value, str):
> + return value
> + # Is the attribute value the name of a feature?
> + try:
> + # Features cannot be nested in other types, so lookup in the global scope.
> + trial = _LookupKind(module.kinds, 'x:' + value,
> + _GetScopeForKind(module, kind))
> + if isinstance(trial, mojom.Feature):
> + return trial
> + except ValueError:
> + pass
> + # Is the attribute value a constant or enum value?
> + try:
> + trial = _LookupValue(module, None, None, ('IDENTIFIER', value))
> + if isinstance(trial, mojom.ConstantValue):
> + return trial.constant
> + if isinstance(trial, mojom.EnumValue):
> + return trial
> + except ValueError:
> + pass
> + # If not a referenceable mojo type - return as a string.
> + return value
> +
> +
> +def _AttributeListToDict(module, kind, attribute_list):
> if attribute_list is None:
> return None
> assert isinstance(attribute_list, ast.AttributeList)
> - # TODO(vtl): Check for duplicate keys here.
> - return dict(
> - [(attribute.key, attribute.value) for attribute in attribute_list])
> + attributes = dict()
> + for attribute in attribute_list:
> + if attribute.key in attributes:
> + raise Exception("Duplicate key (%s) in attribute list" % attribute.key)
> + attributes[attribute.key] = _MapAttributeValue(module, kind,
> + attribute.value)
> + return attributes
>
>
> builtin_values = frozenset([
> @@ -257,7 +558,8 @@ def _Kind(kinds, spec, scope):
> return kind
>
> if spec.startswith('?'):
> - kind = _Kind(kinds, spec[1:], scope).MakeNullableKind()
> + kind = _Kind(kinds, spec[1:], scope)
> + kind = kind.MakeNullableKind()
> elif spec.startswith('a:'):
> kind = mojom.Array(_Kind(kinds, spec[2:], scope))
> elif spec.startswith('asso:'):
> @@ -303,7 +605,8 @@ def _Kind(kinds, spec, scope):
>
> def _Import(module, import_module):
> # Copy the struct kinds from our imports into the current module.
> - importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface)
> + importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface,
> + mojom.Feature)
> for kind in import_module.kinds.values():
> if (isinstance(kind, importable_kinds)
> and kind.module.path == import_module.path):
> @@ -316,6 +619,32 @@ def _Import(module, import_module):
> return import_module
>
>
> +def _Feature(module, parsed_feature):
> + """
> + Args:
> + module: {mojom.Module} Module currently being constructed.
> + parsed_feature: {ast.Feature} Parsed feature.
> +
> + Returns:
> + {mojom.Feature} AST feature.
> + """
> + feature = mojom.Feature(module=module)
> + feature.mojom_name = parsed_feature.mojom_name
> + feature.spec = 'x:' + module.GetNamespacePrefix() + feature.mojom_name
> + module.kinds[feature.spec] = feature
> + feature.constants = []
> + _ProcessElements(
> + parsed_feature.mojom_name, parsed_feature.body, {
> + ast.Const:
> + lambda const: feature.constants.append(
> + _Constant(module, const, feature)),
> + })
> +
> + feature.attributes = _AttributeListToDict(module, feature,
> + parsed_feature.attribute_list)
> + return feature
> +
> +
> def _Struct(module, parsed_struct):
> """
> Args:
> @@ -345,7 +674,8 @@ def _Struct(module, parsed_struct):
> struct.fields_data.append,
> })
>
> - struct.attributes = _AttributeListToDict(parsed_struct.attribute_list)
> + struct.attributes = _AttributeListToDict(module, struct,
> + parsed_struct.attribute_list)
>
> # Enforce that a [Native] attribute is set to make native-only struct
> # declarations more explicit.
> @@ -377,7 +707,8 @@ def _Union(module, parsed_union):
> union.fields_data = []
> _ProcessElements(parsed_union.mojom_name, parsed_union.body,
> {ast.UnionField: union.fields_data.append})
> - union.attributes = _AttributeListToDict(parsed_union.attribute_list)
> + union.attributes = _AttributeListToDict(module, union,
> + parsed_union.attribute_list)
> return union
>
>
> @@ -398,7 +729,8 @@ def _StructField(module, parsed_field, struct):
> field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
> field.default = _LookupValue(module, struct, field.kind,
> parsed_field.default_value)
> - field.attributes = _AttributeListToDict(parsed_field.attribute_list)
> + field.attributes = _AttributeListToDict(module, field,
> + parsed_field.attribute_list)
> return field
>
>
> @@ -414,11 +746,22 @@ def _UnionField(module, parsed_field, union):
> """
> field = mojom.UnionField()
> field.mojom_name = parsed_field.mojom_name
> + # Disallow unions from being self-recursive.
> + parsed_typename = parsed_field.typename
> + if parsed_typename.endswith('?'):
> + parsed_typename = parsed_typename[:-1]
> + assert parsed_typename != union.mojom_name
> field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename),
> (module.mojom_namespace, union.mojom_name))
> field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
> field.default = None
> - field.attributes = _AttributeListToDict(parsed_field.attribute_list)
> + field.attributes = _AttributeListToDict(module, field,
> + parsed_field.attribute_list)
> + if field.is_default and not mojom.IsNullableKind(field.kind) and \
> + not mojom.IsIntegralKind(field.kind):
> + raise Exception(
> + '[Default] field for union %s must be nullable or integral type.' %
> + union.mojom_name)
> return field
>
>
> @@ -439,7 +782,8 @@ def _Parameter(module, parsed_param, interface):
> parameter.ordinal = (parsed_param.ordinal.value
> if parsed_param.ordinal else None)
> parameter.default = None # TODO(tibell): We never have these. Remove field?
> - parameter.attributes = _AttributeListToDict(parsed_param.attribute_list)
> + parameter.attributes = _AttributeListToDict(module, parameter,
> + parsed_param.attribute_list)
> return parameter
>
>
> @@ -464,7 +808,8 @@ def _Method(module, parsed_method, interface):
> method.response_parameters = list(
> map(lambda parameter: _Parameter(module, parameter, interface),
> parsed_method.response_parameter_list))
> - method.attributes = _AttributeListToDict(parsed_method.attribute_list)
> + method.attributes = _AttributeListToDict(module, method,
> + parsed_method.attribute_list)
>
> # Enforce that only methods with response can have a [Sync] attribute.
> if method.sync and method.response_parameters is None:
> @@ -492,7 +837,8 @@ def _Interface(module, parsed_iface):
> interface.mojom_name = parsed_iface.mojom_name
> interface.spec = 'x:' + module.GetNamespacePrefix() + interface.mojom_name
> module.kinds[interface.spec] = interface
> - interface.attributes = _AttributeListToDict(parsed_iface.attribute_list)
> + interface.attributes = _AttributeListToDict(module, interface,
> + parsed_iface.attribute_list)
> interface.enums = []
> interface.constants = []
> interface.methods_data = []
> @@ -522,7 +868,8 @@ def _EnumField(module, enum, parsed_field):
> field = mojom.EnumField()
> field.mojom_name = parsed_field.mojom_name
> field.value = _LookupValue(module, enum, None, parsed_field.value)
> - field.attributes = _AttributeListToDict(parsed_field.attribute_list)
> + field.attributes = _AttributeListToDict(module, field,
> + parsed_field.attribute_list)
> value = mojom.EnumValue(module, enum, field)
> module.values[value.GetSpec()] = value
> return field
> @@ -544,7 +891,7 @@ def _ResolveNumericEnumValues(enum):
> prev_value += 1
>
> # Integral value (e.g: BEGIN = -0x1).
> - elif _IsStrOrUnicode(field.value):
> + elif isinstance(field.value, str):
> prev_value = int(field.value, 0)
>
> # Reference to a previous enum value (e.g: INIT = BEGIN).
> @@ -560,7 +907,10 @@ def _ResolveNumericEnumValues(enum):
> else:
> raise Exception('Unresolved enum value for %s' % field.value.GetSpec())
>
> - #resolved_enum_values[field.mojom_name] = prev_value
> + if prev_value in (-128, -127):
> + raise Exception(f'{field.mojom_name} in {enum.spec} has the value '
> + f'{prev_value}, which is reserved for WTF::HashTrait\'s '
> + 'default enum specialization and may not be used.')
> field.numeric_value = prev_value
> if min_value is None or prev_value < min_value:
> min_value = prev_value
> @@ -588,7 +938,8 @@ def _Enum(module, parsed_enum, parent_kind):
> mojom_name = parent_kind.mojom_name + '.' + mojom_name
> enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name)
> enum.parent_kind = parent_kind
> - enum.attributes = _AttributeListToDict(parsed_enum.attribute_list)
> + enum.attributes = _AttributeListToDict(module, enum,
> + parsed_enum.attribute_list)
>
> if not enum.native_only:
> enum.fields = list(
> @@ -600,11 +951,18 @@ def _Enum(module, parsed_enum, parent_kind):
> for field in enum.fields:
> if field.default:
> if not enum.extensible:
> - raise Exception('Non-extensible enums may not specify a default')
> - if enum.default_field is not None:
> raise Exception(
> - 'Only one enumerator value may be specified as the default')
> + f'Non-extensible enum {enum.spec} may not specify a default')
> + if enum.default_field is not None:
> + raise Exception(f'Multiple [Default] enumerators in enum {enum.spec}')
> enum.default_field = field
> + # While running the backwards compatibility check, ignore errors because the
> + # old version of the enum might not specify [Default].
> + if (enum.extensible and enum.default_field is None
> + and enum.spec not in _EXTENSIBLE_ENUMS_MISSING_DEFAULT
> + and not is_running_backwards_compatibility_check_hack):
> + raise Exception(
> + f'Extensible enum {enum.spec} must specify a [Default] enumerator')
>
> module.kinds[enum.spec] = enum
>
> @@ -696,6 +1054,11 @@ def _CollectReferencedKinds(module, all_defined_kinds):
> for referenced_kind in extract_referenced_user_kinds(param.kind):
> sanitized_kind = sanitize_kind(referenced_kind)
> referenced_user_kinds[sanitized_kind.spec] = sanitized_kind
> + # Consts can reference imported enums.
> + for const in module.constants:
> + if not const.kind in mojom.PRIMITIVES:
> + sanitized_kind = sanitize_kind(const.kind)
> + referenced_user_kinds[sanitized_kind.spec] = sanitized_kind
>
> return referenced_user_kinds
>
> @@ -741,6 +1104,16 @@ def _AssertTypeIsStable(kind):
> assertDependencyIsStable(response_param.kind)
>
>
> +def _AssertStructIsValid(kind):
> + expected_ordinals = set(range(0, len(kind.fields)))
> + ordinals = set(map(lambda field: field.ordinal, kind.fields))
> + if ordinals != expected_ordinals:
> + raise Exception(
> + 'Structs must use contiguous ordinals starting from 0. ' +
> + '{} is missing the following ordinals: {}.'.format(
> + kind.mojom_name, ', '.join(map(str, expected_ordinals - ordinals))))
> +
> +
> def _Module(tree, path, imports):
> """
> Args:
> @@ -778,6 +1151,8 @@ def _Module(tree, path, imports):
> module.structs = []
> module.unions = []
> module.interfaces = []
> + module.features = []
> +
> _ProcessElements(
> filename, tree.definition_list, {
> ast.Const:
> @@ -791,6 +1166,8 @@ def _Module(tree, path, imports):
> ast.Interface:
> lambda interface: module.interfaces.append(
> _Interface(module, interface)),
> + ast.Feature:
> + lambda feature: module.features.append(_Feature(module, feature)),
> })
>
> # Second pass expands fields and methods. This allows fields and parameters
> @@ -806,12 +1183,24 @@ def _Module(tree, path, imports):
> for enum in struct.enums:
> all_defined_kinds[enum.spec] = enum
>
> + for feature in module.features:
> + all_defined_kinds[feature.spec] = feature
> +
> for union in module.unions:
> union.fields = list(
> map(lambda field: _UnionField(module, field, union), union.fields_data))
> _AssignDefaultOrdinals(union.fields)
> + for field in union.fields:
> + if field.is_default:
> + if union.default_field is not None:
> + raise Exception('Multiple [Default] fields in union %s.' %
> + union.mojom_name)
> + union.default_field = field
> del union.fields_data
> all_defined_kinds[union.spec] = union
> + if union.extensible and union.default_field is None:
> + raise Exception('Extensible union %s must specify a [Default] field' %
> + union.mojom_name)
>
> for interface in module.interfaces:
> interface.methods = list(
> @@ -829,8 +1218,8 @@ def _Module(tree, path, imports):
> all_defined_kinds.values())
> imported_kind_specs = set(all_referenced_kinds.keys()).difference(
> set(all_defined_kinds.keys()))
> - module.imported_kinds = dict(
> - (spec, all_referenced_kinds[spec]) for spec in imported_kind_specs)
> + module.imported_kinds = OrderedDict((spec, all_referenced_kinds[spec])
> + for spec in sorted(imported_kind_specs))
>
> generator.AddComputedData(module)
> for iface in module.interfaces:
> @@ -847,6 +1236,9 @@ def _Module(tree, path, imports):
> if kind.stable:
> _AssertTypeIsStable(kind)
>
> + for kind in module.structs:
> + _AssertStructIsValid(kind)
> +
> return module
>
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
> index 19905c8a9540..b4fea92467d5 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
> @@ -1,17 +1,13 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# 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 imp
> -import os.path
> -import sys
> import unittest
>
> from mojom.generate import module as mojom
> from mojom.generate import translate
> from mojom.parse import ast
>
> -
> class TranslateTest(unittest.TestCase):
> """Tests |parser.Parse()|."""
>
> @@ -69,5 +65,77 @@ class TranslateTest(unittest.TestCase):
> # pylint: disable=W0212
> self.assertEquals(
> translate._MapKind("asso<SomeInterface>?"), "?asso:x:SomeInterface")
> - self.assertEquals(
> - translate._MapKind("asso<SomeInterface&>?"), "?asso:r:x:SomeInterface")
> + self.assertEquals(translate._MapKind("rca<SomeInterface>?"),
> + "?rca:x:SomeInterface")
> +
> + def testSelfRecursiveUnions(self):
> + """Verifies _UnionField() raises when a union is self-recursive."""
> + tree = ast.Mojom(None, ast.ImportList(), [
> + ast.Union("SomeUnion", None,
> + ast.UnionBody([ast.UnionField("a", None, None, "SomeUnion")]))
> + ])
> + with self.assertRaises(Exception):
> + translate.OrderedModule(tree, "mojom_tree", [])
> +
> + tree = ast.Mojom(None, ast.ImportList(), [
> + ast.Union(
> + "SomeUnion", None,
> + ast.UnionBody([ast.UnionField("a", None, None, "SomeUnion?")]))
> + ])
> + with self.assertRaises(Exception):
> + translate.OrderedModule(tree, "mojom_tree", [])
> +
> + def testDuplicateAttributesException(self):
> + tree = ast.Mojom(None, ast.ImportList(), [
> + ast.Union(
> + "FakeUnion",
> + ast.AttributeList([
> + ast.Attribute("key1", "value"),
> + ast.Attribute("key1", "value")
> + ]),
> + ast.UnionBody([
> + ast.UnionField("a", None, None, "int32"),
> + ast.UnionField("b", None, None, "string")
> + ]))
> + ])
> + with self.assertRaises(Exception):
> + translate.OrderedModule(tree, "mojom_tree", [])
> +
> + def testEnumWithReservedValues(self):
> + """Verifies that assigning reserved values to enumerators fails."""
> + # -128 is reserved for the empty representation in WTF::HashTraits.
> + tree = ast.Mojom(None, ast.ImportList(), [
> + ast.Enum(
> + "MyEnum", None,
> + ast.EnumValueList([
> + ast.EnumValue('kReserved', None, '-128'),
> + ]))
> + ])
> + with self.assertRaises(Exception) as context:
> + translate.OrderedModule(tree, "mojom_tree", [])
> + self.assertIn("reserved for WTF::HashTrait", str(context.exception))
> +
> + # -127 is reserved for the deleted representation in WTF::HashTraits.
> + tree = ast.Mojom(None, ast.ImportList(), [
> + ast.Enum(
> + "MyEnum", None,
> + ast.EnumValueList([
> + ast.EnumValue('kReserved', None, '-127'),
> + ]))
> + ])
> + with self.assertRaises(Exception) as context:
> + translate.OrderedModule(tree, "mojom_tree", [])
> + self.assertIn("reserved for WTF::HashTrait", str(context.exception))
> +
> + # Implicitly assigning a reserved value should also fail.
> + tree = ast.Mojom(None, ast.ImportList(), [
> + ast.Enum(
> + "MyEnum", None,
> + ast.EnumValueList([
> + ast.EnumValue('kNotReserved', None, '-129'),
> + ast.EnumValue('kImplicitlyReserved', None, None),
> + ]))
> + ])
> + with self.assertRaises(Exception) as context:
> + translate.OrderedModule(tree, "mojom_tree", [])
> + self.assertIn("reserved for WTF::HashTrait", str(context.exception))
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
> index 1f0db200549b..aae9cdb659bd 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
> @@ -1,4 +1,4 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# 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.
> """Node classes for the AST for a Mojo IDL file."""
> @@ -8,17 +8,14 @@
> # and lineno). You may also define __repr__() to help with analyzing test
> # failures, especially for more complex types.
>
> -
> -import sys
> +import os.path
>
>
> -def _IsStrOrUnicode(x):
> - if sys.version_info[0] < 3:
> - return isinstance(x, (unicode, str))
> - return isinstance(x, str)
> +# Instance of 'NodeListBase' has no '_list_item_type' member (no-member)
> +# pylint: disable=no-member
>
>
> -class NodeBase(object):
> +class NodeBase:
> """Base class for nodes in the AST."""
>
> def __init__(self, filename=None, lineno=None):
> @@ -43,7 +40,7 @@ class NodeListBase(NodeBase):
> classes, in a tuple) of the members of the list.)"""
>
> def __init__(self, item_or_items=None, **kwargs):
> - super(NodeListBase, self).__init__(**kwargs)
> + super().__init__(**kwargs)
> self.items = []
> if item_or_items is None:
> pass
> @@ -62,7 +59,7 @@ class NodeListBase(NodeBase):
> return self.items.__iter__()
>
> def __eq__(self, other):
> - return super(NodeListBase, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.items == other.items
>
> # Implement this so that on failure, we get slightly more sensible output.
> @@ -96,7 +93,7 @@ class Definition(NodeBase):
> include parameter definitions.) This class is meant to be subclassed."""
>
> def __init__(self, mojom_name, **kwargs):
> - assert _IsStrOrUnicode(mojom_name)
> + assert isinstance(mojom_name, str)
> NodeBase.__init__(self, **kwargs)
> self.mojom_name = mojom_name
>
> @@ -108,13 +105,13 @@ class Attribute(NodeBase):
> """Represents an attribute."""
>
> def __init__(self, key, value, **kwargs):
> - assert _IsStrOrUnicode(key)
> - super(Attribute, self).__init__(**kwargs)
> + assert isinstance(key, str)
> + super().__init__(**kwargs)
> self.key = key
> self.value = value
>
> def __eq__(self, other):
> - return super(Attribute, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.key == other.key and \
> self.value == other.value
>
> @@ -131,17 +128,17 @@ class Const(Definition):
> def __init__(self, mojom_name, attribute_list, typename, value, **kwargs):
> assert attribute_list is None or isinstance(attribute_list, AttributeList)
> # The typename is currently passed through as a string.
> - assert _IsStrOrUnicode(typename)
> + assert isinstance(typename, str)
> # The value is either a literal (currently passed through as a string) or a
> # "wrapped identifier".
> - assert _IsStrOrUnicode or isinstance(value, tuple)
> - super(Const, self).__init__(mojom_name, **kwargs)
> + assert isinstance(value, (tuple, str))
> + super().__init__(mojom_name, **kwargs)
> self.attribute_list = attribute_list
> self.typename = typename
> self.value = value
>
> def __eq__(self, other):
> - return super(Const, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.attribute_list == other.attribute_list and \
> self.typename == other.typename and \
> self.value == other.value
> @@ -153,12 +150,12 @@ class Enum(Definition):
> def __init__(self, mojom_name, attribute_list, enum_value_list, **kwargs):
> assert attribute_list is None or isinstance(attribute_list, AttributeList)
> assert enum_value_list is None or isinstance(enum_value_list, EnumValueList)
> - super(Enum, self).__init__(mojom_name, **kwargs)
> + super().__init__(mojom_name, **kwargs)
> self.attribute_list = attribute_list
> self.enum_value_list = enum_value_list
>
> def __eq__(self, other):
> - return super(Enum, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.attribute_list == other.attribute_list and \
> self.enum_value_list == other.enum_value_list
>
> @@ -170,13 +167,13 @@ class EnumValue(Definition):
> # The optional value is either an int (which is current a string) or a
> # "wrapped identifier".
> assert attribute_list is None or isinstance(attribute_list, AttributeList)
> - assert value is None or _IsStrOrUnicode(value) or isinstance(value, tuple)
> - super(EnumValue, self).__init__(mojom_name, **kwargs)
> + assert value is None or isinstance(value, (tuple, str))
> + super().__init__(mojom_name, **kwargs)
> self.attribute_list = attribute_list
> self.value = value
>
> def __eq__(self, other):
> - return super(EnumValue, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.attribute_list == other.attribute_list and \
> self.value == other.value
>
> @@ -188,18 +185,47 @@ class EnumValueList(NodeListBase):
> _list_item_type = EnumValue
>
>
> +class Feature(Definition):
> + """Represents a runtime feature definition."""
> + def __init__(self, mojom_name, attribute_list, body, **kwargs):
> + assert attribute_list is None or isinstance(attribute_list, AttributeList)
> + assert isinstance(body, FeatureBody) or body is None
> + super().__init__(mojom_name, **kwargs)
> + self.attribute_list = attribute_list
> + self.body = body
> +
> + def __eq__(self, other):
> + return super().__eq__(other) and \
> + self.attribute_list == other.attribute_list and \
> + self.body == other.body
> +
> + def __repr__(self):
> + return "Feature(mojom_name = %s, attribute_list = %s, body = %s)" % (
> + self.mojom_name, self.attribute_list, self.body)
> +
> +
> +# This needs to be declared after `FeatureConst` and `FeatureField`.
> +class FeatureBody(NodeListBase):
> + """Represents the body of (i.e., list of definitions inside) a feature."""
> +
> + # Features are compile time helpers so all fields are initializers/consts
> + # for the underlying platform feature type.
> + _list_item_type = (Const)
> +
> +
> class Import(NodeBase):
> """Represents an import statement."""
>
> def __init__(self, attribute_list, import_filename, **kwargs):
> assert attribute_list is None or isinstance(attribute_list, AttributeList)
> - assert _IsStrOrUnicode(import_filename)
> - super(Import, self).__init__(**kwargs)
> + assert isinstance(import_filename, str)
> + super().__init__(**kwargs)
> self.attribute_list = attribute_list
> - self.import_filename = import_filename
> + # TODO(crbug.com/953884): Use pathlib once we're migrated fully to Python 3.
> + self.import_filename = os.path.normpath(import_filename).replace('\\', '/')
>
> def __eq__(self, other):
> - return super(Import, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.attribute_list == other.attribute_list and \
> self.import_filename == other.import_filename
>
> @@ -216,12 +242,12 @@ class Interface(Definition):
> def __init__(self, mojom_name, attribute_list, body, **kwargs):
> assert attribute_list is None or isinstance(attribute_list, AttributeList)
> assert isinstance(body, InterfaceBody)
> - super(Interface, self).__init__(mojom_name, **kwargs)
> + super().__init__(mojom_name, **kwargs)
> self.attribute_list = attribute_list
> self.body = body
>
> def __eq__(self, other):
> - return super(Interface, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.attribute_list == other.attribute_list and \
> self.body == other.body
>
> @@ -236,14 +262,14 @@ class Method(Definition):
> assert isinstance(parameter_list, ParameterList)
> assert response_parameter_list is None or \
> isinstance(response_parameter_list, ParameterList)
> - super(Method, self).__init__(mojom_name, **kwargs)
> + super().__init__(mojom_name, **kwargs)
> self.attribute_list = attribute_list
> self.ordinal = ordinal
> self.parameter_list = parameter_list
> self.response_parameter_list = response_parameter_list
>
> def __eq__(self, other):
> - return super(Method, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.attribute_list == other.attribute_list and \
> self.ordinal == other.ordinal and \
> self.parameter_list == other.parameter_list and \
> @@ -264,12 +290,12 @@ class Module(NodeBase):
> # |mojom_namespace| is either none or a "wrapped identifier".
> assert mojom_namespace is None or isinstance(mojom_namespace, tuple)
> assert attribute_list is None or isinstance(attribute_list, AttributeList)
> - super(Module, self).__init__(**kwargs)
> + super().__init__(**kwargs)
> self.mojom_namespace = mojom_namespace
> self.attribute_list = attribute_list
>
> def __eq__(self, other):
> - return super(Module, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.mojom_namespace == other.mojom_namespace and \
> self.attribute_list == other.attribute_list
>
> @@ -281,13 +307,13 @@ class Mojom(NodeBase):
> assert module is None or isinstance(module, Module)
> assert isinstance(import_list, ImportList)
> assert isinstance(definition_list, list)
> - super(Mojom, self).__init__(**kwargs)
> + super().__init__(**kwargs)
> self.module = module
> self.import_list = import_list
> self.definition_list = definition_list
>
> def __eq__(self, other):
> - return super(Mojom, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.module == other.module and \
> self.import_list == other.import_list and \
> self.definition_list == other.definition_list
> @@ -302,11 +328,11 @@ class Ordinal(NodeBase):
>
> def __init__(self, value, **kwargs):
> assert isinstance(value, int)
> - super(Ordinal, self).__init__(**kwargs)
> + super().__init__(**kwargs)
> self.value = value
>
> def __eq__(self, other):
> - return super(Ordinal, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.value == other.value
>
>
> @@ -314,18 +340,18 @@ class Parameter(NodeBase):
> """Represents a method request or response parameter."""
>
> def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
> - assert _IsStrOrUnicode(mojom_name)
> + assert isinstance(mojom_name, str)
> assert attribute_list is None or isinstance(attribute_list, AttributeList)
> assert ordinal is None or isinstance(ordinal, Ordinal)
> - assert _IsStrOrUnicode(typename)
> - super(Parameter, self).__init__(**kwargs)
> + assert isinstance(typename, str)
> + super().__init__(**kwargs)
> self.mojom_name = mojom_name
> self.attribute_list = attribute_list
> self.ordinal = ordinal
> self.typename = typename
>
> def __eq__(self, other):
> - return super(Parameter, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.mojom_name == other.mojom_name and \
> self.attribute_list == other.attribute_list and \
> self.ordinal == other.ordinal and \
> @@ -344,42 +370,51 @@ class Struct(Definition):
> def __init__(self, mojom_name, attribute_list, body, **kwargs):
> assert attribute_list is None or isinstance(attribute_list, AttributeList)
> assert isinstance(body, StructBody) or body is None
> - super(Struct, self).__init__(mojom_name, **kwargs)
> + super().__init__(mojom_name, **kwargs)
> self.attribute_list = attribute_list
> self.body = body
>
> def __eq__(self, other):
> - return super(Struct, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.attribute_list == other.attribute_list and \
> self.body == other.body
>
> + def __repr__(self):
> + return "Struct(mojom_name = %s, attribute_list = %s, body = %s)" % (
> + self.mojom_name, self.attribute_list, self.body)
> +
>
> class StructField(Definition):
> """Represents a struct field definition."""
>
> def __init__(self, mojom_name, attribute_list, ordinal, typename,
> default_value, **kwargs):
> - assert _IsStrOrUnicode(mojom_name)
> + assert isinstance(mojom_name, str)
> assert attribute_list is None or isinstance(attribute_list, AttributeList)
> assert ordinal is None or isinstance(ordinal, Ordinal)
> - assert _IsStrOrUnicode(typename)
> + assert isinstance(typename, str)
> # The optional default value is currently either a value as a string or a
> # "wrapped identifier".
> - assert default_value is None or _IsStrOrUnicode(default_value) or \
> - isinstance(default_value, tuple)
> - super(StructField, self).__init__(mojom_name, **kwargs)
> + assert default_value is None or isinstance(default_value, (str, tuple))
> + super().__init__(mojom_name, **kwargs)
> self.attribute_list = attribute_list
> self.ordinal = ordinal
> self.typename = typename
> self.default_value = default_value
>
> def __eq__(self, other):
> - return super(StructField, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.attribute_list == other.attribute_list and \
> self.ordinal == other.ordinal and \
> self.typename == other.typename and \
> self.default_value == other.default_value
>
> + def __repr__(self):
> + return ("StructField(mojom_name = %s, attribute_list = %s, ordinal = %s, "
> + "typename = %s, default_value = %s") % (
> + self.mojom_name, self.attribute_list, self.ordinal,
> + self.typename, self.default_value)
> +
>
> # This needs to be declared after |StructField|.
> class StructBody(NodeListBase):
> @@ -394,29 +429,29 @@ class Union(Definition):
> def __init__(self, mojom_name, attribute_list, body, **kwargs):
> assert attribute_list is None or isinstance(attribute_list, AttributeList)
> assert isinstance(body, UnionBody)
> - super(Union, self).__init__(mojom_name, **kwargs)
> + super().__init__(mojom_name, **kwargs)
> self.attribute_list = attribute_list
> self.body = body
>
> def __eq__(self, other):
> - return super(Union, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.attribute_list == other.attribute_list and \
> self.body == other.body
>
>
> class UnionField(Definition):
> def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
> - assert _IsStrOrUnicode(mojom_name)
> + assert isinstance(mojom_name, str)
> assert attribute_list is None or isinstance(attribute_list, AttributeList)
> assert ordinal is None or isinstance(ordinal, Ordinal)
> - assert _IsStrOrUnicode(typename)
> - super(UnionField, self).__init__(mojom_name, **kwargs)
> + assert isinstance(typename, str)
> + super().__init__(mojom_name, **kwargs)
> self.attribute_list = attribute_list
> self.ordinal = ordinal
> self.typename = typename
>
> def __eq__(self, other):
> - return super(UnionField, self).__eq__(other) and \
> + return super().__eq__(other) and \
> self.attribute_list == other.attribute_list and \
> self.ordinal == other.ordinal and \
> self.typename == other.typename
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
> index 62798631dbce..b289f7b11f66 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
> @@ -1,32 +1,26 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# 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 imp
> -import os.path
> -import sys
> import unittest
>
> from mojom.parse import ast
>
> -
> class _TestNode(ast.NodeBase):
> """Node type for tests."""
>
> def __init__(self, value, **kwargs):
> - super(_TestNode, self).__init__(**kwargs)
> + super().__init__(**kwargs)
> self.value = value
>
> def __eq__(self, other):
> - return super(_TestNode, self).__eq__(other) and self.value == other.value
> -
> + return super().__eq__(other) and self.value == other.value
>
> class _TestNodeList(ast.NodeListBase):
> """Node list type for tests."""
>
> _list_item_type = _TestNode
>
> -
> class ASTTest(unittest.TestCase):
> """Tests various AST classes."""
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py
> index 3cb73c5d708c..9687edbfab1c 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py
> @@ -1,4 +1,4 @@
> -# Copyright 2018 The Chromium Authors. All rights reserved.
> +# 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.
> """Helpers for processing conditionally enabled features in a mojom."""
> @@ -17,8 +17,10 @@ class EnableIfError(Error):
> def _IsEnabled(definition, enabled_features):
> """Returns true if a definition is enabled.
>
> - A definition is enabled if it has no EnableIf attribute, or if the value of
> - the EnableIf attribute is in enabled_features.
> + A definition is enabled if it has no EnableIf/EnableIfNot attribute.
> + It is retained if it has an EnableIf attribute and the attribute is in
> + enabled_features. It is retained if it has an EnableIfNot attribute and the
> + attribute is not in enabled features.
> """
> if not hasattr(definition, "attribute_list"):
> return True
> @@ -27,17 +29,19 @@ def _IsEnabled(definition, enabled_features):
>
> already_defined = False
> for a in definition.attribute_list:
> - if a.key == 'EnableIf':
> + if a.key == 'EnableIf' or a.key == 'EnableIfNot':
> if already_defined:
> raise EnableIfError(
> definition.filename,
> - "EnableIf attribute may only be defined once per field.",
> + "EnableIf/EnableIfNot attribute may only be set once per field.",
> definition.lineno)
> already_defined = True
>
> for attribute in definition.attribute_list:
> if attribute.key == 'EnableIf' and attribute.value not in enabled_features:
> return False
> + if attribute.key == 'EnableIfNot' and attribute.value in enabled_features:
> + return False
> return True
>
>
> @@ -56,15 +60,12 @@ def _FilterDefinition(definition, enabled_features):
> """Filters definitions with a body."""
> if isinstance(definition, ast.Enum):
> _FilterDisabledFromNodeList(definition.enum_value_list, enabled_features)
> - elif isinstance(definition, ast.Interface):
> - _FilterDisabledFromNodeList(definition.body, enabled_features)
> elif isinstance(definition, ast.Method):
> _FilterDisabledFromNodeList(definition.parameter_list, enabled_features)
> _FilterDisabledFromNodeList(definition.response_parameter_list,
> enabled_features)
> - elif isinstance(definition, ast.Struct):
> - _FilterDisabledFromNodeList(definition.body, enabled_features)
> - elif isinstance(definition, ast.Union):
> + elif isinstance(definition,
> + (ast.Interface, ast.Struct, ast.Union, ast.Feature)):
> _FilterDisabledFromNodeList(definition.body, enabled_features)
>
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
> index aa609be73837..cca1764b1cd5 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
> @@ -1,13 +1,12 @@
> -# Copyright 2018 The Chromium Authors. All rights reserved.
> +# 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.
>
> -import imp
> +import importlib.util
> import os
> import sys
> import unittest
>
> -
> def _GetDirAbove(dirname):
> """Returns the directory "above" this file containing |dirname| (which must
> also be "above" this file)."""
> @@ -18,9 +17,8 @@ def _GetDirAbove(dirname):
> if tail == dirname:
> return path
>
> -
> try:
> - imp.find_module('mojom')
> + importlib.util.find_spec("mojom")
> except ImportError:
> sys.path.append(os.path.join(_GetDirAbove('pylib'), 'pylib'))
> import mojom.parse.ast as ast
> @@ -29,7 +27,6 @@ import mojom.parse.parser as parser
>
> ENABLED_FEATURES = frozenset({'red', 'green', 'blue'})
>
> -
> class ConditionalFeaturesTest(unittest.TestCase):
> """Tests |mojom.parse.conditional_features|."""
>
> @@ -55,6 +52,48 @@ class ConditionalFeaturesTest(unittest.TestCase):
> """
> self.parseAndAssertEqual(const_source, expected_source)
>
> + def testFilterIfNotConst(self):
> + """Test that Consts are correctly filtered."""
> + const_source = """
> + [EnableIfNot=blue]
> + const int kMyConst1 = 1;
> + [EnableIfNot=orange]
> + const double kMyConst2 = 2;
> + [EnableIf=blue]
> + const int kMyConst3 = 3;
> + [EnableIfNot=blue]
> + const int kMyConst4 = 4;
> + [EnableIfNot=purple]
> + const int kMyConst5 = 5;
> + """
> + expected_source = """
> + [EnableIfNot=orange]
> + const double kMyConst2 = 2;
> + [EnableIf=blue]
> + const int kMyConst3 = 3;
> + [EnableIfNot=purple]
> + const int kMyConst5 = 5;
> + """
> + self.parseAndAssertEqual(const_source, expected_source)
> +
> + def testFilterIfNotMultipleConst(self):
> + """Test that Consts are correctly filtered."""
> + const_source = """
> + [EnableIfNot=blue]
> + const int kMyConst1 = 1;
> + [EnableIfNot=orange]
> + const double kMyConst2 = 2;
> + [EnableIfNot=orange]
> + const int kMyConst3 = 3;
> + """
> + expected_source = """
> + [EnableIfNot=orange]
> + const double kMyConst2 = 2;
> + [EnableIfNot=orange]
> + const int kMyConst3 = 3;
> + """
> + self.parseAndAssertEqual(const_source, expected_source)
> +
> def testFilterEnum(self):
> """Test that EnumValues are correctly filtered from an Enum."""
> enum_source = """
> @@ -91,6 +130,24 @@ class ConditionalFeaturesTest(unittest.TestCase):
> """
> self.parseAndAssertEqual(import_source, expected_source)
>
> + def testFilterIfNotImport(self):
> + """Test that imports are correctly filtered from a Mojom."""
> + import_source = """
> + [EnableIf=blue]
> + import "foo.mojom";
> + [EnableIfNot=purple]
> + import "bar.mojom";
> + [EnableIfNot=green]
> + import "baz.mojom";
> + """
> + expected_source = """
> + [EnableIf=blue]
> + import "foo.mojom";
> + [EnableIfNot=purple]
> + import "bar.mojom";
> + """
> + self.parseAndAssertEqual(import_source, expected_source)
> +
> def testFilterInterface(self):
> """Test that definitions are correctly filtered from an Interface."""
> interface_source = """
> @@ -175,6 +232,50 @@ class ConditionalFeaturesTest(unittest.TestCase):
> """
> self.parseAndAssertEqual(struct_source, expected_source)
>
> + def testFilterIfNotStruct(self):
> + """Test that definitions are correctly filtered from a Struct."""
> + struct_source = """
> + struct MyStruct {
> + [EnableIf=blue]
> + enum MyEnum {
> + VALUE1,
> + [EnableIfNot=red]
> + VALUE2,
> + };
> + [EnableIfNot=yellow]
> + const double kMyConst = 1.23;
> + [EnableIf=green]
> + int32 a;
> + double b;
> + [EnableIfNot=purple]
> + int32 c;
> + [EnableIf=blue]
> + double d;
> + int32 e;
> + [EnableIfNot=red]
> + double f;
> + };
> + """
> + expected_source = """
> + struct MyStruct {
> + [EnableIf=blue]
> + enum MyEnum {
> + VALUE1,
> + };
> + [EnableIfNot=yellow]
> + const double kMyConst = 1.23;
> + [EnableIf=green]
> + int32 a;
> + double b;
> + [EnableIfNot=purple]
> + int32 c;
> + [EnableIf=blue]
> + double d;
> + int32 e;
> + };
> + """
> + self.parseAndAssertEqual(struct_source, expected_source)
> +
> def testFilterUnion(self):
> """Test that UnionFields are correctly filtered from a Union."""
> union_source = """
> @@ -216,6 +317,25 @@ class ConditionalFeaturesTest(unittest.TestCase):
> """
> self.parseAndAssertEqual(mojom_source, expected_source)
>
> + def testFeaturesWithEnableIf(self):
> + mojom_source = """
> + feature Foo {
> + const string name = "FooFeature";
> + [EnableIf=red]
> + const bool default_state = false;
> + [EnableIf=yellow]
> + const bool default_state = true;
> + };
> + """
> + expected_source = """
> + feature Foo {
> + const string name = "FooFeature";
> + [EnableIf=red]
> + const bool default_state = false;
> + };
> + """
> + self.parseAndAssertEqual(mojom_source, expected_source)
> +
> def testMultipleEnableIfs(self):
> source = """
> enum Foo {
> @@ -228,6 +348,29 @@ class ConditionalFeaturesTest(unittest.TestCase):
> conditional_features.RemoveDisabledDefinitions,
> definition, ENABLED_FEATURES)
>
> + def testMultipleEnableIfs(self):
> + source = """
> + enum Foo {
> + [EnableIf=red,EnableIfNot=yellow]
> + kBarValue = 5,
> + };
> + """
> + definition = parser.Parse(source, "my_file.mojom")
> + self.assertRaises(conditional_features.EnableIfError,
> + conditional_features.RemoveDisabledDefinitions,
> + definition, ENABLED_FEATURES)
> +
> + def testMultipleEnableIfs(self):
> + source = """
> + enum Foo {
> + [EnableIfNot=red,EnableIfNot=yellow]
> + kBarValue = 5,
> + };
> + """
> + definition = parser.Parse(source, "my_file.mojom")
> + self.assertRaises(conditional_features.EnableIfError,
> + conditional_features.RemoveDisabledDefinitions,
> + definition, ENABLED_FEATURES)
>
> if __name__ == '__main__':
> unittest.main()
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
> index 3e084bbf22b7..00136a8bf94a 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
> @@ -1,8 +1,7 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# 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 imp
> import os.path
> import sys
>
> @@ -22,7 +21,7 @@ class LexError(Error):
>
> # We have methods which look like they could be functions:
> # pylint: disable=R0201
> -class Lexer(object):
> +class Lexer:
> def __init__(self, filename):
> self.filename = filename
>
> @@ -56,6 +55,7 @@ class Lexer(object):
> 'PENDING_RECEIVER',
> 'PENDING_ASSOCIATED_REMOTE',
> 'PENDING_ASSOCIATED_RECEIVER',
> + 'FEATURE',
> )
>
> keyword_map = {}
> @@ -81,7 +81,6 @@ class Lexer(object):
> # Operators
> 'MINUS',
> 'PLUS',
> - 'AMP',
> 'QSTN',
>
> # Assignment
> @@ -168,7 +167,6 @@ class Lexer(object):
> # Operators
> t_MINUS = r'-'
> t_PLUS = r'\+'
> - t_AMP = r'&'
> t_QSTN = r'\?'
>
> # =
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
> index eadc6587cf94..bc9f8354316f 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
> @@ -1,13 +1,12 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# 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 imp
> +import importlib.util
> import os.path
> import sys
> import unittest
>
> -
> def _GetDirAbove(dirname):
> """Returns the directory "above" this file containing |dirname| (which must
> also be "above" this file)."""
> @@ -18,17 +17,15 @@ def _GetDirAbove(dirname):
> if tail == dirname:
> return path
>
> -
> sys.path.insert(1, os.path.join(_GetDirAbove("mojo"), "third_party"))
> from ply import lex
>
> try:
> - imp.find_module("mojom")
> + importlib.util.find_spec("mojom")
> except ImportError:
> sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
> import mojom.parse.lexer
>
> -
> # This (monkey-patching LexToken to make comparison value-based) is evil, but
> # we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing
> # for object identity.)
> @@ -146,7 +143,6 @@ class LexerTest(unittest.TestCase):
> self._SingleTokenForInput("+"), _MakeLexToken("PLUS", "+"))
> self.assertEquals(
> self._SingleTokenForInput("-"), _MakeLexToken("MINUS", "-"))
> - self.assertEquals(self._SingleTokenForInput("&"), _MakeLexToken("AMP", "&"))
> self.assertEquals(
> self._SingleTokenForInput("?"), _MakeLexToken("QSTN", "?"))
> self.assertEquals(
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
> index b3b803d6f334..1dffd98b92a6 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
> @@ -1,8 +1,11 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# 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.
> """Generates a syntax tree from a Mojo IDL file."""
>
> +# Breaking parser stanzas is unhelpful so allow longer lines.
> +# pylint: disable=line-too-long
> +
> import os.path
> import sys
>
> @@ -33,7 +36,7 @@ class ParseError(Error):
>
> # We have methods which look like they could be functions:
> # pylint: disable=R0201
> -class Parser(object):
> +class Parser:
> def __init__(self, lexer, source, filename):
> self.tokens = lexer.tokens
> self.source = source
> @@ -111,7 +114,8 @@ class Parser(object):
> | union
> | interface
> | enum
> - | const"""
> + | const
> + | feature"""
> p[0] = p[1]
>
> def p_attribute_section_1(self, p):
> @@ -140,12 +144,19 @@ class Parser(object):
> p[0].Append(p[3])
>
> def p_attribute_1(self, p):
> - """attribute : NAME EQUALS evaled_literal
> - | NAME EQUALS NAME"""
> - p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))
> + """attribute : name_wrapped EQUALS identifier_wrapped"""
> + p[0] = ast.Attribute(p[1],
> + p[3][1],
> + filename=self.filename,
> + lineno=p.lineno(1))
>
> def p_attribute_2(self, p):
> - """attribute : NAME"""
> + """attribute : name_wrapped EQUALS evaled_literal
> + | name_wrapped EQUALS name_wrapped"""
> + p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))
> +
> + def p_attribute_3(self, p):
> + """attribute : name_wrapped"""
> p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1))
>
> def p_evaled_literal(self, p):
> @@ -161,11 +172,11 @@ class Parser(object):
> p[0] = eval(p[1])
>
> def p_struct_1(self, p):
> - """struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI"""
> + """struct : attribute_section STRUCT name_wrapped LBRACE struct_body RBRACE SEMI"""
> p[0] = ast.Struct(p[3], p[1], p[5])
>
> def p_struct_2(self, p):
> - """struct : attribute_section STRUCT NAME SEMI"""
> + """struct : attribute_section STRUCT name_wrapped SEMI"""
> p[0] = ast.Struct(p[3], p[1], None)
>
> def p_struct_body_1(self, p):
> @@ -180,11 +191,24 @@ class Parser(object):
> p[0].Append(p[2])
>
> def p_struct_field(self, p):
> - """struct_field : attribute_section typename NAME ordinal default SEMI"""
> + """struct_field : attribute_section typename name_wrapped ordinal default SEMI"""
> p[0] = ast.StructField(p[3], p[1], p[4], p[2], p[5])
>
> + def p_feature(self, p):
> + """feature : attribute_section FEATURE NAME LBRACE feature_body RBRACE SEMI"""
> + p[0] = ast.Feature(p[3], p[1], p[5])
> +
> + def p_feature_body_1(self, p):
> + """feature_body : """
> + p[0] = ast.FeatureBody()
> +
> + def p_feature_body_2(self, p):
> + """feature_body : feature_body const"""
> + p[0] = p[1]
> + p[0].Append(p[2])
> +
> def p_union(self, p):
> - """union : attribute_section UNION NAME LBRACE union_body RBRACE SEMI"""
> + """union : attribute_section UNION name_wrapped LBRACE union_body RBRACE SEMI"""
> p[0] = ast.Union(p[3], p[1], p[5])
>
> def p_union_body_1(self, p):
> @@ -197,7 +221,7 @@ class Parser(object):
> p[1].Append(p[2])
>
> def p_union_field(self, p):
> - """union_field : attribute_section typename NAME ordinal SEMI"""
> + """union_field : attribute_section typename name_wrapped ordinal SEMI"""
> p[0] = ast.UnionField(p[3], p[1], p[4], p[2])
>
> def p_default_1(self, p):
> @@ -209,8 +233,7 @@ class Parser(object):
> p[0] = p[2]
>
> def p_interface(self, p):
> - """interface : attribute_section INTERFACE NAME LBRACE interface_body \
> - RBRACE SEMI"""
> + """interface : attribute_section INTERFACE name_wrapped LBRACE interface_body RBRACE SEMI"""
> p[0] = ast.Interface(p[3], p[1], p[5])
>
> def p_interface_body_1(self, p):
> @@ -233,8 +256,7 @@ class Parser(object):
> p[0] = p[3]
>
> def p_method(self, p):
> - """method : attribute_section NAME ordinal LPAREN parameter_list RPAREN \
> - response SEMI"""
> + """method : attribute_section name_wrapped ordinal LPAREN parameter_list RPAREN response SEMI"""
> p[0] = ast.Method(p[2], p[1], p[3], p[5], p[7])
>
> def p_parameter_list_1(self, p):
> @@ -255,7 +277,7 @@ class Parser(object):
> p[0].Append(p[3])
>
> def p_parameter(self, p):
> - """parameter : attribute_section typename NAME ordinal"""
> + """parameter : attribute_section typename name_wrapped ordinal"""
> p[0] = ast.Parameter(
> p[3], p[1], p[4], p[2], filename=self.filename, lineno=p.lineno(3))
>
> @@ -271,8 +293,7 @@ class Parser(object):
> """nonnullable_typename : basictypename
> | array
> | fixed_array
> - | associative_array
> - | interfacerequest"""
> + | associative_array"""
> p[0] = p[1]
>
> def p_basictypename(self, p):
> @@ -297,18 +318,16 @@ class Parser(object):
> p[0] = "rcv<%s>" % p[3]
>
> def p_associatedremotetype(self, p):
> - """associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier \
> - RANGLE"""
> + """associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier RANGLE"""
> p[0] = "rma<%s>" % p[3]
>
> def p_associatedreceivertype(self, p):
> - """associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier \
> - RANGLE"""
> + """associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier RANGLE"""
> p[0] = "rca<%s>" % p[3]
>
> def p_handletype(self, p):
> """handletype : HANDLE
> - | HANDLE LANGLE NAME RANGLE"""
> + | HANDLE LANGLE name_wrapped RANGLE"""
> if len(p) == 2:
> p[0] = p[1]
> else:
> @@ -342,14 +361,6 @@ class Parser(object):
> """associative_array : MAP LANGLE identifier COMMA typename RANGLE"""
> p[0] = p[5] + "{" + p[3] + "}"
>
> - def p_interfacerequest(self, p):
> - """interfacerequest : identifier AMP
> - | ASSOCIATED identifier AMP"""
> - if len(p) == 3:
> - p[0] = p[1] + "&"
> - else:
> - p[0] = "asso<" + p[2] + "&>"
> -
> def p_ordinal_1(self, p):
> """ordinal : """
> p[0] = None
> @@ -366,15 +377,14 @@ class Parser(object):
> p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1))
>
> def p_enum_1(self, p):
> - """enum : attribute_section ENUM NAME LBRACE enum_value_list \
> - RBRACE SEMI
> - | attribute_section ENUM NAME LBRACE nonempty_enum_value_list \
> - COMMA RBRACE SEMI"""
> + """enum : attribute_section ENUM name_wrapped LBRACE enum_value_list RBRACE SEMI
> + | attribute_section ENUM name_wrapped LBRACE \
> + nonempty_enum_value_list COMMA RBRACE SEMI"""
> p[0] = ast.Enum(
> p[3], p[1], p[5], filename=self.filename, lineno=p.lineno(2))
>
> def p_enum_2(self, p):
> - """enum : attribute_section ENUM NAME SEMI"""
> + """enum : attribute_section ENUM name_wrapped SEMI"""
> p[0] = ast.Enum(
> p[3], p[1], None, filename=self.filename, lineno=p.lineno(2))
>
> @@ -396,9 +406,9 @@ class Parser(object):
> p[0].Append(p[3])
>
> def p_enum_value(self, p):
> - """enum_value : attribute_section NAME
> - | attribute_section NAME EQUALS int
> - | attribute_section NAME EQUALS identifier_wrapped"""
> + """enum_value : attribute_section name_wrapped
> + | attribute_section name_wrapped EQUALS int
> + | attribute_section name_wrapped EQUALS identifier_wrapped"""
> p[0] = ast.EnumValue(
> p[2],
> p[1],
> @@ -407,7 +417,7 @@ class Parser(object):
> lineno=p.lineno(2))
>
> def p_const(self, p):
> - """const : attribute_section CONST typename NAME EQUALS constant SEMI"""
> + """const : attribute_section CONST typename name_wrapped EQUALS constant SEMI"""
> p[0] = ast.Const(p[4], p[1], p[3], p[6])
>
> def p_constant(self, p):
> @@ -422,10 +432,16 @@ class Parser(object):
> # TODO(vtl): Make this produce a "wrapped" identifier (probably as an
> # |ast.Identifier|, to be added) and get rid of identifier_wrapped.
> def p_identifier(self, p):
> - """identifier : NAME
> - | NAME DOT identifier"""
> + """identifier : name_wrapped
> + | name_wrapped DOT identifier"""
> p[0] = ''.join(p[1:])
>
> + # Allow 'feature' to be a name literal not just a keyword.
> + def p_name_wrapped(self, p):
> + """name_wrapped : NAME
> + | FEATURE"""
> + p[0] = p[1]
> +
> def p_literal(self, p):
> """literal : int
> | float
> @@ -458,6 +474,12 @@ class Parser(object):
> # TODO(vtl): Can we figure out what's missing?
> raise ParseError(self.filename, "Unexpected end of file")
>
> + if e.value == 'feature':
> + raise ParseError(self.filename,
> + "`feature` is reserved for a future mojom keyword",
> + lineno=e.lineno,
> + snippet=self._GetSnippet(e.lineno))
> +
> raise ParseError(
> self.filename,
> "Unexpected %r:" % e.value,
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
> index 6d6b71532410..0a26307b1a32 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
> @@ -1,17 +1,13 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# 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 imp
> -import os.path
> -import sys
> import unittest
>
> from mojom.parse import ast
> from mojom.parse import lexer
> from mojom.parse import parser
>
> -
> class ParserTest(unittest.TestCase):
> """Tests |parser.Parse()|."""
>
> @@ -1086,7 +1082,7 @@ class ParserTest(unittest.TestCase):
> handle<data_pipe_producer>? k;
> handle<message_pipe>? l;
> handle<shared_buffer>? m;
> - some_interface&? n;
> + pending_receiver<some_interface>? n;
> handle<platform>? o;
> };
> """
> @@ -1110,7 +1106,7 @@ class ParserTest(unittest.TestCase):
> ast.StructField('l', None, None, 'handle<message_pipe>?', None),
> ast.StructField('m', None, None, 'handle<shared_buffer>?',
> None),
> - ast.StructField('n', None, None, 'some_interface&?', None),
> + ast.StructField('n', None, None, 'rcv<some_interface>?', None),
> ast.StructField('o', None, None, 'handle<platform>?', None)
> ]))
> ])
> @@ -1138,16 +1134,6 @@ class ParserTest(unittest.TestCase):
> r" *handle\?<data_pipe_consumer> a;$"):
> parser.Parse(source2, "my_file.mojom")
>
> - source3 = """\
> - struct MyStruct {
> - some_interface?& a;
> - };
> - """
> - with self.assertRaisesRegexp(
> - parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '&':\n"
> - r" *some_interface\?& a;$"):
> - parser.Parse(source3, "my_file.mojom")
> -
> def testSimpleUnion(self):
> """Tests a simple .mojom source that just defines a union."""
> source = """\
> @@ -1317,9 +1303,9 @@ class ParserTest(unittest.TestCase):
> source1 = """\
> struct MyStruct {
> associated MyInterface a;
> - associated MyInterface& b;
> + pending_associated_receiver<MyInterface> b;
> associated MyInterface? c;
> - associated MyInterface&? d;
> + pending_associated_receiver<MyInterface>? d;
> };
> """
> expected1 = ast.Mojom(None, ast.ImportList(), [
> @@ -1327,16 +1313,16 @@ class ParserTest(unittest.TestCase):
> 'MyStruct', None,
> ast.StructBody([
> ast.StructField('a', None, None, 'asso<MyInterface>', None),
> - ast.StructField('b', None, None, 'asso<MyInterface&>', None),
> + ast.StructField('b', None, None, 'rca<MyInterface>', None),
> ast.StructField('c', None, None, 'asso<MyInterface>?', None),
> - ast.StructField('d', None, None, 'asso<MyInterface&>?', None)
> + ast.StructField('d', None, None, 'rca<MyInterface>?', None)
> ]))
> ])
> self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
>
> source2 = """\
> interface MyInterface {
> - MyMethod(associated A a) =>(associated B& b);
> + MyMethod(associated A a) =>(pending_associated_receiver<B> b);
> };"""
> expected2 = ast.Mojom(None, ast.ImportList(), [
> ast.Interface(
> @@ -1344,10 +1330,10 @@ class ParserTest(unittest.TestCase):
> ast.InterfaceBody(
> ast.Method(
> 'MyMethod', None, None,
> - ast.ParameterList(
> - ast.Parameter('a', None, None, 'asso<A>')),
> - ast.ParameterList(
> - ast.Parameter('b', None, None, 'asso<B&>')))))
> + ast.ParameterList(ast.Parameter('a', None, None,
> + 'asso<A>')),
> + ast.ParameterList(ast.Parameter('b', None, None,
> + 'rca<B>')))))
> ])
> self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
>
> @@ -1385,6 +1371,5 @@ class ParserTest(unittest.TestCase):
> r" *associated\? MyInterface& a;$"):
> parser.Parse(source3, "my_file.mojom")
>
> -
> if __name__ == "__main__":
> unittest.main()
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
> index eb90c825f9bc..9693090e44ea 100755
> --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
> @@ -1,5 +1,5 @@
> -#!/usr/bin/env python
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +#!/usr/bin/env python3
> +# 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.
> """Parses mojom IDL files.
> @@ -11,6 +11,7 @@ generate usable language bindings.
> """
>
> import argparse
> +import builtins
> import codecs
> import errno
> import json
> @@ -19,6 +20,7 @@ import multiprocessing
> import os
> import os.path
> import sys
> +import traceback
> from collections import defaultdict
>
> from mojom.generate import module
> @@ -28,16 +30,12 @@ from mojom.parse import conditional_features
>
>
> # Disable this for easier debugging.
> -# In Python 2, subprocesses just hang when exceptions are thrown :(.
> -_ENABLE_MULTIPROCESSING = sys.version_info[0] > 2
> +_ENABLE_MULTIPROCESSING = True
>
> -if sys.version_info < (3, 4):
> - _MULTIPROCESSING_USES_FORK = sys.platform.startswith('linux')
> -else:
> - # https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725
> - if __name__ == '__main__' and sys.platform == 'darwin':
> - multiprocessing.set_start_method('fork')
> - _MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'
> +# https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725
> +if __name__ == '__main__' and sys.platform == 'darwin':
> + multiprocessing.set_start_method('fork')
> +_MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'
>
>
> def _ResolveRelativeImportPath(path, roots):
> @@ -63,7 +61,7 @@ def _ResolveRelativeImportPath(path, roots):
> raise ValueError('"%s" does not exist in any of %s' % (path, roots))
>
>
> -def _RebaseAbsolutePath(path, roots):
> +def RebaseAbsolutePath(path, roots):
> """Rewrites an absolute file path as relative to an absolute directory path in
> roots.
>
> @@ -139,7 +137,7 @@ def _EnsureInputLoaded(mojom_abspath, module_path, abs_paths, asts,
> # Already done.
> return
>
> - for dep_abspath, dep_path in dependencies[mojom_abspath]:
> + for dep_abspath, dep_path in sorted(dependencies[mojom_abspath]):
> if dep_abspath not in loaded_modules:
> _EnsureInputLoaded(dep_abspath, dep_path, abs_paths, asts, dependencies,
> loaded_modules, module_metadata)
> @@ -159,11 +157,19 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
>
> def collect(metadata_filename):
> processed_deps.add(metadata_filename)
> +
> + # Paths in the metadata file are relative to the metadata file's dir.
> + metadata_dir = os.path.abspath(os.path.dirname(metadata_filename))
> +
> + def to_abs(s):
> + return os.path.normpath(os.path.join(metadata_dir, s))
> +
> with open(metadata_filename) as f:
> metadata = json.load(f)
> allowed_imports.update(
> - map(os.path.normcase, map(os.path.normpath, metadata['sources'])))
> + [os.path.normcase(to_abs(s)) for s in metadata['sources']])
> for dep_metadata in metadata['deps']:
> + dep_metadata = to_abs(dep_metadata)
> if dep_metadata not in processed_deps:
> collect(dep_metadata)
>
> @@ -172,8 +178,7 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
>
>
> # multiprocessing helper.
> -def _ParseAstHelper(args):
> - mojom_abspath, enabled_features = args
> +def _ParseAstHelper(mojom_abspath, enabled_features):
> with codecs.open(mojom_abspath, encoding='utf-8') as f:
> ast = parser.Parse(f.read(), mojom_abspath)
> conditional_features.RemoveDisabledDefinitions(ast, enabled_features)
> @@ -181,8 +186,7 @@ def _ParseAstHelper(args):
>
>
> # multiprocessing helper.
> -def _SerializeHelper(args):
> - mojom_abspath, mojom_path = args
> +def _SerializeHelper(mojom_abspath, mojom_path):
> module_path = os.path.join(_SerializeHelper.output_root_path,
> _GetModuleFilename(mojom_path))
> module_dir = os.path.dirname(module_path)
> @@ -199,12 +203,33 @@ def _SerializeHelper(args):
> _SerializeHelper.loaded_modules[mojom_abspath].Dump(f)
>
>
> -def _Shard(target_func, args, processes=None):
> - args = list(args)
> +class _ExceptionWrapper:
> + def __init__(self):
> + # Do not capture exception object to ensure pickling works.
> + self.formatted_trace = traceback.format_exc()
> +
> +
> +class _FuncWrapper:
> + """Marshals exceptions and spreads args."""
> +
> + def __init__(self, func):
> + self._func = func
> +
> + def __call__(self, args):
> + # multiprocessing does not gracefully handle excptions.
> + # https://crbug.com/1219044
> + try:
> + return self._func(*args)
> + except: # pylint: disable=bare-except
> + return _ExceptionWrapper()
> +
> +
> +def _Shard(target_func, arg_list, processes=None):
> + arg_list = list(arg_list)
> if processes is None:
> processes = multiprocessing.cpu_count()
> # Seems optimal to have each process perform at least 2 tasks.
> - processes = min(processes, len(args) // 2)
> + processes = min(processes, len(arg_list) // 2)
>
> if sys.platform == 'win32':
> # TODO(crbug.com/1190269) - we can't use more than 56
> @@ -213,13 +238,17 @@ def _Shard(target_func, args, processes=None):
>
> # Don't spin up processes unless there is enough work to merit doing so.
> if not _ENABLE_MULTIPROCESSING or processes < 2:
> - for result in map(target_func, args):
> - yield result
> + for arg_tuple in arg_list:
> + yield target_func(*arg_tuple)
> return
>
> pool = multiprocessing.Pool(processes=processes)
> try:
> - for result in pool.imap_unordered(target_func, args):
> + wrapped_func = _FuncWrapper(target_func)
> + for result in pool.imap_unordered(wrapped_func, arg_list):
> + if isinstance(result, _ExceptionWrapper):
> + sys.stderr.write(result.formatted_trace)
> + sys.exit(1)
> yield result
> finally:
> pool.close()
> @@ -230,6 +259,7 @@ def _Shard(target_func, args, processes=None):
> def _ParseMojoms(mojom_files,
> input_root_paths,
> output_root_path,
> + module_root_paths,
> enabled_features,
> module_metadata,
> allowed_imports=None):
> @@ -245,8 +275,10 @@ def _ParseMojoms(mojom_files,
> are based on the mojom's relative path, rebased onto this path.
> Additionally, the script expects this root to contain already-generated
> modules for any transitive dependencies not listed in mojom_files.
> + module_root_paths: A list of absolute filesystem paths which contain
> + already-generated modules for any non-transitive dependencies.
> enabled_features: A list of enabled feature names, controlling which AST
> - nodes are filtered by [EnableIf] attributes.
> + nodes are filtered by [EnableIf] or [EnableIfNot] attributes.
> module_metadata: A list of 2-tuples representing metadata key-value pairs to
> attach to each compiled module output.
>
> @@ -262,7 +294,7 @@ def _ParseMojoms(mojom_files,
> loaded_modules = {}
> input_dependencies = defaultdict(set)
> mojom_files_to_parse = dict((os.path.normcase(abs_path),
> - _RebaseAbsolutePath(abs_path, input_root_paths))
> + RebaseAbsolutePath(abs_path, input_root_paths))
> for abs_path in mojom_files)
> abs_paths = dict(
> (path, abs_path) for abs_path, path in mojom_files_to_parse.items())
> @@ -274,7 +306,7 @@ def _ParseMojoms(mojom_files,
> loaded_mojom_asts[mojom_abspath] = ast
>
> logging.info('Processing dependencies')
> - for mojom_abspath, ast in loaded_mojom_asts.items():
> + for mojom_abspath, ast in sorted(loaded_mojom_asts.items()):
> invalid_imports = []
> for imp in ast.import_list:
> import_abspath = _ResolveRelativeImportPath(imp.import_filename,
> @@ -295,8 +327,8 @@ def _ParseMojoms(mojom_files,
> # be parsed and have a module file sitting in a corresponding output
> # location.
> module_path = _GetModuleFilename(imp.import_filename)
> - module_abspath = _ResolveRelativeImportPath(module_path,
> - [output_root_path])
> + module_abspath = _ResolveRelativeImportPath(
> + module_path, module_root_paths + [output_root_path])
> with open(module_abspath, 'rb') as module_file:
> loaded_modules[import_abspath] = module.Module.Load(module_file)
>
> @@ -370,6 +402,15 @@ already present in the provided output root.""")
> 'based on the relative input path, rebased onto this root. Note that '
> 'ROOT is also searched for existing modules of any transitive imports '
> 'which were not included in the set of inputs.')
> + arg_parser.add_argument(
> + '--module-root',
> + default=[],
> + action='append',
> + metavar='ROOT',
> + dest='module_root_paths',
> + help='Adds ROOT to the set of root paths to search for existing modules '
> + 'of non-transitive imports. Provided root paths are always searched in '
> + 'order from longest absolute path to shortest.')
> arg_parser.add_argument(
> '--mojoms',
> nargs='+',
> @@ -396,9 +437,9 @@ already present in the provided output root.""")
> help='Enables a named feature when parsing the given mojoms. Features '
> 'are identified by arbitrary string values. Specifying this flag with a '
> 'given FEATURE name will cause the parser to process any syntax elements '
> - 'tagged with an [EnableIf=FEATURE] attribute. If this flag is not '
> - 'provided for a given FEATURE, such tagged elements are discarded by the '
> - 'parser and will not be present in the compiled output.')
> + 'tagged with an [EnableIf=FEATURE] or [EnableIfNot] attribute. If this '
> + 'flag is not provided for a given FEATURE, such tagged elements are '
> + 'discarded by the parser and will not be present in the compiled output.')
> arg_parser.add_argument(
> '--check-imports',
> dest='build_metadata_filename',
> @@ -436,6 +477,7 @@ already present in the provided output root.""")
> mojom_files = list(map(os.path.abspath, args.mojom_files))
> input_roots = list(map(os.path.abspath, args.input_root_paths))
> output_root = os.path.abspath(args.output_root_path)
> + module_roots = list(map(os.path.abspath, args.module_root_paths))
>
> if args.build_metadata_filename:
> allowed_imports = _CollectAllowedImportsFromBuildMetadata(
> @@ -445,13 +487,16 @@ already present in the provided output root.""")
>
> module_metadata = list(
> map(lambda kvp: tuple(kvp.split('=')), args.module_metadata))
> - _ParseMojoms(mojom_files, input_roots, output_root, args.enabled_features,
> - module_metadata, allowed_imports)
> + _ParseMojoms(mojom_files, input_roots, output_root, module_roots,
> + args.enabled_features, module_metadata, allowed_imports)
> logging.info('Finished')
> - # Exit without running GC, which can save multiple seconds due the large
> - # number of object created.
> - os._exit(0)
>
>
> if __name__ == '__main__':
> Run(sys.argv[1:])
> + # 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(0)
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py
> index e213fbfa760f..f0ee6966ac20 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py
> @@ -1,4 +1,4 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# 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.
>
> @@ -20,7 +20,7 @@ class MojomParserTestCase(unittest.TestCase):
> resolution, and module serialization and deserialization."""
>
> def __init__(self, method_name):
> - super(MojomParserTestCase, self).__init__(method_name)
> + super().__init__(method_name)
> self._temp_dir = None
>
> def setUp(self):
> @@ -67,7 +67,7 @@ class MojomParserTestCase(unittest.TestCase):
> self.ParseMojoms([filename])
> m = self.LoadModule(filename)
> definitions = {}
> - for kinds in (m.enums, m.structs, m.unions, m.interfaces):
> + for kinds in (m.enums, m.structs, m.unions, m.interfaces, m.features):
> for kind in kinds:
> definitions[kind.mojom_name] = kind
> return definitions
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
> index a93f34bacb41..353a2b6e2980 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
> @@ -1,7 +1,9 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# 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 json
> +
> from mojom_parser_test_case import MojomParserTestCase
>
>
> @@ -119,15 +121,22 @@ class MojomParserTest(MojomParserTestCase):
> c = 'c.mojom'
> c_metadata = 'out/c.build_metadata'
> self.WriteFile(a_metadata,
> - '{"sources": ["%s"], "deps": []}\n' % self.GetPath(a))
> + json.dumps({
> + "sources": [self.GetPath(a)],
> + "deps": []
> + }))
> self.WriteFile(
> b_metadata,
> - '{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(b),
> - self.GetPath(a_metadata)))
> + json.dumps({
> + "sources": [self.GetPath(b)],
> + "deps": [self.GetPath(a_metadata)]
> + }))
> self.WriteFile(
> c_metadata,
> - '{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(c),
> - self.GetPath(b_metadata)))
> + json.dumps({
> + "sources": [self.GetPath(c)],
> + "deps": [self.GetPath(b_metadata)]
> + }))
> self.WriteFile(a, """\
> module a;
> struct Bar {};""")
> @@ -154,9 +163,15 @@ class MojomParserTest(MojomParserTestCase):
> b = 'b.mojom'
> b_metadata = 'out/b.build_metadata'
> self.WriteFile(a_metadata,
> - '{"sources": ["%s"], "deps": []}\n' % self.GetPath(a))
> + json.dumps({
> + "sources": [self.GetPath(a)],
> + "deps": []
> + }))
> self.WriteFile(b_metadata,
> - '{"sources": ["%s"], "deps": []}\n' % self.GetPath(b))
> + json.dumps({
> + "sources": [self.GetPath(b)],
> + "deps": []
> + }))
> self.WriteFile(a, """\
> module a;
> struct Bar {};""")
> diff --git a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
> index d45ec5862999..d10d69c68b13 100644
> --- a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# 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.
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/union_unittest.py b/utils/ipc/mojo/public/tools/mojom/union_unittest.py
> new file mode 100644
> index 000000000000..6b2525e5c5d0
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/mojom/union_unittest.py
> @@ -0,0 +1,44 @@
> +# 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.
> +
> +from mojom_parser_test_case import MojomParserTestCase
> +
> +
> +class UnionTest(MojomParserTestCase):
> + """Tests union parsing behavior."""
> +
> + def testExtensibleMustHaveDefault(self):
> + """Verifies that extensible unions must have a default field."""
> + mojom = 'foo.mojom'
> + self.WriteFile(mojom, 'module foo; [Extensible] union U { bool x; };')
> + with self.assertRaisesRegexp(Exception, 'must specify a \[Default\]'):
> + self.ParseMojoms([mojom])
> +
> + def testExtensibleSingleDefault(self):
> + """Verifies that extensible unions must not have multiple default fields."""
> + mojom = 'foo.mojom'
> + self.WriteFile(
> + mojom, """\
> + module foo;
> + [Extensible] union U {
> + [Default] bool x;
> + [Default] bool y;
> + };
> + """)
> + with self.assertRaisesRegexp(Exception, 'Multiple \[Default\] fields'):
> + self.ParseMojoms([mojom])
> +
> + def testExtensibleDefaultTypeValid(self):
> + """Verifies that an extensible union's default field must be nullable or
> + integral type."""
> + mojom = 'foo.mojom'
> + self.WriteFile(
> + mojom, """\
> + module foo;
> + [Extensible] union U {
> + [Default] handle<message_pipe> p;
> + };
> + """)
> + with self.assertRaisesRegexp(Exception, 'must be nullable or integral'):
> + self.ParseMojoms([mojom])
> diff --git a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
> index 65db4dc9cf59..45e45ec55e69 100644
> --- a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# 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.
>
> @@ -23,9 +23,12 @@ class VersionCompatibilityTest(MojomParserTestCase):
>
> checker = module.BackwardCompatibilityChecker()
> compatibility_map = {}
> - for name in old.keys():
> - compatibility_map[name] = checker.IsBackwardCompatible(
> - new[name], old[name])
> + for name in old:
> + try:
> + compatibility_map[name] = checker.IsBackwardCompatible(
> + new[name], old[name])
> + except Exception:
> + compatibility_map[name] = False
> return compatibility_map
>
> def assertBackwardCompatible(self, old_mojom, new_mojom):
> @@ -60,40 +63,48 @@ class VersionCompatibilityTest(MojomParserTestCase):
> """Adding a value to an existing version is not allowed, even if the old
> enum was marked [Extensible]. Note that it is irrelevant whether or not the
> new enum is marked [Extensible]."""
> - self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',
> - 'enum E { kFoo, kBar, kBaz };')
> self.assertNotBackwardCompatible(
> - '[Extensible] enum E { kFoo, kBar };',
> - '[Extensible] enum E { kFoo, kBar, kBaz };')
> + '[Extensible] enum E { [Default] kFoo, kBar };',
> + 'enum E { kFoo, kBar, kBaz };')
> self.assertNotBackwardCompatible(
> - '[Extensible] enum E { kFoo, [MinVersion=1] kBar };',
> + '[Extensible] enum E { [Default] kFoo, kBar };',
> + '[Extensible] enum E { [Default] kFoo, kBar, kBaz };')
> + self.assertNotBackwardCompatible(
> + '[Extensible] enum E { [Default] kFoo, [MinVersion=1] kBar };',
> 'enum E { kFoo, [MinVersion=1] kBar, [MinVersion=1] kBaz };')
>
> def testEnumValueRemoval(self):
> """Removal of an enum value is never valid even for [Extensible] enums."""
> self.assertNotBackwardCompatible('enum E { kFoo, kBar };',
> 'enum E { kFoo };')
> - self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',
> - '[Extensible] enum E { kFoo };')
> self.assertNotBackwardCompatible(
> - '[Extensible] enum E { kA, [MinVersion=1] kB };',
> - '[Extensible] enum E { kA, };')
> + '[Extensible] enum E { [Default] kFoo, kBar };',
> + '[Extensible] enum E { [Default] kFoo };')
> self.assertNotBackwardCompatible(
> - '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=1] kZ };',
> - '[Extensible] enum E { kA, [MinVersion=1] kB };')
> + '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',
> + '[Extensible] enum E { [Default] kA, };')
> + self.assertNotBackwardCompatible(
> + """[Extensible] enum E {
> + [Default] kA,
> + [MinVersion=1] kB,
> + [MinVersion=1] kZ };""",
> + '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };')
>
> def testNewExtensibleEnumValueWithMinVersion(self):
> """Adding a new and properly [MinVersion]'d value to an [Extensible] enum
> is a backward-compatible change. Note that it is irrelevant whether or not
> the new enum is marked [Extensible]."""
> - self.assertBackwardCompatible('[Extensible] enum E { kA, kB };',
> + self.assertBackwardCompatible('[Extensible] enum E { [Default] kA, kB };',
> 'enum E { kA, kB, [MinVersion=1] kC };')
> self.assertBackwardCompatible(
> - '[Extensible] enum E { kA, kB };',
> - '[Extensible] enum E { kA, kB, [MinVersion=1] kC };')
> + '[Extensible] enum E { [Default] kA, kB };',
> + '[Extensible] enum E { [Default] kA, kB, [MinVersion=1] kC };')
> self.assertBackwardCompatible(
> - '[Extensible] enum E { kA, [MinVersion=1] kB };',
> - '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=2] kC };')
> + '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',
> + """[Extensible] enum E {
> + [Default] kA,
> + [MinVersion=1] kB,
> + [MinVersion=2] kC };""")
>
> def testRenameEnumValue(self):
> """Renaming an enum value does not affect backward-compatibility. Only
> @@ -161,14 +172,17 @@ class VersionCompatibilityTest(MojomParserTestCase):
> 'struct S {}; struct T { S s; };',
> 'struct S { [MinVersion=1] int32 x; }; struct T { S s; };')
> self.assertBackwardCompatible(
> - '[Extensible] enum E { kA }; struct S { E e; };',
> - '[Extensible] enum E { kA, [MinVersion=1] kB }; struct S { E e; };')
> + '[Extensible] enum E { [Default] kA }; struct S { E e; };',
> + """[Extensible] enum E {
> + [Default] kA,
> + [MinVersion=1] kB };
> + struct S { E e; };""")
> self.assertNotBackwardCompatible(
> 'struct S {}; struct T { S s; };',
> 'struct S { int32 x; }; struct T { S s; };')
> self.assertNotBackwardCompatible(
> - '[Extensible] enum E { kA }; struct S { E e; };',
> - '[Extensible] enum E { kA, kB }; struct S { E e; };')
> + '[Extensible] enum E { [Default] kA }; struct S { E e; };',
> + '[Extensible] enum E { [Default] kA, kB }; struct S { E e; };')
>
> def testNewStructFieldWithInvalidMinVersion(self):
> """Adding a new field using an existing MinVersion breaks backward-
> @@ -305,14 +319,17 @@ class VersionCompatibilityTest(MojomParserTestCase):
> 'struct S {}; union U { S s; };',
> 'struct S { [MinVersion=1] int32 x; }; union U { S s; };')
> self.assertBackwardCompatible(
> - '[Extensible] enum E { kA }; union U { E e; };',
> - '[Extensible] enum E { kA, [MinVersion=1] kB }; union U { E e; };')
> + '[Extensible] enum E { [Default] kA }; union U { E e; };',
> + """[Extensible] enum E {
> + [Default] kA,
> + [MinVersion=1] kB };
> + union U { E e; };""")
> self.assertNotBackwardCompatible(
> 'struct S {}; union U { S s; };',
> 'struct S { int32 x; }; union U { S s; };')
> self.assertNotBackwardCompatible(
> - '[Extensible] enum E { kA }; union U { E e; };',
> - '[Extensible] enum E { kA, kB }; union U { E e; };')
> + '[Extensible] enum E { [Default] kA }; union U { E e; };',
> + '[Extensible] enum E { [Default] kA, kB }; union U { E e; };')
>
> def testNewUnionFieldWithInvalidMinVersion(self):
> """Adding a new field using an existing MinVersion breaks backward-
> diff --git a/utils/ipc/mojo/public/tools/run_all_python_unittests.py b/utils/ipc/mojo/public/tools/run_all_python_unittests.py
> index b20109583071..98bce18cbb6d 100755
> --- a/utils/ipc/mojo/public/tools/run_all_python_unittests.py
> +++ b/utils/ipc/mojo/public/tools/run_all_python_unittests.py
> @@ -1,5 +1,5 @@
> -#!/usr/bin/env python
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +#!/usr/bin/env python3
> +# 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.
>
> @@ -8,11 +8,13 @@ import sys
>
> _TOOLS_DIR = os.path.dirname(__file__)
> _MOJOM_DIR = os.path.join(_TOOLS_DIR, 'mojom')
> +_BINDINGS_DIR = os.path.join(_TOOLS_DIR, 'bindings')
> _SRC_DIR = os.path.join(_TOOLS_DIR, os.path.pardir, os.path.pardir,
> os.path.pardir)
>
> # Ensure that the mojom library is discoverable.
> sys.path.append(_MOJOM_DIR)
> +sys.path.append(_BINDINGS_DIR)
>
> # Help Python find typ in //third_party/catapult/third_party/typ/
> sys.path.append(
> @@ -21,7 +23,7 @@ import typ
>
>
> def Main():
> - return typ.main(top_level_dir=_MOJOM_DIR)
> + return typ.main(top_level_dirs=[_MOJOM_DIR, _BINDINGS_DIR])
>
>
> if __name__ == '__main__':
> diff --git a/utils/ipc/tools/README b/utils/ipc/tools/README
> index d5c24fc3b5cb..961cabd222e3 100644
> --- a/utils/ipc/tools/README
> +++ b/utils/ipc/tools/README
> @@ -1,4 +1,4 @@
> # SPDX-License-Identifier: CC0-1.0
>
> -Files in this directory are imported from 9c138d992bfc of Chromium. Do not
> +Files in this directory are imported from 9be4263648d7 of Chromium. Do not
> modify them manually.
> diff --git a/utils/ipc/tools/diagnosis/crbug_1001171.py b/utils/ipc/tools/diagnosis/crbug_1001171.py
> index 478fb8c1a610..40900d10d38a 100644
> --- a/utils/ipc/tools/diagnosis/crbug_1001171.py
> +++ b/utils/ipc/tools/diagnosis/crbug_1001171.py
> @@ -1,4 +1,4 @@
> -# Copyright 2019 The Chromium Authors. All rights reserved.
> +# 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.
More information about the libcamera-devel
mailing list