diff options
author | Laurent Pinchart <laurent.pinchart@ideasonboard.com> | 2024-08-08 18:13:00 +0300 |
---|---|---|
committer | Laurent Pinchart <laurent.pinchart@ideasonboard.com> | 2024-08-15 23:59:08 +0300 |
commit | 50c92cc7e2924009ecab3e4004448b01d687707c (patch) | |
tree | c22b49816a3c79dae4727780962aa0928df42b52 /utils/codegen/ipc/mojo/public/tools/bindings | |
parent | d3bf27180ef1d91b86b7b87a2378e559eaff5455 (diff) |
meson: Move all code generation scripts to utils/codegen/
We have multiple code generation scripts in utils/, mixed with other
miscellaneous utilities, as well as a larger code base based on mojom in
utils/ipc/. To make code sharing easier between the generator scripts,
without creating a mess in the utils/ directory, move all the code
generation code to utils/codegen/.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Diffstat (limited to 'utils/codegen/ipc/mojo/public/tools/bindings')
19 files changed, 5156 insertions, 0 deletions
diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/BUILD.gn b/utils/codegen/ipc/mojo/public/tools/bindings/BUILD.gn new file mode 100644 index 00000000..eeca73ea --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/BUILD.gn @@ -0,0 +1,131 @@ +# Copyright 2016 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//mojo/public/tools/bindings/mojom.gni") +import("//third_party/jinja2/jinja2.gni") + +action("precompile_templates") { + sources = mojom_generator_sources + sources += [ + "$mojom_generator_root/generators/cpp_templates/cpp_macros.tmpl", + "$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl", + "$mojom_generator_root/generators/cpp_templates/enum_serialization_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/feature_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/feature_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_feature_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_macros.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-features.h.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-forward.h.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-import-headers.h.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-params-data.h.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-shared-internal.h.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-shared-message-ids.h.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-test-utils.h.tmpl", + "$mojom_generator_root/generators/cpp_templates/module.cc.tmpl", + "$mojom_generator_root/generators/cpp_templates/module.h.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_data_view_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_data_view_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_macros.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_serialization_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_traits_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_traits_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_unserialized_message_context.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_data_view_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_data_view_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_serialization_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_traits_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_traits_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/validation_macros.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_class_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_class_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_class_template_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_union_class_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_union_class_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_union_class_template_definition.tmpl", + "$mojom_generator_root/generators/java_templates/constant_definition.tmpl", + "$mojom_generator_root/generators/java_templates/constants.java.tmpl", + "$mojom_generator_root/generators/java_templates/data_types_definition.tmpl", + "$mojom_generator_root/generators/java_templates/enum.java.tmpl", + "$mojom_generator_root/generators/java_templates/enum_definition.tmpl", + "$mojom_generator_root/generators/java_templates/header.java.tmpl", + "$mojom_generator_root/generators/java_templates/interface.java.tmpl", + "$mojom_generator_root/generators/java_templates/interface_definition.tmpl", + "$mojom_generator_root/generators/java_templates/interface_internal.java.tmpl", + "$mojom_generator_root/generators/java_templates/struct.java.tmpl", + "$mojom_generator_root/generators/java_templates/union.java.tmpl", + "$mojom_generator_root/generators/js_templates/enum_definition.tmpl", + "$mojom_generator_root/generators/js_templates/fuzzing.tmpl", + "$mojom_generator_root/generators/js_templates/interface_definition.tmpl", + "$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl", + "$mojom_generator_root/generators/js_templates/lite/enum_definition_for_module.tmpl", + "$mojom_generator_root/generators/js_templates/lite/interface_definition.tmpl", + "$mojom_generator_root/generators/js_templates/lite/interface_definition_for_module.tmpl", + "$mojom_generator_root/generators/js_templates/lite/module_definition.tmpl", + "$mojom_generator_root/generators/js_templates/lite/mojom-lite.js.tmpl", + "$mojom_generator_root/generators/js_templates/lite/mojom.m.js.tmpl", + "$mojom_generator_root/generators/js_templates/lite/struct_definition.tmpl", + "$mojom_generator_root/generators/js_templates/lite/struct_definition_for_module.tmpl", + "$mojom_generator_root/generators/js_templates/lite/union_definition.tmpl", + "$mojom_generator_root/generators/js_templates/lite/union_definition_for_module.tmpl", + "$mojom_generator_root/generators/js_templates/module.amd.tmpl", + "$mojom_generator_root/generators/js_templates/module_definition.tmpl", + "$mojom_generator_root/generators/js_templates/struct_definition.tmpl", + "$mojom_generator_root/generators/js_templates/union_definition.tmpl", + "$mojom_generator_root/generators/js_templates/validation_macros.tmpl", + "$mojom_generator_root/generators/mojolpm_templates/mojolpm.cc.tmpl", + "$mojom_generator_root/generators/mojolpm_templates/mojolpm.h.tmpl", + "$mojom_generator_root/generators/mojolpm_templates/mojolpm.proto.tmpl", + "$mojom_generator_root/generators/mojolpm_templates/mojolpm_from_proto_macros.tmpl", + "$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl", + "$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl", + "$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl", + "$mojom_generator_root/generators/ts_templates/enum_definition.tmpl", + "$mojom_generator_root/generators/ts_templates/interface_definition.tmpl", + "$mojom_generator_root/generators/ts_templates/module_definition.tmpl", + "$mojom_generator_root/generators/ts_templates/struct_definition.tmpl", + "$mojom_generator_root/generators/ts_templates/union_definition.tmpl", + ] + script = mojom_generator_script + + inputs = jinja2_sources + outputs = [ + "$target_gen_dir/cpp_templates.zip", + "$target_gen_dir/java_templates.zip", + "$target_gen_dir/js_templates.zip", + "$target_gen_dir/mojolpm_templates.zip", + "$target_gen_dir/ts_templates.zip", + ] + args = [ + "-o", + rebase_path(target_gen_dir, root_build_dir), + "--use_bundled_pylibs", + "precompile", + ] +} + +group("tests") { + data = [ + mojom_generator_script, + "checks/mojom_attributes_check_unittest.py", + "checks/mojom_interface_feature_check_unittest.py", + "checks/mojom_restrictions_checks_unittest.py", + "mojom_bindings_generator_unittest.py", + "//tools/diagnosis/crbug_1001171.py", + "//third_party/markupsafe/", + ] + data += mojom_generator_sources + data += jinja2_sources +} diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/README.md b/utils/codegen/ipc/mojo/public/tools/bindings/README.md new file mode 100644 index 00000000..b27b2d01 --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/README.md @@ -0,0 +1,1014 @@ +# Mojom Interface Definition Language (IDL) +This document is a subset of the [Mojo documentation](/mojo/README.md). + +[TOC] + +## Overview + +Mojom is the IDL for Mojo interfaces. Given a `.mojom` file, the +[bindings +generator](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings/) can +output bindings for any supported language: **C++**, **JavaScript**, or +**Java**. + +For a trivial example consider the following hypothetical Mojom file we write to +`//services/widget/public/mojom/frobinator.mojom`: + +``` +module widget.mojom; + +interface Frobinator { + Frobinate(); +}; +``` + +This defines a single [interface](#Interfaces) named `Frobinator` in a +[module](#Modules) named `widget.mojom` (and thus fully qualified in Mojom as +`widget.mojom.Frobinator`.) Note that many interfaces and/or other types of +definitions (structs, enums, *etc.*) may be included in a single Mojom file. + +If we add a corresponding GN target to +`//services/widget/public/mojom/BUILD.gn`: + +``` +import("mojo/public/tools/bindings/mojom.gni") + +mojom("mojom") { + sources = [ + "frobinator.mojom", + ] +} +``` + +and then build this target: + +``` +ninja -C out/r services/widget/public/mojom +``` + +we'll find several generated sources in our output directory: + +``` +out/r/gen/services/widget/public/mojom/frobinator.mojom.cc +out/r/gen/services/widget/public/mojom/frobinator.mojom.h +out/r/gen/services/widget/public/mojom/frobinator.mojom-shared.h +etc... +``` + +Each of these generated source modules includes a set of definitions +representing the Mojom contents in C++. You can also build or depend on suffixed +target names to get bindings for other languages. For example, + +``` +ninja -C out/r services/widget/public/mojom:mojom_js +ninja -C out/r services/widget/public/mojom:mojom_java +``` + +would generate JavaScript and Java bindings respectively, in the same generated +output directory. + +For more details regarding the generated +outputs please see +[documentation for individual target languages](#Generated-Code-For-Target-Languages). + +## Mojom Syntax + +Mojom IDL allows developers to define **structs**, **unions**, **interfaces**, +**constants**, and **enums**, all within the context of a **module**. These +definitions are used to generate code in the supported target languages at build +time. + +Mojom files may **import** other Mojom files in order to reference their +definitions. + +### Primitive Types +Mojom supports a few basic data types which may be composed into structs or used +for message parameters. + +| Type | Description +|-------------------------------|-------------------------------------------------------| +| `bool` | Boolean type (`true` or `false`.) +| `int8`, `uint8` | Signed or unsigned 8-bit integer. +| `int16`, `uint16` | Signed or unsigned 16-bit integer. +| `int32`, `uint32` | Signed or unsigned 32-bit integer. +| `int64`, `uint64` | Signed or unsigned 64-bit integer. +| `float`, `double` | 32- or 64-bit floating point number. +| `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 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. +| `handle<data_pipe_producer>` | Data pipe producer handle. +| `handle<data_pipe_consumer>` | Data pipe consumer handle. +| `handle<platform>` | A native platform/OS handle. +| *`pending_remote<InterfaceType>`* | Any user-defined Mojom interface type. This is sugar for a strongly-typed message pipe handle which should eventually be used to make outgoing calls on the interface. +| *`pending_receiver<InterfaceType>`* | A pending receiver for any user-defined Mojom interface type. This is sugar for a more strongly-typed message pipe handle which is expected to receive request messages and should therefore eventually be bound to an implementation of the interface. +| *`pending_associated_remote<InterfaceType>`* | An associated interface handle. See [Associated Interfaces](#Associated-Interfaces) +| *`pending_associated_receiver<InterfaceType>`* | A pending associated receiver. See [Associated Interfaces](#Associated-Interfaces) +| *T*? | An optional (nullable) value. Primitive numeric types (integers, floats, booleans, and enums) are not nullable. All other types are nullable. + +### Modules + +Every Mojom file may optionally specify a single **module** to which it belongs. + +This is used strictly for aggregating all defined symbols therein within a +common Mojom namespace. The specific impact this has on generated bindings code +varies for each target language. For example, if the following Mojom is used to +generate bindings: + +``` +module business.stuff; + +interface MoneyGenerator { + GenerateMoney(); +}; +``` + +Generated C++ bindings will define a class interface `MoneyGenerator` in the +`business::stuff` namespace, while Java bindings will define an interface +`MoneyGenerator` in the `org.chromium.business.stuff` package. JavaScript +bindings at this time are unaffected by module declarations. + +**NOTE:** By convention in the Chromium codebase, **all** Mojom files should +declare a module name with at least (and preferably exactly) one top-level name +as well as an inner `mojom` module suffix. *e.g.*, `chrome.mojom`, +`business.mojom`, *etc.* + +This convention makes it easy to tell which symbols are generated by Mojom when +reading non-Mojom code, and it also avoids namespace collisions in the fairly +common scenario where you have a real C++ or Java `Foo` along with a +corresponding Mojom `Foo` for its serialized representation. + +### Imports + +If your Mojom references definitions from other Mojom files, you must **import** +those files. Import syntax is as follows: + +``` +import "services/widget/public/mojom/frobinator.mojom"; +``` + +Import paths are always relative to the top-level directory. + +Note that circular imports are **not** supported. + +### Structs + +Structs are defined using the **struct** keyword, and they provide a way to +group related fields together: + +``` cpp +struct StringPair { + string first; + string second; +}; +``` + +Struct fields may be comprised of any of the types listed above in the +[Primitive Types](#Primitive-Types) section. + +Default values may be specified as long as they are constant: + +``` cpp +struct Request { + int32 id = -1; + string details; +}; +``` + +What follows is a fairly +comprehensive example using the supported field types: + +``` cpp +struct StringPair { + string first; + string second; +}; + +enum AnEnum { + kYes, + kNo +}; + +interface SampleInterface { + DoStuff(); +}; + +struct AllTheThings { + // Note that these types can never be marked nullable! + bool boolean_value; + int8 signed_8bit_value = 42; + uint8 unsigned_8bit_value; + int16 signed_16bit_value; + uint16 unsigned_16bit_value; + int32 signed_32bit_value; + uint32 unsigned_32bit_value; + int64 signed_64bit_value; + uint64 unsigned_64bit_value; + float float_value_32bit; + double float_value_64bit; + AnEnum enum_value = AnEnum.kYes; + + // Strings may be nullable. + string? maybe_a_string_maybe_not; + + // Structs may contain other structs. These may also be nullable. + StringPair some_strings; + StringPair? maybe_some_more_strings; + + // In fact structs can also be nested, though in practice you must always make + // such fields nullable -- otherwise messages would need to be infinitely long + // in order to pass validation! + AllTheThings? more_things; + + // Arrays may be templated over any Mojom type, and are always nullable: + array<int32> numbers; + array<int32>? maybe_more_numbers; + + // Arrays of arrays of arrays... are fine. + array<array<array<AnEnum>>> this_works_but_really_plz_stop; + + // The element type may be nullable if it's a type which is allowed to be + // nullable. + array<AllTheThings?> more_maybe_things; + + // Fixed-size arrays get some extra validation on the receiving end to ensure + // that the correct number of elements is always received. + array<uint64, 2> uuid; + + // Maps follow many of the same rules as arrays. Key types may be any + // non-handle, non-collection type, and value types may be any supported + // struct field type. Maps may also be nullable. + map<string, int32> one_map; + map<AnEnum, string>? maybe_another_map; + map<StringPair, AllTheThings?>? maybe_a_pretty_weird_but_valid_map; + map<StringPair, map<int32, array<map<string, string>?>?>?> ridiculous; + + // And finally, all handle types are valid as struct fields and may be + // nullable. Note that interfaces and interface requests (the "Foo" and + // "Foo&" type syntax respectively) are just strongly-typed message pipe + // handles. + handle generic_handle; + handle<data_pipe_consumer> reader; + handle<data_pipe_producer>? maybe_writer; + handle<shared_buffer> dumping_ground; + handle<message_pipe> raw_message_pipe; + pending_remote<SampleInterface>? maybe_a_sample_interface_client_pipe; + pending_receiver<SampleInterface> non_nullable_sample_pending_receiver; + pending_receiver<SampleInterface>? nullable_sample_pending_receiver; + pending_associated_remote<SampleInterface> associated_interface_client; + pending_associated_receiver<SampleInterface> associated_pending_receiver; + pending_associated_receiver<SampleInterface>? maybe_another_pending_receiver; +}; +``` + +For details on how all of these different types translate to usable generated +code, see +[documentation for individual target languages](#Generated-Code-For-Target-Languages). + +### Unions + +Mojom supports tagged unions using the **union** keyword. A union is a +collection of fields which may take the value of any single one of those fields +at a time. Thus they provide a way to represent a variant value type while +minimizing storage requirements. + +Union fields may be of any type supported by [struct](#Structs) fields. For +example: + +```cpp +union ExampleUnion { + string str; + StringPair pair; + int64 id; + array<uint64, 2> guid; + SampleInterface iface; +}; +``` + +For details on how unions like this translate to generated bindings code, see +[documentation for individual target languages](#Generated-Code-For-Target-Languages). + +### Enumeration Types + +Enumeration types may be defined using the **enum** keyword either directly +within a module or nested within the namespace of some struct or interface: + +``` +module business.mojom; + +enum Department { + kSales = 0, + kDev, +}; + +struct Employee { + enum Type { + kFullTime, + kPartTime, + }; + + Type type; + // ... +}; +``` + +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. + +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). + +### Constants + +Constants may be defined using the **const** keyword either directly within a +module or nested within the namespace of some struct or interface: + +``` +module business.mojom; + +const string kServiceName = "business"; + +struct Employee { + const uint64 kInvalidId = 0; + + enum Type { + kFullTime, + kPartTime, + }; + + uint64 id = kInvalidId; + Type type; +}; +``` + +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 +request message may optionally define a parameterized response message. Here's +an example to define an interface `Foo` with various kinds of requests: + +``` +interface Foo { + // A request which takes no arguments and expects no response. + MyMessage(); + + // A request which has some arguments and expects no response. + MyOtherMessage(string name, array<uint8> bytes); + + // A request which expects a single-argument response. + MyMessageWithResponse(string command) => (bool success); + + // A request which expects a response with multiple arguments. + MyMessageWithMoarResponse(string a, string b) => (int8 c, int8 d); +}; +``` + +Anything which is a valid struct field type (see [Structs](#Structs)) is also a +valid request or response argument type. The type notation is the same for both. + +### Attributes + +Mojom definitions may have their meaning altered by **attributes**, specified +with a syntax similar to Java or C# attributes. There are a handle of +interesting attributes supported today. + +* **`[Sync]`**: + The `Sync` attribute may be specified for any interface method which expects a + response. This makes it so that callers of the method can wait synchronously + for a response. See [Synchronous + Calls](/mojo/public/cpp/bindings/README.md#Synchronous-Calls) in the C++ + bindings documentation. Note that sync methods are only actually synchronous + when called from C++. + +* **`[NoInterrupt]`**: + When a thread is waiting for a reply to a `Sync` message, it's possible to be + woken up to dispatch other unrelated incoming `Sync` messages. This measure + helps to avoid deadlocks. If a `Sync` message is also marked as `NoInterrupt` + however, this behavior is disabled: instead the calling thread will only wake + up for the precise message being waited upon. This attribute must be used with + extreme caution, because it can lead to deadlocks otherwise. + +* **`[Default]`**: + 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 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. + + 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 + provide a nominal bridge between Mojo IPC and legacy `IPC::ParamTraits` or + `IPC_STRUCT_TRAITS*` macros. See [Repurposing Legacy IPC + Traits](/docs/mojo_ipc_conversion.md#repurposing-and-invocations) for more + details. Note support for this attribute is strictly limited to C++ bindings + generation. + +* **`[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. `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 + definition can be considered stable over time, meaning it is safe to use for + things like persistent storage or communication between independent + version-skewed binaries. Stable definitions may only depend on builtin mojom + types or other stable definitions, and changes to such definitions MUST + preserve backward-compatibility through appropriate use of versioning. + Backward-compatibility of changes is enforced in the Chromium tree using a + strict presubmit check. See [Versioning](#Versioning) for more details on + backward-compatibility constraints. + +* **`[Uuid=<UUID>]`**: + Specifies a UUID to be associated with a given interface. The UUID is intended + to remain stable across all changes to the interface definition, including + name changes. The value given for this attribute should be a standard UUID + 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 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 + +When the bindings generator successfully processes an input Mojom file, it emits +corresponding code for each supported target language. For more details on how +Mojom concepts translate to a given target language, please refer to the +bindings API documentation for that language: + +* [C++ Bindings](/mojo/public/cpp/bindings/README.md) +* [JavaScript Bindings](/mojo/public/js/README.md) +* [Java Bindings](/mojo/public/java/bindings/README.md) + +## Message Validation + +Regardless of target language, all interface messages are validated during +deserialization before they are dispatched to a receiving implementation of the +interface. This helps to ensure consistent validation across interfaces without +leaving the burden to developers and security reviewers every time a new message +is added. + +If a message fails validation, it is never dispatched. Instead a **connection +error** is raised on the binding object (see +[C++ Connection Errors](/mojo/public/cpp/bindings/README.md#Connection-Errors), +[Java Connection Errors](/mojo/public/java/bindings/README.md#Connection-Errors), +or +[JavaScript Connection Errors](/mojo/public/js/README.md#Connection-Errors) for +details.) + +Some baseline level of validation is done automatically for primitive Mojom +types. + +### Non-Nullable Objects + +Mojom fields or parameter values (*e.g.*, structs, interfaces, arrays, *etc.*) +may be marked nullable in Mojom definitions (see +[Primitive Types](#Primitive-Types).) If a field or parameter is **not** marked +nullable but a message is received with a null value in its place, that message +will fail validation. + +### Enums + +Enums declared in Mojom are automatically validated against the range of legal +values. For example if a Mojom declares the enum: + +``` cpp +enum AdvancedBoolean { + kTrue = 0, + kFalse = 1, + kFileNotFound = 2, +}; +``` + +and a message is received with the integral value 3 (or anything other than 0, +1, or 2) in place of some `AdvancedBoolean` field or parameter, the message will +fail validation. + +*** note +NOTE: It's possible to avoid this type of validation error by explicitly marking +an enum as [Extensible](#Attributes) if you anticipate your enum being exchanged +between two different versions of the binding interface. See +[Versioning](#Versioning). +*** + +### Other failures + +There are a host of internal validation errors that may occur when a malformed +message is received, but developers should not be concerned with these +specifically; in general they can only result from internal bindings bugs, +compromised processes, or some remote endpoint making a dubious effort to +manually encode their own bindings messages. + +### Custom Validation + +It's also possible for developers to define custom validation logic for specific +Mojom struct types by exploiting the +[type mapping](/mojo/public/cpp/bindings/README.md#Type-Mapping) system for C++ +bindings. Messages rejected by custom validation logic trigger the same +validation failure behavior as the built-in type validation routines. + +## Associated Interfaces + +As mentioned in the [Primitive Types](#Primitive-Types) section above, pending_remote +and pending_receiver fields and parameters may be marked as `associated`. This +essentially means that they are piggy-backed on some other interface's message +pipe. + +Because individual interface message pipes operate independently there can be no +relative ordering guarantees among them. Associated interfaces are useful when +one interface needs to guarantee strict FIFO ordering with respect to one or +more other interfaces, as they allow interfaces to share a single pipe. + +Currently associated interfaces are only supported in generated C++ bindings. +See the documentation for +[C++ Associated Interfaces](/mojo/public/cpp/bindings/README.md#Associated-Interfaces). + +## Versioning + +### Overview + +*** note +**NOTE:** You don't need to worry about versioning if you don't care about +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 +want to use those new features when they are available. If services and clients +are not updated at the same time, it's important for them to be able to +communicate with each other using different snapshots (versions) of their +interfaces. + +This document shows how to extend Mojom interfaces in a backwards-compatible +way. Changing interfaces in a non-backwards-compatible way is not discussed, +because in that case communication between different interface versions is +impossible anyway. + +### Versioned Structs + +You can use the `MinVersion` [attribute](#Attributes) to indicate from which +version a struct field is introduced. Assume you have the following struct: + +``` cpp +struct Employee { + uint64 employee_id; + string name; +}; +``` + +and you would like to add birthday and nickname fields. You can add them as +optional types with a `MinVersion` like so: + +``` cpp +struct Employee { + uint64 employee_id; + string name; + [MinVersion=1] Date? birthday; + [MinVersion=1] string? nickname; +}; +``` + +*** note +**NOTE:** Mojo object or handle types added with a `MinVersion` **MUST** be +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 +struct definition (*i.e*., existing fields must not change **ordinal value**) +with the `MinVersion` attribute set to a number greater than any previous +existing versions. + +The value of `MinVersion` is unrelated to ordinals. The choice of a particular +version number is arbitrary. All its usage means is that a field isn't present +before the numbered version. + +*** note +**NOTE:** do not change existing fields in versioned structs, as this is +not backwards-compatible. Instead, rename the old field to make its +deprecation clear and add a new field with a new `MinVersion` number. +*** + +**Ordinal value** refers to the relative positional layout of a struct's fields +(and an interface's methods) when encoded in a message. Implicitly, ordinal +numbers are assigned to fields according to lexical position. In the example +above, `employee_id` has an ordinal value of 0 and `name` has an ordinal value +of 1. + +Ordinal values can be specified explicitly using `**@**` notation, subject to +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, 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 +backwards-compatible: + +``` cpp +struct Employee { + uint64 employee_id@0; + [MinVersion=1] Date? birthday@2; + string name@1; + [MinVersion=1] string? nickname@3; +}; +``` + +### Versioned Interfaces + +There are two dimensions on which an interface can be extended + +**Appending New Parameters To Existing Methods** +: Parameter lists are treated as structs internally, so all the rules of + versioned structs apply to method parameter lists. The only difference is + 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. + +**Appending New Methods** +: Similarly, you can reorder methods with explicit ordinal values as long as + the ordinal values of existing methods are unchanged. + +For example: + +``` cpp +// Old version: +interface HumanResourceDatabase { + QueryEmployee(uint64 id) => (Employee? employee); +}; + +// New version: +interface HumanResourceDatabase { + QueryEmployee(uint64 id) => (Employee? employee); + + [MinVersion=1] + AttachFingerPrint(uint64 id, array<uint8> finger_print) + => (bool success); +}; +``` + +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. + +Bindings target languages that support versioning expose means to query or +assert the remote version from a client handle (*e.g.*, an +`mojo::Remote<T>` in C++ bindings.) + +See +[C++ Versioning Considerations](/mojo/public/cpp/bindings/README.md#Versioning-Considerations) +and +[Java Versioning Considerations](/mojo/public/java/bindings/README.md#Versioning-Considerations) + +### Versioned Enums + +**By default, enums are non-extensible**, which means that generated message +validation code does not expect to see new values in the future. When an unknown +value is seen for a non-extensible enum field or parameter, a validation error +is raised. + +If you want an enum to be extensible in the future, you can apply the +`[Extensible]` [attribute](#Attributes): + +``` cpp +[Extensible] +enum Department { + kSales, + kDev, +}; +``` + +And later you can extend this enum without breaking backwards compatibility: + +``` cpp +[Extensible] +enum Department { + kSales, + kDev, + [MinVersion=1] kResearch, +}; +``` + +*** note +**NOTE:** For versioned enum definitions, the use of a `[MinVersion]` attribute +is strictly for documentation purposes. It has no impact on the generated code. +*** + +With extensible enums, bound interface implementations may receive unknown enum +values and will need to deal with them gracefully. See +[C++ Versioning Considerations](/mojo/public/cpp/bindings/README.md#Versioning-Considerations) +for details. + +### Renaming versioned structs +It's possible to rename versioned structs by using the `[RenamedFrom]` attribute. +RenamedFrom + +``` cpp +module asdf.mojom; + +// Old version: +[Stable] +struct OldStruct { +}; + +// New version: +[Stable, RenamedFrom="asdf.mojom.OldStruct"] +struct NewStruct { +}; +``` + +## Component targets + +If there are multiple components depending on the same mojom target within one binary, +the target will need to be defined as `mojom_component` instead of `mojom`. +Since `mojom` targets are generated `source_set` targets and `mojom_component` targets +are generated `component` targets, you would use `mojom_component` in the same cases +where you would use `component` for non-mojom files. +*** note +**NOTE**: by default, components for both blink and non-blink bindings are generated. +Use the `disable_variants` target parameter to generate only non-blink bindings. +You can also generate a `source_set` for one of the variants by defining +[export_*](https://source.chromium.org/chromium/chromium/src/+/main:mojo/public/tools/bindings/mojom.gni;drc=739b9fbce50310c1dd2b59c279cd90a9319cb6e8;l=318) +parameters for the `mojom_component` target. +*** + +## Grammar Reference + +Below is the (BNF-ish) context-free grammar of the Mojom language: + +``` +MojomFile = StatementList +StatementList = Statement StatementList | Statement +Statement = ModuleStatement | ImportStatement | Definition + +ModuleStatement = AttributeSection "module" Identifier ";" +ImportStatement = "import" StringLiteral ";" +Definition = Struct Union Interface Enum Feature Const + +AttributeSection = <empty> | "[" AttributeList "]" +AttributeList = <empty> | NonEmptyAttributeList +NonEmptyAttributeList = Attribute + | Attribute "," NonEmptyAttributeList +Attribute = Name + | Name "=" Name + | Name "=" Literal + +Struct = AttributeSection "struct" Name "{" StructBody "}" ";" + | AttributeSection "struct" Name ";" +StructBody = <empty> + | StructBody Const + | StructBody Enum + | StructBody StructField +StructField = AttributeSection TypeSpec Name Ordinal Default ";" + +Union = AttributeSection "union" Name "{" UnionBody "}" ";" +UnionBody = <empty> | UnionBody UnionField +UnionField = AttributeSection TypeSpec Name Ordinal ";" + +Interface = AttributeSection "interface" Name "{" InterfaceBody "}" ";" +InterfaceBody = <empty> + | InterfaceBody Const + | InterfaceBody Enum + | InterfaceBody Method +Method = AttributeSection Name Ordinal "(" ParameterList ")" Response ";" +ParameterList = <empty> | NonEmptyParameterList +NonEmptyParameterList = Parameter + | Parameter "," NonEmptyParameterList +Parameter = AttributeSection TypeSpec Name Ordinal +Response = <empty> | "=>" "(" ParameterList ")" + +TypeSpec = TypeName "?" | TypeName +TypeName = BasicTypeName + | Array + | FixedArray + | Map + | InterfaceRequest +BasicTypeName = Identifier | "associated" Identifier | HandleType | NumericType +NumericType = "bool" | "int8" | "uint8" | "int16" | "uint16" | "int32" + | "uint32" | "int64" | "uint64" | "float" | "double" +HandleType = "handle" | "handle" "<" SpecificHandleType ">" +SpecificHandleType = "message_pipe" + | "shared_buffer" + | "data_pipe_consumer" + | "data_pipe_producer" + | "platform" +Array = "array" "<" TypeSpec ">" +FixedArray = "array" "<" TypeSpec "," IntConstDec ">" +Map = "map" "<" Identifier "," TypeSpec ">" +InterfaceRequest = Identifier "&" | "associated" Identifier "&" + +Ordinal = <empty> | OrdinalValue + +Default = <empty> | "=" Constant + +Enum = AttributeSection "enum" Name "{" NonEmptyEnumValueList "}" ";" + | AttributeSection "enum" Name "{" NonEmptyEnumValueList "," "}" ";" +NonEmptyEnumValueList = EnumValue | NonEmptyEnumValueList "," EnumValue +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 ";" + +Identifier = Name | Name "." Identifier + +Literal = Integer | Float | "true" | "false" | "default" | StringLiteral + +Integer = IntConst | "+" IntConst | "-" IntConst +IntConst = IntConstDec | IntConstHex + +Float = FloatConst | "+" FloatConst | "-" FloatConst + +; The rules below are for tokens matched strictly according to the given regexes + +Identifier = /[a-zA-Z_][0-9a-zA-Z_]*/ +IntConstDec = /0|(1-9[0-9]*)/ +IntConstHex = /0[xX][0-9a-fA-F]+/ +OrdinalValue = /@(0|(1-9[0-9]*))/ +FloatConst = ... # Imagine it's close enough to C-style float syntax. +StringLiteral = ... # Imagine it's close enough to C-style string literals, including escapes. +``` + +## Additional Documentation + +[Mojom Message Format](https://docs.google.com/document/d/13pv9cFh5YKuBggDBQ1-AL8VReF-IYpFOFpRfvWFrwio/edit) +: Describes the wire format used by Mojo bindings interfaces over message + pipes. + +[Input Format of Mojom Message Validation Tests](https://docs.google.com/document/d/1-y-2IYctyX2NPaLxJjpJfzVNWCC2SR2MJAD9MpIytHQ/edit) +: Describes a text format used to facilitate bindings message validation + tests. diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/checks/__init__.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/__init__.py diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py new file mode 100644 index 00000000..e6e4f2c9 --- /dev/null +++ b/utils/codegen/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/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py new file mode 100644 index 00000000..f1a50a4a --- /dev/null +++ b/utils/codegen/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@0(int32 a) => (int32 b); + [MinVersion=2,Sync,UnlimitedSize,NoInterrupt] + Bar@1(int32 b, [MinVersion=2]Structure? s) => (bool c); + }; + + [RuntimeFeature=test.mojom.FeatureName] + interface FooFeatureControlled {}; + + interface FooMethodFeatureControlled { + [RuntimeFeature=test.mojom.FeatureName] + MethodWithFeature() => (bool c); + }; + """) + + def testWrongModuleStable(self): + contents = """ + // err: module cannot be Stable + [Stable] + module a; + enum Hello { kValue, kValue2, kValue3 }; + enum NativeEnum {}; + struct Structure { Hello hi; }; + + interface Foo { + Foo(int32 a) => (int32 b); + Bar(int32 b, Structure? s) => (bool c); + }; + """ + self._testThrows('b.mojom', contents, + 'attribute Stable not allowed on module') + + def testWrongEnumDefault(self): + contents = """ + module a; + // err: default should go on EnumValue not Enum. + [Default=kValue] + enum Hello { kValue, kValue2, kValue3 }; + enum NativeEnum {}; + struct Structure { Hello hi; }; + + interface Foo { + Foo(int32 a) => (int32 b); + Bar(int32 b, Structure? s) => (bool c); + }; + """ + self._testThrows('b.mojom', contents, + 'attribute Default not allowed on enum') + + def testWrongStructMinVersion(self): + contents = """ + module a; + enum Hello { kValue, kValue2, kValue3 }; + enum NativeEnum {}; + // err: struct cannot have MinVersion. + [MinVersion=2] + struct Structure { Hello hi; }; + + interface Foo { + Foo(int32 a) => (int32 b); + Bar(int32 b, Structure? s) => (bool c); + }; + """ + self._testThrows('b.mojom', contents, + 'attribute MinVersion not allowed on struct') + + def testWrongMethodRequireContext(self): + contents = """ + module a; + enum Hello { kValue, kValue2, kValue3 }; + enum NativeEnum {}; + struct Structure { Hello hi; }; + + interface Foo { + // err: RequireContext is for interfaces. + [RequireContext=Hello.kValue] + Foo(int32 a) => (int32 b); + Bar(int32 b, Structure? s) => (bool c); + }; + """ + self._testThrows('b.mojom', contents, + 'RequireContext not allowed on method') + + def testWrongMethodRequireContext(self): + # crbug.com/1230122 + contents = """ + module a; + interface Foo { + // err: sync not Sync. + [sync] + Foo(int32 a) => (int32 b); + }; + """ + self._testThrows('b.mojom', contents, + 'attribute sync not allowed.*Did you mean: Sync') + + def testStableExtensibleEnum(self): + # crbug.com/1193875 + contents = """ + module a; + [Stable] + enum Foo { + kDefaultVal, + kOtherVal = 2, + }; + """ + self._testThrows('a.mojom', contents, + 'Extensible.*?required.*?Stable.*?enum') diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py new file mode 100644 index 00000000..702d41c3 --- /dev/null +++ b/utils/codegen/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/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py new file mode 100644 index 00000000..07f51a64 --- /dev/null +++ b/utils/codegen/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/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py new file mode 100644 index 00000000..e96152fd --- /dev/null +++ b/utils/codegen/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/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py new file mode 100644 index 00000000..d570e26c --- /dev/null +++ b/utils/codegen/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/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py new file mode 100644 index 00000000..a6cd71e2 --- /dev/null +++ b/utils/codegen/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/codegen/ipc/mojo/public/tools/bindings/concatenate-files.py b/utils/codegen/ipc/mojo/public/tools/bindings/concatenate-files.py new file mode 100755 index 00000000..4dd26d4a --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/concatenate-files.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# Copyright 2019 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# This utility concatenates several files into one. On Unix-like systems +# it is equivalent to: +# cat file1 file2 file3 ...files... > target +# +# The reason for writing a separate utility is that 'cat' is not available +# on all supported build platforms, but Python is, and hence this provides +# us with an easy and uniform way of doing this on all platforms. + +# for py2/py3 compatibility +from __future__ import print_function + +import optparse +import sys + + +def Concatenate(filenames): + """Concatenate files. + + Args: + files: Array of file names. + The last name is the target; all earlier ones are sources. + + Returns: + True, if the operation was successful. + """ + if len(filenames) < 2: + print("An error occurred generating %s:\nNothing to do." % filenames[-1]) + return False + + try: + with open(filenames[-1], "wb") as target: + for filename in filenames[:-1]: + with open(filename, "rb") as current: + target.write(current.read()) + return True + except IOError as e: + print("An error occurred when writing %s:\n%s" % (filenames[-1], e)) + return False + + +def main(): + parser = optparse.OptionParser() + parser.set_usage("""Concatenate several files into one. + Equivalent to: cat file1 ... > target.""") + (_options, args) = parser.parse_args() + sys.exit(0 if Concatenate(args) else 1) + + +if __name__ == "__main__": + main() diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py b/utils/codegen/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py new file mode 100755 index 00000000..7d56c9f9 --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# Copyright 2018 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Simple utility which concatenates a set of files into a single output file +while also stripping any goog.provide or goog.require lines. This allows us to +provide a very primitive sort of "compilation" without any extra toolchain +support and without having to modify otherwise compilable sources in the tree +which use these directives. + +goog.provide lines are replaced with an equivalent invocation of +mojo.internal.exportModule, which accomplishes essentially the same thing in an +uncompiled context. A singular exception is made for the 'mojo.internal' export, +which is instead replaced with an inlined assignment to initialize the +namespace. +""" + +from __future__ import print_function + +import optparse +import re +import sys + + +_MOJO_INTERNAL_MODULE_NAME = "mojo.internal" +_MOJO_EXPORT_MODULE_SYMBOL = "mojo.internal.exportModule" + + +def FilterLine(filename, line, output): + if line.startswith("goog.require"): + return + + if line.startswith("goog.provide"): + match = re.match(r"goog.provide\('([^']+)'\);", line) + if not match: + print("Invalid goog.provide line in %s:\n%s" % (filename, line)) + sys.exit(1) + + module_name = match.group(1) + if module_name == _MOJO_INTERNAL_MODULE_NAME: + output.write("self.mojo = { internal: {} };") + else: + output.write("%s('%s');\n" % (_MOJO_EXPORT_MODULE_SYMBOL, module_name)) + return + + output.write(line) + +def ConcatenateAndReplaceExports(filenames): + if (len(filenames) < 2): + print("At least two filenames (one input and the output) are required.") + return False + + try: + with open(filenames[-1], "w") as target: + for filename in filenames[:-1]: + with open(filename, "r") as current: + for line in current.readlines(): + FilterLine(filename, line, target) + return True + except IOError as e: + print("Error generating %s\n: %s" % (filenames[-1], e)) + return False + +def main(): + parser = optparse.OptionParser() + parser.set_usage("""file1 [file2...] outfile + Concatenate several files into one, stripping Closure provide and + require directives along the way.""") + (_, args) = parser.parse_args() + sys.exit(0 if ConcatenateAndReplaceExports(args) else 1) + + +if __name__ == "__main__": + main() diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/gen_data_files_list.py b/utils/codegen/ipc/mojo/public/tools/bindings/gen_data_files_list.py new file mode 100644 index 00000000..c6daff03 --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/gen_data_files_list.py @@ -0,0 +1,48 @@ +# Copyright 2017 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Generates a list of all files in a directory. + +This script takes in a directory and an output file name as input. +It then reads the directory and creates a list of all file names +in that directory. The list is written to the output file. +There is also an option to pass in '-p' or '--pattern' +which will check each file name against a regular expression +pattern that is passed in. Only files which match the regex +will be written to the list. +""" + +from __future__ import print_function + +import os +import re +import sys + +from optparse import OptionParser + +sys.path.insert( + 0, + os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "mojom")) + +from mojom.generate.generator import WriteFile + + +def main(): + parser = OptionParser() + parser.add_option('-d', '--directory', help='Read files from DIRECTORY') + parser.add_option('-o', '--output', help='Write list to FILE') + parser.add_option('-p', + '--pattern', + help='Only reads files that name matches PATTERN', + default=".") + (options, _) = parser.parse_args() + pattern = re.compile(options.pattern) + files = [f for f in os.listdir(options.directory) if pattern.match(f)] + + contents = '\n'.join(f for f in files) + '\n' + WriteFile(contents, options.output) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/generate_type_mappings.py b/utils/codegen/ipc/mojo/public/tools/bindings/generate_type_mappings.py new file mode 100755 index 00000000..4a53e2bf --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/generate_type_mappings.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# Copyright 2016 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Generates a JSON typemap from its command-line arguments and dependencies. + +Each typemap should be specified in an command-line argument of the form +key=value, with an argument of "--start-typemap" preceding each typemap. + +For example, +generate_type_mappings.py --output=foo.typemap --start-typemap \\ + public_headers=foo.h traits_headers=foo_traits.h \\ + type_mappings=mojom.Foo=FooImpl + +generates a foo.typemap containing +{ + "c++": { + "mojom.Foo": { + "typename": "FooImpl", + "traits_headers": [ + "foo_traits.h" + ], + "public_headers": [ + "foo.h" + ] + } + } +} + +Then, +generate_type_mappings.py --dependency foo.typemap --output=bar.typemap \\ + --start-typemap public_headers=bar.h traits_headers=bar_traits.h \\ + type_mappings=mojom.Bar=BarImpl + +generates a bar.typemap containing +{ + "c++": { + "mojom.Bar": { + "typename": "BarImpl", + "traits_headers": [ + "bar_traits.h" + ], + "public_headers": [ + "bar.h" + ] + }, + "mojom.Foo": { + "typename": "FooImpl", + "traits_headers": [ + "foo_traits.h" + ], + "public_headers": [ + "foo.h" + ] + } + } +} +""" + +import argparse +import json +import os +import re +import sys + +sys.path.insert( + 0, + os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "mojom")) + +from mojom.generate.generator import WriteFile + +def ReadTypemap(path): + with open(path) as f: + return json.load(f)['c++'] + + +def LoadCppTypemapConfig(path): + configs = {} + with open(path) as f: + for config in json.load(f): + for entry in config['types']: + configs[entry['mojom']] = { + 'typename': entry['cpp'], + 'forward_declaration': entry.get('forward_declaration', None), + 'public_headers': config.get('traits_headers', []), + 'traits_headers': config.get('traits_private_headers', []), + 'copyable_pass_by_value': entry.get('copyable_pass_by_value', + False), + 'default_constructible': entry.get('default_constructible', True), + 'force_serialize': entry.get('force_serialize', False), + 'hashable': entry.get('hashable', False), + 'move_only': entry.get('move_only', False), + 'nullable_is_same_type': entry.get('nullable_is_same_type', False), + 'non_copyable_non_movable': False, + } + return configs + +def main(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + '--dependency', + type=str, + action='append', + default=[], + help=('A path to another JSON typemap to merge into the output. ' + 'This may be repeated to merge multiple typemaps.')) + parser.add_argument( + '--cpp-typemap-config', + type=str, + action='store', + dest='cpp_config_path', + help=('A path to a single JSON-formatted typemap config as emitted by' + 'GN when processing a mojom_cpp_typemap build rule.')) + parser.add_argument('--output', + type=str, + required=True, + help='The path to which to write the generated JSON.') + params, _ = parser.parse_known_args() + typemaps = {} + if params.cpp_config_path: + typemaps = LoadCppTypemapConfig(params.cpp_config_path) + missing = [path for path in params.dependency if not os.path.exists(path)] + if missing: + raise IOError('Missing dependencies: %s' % ', '.join(missing)) + for path in params.dependency: + typemaps.update(ReadTypemap(path)) + + WriteFile(json.dumps({'c++': typemaps}, indent=2), params.output) + + +if __name__ == '__main__': + main() diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/minify_with_terser.py b/utils/codegen/ipc/mojo/public/tools/bindings/minify_with_terser.py new file mode 100755 index 00000000..cefee7a4 --- /dev/null +++ b/utils/codegen/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/codegen/ipc/mojo/public/tools/bindings/mojom.gni b/utils/codegen/ipc/mojo/public/tools/bindings/mojom.gni new file mode 100644 index 00000000..3f6e54e0 --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/mojom.gni @@ -0,0 +1,2118 @@ +# 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("//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/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 + # configuration. This may be disabled when building external projects which + # depend on //mojo but which do not need/want all of the Chromium tree + # dependencies that come with typemapping. + # + # Note that (perhaps obviously) a huge amount of Chromium code will not build + # with typemapping disabled, so it is never valid to set this to |false| in + # any Chromium build configuration. + enable_mojom_typemapping = true + + # 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. 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 +# value, so we limit the behavior to desktop builds for now. There is some +# redundancy in the conditions here, but it is tolerated for clarity: +# We're explicit about Mac, Windows, and Linux desktop support, but it's +# also necessary to ensure that bindings in alternate toolchains (e.g. +# NaCl IRT) are always consistent with the default toolchain; for that +# reason we always enable scrambling within NaCl toolchains when possible, +# as well as within the default toolchain when NaCl is enabled. +# +# Finally, because we *cannot* enable scrambling on Chrome OS (it would break +# ARC) we have to explicitly opt out there even when NaCl is enabled (and +# consequently also when building for NaCl toolchains.) For this reason we +# check |target_os| explicitly, as it's consistent across all toolchains. +# +# TODO(crbug.com/1052397): Remove !chromeos_is_browser_only once +# lacros-chrome switches to target_os="chromeos" +enable_scrambled_message_ids = + enable_mojom_message_id_scrambling && + (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" +_mojom_library_root = "$_mojom_tools_root/mojom/mojom" +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", + "$_mojom_library_root/generate/template_expander.py", + "$_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_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) { + declare_args() { + # The path to a file whose contents can be used as the basis for a message + # ID scrambling salt. + mojom_message_id_salt_path = "//chrome/VERSION" + } + + assert(mojom_message_id_salt_path != "") + message_scrambling_args = [ + "--scrambled_message_id_salt_path", + rebase_path(mojom_message_id_salt_path, root_build_dir), + ] + message_scrambling_inputs = [ mojom_message_id_salt_path ] +} else { + message_scrambling_args = [] + message_scrambling_inputs = [] +} + +# Generates targets for building C++, JavaScript and Java bindings from mojom +# files. The output files will go under the generated file directory tree with +# the same path as each input file. +# +# Other targets should depend on one of these generated targets (where "foo" +# is the target name): +# +# foo +# C++ bindings. +# +# foo_blink +# C++ bindings using Blink standard types. +# +# foo_java +# Java bindings. +# +# foo_js +# JavaScript bindings; used as compile-time dependency. +# +# foo_js_data_deps +# JavaScript bindings; used as run-time dependency. +# +# Parameters: +# +# sources (optional if one of the deps sets listed below is present) +# List of source .mojom files to compile. +# +# deps (optional) +# Note: this can contain only other mojom targets. +# +# DEPRECATED: This is synonymous with public_deps because all mojom +# dependencies must be public by design. Please use public_deps. +# +# public_deps (optional) +# Note: this can contain only other mojom targets. +# +# parser_deps (optional) +# List of non-mojom targets required for the mojom sources to be parsed. +# +# import_dirs (optional) +# List of import directories that will get added when processing sources. +# +# input_root_override (optional) +# Root path for the .mojom files used to generate the namespaces for +# interfaces. Useful with targets outside //, e.g. in parent directories +# above "//". The default input root is // +# Example: Vivaldi's source root is "//vivaldi/", +# and "//vivaldi/chromium/" is "//" +# In such cases, not using this argument lead to the output files being +# located in different directories than expected. +# +# testonly (optional) +# +# visibility (optional) +# +# visibility_blink (optional) +# The value to use for visibility for the blink variant. If unset, +# |visibility| is used. +# +# cpp_only (optional) +# If set to true, only the C++ bindings targets will be generated. +# +# NOTE: If the global |enable_mojom_fuzzer| build arg is true, JS bindings +# will still be generated even when |cpp_only| is set to |true|, unless +# you also set |enable_fuzzing| to |false| in your mojom target. +# +# cpp_typemaps (optional) +# A list of typemaps to be applied to the generated C++ bindings for this +# mojom target. Note that this only applies to the non-Blink variant of +# generated C++ bindings. +# +# Every typemap is a GN scope describing how one or more mojom types maps +# to a non-mojom C++ type, including necessary deps and headers required +# for the mapping to work. See the Typemaps section below. +# +# blink_cpp_typemaps (optional) +# Same as above, but for the Blink variant of generated C++ bindings. +# +# cpp_proxy_target (optional) +# The name of a target which all C++ dependencies will link against +# instead of linking directly against this mojom target's generated C++ +# sources. Normally when declaring invoking the mojom("foo") target, GN +# emits a source_set or component target named "foo" which encompasses the +# default variant of generated C++ bindings. This changes that to instead +# emit a group("foo") which merely forwards public_deps to the named +# `cpp_proxy_target`. That target must in turn depend on +# "foo_cpp_sources". +# +# This is useful primarily in conjunction with export_define et al to +# embed generated C++ bindings within an existing component target. +# +# blink_cpp_proxy_target (optional) +# Same concept as `cpp_proxy_target` above, but affects the generated +# "foo_blink" Blink-variant C++ bindings. +# +# cpp_configs (optional) +# A list of extra configs to apply to the default variant of generated C++ +# bindings. +# +# blink_cpp_configs (optional) +# A list of extra configs to apply to the Blink variant of generated C++ +# bindings. +# +# mojom_source_deps (optional) +# A list of mojoms this target depends upon. This is equivalent to +# public_deps except that the C++ bindings depend on each of the named +# "foo" targets' "foo_cpp_sources" rather than on foo's +# `cpp_proxy_target`. It only makes sense to use this for dependencies +# that set `cpp_proxy_target`, and only when the dependent mojom() would +# otherwise have circular dependencies with that proxy target. +# +# mojom_blink_source_deps (optional) +# Same as above but depends on "foo_blink_cpp_sources" and is used for +# dependencies that specify a `blink_cpp_proxy_target`. +# +# generate_java (optional) +# If set to true, Java bindings are generated for Android builds. If +# |cpp_only| is set to true, it overrides this to prevent generation of +# Java bindings. +# +# 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|. +# +# support_lazy_serialization (optional) +# If set to |true|, generated C++ bindings will effectively prefer to +# transmit messages in an unserialized form when going between endpoints +# in the same process. This avoids the runtime cost of serialization, +# deserialization, and validation logic at the expensive of increased +# code size. Defaults to |false|. +# +# disable_variants (optional) +# If |true|, no variant sources will be generated for the target. Defaults +# to |false|. +# +# disallow_native_types (optional) +# If set to |true|, mojoms in this target may not apply the [Native] +# attribute to struct or enum declarations. This avoids emitting code +# which depends on legacy IPC serialization. Default is |false|, meaning +# [Native] types are allowed. +# +# disallow_interfaces (optional) +# If set to |true|, mojoms in this target may not define interfaces. +# Generates bindings with a smaller set of dependencies. Defaults to +# |false|. +# +# scramble_message_ids (optional) +# If set to |true| (the default), generated mojom interfaces will use +# scrambled ordinal identifiers in encoded messages. +# +# component_output_prefix (optional) +# The prefix to use for the output_name of any component library emitted +# for generated C++ bindings. If this is omitted, C++ bindings targets are +# emitted as source_sets instead. Because this controls the name of the +# output shared library binary in the root output directory, it must be +# unique across the entire build configuration. +# +# This is required if |component_macro_prefix| is specified. +# +# component_macro_prefix (optional) +# This specifies a macro prefix to use for component export macros and +# should therefore be globally unique in the project. For example if this +# is "FOO_BAR", then the generated C++ sources will be built with +# IS_FOO_BAR_{suffix}_IMPL defined, and the generated public headers will +# annotate public symbol definitions with +# COMPONENT_EXPORT(FOO_BAR_{suffix}). "suffix" in this case depends on +# which internal subtarget is generating the code (e.g. "SHARED", or a +# variant name like "BLINK"). +# +# enabled_features (optional) +# Definitions in a mojom file can be guarded by an EnableIf attribute. If +# the value specified by the attribute does not match any items in the +# list of enabled_features, the definition will be disabled, with no code +# emitted for it. +# +# generate_closure_exports (optional) +# Generates JS lite bindings will use goog.provide and goog.require +# annotations to export its symbols and import core Mojo bindings support +# and other mojom dependency modules. Use this if you plan to compile your +# bindings into a larger JS binary. Defaults to |false|, instead +# generating JS lite bindings which assume they will be manually loaded in +# correct dependency order. Note that this only has an effect if +# the |enable_mojom_closure_compile| global arg is set to |true| as well. +# +# 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. +# +# extra_cpp_template_paths (optional) +# List of extra C++ templates that are used to generate additional source +# and/or header files. The templates should end with extension ".tmpl". +# +# 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". 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 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 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 +# export settings for classes. The first three are for the chromium variant, and +# the last three are for the blink variant. These parameters can also override +# |component_macro_prefix| for a specific variant, allowing e.g. one variant +# to be linked into a larger non-mojom component target, while all other +# variants get their own unique component target. +# export_class_attribute (optional) +# The attribute to add to the class declaration. e.g. "CONTENT_EXPORT" +# export_define (optional) +# A define to be added to the source_set which is needed by the export +# header. e.g. "CONTENT_IMPLEMENTATION=1" +# export_header (optional) +# A header to be added to the generated bindings to support the component +# build. e.g. "content/common/content_export.h" +# export_class_attribute_blink (optional) +# export_define_blink (optional) +# export_header_blink (optional) +# These three parameters are the blink variants of the previous 3. +# +# The following parameters are used to correct component build dependencies. +# They are needed so mojom-mojom dependencies follow the rule that dependencies +# on a source set in another component are replaced by a dependency on the +# containing component. The first two are for the chromium variant; the other +# two are for the blink variant. +# overridden_deps (optional) +# The list of mojom deps to be overridden. +# component_deps (optional) +# The list of component deps to add to replace overridden_deps. +# overridden_deps_blink (optional) +# component_deps_blink (optional) +# These two parameters are the blink variants of the previous two. +# +# check_includes_blink (optional) +# Overrides the check_includes variable for the blink variant. +# If check_includes_blink is not defined, the check_includes variable +# retains its original value. +# +# Typemaps +# ======== +# The cpp_typemaps and blink_cpp_typemaps each specify an optional list of +# typemapping configurations. Each configuration is a GN scope with metadata +# describing what and how to map. +# +# Typemap scope parameters: +# types +# A list of type specifications for this typemap. Each type specification +# is a nested GN scope which can be expressed with the following syntax: +# +# { +# mojom = "foo.mojom.Bar" +# cpp = "::foo::LegitBar" +# move_only = true +# # etc... +# } +# +# Each type specification supports the following values: +# +# mojom (required) +# The fully qualified name of a mojom type to be mapped. This is a +# string like "foo.mojom.Bar". +# +# cpp (required) +# The fully qualified name of the C++ type to which the mojom type +# should be mapped in generated bindings. This is a string like +# "::base::Value" or "std::vector<::base::Value>". +# +# 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 +# traits for the type must define IsNull and SetToNull methods. +# +# When false, nullable fields are represented by wrapping the C++ +# type with absl::optional, and null values are simply +# absl::nullopt. +# +# Additional typemap scope parameters: +# +# traits_headers (optional) +# Headers which must be included in the generated mojom in order for +# serialization to be possible. This generally means including at least +# the header for the corresponding mojom traits definitions. +# +# traits_private_headers (optional) +# Headers which must be included in generated C++ serialization code for +# a mojom using the typemap. This should be used only when including a +# header in |traits_headers| is problematic for compilation, as is +# sometimes the case with legacy IPC message headers. +# +# traits_sources (optional) +# The references to the source files (typically a single .cc and .h file) +# defining an appropriate set of EnumTraits or StructTraits, etc for the +# the type-mapping. Using this will cause the listed sources to be +# integrated directly into the dependent mojom's generated type-mapping +# targets. +# +# Prefer using |traits_public_deps| over inlined |traits_sources|, as this +# will generally lead to easier build maintenance over time. +# +# NOTE: If a typemap is shared by Blink and non-Blink bindings, you cannot +# use this and MUST use |traits_public_deps| to reference traits built +# within a separate target. +# +# traits_deps / traits_public_deps (optional) +# Any dependencies of sources in |traits_headers| or |traits_sources| must +# be listed here. +# +template("mojom") { + assert( + defined(invoker.sources) || defined(invoker.deps) || + defined(invoker.public_deps), + "\"sources\" or \"deps\" must be defined for the $target_name template.") + + if (defined(invoker.export_class_attribute) || + defined(invoker.export_define) || defined(invoker.export_header)) { + assert(defined(invoker.export_class_attribute)) + assert(defined(invoker.export_define) || defined(invoker.cpp_configs)) + assert(defined(invoker.export_header)) + } + if (defined(invoker.export_class_attribute_blink) || + defined(invoker.export_define_blink) || + defined(invoker.export_header_blink)) { + assert(defined(invoker.export_class_attribute_blink)) + assert(defined(invoker.export_define_blink) || + defined(invoker.blink_cpp_configs)) + assert(defined(invoker.export_header_blink)) + + # Not all platforms use the Blink variant, so make sure GN doesn't complain + # about these values being inconsequential. + not_needed(invoker, + [ + "export_class_attribute_blink", + "export_define_blink", + "export_header_blink", + ]) + } + if (defined(invoker.overridden_deps) || defined(invoker.component_deps)) { + assert(defined(invoker.overridden_deps)) + assert(defined(invoker.component_deps)) + } + + if (defined(invoker.overridden_deps_blink) || + defined(invoker.component_deps_blink)) { + assert(defined(invoker.overridden_deps_blink)) + assert(defined(invoker.component_deps_blink)) + } + + # Type-mapping may be disabled or we may not generate C++ bindings. + not_needed(invoker, + [ + "cpp_typemaps", + "blink_cpp_typemaps", + ]) + + require_full_cpp_deps = + !defined(invoker.disallow_native_types) || + !invoker.disallow_native_types || !defined(invoker.disallow_interfaces) || + !invoker.disallow_interfaces + + all_deps = [] + mojom_cpp_deps = [] + if (defined(invoker.deps)) { + all_deps += invoker.deps + mojom_cpp_deps += invoker.deps + } + if (defined(invoker.public_deps)) { + all_deps += invoker.public_deps + mojom_cpp_deps += invoker.public_deps + } + if (defined(invoker.mojom_source_deps)) { + all_deps += invoker.mojom_source_deps + } + if (defined(invoker.mojom_blink_source_deps)) { + all_deps += invoker.mojom_blink_source_deps + } + not_needed([ "mojom_deps" ]) + + if (defined(invoker.component_macro_prefix)) { + assert(defined(invoker.component_output_prefix)) + } + + group("${target_name}__is_mojom") { + } + + # Explicitly ensure that all dependencies (invoker.deps and + # invoker.public_deps) are mojom targets. + group("${target_name}__check_deps_are_all_mojom") { + deps = [] + foreach(d, all_deps) { + name = get_label_info(d, "label_no_toolchain") + toolchain = get_label_info(d, "toolchain") + deps += [ "${name}__is_mojom(${toolchain})" ] + } + } + + sources_list = [] + if (defined(invoker.sources)) { + sources_list = invoker.sources + } + + # Listed sources may be relative to the current target dir, or they may be + # absolute paths, including paths to generated mojom files. While those are + # fine as-is for input references, deriving output paths can be more subtle. + # + # Here we rewrite all source paths to be relative to the root build dir and + # strip any root_gen_dir prefixes. + # + # So for a target in //foo/bar with: + # + # sources = [ + # "a.mojom", + # "b/c.mojom", + # "//baz/d.mojom", + # "$target_gen_dir/e.mojom", + # ] + # + # output_file_base_paths will be: + # + # [ + # "foo/bar/a.mojom", + # "foo/bar/b/c.mojom", + # "baz/d.mojom", + # "foo/bar/e.mojom", + # ] + # + # This result is essentially a list of base filename paths which are suitable + # for the naming of any generated output files derived from their + # corresponding input mojoms. These paths are always considered to be relative + # to root_gen_dir. + if (defined(invoker.input_root_override)) { + source_abspaths = rebase_path(sources_list, invoker.input_root_override) + } else { + source_abspaths = rebase_path(sources_list, "//") + } + output_file_base_paths = [] + foreach(path, source_abspaths) { + output_file_base_paths += + [ string_replace(path, rebase_path(root_gen_dir, "//") + "/", "") ] + } + + # Sanity check that either all input files have a .mojom extension, or + # all input files have a .test-mojom extension AND |testonly| is |true|. + sources_list_filenames = + process_file_template(sources_list, "{{source_file_part}}") + sources_list_filenames_with_mojom_extension = + process_file_template(sources_list, "{{source_name_part}}.mojom") + if (sources_list_filenames != sources_list_filenames_with_mojom_extension) { + sources_list_filenames_with_test_mojom_extension = + process_file_template(sources_list, "{{source_name_part}}.test-mojom") + if (sources_list_filenames == + sources_list_filenames_with_test_mojom_extension) { + assert( + defined(invoker.testonly) && invoker.testonly, + "mojom targets for .test-mojom files must set |testonly| to |true|") + } else { + assert( + false, + "One or more mojom files has an invalid extension. The only " + + "allowed extensions are .mojom and .test-mojom, and any given " + + "mojom target must use one or the other exclusively.") + } + } + + build_metadata_filename = "$target_gen_dir/$target_name.build_metadata" + build_metadata = { + } + 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", + target_gen_dir) ] + } + write_file(build_metadata_filename, build_metadata, "json") + + 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) { + _label = get_label_info(dep, "label_no_toolchain") + parser_deps += [ "${_label}__parser" ] + } + if (defined(invoker.parser_deps)) { + parser_deps += invoker.parser_deps + } + if (sources_list == []) { + # Even without sources we generate a parser target to at least forward + # other parser dependencies. + group(parser_target_name) { + public_deps = parser_deps + } + } else { + enabled_features = [] + if (defined(invoker.enabled_features)) { + enabled_features += invoker.enabled_features + } + if (is_posix) { + enabled_features += [ "is_posix" ] + } + if (is_android) { + enabled_features += [ "is_android" ] + } else if (is_chromeos_ash) { + enabled_features += [ + "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) { + enabled_features += [ "is_linux" ] + } else if (is_mac) { + enabled_features += [ "is_mac" ] + } else if (is_win) { + enabled_features += [ "is_win" ] + } + + 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 + ply_sources + [ build_metadata_filename ] + sources = sources_list + public_deps = parser_deps + outputs = [] + foreach(base_path, output_file_base_paths) { + filename = get_path_info(base_path, "file") + dirname = get_path_info(base_path, "dir") + outputs += [ "$root_gen_dir/$dirname/${filename}-module" ] + } + + filelist = [] + foreach(source, sources_list) { + filelist += [ rebase_path(source, root_build_dir) ] + } + + # 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("//.", root_build_dir), + "--input-root", + rebase_path(root_gen_dir, root_build_dir), + + "--output-root", + rebase_path(root_gen_dir, root_build_dir), + + "--mojom-file-list=" + rebase_path(rsp_file, root_build_dir), + + "--check-imports", + rebase_path(build_metadata_filename, root_build_dir), + ] + + if (defined(invoker.input_root_override)) { + args += [ + "--input-root", + rebase_path(invoker.input_root_override, root_build_dir), + ] + } + + foreach(enabled_feature, enabled_features) { + args += [ + "--enable-feature", + enabled_feature, + ] + } + + if (defined(invoker.webui_module_path)) { + args += [ + "--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", + ] + } + } + } + } + + generator_cpp_message_ids_target_name = "${target_name}__generate_message_ids" + + # Generate code that is shared by different variants. + if (sources_list != []) { + base_dir = "//" + if (defined(invoker.input_root_override)) { + base_dir = invoker.input_root_override + } + + common_generator_args = [ + "--use_bundled_pylibs", + "-o", + rebase_path(root_gen_dir, root_build_dir), + "generate", + "-d", + rebase_path(base_dir, root_build_dir), + "-I", + rebase_path("//", root_build_dir), + "--bytecode_path", + rebase_path("$root_gen_dir/mojo/public/tools/bindings", root_build_dir), + ] + if (defined(invoker.input_root_override)) { + common_generator_args += [ + "-I", + rebase_path(invoker.input_root_override, root_build_dir), + ] + } + + if (defined(invoker.disallow_native_types) && + invoker.disallow_native_types) { + common_generator_args += [ "--disallow_native_types" ] + } + + if (defined(invoker.disallow_interfaces) && invoker.disallow_interfaces) { + common_generator_args += [ "--disallow_interfaces" ] + } + + if (defined(invoker.import_dirs)) { + foreach(import_dir, invoker.import_dirs) { + common_generator_args += [ + "-I", + rebase_path(import_dir, root_build_dir), + ] + } + } + + if (defined(invoker.component_macro_prefix)) { + shared_component_export_macro = + "COMPONENT_EXPORT(${invoker.component_macro_prefix}_SHARED)" + shared_component_impl_macro = + "IS_${invoker.component_macro_prefix}_SHARED_IMPL" + shared_component_output_name = "${invoker.component_output_prefix}_shared" + } else if (defined(invoker.export_class_attribute_shared) || + defined(invoker.export_class_attribute)) { + if (defined(invoker.export_class_attribute_shared)) { + assert(defined(invoker.export_header_shared)) + shared_component_export_macro = invoker.export_class_attribute_shared + shared_component_impl_macro = invoker.export_define_shared + } else { + assert(!defined(invoker.export_header_shared)) + + # If no explicit shared attribute/define was provided by the invoker, + # we derive some reasonable settings frorm the default variant. + shared_component_export_macro = "COMPONENT_EXPORT(MOJOM_SHARED_" + + invoker.export_class_attribute + ")" + shared_component_impl_macro = + "IS_MOJOM_SHARED_" + invoker.export_class_attribute + "_IMPL" + } + + if (defined(invoker.component_output_prefix)) { + shared_component_output_name = + "${invoker.component_output_prefix}_shared" + } else { + shared_component_output_name = "${target_name}_shared" + } + } + + action(generator_cpp_message_ids_target_name) { + allow_remote = true + script = mojom_generator_script + inputs = mojom_generator_sources + jinja2_sources + sources = sources_list + + [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ] + deps = [ + ":$parser_target_name", + "//mojo/public/tools/bindings:precompile_templates", + ] + if (defined(invoker.parser_deps)) { + deps += invoker.parser_deps + } + outputs = [] + args = common_generator_args + filelist = [] + foreach(source, sources_list) { + 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" ] + } + + # 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=" + rebase_path(rsp_file, root_build_dir), + "--generate_non_variant_code", + "--generate_message_ids", + "-g", + "c++", + ] + + if (!defined(invoker.scramble_message_ids) || + invoker.scramble_message_ids) { + inputs += message_scrambling_inputs + args += message_scrambling_args + } + } + + generator_shared_target_name = "${target_name}_shared__generator" + + action(generator_shared_target_name) { + allow_remote = true + visibility = [ ":*" ] + script = mojom_generator_script + inputs = mojom_generator_sources + jinja2_sources + sources = sources_list + + [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ] + deps = [ + ":$parser_target_name", + "//mojo/public/tools/bindings:precompile_templates", + ] + if (defined(invoker.parser_deps)) { + deps += invoker.parser_deps + } + + outputs = [] + args = common_generator_args + filelist = [] + foreach(source, sources_list) { + 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", + "$root_gen_dir/$base_path-shared.h", + ] + } + + # 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=" + rebase_path(rsp_file, root_build_dir), + "--generate_non_variant_code", + "-g", + "c++", + ] + + if (defined(shared_component_export_macro)) { + args += [ + "--export_attribute", + shared_component_export_macro, + "--export_header", + "base/component_export.h", + ] + } + + # Enable adding annotations to generated C++ headers that are used for + # cross-references in CodeSearch. + if (enable_kythe_annotations) { + args += [ "--enable_kythe_annotations" ] + } + } + } else { + group(generator_cpp_message_ids_target_name) { + } + } + + shared_cpp_sources_target_name = "${target_name}_shared_cpp_sources" + source_set(shared_cpp_sources_target_name) { + 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", + "$root_gen_dir/$base_path-shared.h", + ] + } + public_deps += [ ":$generator_shared_target_name" ] + } + if (require_full_cpp_deps) { + public_deps += [ "//mojo/public/cpp/bindings" ] + } else { + public_deps += [ "//mojo/public/cpp/bindings:bindings_base" ] + } + foreach(d, all_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append shared_cpp_sources_suffix + # to get the cpp dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + public_deps += [ "${full_name}_shared" ] + } + if (defined(shared_component_impl_macro)) { + defines = [ shared_component_impl_macro ] + } + } + + shared_cpp_library_target_name = "${target_name}_shared" + if (defined(shared_component_output_name)) { + component(shared_cpp_library_target_name) { + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + output_name = "$shared_component_output_name" + public_deps = [ ":$shared_cpp_sources_target_name" ] + } + } else { + group(shared_cpp_library_target_name) { + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + public_deps = [ ":$shared_cpp_sources_target_name" ] + } + } + + 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 + # separately here. + + generator_mojolpm_proto_target_name = + "${target_name}_mojolpm_proto_generator" + + action(generator_mojolpm_proto_target_name) { + allow_remote = true + script = mojom_generator_script + inputs = mojom_generator_sources + jinja2_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", + ] + + outputs = [] + args = common_generator_args + filelist = [] + + # 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" ] + } + + 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=" + rebase_path(rsp_file, root_build_dir), + "--generate_non_variant_code", + "-g", + "mojolpm", + ] + } + + mojolpm_proto_target_name = "${target_name}_mojolpm_proto" + if (defined(invoker.sources)) { + 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( + 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 = "." + proto_deps = [ ":$generator_mojolpm_proto_target_name" ] + link_deps = [ "//mojo/public/tools/fuzzers:mojolpm_proto" ] + + foreach(d, all_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append mojolpm_proto_suffix + # to get the proto dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + proto_deps += [ "${full_name}_mojolpm_proto" ] + link_deps += [ "${full_name}_mojolpm_proto" ] + } + } + } else { + group(mojolpm_proto_target_name) { + testonly = true + public_deps = [ "//mojo/public/tools/fuzzers:mojolpm_proto" ] + if (defined(generator_shared_target_name)) { + public_deps += [ ":$generator_shared_target_name" ] + } + foreach(d, all_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append #mojolpm_proto_suffix + # to get the proto dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + public_deps += [ "${full_name}_mojolpm_proto" ] + } + } + } + } + + # Generate code for variants. + default_variant = { + component_macro_suffix = "" + } + if ((!defined(invoker.disable_variants) || !invoker.disable_variants) && + use_blink) { + blink_variant = { + variant = "blink" + component_macro_suffix = "_BLINK" + for_blink = true + } + enabled_configurations = [ + default_variant, + blink_variant, + ] + } else { + enabled_configurations = [ default_variant ] + } + foreach(bindings_configuration, enabled_configurations) { + cpp_only = false + if (defined(invoker.cpp_only)) { + cpp_only = invoker.cpp_only + } + variant_suffix = "" + if (defined(bindings_configuration.variant)) { + variant = bindings_configuration.variant + variant_suffix = "_${variant}" + cpp_only = true + } + + cpp_typemap_configs = [] + export_defines = [] + export_defines_overridden = false + force_source_set = false + proxy_target = "" + extra_configs = [] + output_visibility = [] + output_visibility = [ "*" ] + cpp_source_deps = [] + if (defined(bindings_configuration.for_blink) && + bindings_configuration.for_blink) { + if (defined(invoker.blink_cpp_typemaps)) { + cpp_typemap_configs = invoker.blink_cpp_typemaps + } + if (defined(invoker.export_define_blink)) { + export_defines_overridden = true + export_defines = [ invoker.export_define_blink ] + force_source_set = true + } + if (defined(invoker.blink_cpp_configs)) { + extra_configs += invoker.blink_cpp_configs + } + if (defined(invoker.blink_cpp_proxy_target)) { + proxy_target = invoker.blink_cpp_proxy_target + } + if (defined(invoker.visibility_blink)) { + output_visibility = [] + output_visibility = invoker.visibility_blink + } + if (defined(invoker.mojom_blink_source_deps)) { + cpp_source_deps = invoker.mojom_blink_source_deps + } + } else { + if (defined(invoker.cpp_typemaps)) { + cpp_typemap_configs = invoker.cpp_typemaps + } + if (defined(invoker.export_define)) { + export_defines_overridden = true + export_defines = [ invoker.export_define ] + force_source_set = true + } + if (defined(invoker.cpp_configs)) { + extra_configs += invoker.cpp_configs + } + if (defined(invoker.cpp_proxy_target)) { + proxy_target = invoker.cpp_proxy_target + } + if (defined(invoker.visibility)) { + output_visibility = [] + output_visibility = invoker.visibility + } + if (defined(invoker.mojom_source_deps)) { + cpp_source_deps = invoker.mojom_source_deps + } + } + not_needed([ "cpp_typemap_configs" ]) + if (proxy_target != "") { + group("${target_name}${variant_suffix}__has_cpp_proxy") { + } + } + + if (!export_defines_overridden && defined(invoker.component_macro_prefix)) { + output_name_override = + "${invoker.component_output_prefix}${variant_suffix}" + export_defines = + [ "IS_${invoker.component_macro_prefix}" + + "${bindings_configuration.component_macro_suffix}_IMPL" ] + } + + generate_java = false + if (!cpp_only && defined(invoker.generate_java)) { + generate_java = invoker.generate_java + } + type_mappings_target_name = "${target_name}${variant_suffix}__type_mappings" + 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)) { + variant_dash_suffix = "-${variant}" + } + generator_cpp_output_suffixes += [ + "${variant_dash_suffix}-forward.h", + "${variant_dash_suffix}-import-headers.h", + "${variant_dash_suffix}-test-utils.h", + "${variant_dash_suffix}.cc", + "${variant_dash_suffix}.h", + ] + + generator_target_name = "${target_name}${variant_suffix}__generator" + + # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. + action(generator_target_name) { + allow_remote = true + visibility = [ ":*" ] + script = mojom_generator_script + inputs = mojom_generator_sources + jinja2_sources + 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 + } + outputs = [] + args = common_generator_args + export_args + filelist = [] + foreach(source, sources_list) { + 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.h", + "$root_gen_dir/${base_path}${variant_dash_suffix}.cc", + "$root_gen_dir/${base_path}${variant_dash_suffix}.h", + ] + 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", + ] + } + } + + # 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=" + rebase_path("$rsp_file", root_build_dir), + "-g", + ] + + if (generate_mojolpm_fuzzing && + !defined(bindings_configuration.variant)) { + args += [ "c++,mojolpm" ] + } else { + args += [ "c++" ] + } + + if (defined(bindings_configuration.variant)) { + args += [ + "--variant", + bindings_configuration.variant, + ] + } + + args += [ + "--typemap", + rebase_path(type_mappings_path, root_build_dir), + ] + + if (defined(bindings_configuration.for_blink) && + bindings_configuration.for_blink) { + args += [ "--for_blink" ] + } + + if (defined(invoker.support_lazy_serialization) && + invoker.support_lazy_serialization) { + args += [ "--support_lazy_serialization" ] + } + + if (enable_kythe_annotations) { + args += [ "--enable_kythe_annotations" ] + } + + if (!defined(invoker.scramble_message_ids) || + invoker.scramble_message_ids) { + inputs += message_scrambling_inputs + args += message_scrambling_args + } + + if (defined(invoker.extra_cpp_template_paths)) { + foreach(extra_cpp_template, invoker.extra_cpp_template_paths) { + args += [ + "--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") + foreach(base_path, output_file_base_paths) { + template_file_name = get_path_info("$extra_cpp_template", "name") + outputs += [ "$root_gen_dir/${base_path}${variant_dash_suffix}-${template_file_name}" ] + } + } + } + } + } + + # 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. + _typemap_config_filename = + "$target_gen_dir/${target_name}${variant_suffix}.typemap_config" + _typemap_stamp_filename = "${_typemap_config_filename}.validated" + _typemap_validator_target_name = "${type_mappings_target_name}__validator" + _rebased_typemap_configs = [] + foreach(config, cpp_typemap_configs) { + _rebased_config = { + } + _rebased_config = config + if (defined(config.traits_headers)) { + _rebased_config.traits_headers = [] + _rebased_config.traits_headers = + rebase_path(config.traits_headers, "//") + } + if (defined(config.traits_private_headers)) { + _rebased_config.traits_private_headers = [] + _rebased_config.traits_private_headers = + rebase_path(config.traits_private_headers, "//") + } + _rebased_typemap_configs += [ _rebased_config ] + } + write_file(_typemap_config_filename, _rebased_typemap_configs, "json") + _mojom_target_name = 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, root_build_dir), + rebase_path(_typemap_stamp_filename, root_build_dir), + ] + } + + action(type_mappings_target_name) { + allow_remote = true + inputs = + mojom_generator_sources + jinja2_sources + [ _typemap_stamp_filename ] + outputs = [ type_mappings_path ] + script = "$mojom_generator_root/generate_type_mappings.py" + deps = [ ":$_typemap_validator_target_name" ] + args = [ + "--output", + 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") + dependency_output = "${name}${variant_suffix}__type_mappings" + dependency_target = "${dependency_output}(${toolchain})" + deps += [ dependency_target ] + dependency_output_dir = + get_label_info(dependency_output, "target_gen_dir") + dependency_name = get_label_info(dependency_output, "name") + dependency_path = "$dependency_output_dir/${dependency_name}" + sources += [ dependency_path ] + args += [ + "--dependency", + rebase_path(dependency_path, root_build_dir), + ] + } + + # Newer GN-based typemaps are aggregated into a single config. + inputs += [ _typemap_config_filename ] + args += [ + "--cpp-typemap-config", + rebase_path(_typemap_config_filename, root_build_dir), + ] + } + + group("${target_name}${variant_suffix}_headers") { + public_deps = [] + if (sources_list != []) { + public_deps += [ + ":$generator_cpp_message_ids_target_name", + ":$generator_shared_target_name", + ":$generator_target_name", + ] + } + foreach(d, all_deps) { + full_name = get_label_info("$d", "label_no_toolchain") + public_deps += [ "${full_name}${variant_suffix}_headers" ] + } + if (defined(bindings_configuration.for_blink) && + bindings_configuration.for_blink) { + public_deps += [ "//mojo/public/cpp/bindings:wtf_support" ] + } + } + + js_data_deps_target_name = target_name + "_js_data_deps" + not_needed([ "js_data_deps_target_name" ]) + + if (!force_source_set && defined(invoker.component_macro_prefix)) { + sources_target_type = "component" + } else { + sources_target_type = "source_set" + } + + output_target_name = "${target_name}${variant_suffix}" + if (proxy_target != "") { + group(output_target_name) { + public_deps = [ proxy_target ] + visibility = output_visibility + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + } + sources_target_name = "${output_target_name}_cpp_sources" + } else { + sources_target_name = output_target_name + } + + target(sources_target_type, sources_target_name) { + if (defined(output_name_override)) { + output_name = output_name_override + } + 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 = [] + foreach(base_path, output_file_base_paths) { + foreach(suffix, generator_cpp_output_suffixes) { + sources += [ "$root_gen_dir/${base_path}$suffix" ] + } + } + } + deps = [ + ":$generator_cpp_message_ids_target_name", + "//mojo/public/cpp/bindings:struct_traits", + "//mojo/public/interfaces/bindings:bindings_headers", + ] + public_deps = [ + ":$shared_cpp_library_target_name", + "//base", + ] + if (require_full_cpp_deps) { + public_deps += [ "//mojo/public/cpp/bindings" ] + } else { + public_deps += [ "//mojo/public/cpp/bindings:bindings_base" ] + } + + if (sources_list != []) { + public_deps += [ ":$generator_target_name" ] + } + foreach(d, mojom_cpp_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}${variant_suffix}" ] + } + foreach(d, cpp_source_deps) { + full_name = get_label_info(d, "label_no_toolchain") + public_deps += [ + "${full_name}${variant_suffix}__has_cpp_proxy", + "${full_name}${variant_suffix}_cpp_sources", + ] + } + if (defined(bindings_configuration.for_blink) && + bindings_configuration.for_blink) { + if (defined(invoker.overridden_deps_blink)) { + foreach(d, invoker.overridden_deps_blink) { + # 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}${variant_suffix}" ] + } + public_deps += invoker.component_deps_blink + } + if (defined(invoker.check_includes_blink)) { + check_includes = invoker.check_includes_blink + } + } else { + if (defined(invoker.check_includes_blink)) { + not_needed(invoker, [ "check_includes_blink" ]) + } + if (defined(invoker.overridden_deps)) { + foreach(d, invoker.overridden_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}${variant_suffix}" ] + } + public_deps += invoker.component_deps + } + } + foreach(config, cpp_typemap_configs) { + if (defined(config.traits_sources)) { + sources += config.traits_sources + } + if (defined(config.traits_deps)) { + deps += config.traits_deps + } + if (defined(config.traits_public_deps)) { + public_deps += config.traits_public_deps + } + } + if (defined(bindings_configuration.for_blink) && + bindings_configuration.for_blink) { + public_deps += [ "//mojo/public/cpp/bindings:wtf_support" ] + } + } + + 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 != []) { + action(java_generator_target_name) { + script = mojom_generator_script + inputs = mojom_generator_sources + jinja2_sources + sources = sources_list + deps = [ + ":$parser_target_name", + ":$type_mappings_target_name", + "//mojo/public/tools/bindings:precompile_templates", + ] + outputs = [] + args = common_generator_args + filelist = [] + foreach(source, sources_list) { + filelist += [ rebase_path(source, root_build_dir) ] + } + foreach(base_path, output_file_base_paths) { + outputs += [ "$root_gen_dir/$base_path.srcjar" ] + } + + response_file_contents = filelist + + args += [ + "--filelist={{response_file_name}}", + "-g", + "java", + ] + + if (!defined(invoker.scramble_message_ids) || + invoker.scramble_message_ids) { + inputs += message_scrambling_inputs + args += message_scrambling_args + } + } + } else { + group(java_generator_target_name) { + } + } + + java_srcjar_target_name = target_name + "_java_sources" + + action(java_srcjar_target_name) { + script = "//build/android/gyp/zip.py" + inputs = [] + if (output_file_base_paths != []) { + foreach(base_path, output_file_base_paths) { + inputs += [ "$root_gen_dir/${base_path}.srcjar" ] + } + } + output = "$target_gen_dir/$target_name.srcjar" + outputs = [ output ] + rebase_inputs = rebase_path(inputs, root_build_dir) + rebase_output = rebase_path(output, root_build_dir) + args = [ + "--input-zips=$rebase_inputs", + "--output=$rebase_output", + ] + deps = [] + if (sources_list != []) { + deps = [ ":$java_generator_target_name" ] + } + } + + java_target_name = target_name + "_java" + android_library(java_target_name) { + forward_variables_from(invoker, [ "enable_bytecode_checks" ]) + deps = [ + "//mojo/public/java:bindings_java", + "//mojo/public/java:system_java", + "//third_party/androidx:androidx_annotation_annotation_java", + ] + + # Disable warnings/checks on these generated files. + chromium_code = false + + foreach(d, all_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append "_java" to get the java + # dependency name. + full_name = get_label_info(d, "label_no_toolchain") + deps += [ "${full_name}_java" ] + } + + srcjar_deps = [ ":$java_srcjar_target_name" ] + } + } + } + + 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") + } + + 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" + + action(generator_js_target_name) { + script = mojom_generator_script + inputs = mojom_generator_sources + jinja2_sources + sources = sources_list + deps = [ + ":$parser_target_name", + "//mojo/public/tools/bindings:precompile_templates", + ] + if (defined(invoker.parser_deps)) { + deps += invoker.parser_deps + } + outputs = [] + args = common_generator_args + filelist = [] + foreach(source, sources_list) { + 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.m.js", + "$root_gen_dir/$base_path-lite.js", + "$root_gen_dir/$base_path-lite-for-compile.js", + ] + + if (defined(invoker.webui_module_path) && + !use_typescript_for_target) { + outputs += [ "$root_gen_dir/mojom-webui/$base_path-webui.js" ] + } + } + + response_file_contents = filelist + + args += [ + "--filelist={{response_file_name}}", + "-g", + "javascript", + ] + + if (defined(invoker.js_generate_struct_deserializers) && + invoker.js_generate_struct_deserializers) { + args += [ "--js_generate_struct_deserializers" ] + } + + if (!defined(invoker.scramble_message_ids) || + invoker.scramble_message_ids) { + inputs += message_scrambling_inputs + args += message_scrambling_args + } + + if (generate_js_fuzzing) { + args += [ "--generate_fuzzing" ] + } + } + } + + js_target_name = target_name + "_js" + group(js_target_name) { + public_deps = [] + if (sources_list != []) { + 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) { + deps = [] + if (sources_list != []) { + data = [] + foreach(base_path, output_file_base_paths) { + data += [ + "$root_gen_dir/${base_path}.js", + "$root_gen_dir/${base_path}.m.js", + "$root_gen_dir/${base_path}-lite.js", + ] + } + deps += [ ":$generator_js_target_name" ] + } + + if (defined(invoker.disallow_native_types) && + invoker.disallow_native_types) { + data_deps = [] + } else { + data_deps = [ "//mojo/public/js:bindings_module" ] + } + foreach(d, all_deps) { + full_name = get_label_info(d, "label_no_toolchain") + data_deps += [ "${full_name}_js_data_deps" ] + } + } + } + + # 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) { + extra_public_deps = [ ":$generator_js_target_name" ] + sources = [] + foreach(base_path, output_file_base_paths) { + sources += [ "$root_gen_dir/${base_path}-lite-for-compile.js" ] + } + externs_list = [ + "${externs_path}/mojo_core.js", + "${externs_path}/pending.js", + ] + deps = [] + if (!defined(invoker.disallow_native_types)) { + deps += [ "//mojo/public/js:bindings_lite_sources" ] + } + foreach(d, all_deps) { + full_name = get_label_info(d, "label_no_toolchain") + deps += [ "${full_name}_js_library_for_compile" ] + } + } + } else { + group(js_library_for_compile_target_name) { + } + } + + # 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) { + extra_public_deps = [ ":$generator_js_target_name" ] + sources = [] + foreach(base_path, output_file_base_paths) { + sources += [ "$root_gen_dir/mojom-webui/${base_path}-webui.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}_webui_js" ] + } + } + } else { + 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_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}_ts__generator" + + action(generator_ts_target_name) { + script = mojom_generator_script + inputs = mojom_generator_sources + jinja2_sources + sources = sources_list + deps = [ + ":$parser_target_name", + "//mojo/public/tools/bindings:precompile_templates", + ] + + 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 + + args += [ + "--filelist={{response_file_name}}", + "-g", + "typescript", + ] + + if (!defined(invoker.scramble_message_ids) || + invoker.scramble_message_ids) { + inputs += message_scrambling_inputs + args += message_scrambling_args + } + + 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. + } + } + } +} + +# A helper for the mojom() template above when component libraries are desired +# for generated C++ bindings units. Supports all the same arguments as mojom() +# except for the optional |component_output_prefix| and |component_macro_prefix| +# arguments. These are instead shortened to |output_prefix| and |macro_prefix| +# and are *required*. +template("mojom_component") { + assert(defined(invoker.output_prefix) && defined(invoker.macro_prefix)) + + mojom(target_name) { + forward_variables_from(invoker, + "*", + [ + "output_prefix", + "macro_prefix", + ]) + component_output_prefix = invoker.output_prefix + component_macro_prefix = invoker.macro_prefix + } +} diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py b/utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py new file mode 100755 index 00000000..8c641c2a --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py @@ -0,0 +1,424 @@ +#!/usr/bin/env python +# Copyright 2013 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""The frontend for the Mojo bindings system.""" + +from __future__ import print_function + +import argparse + +import hashlib +import importlib +import json +import os +import pprint +import re +import struct +import sys + +# Disable lint check for finding modules: +# pylint: disable=F0401 + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + + +sys.path.insert( + 0, + os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "mojom")) + +from mojom.error import Error +import mojom.fileutil as fileutil +from mojom.generate.module import Module +from mojom.generate import template_expander +from mojom.generate import translate +from mojom.generate.generator import WriteFile + +sys.path.append( + os.path.join(_GetDirAbove("mojo"), "tools", "diagnosis")) +import crbug_1001171 + + +_BUILTIN_GENERATORS = { + "c++": "mojom_cpp_generator", + "javascript": "mojom_js_generator", + "java": "mojom_java_generator", + "mojolpm": "mojom_mojolpm_generator", + "typescript": "mojom_ts_generator", +} + +_BUILTIN_CHECKS = { + "attributes": "mojom_attributes_check", + "definitions": "mojom_definitions_check", + "features": "mojom_interface_feature_check", + "restrictions": "mojom_restrictions_check", +} + + +def LoadGenerators(generators_string): + if not generators_string: + return {} # No generators. + + generators = {} + for generator_name in [s.strip() for s in generators_string.split(",")]: + language = generator_name.lower() + if language not in _BUILTIN_GENERATORS: + print("Unknown generator name %s" % generator_name) + sys.exit(1) + generator_module = importlib.import_module( + "generators.%s" % _BUILTIN_GENERATORS[language]) + generators[language] = generator_module + return generators + + +def LoadChecks(checks_string): + if not checks_string: + return {} # No checks. + + checks = {} + for check_name in [s.strip() for s in checks_string.split(",")]: + check = check_name.lower() + if check not in _BUILTIN_CHECKS: + print("Unknown check name %s" % check_name) + sys.exit(1) + check_module = importlib.import_module("checks.%s" % _BUILTIN_CHECKS[check]) + checks[check] = check_module + return checks + + +def MakeImportStackMessage(imported_filename_stack): + """Make a (human-readable) message listing a chain of imports. (Returned + string begins with a newline (if nonempty) and does not end with one.)""" + return ''.join( + reversed(["\n %s was imported by %s" % (a, b) for (a, b) in \ + zip(imported_filename_stack[1:], imported_filename_stack)])) + + +class RelativePath: + """Represents a path relative to the source tree or generated output dir.""" + + def __init__(self, path, source_root, output_dir): + self.path = path + if path.startswith(source_root): + self.root = source_root + elif path.startswith(output_dir): + self.root = output_dir + else: + raise Exception("Invalid input path %s" % path) + + def relative_path(self): + return os.path.relpath( + os.path.abspath(self.path), os.path.abspath(self.root)) + + +def _GetModulePath(path, output_dir): + return os.path.join(output_dir, path.relative_path() + '-module') + + +def ScrambleMethodOrdinals(interfaces, salt): + already_generated = set() + for interface in interfaces: + i = 0 + already_generated.clear() + for method in interface.methods: + if method.explicit_ordinal is not None: + continue + while True: + i = i + 1 + if i == 1000000: + raise Exception("Could not generate %d method ordinals for %s" % + (len(interface.methods), interface.mojom_name)) + # Generate a scrambled method.ordinal value. The algorithm doesn't have + # to be very strong, cryptographically. It just needs to be non-trivial + # to guess the results without the secret salt, in order to make it + # harder for a compromised process to send fake Mojo messages. + sha256 = hashlib.sha256(salt) + sha256.update(interface.mojom_name.encode('utf-8')) + sha256.update(str(i).encode('utf-8')) + # Take the first 4 bytes as a little-endian uint32. + ordinal = struct.unpack('<L', sha256.digest()[:4])[0] + # Trim to 31 bits, so it always fits into a Java (signed) int. + ordinal = ordinal & 0x7fffffff + if ordinal in already_generated: + continue + already_generated.add(ordinal) + method.ordinal = ordinal + method.ordinal_comment = ( + 'The %s value is based on sha256(salt + "%s%d").' % + (ordinal, interface.mojom_name, i)) + break + + +def ReadFileContents(filename): + with open(filename, 'rb') as f: + return f.read() + + +class MojomProcessor: + """Takes parsed mojom modules and generates language bindings from them. + + Attributes: + _processed_files: {Dict[str, mojom.generate.module.Module]} Mapping from + relative mojom filename paths to the module AST for that mojom file. + """ + def __init__(self, should_generate): + self._should_generate = should_generate + self._processed_files = {} + self._typemap = {} + + def LoadTypemaps(self, typemaps): + # Support some very simple single-line comments in typemap JSON. + comment_expr = r"^\s*//.*$" + def no_comments(line): + return not re.match(comment_expr, line) + for filename in typemaps: + with open(filename) as f: + typemaps = json.loads("".join(filter(no_comments, f.readlines()))) + for language, typemap in typemaps.items(): + language_map = self._typemap.get(language, {}) + language_map.update(typemap) + self._typemap[language] = language_map + if 'c++' in self._typemap: + self._typemap['mojolpm'] = self._typemap['c++'] + + def _GenerateModule(self, args, remaining_args, check_modules, + generator_modules, rel_filename, imported_filename_stack): + # Return the already-generated module. + if rel_filename.path in self._processed_files: + return self._processed_files[rel_filename.path] + + if rel_filename.path in imported_filename_stack: + print("%s: Error: Circular dependency" % rel_filename.path + \ + MakeImportStackMessage(imported_filename_stack + [rel_filename.path])) + sys.exit(1) + + module_path = _GetModulePath(rel_filename, args.output_dir) + with open(module_path, 'rb') as f: + module = Module.Load(f) + + if args.scrambled_message_id_salt_paths: + salt = b''.join( + map(ReadFileContents, args.scrambled_message_id_salt_paths)) + ScrambleMethodOrdinals(module.interfaces, salt) + + if self._should_generate(rel_filename.path): + # Run checks on module first. + for check_module in check_modules.values(): + checker = check_module.Check(module) + checker.CheckModule() + # Then run generation. + for language, generator_module in generator_modules.items(): + generator = generator_module.Generator( + module, args.output_dir, typemap=self._typemap.get(language, {}), + variant=args.variant, bytecode_path=args.bytecode_path, + for_blink=args.for_blink, + js_generate_struct_deserializers=\ + args.js_generate_struct_deserializers, + export_attribute=args.export_attribute, + export_header=args.export_header, + generate_non_variant_code=args.generate_non_variant_code, + support_lazy_serialization=args.support_lazy_serialization, + disallow_native_types=args.disallow_native_types, + disallow_interfaces=args.disallow_interfaces, + generate_message_ids=args.generate_message_ids, + generate_fuzzing=args.generate_fuzzing, + enable_kythe_annotations=args.enable_kythe_annotations, + extra_cpp_template_paths=args.extra_cpp_template_paths, + generate_extra_cpp_only=args.generate_extra_cpp_only) + filtered_args = [] + if hasattr(generator_module, 'GENERATOR_PREFIX'): + prefix = '--' + generator_module.GENERATOR_PREFIX + '_' + filtered_args = [arg for arg in remaining_args + if arg.startswith(prefix)] + generator.GenerateFiles(filtered_args) + + # Save result. + self._processed_files[rel_filename.path] = module + return module + + +def _Generate(args, remaining_args): + if args.variant == "none": + args.variant = None + + for idx, import_dir in enumerate(args.import_directories): + tokens = import_dir.split(":") + if len(tokens) >= 2: + args.import_directories[idx] = RelativePath(tokens[0], tokens[1], + args.output_dir) + else: + args.import_directories[idx] = RelativePath(tokens[0], args.depth, + args.output_dir) + generator_modules = LoadGenerators(args.generators_string) + check_modules = LoadChecks(args.checks_string) + + fileutil.EnsureDirectoryExists(args.output_dir) + + processor = MojomProcessor(lambda filename: filename in args.filename) + processor.LoadTypemaps(set(args.typemaps)) + + if args.filelist: + with open(args.filelist) as f: + args.filename.extend(f.read().split()) + + for filename in args.filename: + processor._GenerateModule( + args, remaining_args, check_modules, generator_modules, + RelativePath(filename, args.depth, args.output_dir), []) + + return 0 + + +def _Precompile(args, _): + generator_modules = LoadGenerators(",".join(_BUILTIN_GENERATORS.keys())) + + template_expander.PrecompileTemplates(generator_modules, args.output_dir) + return 0 + + +def main(): + parser = argparse.ArgumentParser( + description="Generate bindings from mojom files.") + parser.add_argument("--use_bundled_pylibs", action="store_true", + help="use Python modules bundled in the SDK") + parser.add_argument( + "-o", + "--output_dir", + dest="output_dir", + default=".", + help="output directory for generated files") + + subparsers = parser.add_subparsers() + + generate_parser = subparsers.add_parser( + "generate", description="Generate bindings from mojom files.") + generate_parser.add_argument("filename", nargs="*", + help="mojom input file") + generate_parser.add_argument("--filelist", help="mojom input file list") + generate_parser.add_argument("-d", "--depth", dest="depth", default=".", + help="depth from source root") + generate_parser.add_argument("-g", + "--generators", + dest="generators_string", + metavar="GENERATORS", + default="c++,javascript,java,mojolpm", + help="comma-separated list of generators") + generate_parser.add_argument("-c", + "--checks", + dest="checks_string", + metavar="CHECKS", + default=",".join(_BUILTIN_CHECKS.keys()), + help="comma-separated list of checks") + generate_parser.add_argument( + "--gen_dir", dest="gen_directories", action="append", metavar="directory", + default=[], help="add a directory to be searched for the syntax trees.") + generate_parser.add_argument( + "-I", dest="import_directories", action="append", metavar="directory", + default=[], + help="add a directory to be searched for import files. The depth from " + "source root can be specified for each import by appending it after " + "a colon") + generate_parser.add_argument("--typemap", action="append", metavar="TYPEMAP", + default=[], dest="typemaps", + help="apply TYPEMAP to generated output") + generate_parser.add_argument("--variant", dest="variant", default=None, + help="output a named variant of the bindings") + generate_parser.add_argument( + "--bytecode_path", required=True, help=( + "the path from which to load template bytecode; to generate template " + "bytecode, run %s precompile BYTECODE_PATH" % os.path.basename( + sys.argv[0]))) + generate_parser.add_argument("--for_blink", action="store_true", + help="Use WTF types as generated types for mojo " + "string/array/map.") + generate_parser.add_argument( + "--js_generate_struct_deserializers", action="store_true", + help="Generate javascript deserialize methods for structs in " + "mojom-lite.js file") + generate_parser.add_argument( + "--export_attribute", default="", + help="Optional attribute to specify on class declaration to export it " + "for the component build.") + generate_parser.add_argument( + "--export_header", default="", + help="Optional header to include in the generated headers to support the " + "component build.") + generate_parser.add_argument( + "--generate_non_variant_code", action="store_true", + help="Generate code that is shared by different variants.") + generate_parser.add_argument( + "--scrambled_message_id_salt_path", + dest="scrambled_message_id_salt_paths", + help="If non-empty, the path to a file whose contents should be used as" + "a salt for generating scrambled message IDs. If this switch is specified" + "more than once, the contents of all salt files are concatenated to form" + "the salt value.", default=[], action="append") + generate_parser.add_argument( + "--support_lazy_serialization", + help="If set, generated bindings will serialize lazily when possible.", + action="store_true") + generate_parser.add_argument( + "--extra_cpp_template_paths", + dest="extra_cpp_template_paths", + action="append", + metavar="path_to_template", + default=[], + help="Provide a path to a new template (.tmpl) that is used to generate " + "additional C++ source/header files ") + generate_parser.add_argument( + "--generate_extra_cpp_only", + help="If set and extra_cpp_template_paths provided, will only generate" + "extra_cpp_template related C++ bindings", + action="store_true") + generate_parser.add_argument( + "--disallow_native_types", + help="Disallows the [Native] attribute to be specified on structs or " + "enums within the mojom file.", action="store_true") + generate_parser.add_argument( + "--disallow_interfaces", + help="Disallows interface definitions within the mojom file. It is an " + "error to specify this flag when processing a mojom file which defines " + "any interface.", action="store_true") + generate_parser.add_argument( + "--generate_message_ids", + help="Generates only the message IDs header for C++ bindings. Note that " + "this flag only matters if --generate_non_variant_code is also " + "specified.", action="store_true") + generate_parser.add_argument( + "--generate_fuzzing", + action="store_true", + help="Generates additional bindings for fuzzing in JS.") + generate_parser.add_argument( + "--enable_kythe_annotations", + action="store_true", + help="Adds annotations for kythe metadata generation.") + + generate_parser.set_defaults(func=_Generate) + + precompile_parser = subparsers.add_parser("precompile", + description="Precompile templates for the mojom bindings generator.") + precompile_parser.set_defaults(func=_Precompile) + + args, remaining_args = parser.parse_known_args() + return args.func(args, remaining_args) + + +if __name__ == "__main__": + with crbug_1001171.DumpStateOnLookupError(): + ret = main() + # Exit without running GC, which can save multiple seconds due to the large + # number of object created. But flush is necessary as os._exit doesn't do + # that. + sys.stdout.flush() + sys.stderr.flush() + os._exit(ret) diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py new file mode 100644 index 00000000..761922b6 --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py @@ -0,0 +1,62 @@ +# Copyright 2014 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import unittest + +from mojom_bindings_generator import MakeImportStackMessage +from mojom_bindings_generator import ScrambleMethodOrdinals + + +class FakeIface: + def __init__(self): + self.mojom_name = None + self.methods = None + + +class FakeMethod: + def __init__(self, explicit_ordinal=None): + self.explicit_ordinal = explicit_ordinal + self.ordinal = explicit_ordinal + self.ordinal_comment = None + + +class MojoBindingsGeneratorTest(unittest.TestCase): + """Tests mojo_bindings_generator.""" + + def testMakeImportStackMessage(self): + """Tests MakeImportStackMessage().""" + self.assertEqual(MakeImportStackMessage(["x"]), "") + self.assertEqual(MakeImportStackMessage(["x", "y"]), + "\n y was imported by x") + self.assertEqual(MakeImportStackMessage(["x", "y", "z"]), + "\n z was imported by y\n y was imported by x") + + def testScrambleMethodOrdinals(self): + """Tests ScrambleMethodOrdinals().""" + interface = FakeIface() + interface.mojom_name = 'RendererConfiguration' + interface.methods = [ + FakeMethod(), + FakeMethod(), + FakeMethod(), + FakeMethod(explicit_ordinal=42) + ] + ScrambleMethodOrdinals([interface], "foo".encode('utf-8')) + # These next three values are hard-coded. If the generation algorithm + # changes from being based on sha256(seed + interface.name + str(i)) then + # these numbers will obviously need to change too. + # + # Note that hashlib.sha256('fooRendererConfiguration1').digest()[:4] is + # '\xa5\xbc\xf9\xca' and that hex(1257880741) = '0x4af9bca5'. The + # difference in 0x4a vs 0xca is because we only take 31 bits. + self.assertEqual(interface.methods[0].ordinal, 1257880741) + self.assertEqual(interface.methods[1].ordinal, 631133653) + self.assertEqual(interface.methods[2].ordinal, 549336076) + + # Explicit method ordinals should not be scrambled. + self.assertEqual(interface.methods[3].ordinal, 42) + + +if __name__ == "__main__": + unittest.main() diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/validate_typemap_config.py b/utils/codegen/ipc/mojo/public/tools/bindings/validate_typemap_config.py new file mode 100755 index 00000000..6bb7a209 --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/validate_typemap_config.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# Copyright 2020 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import argparse +import json +import os +import re +import sys + + +def CheckCppTypemapConfigs(target_name, config_filename, out_filename): + _SUPPORTED_CONFIG_KEYS = set([ + 'types', 'traits_headers', 'traits_private_headers', 'traits_sources', + 'traits_deps', 'traits_public_deps' + ]) + _SUPPORTED_TYPE_KEYS = set([ + 'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable', + 'move_only', 'nullable_is_same_type', 'forward_declaration', + 'default_constructible' + ]) + with open(config_filename, 'r') as f: + for config in json.load(f): + for key in config.keys(): + if key not in _SUPPORTED_CONFIG_KEYS: + raise ValueError('Invalid typemap property "%s" when processing %s' % + (key, target_name)) + + types = config.get('types') + if not types: + raise ValueError('Typemap for %s must specify at least one type to map' + % target_name) + + for entry in types: + for key in entry.keys(): + if key not in _SUPPORTED_TYPE_KEYS: + raise IOError( + 'Invalid type property "%s" in typemap for "%s" on target %s' % + (key, entry.get('mojom', '(unknown)'), target_name)) + + with open(out_filename, 'w') as f: + f.truncate(0) + + +def main(): + parser = argparse.ArgumentParser() + _, args = parser.parse_known_args() + if len(args) != 3: + print('Usage: validate_typemap_config.py target_name config_filename ' + 'stamp_filename') + sys.exit(1) + + CheckCppTypemapConfigs(args[0], args[1], args[2]) + + +if __name__ == '__main__': + main() |