utils: ipc: Update mojo
Update mojo from commit 9be4263648d7d1a04bb78be75df53f56449a5e3a "Updating trunk VERSION from 6225.0 to 6226.0" from the Chromium repository. The update-mojo.sh script was used for this update. Bug: https://bugs.libcamera.org/show_bug.cgi?id=206 Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Milan Zamazal <mzamazal@redhat.com> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
This commit is contained in:
parent
8ac367fe0c
commit
d17de86904
64 changed files with 3830 additions and 1416 deletions
|
@ -1,4 +1,4 @@
|
|||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
Files in this directory are imported from 9c138d992bfc of Chromium. Do not
|
||||
Files in this directory are imported from 9be4263648d7 of Chromium. Do not
|
||||
modify them manually.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
// Copyright 2014 The Chromium Authors
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2020 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
@ -10,7 +10,11 @@ group("mojo_python_unittests") {
|
|||
"run_all_python_unittests.py",
|
||||
"//testing/scripts/run_isolated_script_test.py",
|
||||
]
|
||||
deps = [ "//mojo/public/tools/mojom/mojom:tests" ]
|
||||
deps = [
|
||||
"//mojo/public/tools/bindings:tests",
|
||||
"//mojo/public/tools/mojom:tests",
|
||||
"//mojo/public/tools/mojom/mojom:tests",
|
||||
]
|
||||
data_deps = [
|
||||
"//testing:test_scripts_shared",
|
||||
"//third_party/catapult/third_party/typ/",
|
||||
|
|
|
@ -1,24 +1,27 @@
|
|||
# Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2016 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import("//build/config/python.gni")
|
||||
import("//mojo/public/tools/bindings/mojom.gni")
|
||||
import("//third_party/jinja2/jinja2.gni")
|
||||
|
||||
# TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
|
||||
python2_action("precompile_templates") {
|
||||
action("precompile_templates") {
|
||||
sources = mojom_generator_sources
|
||||
sources += [
|
||||
"$mojom_generator_root/generators/cpp_templates/cpp_macros.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/enum_serialization_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/feature_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/feature_definition.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_definition.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_feature_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_macros.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-features.h.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-forward.h.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-import-headers.h.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-params-data.h.tmpl",
|
||||
|
@ -26,7 +29,6 @@ python2_action("precompile_templates") {
|
|||
"$mojom_generator_root/generators/cpp_templates/module-shared-message-ids.h.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-test-utils.cc.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module-test-utils.h.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module.cc.tmpl",
|
||||
"$mojom_generator_root/generators/cpp_templates/module.h.tmpl",
|
||||
|
@ -65,9 +67,6 @@ python2_action("precompile_templates") {
|
|||
"$mojom_generator_root/generators/java_templates/struct.java.tmpl",
|
||||
"$mojom_generator_root/generators/java_templates/union.java.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/enum_definition.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/externs/interface_definition.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/externs/module.externs.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/externs/struct_definition.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/fuzzing.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/interface_definition.tmpl",
|
||||
"$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl",
|
||||
|
@ -93,8 +92,11 @@ python2_action("precompile_templates") {
|
|||
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl",
|
||||
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl",
|
||||
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl",
|
||||
"$mojom_generator_root/generators/ts_templates/enum_definition.tmpl",
|
||||
"$mojom_generator_root/generators/ts_templates/interface_definition.tmpl",
|
||||
"$mojom_generator_root/generators/ts_templates/module_definition.tmpl",
|
||||
"$mojom_generator_root/generators/ts_templates/mojom.tmpl",
|
||||
"$mojom_generator_root/generators/ts_templates/struct_definition.tmpl",
|
||||
"$mojom_generator_root/generators/ts_templates/union_definition.tmpl",
|
||||
]
|
||||
script = mojom_generator_script
|
||||
|
||||
|
@ -102,8 +104,8 @@ python2_action("precompile_templates") {
|
|||
outputs = [
|
||||
"$target_gen_dir/cpp_templates.zip",
|
||||
"$target_gen_dir/java_templates.zip",
|
||||
"$target_gen_dir/mojolpm_templates.zip",
|
||||
"$target_gen_dir/js_templates.zip",
|
||||
"$target_gen_dir/mojolpm_templates.zip",
|
||||
"$target_gen_dir/ts_templates.zip",
|
||||
]
|
||||
args = [
|
||||
|
@ -113,3 +115,17 @@ python2_action("precompile_templates") {
|
|||
"precompile",
|
||||
]
|
||||
}
|
||||
|
||||
group("tests") {
|
||||
data = [
|
||||
mojom_generator_script,
|
||||
"checks/mojom_attributes_check_unittest.py",
|
||||
"checks/mojom_interface_feature_check_unittest.py",
|
||||
"checks/mojom_restrictions_checks_unittest.py",
|
||||
"mojom_bindings_generator_unittest.py",
|
||||
"//tools/diagnosis/crbug_1001171.py",
|
||||
"//third_party/markupsafe/",
|
||||
]
|
||||
data += mojom_generator_sources
|
||||
data += jinja2_sources
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ for message parameters.
|
|||
| `string` | UTF-8 encoded string.
|
||||
| `array<T>` | Array of any Mojom type *T*; for example, `array<uint8>` or `array<array<string>>`.
|
||||
| `array<T, N>` | Fixed-length array of any Mojom type *T*. The parameter *N* must be an integral constant.
|
||||
| `map<S, T>` | Associated array maping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type.
|
||||
| `map<S, T>` | Associated array mapping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type.
|
||||
| `handle` | Generic Mojo handle. May be any type of handle, including a wrapped native platform handle.
|
||||
| `handle<message_pipe>` | Generic message pipe handle.
|
||||
| `handle<shared_buffer>` | Shared buffer handle.
|
||||
|
@ -188,8 +188,8 @@ struct StringPair {
|
|||
};
|
||||
|
||||
enum AnEnum {
|
||||
YES,
|
||||
NO
|
||||
kYes,
|
||||
kNo
|
||||
};
|
||||
|
||||
interface SampleInterface {
|
||||
|
@ -209,7 +209,7 @@ struct AllTheThings {
|
|||
uint64 unsigned_64bit_value;
|
||||
float float_value_32bit;
|
||||
double float_value_64bit;
|
||||
AnEnum enum_value = AnEnum.YES;
|
||||
AnEnum enum_value = AnEnum.kYes;
|
||||
|
||||
// Strings may be nullable.
|
||||
string? maybe_a_string_maybe_not;
|
||||
|
@ -300,14 +300,14 @@ within a module or nested within the namespace of some struct or interface:
|
|||
module business.mojom;
|
||||
|
||||
enum Department {
|
||||
SALES = 0,
|
||||
DEV,
|
||||
kSales = 0,
|
||||
kDev,
|
||||
};
|
||||
|
||||
struct Employee {
|
||||
enum Type {
|
||||
FULL_TIME,
|
||||
PART_TIME,
|
||||
kFullTime,
|
||||
kPartTime,
|
||||
};
|
||||
|
||||
Type type;
|
||||
|
@ -315,6 +315,9 @@ struct Employee {
|
|||
};
|
||||
```
|
||||
|
||||
C++ constant-style enum value names are preferred as specified in the
|
||||
[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Enumerator_Names).
|
||||
|
||||
Similar to C-style enums, individual values may be explicitly assigned within an
|
||||
enum definition. By default, values are based at zero and increment by
|
||||
1 sequentially.
|
||||
|
@ -336,8 +339,8 @@ struct Employee {
|
|||
const uint64 kInvalidId = 0;
|
||||
|
||||
enum Type {
|
||||
FULL_TIME,
|
||||
PART_TIME,
|
||||
kFullTime,
|
||||
kPartTime,
|
||||
};
|
||||
|
||||
uint64 id = kInvalidId;
|
||||
|
@ -348,6 +351,37 @@ struct Employee {
|
|||
The effect of nested definitions on generated bindings varies depending on the
|
||||
target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages).
|
||||
|
||||
### Features
|
||||
|
||||
Features can be declared with a `name` and `default_state` and can be attached
|
||||
in mojo to interfaces or methods using the `RuntimeFeature` attribute. If the
|
||||
feature is disabled at runtime, the method will crash and the interface will
|
||||
refuse to be bound / instantiated. Features cannot be serialized to be sent over
|
||||
IPC at this time.
|
||||
|
||||
```
|
||||
module experimental.mojom;
|
||||
|
||||
feature kUseElevators {
|
||||
const string name = "UseElevators";
|
||||
const bool default_state = false;
|
||||
}
|
||||
|
||||
[RuntimeFeature=kUseElevators]
|
||||
interface Elevator {
|
||||
// This interface cannot be bound or called if the feature is disabled.
|
||||
}
|
||||
|
||||
interface Building {
|
||||
// This method cannot be called if the feature is disabled.
|
||||
[RuntimeFeature=kUseElevators]
|
||||
CallElevator(int floor);
|
||||
|
||||
// This method can be called.
|
||||
RingDoorbell(int volume);
|
||||
}
|
||||
```
|
||||
|
||||
### Interfaces
|
||||
|
||||
An **interface** is a logical bundle of parameterized request messages. Each
|
||||
|
@ -396,20 +430,33 @@ interesting attributes supported today.
|
|||
extreme caution, because it can lead to deadlocks otherwise.
|
||||
|
||||
* **`[Default]`**:
|
||||
The `Default` attribute may be used to specify an enumerator value that
|
||||
will be used if an `Extensible` enumeration does not deserialize to a known
|
||||
value on the receiver side, i.e. the sender is using a newer version of the
|
||||
enum. This allows unknown values to be mapped to a well-defined value that can
|
||||
be appropriately handled.
|
||||
The `Default` attribute may be used to specify an enumerator value or union
|
||||
field that will be used if an `Extensible` enumeration or union does not
|
||||
deserialize to a known value on the receiver side, i.e. the sender is using a
|
||||
newer version of the enum or union. This allows unknown values to be mapped to
|
||||
a well-defined value that can be appropriately handled.
|
||||
|
||||
Note: The `Default` field for a union must be of nullable or integral type.
|
||||
When a union is defaulted to this field, the field takes on the default value
|
||||
for its type: null for nullable types, and zero/false for integral types.
|
||||
|
||||
* **`[Extensible]`**:
|
||||
The `Extensible` attribute may be specified for any enum definition. This
|
||||
essentially disables builtin range validation when receiving values of the
|
||||
enum type in a message, allowing older bindings to tolerate unrecognized
|
||||
values from newer versions of the enum.
|
||||
The `Extensible` attribute may be specified for any enum or union definition.
|
||||
For enums, this essentially disables builtin range validation when receiving
|
||||
values of the enum type in a message, allowing older bindings to tolerate
|
||||
unrecognized values from newer versions of the enum.
|
||||
|
||||
Note: in the future, an `Extensible` enumeration will require that a `Default`
|
||||
enumerator value also be specified.
|
||||
If an enum value within an extensible enum definition is affixed with the
|
||||
`Default` attribute, out-of-range values for the enum will deserialize to that
|
||||
default value. Only one enum value may be designated as the `Default`.
|
||||
|
||||
Similarly, a union marked `Extensible` will deserialize to its `Default` field
|
||||
when an unrecognized field is received. Extensible unions MUST specify exactly
|
||||
one `Default` field, and the field must be of nullable or integral type. When
|
||||
defaulted to this field, the value is always null/zero/false as appropriate.
|
||||
|
||||
An `Extensible` enumeration REQUIRES that a `Default` value be specified,
|
||||
so all new extensible enums should specify one.
|
||||
|
||||
* **`[Native]`**:
|
||||
The `Native` attribute may be specified for an empty struct declaration to
|
||||
|
@ -422,7 +469,10 @@ interesting attributes supported today.
|
|||
* **`[MinVersion=N]`**:
|
||||
The `MinVersion` attribute is used to specify the version at which a given
|
||||
field, enum value, interface method, or method parameter was introduced.
|
||||
See [Versioning](#Versioning) for more details.
|
||||
See [Versioning](#Versioning) for more details. `MinVersion` does not apply
|
||||
to interfaces, structs or enums, but to the fields of those types.
|
||||
`MinVersion` is not a module-global value, but it is ok to pretend it is by
|
||||
skipping versions when adding fields or parameters.
|
||||
|
||||
* **`[Stable]`**:
|
||||
The `Stable` attribute specifies that a given mojom type or interface
|
||||
|
@ -442,13 +492,73 @@ interesting attributes supported today.
|
|||
string representation as specified by RFC 4122. New UUIDs can be generated
|
||||
with common tools such as `uuidgen`.
|
||||
|
||||
* **`[RuntimeFeature=feature]`**
|
||||
The `RuntimeFeature` attribute should reference a mojo `feature`. If this
|
||||
feature is enabled (e.g. using `--enable-features={feature.name}`) then the
|
||||
interface behaves entirely as expected. If the feature is not enabled the
|
||||
interface cannot be bound to a concrete receiver or remote - attempting to do
|
||||
so will result in the receiver or remote being reset() to an unbound state.
|
||||
Note that this is a different concept to the build-time `EnableIf` directive.
|
||||
`RuntimeFeature` is currently only supported for C++ bindings and has no
|
||||
effect for, say, Java or TypeScript bindings (see https://crbug.com/1278253).
|
||||
|
||||
* **`[EnableIf=value]`**:
|
||||
The `EnableIf` attribute is used to conditionally enable definitions when the
|
||||
mojom is parsed. If the `mojom` target in the GN file does not include the
|
||||
matching `value` in the list of `enabled_features`, the definition will be
|
||||
disabled. This is useful for mojom definitions that only make sense on one
|
||||
platform. Note that the `EnableIf` attribute can only be set once per
|
||||
definition.
|
||||
definition and cannot be set at the same time as `EnableIfNot`. Also be aware
|
||||
that only one condition can be tested, `EnableIf=value,xyz` introduces a new
|
||||
`xyz` attribute. `xyz` is not part of the `EnableIf` condition that depends
|
||||
only on the feature `value`. Complex conditions can be introduced via
|
||||
enabled_features in `build.gn` files.
|
||||
|
||||
* **`[EnableIfNot=value]`**:
|
||||
The `EnableIfNot` attribute is used to conditionally enable definitions when
|
||||
the mojom is parsed. If the `mojom` target in the GN file includes the
|
||||
matching `value` in the list of `enabled_features`, the definition will be
|
||||
disabled. This is useful for mojom definitions that only make sense on all but
|
||||
one platform. Note that the `EnableIfNot` attribute can only be set once per
|
||||
definition and cannot be set at the same time as `EnableIf`.
|
||||
|
||||
* **`[ServiceSandbox=value]`**:
|
||||
The `ServiceSandbox` attribute is used in Chromium to tag which sandbox a
|
||||
service hosting an implementation of interface will be launched in. This only
|
||||
applies to `C++` bindings. `value` should match a constant defined in an
|
||||
imported `sandbox.mojom.Sandbox` enum (for Chromium this is
|
||||
`//sandbox/policy/mojom/sandbox.mojom`), such as `kService`.
|
||||
|
||||
* **`[RequireContext=enum]`**:
|
||||
The `RequireContext` attribute is used in Chromium to tag interfaces that
|
||||
should be passed (as remotes or receivers) only to privileged process
|
||||
contexts. The process context must be an enum that is imported into the
|
||||
mojom that defines the tagged interface. `RequireContext` may be used in
|
||||
future to DCHECK or CHECK if remotes are made available in contexts that
|
||||
conflict with the one provided in the interface definition. Process contexts
|
||||
are not the same as the sandbox a process is running in, but will reflect
|
||||
the set of capabilities provided to the service.
|
||||
|
||||
* **`[AllowedContext=enum]`**:
|
||||
The `AllowedContext` attribute is used in Chromium to tag methods that pass
|
||||
remotes or receivers of interfaces that are marked with a `RequireContext`
|
||||
attribute. The enum provided on the method must be equal or better (lower
|
||||
numerically) than the one required on the interface being passed. At present
|
||||
failing to specify an adequate `AllowedContext` value will cause mojom
|
||||
generation to fail at compile time. In future DCHECKs or CHECKs might be
|
||||
added to enforce that method is only called from a process context that meets
|
||||
the given `AllowedContext` value. The enum must of the same type as that
|
||||
specified in the interface's `RequireContext` attribute. Adding an
|
||||
`AllowedContext` attribute to a method is a strong indication that you need
|
||||
a detailed security review of your design - please reach out to the security
|
||||
team.
|
||||
|
||||
* **`[SupportsUrgent]`**:
|
||||
The `SupportsUrgent` attribute is used in conjunction with
|
||||
`mojo::UrgentMessageScope` in Chromium to tag messages as having high
|
||||
priority. The IPC layer notifies the underlying scheduler upon both receiving
|
||||
and processing an urgent message. At present, this attribute only affects
|
||||
channel associated messages in the renderer process.
|
||||
|
||||
## Generated Code For Target Languages
|
||||
|
||||
|
@ -495,9 +605,9 @@ values. For example if a Mojom declares the enum:
|
|||
|
||||
``` cpp
|
||||
enum AdvancedBoolean {
|
||||
TRUE = 0,
|
||||
FALSE = 1,
|
||||
FILE_NOT_FOUND = 2,
|
||||
kTrue = 0,
|
||||
kFalse = 1,
|
||||
kFileNotFound = 2,
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -550,10 +660,16 @@ See the documentation for
|
|||
|
||||
*** note
|
||||
**NOTE:** You don't need to worry about versioning if you don't care about
|
||||
backwards compatibility. Specifically, all parts of Chrome are updated
|
||||
atomically today and there is not yet any possibility of any two Chrome
|
||||
processes communicating with two different versions of any given Mojom
|
||||
interface.
|
||||
backwards compatibility. Today, all parts of the Chrome browser are
|
||||
updated atomically and there is not yet any possibility of any two
|
||||
Chrome processes communicating with two different versions of any given Mojom
|
||||
interface. On Chrome OS, there are several places where versioning is required.
|
||||
For example,
|
||||
[ARC++](https://developer.android.com/chrome-os/intro)
|
||||
uses versioned mojo to send IPC to the Android container.
|
||||
Likewise, the
|
||||
[Lacros](/docs/lacros.md)
|
||||
browser uses versioned mojo to talk to the ash system UI.
|
||||
***
|
||||
|
||||
Services extend their interfaces to support new features over time, and clients
|
||||
|
@ -593,8 +709,8 @@ struct Employee {
|
|||
|
||||
*** note
|
||||
**NOTE:** Mojo object or handle types added with a `MinVersion` **MUST** be
|
||||
optional (nullable). See [Primitive Types](#Primitive-Types) for details on
|
||||
nullable values.
|
||||
optional (nullable) or primitive. See [Primitive Types](#Primitive-Types) for
|
||||
details on nullable values.
|
||||
***
|
||||
|
||||
By default, fields belong to version 0. New fields must be appended to the
|
||||
|
@ -624,10 +740,10 @@ the following hard constraints:
|
|||
* For any given struct or interface, if any field or method explicitly specifies
|
||||
an ordinal value, all fields or methods must explicitly specify an ordinal
|
||||
value.
|
||||
* For an *N*-field struct or *N*-method interface, the set of explicitly
|
||||
assigned ordinal values must be limited to the range *[0, N-1]*. Interfaces
|
||||
should include placeholder methods to fill the ordinal positions of removed
|
||||
methods (for example "Unused_Message_7@7()" or "RemovedMessage@42()", etc).
|
||||
* For an *N*-field struct, the set of explicitly assigned ordinal values must be
|
||||
limited to the range *[0, N-1]*. Structs should include placeholder fields
|
||||
to fill the ordinal positions of removed fields (for example "Unused_Field"
|
||||
or "RemovedField", etc).
|
||||
|
||||
You may reorder fields, but you must ensure that the ordinal values of existing
|
||||
fields remain unchanged. For example, the following struct remains
|
||||
|
@ -652,6 +768,24 @@ There are two dimensions on which an interface can be extended
|
|||
that the version number is scoped to the whole interface rather than to any
|
||||
individual parameter list.
|
||||
|
||||
``` cpp
|
||||
// Old version:
|
||||
interface HumanResourceDatabase {
|
||||
QueryEmployee(uint64 id) => (Employee? employee);
|
||||
};
|
||||
|
||||
// New version:
|
||||
interface HumanResourceDatabase {
|
||||
QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)
|
||||
=> (Employee? employee,
|
||||
[MinVersion=1] array<uint8>? finger_print);
|
||||
};
|
||||
```
|
||||
|
||||
Similar to [versioned structs](#Versioned-Structs), when you pass the parameter
|
||||
list of a request or response method to a destination using an older version of
|
||||
an interface, unrecognized fields are silently discarded.
|
||||
|
||||
Please note that adding a response to a message which did not previously
|
||||
expect a response is a not a backwards-compatible change.
|
||||
|
||||
|
@ -664,17 +798,12 @@ For example:
|
|||
``` cpp
|
||||
// Old version:
|
||||
interface HumanResourceDatabase {
|
||||
AddEmployee(Employee employee) => (bool success);
|
||||
QueryEmployee(uint64 id) => (Employee? employee);
|
||||
};
|
||||
|
||||
// New version:
|
||||
interface HumanResourceDatabase {
|
||||
AddEmployee(Employee employee) => (bool success);
|
||||
|
||||
QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)
|
||||
=> (Employee? employee,
|
||||
[MinVersion=1] array<uint8>? finger_print);
|
||||
QueryEmployee(uint64 id) => (Employee? employee);
|
||||
|
||||
[MinVersion=1]
|
||||
AttachFingerPrint(uint64 id, array<uint8> finger_print)
|
||||
|
@ -682,10 +811,7 @@ interface HumanResourceDatabase {
|
|||
};
|
||||
```
|
||||
|
||||
Similar to [versioned structs](#Versioned-Structs), when you pass the parameter
|
||||
list of a request or response method to a destination using an older version of
|
||||
an interface, unrecognized fields are silently discarded. However, if the method
|
||||
call itself is not recognized, it is considered a validation error and the
|
||||
If a method call is not recognized, it is considered a validation error and the
|
||||
receiver will close its end of the interface pipe. For example, if a client on
|
||||
version 1 of the above interface sends an `AttachFingerPrint` request to an
|
||||
implementation of version 0, the client will be disconnected.
|
||||
|
@ -712,8 +838,8 @@ If you want an enum to be extensible in the future, you can apply the
|
|||
``` cpp
|
||||
[Extensible]
|
||||
enum Department {
|
||||
SALES,
|
||||
DEV,
|
||||
kSales,
|
||||
kDev,
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -722,9 +848,9 @@ And later you can extend this enum without breaking backwards compatibility:
|
|||
``` cpp
|
||||
[Extensible]
|
||||
enum Department {
|
||||
SALES,
|
||||
DEV,
|
||||
[MinVersion=1] RESEARCH,
|
||||
kSales,
|
||||
kDev,
|
||||
[MinVersion=1] kResearch,
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -782,7 +908,7 @@ Statement = ModuleStatement | ImportStatement | Definition
|
|||
|
||||
ModuleStatement = AttributeSection "module" Identifier ";"
|
||||
ImportStatement = "import" StringLiteral ";"
|
||||
Definition = Struct Union Interface Enum Const
|
||||
Definition = Struct Union Interface Enum Feature Const
|
||||
|
||||
AttributeSection = <empty> | "[" AttributeList "]"
|
||||
AttributeList = <empty> | NonEmptyAttributeList
|
||||
|
@ -809,7 +935,7 @@ InterfaceBody = <empty>
|
|||
| InterfaceBody Const
|
||||
| InterfaceBody Enum
|
||||
| InterfaceBody Method
|
||||
Method = AttributeSection Name Ordinal "(" ParamterList ")" Response ";"
|
||||
Method = AttributeSection Name Ordinal "(" ParameterList ")" Response ";"
|
||||
ParameterList = <empty> | NonEmptyParameterList
|
||||
NonEmptyParameterList = Parameter
|
||||
| Parameter "," NonEmptyParameterList
|
||||
|
@ -847,6 +973,13 @@ EnumValue = AttributeSection Name
|
|||
| AttributeSection Name "=" Integer
|
||||
| AttributeSection Name "=" Identifier
|
||||
|
||||
; Note: `feature` is a weak keyword and can appear as, say, a struct field name.
|
||||
Feature = AttributeSection "feature" Name "{" FeatureBody "}" ";"
|
||||
| AttributeSection "feature" Name ";"
|
||||
FeatureBody = <empty>
|
||||
| FeatureBody FeatureField
|
||||
FeatureField = AttributeSection TypeSpec Name Default ";"
|
||||
|
||||
Const = "const" TypeSpec Name "=" Constant ";"
|
||||
|
||||
Constant = Literal | Identifier ";"
|
||||
|
|
0
utils/ipc/mojo/public/tools/bindings/checks/__init__.py
Normal file
0
utils/ipc/mojo/public/tools/bindings/checks/__init__.py
Normal file
|
@ -0,0 +1,170 @@
|
|||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Validate mojo attributes are allowed in Chrome before generation."""
|
||||
|
||||
import mojom.generate.check as check
|
||||
import mojom.generate.module as module
|
||||
|
||||
_COMMON_ATTRIBUTES = {
|
||||
'EnableIf',
|
||||
'EnableIfNot',
|
||||
}
|
||||
|
||||
# For struct, union & parameter lists.
|
||||
_COMMON_FIELD_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'MinVersion',
|
||||
'RenamedFrom',
|
||||
}
|
||||
|
||||
# Note: `Default`` goes on the default _value_, not on the enum.
|
||||
# Note: [Stable] without [Extensible] is not allowed.
|
||||
_ENUM_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'Extensible',
|
||||
'Native',
|
||||
'Stable',
|
||||
'RenamedFrom',
|
||||
'Uuid',
|
||||
}
|
||||
|
||||
# TODO(crbug.com/1234883) MinVersion is not needed for EnumVal.
|
||||
_ENUMVAL_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'Default',
|
||||
'MinVersion',
|
||||
}
|
||||
|
||||
_INTERFACE_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'RenamedFrom',
|
||||
'RequireContext',
|
||||
'RuntimeFeature',
|
||||
'ServiceSandbox',
|
||||
'Stable',
|
||||
'Uuid',
|
||||
}
|
||||
|
||||
_METHOD_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'AllowedContext',
|
||||
'MinVersion',
|
||||
'NoInterrupt',
|
||||
'RuntimeFeature',
|
||||
'SupportsUrgent',
|
||||
'Sync',
|
||||
'UnlimitedSize',
|
||||
}
|
||||
|
||||
_MODULE_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'JavaConstantsClassName',
|
||||
'JavaPackage',
|
||||
}
|
||||
|
||||
_PARAMETER_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES
|
||||
|
||||
_STRUCT_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'CustomSerializer',
|
||||
'JavaClassName',
|
||||
'Native',
|
||||
'Stable',
|
||||
'RenamedFrom',
|
||||
'Uuid',
|
||||
}
|
||||
|
||||
_STRUCT_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES
|
||||
|
||||
_UNION_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'Extensible',
|
||||
'Stable',
|
||||
'RenamedFrom',
|
||||
'Uuid',
|
||||
}
|
||||
|
||||
_UNION_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES | {
|
||||
'Default',
|
||||
}
|
||||
|
||||
# TODO(https://crbug.com/1193875) empty this set and remove the allowlist.
|
||||
_STABLE_ONLY_ALLOWLISTED_ENUMS = {
|
||||
'crosapi.mojom.OptionalBool',
|
||||
'crosapi.mojom.TriState',
|
||||
}
|
||||
|
||||
|
||||
class Check(check.Check):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Check, self).__init__(*args, **kwargs)
|
||||
|
||||
def _Respell(self, allowed, attribute):
|
||||
for a in allowed:
|
||||
if a.lower() == attribute.lower():
|
||||
return f" - Did you mean: {a}?"
|
||||
return ""
|
||||
|
||||
def _CheckAttributes(self, context, allowed, attributes):
|
||||
if not attributes:
|
||||
return
|
||||
for attribute in attributes:
|
||||
if not attribute in allowed:
|
||||
# Is there a close misspelling?
|
||||
hint = self._Respell(allowed, attribute)
|
||||
raise check.CheckException(
|
||||
self.module,
|
||||
f"attribute {attribute} not allowed on {context}{hint}")
|
||||
|
||||
def _CheckEnumAttributes(self, enum):
|
||||
if enum.attributes:
|
||||
self._CheckAttributes("enum", _ENUM_ATTRIBUTES, enum.attributes)
|
||||
if 'Stable' in enum.attributes and not 'Extensible' in enum.attributes:
|
||||
full_name = f"{self.module.mojom_namespace}.{enum.mojom_name}"
|
||||
if full_name not in _STABLE_ONLY_ALLOWLISTED_ENUMS:
|
||||
raise check.CheckException(
|
||||
self.module,
|
||||
f"[Extensible] required on [Stable] enum {full_name}")
|
||||
for enumval in enum.fields:
|
||||
self._CheckAttributes("enum value", _ENUMVAL_ATTRIBUTES,
|
||||
enumval.attributes)
|
||||
|
||||
def _CheckInterfaceAttributes(self, interface):
|
||||
self._CheckAttributes("interface", _INTERFACE_ATTRIBUTES,
|
||||
interface.attributes)
|
||||
for method in interface.methods:
|
||||
self._CheckAttributes("method", _METHOD_ATTRIBUTES, method.attributes)
|
||||
for param in method.parameters:
|
||||
self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES,
|
||||
param.attributes)
|
||||
if method.response_parameters:
|
||||
for param in method.response_parameters:
|
||||
self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES,
|
||||
param.attributes)
|
||||
for enum in interface.enums:
|
||||
self._CheckEnumAttributes(enum)
|
||||
|
||||
def _CheckModuleAttributes(self):
|
||||
self._CheckAttributes("module", _MODULE_ATTRIBUTES, self.module.attributes)
|
||||
|
||||
def _CheckStructAttributes(self, struct):
|
||||
self._CheckAttributes("struct", _STRUCT_ATTRIBUTES, struct.attributes)
|
||||
for field in struct.fields:
|
||||
self._CheckAttributes("struct field", _STRUCT_FIELD_ATTRIBUTES,
|
||||
field.attributes)
|
||||
for enum in struct.enums:
|
||||
self._CheckEnumAttributes(enum)
|
||||
|
||||
def _CheckUnionAttributes(self, union):
|
||||
self._CheckAttributes("union", _UNION_ATTRIBUTES, union.attributes)
|
||||
for field in union.fields:
|
||||
self._CheckAttributes("union field", _UNION_FIELD_ATTRIBUTES,
|
||||
field.attributes)
|
||||
|
||||
def CheckModule(self):
|
||||
"""Note that duplicate attributes are forbidden at the parse phase.
|
||||
We also do not need to look at the types of any parameters, as they will be
|
||||
checked where they are defined. Consts do not have attributes so can be
|
||||
skipped."""
|
||||
self._CheckModuleAttributes()
|
||||
for interface in self.module.interfaces:
|
||||
self._CheckInterfaceAttributes(interface)
|
||||
for enum in self.module.enums:
|
||||
self._CheckEnumAttributes(enum)
|
||||
for struct in self.module.structs:
|
||||
self._CheckStructAttributes(struct)
|
||||
for union in self.module.unions:
|
||||
self._CheckUnionAttributes(union)
|
|
@ -0,0 +1,194 @@
|
|||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import unittest
|
||||
|
||||
import mojom.generate.check as check
|
||||
from mojom_bindings_generator import LoadChecks, _Generate
|
||||
from mojom_parser_test_case import MojomParserTestCase
|
||||
|
||||
|
||||
class FakeArgs:
|
||||
"""Fakes args to _Generate - intention is to do just enough to run checks"""
|
||||
|
||||
def __init__(self, tester, files=None):
|
||||
""" `tester` is MojomParserTestCase for paths.
|
||||
`files` will have tester path added."""
|
||||
self.checks_string = 'attributes'
|
||||
self.depth = tester.GetPath('')
|
||||
self.filelist = None
|
||||
self.filename = [tester.GetPath(x) for x in files]
|
||||
self.gen_directories = tester.GetPath('gen')
|
||||
self.generators_string = ''
|
||||
self.import_directories = []
|
||||
self.output_dir = tester.GetPath('out')
|
||||
self.scrambled_message_id_salt_paths = None
|
||||
self.typemaps = []
|
||||
self.variant = 'none'
|
||||
|
||||
|
||||
class MojoBindingsCheckTest(MojomParserTestCase):
|
||||
def _ParseAndGenerate(self, mojoms):
|
||||
self.ParseMojoms(mojoms)
|
||||
args = FakeArgs(self, files=mojoms)
|
||||
_Generate(args, {})
|
||||
|
||||
def _testValid(self, filename, content):
|
||||
self.WriteFile(filename, content)
|
||||
self._ParseAndGenerate([filename])
|
||||
|
||||
def _testThrows(self, filename, content, regexp):
|
||||
mojoms = []
|
||||
self.WriteFile(filename, content)
|
||||
mojoms.append(filename)
|
||||
with self.assertRaisesRegexp(check.CheckException, regexp):
|
||||
self._ParseAndGenerate(mojoms)
|
||||
|
||||
def testLoads(self):
|
||||
"""Validate that the check is registered under the expected name."""
|
||||
check_modules = LoadChecks('attributes')
|
||||
self.assertTrue(check_modules['attributes'])
|
||||
|
||||
def testNoAnnotations(self):
|
||||
# Undecorated mojom should be fine.
|
||||
self._testValid(
|
||||
"a.mojom", """
|
||||
module a;
|
||||
struct Bar { int32 a; };
|
||||
enum Hello { kValue };
|
||||
union Thingy { Bar b; Hello hi; };
|
||||
interface Foo {
|
||||
Foo(int32 a, Hello hi, Thingy t) => (Bar b);
|
||||
};
|
||||
""")
|
||||
|
||||
def testValidAnnotations(self):
|
||||
# Obviously this is meaningless and won't generate, but it should pass
|
||||
# the attribute check's validation.
|
||||
self._testValid(
|
||||
"a.mojom", """
|
||||
[JavaConstantsClassName="FakeClass",JavaPackage="org.chromium.Fake"]
|
||||
module a;
|
||||
[Stable, Extensible]
|
||||
enum Hello { [Default] kValue, kValue2, [MinVersion=2] kValue3 };
|
||||
[Native]
|
||||
enum NativeEnum {};
|
||||
[Stable,Extensible]
|
||||
union Thingy { Bar b; [Default]int32 c; Hello hi; };
|
||||
|
||||
[Stable,RenamedFrom="module.other.Foo",
|
||||
Uuid="4C178401-4B07-4C2E-9255-5401A943D0C7"]
|
||||
struct Structure { Hello hi; };
|
||||
|
||||
[ServiceSandbox=Hello.kValue,RequireContext=Hello.kValue,Stable,
|
||||
Uuid="2F17D7DD-865A-4B1C-9394-9C94E035E82F"]
|
||||
interface Foo {
|
||||
[AllowedContext=Hello.kValue]
|
||||
Foo@0(int32 a) => (int32 b);
|
||||
[MinVersion=2,Sync,UnlimitedSize,NoInterrupt]
|
||||
Bar@1(int32 b, [MinVersion=2]Structure? s) => (bool c);
|
||||
};
|
||||
|
||||
[RuntimeFeature=test.mojom.FeatureName]
|
||||
interface FooFeatureControlled {};
|
||||
|
||||
interface FooMethodFeatureControlled {
|
||||
[RuntimeFeature=test.mojom.FeatureName]
|
||||
MethodWithFeature() => (bool c);
|
||||
};
|
||||
""")
|
||||
|
||||
def testWrongModuleStable(self):
|
||||
contents = """
|
||||
// err: module cannot be Stable
|
||||
[Stable]
|
||||
module a;
|
||||
enum Hello { kValue, kValue2, kValue3 };
|
||||
enum NativeEnum {};
|
||||
struct Structure { Hello hi; };
|
||||
|
||||
interface Foo {
|
||||
Foo(int32 a) => (int32 b);
|
||||
Bar(int32 b, Structure? s) => (bool c);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'attribute Stable not allowed on module')
|
||||
|
||||
def testWrongEnumDefault(self):
|
||||
contents = """
|
||||
module a;
|
||||
// err: default should go on EnumValue not Enum.
|
||||
[Default=kValue]
|
||||
enum Hello { kValue, kValue2, kValue3 };
|
||||
enum NativeEnum {};
|
||||
struct Structure { Hello hi; };
|
||||
|
||||
interface Foo {
|
||||
Foo(int32 a) => (int32 b);
|
||||
Bar(int32 b, Structure? s) => (bool c);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'attribute Default not allowed on enum')
|
||||
|
||||
def testWrongStructMinVersion(self):
|
||||
contents = """
|
||||
module a;
|
||||
enum Hello { kValue, kValue2, kValue3 };
|
||||
enum NativeEnum {};
|
||||
// err: struct cannot have MinVersion.
|
||||
[MinVersion=2]
|
||||
struct Structure { Hello hi; };
|
||||
|
||||
interface Foo {
|
||||
Foo(int32 a) => (int32 b);
|
||||
Bar(int32 b, Structure? s) => (bool c);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'attribute MinVersion not allowed on struct')
|
||||
|
||||
def testWrongMethodRequireContext(self):
|
||||
contents = """
|
||||
module a;
|
||||
enum Hello { kValue, kValue2, kValue3 };
|
||||
enum NativeEnum {};
|
||||
struct Structure { Hello hi; };
|
||||
|
||||
interface Foo {
|
||||
// err: RequireContext is for interfaces.
|
||||
[RequireContext=Hello.kValue]
|
||||
Foo(int32 a) => (int32 b);
|
||||
Bar(int32 b, Structure? s) => (bool c);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'RequireContext not allowed on method')
|
||||
|
||||
def testWrongMethodRequireContext(self):
|
||||
# crbug.com/1230122
|
||||
contents = """
|
||||
module a;
|
||||
interface Foo {
|
||||
// err: sync not Sync.
|
||||
[sync]
|
||||
Foo(int32 a) => (int32 b);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'attribute sync not allowed.*Did you mean: Sync')
|
||||
|
||||
def testStableExtensibleEnum(self):
|
||||
# crbug.com/1193875
|
||||
contents = """
|
||||
module a;
|
||||
[Stable]
|
||||
enum Foo {
|
||||
kDefaultVal,
|
||||
kOtherVal = 2,
|
||||
};
|
||||
"""
|
||||
self._testThrows('a.mojom', contents,
|
||||
'Extensible.*?required.*?Stable.*?enum')
|
|
@ -0,0 +1,34 @@
|
|||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Ensure no duplicate type definitions before generation."""
|
||||
|
||||
import mojom.generate.check as check
|
||||
import mojom.generate.module as module
|
||||
|
||||
|
||||
class Check(check.Check):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Check, self).__init__(*args, **kwargs)
|
||||
|
||||
def CheckModule(self):
|
||||
kinds = dict()
|
||||
for module in self.module.imports:
|
||||
for kind in module.enums + module.structs + module.unions:
|
||||
kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'
|
||||
if kind_name in kinds:
|
||||
previous_module = kinds[kind_name]
|
||||
if previous_module.path != module.path:
|
||||
raise check.CheckException(
|
||||
self.module, f"multiple-definition for type {kind_name}" +
|
||||
f"(defined in both {previous_module} and {module})")
|
||||
kinds[kind_name] = kind.module
|
||||
|
||||
for kind in self.module.enums + self.module.structs + self.module.unions:
|
||||
kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'
|
||||
if kind_name in kinds:
|
||||
previous_module = kinds[kind_name]
|
||||
raise check.CheckException(
|
||||
self.module, f"multiple-definition for type {kind_name}" +
|
||||
f"(previous definition in {previous_module})")
|
||||
return True
|
|
@ -0,0 +1,62 @@
|
|||
# Copyright 2023 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Validate mojo runtime feature guarded interfaces are nullable."""
|
||||
|
||||
import mojom.generate.check as check
|
||||
import mojom.generate.module as module
|
||||
|
||||
|
||||
class Check(check.Check):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Check, self).__init__(*args, **kwargs)
|
||||
|
||||
# `param` is an Interface of some sort.
|
||||
def _CheckNonNullableFeatureGuardedInterface(self, kind):
|
||||
# Only need to validate interface if it has a RuntimeFeature
|
||||
if not kind.kind.runtime_feature:
|
||||
return
|
||||
# Nullable (optional) is ok as the interface expects they might not be sent.
|
||||
if kind.is_nullable:
|
||||
return
|
||||
interface = kind.kind.mojom_name
|
||||
raise check.CheckException(
|
||||
self.module,
|
||||
f"interface {interface} has a RuntimeFeature but is not nullable")
|
||||
|
||||
# `param` can be a lot of things so check if it is a remote/receiver.
|
||||
# Array/Map must be recursed into.
|
||||
def _CheckFieldOrParam(self, kind):
|
||||
if module.IsAnyInterfaceKind(kind):
|
||||
self._CheckNonNullableFeatureGuardedInterface(kind)
|
||||
if module.IsArrayKind(kind):
|
||||
self._CheckFieldOrParam(kind.kind)
|
||||
if module.IsMapKind(kind):
|
||||
self._CheckFieldOrParam(kind.key_kind)
|
||||
self._CheckFieldOrParam(kind.value_kind)
|
||||
|
||||
def _CheckInterfaceFeatures(self, interface):
|
||||
for method in interface.methods:
|
||||
for param in method.parameters:
|
||||
self._CheckFieldOrParam(param.kind)
|
||||
if method.response_parameters:
|
||||
for param in method.response_parameters:
|
||||
self._CheckFieldOrParam(param.kind)
|
||||
|
||||
def _CheckStructFeatures(self, struct):
|
||||
for field in struct.fields:
|
||||
self._CheckFieldOrParam(field.kind)
|
||||
|
||||
def _CheckUnionFeatures(self, union):
|
||||
for field in union.fields:
|
||||
self._CheckFieldOrParam(field.kind)
|
||||
|
||||
def CheckModule(self):
|
||||
"""Validate that any runtime feature guarded interfaces that might be passed
|
||||
over mojo are nullable."""
|
||||
for interface in self.module.interfaces:
|
||||
self._CheckInterfaceFeatures(interface)
|
||||
for struct in self.module.structs:
|
||||
self._CheckStructFeatures(struct)
|
||||
for union in self.module.unions:
|
||||
self._CheckUnionFeatures(union)
|
|
@ -0,0 +1,173 @@
|
|||
# Copyright 2023 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import unittest
|
||||
|
||||
import mojom.generate.check as check
|
||||
from mojom_bindings_generator import LoadChecks, _Generate
|
||||
from mojom_parser_test_case import MojomParserTestCase
|
||||
|
||||
|
||||
class FakeArgs:
|
||||
"""Fakes args to _Generate - intention is to do just enough to run checks"""
|
||||
def __init__(self, tester, files=None):
|
||||
""" `tester` is MojomParserTestCase for paths.
|
||||
`files` will have tester path added."""
|
||||
self.checks_string = 'features'
|
||||
self.depth = tester.GetPath('')
|
||||
self.filelist = None
|
||||
self.filename = [tester.GetPath(x) for x in files]
|
||||
self.gen_directories = tester.GetPath('gen')
|
||||
self.generators_string = ''
|
||||
self.import_directories = []
|
||||
self.output_dir = tester.GetPath('out')
|
||||
self.scrambled_message_id_salt_paths = None
|
||||
self.typemaps = []
|
||||
self.variant = 'none'
|
||||
|
||||
|
||||
class MojoBindingsCheckTest(MojomParserTestCase):
|
||||
def _ParseAndGenerate(self, mojoms):
|
||||
self.ParseMojoms(mojoms)
|
||||
args = FakeArgs(self, files=mojoms)
|
||||
_Generate(args, {})
|
||||
|
||||
def assertValid(self, filename, content):
|
||||
self.WriteFile(filename, content)
|
||||
self._ParseAndGenerate([filename])
|
||||
|
||||
def assertThrows(self, filename, content, regexp):
|
||||
mojoms = []
|
||||
self.WriteFile(filename, content)
|
||||
mojoms.append(filename)
|
||||
with self.assertRaisesRegexp(check.CheckException, regexp):
|
||||
self._ParseAndGenerate(mojoms)
|
||||
|
||||
def testLoads(self):
|
||||
"""Validate that the check is registered under the expected name."""
|
||||
check_modules = LoadChecks('features')
|
||||
self.assertTrue(check_modules['features'])
|
||||
|
||||
def testNullableOk(self):
|
||||
self.assertValid(
|
||||
"a.mojom", """
|
||||
module a;
|
||||
// Scaffolding.
|
||||
feature kFeature {
|
||||
const string name = "Hello";
|
||||
const bool enabled_state = false;
|
||||
};
|
||||
[RuntimeFeature=kFeature]
|
||||
interface Guarded {
|
||||
};
|
||||
|
||||
// Unguarded interfaces should be ok everywhere.
|
||||
interface NotGuarded { };
|
||||
|
||||
// Optional (nullable) interfaces should be ok everywhere:
|
||||
struct Bar {
|
||||
pending_remote<Guarded>? remote;
|
||||
pending_receiver<Guarded>? receiver;
|
||||
};
|
||||
union Thingy {
|
||||
pending_remote<Guarded>? remote;
|
||||
pending_receiver<Guarded>? receiver;
|
||||
};
|
||||
interface Foo {
|
||||
Foo(
|
||||
pending_remote<Guarded>? remote,
|
||||
pending_receiver<Guarded>? receiver,
|
||||
pending_associated_remote<Guarded>? a_remote,
|
||||
pending_associated_receiver<Guarded>? a_receiver,
|
||||
// Unguarded interfaces do not have to be nullable.
|
||||
pending_remote<NotGuarded> remote,
|
||||
pending_receiver<NotGuarded> receiver,
|
||||
pending_associated_remote<NotGuarded> a_remote,
|
||||
pending_associated_receiver<NotGuarded> a_receiver
|
||||
) => (
|
||||
pending_remote<Guarded>? remote,
|
||||
pending_receiver<Guarded>? receiver
|
||||
);
|
||||
Bar(array<pending_remote<Guarded>?> remote)
|
||||
=> (map<string, pending_receiver<Guarded>?> a);
|
||||
};
|
||||
""")
|
||||
|
||||
def testMethodParamsMustBeNullable(self):
|
||||
prelude = """
|
||||
module a;
|
||||
// Scaffolding.
|
||||
feature kFeature {
|
||||
const string name = "Hello";
|
||||
const bool enabled_state = false;
|
||||
};
|
||||
[RuntimeFeature=kFeature]
|
||||
interface Guarded { };
|
||||
"""
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(pending_remote<Guarded> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(bool foo) => (pending_receiver<Guarded> a);
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(pending_receiver<Guarded> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(pending_associated_remote<Guarded> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(pending_associated_receiver<Guarded> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(array<pending_associated_receiver<Guarded>> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(map<string, pending_associated_receiver<Guarded>> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
|
||||
def testStructUnionMembersMustBeNullable(self):
|
||||
prelude = """
|
||||
module a;
|
||||
// Scaffolding.
|
||||
feature kFeature {
|
||||
const string name = "Hello";
|
||||
const bool enabled_state = false;
|
||||
};
|
||||
[RuntimeFeature=kFeature]
|
||||
interface Guarded { };
|
||||
"""
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
struct Trial {
|
||||
pending_remote<Guarded> a;
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
union Trial {
|
||||
pending_remote<Guarded> a;
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
|
@ -0,0 +1,102 @@
|
|||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Validate RequireContext and AllowedContext annotations before generation."""
|
||||
|
||||
import mojom.generate.check as check
|
||||
import mojom.generate.module as module
|
||||
|
||||
|
||||
class Check(check.Check):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.kind_to_interfaces = dict()
|
||||
super(Check, self).__init__(*args, **kwargs)
|
||||
|
||||
def _IsPassedInterface(self, candidate):
|
||||
if isinstance(
|
||||
candidate.kind,
|
||||
(module.PendingReceiver, module.PendingRemote,
|
||||
module.PendingAssociatedReceiver, module.PendingAssociatedRemote)):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _CheckInterface(self, method, param):
|
||||
# |param| is a pending_x<Interface> so need .kind.kind to get Interface.
|
||||
interface = param.kind.kind
|
||||
if interface.require_context:
|
||||
if method.allowed_context is None:
|
||||
raise check.CheckException(
|
||||
self.module, "method `{}` has parameter `{}` which passes interface"
|
||||
" `{}` that requires an AllowedContext annotation but none exists.".
|
||||
format(
|
||||
method.mojom_name,
|
||||
param.mojom_name,
|
||||
interface.mojom_name,
|
||||
))
|
||||
# If a string was provided, or if an enum was not imported, this will
|
||||
# be a string and we cannot validate that it is in range.
|
||||
if not isinstance(method.allowed_context, module.EnumValue):
|
||||
raise check.CheckException(
|
||||
self.module,
|
||||
"method `{}` has AllowedContext={} which is not a valid enum value."
|
||||
.format(method.mojom_name, method.allowed_context))
|
||||
# EnumValue must be from the same enum to be compared.
|
||||
if interface.require_context.enum != method.allowed_context.enum:
|
||||
raise check.CheckException(
|
||||
self.module, "method `{}` has parameter `{}` which passes interface"
|
||||
" `{}` that requires AllowedContext={} but one of kind `{}` was "
|
||||
"provided.".format(
|
||||
method.mojom_name,
|
||||
param.mojom_name,
|
||||
interface.mojom_name,
|
||||
interface.require_context.enum,
|
||||
method.allowed_context.enum,
|
||||
))
|
||||
# RestrictContext enums have most privileged field first (lowest value).
|
||||
interface_value = interface.require_context.field.numeric_value
|
||||
method_value = method.allowed_context.field.numeric_value
|
||||
if interface_value < method_value:
|
||||
raise check.CheckException(
|
||||
self.module, "RequireContext={} > AllowedContext={} for method "
|
||||
"`{}` which passes interface `{}`.".format(
|
||||
interface.require_context.GetSpec(),
|
||||
method.allowed_context.GetSpec(), method.mojom_name,
|
||||
interface.mojom_name))
|
||||
return True
|
||||
|
||||
def _GatherReferencedInterfaces(self, field):
|
||||
key = field.kind.spec
|
||||
# structs/unions can nest themselves so we need to bookkeep.
|
||||
if not key in self.kind_to_interfaces:
|
||||
# Might reference ourselves so have to create the list first.
|
||||
self.kind_to_interfaces[key] = set()
|
||||
for param in field.kind.fields:
|
||||
if self._IsPassedInterface(param):
|
||||
self.kind_to_interfaces[key].add(param)
|
||||
elif isinstance(param.kind, (module.Struct, module.Union)):
|
||||
for iface in self._GatherReferencedInterfaces(param):
|
||||
self.kind_to_interfaces[key].add(iface)
|
||||
return self.kind_to_interfaces[key]
|
||||
|
||||
def _CheckParams(self, method, params):
|
||||
# Note: we have to repeat _CheckParams for each method as each might have
|
||||
# different AllowedContext= attributes. We cannot memoize this function,
|
||||
# but can do so for gathering referenced interfaces as their RequireContext
|
||||
# attributes do not change.
|
||||
for param in params:
|
||||
if self._IsPassedInterface(param):
|
||||
self._CheckInterface(method, param)
|
||||
elif isinstance(param.kind, (module.Struct, module.Union)):
|
||||
for interface in self._GatherReferencedInterfaces(param):
|
||||
self._CheckInterface(method, interface)
|
||||
|
||||
def _CheckMethod(self, method):
|
||||
if method.parameters:
|
||||
self._CheckParams(method, method.parameters)
|
||||
if method.response_parameters:
|
||||
self._CheckParams(method, method.response_parameters)
|
||||
|
||||
def CheckModule(self):
|
||||
for interface in self.module.interfaces:
|
||||
for method in interface.methods:
|
||||
self._CheckMethod(method)
|
|
@ -0,0 +1,254 @@
|
|||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import unittest
|
||||
|
||||
import mojom.generate.check as check
|
||||
from mojom_bindings_generator import LoadChecks, _Generate
|
||||
from mojom_parser_test_case import MojomParserTestCase
|
||||
|
||||
# Mojoms that we will use in multiple tests.
|
||||
basic_mojoms = {
|
||||
'level.mojom':
|
||||
"""
|
||||
module level;
|
||||
enum Level {
|
||||
kHighest,
|
||||
kMiddle,
|
||||
kLowest,
|
||||
};
|
||||
""",
|
||||
'interfaces.mojom':
|
||||
"""
|
||||
module interfaces;
|
||||
import "level.mojom";
|
||||
struct Foo {int32 bar;};
|
||||
[RequireContext=level.Level.kHighest]
|
||||
interface High {
|
||||
DoFoo(Foo foo);
|
||||
};
|
||||
[RequireContext=level.Level.kMiddle]
|
||||
interface Mid {
|
||||
DoFoo(Foo foo);
|
||||
};
|
||||
[RequireContext=level.Level.kLowest]
|
||||
interface Low {
|
||||
DoFoo(Foo foo);
|
||||
};
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
class FakeArgs:
|
||||
"""Fakes args to _Generate - intention is to do just enough to run checks"""
|
||||
|
||||
def __init__(self, tester, files=None):
|
||||
""" `tester` is MojomParserTestCase for paths.
|
||||
`files` will have tester path added."""
|
||||
self.checks_string = 'restrictions'
|
||||
self.depth = tester.GetPath('')
|
||||
self.filelist = None
|
||||
self.filename = [tester.GetPath(x) for x in files]
|
||||
self.gen_directories = tester.GetPath('gen')
|
||||
self.generators_string = ''
|
||||
self.import_directories = []
|
||||
self.output_dir = tester.GetPath('out')
|
||||
self.scrambled_message_id_salt_paths = None
|
||||
self.typemaps = []
|
||||
self.variant = 'none'
|
||||
|
||||
|
||||
class MojoBindingsCheckTest(MojomParserTestCase):
|
||||
def _WriteBasicMojoms(self):
|
||||
for filename, contents in basic_mojoms.items():
|
||||
self.WriteFile(filename, contents)
|
||||
return list(basic_mojoms.keys())
|
||||
|
||||
def _ParseAndGenerate(self, mojoms):
|
||||
self.ParseMojoms(mojoms)
|
||||
args = FakeArgs(self, files=mojoms)
|
||||
_Generate(args, {})
|
||||
|
||||
def testLoads(self):
|
||||
"""Validate that the check is registered under the expected name."""
|
||||
check_modules = LoadChecks('restrictions')
|
||||
self.assertTrue(check_modules['restrictions'])
|
||||
|
||||
def testValidAnnotations(self):
|
||||
mojoms = self._WriteBasicMojoms()
|
||||
|
||||
a = 'a.mojom'
|
||||
self.WriteFile(
|
||||
a, """
|
||||
module a;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
|
||||
interface PassesHigh {
|
||||
[AllowedContext=level.Level.kHighest]
|
||||
DoHigh(pending_receiver<interfaces.High> hi);
|
||||
};
|
||||
interface PassesMedium {
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoMedium(pending_receiver<interfaces.Mid> hi);
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoMediumRem(pending_remote<interfaces.Mid> hi);
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoMediumAssoc(pending_associated_receiver<interfaces.Mid> hi);
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoMediumAssocRem(pending_associated_remote<interfaces.Mid> hi);
|
||||
};
|
||||
interface PassesLow {
|
||||
[AllowedContext=level.Level.kLowest]
|
||||
DoLow(pending_receiver<interfaces.Low> hi);
|
||||
};
|
||||
|
||||
struct One { pending_receiver<interfaces.High> hi; };
|
||||
struct Two { One one; };
|
||||
interface PassesNestedHigh {
|
||||
[AllowedContext=level.Level.kHighest]
|
||||
DoNestedHigh(Two two);
|
||||
};
|
||||
|
||||
// Allowed as PassesHigh is not itself restricted.
|
||||
interface PassesPassesHigh {
|
||||
DoPass(pending_receiver<PassesHigh> hiho);
|
||||
};
|
||||
""")
|
||||
mojoms.append(a)
|
||||
self._ParseAndGenerate(mojoms)
|
||||
|
||||
def _testThrows(self, filename, content, regexp):
|
||||
mojoms = self._WriteBasicMojoms()
|
||||
self.WriteFile(filename, content)
|
||||
mojoms.append(filename)
|
||||
with self.assertRaisesRegexp(check.CheckException, regexp):
|
||||
self._ParseAndGenerate(mojoms)
|
||||
|
||||
def testMissingAnnotation(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
|
||||
interface PassesHigh {
|
||||
// err: missing annotation.
|
||||
DoHigh(pending_receiver<interfaces.High> hi);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
|
||||
|
||||
def testAllowTooLow(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
|
||||
interface PassesHigh {
|
||||
// err: level is worse than required.
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoHigh(pending_receiver<interfaces.High> hi);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')
|
||||
|
||||
def testWrongEnumInAllow(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
enum Blah {
|
||||
kZero,
|
||||
};
|
||||
interface PassesHigh {
|
||||
// err: different enums.
|
||||
[AllowedContext=Blah.kZero]
|
||||
DoHigh(pending_receiver<interfaces.High> hi);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents, 'but one of kind')
|
||||
|
||||
def testNotAnEnumInAllow(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
interface PassesHigh {
|
||||
// err: not an enum.
|
||||
[AllowedContext=doopdedoo.mojom.kWhatever]
|
||||
DoHigh(pending_receiver<interfaces.High> hi);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents, 'not a valid enum value')
|
||||
|
||||
def testMissingAllowedForNestedStructs(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
struct One { pending_receiver<interfaces.High> hi; };
|
||||
struct Two { One one; };
|
||||
interface PassesNestedHigh {
|
||||
// err: missing annotation.
|
||||
DoNestedHigh(Two two);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
|
||||
|
||||
def testMissingAllowedForNestedUnions(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
struct One { pending_receiver<interfaces.High> hi; };
|
||||
struct Two { One one; };
|
||||
union Three {One one; Two two; };
|
||||
interface PassesNestedHigh {
|
||||
// err: missing annotation.
|
||||
DoNestedHigh(Three three);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
|
||||
|
||||
def testMultipleInterfacesThrows(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
struct One { pending_receiver<interfaces.High> hi; };
|
||||
interface PassesMultipleInterfaces {
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoMultiple(
|
||||
pending_remote<interfaces.Mid> mid,
|
||||
pending_receiver<interfaces.High> hi,
|
||||
One one
|
||||
);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')
|
||||
|
||||
def testMultipleInterfacesAllowed(self):
|
||||
"""Multiple interfaces can be passed, all satisfy the level."""
|
||||
mojoms = self._WriteBasicMojoms()
|
||||
|
||||
b = "b.mojom"
|
||||
self.WriteFile(
|
||||
b, """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
struct One { pending_receiver<interfaces.High> hi; };
|
||||
interface PassesMultipleInterfaces {
|
||||
[AllowedContext=level.Level.kHighest]
|
||||
DoMultiple(
|
||||
pending_receiver<interfaces.High> hi,
|
||||
pending_remote<interfaces.Mid> mid,
|
||||
One one
|
||||
);
|
||||
};
|
||||
""")
|
||||
mojoms.append(b)
|
||||
self._ParseAndGenerate(mojoms)
|
|
@ -1,51 +0,0 @@
|
|||
# Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
_typemap_imports = [
|
||||
"//chrome/chrome_cleaner/mojom/typemaps/typemaps.gni",
|
||||
"//chrome/common/importer/typemaps.gni",
|
||||
"//chrome/common/media_router/mojom/typemaps.gni",
|
||||
"//chrome/typemaps.gni",
|
||||
"//chromecast/typemaps.gni",
|
||||
"//chromeos/typemaps.gni",
|
||||
"//chromeos/components/multidevice/mojom/typemaps.gni",
|
||||
"//chromeos/services/cros_healthd/public/mojom/typemaps.gni",
|
||||
"//chromeos/services/device_sync/public/mojom/typemaps.gni",
|
||||
"//chromeos/services/network_config/public/mojom/typemaps.gni",
|
||||
"//chromeos/services/secure_channel/public/mojom/typemaps.gni",
|
||||
"//components/arc/mojom/typemaps.gni",
|
||||
"//components/chromeos_camera/common/typemaps.gni",
|
||||
"//components/services/storage/public/cpp/filesystem/typemaps.gni",
|
||||
"//components/sync/mojom/typemaps.gni",
|
||||
"//components/typemaps.gni",
|
||||
"//content/browser/typemaps.gni",
|
||||
"//content/public/common/typemaps.gni",
|
||||
"//sandbox/mac/mojom/typemaps.gni",
|
||||
"//services/media_session/public/cpp/typemaps.gni",
|
||||
"//services/proxy_resolver/public/cpp/typemaps.gni",
|
||||
"//services/resource_coordinator/public/cpp/typemaps.gni",
|
||||
"//services/service_manager/public/cpp/typemaps.gni",
|
||||
"//services/tracing/public/mojom/typemaps.gni",
|
||||
]
|
||||
|
||||
_typemaps = []
|
||||
foreach(typemap_import, _typemap_imports) {
|
||||
# Avoid reassignment error by assigning to empty scope first.
|
||||
_imported = {
|
||||
}
|
||||
_imported = read_file(typemap_import, "scope")
|
||||
_typemaps += _imported.typemaps
|
||||
}
|
||||
|
||||
typemaps = []
|
||||
foreach(typemap, _typemaps) {
|
||||
typemaps += [
|
||||
{
|
||||
filename = typemap
|
||||
config = read_file(typemap, "scope")
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
component_macro_suffix = ""
|
|
@ -1,27 +0,0 @@
|
|||
# Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
_HERE_PATH = os.path.dirname(__file__)
|
||||
_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))
|
||||
|
||||
sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
|
||||
import node
|
||||
import node_modules
|
||||
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--tsconfig_path', required=True)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
result = node.RunNode([node_modules.PathToTypescript()] +
|
||||
['--project', args.tsconfig_path])
|
||||
if len(result) != 0:
|
||||
raise RuntimeError('Failed to compile Typescript: \n%s' % result)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2019 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
#
|
||||
|
@ -15,6 +15,7 @@
|
|||
from __future__ import print_function
|
||||
|
||||
import optparse
|
||||
import sys
|
||||
|
||||
|
||||
def Concatenate(filenames):
|
||||
|
@ -47,7 +48,7 @@ def main():
|
|||
parser.set_usage("""Concatenate several files into one.
|
||||
Equivalent to: cat file1 ... > target.""")
|
||||
(_options, args) = parser.parse_args()
|
||||
exit(0 if Concatenate(args) else 1)
|
||||
sys.exit(0 if Concatenate(args) else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2018 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
@ -20,6 +20,7 @@ from __future__ import print_function
|
|||
|
||||
import optparse
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
_MOJO_INTERNAL_MODULE_NAME = "mojo.internal"
|
||||
|
@ -31,10 +32,10 @@ def FilterLine(filename, line, output):
|
|||
return
|
||||
|
||||
if line.startswith("goog.provide"):
|
||||
match = re.match("goog.provide\('([^']+)'\);", line)
|
||||
match = re.match(r"goog.provide\('([^']+)'\);", line)
|
||||
if not match:
|
||||
print("Invalid goog.provide line in %s:\n%s" % (filename, line))
|
||||
exit(1)
|
||||
sys.exit(1)
|
||||
|
||||
module_name = match.group(1)
|
||||
if module_name == _MOJO_INTERNAL_MODULE_NAME:
|
||||
|
@ -67,7 +68,8 @@ def main():
|
|||
Concatenate several files into one, stripping Closure provide and
|
||||
require directives along the way.""")
|
||||
(_, args) = parser.parse_args()
|
||||
exit(0 if ConcatenateAndReplaceExports(args) else 1)
|
||||
sys.exit(0 if ConcatenateAndReplaceExports(args) else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
|
||||
# This utility converts mojom dependencies into their corresponding typemap
|
||||
# paths and formats them to be consumed by generate_type_mappings.py.
|
||||
|
||||
|
||||
def FormatTypemap(typemap_filename):
|
||||
# A simple typemap is valid Python with a minor alteration.
|
||||
with open(typemap_filename) as f:
|
||||
typemap_content = f.read().replace('=\n', '=')
|
||||
typemap = {}
|
||||
exec typemap_content in typemap
|
||||
|
||||
for header in typemap.get('public_headers', []):
|
||||
yield 'public_headers=%s' % header
|
||||
for header in typemap.get('traits_headers', []):
|
||||
yield 'traits_headers=%s' % header
|
||||
for header in typemap.get('type_mappings', []):
|
||||
yield 'type_mappings=%s' % header
|
||||
|
||||
|
||||
def main():
|
||||
typemaps = sys.argv[1:]
|
||||
print(' '.join('--start-typemap %s' % ' '.join(FormatTypemap(typemap))
|
||||
for typemap in typemaps))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2017 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Generates a list of all files in a directory.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2016 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Generates a JSON typemap from its command-line arguments and dependencies.
|
||||
|
@ -82,10 +82,12 @@ def LoadCppTypemapConfig(path):
|
|||
for entry in config['types']:
|
||||
configs[entry['mojom']] = {
|
||||
'typename': entry['cpp'],
|
||||
'forward_declaration': entry.get('forward_declaration', None),
|
||||
'public_headers': config.get('traits_headers', []),
|
||||
'traits_headers': config.get('traits_private_headers', []),
|
||||
'copyable_pass_by_value': entry.get('copyable_pass_by_value',
|
||||
False),
|
||||
'default_constructible': entry.get('default_constructible', True),
|
||||
'force_serialize': entry.get('force_serialize', False),
|
||||
'hashable': entry.get('hashable', False),
|
||||
'move_only': entry.get('move_only', False),
|
||||
|
|
47
utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
Executable file
47
utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
Executable file
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright 2023 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
#
|
||||
# This utility minifies JS files with terser.
|
||||
#
|
||||
# Instance of 'node' has no 'RunNode' member (no-member)
|
||||
# pylint: disable=no-member
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
_HERE_PATH = os.path.dirname(__file__)
|
||||
_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))
|
||||
_CWD = os.getcwd()
|
||||
sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
|
||||
import node
|
||||
import node_modules
|
||||
|
||||
|
||||
def MinifyFile(input_file, output_file):
|
||||
node.RunNode([
|
||||
node_modules.PathToTerser(), input_file, '--mangle', '--compress',
|
||||
'--comments', 'false', '--output', output_file
|
||||
])
|
||||
|
||||
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--input', required=True)
|
||||
parser.add_argument('--output', required=True)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
# Delete the output file if it already exists. It may be a sym link to the
|
||||
# input, because in non-optimized/pre-Terser builds the input file is copied
|
||||
# to the output location with gn copy().
|
||||
out_path = os.path.join(_CWD, args.output)
|
||||
if (os.path.exists(out_path)):
|
||||
os.remove(out_path)
|
||||
|
||||
MinifyFile(os.path.join(_CWD, args.input), out_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2013 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
@ -57,10 +57,17 @@ _BUILTIN_GENERATORS = {
|
|||
"typescript": "mojom_ts_generator",
|
||||
}
|
||||
|
||||
_BUILTIN_CHECKS = {
|
||||
"attributes": "mojom_attributes_check",
|
||||
"definitions": "mojom_definitions_check",
|
||||
"features": "mojom_interface_feature_check",
|
||||
"restrictions": "mojom_restrictions_check",
|
||||
}
|
||||
|
||||
|
||||
def LoadGenerators(generators_string):
|
||||
if not generators_string:
|
||||
return [] # No generators.
|
||||
return {} # No generators.
|
||||
|
||||
generators = {}
|
||||
for generator_name in [s.strip() for s in generators_string.split(",")]:
|
||||
|
@ -74,6 +81,21 @@ def LoadGenerators(generators_string):
|
|||
return generators
|
||||
|
||||
|
||||
def LoadChecks(checks_string):
|
||||
if not checks_string:
|
||||
return {} # No checks.
|
||||
|
||||
checks = {}
|
||||
for check_name in [s.strip() for s in checks_string.split(",")]:
|
||||
check = check_name.lower()
|
||||
if check not in _BUILTIN_CHECKS:
|
||||
print("Unknown check name %s" % check_name)
|
||||
sys.exit(1)
|
||||
check_module = importlib.import_module("checks.%s" % _BUILTIN_CHECKS[check])
|
||||
checks[check] = check_module
|
||||
return checks
|
||||
|
||||
|
||||
def MakeImportStackMessage(imported_filename_stack):
|
||||
"""Make a (human-readable) message listing a chain of imports. (Returned
|
||||
string begins with a newline (if nonempty) and does not end with one.)"""
|
||||
|
@ -82,7 +104,7 @@ def MakeImportStackMessage(imported_filename_stack):
|
|||
zip(imported_filename_stack[1:], imported_filename_stack)]))
|
||||
|
||||
|
||||
class RelativePath(object):
|
||||
class RelativePath:
|
||||
"""Represents a path relative to the source tree or generated output dir."""
|
||||
|
||||
def __init__(self, path, source_root, output_dir):
|
||||
|
@ -142,7 +164,7 @@ def ReadFileContents(filename):
|
|||
return f.read()
|
||||
|
||||
|
||||
class MojomProcessor(object):
|
||||
class MojomProcessor:
|
||||
"""Takes parsed mojom modules and generates language bindings from them.
|
||||
|
||||
Attributes:
|
||||
|
@ -169,8 +191,8 @@ class MojomProcessor(object):
|
|||
if 'c++' in self._typemap:
|
||||
self._typemap['mojolpm'] = self._typemap['c++']
|
||||
|
||||
def _GenerateModule(self, args, remaining_args, generator_modules,
|
||||
rel_filename, imported_filename_stack):
|
||||
def _GenerateModule(self, args, remaining_args, check_modules,
|
||||
generator_modules, rel_filename, imported_filename_stack):
|
||||
# Return the already-generated module.
|
||||
if rel_filename.path in self._processed_files:
|
||||
return self._processed_files[rel_filename.path]
|
||||
|
@ -190,12 +212,16 @@ class MojomProcessor(object):
|
|||
ScrambleMethodOrdinals(module.interfaces, salt)
|
||||
|
||||
if self._should_generate(rel_filename.path):
|
||||
# Run checks on module first.
|
||||
for check_module in check_modules.values():
|
||||
checker = check_module.Check(module)
|
||||
checker.CheckModule()
|
||||
# Then run generation.
|
||||
for language, generator_module in generator_modules.items():
|
||||
generator = generator_module.Generator(
|
||||
module, args.output_dir, typemap=self._typemap.get(language, {}),
|
||||
variant=args.variant, bytecode_path=args.bytecode_path,
|
||||
for_blink=args.for_blink,
|
||||
js_bindings_mode=args.js_bindings_mode,
|
||||
js_generate_struct_deserializers=\
|
||||
args.js_generate_struct_deserializers,
|
||||
export_attribute=args.export_attribute,
|
||||
|
@ -234,6 +260,7 @@ def _Generate(args, remaining_args):
|
|||
args.import_directories[idx] = RelativePath(tokens[0], args.depth,
|
||||
args.output_dir)
|
||||
generator_modules = LoadGenerators(args.generators_string)
|
||||
check_modules = LoadChecks(args.checks_string)
|
||||
|
||||
fileutil.EnsureDirectoryExists(args.output_dir)
|
||||
|
||||
|
@ -246,7 +273,7 @@ def _Generate(args, remaining_args):
|
|||
|
||||
for filename in args.filename:
|
||||
processor._GenerateModule(
|
||||
args, remaining_args, generator_modules,
|
||||
args, remaining_args, check_modules, generator_modules,
|
||||
RelativePath(filename, args.depth, args.output_dir), [])
|
||||
|
||||
return 0
|
||||
|
@ -286,6 +313,12 @@ def main():
|
|||
metavar="GENERATORS",
|
||||
default="c++,javascript,java,mojolpm",
|
||||
help="comma-separated list of generators")
|
||||
generate_parser.add_argument("-c",
|
||||
"--checks",
|
||||
dest="checks_string",
|
||||
metavar="CHECKS",
|
||||
default=",".join(_BUILTIN_CHECKS.keys()),
|
||||
help="comma-separated list of checks")
|
||||
generate_parser.add_argument(
|
||||
"--gen_dir", dest="gen_directories", action="append", metavar="directory",
|
||||
default=[], help="add a directory to be searched for the syntax trees.")
|
||||
|
@ -308,11 +341,6 @@ def main():
|
|||
generate_parser.add_argument("--for_blink", action="store_true",
|
||||
help="Use WTF types as generated types for mojo "
|
||||
"string/array/map.")
|
||||
generate_parser.add_argument(
|
||||
"--js_bindings_mode", choices=["new", "old"], default="old",
|
||||
help="This option only affects the JavaScript bindings. The value could "
|
||||
"be \"new\" to generate new-style lite JS bindings in addition to the "
|
||||
"old, or \"old\" to only generate old bindings.")
|
||||
generate_parser.add_argument(
|
||||
"--js_generate_struct_deserializers", action="store_true",
|
||||
help="Generate javascript deserialize methods for structs in "
|
||||
|
@ -387,4 +415,10 @@ def main():
|
|||
|
||||
if __name__ == "__main__":
|
||||
with crbug_1001171.DumpStateOnLookupError():
|
||||
sys.exit(main())
|
||||
ret = main()
|
||||
# Exit without running GC, which can save multiple seconds due to the large
|
||||
# number of object created. But flush is necessary as os._exit doesn't do
|
||||
# that.
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
os._exit(ret)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2014 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
@ -8,13 +8,13 @@ from mojom_bindings_generator import MakeImportStackMessage
|
|||
from mojom_bindings_generator import ScrambleMethodOrdinals
|
||||
|
||||
|
||||
class FakeIface(object):
|
||||
class FakeIface:
|
||||
def __init__(self):
|
||||
self.mojom_name = None
|
||||
self.methods = None
|
||||
|
||||
|
||||
class FakeMethod(object):
|
||||
class FakeMethod:
|
||||
def __init__(self, explicit_ordinal=None):
|
||||
self.explicit_ordinal = explicit_ordinal
|
||||
self.ordinal = explicit_ordinal
|
||||
|
|
|
@ -1,119 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Downgrades *.mojom files to the old mojo types for remotes and receivers."""
|
||||
|
||||
import argparse
|
||||
import fnmatch
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
# List of patterns and replacements to match and use against the contents of a
|
||||
# mojo file. Each replacement string will be used with Python string's format()
|
||||
# function, so the '{}' substring is used to mark where the mojo type should go.
|
||||
_MOJO_REPLACEMENTS = {
|
||||
r'pending_remote': r'{}',
|
||||
r'pending_receiver': r'{}&',
|
||||
r'pending_associated_remote': r'associated {}',
|
||||
r'pending_associated_receiver': r'associated {}&',
|
||||
}
|
||||
|
||||
# Pre-compiled regular expression that matches against any of the replacements.
|
||||
_REGEXP_PATTERN = re.compile(
|
||||
r'|'.join(
|
||||
['{}\s*<\s*(.*?)\s*>'.format(k) for k in _MOJO_REPLACEMENTS.keys()]),
|
||||
flags=re.DOTALL)
|
||||
|
||||
|
||||
def ReplaceFunction(match_object):
|
||||
"""Returns the right replacement for the string matched against the regexp."""
|
||||
for index, (match, repl) in enumerate(_MOJO_REPLACEMENTS.items(), 1):
|
||||
if match_object.group(0).startswith(match):
|
||||
return repl.format(match_object.group(index))
|
||||
|
||||
|
||||
def DowngradeFile(path, output_dir=None):
|
||||
"""Downgrades the mojom file specified by |path| to the old mojo types.
|
||||
|
||||
Optionally pass |output_dir| to place the result under a separate output
|
||||
directory, preserving the relative path to the file included in |path|.
|
||||
"""
|
||||
# Use a temporary file to dump the new contents after replacing the patterns.
|
||||
with open(path) as src_mojo_file:
|
||||
with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp_mojo_file:
|
||||
tmp_contents = _REGEXP_PATTERN.sub(ReplaceFunction, src_mojo_file.read())
|
||||
tmp_mojo_file.write(tmp_contents)
|
||||
|
||||
# Files should be placed in the desired output directory
|
||||
if output_dir:
|
||||
output_filepath = os.path.join(output_dir, os.path.basename(path))
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
else:
|
||||
output_filepath = path
|
||||
|
||||
# Write the new contents preserving the original file's attributes.
|
||||
shutil.copystat(path, tmp_mojo_file.name)
|
||||
shutil.move(tmp_mojo_file.name, output_filepath)
|
||||
|
||||
# Make sure to "touch" the new file so that access, modify and change times
|
||||
# are always newer than the source file's, otherwise Modify time will be kept
|
||||
# as per the call to shutil.copystat(), causing unnecessary generations of the
|
||||
# output file in subsequent builds due to ninja considering it dirty.
|
||||
os.utime(output_filepath, None)
|
||||
|
||||
|
||||
def DowngradeDirectory(path, output_dir=None):
|
||||
"""Downgrades mojom files inside directory |path| to the old mojo types.
|
||||
|
||||
Optionally pass |output_dir| to place the result under a separate output
|
||||
directory, preserving the relative path to the file included in |path|.
|
||||
"""
|
||||
# We don't have recursive glob.glob() nor pathlib.Path.rglob() in Python 2.7
|
||||
mojom_filepaths = []
|
||||
for dir_path, _, filenames in os.walk(path):
|
||||
for filename in fnmatch.filter(filenames, "*mojom"):
|
||||
mojom_filepaths.append(os.path.join(dir_path, filename))
|
||||
|
||||
for path in mojom_filepaths:
|
||||
absolute_dirpath = os.path.dirname(os.path.abspath(path))
|
||||
if output_dir:
|
||||
dest_dirpath = output_dir + absolute_dirpath
|
||||
else:
|
||||
dest_dirpath = absolute_dirpath
|
||||
DowngradeFile(path, dest_dirpath)
|
||||
|
||||
|
||||
def DowngradePath(src_path, output_dir=None):
|
||||
"""Downgrades the mojom files pointed by |src_path| to the old mojo types.
|
||||
|
||||
Optionally pass |output_dir| to place the result under a separate output
|
||||
directory, preserving the relative path to the file included in |path|.
|
||||
"""
|
||||
if os.path.isdir(src_path):
|
||||
DowngradeDirectory(src_path, output_dir)
|
||||
elif os.path.isfile(src_path):
|
||||
DowngradeFile(src_path, output_dir)
|
||||
else:
|
||||
print(">>> {} not pointing to a valid file or directory".format(src_path))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Downgrade *.mojom files to use the old mojo types.")
|
||||
parser.add_argument(
|
||||
"srcpath", help="path to the file or directory to apply the conversion")
|
||||
parser.add_argument(
|
||||
"--outdir", help="the directory to place the converted file(s) under")
|
||||
args = parser.parse_args()
|
||||
|
||||
DowngradePath(args.srcpath, args.outdir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2020 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
@ -17,7 +17,8 @@ def CheckCppTypemapConfigs(target_name, config_filename, out_filename):
|
|||
])
|
||||
_SUPPORTED_TYPE_KEYS = set([
|
||||
'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable',
|
||||
'move_only', 'nullable_is_same_type'
|
||||
'move_only', 'nullable_is_same_type', 'forward_declaration',
|
||||
'default_constructible'
|
||||
])
|
||||
with open(config_filename, 'r') as f:
|
||||
for config in json.load(f):
|
||||
|
|
18
utils/ipc/mojo/public/tools/mojom/BUILD.gn
Normal file
18
utils/ipc/mojo/public/tools/mojom/BUILD.gn
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
group("tests") {
|
||||
data = [
|
||||
"check_stable_mojom_compatibility_unittest.py",
|
||||
"check_stable_mojom_compatibility.py",
|
||||
"const_unittest.py",
|
||||
"enum_unittest.py",
|
||||
"feature_unittest.py",
|
||||
"mojom_parser_test_case.py",
|
||||
"mojom_parser_unittest.py",
|
||||
"mojom_parser.py",
|
||||
"stable_attribute_unittest.py",
|
||||
"version_compatibility_unittest.py",
|
||||
]
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2020 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Verifies backward-compatibility of mojom type changes.
|
||||
|
@ -12,20 +12,18 @@ This can be used e.g. by a presubmit check to prevent developers from making
|
|||
breaking changes to stable mojoms."""
|
||||
|
||||
import argparse
|
||||
import errno
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import os.path
|
||||
import shutil
|
||||
import six
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from mojom.generate import module
|
||||
from mojom.generate import translate
|
||||
from mojom.parse import parser
|
||||
|
||||
# pylint: disable=raise-missing-from
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
pass
|
||||
|
@ -41,6 +39,8 @@ def _ValidateDelta(root, delta):
|
|||
transitive closure of a mojom's input dependencies all at once.
|
||||
"""
|
||||
|
||||
translate.is_running_backwards_compatibility_check_hack = True
|
||||
|
||||
# First build a map of all files covered by the delta
|
||||
affected_files = set()
|
||||
old_files = {}
|
||||
|
@ -73,11 +73,35 @@ def _ValidateDelta(root, delta):
|
|||
try:
|
||||
ast = parser.Parse(contents, mojom)
|
||||
except Exception as e:
|
||||
six.reraise(
|
||||
ParseError,
|
||||
'encountered exception {0} while parsing {1}'.format(e, mojom),
|
||||
sys.exc_info()[2])
|
||||
raise ParseError('encountered exception {0} while parsing {1}'.format(
|
||||
e, mojom))
|
||||
|
||||
# Files which are generated at compile time can't be checked by this script
|
||||
# (at the moment) since they may not exist in the output directory.
|
||||
generated_files_to_skip = {
|
||||
('third_party/blink/public/mojom/runtime_feature_state/'
|
||||
'runtime_feature.mojom'),
|
||||
('third_party/blink/public/mojom/origin_trial_feature/'
|
||||
'origin_trial_feature.mojom'),
|
||||
}
|
||||
|
||||
ast.import_list.items = [
|
||||
x for x in ast.import_list.items
|
||||
if x.import_filename not in generated_files_to_skip
|
||||
]
|
||||
|
||||
for imp in ast.import_list:
|
||||
if (not file_overrides.get(imp.import_filename)
|
||||
and not os.path.exists(os.path.join(root, imp.import_filename))):
|
||||
# Speculatively construct a path prefix to locate the import_filename
|
||||
mojom_path = os.path.dirname(os.path.normpath(mojom)).split(os.sep)
|
||||
test_prefix = ''
|
||||
for path_component in mojom_path:
|
||||
test_prefix = os.path.join(test_prefix, path_component)
|
||||
test_import_filename = os.path.join(test_prefix, imp.import_filename)
|
||||
if os.path.exists(os.path.join(root, test_import_filename)):
|
||||
imp.import_filename = test_import_filename
|
||||
break
|
||||
parseMojom(imp.import_filename, file_overrides, override_modules)
|
||||
|
||||
# Now that the transitive set of dependencies has been imported and parsed
|
||||
|
@ -89,10 +113,10 @@ def _ValidateDelta(root, delta):
|
|||
modules[mojom] = translate.OrderedModule(ast, mojom, all_modules)
|
||||
|
||||
old_modules = {}
|
||||
for mojom in old_files.keys():
|
||||
for mojom in old_files:
|
||||
parseMojom(mojom, old_files, old_modules)
|
||||
new_modules = {}
|
||||
for mojom in new_files.keys():
|
||||
for mojom in new_files:
|
||||
parseMojom(mojom, new_files, new_modules)
|
||||
|
||||
# At this point we have a complete set of translated Modules from both the
|
||||
|
@ -132,12 +156,21 @@ def _ValidateDelta(root, delta):
|
|||
'can be deleted by a subsequent change.' % qualified_name)
|
||||
|
||||
checker = module.BackwardCompatibilityChecker()
|
||||
try:
|
||||
if not checker.IsBackwardCompatible(new_types[new_name], kind):
|
||||
raise Exception('Stable type %s appears to have changed in a way which '
|
||||
raise Exception(
|
||||
'Stable type %s appears to have changed in a way which '
|
||||
'breaks backward-compatibility. Please fix!\n\nIf you '
|
||||
'believe this assessment to be incorrect, please file a '
|
||||
'Chromium bug against the "Internals>Mojo>Bindings" '
|
||||
'component.' % qualified_name)
|
||||
except Exception as e:
|
||||
raise Exception(
|
||||
'Stable type %s appears to have changed in a way which '
|
||||
'breaks backward-compatibility: \n\n%s.\nPlease fix!\n\nIf you '
|
||||
'believe this assessment to be incorrect, please file a '
|
||||
'Chromium bug against the "Internals>Mojo>Bindings" '
|
||||
'component.' % (qualified_name, e))
|
||||
|
||||
|
||||
def Run(command_line, delta=None):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2020 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
@ -15,7 +15,7 @@ import check_stable_mojom_compatibility
|
|||
from mojom.generate import module
|
||||
|
||||
|
||||
class Change(object):
|
||||
class Change:
|
||||
"""Helper to clearly define a mojom file delta to be analyzed."""
|
||||
|
||||
def __init__(self, filename, old=None, new=None):
|
||||
|
@ -28,7 +28,7 @@ class Change(object):
|
|||
|
||||
class UnchangedFile(Change):
|
||||
def __init__(self, filename, contents):
|
||||
super(UnchangedFile, self).__init__(filename, old=contents, new=contents)
|
||||
super().__init__(filename, old=contents, new=contents)
|
||||
|
||||
|
||||
class CheckStableMojomCompatibilityTest(unittest.TestCase):
|
||||
|
@ -258,3 +258,82 @@ class CheckStableMojomCompatibilityTest(unittest.TestCase):
|
|||
[Stable] struct T { foo.S s; int32 x; };
|
||||
""")
|
||||
])
|
||||
|
||||
def testWithPartialImport(self):
|
||||
"""The compatibility checking tool correctly parses imports with partial
|
||||
paths."""
|
||||
self.assertBackwardCompatible([
|
||||
UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
|
||||
Change('foo/bar.mojom',
|
||||
old="""\
|
||||
module bar;
|
||||
import "foo/foo.mojom";
|
||||
[Stable] struct T { foo.S s; };
|
||||
""",
|
||||
new="""\
|
||||
module bar;
|
||||
import "foo.mojom";
|
||||
[Stable] struct T { foo.S s; };
|
||||
""")
|
||||
])
|
||||
|
||||
self.assertBackwardCompatible([
|
||||
UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
|
||||
Change('foo/bar.mojom',
|
||||
old="""\
|
||||
module bar;
|
||||
import "foo.mojom";
|
||||
[Stable] struct T { foo.S s; };
|
||||
""",
|
||||
new="""\
|
||||
module bar;
|
||||
import "foo/foo.mojom";
|
||||
[Stable] struct T { foo.S s; };
|
||||
""")
|
||||
])
|
||||
|
||||
self.assertNotBackwardCompatible([
|
||||
UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
|
||||
Change('bar/bar.mojom',
|
||||
old="""\
|
||||
module bar;
|
||||
import "foo/foo.mojom";
|
||||
[Stable] struct T { foo.S s; };
|
||||
""",
|
||||
new="""\
|
||||
module bar;
|
||||
import "foo.mojom";
|
||||
[Stable] struct T { foo.S s; };
|
||||
""")
|
||||
])
|
||||
|
||||
self.assertNotBackwardCompatible([
|
||||
UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
|
||||
Change('bar/bar.mojom',
|
||||
old="""\
|
||||
module bar;
|
||||
import "foo.mojom";
|
||||
[Stable] struct T { foo.S s; };
|
||||
""",
|
||||
new="""\
|
||||
module bar;
|
||||
import "foo/foo.mojom";
|
||||
[Stable] struct T { foo.S s; };
|
||||
""")
|
||||
])
|
||||
|
||||
def testNewEnumDefault(self):
|
||||
# Should be backwards compatible since it does not affect the wire format.
|
||||
# This specific case also checks that the backwards compatibility checker
|
||||
# does not throw an error due to the older version of the enum not
|
||||
# specifying [Default].
|
||||
self.assertBackwardCompatible([
|
||||
Change('foo/foo.mojom',
|
||||
old='[Extensible] enum E { One };',
|
||||
new='[Extensible] enum E { [Default] One };')
|
||||
])
|
||||
self.assertBackwardCompatible([
|
||||
Change('foo/foo.mojom',
|
||||
old='[Extensible] enum E { [Default] One, Two, };',
|
||||
new='[Extensible] enum E { One, [Default] Two, };')
|
||||
])
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2020 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2020 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
@ -90,3 +90,31 @@ class EnumTest(MojomParserTestCase):
|
|||
self.assertEqual('F', b.enums[0].mojom_name)
|
||||
self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name)
|
||||
self.assertEqual(37, b.enums[0].fields[0].numeric_value)
|
||||
|
||||
def testEnumAttributesAreEnums(self):
|
||||
"""Verifies that enum values in attributes are really enum types."""
|
||||
a_mojom = 'a.mojom'
|
||||
self.WriteFile(a_mojom, 'module a; enum E { kFoo, kBar };')
|
||||
b_mojom = 'b.mojom'
|
||||
self.WriteFile(
|
||||
b_mojom, 'module b;'
|
||||
'import "a.mojom";'
|
||||
'[MooCow=a.E.kFoo]'
|
||||
'interface Foo { Foo(); };')
|
||||
self.ParseMojoms([a_mojom, b_mojom])
|
||||
b = self.LoadModule(b_mojom)
|
||||
self.assertEqual(b.interfaces[0].attributes['MooCow'].mojom_name, 'kFoo')
|
||||
|
||||
def testConstantAttributes(self):
|
||||
"""Verifies that constants as attributes are translated to the constant."""
|
||||
a_mojom = 'a.mojom'
|
||||
self.WriteFile(
|
||||
a_mojom, 'module a;'
|
||||
'enum E { kFoo, kBar };'
|
||||
'const E kB = E.kFoo;'
|
||||
'[Attr=kB] interface Hello { Foo(); };')
|
||||
self.ParseMojoms([a_mojom])
|
||||
a = self.LoadModule(a_mojom)
|
||||
self.assertEqual(a.interfaces[0].attributes['Attr'].mojom_name, 'kB')
|
||||
self.assertEquals(a.interfaces[0].attributes['Attr'].value.mojom_name,
|
||||
'kFoo')
|
||||
|
|
84
utils/ipc/mojo/public/tools/mojom/feature_unittest.py
Normal file
84
utils/ipc/mojo/public/tools/mojom/feature_unittest.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
# Copyright 2023 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
from mojom_parser_test_case import MojomParserTestCase
|
||||
|
||||
|
||||
class FeatureTest(MojomParserTestCase):
|
||||
"""Tests feature parsing behavior."""
|
||||
def testFeatureOff(self):
|
||||
"""Verifies basic parsing of feature types."""
|
||||
types = self.ExtractTypes("""
|
||||
// e.g. BASE_DECLARE_FEATURE(kFeature);
|
||||
[AttributeOne=ValueOne]
|
||||
feature kFeature {
|
||||
// BASE_FEATURE(kFeature,"MyFeature",
|
||||
// base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
const string name = "MyFeature";
|
||||
const bool default_state = false;
|
||||
};
|
||||
""")
|
||||
self.assertEqual('name', types['kFeature'].constants[0].mojom_name)
|
||||
self.assertEqual('"MyFeature"', types['kFeature'].constants[0].value)
|
||||
self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)
|
||||
self.assertEqual('false', types['kFeature'].constants[1].value)
|
||||
|
||||
def testFeatureOn(self):
|
||||
"""Verifies basic parsing of feature types."""
|
||||
types = self.ExtractTypes("""
|
||||
// e.g. BASE_DECLARE_FEATURE(kFeature);
|
||||
feature kFeature {
|
||||
// BASE_FEATURE(kFeature,"MyFeature",
|
||||
// base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
const string name = "MyFeature";
|
||||
const bool default_state = true;
|
||||
};
|
||||
""")
|
||||
self.assertEqual('name', types['kFeature'].constants[0].mojom_name)
|
||||
self.assertEqual('"MyFeature"', types['kFeature'].constants[0].value)
|
||||
self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)
|
||||
self.assertEqual('true', types['kFeature'].constants[1].value)
|
||||
|
||||
def testFeatureWeakKeyword(self):
|
||||
"""Verifies that `feature` is a weak keyword."""
|
||||
types = self.ExtractTypes("""
|
||||
// e.g. BASE_DECLARE_FEATURE(kFeature);
|
||||
[AttributeOne=ValueOne]
|
||||
feature kFeature {
|
||||
// BASE_FEATURE(kFeature,"MyFeature",
|
||||
// base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
const string name = "MyFeature";
|
||||
const bool default_state = false;
|
||||
};
|
||||
struct MyStruct {
|
||||
bool feature = true;
|
||||
};
|
||||
interface InterfaceName {
|
||||
Method(string feature) => (int32 feature);
|
||||
};
|
||||
""")
|
||||
self.assertEqual('name', types['kFeature'].constants[0].mojom_name)
|
||||
self.assertEqual('"MyFeature"', types['kFeature'].constants[0].value)
|
||||
self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)
|
||||
self.assertEqual('false', types['kFeature'].constants[1].value)
|
||||
|
||||
def testFeatureAttributesAreFeatures(self):
|
||||
"""Verifies that feature values in attributes are really feature types."""
|
||||
a_mojom = 'a.mojom'
|
||||
self.WriteFile(
|
||||
a_mojom, 'module a;'
|
||||
'feature F { const string name = "f";'
|
||||
'const bool default_state = false; };')
|
||||
b_mojom = 'b.mojom'
|
||||
self.WriteFile(
|
||||
b_mojom, 'module b;'
|
||||
'import "a.mojom";'
|
||||
'feature G'
|
||||
'{const string name = "g"; const bool default_state = false;};'
|
||||
'[Attri=a.F] interface Foo { Foo(); };'
|
||||
'[Boink=G] interface Bar {};')
|
||||
self.ParseMojoms([a_mojom, b_mojom])
|
||||
b = self.LoadModule(b_mojom)
|
||||
self.assertEqual(b.interfaces[0].attributes['Attri'].mojom_name, 'F')
|
||||
self.assertEqual(b.interfaces[1].attributes['Boink'].mojom_name, 'G')
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2020 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
@ -8,6 +8,7 @@ group("mojom") {
|
|||
"error.py",
|
||||
"fileutil.py",
|
||||
"generate/__init__.py",
|
||||
"generate/check.py",
|
||||
"generate/generator.py",
|
||||
"generate/module.py",
|
||||
"generate/pack.py",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2014 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
# Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2015 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import errno
|
||||
import imp
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
# Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2015 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import imp
|
||||
import os.path
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from mojom import fileutil
|
||||
|
||||
|
||||
class FileUtilTest(unittest.TestCase):
|
||||
def testEnsureDirectoryExists(self):
|
||||
"""Test that EnsureDirectoryExists fuctions correctly."""
|
||||
"""Test that EnsureDirectoryExists functions correctly."""
|
||||
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
try:
|
||||
|
|
26
utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
Normal file
26
utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Code shared by the various pre-generation mojom checkers."""
|
||||
|
||||
|
||||
class CheckException(Exception):
|
||||
def __init__(self, module, message):
|
||||
self.module = module
|
||||
self.message = message
|
||||
super().__init__(self.message)
|
||||
|
||||
def __str__(self):
|
||||
return "Failed mojo pre-generation check for {}:\n{}".format(
|
||||
self.module.path, self.message)
|
||||
|
||||
|
||||
class Check:
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
|
||||
def CheckModule(self):
|
||||
""" Subclass should return True if its Checks pass, and throw an
|
||||
exception otherwise. CheckModule will be called immediately before
|
||||
mojom.generate.Generator.GenerateFiles()"""
|
||||
raise NotImplementedError("Subclasses must override/implement this method")
|
|
@ -1,93 +0,0 @@
|
|||
# Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Resolves the values used for constants and enums."""
|
||||
|
||||
from itertools import ifilter
|
||||
|
||||
from mojom.generate import module as mojom
|
||||
|
||||
|
||||
def ResolveConstants(module, expression_to_text):
|
||||
in_progress = set()
|
||||
computed = set()
|
||||
|
||||
def GetResolvedValue(named_value):
|
||||
assert isinstance(named_value, (mojom.EnumValue, mojom.ConstantValue))
|
||||
if isinstance(named_value, mojom.EnumValue):
|
||||
field = next(
|
||||
ifilter(lambda field: field.name == named_value.name,
|
||||
named_value.enum.fields), None)
|
||||
if not field:
|
||||
raise RuntimeError(
|
||||
'Unable to get computed value for field %s of enum %s' %
|
||||
(named_value.name, named_value.enum.name))
|
||||
if field not in computed:
|
||||
ResolveEnum(named_value.enum)
|
||||
return field.resolved_value
|
||||
else:
|
||||
ResolveConstant(named_value.constant)
|
||||
named_value.resolved_value = named_value.constant.resolved_value
|
||||
return named_value.resolved_value
|
||||
|
||||
def ResolveConstant(constant):
|
||||
if constant in computed:
|
||||
return
|
||||
if constant in in_progress:
|
||||
raise RuntimeError('Circular dependency for constant: %s' % constant.name)
|
||||
in_progress.add(constant)
|
||||
if isinstance(constant.value, (mojom.EnumValue, mojom.ConstantValue)):
|
||||
resolved_value = GetResolvedValue(constant.value)
|
||||
else:
|
||||
resolved_value = expression_to_text(constant.value)
|
||||
constant.resolved_value = resolved_value
|
||||
in_progress.remove(constant)
|
||||
computed.add(constant)
|
||||
|
||||
def ResolveEnum(enum):
|
||||
def ResolveEnumField(enum, field, default_value):
|
||||
if field in computed:
|
||||
return
|
||||
if field in in_progress:
|
||||
raise RuntimeError('Circular dependency for enum: %s' % enum.name)
|
||||
in_progress.add(field)
|
||||
if field.value:
|
||||
if isinstance(field.value, mojom.EnumValue):
|
||||
resolved_value = GetResolvedValue(field.value)
|
||||
elif isinstance(field.value, str):
|
||||
resolved_value = int(field.value, 0)
|
||||
else:
|
||||
raise RuntimeError('Unexpected value: %s' % field.value)
|
||||
else:
|
||||
resolved_value = default_value
|
||||
field.resolved_value = resolved_value
|
||||
in_progress.remove(field)
|
||||
computed.add(field)
|
||||
|
||||
current_value = 0
|
||||
for field in enum.fields:
|
||||
ResolveEnumField(enum, field, current_value)
|
||||
current_value = field.resolved_value + 1
|
||||
|
||||
for constant in module.constants:
|
||||
ResolveConstant(constant)
|
||||
|
||||
for enum in module.enums:
|
||||
ResolveEnum(enum)
|
||||
|
||||
for struct in module.structs:
|
||||
for constant in struct.constants:
|
||||
ResolveConstant(constant)
|
||||
for enum in struct.enums:
|
||||
ResolveEnum(enum)
|
||||
for field in struct.fields:
|
||||
if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)):
|
||||
field.default.resolved_value = GetResolvedValue(field.default)
|
||||
|
||||
for interface in module.interfaces:
|
||||
for constant in interface.constants:
|
||||
ResolveConstant(constant)
|
||||
for enum in interface.enums:
|
||||
ResolveEnum(enum)
|
||||
|
||||
return module
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2013 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Code shared by the various language-specific code generators."""
|
||||
|
@ -97,7 +97,7 @@ def ToLowerSnakeCase(identifier):
|
|||
return _ToSnakeCase(identifier, upper=False)
|
||||
|
||||
|
||||
class Stylizer(object):
|
||||
class Stylizer:
|
||||
"""Stylizers specify naming rules to map mojom names to names in generated
|
||||
code. For example, if you would like method_name in mojom to be mapped to
|
||||
MethodName in the generated code, you need to define a subclass of Stylizer
|
||||
|
@ -130,6 +130,9 @@ class Stylizer(object):
|
|||
def StylizeEnum(self, mojom_name):
|
||||
return mojom_name
|
||||
|
||||
def StylizeFeature(self, mojom_name):
|
||||
return mojom_name
|
||||
|
||||
def StylizeModule(self, mojom_namespace):
|
||||
return mojom_namespace
|
||||
|
||||
|
@ -233,7 +236,7 @@ def AddComputedData(module):
|
|||
_AddInterfaceComputedData(interface)
|
||||
|
||||
|
||||
class Generator(object):
|
||||
class Generator:
|
||||
# Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all
|
||||
# files to stdout.
|
||||
def __init__(self,
|
||||
|
@ -243,7 +246,6 @@ class Generator(object):
|
|||
variant=None,
|
||||
bytecode_path=None,
|
||||
for_blink=False,
|
||||
js_bindings_mode="new",
|
||||
js_generate_struct_deserializers=False,
|
||||
export_attribute=None,
|
||||
export_header=None,
|
||||
|
@ -262,7 +264,6 @@ class Generator(object):
|
|||
self.variant = variant
|
||||
self.bytecode_path = bytecode_path
|
||||
self.for_blink = for_blink
|
||||
self.js_bindings_mode = js_bindings_mode
|
||||
self.js_generate_struct_deserializers = js_generate_struct_deserializers
|
||||
self.export_attribute = export_attribute
|
||||
self.export_header = export_header
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2014 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import imp
|
||||
import importlib.util
|
||||
import os.path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
def _GetDirAbove(dirname):
|
||||
"""Returns the directory "above" this file containing |dirname| (which must
|
||||
also be "above" this file)."""
|
||||
|
@ -20,12 +19,11 @@ def _GetDirAbove(dirname):
|
|||
|
||||
|
||||
try:
|
||||
imp.find_module("mojom")
|
||||
importlib.util.find_spec("mojom")
|
||||
except ImportError:
|
||||
sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
|
||||
from mojom.generate import generator
|
||||
|
||||
|
||||
class StringManipulationTest(unittest.TestCase):
|
||||
"""generator contains some string utilities, this tests only those."""
|
||||
|
||||
|
@ -69,6 +67,5 @@ class StringManipulationTest(unittest.TestCase):
|
|||
self.assertEquals("SNAKE_D3D11_CASE",
|
||||
generator.ToUpperSnakeCase("snakeD3d11Case"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2014 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2013 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import copy
|
||||
from mojom.generate import module as mojom
|
||||
|
||||
# This module provides a mechanism for determining the packed order and offsets
|
||||
|
@ -15,7 +16,7 @@ from mojom.generate import module as mojom
|
|||
HEADER_SIZE = 8
|
||||
|
||||
|
||||
class PackedField(object):
|
||||
class PackedField:
|
||||
kind_to_size = {
|
||||
mojom.BOOL: 1,
|
||||
mojom.INT8: 1,
|
||||
|
@ -75,18 +76,55 @@ class PackedField(object):
|
|||
return 8
|
||||
return cls.GetSizeForKind(kind)
|
||||
|
||||
def __init__(self, field, index, ordinal):
|
||||
def __init__(self,
|
||||
field,
|
||||
index,
|
||||
ordinal,
|
||||
original_field=None,
|
||||
sub_ordinal=None,
|
||||
linked_value_packed_field=None):
|
||||
"""
|
||||
Args:
|
||||
field: the original field.
|
||||
index: the position of the original field in the struct.
|
||||
ordinal: the ordinal of the field for serialization.
|
||||
original_field: See below.
|
||||
sub_ordinal: See below.
|
||||
linked_value_packed_field: See below.
|
||||
|
||||
original_field, sub_ordinal, and linked_value_packed_field are used to
|
||||
support nullable ValueKind fields. For legacy reasons, nullable ValueKind
|
||||
fields actually generate two PackedFields. This allows:
|
||||
|
||||
- backwards compatibility prior to Mojo support for nullable ValueKinds.
|
||||
- correct packing of fields for the aforementioned backwards compatibility.
|
||||
|
||||
When translating Fields to PackedFields, the original field is turned into
|
||||
two PackedFields: the first PackedField always has type mojom.BOOL, while
|
||||
the second PackedField has the non-nullable version of the field's kind.
|
||||
|
||||
When constructing these PackedFields, original_field references the field
|
||||
as defined in the mojom; the name as defined in the mojom will be used for
|
||||
all layers above the wire/data layer.
|
||||
|
||||
sub_ordinal is used to sort the two PackedFields correctly with respect to
|
||||
each other: the first mojom.BOOL field always has sub_ordinal 0, while the
|
||||
second field always has sub_ordinal 1.
|
||||
|
||||
Finally, linked_value_packed_field is used by the serialization and
|
||||
deserialization helpers, which generally just iterate over a PackedStruct's
|
||||
PackedField's in ordinal order. This allows the helpers to easily reference
|
||||
any related PackedFields rather than having to lookup related PackedFields
|
||||
by index while iterating.
|
||||
"""
|
||||
self.field = field
|
||||
self.index = index
|
||||
self.ordinal = ordinal
|
||||
self.size = self.GetSizeForKind(field.kind)
|
||||
self.alignment = self.GetAlignmentForKind(field.kind)
|
||||
self.original_field = original_field
|
||||
self.sub_ordinal = sub_ordinal
|
||||
self.linked_value_packed_field = linked_value_packed_field
|
||||
self.size = self.GetSizeForKind(self.field.kind)
|
||||
self.alignment = self.GetAlignmentForKind(self.field.kind)
|
||||
self.offset = None
|
||||
self.bit = None
|
||||
self.min_version = None
|
||||
|
@ -120,7 +158,33 @@ def GetPayloadSizeUpToField(field):
|
|||
return offset + pad
|
||||
|
||||
|
||||
class PackedStruct(object):
|
||||
def IsNullableValueKindPackedField(field):
|
||||
"""Returns true if `field` is derived from a nullable ValueKind field.
|
||||
|
||||
Nullable ValueKind fields often require special handling in the bindings due
|
||||
to the way the implementation is constrained for wire compatibility.
|
||||
"""
|
||||
assert isinstance(field, PackedField)
|
||||
return field.sub_ordinal is not None
|
||||
|
||||
|
||||
def IsPrimaryNullableValueKindPackedField(field):
|
||||
"""Returns true if `field` is derived from a nullable ValueKind mojom field
|
||||
and is the "primary" field.
|
||||
|
||||
The primary field is a bool PackedField that controls if the field should be
|
||||
considered as present or not; it will have a reference to the PackedField that
|
||||
holds the actual value representation if considered present.
|
||||
|
||||
Bindings code that translates between the wire protocol and the higher layers
|
||||
can use this to simplify mapping multiple PackedFields to the single field
|
||||
that is logically exposed to bindings consumers.
|
||||
"""
|
||||
assert isinstance(field, PackedField)
|
||||
return field.linked_value_packed_field is not None
|
||||
|
||||
|
||||
class PackedStruct:
|
||||
def __init__(self, struct):
|
||||
self.struct = struct
|
||||
# |packed_fields| contains all the fields, in increasing offset order.
|
||||
|
@ -139,9 +203,41 @@ class PackedStruct(object):
|
|||
for index, field in enumerate(struct.fields):
|
||||
if field.ordinal is not None:
|
||||
ordinal = field.ordinal
|
||||
# Nullable value types are a bit weird: they generate two PackedFields
|
||||
# despite being a single ValueKind. This is for wire compatibility to
|
||||
# ease the transition from legacy mojom syntax where nullable value types
|
||||
# were not supported.
|
||||
if isinstance(field.kind, mojom.ValueKind) and field.kind.is_nullable:
|
||||
# The suffixes intentionally use Unicode codepoints which are considered
|
||||
# valid C++/Java/JavaScript identifiers, yet are unlikely to be used in
|
||||
# actual user code.
|
||||
has_value_field = copy.copy(field)
|
||||
has_value_field.name = f'{field.mojom_name}_$flag'
|
||||
has_value_field.kind = mojom.BOOL
|
||||
|
||||
value_field = copy.copy(field)
|
||||
value_field.name = f'{field.mojom_name}_$value'
|
||||
value_field.kind = field.kind.MakeUnnullableKind()
|
||||
|
||||
value_packed_field = PackedField(value_field,
|
||||
index,
|
||||
ordinal,
|
||||
original_field=field,
|
||||
sub_ordinal=1,
|
||||
linked_value_packed_field=None)
|
||||
has_value_packed_field = PackedField(
|
||||
has_value_field,
|
||||
index,
|
||||
ordinal,
|
||||
original_field=field,
|
||||
sub_ordinal=0,
|
||||
linked_value_packed_field=value_packed_field)
|
||||
src_fields.append(has_value_packed_field)
|
||||
src_fields.append(value_packed_field)
|
||||
else:
|
||||
src_fields.append(PackedField(field, index, ordinal))
|
||||
ordinal += 1
|
||||
src_fields.sort(key=lambda field: field.ordinal)
|
||||
src_fields.sort(key=lambda field: (field.ordinal, field.sub_ordinal))
|
||||
|
||||
# Set |min_version| for each field.
|
||||
next_min_version = 0
|
||||
|
@ -156,8 +252,9 @@ class PackedStruct(object):
|
|||
if (packed_field.min_version != 0
|
||||
and mojom.IsReferenceKind(packed_field.field.kind)
|
||||
and not packed_field.field.kind.is_nullable):
|
||||
raise Exception("Non-nullable fields are only allowed in version 0 of "
|
||||
"a struct. %s.%s is defined with [MinVersion=%d]." %
|
||||
raise Exception(
|
||||
"Non-nullable reference fields are only allowed in version 0 of a "
|
||||
"struct. %s.%s is defined with [MinVersion=%d]." %
|
||||
(self.struct.name, packed_field.field.name,
|
||||
packed_field.min_version))
|
||||
|
||||
|
@ -186,7 +283,7 @@ class PackedStruct(object):
|
|||
dst_fields.append(src_field)
|
||||
|
||||
|
||||
class ByteInfo(object):
|
||||
class ByteInfo:
|
||||
def __init__(self):
|
||||
self.is_padding = False
|
||||
self.packed_fields = []
|
||||
|
@ -214,10 +311,11 @@ def GetByteLayout(packed_struct):
|
|||
return byte_info
|
||||
|
||||
|
||||
class VersionInfo(object):
|
||||
def __init__(self, version, num_fields, num_bytes):
|
||||
class VersionInfo:
|
||||
def __init__(self, version, num_fields, num_packed_fields, num_bytes):
|
||||
self.version = version
|
||||
self.num_fields = num_fields
|
||||
self.num_packed_fields = num_packed_fields
|
||||
self.num_bytes = num_bytes
|
||||
|
||||
|
||||
|
@ -235,24 +333,35 @@ def GetVersionInfo(packed_struct):
|
|||
versions = []
|
||||
last_version = 0
|
||||
last_num_fields = 0
|
||||
last_num_packed_fields = 0
|
||||
last_payload_size = 0
|
||||
|
||||
for packed_field in packed_struct.packed_fields_in_ordinal_order:
|
||||
if packed_field.min_version != last_version:
|
||||
versions.append(
|
||||
VersionInfo(last_version, last_num_fields,
|
||||
VersionInfo(last_version, last_num_fields, last_num_packed_fields,
|
||||
last_payload_size + HEADER_SIZE))
|
||||
last_version = packed_field.min_version
|
||||
|
||||
last_num_fields += 1
|
||||
# Nullable numeric fields (e.g. `int32?`) expand to two packed fields, so to
|
||||
# avoid double-counting, only increment if the field is:
|
||||
# - not used for representing a nullable value kind field, or
|
||||
# - the primary field representing the nullable value kind field.
|
||||
last_num_fields += 1 if (
|
||||
not IsNullableValueKindPackedField(packed_field)
|
||||
or IsPrimaryNullableValueKindPackedField(packed_field)) else 0
|
||||
|
||||
last_num_packed_fields += 1
|
||||
|
||||
# The fields are iterated in ordinal order here. However, the size of a
|
||||
# version is determined by the last field of that version in pack order,
|
||||
# instead of ordinal order. Therefore, we need to calculate the max value.
|
||||
last_payload_size = max(
|
||||
GetPayloadSizeUpToField(packed_field), last_payload_size)
|
||||
last_payload_size = max(GetPayloadSizeUpToField(packed_field),
|
||||
last_payload_size)
|
||||
|
||||
assert len(versions) == 0 or last_num_fields != versions[-1].num_fields
|
||||
assert len(
|
||||
versions) == 0 or last_num_packed_fields != versions[-1].num_packed_fields
|
||||
versions.append(
|
||||
VersionInfo(last_version, last_num_fields,
|
||||
VersionInfo(last_version, last_num_fields, last_num_packed_fields,
|
||||
last_payload_size + HEADER_SIZE))
|
||||
return versions
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2013 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
@ -205,6 +205,34 @@ class PackTest(unittest.TestCase):
|
|||
self.assertEqual(4, versions[2].num_fields)
|
||||
self.assertEqual(32, versions[2].num_bytes)
|
||||
|
||||
def testGetVersionInfoPackedStruct(self):
|
||||
"""Tests that pack.GetVersionInfo() correctly sets version, num_fields,
|
||||
and num_packed_fields for a packed struct.
|
||||
"""
|
||||
struct = mojom.Struct('test')
|
||||
struct.AddField('field_0', mojom.BOOL, ordinal=0)
|
||||
struct.AddField('field_1',
|
||||
mojom.NULLABLE_BOOL,
|
||||
ordinal=1,
|
||||
attributes={'MinVersion': 1})
|
||||
struct.AddField('field_2',
|
||||
mojom.NULLABLE_BOOL,
|
||||
ordinal=2,
|
||||
attributes={'MinVersion': 2})
|
||||
ps = pack.PackedStruct(struct)
|
||||
versions = pack.GetVersionInfo(ps)
|
||||
|
||||
self.assertEqual(3, len(versions))
|
||||
self.assertEqual(0, versions[0].version)
|
||||
self.assertEqual(1, versions[1].version)
|
||||
self.assertEqual(2, versions[2].version)
|
||||
self.assertEqual(1, versions[0].num_fields)
|
||||
self.assertEqual(2, versions[1].num_fields)
|
||||
self.assertEqual(3, versions[2].num_fields)
|
||||
self.assertEqual(1, versions[0].num_packed_fields)
|
||||
self.assertEqual(3, versions[1].num_packed_fields)
|
||||
self.assertEqual(5, versions[2].num_packed_fields)
|
||||
|
||||
def testInterfaceAlignment(self):
|
||||
"""Tests that interfaces are aligned on 4-byte boundaries, although the size
|
||||
of an interface is 8 bytes.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2013 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2013 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Convert parse tree to AST.
|
||||
|
@ -12,17 +12,294 @@ already been parsed and converted to ASTs before.
|
|||
import itertools
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from collections import OrderedDict
|
||||
from mojom.generate import generator
|
||||
from mojom.generate import module as mojom
|
||||
from mojom.parse import ast
|
||||
|
||||
|
||||
def _IsStrOrUnicode(x):
|
||||
if sys.version_info[0] < 3:
|
||||
return isinstance(x, (unicode, str))
|
||||
return isinstance(x, str)
|
||||
is_running_backwards_compatibility_check_hack = False
|
||||
|
||||
### DO NOT ADD ENTRIES TO THIS LIST. ###
|
||||
_EXTENSIBLE_ENUMS_MISSING_DEFAULT = (
|
||||
'x:arc.keymaster.mojom.Algorithm',
|
||||
'x:arc.keymaster.mojom.Digest',
|
||||
'x:arc.keymaster.mojom.SignatureResult',
|
||||
'x:arc.mojom.AccessibilityActionType',
|
||||
'x:arc.mojom.AccessibilityBooleanProperty',
|
||||
'x:arc.mojom.AccessibilityEventIntListProperty',
|
||||
'x:arc.mojom.AccessibilityEventIntProperty',
|
||||
'x:arc.mojom.AccessibilityEventStringProperty',
|
||||
'x:arc.mojom.AccessibilityEventType',
|
||||
'x:arc.mojom.AccessibilityFilterType',
|
||||
'x:arc.mojom.AccessibilityIntListProperty',
|
||||
'x:arc.mojom.AccessibilityIntProperty',
|
||||
'x:arc.mojom.AccessibilityLiveRegionType',
|
||||
'x:arc.mojom.AccessibilityNotificationStateType',
|
||||
'x:arc.mojom.AccessibilityRangeType',
|
||||
'x:arc.mojom.AccessibilitySelectionMode',
|
||||
'x:arc.mojom.AccessibilityStringListProperty',
|
||||
'x:arc.mojom.AccessibilityStringProperty',
|
||||
'x:arc.mojom.AccessibilityWindowBooleanProperty',
|
||||
'x:arc.mojom.AccessibilityWindowIntListProperty',
|
||||
'x:arc.mojom.AccessibilityWindowIntProperty',
|
||||
'x:arc.mojom.AccessibilityWindowStringProperty',
|
||||
'x:arc.mojom.AccessibilityWindowType',
|
||||
'x:arc.mojom.AccountCheckStatus',
|
||||
'x:arc.mojom.AccountUpdateType',
|
||||
'x:arc.mojom.ActionType',
|
||||
'x:arc.mojom.Algorithm',
|
||||
'x:arc.mojom.AndroidIdSource',
|
||||
'x:arc.mojom.AnrSource',
|
||||
'x:arc.mojom.AnrType',
|
||||
'x:arc.mojom.AppDiscoveryRequestState',
|
||||
'x:arc.mojom.AppKillType',
|
||||
'x:arc.mojom.AppPermission',
|
||||
'x:arc.mojom.AppPermissionGroup',
|
||||
'x:arc.mojom.AppReinstallState',
|
||||
'x:arc.mojom.AppShortcutItemType',
|
||||
'x:arc.mojom.ArcAuthCodeStatus',
|
||||
'x:arc.mojom.ArcClipboardDragDropEvent',
|
||||
'x:arc.mojom.ArcCorePriAbiMigEvent',
|
||||
'x:arc.mojom.ArcDnsQuery',
|
||||
'x:arc.mojom.ArcImageCopyPasteCompatAction',
|
||||
'x:arc.mojom.ArcNetworkError',
|
||||
'x:arc.mojom.ArcNetworkEvent',
|
||||
'x:arc.mojom.ArcNotificationEvent',
|
||||
'x:arc.mojom.ArcNotificationExpandState',
|
||||
'x:arc.mojom.ArcNotificationPriority',
|
||||
'x:arc.mojom.ArcNotificationRemoteInputState',
|
||||
'x:arc.mojom.ArcNotificationShownContents',
|
||||
'x:arc.mojom.ArcNotificationStyle',
|
||||
'x:arc.mojom.ArcNotificationType',
|
||||
'x:arc.mojom.ArcPipEvent',
|
||||
'x:arc.mojom.ArcResizeLockState',
|
||||
'x:arc.mojom.ArcSignInSuccess',
|
||||
'x:arc.mojom.ArcTimerResult',
|
||||
'x:arc.mojom.AudioSwitch',
|
||||
'x:arc.mojom.BluetoothAclState',
|
||||
'x:arc.mojom.BluetoothAdapterState',
|
||||
'x:arc.mojom.BluetoothAdvertisingDataType',
|
||||
'x:arc.mojom.BluetoothBondState',
|
||||
'x:arc.mojom.BluetoothDeviceType',
|
||||
'x:arc.mojom.BluetoothDiscoveryState',
|
||||
'x:arc.mojom.BluetoothGattDBAttributeType',
|
||||
'x:arc.mojom.BluetoothGattStatus',
|
||||
'x:arc.mojom.BluetoothPropertyType',
|
||||
'x:arc.mojom.BluetoothScanMode',
|
||||
'x:arc.mojom.BluetoothSdpAttributeType',
|
||||
'x:arc.mojom.BluetoothSocketType',
|
||||
'x:arc.mojom.BluetoothStatus',
|
||||
'x:arc.mojom.BootType',
|
||||
'x:arc.mojom.CaptionTextShadowType',
|
||||
'x:arc.mojom.ChangeType',
|
||||
'x:arc.mojom.ChromeAccountType',
|
||||
'x:arc.mojom.ChromeApp',
|
||||
'x:arc.mojom.ChromePage',
|
||||
'x:arc.mojom.ClockId',
|
||||
'x:arc.mojom.CloudProvisionFlowError',
|
||||
'x:arc.mojom.CommandResultType',
|
||||
'x:arc.mojom.CompanionLibApiId',
|
||||
'x:arc.mojom.ConnectionStateType',
|
||||
'x:arc.mojom.ContentChangeType',
|
||||
'x:arc.mojom.CpuRestrictionState',
|
||||
'x:arc.mojom.CursorCoordinateSpace',
|
||||
'x:arc.mojom.DataRestoreStatus',
|
||||
'x:arc.mojom.DecoderStatus',
|
||||
'x:arc.mojom.DeviceType',
|
||||
'x:arc.mojom.Digest',
|
||||
'x:arc.mojom.DisplayWakeLockType',
|
||||
'x:arc.mojom.EapMethod',
|
||||
'x:arc.mojom.EapPhase2Method',
|
||||
'x:arc.mojom.FileSelectorEventType',
|
||||
'x:arc.mojom.GMSCheckInError',
|
||||
'x:arc.mojom.GMSSignInError',
|
||||
'x:arc.mojom.GeneralSignInError',
|
||||
'x:arc.mojom.GetNetworksRequestType',
|
||||
'x:arc.mojom.HalPixelFormat',
|
||||
'x:arc.mojom.IPAddressType',
|
||||
'x:arc.mojom.InstallErrorReason',
|
||||
'x:arc.mojom.KeyFormat',
|
||||
'x:arc.mojom.KeyManagement',
|
||||
'x:arc.mojom.KeyPurpose',
|
||||
'x:arc.mojom.KeymasterError',
|
||||
'x:arc.mojom.MainAccountHashMigrationStatus',
|
||||
'x:arc.mojom.MainAccountResolutionStatus',
|
||||
'x:arc.mojom.ManagementChangeStatus',
|
||||
'x:arc.mojom.ManagementState',
|
||||
'x:arc.mojom.MessageCenterVisibility',
|
||||
'x:arc.mojom.MetricsType',
|
||||
'x:arc.mojom.MountEvent',
|
||||
'x:arc.mojom.NativeBridgeType',
|
||||
'x:arc.mojom.NetworkResult',
|
||||
'x:arc.mojom.NetworkType',
|
||||
'x:arc.mojom.OemCryptoAlgorithm',
|
||||
'x:arc.mojom.OemCryptoCipherMode',
|
||||
'x:arc.mojom.OemCryptoHdcpCapability',
|
||||
'x:arc.mojom.OemCryptoLicenseType',
|
||||
'x:arc.mojom.OemCryptoPrivateKey',
|
||||
'x:arc.mojom.OemCryptoProvisioningMethod',
|
||||
'x:arc.mojom.OemCryptoResult',
|
||||
'x:arc.mojom.OemCryptoRsaPaddingScheme',
|
||||
'x:arc.mojom.OemCryptoUsageEntryStatus',
|
||||
'x:arc.mojom.Padding',
|
||||
'x:arc.mojom.PaiFlowState',
|
||||
'x:arc.mojom.PatternType',
|
||||
'x:arc.mojom.PressureLevel',
|
||||
'x:arc.mojom.PrintColorMode',
|
||||
'x:arc.mojom.PrintContentType',
|
||||
'x:arc.mojom.PrintDuplexMode',
|
||||
'x:arc.mojom.PrinterStatus',
|
||||
'x:arc.mojom.ProcessState',
|
||||
'x:arc.mojom.PurchaseState',
|
||||
'x:arc.mojom.ReauthReason',
|
||||
'x:arc.mojom.ScaleFactor',
|
||||
'x:arc.mojom.SecurityType',
|
||||
'x:arc.mojom.SegmentStyle',
|
||||
'x:arc.mojom.SelectFilesActionType',
|
||||
'x:arc.mojom.SetNativeChromeVoxResponse',
|
||||
'x:arc.mojom.ShowPackageInfoPage',
|
||||
'x:arc.mojom.SpanType',
|
||||
'x:arc.mojom.SupportedLinkChangeSource',
|
||||
'x:arc.mojom.TetheringClientState',
|
||||
'x:arc.mojom.TextInputType',
|
||||
'x:arc.mojom.TtsEventType',
|
||||
'x:arc.mojom.VideoCodecProfile',
|
||||
'x:arc.mojom.VideoDecodeAccelerator.Result',
|
||||
'x:arc.mojom.VideoEncodeAccelerator.Error',
|
||||
'x:arc.mojom.VideoFrameStorageType',
|
||||
'x:arc.mojom.VideoPixelFormat',
|
||||
'x:arc.mojom.WakefulnessMode',
|
||||
'x:arc.mojom.WebApkInstallResult',
|
||||
'x:ash.ime.mojom.InputFieldType',
|
||||
'x:ash.ime.mojom.PersonalizationMode',
|
||||
'x:ash.language.mojom.FeatureId',
|
||||
'x:blink.mojom.ScrollRestorationType',
|
||||
'x:chromeos.cdm.mojom.CdmKeyStatus',
|
||||
'x:chromeos.cdm.mojom.CdmMessageType',
|
||||
'x:chromeos.cdm.mojom.CdmSessionType',
|
||||
'x:chromeos.cdm.mojom.DecryptStatus',
|
||||
'x:chromeos.cdm.mojom.EmeInitDataType',
|
||||
'x:chromeos.cdm.mojom.EncryptionScheme',
|
||||
'x:chromeos.cdm.mojom.HdcpVersion',
|
||||
'x:chromeos.cdm.mojom.OutputProtection.LinkType',
|
||||
'x:chromeos.cdm.mojom.OutputProtection.ProtectionType',
|
||||
'x:chromeos.cdm.mojom.PromiseException',
|
||||
'x:chromeos.cfm.mojom.EnqueuePriority',
|
||||
'x:chromeos.cfm.mojom.LoggerErrorCode',
|
||||
'x:chromeos.cfm.mojom.LoggerState',
|
||||
'x:chromeos.cros_healthd.mojom.CryptoAlgorithm',
|
||||
'x:chromeos.cros_healthd.mojom.EncryptionState',
|
||||
'x:chromeos.machine_learning.mojom.AnnotationUsecase',
|
||||
'x:chromeos.machine_learning.mojom.BuiltinModelId',
|
||||
'x:chromeos.machine_learning.mojom.CreateGraphExecutorResult',
|
||||
'x:chromeos.machine_learning.mojom.DocumentScannerResultStatus',
|
||||
'x:chromeos.machine_learning.mojom.EndpointReason',
|
||||
'x:chromeos.machine_learning.mojom.EndpointerType',
|
||||
'x:chromeos.machine_learning.mojom.ExecuteResult',
|
||||
'x:chromeos.machine_learning.mojom.GrammarCheckerResult.Status',
|
||||
'x:chromeos.machine_learning.mojom.HandwritingRecognizerResult.Status',
|
||||
'x:chromeos.machine_learning.mojom.LoadHandwritingModelResult',
|
||||
'x:chromeos.machine_learning.mojom.LoadModelResult',
|
||||
'x:chromeos.machine_learning.mojom.Rotation',
|
||||
'x:chromeos.network_config.mojom.ConnectionStateType',
|
||||
'x:chromeos.network_config.mojom.DeviceStateType',
|
||||
'x:chromeos.network_config.mojom.IPConfigType',
|
||||
'x:chromeos.network_config.mojom.NetworkType',
|
||||
'x:chromeos.network_config.mojom.OncSource',
|
||||
'x:chromeos.network_config.mojom.PolicySource',
|
||||
'x:chromeos.network_config.mojom.PortalState',
|
||||
'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdEvent',
|
||||
'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestHttpMethod',
|
||||
'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestStatus',
|
||||
'x:cros.mojom.CameraClientType',
|
||||
'x:cros.mojom.CameraMetadataSectionStart',
|
||||
'x:cros.mojom.CameraMetadataTag',
|
||||
'x:cros.mojom.HalPixelFormat',
|
||||
'x:crosapi.mojom.AllowedPaths',
|
||||
'x:crosapi.mojom.BrowserAppInstanceType',
|
||||
'x:crosapi.mojom.CreationResult',
|
||||
'x:crosapi.mojom.DeviceAccessResultCode',
|
||||
'x:crosapi.mojom.DeviceMode',
|
||||
'x:crosapi.mojom.DlpRestrictionLevel',
|
||||
'x:crosapi.mojom.ExoImeSupport',
|
||||
'x:crosapi.mojom.FullscreenVisibility',
|
||||
'x:crosapi.mojom.GoogleServiceAuthError.State',
|
||||
'x:crosapi.mojom.IsInstallableResult',
|
||||
'x:crosapi.mojom.KeyTag',
|
||||
'x:crosapi.mojom.KeystoreSigningAlgorithmName',
|
||||
'x:crosapi.mojom.KeystoreType',
|
||||
'x:crosapi.mojom.LacrosFeedbackSource',
|
||||
'x:crosapi.mojom.MemoryPressureLevel',
|
||||
'x:crosapi.mojom.MetricsReportingManaged',
|
||||
'x:crosapi.mojom.NotificationType',
|
||||
'x:crosapi.mojom.OndeviceHandwritingSupport',
|
||||
'x:crosapi.mojom.OpenResult',
|
||||
'x:crosapi.mojom.PolicyDomain',
|
||||
'x:crosapi.mojom.RegistrationCodeType',
|
||||
'x:crosapi.mojom.ScaleFactor',
|
||||
'x:crosapi.mojom.SearchResult.OptionalBool',
|
||||
'x:crosapi.mojom.SelectFileDialogType',
|
||||
'x:crosapi.mojom.SelectFileResult',
|
||||
'x:crosapi.mojom.SharesheetResult',
|
||||
'x:crosapi.mojom.TouchEventType',
|
||||
'x:crosapi.mojom.VideoRotation',
|
||||
'x:crosapi.mojom.WallpaperLayout',
|
||||
'x:crosapi.mojom.WebAppInstallResultCode',
|
||||
'x:crosapi.mojom.WebAppUninstallResultCode',
|
||||
'x:device.mojom.HidBusType',
|
||||
'x:device.mojom.WakeLockReason',
|
||||
'x:device.mojom.WakeLockType',
|
||||
'x:drivefs.mojom.DialogReason.Type',
|
||||
'x:drivefs.mojom.DriveError.Type',
|
||||
'x:drivefs.mojom.DriveFsDelegate.ExtensionConnectionStatus',
|
||||
'x:drivefs.mojom.FileMetadata.CanPinStatus',
|
||||
'x:drivefs.mojom.FileMetadata.Type',
|
||||
'x:drivefs.mojom.ItemEventReason',
|
||||
'x:drivefs.mojom.MirrorPathStatus',
|
||||
'x:drivefs.mojom.MirrorSyncStatus',
|
||||
'x:drivefs.mojom.QueryParameters.SortField',
|
||||
'x:fuzz.mojom.FuzzEnum',
|
||||
'x:media.mojom.FillLightMode',
|
||||
'x:media.mojom.MeteringMode',
|
||||
'x:media.mojom.PowerLineFrequency',
|
||||
'x:media.mojom.RedEyeReduction',
|
||||
'x:media.mojom.ResolutionChangePolicy',
|
||||
'x:media.mojom.VideoCaptureApi',
|
||||
'x:media.mojom.VideoCaptureBufferType',
|
||||
'x:media.mojom.VideoCaptureError',
|
||||
'x:media.mojom.VideoCaptureFrameDropReason',
|
||||
'x:media.mojom.VideoCapturePixelFormat',
|
||||
'x:media.mojom.VideoCaptureTransportType',
|
||||
'x:media.mojom.VideoFacingMode',
|
||||
'x:media_session.mojom.AudioFocusType',
|
||||
'x:media_session.mojom.CameraState',
|
||||
'x:media_session.mojom.EnforcementMode',
|
||||
'x:media_session.mojom.MediaAudioVideoState',
|
||||
'x:media_session.mojom.MediaImageBitmapColorType',
|
||||
'x:media_session.mojom.MediaPictureInPictureState',
|
||||
'x:media_session.mojom.MediaPlaybackState',
|
||||
'x:media_session.mojom.MediaSession.SuspendType',
|
||||
'x:media_session.mojom.MediaSessionAction',
|
||||
'x:media_session.mojom.MediaSessionImageType',
|
||||
'x:media_session.mojom.MediaSessionInfo.SessionState',
|
||||
'x:media_session.mojom.MicrophoneState',
|
||||
'x:ml.model_loader.mojom.ComputeResult',
|
||||
'x:ml.model_loader.mojom.CreateModelLoaderResult',
|
||||
'x:ml.model_loader.mojom.LoadModelResult',
|
||||
'x:mojo.test.AnExtensibleEnum',
|
||||
'x:mojo.test.EnumB',
|
||||
'x:mojo.test.ExtensibleEmptyEnum',
|
||||
'x:mojo.test.enum_default_unittest.mojom.ExtensibleEnumWithoutDefault',
|
||||
'x:network.mojom.WebSandboxFlags',
|
||||
'x:payments.mojom.BillingResponseCode',
|
||||
'x:payments.mojom.CreateDigitalGoodsResponseCode',
|
||||
'x:payments.mojom.ItemType',
|
||||
'x:printing.mojom.PrinterType',
|
||||
'x:ui.mojom.KeyboardCode',
|
||||
)
|
||||
### DO NOT ADD ENTRIES TO THIS LIST. ###
|
||||
|
||||
|
||||
def _DuplicateName(values):
|
||||
|
@ -98,12 +375,6 @@ def _MapKind(kind):
|
|||
}
|
||||
if kind.endswith('?'):
|
||||
base_kind = _MapKind(kind[0:-1])
|
||||
# NOTE: This doesn't rule out enum types. Those will be detected later, when
|
||||
# cross-reference is established.
|
||||
reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso', 'rmt', 'rcv',
|
||||
'rma', 'rca')
|
||||
if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds:
|
||||
raise Exception('A type (spec "%s") cannot be made nullable' % base_kind)
|
||||
return '?' + base_kind
|
||||
if kind.endswith('}'):
|
||||
lbracket = kind.rfind('{')
|
||||
|
@ -113,8 +384,6 @@ def _MapKind(kind):
|
|||
lbracket = kind.rfind('[')
|
||||
typename = kind[0:lbracket]
|
||||
return 'a' + kind[lbracket + 1:-1] + ':' + _MapKind(typename)
|
||||
if kind.endswith('&'):
|
||||
return 'r:' + _MapKind(kind[0:-1])
|
||||
if kind.startswith('asso<'):
|
||||
assert kind.endswith('>')
|
||||
return 'asso:' + _MapKind(kind[5:-1])
|
||||
|
@ -135,13 +404,45 @@ def _MapKind(kind):
|
|||
return 'x:' + kind
|
||||
|
||||
|
||||
def _AttributeListToDict(attribute_list):
|
||||
def _MapAttributeValue(module, kind, value):
|
||||
# True/False/None
|
||||
if value is None:
|
||||
return value
|
||||
if not isinstance(value, str):
|
||||
return value
|
||||
# Is the attribute value the name of a feature?
|
||||
try:
|
||||
# Features cannot be nested in other types, so lookup in the global scope.
|
||||
trial = _LookupKind(module.kinds, 'x:' + value,
|
||||
_GetScopeForKind(module, kind))
|
||||
if isinstance(trial, mojom.Feature):
|
||||
return trial
|
||||
except ValueError:
|
||||
pass
|
||||
# Is the attribute value a constant or enum value?
|
||||
try:
|
||||
trial = _LookupValue(module, None, None, ('IDENTIFIER', value))
|
||||
if isinstance(trial, mojom.ConstantValue):
|
||||
return trial.constant
|
||||
if isinstance(trial, mojom.EnumValue):
|
||||
return trial
|
||||
except ValueError:
|
||||
pass
|
||||
# If not a referenceable mojo type - return as a string.
|
||||
return value
|
||||
|
||||
|
||||
def _AttributeListToDict(module, kind, attribute_list):
|
||||
if attribute_list is None:
|
||||
return None
|
||||
assert isinstance(attribute_list, ast.AttributeList)
|
||||
# TODO(vtl): Check for duplicate keys here.
|
||||
return dict(
|
||||
[(attribute.key, attribute.value) for attribute in attribute_list])
|
||||
attributes = dict()
|
||||
for attribute in attribute_list:
|
||||
if attribute.key in attributes:
|
||||
raise Exception("Duplicate key (%s) in attribute list" % attribute.key)
|
||||
attributes[attribute.key] = _MapAttributeValue(module, kind,
|
||||
attribute.value)
|
||||
return attributes
|
||||
|
||||
|
||||
builtin_values = frozenset([
|
||||
|
@ -257,7 +558,8 @@ def _Kind(kinds, spec, scope):
|
|||
return kind
|
||||
|
||||
if spec.startswith('?'):
|
||||
kind = _Kind(kinds, spec[1:], scope).MakeNullableKind()
|
||||
kind = _Kind(kinds, spec[1:], scope)
|
||||
kind = kind.MakeNullableKind()
|
||||
elif spec.startswith('a:'):
|
||||
kind = mojom.Array(_Kind(kinds, spec[2:], scope))
|
||||
elif spec.startswith('asso:'):
|
||||
|
@ -303,7 +605,8 @@ def _Kind(kinds, spec, scope):
|
|||
|
||||
def _Import(module, import_module):
|
||||
# Copy the struct kinds from our imports into the current module.
|
||||
importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface)
|
||||
importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface,
|
||||
mojom.Feature)
|
||||
for kind in import_module.kinds.values():
|
||||
if (isinstance(kind, importable_kinds)
|
||||
and kind.module.path == import_module.path):
|
||||
|
@ -316,6 +619,32 @@ def _Import(module, import_module):
|
|||
return import_module
|
||||
|
||||
|
||||
def _Feature(module, parsed_feature):
|
||||
"""
|
||||
Args:
|
||||
module: {mojom.Module} Module currently being constructed.
|
||||
parsed_feature: {ast.Feature} Parsed feature.
|
||||
|
||||
Returns:
|
||||
{mojom.Feature} AST feature.
|
||||
"""
|
||||
feature = mojom.Feature(module=module)
|
||||
feature.mojom_name = parsed_feature.mojom_name
|
||||
feature.spec = 'x:' + module.GetNamespacePrefix() + feature.mojom_name
|
||||
module.kinds[feature.spec] = feature
|
||||
feature.constants = []
|
||||
_ProcessElements(
|
||||
parsed_feature.mojom_name, parsed_feature.body, {
|
||||
ast.Const:
|
||||
lambda const: feature.constants.append(
|
||||
_Constant(module, const, feature)),
|
||||
})
|
||||
|
||||
feature.attributes = _AttributeListToDict(module, feature,
|
||||
parsed_feature.attribute_list)
|
||||
return feature
|
||||
|
||||
|
||||
def _Struct(module, parsed_struct):
|
||||
"""
|
||||
Args:
|
||||
|
@ -345,7 +674,8 @@ def _Struct(module, parsed_struct):
|
|||
struct.fields_data.append,
|
||||
})
|
||||
|
||||
struct.attributes = _AttributeListToDict(parsed_struct.attribute_list)
|
||||
struct.attributes = _AttributeListToDict(module, struct,
|
||||
parsed_struct.attribute_list)
|
||||
|
||||
# Enforce that a [Native] attribute is set to make native-only struct
|
||||
# declarations more explicit.
|
||||
|
@ -377,7 +707,8 @@ def _Union(module, parsed_union):
|
|||
union.fields_data = []
|
||||
_ProcessElements(parsed_union.mojom_name, parsed_union.body,
|
||||
{ast.UnionField: union.fields_data.append})
|
||||
union.attributes = _AttributeListToDict(parsed_union.attribute_list)
|
||||
union.attributes = _AttributeListToDict(module, union,
|
||||
parsed_union.attribute_list)
|
||||
return union
|
||||
|
||||
|
||||
|
@ -398,7 +729,8 @@ def _StructField(module, parsed_field, struct):
|
|||
field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
|
||||
field.default = _LookupValue(module, struct, field.kind,
|
||||
parsed_field.default_value)
|
||||
field.attributes = _AttributeListToDict(parsed_field.attribute_list)
|
||||
field.attributes = _AttributeListToDict(module, field,
|
||||
parsed_field.attribute_list)
|
||||
return field
|
||||
|
||||
|
||||
|
@ -414,11 +746,22 @@ def _UnionField(module, parsed_field, union):
|
|||
"""
|
||||
field = mojom.UnionField()
|
||||
field.mojom_name = parsed_field.mojom_name
|
||||
# Disallow unions from being self-recursive.
|
||||
parsed_typename = parsed_field.typename
|
||||
if parsed_typename.endswith('?'):
|
||||
parsed_typename = parsed_typename[:-1]
|
||||
assert parsed_typename != union.mojom_name
|
||||
field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename),
|
||||
(module.mojom_namespace, union.mojom_name))
|
||||
field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
|
||||
field.default = None
|
||||
field.attributes = _AttributeListToDict(parsed_field.attribute_list)
|
||||
field.attributes = _AttributeListToDict(module, field,
|
||||
parsed_field.attribute_list)
|
||||
if field.is_default and not mojom.IsNullableKind(field.kind) and \
|
||||
not mojom.IsIntegralKind(field.kind):
|
||||
raise Exception(
|
||||
'[Default] field for union %s must be nullable or integral type.' %
|
||||
union.mojom_name)
|
||||
return field
|
||||
|
||||
|
||||
|
@ -439,7 +782,8 @@ def _Parameter(module, parsed_param, interface):
|
|||
parameter.ordinal = (parsed_param.ordinal.value
|
||||
if parsed_param.ordinal else None)
|
||||
parameter.default = None # TODO(tibell): We never have these. Remove field?
|
||||
parameter.attributes = _AttributeListToDict(parsed_param.attribute_list)
|
||||
parameter.attributes = _AttributeListToDict(module, parameter,
|
||||
parsed_param.attribute_list)
|
||||
return parameter
|
||||
|
||||
|
||||
|
@ -464,7 +808,8 @@ def _Method(module, parsed_method, interface):
|
|||
method.response_parameters = list(
|
||||
map(lambda parameter: _Parameter(module, parameter, interface),
|
||||
parsed_method.response_parameter_list))
|
||||
method.attributes = _AttributeListToDict(parsed_method.attribute_list)
|
||||
method.attributes = _AttributeListToDict(module, method,
|
||||
parsed_method.attribute_list)
|
||||
|
||||
# Enforce that only methods with response can have a [Sync] attribute.
|
||||
if method.sync and method.response_parameters is None:
|
||||
|
@ -492,7 +837,8 @@ def _Interface(module, parsed_iface):
|
|||
interface.mojom_name = parsed_iface.mojom_name
|
||||
interface.spec = 'x:' + module.GetNamespacePrefix() + interface.mojom_name
|
||||
module.kinds[interface.spec] = interface
|
||||
interface.attributes = _AttributeListToDict(parsed_iface.attribute_list)
|
||||
interface.attributes = _AttributeListToDict(module, interface,
|
||||
parsed_iface.attribute_list)
|
||||
interface.enums = []
|
||||
interface.constants = []
|
||||
interface.methods_data = []
|
||||
|
@ -522,7 +868,8 @@ def _EnumField(module, enum, parsed_field):
|
|||
field = mojom.EnumField()
|
||||
field.mojom_name = parsed_field.mojom_name
|
||||
field.value = _LookupValue(module, enum, None, parsed_field.value)
|
||||
field.attributes = _AttributeListToDict(parsed_field.attribute_list)
|
||||
field.attributes = _AttributeListToDict(module, field,
|
||||
parsed_field.attribute_list)
|
||||
value = mojom.EnumValue(module, enum, field)
|
||||
module.values[value.GetSpec()] = value
|
||||
return field
|
||||
|
@ -544,7 +891,7 @@ def _ResolveNumericEnumValues(enum):
|
|||
prev_value += 1
|
||||
|
||||
# Integral value (e.g: BEGIN = -0x1).
|
||||
elif _IsStrOrUnicode(field.value):
|
||||
elif isinstance(field.value, str):
|
||||
prev_value = int(field.value, 0)
|
||||
|
||||
# Reference to a previous enum value (e.g: INIT = BEGIN).
|
||||
|
@ -560,7 +907,10 @@ def _ResolveNumericEnumValues(enum):
|
|||
else:
|
||||
raise Exception('Unresolved enum value for %s' % field.value.GetSpec())
|
||||
|
||||
#resolved_enum_values[field.mojom_name] = prev_value
|
||||
if prev_value in (-128, -127):
|
||||
raise Exception(f'{field.mojom_name} in {enum.spec} has the value '
|
||||
f'{prev_value}, which is reserved for WTF::HashTrait\'s '
|
||||
'default enum specialization and may not be used.')
|
||||
field.numeric_value = prev_value
|
||||
if min_value is None or prev_value < min_value:
|
||||
min_value = prev_value
|
||||
|
@ -588,7 +938,8 @@ def _Enum(module, parsed_enum, parent_kind):
|
|||
mojom_name = parent_kind.mojom_name + '.' + mojom_name
|
||||
enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name)
|
||||
enum.parent_kind = parent_kind
|
||||
enum.attributes = _AttributeListToDict(parsed_enum.attribute_list)
|
||||
enum.attributes = _AttributeListToDict(module, enum,
|
||||
parsed_enum.attribute_list)
|
||||
|
||||
if not enum.native_only:
|
||||
enum.fields = list(
|
||||
|
@ -600,11 +951,18 @@ def _Enum(module, parsed_enum, parent_kind):
|
|||
for field in enum.fields:
|
||||
if field.default:
|
||||
if not enum.extensible:
|
||||
raise Exception('Non-extensible enums may not specify a default')
|
||||
if enum.default_field is not None:
|
||||
raise Exception(
|
||||
'Only one enumerator value may be specified as the default')
|
||||
f'Non-extensible enum {enum.spec} may not specify a default')
|
||||
if enum.default_field is not None:
|
||||
raise Exception(f'Multiple [Default] enumerators in enum {enum.spec}')
|
||||
enum.default_field = field
|
||||
# While running the backwards compatibility check, ignore errors because the
|
||||
# old version of the enum might not specify [Default].
|
||||
if (enum.extensible and enum.default_field is None
|
||||
and enum.spec not in _EXTENSIBLE_ENUMS_MISSING_DEFAULT
|
||||
and not is_running_backwards_compatibility_check_hack):
|
||||
raise Exception(
|
||||
f'Extensible enum {enum.spec} must specify a [Default] enumerator')
|
||||
|
||||
module.kinds[enum.spec] = enum
|
||||
|
||||
|
@ -696,6 +1054,11 @@ def _CollectReferencedKinds(module, all_defined_kinds):
|
|||
for referenced_kind in extract_referenced_user_kinds(param.kind):
|
||||
sanitized_kind = sanitize_kind(referenced_kind)
|
||||
referenced_user_kinds[sanitized_kind.spec] = sanitized_kind
|
||||
# Consts can reference imported enums.
|
||||
for const in module.constants:
|
||||
if not const.kind in mojom.PRIMITIVES:
|
||||
sanitized_kind = sanitize_kind(const.kind)
|
||||
referenced_user_kinds[sanitized_kind.spec] = sanitized_kind
|
||||
|
||||
return referenced_user_kinds
|
||||
|
||||
|
@ -741,6 +1104,16 @@ def _AssertTypeIsStable(kind):
|
|||
assertDependencyIsStable(response_param.kind)
|
||||
|
||||
|
||||
def _AssertStructIsValid(kind):
|
||||
expected_ordinals = set(range(0, len(kind.fields)))
|
||||
ordinals = set(map(lambda field: field.ordinal, kind.fields))
|
||||
if ordinals != expected_ordinals:
|
||||
raise Exception(
|
||||
'Structs must use contiguous ordinals starting from 0. ' +
|
||||
'{} is missing the following ordinals: {}.'.format(
|
||||
kind.mojom_name, ', '.join(map(str, expected_ordinals - ordinals))))
|
||||
|
||||
|
||||
def _Module(tree, path, imports):
|
||||
"""
|
||||
Args:
|
||||
|
@ -778,6 +1151,8 @@ def _Module(tree, path, imports):
|
|||
module.structs = []
|
||||
module.unions = []
|
||||
module.interfaces = []
|
||||
module.features = []
|
||||
|
||||
_ProcessElements(
|
||||
filename, tree.definition_list, {
|
||||
ast.Const:
|
||||
|
@ -791,6 +1166,8 @@ def _Module(tree, path, imports):
|
|||
ast.Interface:
|
||||
lambda interface: module.interfaces.append(
|
||||
_Interface(module, interface)),
|
||||
ast.Feature:
|
||||
lambda feature: module.features.append(_Feature(module, feature)),
|
||||
})
|
||||
|
||||
# Second pass expands fields and methods. This allows fields and parameters
|
||||
|
@ -806,12 +1183,24 @@ def _Module(tree, path, imports):
|
|||
for enum in struct.enums:
|
||||
all_defined_kinds[enum.spec] = enum
|
||||
|
||||
for feature in module.features:
|
||||
all_defined_kinds[feature.spec] = feature
|
||||
|
||||
for union in module.unions:
|
||||
union.fields = list(
|
||||
map(lambda field: _UnionField(module, field, union), union.fields_data))
|
||||
_AssignDefaultOrdinals(union.fields)
|
||||
for field in union.fields:
|
||||
if field.is_default:
|
||||
if union.default_field is not None:
|
||||
raise Exception('Multiple [Default] fields in union %s.' %
|
||||
union.mojom_name)
|
||||
union.default_field = field
|
||||
del union.fields_data
|
||||
all_defined_kinds[union.spec] = union
|
||||
if union.extensible and union.default_field is None:
|
||||
raise Exception('Extensible union %s must specify a [Default] field' %
|
||||
union.mojom_name)
|
||||
|
||||
for interface in module.interfaces:
|
||||
interface.methods = list(
|
||||
|
@ -829,8 +1218,8 @@ def _Module(tree, path, imports):
|
|||
all_defined_kinds.values())
|
||||
imported_kind_specs = set(all_referenced_kinds.keys()).difference(
|
||||
set(all_defined_kinds.keys()))
|
||||
module.imported_kinds = dict(
|
||||
(spec, all_referenced_kinds[spec]) for spec in imported_kind_specs)
|
||||
module.imported_kinds = OrderedDict((spec, all_referenced_kinds[spec])
|
||||
for spec in sorted(imported_kind_specs))
|
||||
|
||||
generator.AddComputedData(module)
|
||||
for iface in module.interfaces:
|
||||
|
@ -847,6 +1236,9 @@ def _Module(tree, path, imports):
|
|||
if kind.stable:
|
||||
_AssertTypeIsStable(kind)
|
||||
|
||||
for kind in module.structs:
|
||||
_AssertStructIsValid(kind)
|
||||
|
||||
return module
|
||||
|
||||
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2014 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import imp
|
||||
import os.path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from mojom.generate import module as mojom
|
||||
from mojom.generate import translate
|
||||
from mojom.parse import ast
|
||||
|
||||
|
||||
class TranslateTest(unittest.TestCase):
|
||||
"""Tests |parser.Parse()|."""
|
||||
|
||||
|
@ -69,5 +65,77 @@ class TranslateTest(unittest.TestCase):
|
|||
# pylint: disable=W0212
|
||||
self.assertEquals(
|
||||
translate._MapKind("asso<SomeInterface>?"), "?asso:x:SomeInterface")
|
||||
self.assertEquals(
|
||||
translate._MapKind("asso<SomeInterface&>?"), "?asso:r:x:SomeInterface")
|
||||
self.assertEquals(translate._MapKind("rca<SomeInterface>?"),
|
||||
"?rca:x:SomeInterface")
|
||||
|
||||
def testSelfRecursiveUnions(self):
|
||||
"""Verifies _UnionField() raises when a union is self-recursive."""
|
||||
tree = ast.Mojom(None, ast.ImportList(), [
|
||||
ast.Union("SomeUnion", None,
|
||||
ast.UnionBody([ast.UnionField("a", None, None, "SomeUnion")]))
|
||||
])
|
||||
with self.assertRaises(Exception):
|
||||
translate.OrderedModule(tree, "mojom_tree", [])
|
||||
|
||||
tree = ast.Mojom(None, ast.ImportList(), [
|
||||
ast.Union(
|
||||
"SomeUnion", None,
|
||||
ast.UnionBody([ast.UnionField("a", None, None, "SomeUnion?")]))
|
||||
])
|
||||
with self.assertRaises(Exception):
|
||||
translate.OrderedModule(tree, "mojom_tree", [])
|
||||
|
||||
def testDuplicateAttributesException(self):
|
||||
tree = ast.Mojom(None, ast.ImportList(), [
|
||||
ast.Union(
|
||||
"FakeUnion",
|
||||
ast.AttributeList([
|
||||
ast.Attribute("key1", "value"),
|
||||
ast.Attribute("key1", "value")
|
||||
]),
|
||||
ast.UnionBody([
|
||||
ast.UnionField("a", None, None, "int32"),
|
||||
ast.UnionField("b", None, None, "string")
|
||||
]))
|
||||
])
|
||||
with self.assertRaises(Exception):
|
||||
translate.OrderedModule(tree, "mojom_tree", [])
|
||||
|
||||
def testEnumWithReservedValues(self):
|
||||
"""Verifies that assigning reserved values to enumerators fails."""
|
||||
# -128 is reserved for the empty representation in WTF::HashTraits.
|
||||
tree = ast.Mojom(None, ast.ImportList(), [
|
||||
ast.Enum(
|
||||
"MyEnum", None,
|
||||
ast.EnumValueList([
|
||||
ast.EnumValue('kReserved', None, '-128'),
|
||||
]))
|
||||
])
|
||||
with self.assertRaises(Exception) as context:
|
||||
translate.OrderedModule(tree, "mojom_tree", [])
|
||||
self.assertIn("reserved for WTF::HashTrait", str(context.exception))
|
||||
|
||||
# -127 is reserved for the deleted representation in WTF::HashTraits.
|
||||
tree = ast.Mojom(None, ast.ImportList(), [
|
||||
ast.Enum(
|
||||
"MyEnum", None,
|
||||
ast.EnumValueList([
|
||||
ast.EnumValue('kReserved', None, '-127'),
|
||||
]))
|
||||
])
|
||||
with self.assertRaises(Exception) as context:
|
||||
translate.OrderedModule(tree, "mojom_tree", [])
|
||||
self.assertIn("reserved for WTF::HashTrait", str(context.exception))
|
||||
|
||||
# Implicitly assigning a reserved value should also fail.
|
||||
tree = ast.Mojom(None, ast.ImportList(), [
|
||||
ast.Enum(
|
||||
"MyEnum", None,
|
||||
ast.EnumValueList([
|
||||
ast.EnumValue('kNotReserved', None, '-129'),
|
||||
ast.EnumValue('kImplicitlyReserved', None, None),
|
||||
]))
|
||||
])
|
||||
with self.assertRaises(Exception) as context:
|
||||
translate.OrderedModule(tree, "mojom_tree", [])
|
||||
self.assertIn("reserved for WTF::HashTrait", str(context.exception))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2014 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Node classes for the AST for a Mojo IDL file."""
|
||||
|
@ -8,17 +8,14 @@
|
|||
# and lineno). You may also define __repr__() to help with analyzing test
|
||||
# failures, especially for more complex types.
|
||||
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
|
||||
|
||||
def _IsStrOrUnicode(x):
|
||||
if sys.version_info[0] < 3:
|
||||
return isinstance(x, (unicode, str))
|
||||
return isinstance(x, str)
|
||||
# Instance of 'NodeListBase' has no '_list_item_type' member (no-member)
|
||||
# pylint: disable=no-member
|
||||
|
||||
|
||||
class NodeBase(object):
|
||||
class NodeBase:
|
||||
"""Base class for nodes in the AST."""
|
||||
|
||||
def __init__(self, filename=None, lineno=None):
|
||||
|
@ -43,7 +40,7 @@ class NodeListBase(NodeBase):
|
|||
classes, in a tuple) of the members of the list.)"""
|
||||
|
||||
def __init__(self, item_or_items=None, **kwargs):
|
||||
super(NodeListBase, self).__init__(**kwargs)
|
||||
super().__init__(**kwargs)
|
||||
self.items = []
|
||||
if item_or_items is None:
|
||||
pass
|
||||
|
@ -62,7 +59,7 @@ class NodeListBase(NodeBase):
|
|||
return self.items.__iter__()
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(NodeListBase, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.items == other.items
|
||||
|
||||
# Implement this so that on failure, we get slightly more sensible output.
|
||||
|
@ -96,7 +93,7 @@ class Definition(NodeBase):
|
|||
include parameter definitions.) This class is meant to be subclassed."""
|
||||
|
||||
def __init__(self, mojom_name, **kwargs):
|
||||
assert _IsStrOrUnicode(mojom_name)
|
||||
assert isinstance(mojom_name, str)
|
||||
NodeBase.__init__(self, **kwargs)
|
||||
self.mojom_name = mojom_name
|
||||
|
||||
|
@ -108,13 +105,13 @@ class Attribute(NodeBase):
|
|||
"""Represents an attribute."""
|
||||
|
||||
def __init__(self, key, value, **kwargs):
|
||||
assert _IsStrOrUnicode(key)
|
||||
super(Attribute, self).__init__(**kwargs)
|
||||
assert isinstance(key, str)
|
||||
super().__init__(**kwargs)
|
||||
self.key = key
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(Attribute, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.key == other.key and \
|
||||
self.value == other.value
|
||||
|
||||
|
@ -131,17 +128,17 @@ class Const(Definition):
|
|||
def __init__(self, mojom_name, attribute_list, typename, value, **kwargs):
|
||||
assert attribute_list is None or isinstance(attribute_list, AttributeList)
|
||||
# The typename is currently passed through as a string.
|
||||
assert _IsStrOrUnicode(typename)
|
||||
assert isinstance(typename, str)
|
||||
# The value is either a literal (currently passed through as a string) or a
|
||||
# "wrapped identifier".
|
||||
assert _IsStrOrUnicode or isinstance(value, tuple)
|
||||
super(Const, self).__init__(mojom_name, **kwargs)
|
||||
assert isinstance(value, (tuple, str))
|
||||
super().__init__(mojom_name, **kwargs)
|
||||
self.attribute_list = attribute_list
|
||||
self.typename = typename
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(Const, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.attribute_list == other.attribute_list and \
|
||||
self.typename == other.typename and \
|
||||
self.value == other.value
|
||||
|
@ -153,12 +150,12 @@ class Enum(Definition):
|
|||
def __init__(self, mojom_name, attribute_list, enum_value_list, **kwargs):
|
||||
assert attribute_list is None or isinstance(attribute_list, AttributeList)
|
||||
assert enum_value_list is None or isinstance(enum_value_list, EnumValueList)
|
||||
super(Enum, self).__init__(mojom_name, **kwargs)
|
||||
super().__init__(mojom_name, **kwargs)
|
||||
self.attribute_list = attribute_list
|
||||
self.enum_value_list = enum_value_list
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(Enum, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.attribute_list == other.attribute_list and \
|
||||
self.enum_value_list == other.enum_value_list
|
||||
|
||||
|
@ -170,13 +167,13 @@ class EnumValue(Definition):
|
|||
# The optional value is either an int (which is current a string) or a
|
||||
# "wrapped identifier".
|
||||
assert attribute_list is None or isinstance(attribute_list, AttributeList)
|
||||
assert value is None or _IsStrOrUnicode(value) or isinstance(value, tuple)
|
||||
super(EnumValue, self).__init__(mojom_name, **kwargs)
|
||||
assert value is None or isinstance(value, (tuple, str))
|
||||
super().__init__(mojom_name, **kwargs)
|
||||
self.attribute_list = attribute_list
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(EnumValue, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.attribute_list == other.attribute_list and \
|
||||
self.value == other.value
|
||||
|
||||
|
@ -188,18 +185,47 @@ class EnumValueList(NodeListBase):
|
|||
_list_item_type = EnumValue
|
||||
|
||||
|
||||
class Feature(Definition):
|
||||
"""Represents a runtime feature definition."""
|
||||
def __init__(self, mojom_name, attribute_list, body, **kwargs):
|
||||
assert attribute_list is None or isinstance(attribute_list, AttributeList)
|
||||
assert isinstance(body, FeatureBody) or body is None
|
||||
super().__init__(mojom_name, **kwargs)
|
||||
self.attribute_list = attribute_list
|
||||
self.body = body
|
||||
|
||||
def __eq__(self, other):
|
||||
return super().__eq__(other) and \
|
||||
self.attribute_list == other.attribute_list and \
|
||||
self.body == other.body
|
||||
|
||||
def __repr__(self):
|
||||
return "Feature(mojom_name = %s, attribute_list = %s, body = %s)" % (
|
||||
self.mojom_name, self.attribute_list, self.body)
|
||||
|
||||
|
||||
# This needs to be declared after `FeatureConst` and `FeatureField`.
|
||||
class FeatureBody(NodeListBase):
|
||||
"""Represents the body of (i.e., list of definitions inside) a feature."""
|
||||
|
||||
# Features are compile time helpers so all fields are initializers/consts
|
||||
# for the underlying platform feature type.
|
||||
_list_item_type = (Const)
|
||||
|
||||
|
||||
class Import(NodeBase):
|
||||
"""Represents an import statement."""
|
||||
|
||||
def __init__(self, attribute_list, import_filename, **kwargs):
|
||||
assert attribute_list is None or isinstance(attribute_list, AttributeList)
|
||||
assert _IsStrOrUnicode(import_filename)
|
||||
super(Import, self).__init__(**kwargs)
|
||||
assert isinstance(import_filename, str)
|
||||
super().__init__(**kwargs)
|
||||
self.attribute_list = attribute_list
|
||||
self.import_filename = import_filename
|
||||
# TODO(crbug.com/953884): Use pathlib once we're migrated fully to Python 3.
|
||||
self.import_filename = os.path.normpath(import_filename).replace('\\', '/')
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(Import, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.attribute_list == other.attribute_list and \
|
||||
self.import_filename == other.import_filename
|
||||
|
||||
|
@ -216,12 +242,12 @@ class Interface(Definition):
|
|||
def __init__(self, mojom_name, attribute_list, body, **kwargs):
|
||||
assert attribute_list is None or isinstance(attribute_list, AttributeList)
|
||||
assert isinstance(body, InterfaceBody)
|
||||
super(Interface, self).__init__(mojom_name, **kwargs)
|
||||
super().__init__(mojom_name, **kwargs)
|
||||
self.attribute_list = attribute_list
|
||||
self.body = body
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(Interface, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.attribute_list == other.attribute_list and \
|
||||
self.body == other.body
|
||||
|
||||
|
@ -236,14 +262,14 @@ class Method(Definition):
|
|||
assert isinstance(parameter_list, ParameterList)
|
||||
assert response_parameter_list is None or \
|
||||
isinstance(response_parameter_list, ParameterList)
|
||||
super(Method, self).__init__(mojom_name, **kwargs)
|
||||
super().__init__(mojom_name, **kwargs)
|
||||
self.attribute_list = attribute_list
|
||||
self.ordinal = ordinal
|
||||
self.parameter_list = parameter_list
|
||||
self.response_parameter_list = response_parameter_list
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(Method, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.attribute_list == other.attribute_list and \
|
||||
self.ordinal == other.ordinal and \
|
||||
self.parameter_list == other.parameter_list and \
|
||||
|
@ -264,12 +290,12 @@ class Module(NodeBase):
|
|||
# |mojom_namespace| is either none or a "wrapped identifier".
|
||||
assert mojom_namespace is None or isinstance(mojom_namespace, tuple)
|
||||
assert attribute_list is None or isinstance(attribute_list, AttributeList)
|
||||
super(Module, self).__init__(**kwargs)
|
||||
super().__init__(**kwargs)
|
||||
self.mojom_namespace = mojom_namespace
|
||||
self.attribute_list = attribute_list
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(Module, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.mojom_namespace == other.mojom_namespace and \
|
||||
self.attribute_list == other.attribute_list
|
||||
|
||||
|
@ -281,13 +307,13 @@ class Mojom(NodeBase):
|
|||
assert module is None or isinstance(module, Module)
|
||||
assert isinstance(import_list, ImportList)
|
||||
assert isinstance(definition_list, list)
|
||||
super(Mojom, self).__init__(**kwargs)
|
||||
super().__init__(**kwargs)
|
||||
self.module = module
|
||||
self.import_list = import_list
|
||||
self.definition_list = definition_list
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(Mojom, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.module == other.module and \
|
||||
self.import_list == other.import_list and \
|
||||
self.definition_list == other.definition_list
|
||||
|
@ -302,11 +328,11 @@ class Ordinal(NodeBase):
|
|||
|
||||
def __init__(self, value, **kwargs):
|
||||
assert isinstance(value, int)
|
||||
super(Ordinal, self).__init__(**kwargs)
|
||||
super().__init__(**kwargs)
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(Ordinal, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.value == other.value
|
||||
|
||||
|
||||
|
@ -314,18 +340,18 @@ class Parameter(NodeBase):
|
|||
"""Represents a method request or response parameter."""
|
||||
|
||||
def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
|
||||
assert _IsStrOrUnicode(mojom_name)
|
||||
assert isinstance(mojom_name, str)
|
||||
assert attribute_list is None or isinstance(attribute_list, AttributeList)
|
||||
assert ordinal is None or isinstance(ordinal, Ordinal)
|
||||
assert _IsStrOrUnicode(typename)
|
||||
super(Parameter, self).__init__(**kwargs)
|
||||
assert isinstance(typename, str)
|
||||
super().__init__(**kwargs)
|
||||
self.mojom_name = mojom_name
|
||||
self.attribute_list = attribute_list
|
||||
self.ordinal = ordinal
|
||||
self.typename = typename
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(Parameter, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.mojom_name == other.mojom_name and \
|
||||
self.attribute_list == other.attribute_list and \
|
||||
self.ordinal == other.ordinal and \
|
||||
|
@ -344,42 +370,51 @@ class Struct(Definition):
|
|||
def __init__(self, mojom_name, attribute_list, body, **kwargs):
|
||||
assert attribute_list is None or isinstance(attribute_list, AttributeList)
|
||||
assert isinstance(body, StructBody) or body is None
|
||||
super(Struct, self).__init__(mojom_name, **kwargs)
|
||||
super().__init__(mojom_name, **kwargs)
|
||||
self.attribute_list = attribute_list
|
||||
self.body = body
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(Struct, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.attribute_list == other.attribute_list and \
|
||||
self.body == other.body
|
||||
|
||||
def __repr__(self):
|
||||
return "Struct(mojom_name = %s, attribute_list = %s, body = %s)" % (
|
||||
self.mojom_name, self.attribute_list, self.body)
|
||||
|
||||
|
||||
class StructField(Definition):
|
||||
"""Represents a struct field definition."""
|
||||
|
||||
def __init__(self, mojom_name, attribute_list, ordinal, typename,
|
||||
default_value, **kwargs):
|
||||
assert _IsStrOrUnicode(mojom_name)
|
||||
assert isinstance(mojom_name, str)
|
||||
assert attribute_list is None or isinstance(attribute_list, AttributeList)
|
||||
assert ordinal is None or isinstance(ordinal, Ordinal)
|
||||
assert _IsStrOrUnicode(typename)
|
||||
assert isinstance(typename, str)
|
||||
# The optional default value is currently either a value as a string or a
|
||||
# "wrapped identifier".
|
||||
assert default_value is None or _IsStrOrUnicode(default_value) or \
|
||||
isinstance(default_value, tuple)
|
||||
super(StructField, self).__init__(mojom_name, **kwargs)
|
||||
assert default_value is None or isinstance(default_value, (str, tuple))
|
||||
super().__init__(mojom_name, **kwargs)
|
||||
self.attribute_list = attribute_list
|
||||
self.ordinal = ordinal
|
||||
self.typename = typename
|
||||
self.default_value = default_value
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(StructField, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.attribute_list == other.attribute_list and \
|
||||
self.ordinal == other.ordinal and \
|
||||
self.typename == other.typename and \
|
||||
self.default_value == other.default_value
|
||||
|
||||
def __repr__(self):
|
||||
return ("StructField(mojom_name = %s, attribute_list = %s, ordinal = %s, "
|
||||
"typename = %s, default_value = %s") % (
|
||||
self.mojom_name, self.attribute_list, self.ordinal,
|
||||
self.typename, self.default_value)
|
||||
|
||||
|
||||
# This needs to be declared after |StructField|.
|
||||
class StructBody(NodeListBase):
|
||||
|
@ -394,29 +429,29 @@ class Union(Definition):
|
|||
def __init__(self, mojom_name, attribute_list, body, **kwargs):
|
||||
assert attribute_list is None or isinstance(attribute_list, AttributeList)
|
||||
assert isinstance(body, UnionBody)
|
||||
super(Union, self).__init__(mojom_name, **kwargs)
|
||||
super().__init__(mojom_name, **kwargs)
|
||||
self.attribute_list = attribute_list
|
||||
self.body = body
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(Union, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.attribute_list == other.attribute_list and \
|
||||
self.body == other.body
|
||||
|
||||
|
||||
class UnionField(Definition):
|
||||
def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
|
||||
assert _IsStrOrUnicode(mojom_name)
|
||||
assert isinstance(mojom_name, str)
|
||||
assert attribute_list is None or isinstance(attribute_list, AttributeList)
|
||||
assert ordinal is None or isinstance(ordinal, Ordinal)
|
||||
assert _IsStrOrUnicode(typename)
|
||||
super(UnionField, self).__init__(mojom_name, **kwargs)
|
||||
assert isinstance(typename, str)
|
||||
super().__init__(mojom_name, **kwargs)
|
||||
self.attribute_list = attribute_list
|
||||
self.ordinal = ordinal
|
||||
self.typename = typename
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(UnionField, self).__eq__(other) and \
|
||||
return super().__eq__(other) and \
|
||||
self.attribute_list == other.attribute_list and \
|
||||
self.ordinal == other.ordinal and \
|
||||
self.typename == other.typename
|
||||
|
|
|
@ -1,32 +1,26 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2014 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import imp
|
||||
import os.path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from mojom.parse import ast
|
||||
|
||||
|
||||
class _TestNode(ast.NodeBase):
|
||||
"""Node type for tests."""
|
||||
|
||||
def __init__(self, value, **kwargs):
|
||||
super(_TestNode, self).__init__(**kwargs)
|
||||
super().__init__(**kwargs)
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other):
|
||||
return super(_TestNode, self).__eq__(other) and self.value == other.value
|
||||
|
||||
return super().__eq__(other) and self.value == other.value
|
||||
|
||||
class _TestNodeList(ast.NodeListBase):
|
||||
"""Node list type for tests."""
|
||||
|
||||
_list_item_type = _TestNode
|
||||
|
||||
|
||||
class ASTTest(unittest.TestCase):
|
||||
"""Tests various AST classes."""
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2018 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Helpers for processing conditionally enabled features in a mojom."""
|
||||
|
@ -17,8 +17,10 @@ class EnableIfError(Error):
|
|||
def _IsEnabled(definition, enabled_features):
|
||||
"""Returns true if a definition is enabled.
|
||||
|
||||
A definition is enabled if it has no EnableIf attribute, or if the value of
|
||||
the EnableIf attribute is in enabled_features.
|
||||
A definition is enabled if it has no EnableIf/EnableIfNot attribute.
|
||||
It is retained if it has an EnableIf attribute and the attribute is in
|
||||
enabled_features. It is retained if it has an EnableIfNot attribute and the
|
||||
attribute is not in enabled features.
|
||||
"""
|
||||
if not hasattr(definition, "attribute_list"):
|
||||
return True
|
||||
|
@ -27,17 +29,19 @@ def _IsEnabled(definition, enabled_features):
|
|||
|
||||
already_defined = False
|
||||
for a in definition.attribute_list:
|
||||
if a.key == 'EnableIf':
|
||||
if a.key == 'EnableIf' or a.key == 'EnableIfNot':
|
||||
if already_defined:
|
||||
raise EnableIfError(
|
||||
definition.filename,
|
||||
"EnableIf attribute may only be defined once per field.",
|
||||
"EnableIf/EnableIfNot attribute may only be set once per field.",
|
||||
definition.lineno)
|
||||
already_defined = True
|
||||
|
||||
for attribute in definition.attribute_list:
|
||||
if attribute.key == 'EnableIf' and attribute.value not in enabled_features:
|
||||
return False
|
||||
if attribute.key == 'EnableIfNot' and attribute.value in enabled_features:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
@ -56,15 +60,12 @@ def _FilterDefinition(definition, enabled_features):
|
|||
"""Filters definitions with a body."""
|
||||
if isinstance(definition, ast.Enum):
|
||||
_FilterDisabledFromNodeList(definition.enum_value_list, enabled_features)
|
||||
elif isinstance(definition, ast.Interface):
|
||||
_FilterDisabledFromNodeList(definition.body, enabled_features)
|
||||
elif isinstance(definition, ast.Method):
|
||||
_FilterDisabledFromNodeList(definition.parameter_list, enabled_features)
|
||||
_FilterDisabledFromNodeList(definition.response_parameter_list,
|
||||
enabled_features)
|
||||
elif isinstance(definition, ast.Struct):
|
||||
_FilterDisabledFromNodeList(definition.body, enabled_features)
|
||||
elif isinstance(definition, ast.Union):
|
||||
elif isinstance(definition,
|
||||
(ast.Interface, ast.Struct, ast.Union, ast.Feature)):
|
||||
_FilterDisabledFromNodeList(definition.body, enabled_features)
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
# Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2018 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import imp
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
def _GetDirAbove(dirname):
|
||||
"""Returns the directory "above" this file containing |dirname| (which must
|
||||
also be "above" this file)."""
|
||||
|
@ -18,9 +17,8 @@ def _GetDirAbove(dirname):
|
|||
if tail == dirname:
|
||||
return path
|
||||
|
||||
|
||||
try:
|
||||
imp.find_module('mojom')
|
||||
importlib.util.find_spec("mojom")
|
||||
except ImportError:
|
||||
sys.path.append(os.path.join(_GetDirAbove('pylib'), 'pylib'))
|
||||
import mojom.parse.ast as ast
|
||||
|
@ -29,7 +27,6 @@ import mojom.parse.parser as parser
|
|||
|
||||
ENABLED_FEATURES = frozenset({'red', 'green', 'blue'})
|
||||
|
||||
|
||||
class ConditionalFeaturesTest(unittest.TestCase):
|
||||
"""Tests |mojom.parse.conditional_features|."""
|
||||
|
||||
|
@ -55,6 +52,48 @@ class ConditionalFeaturesTest(unittest.TestCase):
|
|||
"""
|
||||
self.parseAndAssertEqual(const_source, expected_source)
|
||||
|
||||
def testFilterIfNotConst(self):
|
||||
"""Test that Consts are correctly filtered."""
|
||||
const_source = """
|
||||
[EnableIfNot=blue]
|
||||
const int kMyConst1 = 1;
|
||||
[EnableIfNot=orange]
|
||||
const double kMyConst2 = 2;
|
||||
[EnableIf=blue]
|
||||
const int kMyConst3 = 3;
|
||||
[EnableIfNot=blue]
|
||||
const int kMyConst4 = 4;
|
||||
[EnableIfNot=purple]
|
||||
const int kMyConst5 = 5;
|
||||
"""
|
||||
expected_source = """
|
||||
[EnableIfNot=orange]
|
||||
const double kMyConst2 = 2;
|
||||
[EnableIf=blue]
|
||||
const int kMyConst3 = 3;
|
||||
[EnableIfNot=purple]
|
||||
const int kMyConst5 = 5;
|
||||
"""
|
||||
self.parseAndAssertEqual(const_source, expected_source)
|
||||
|
||||
def testFilterIfNotMultipleConst(self):
|
||||
"""Test that Consts are correctly filtered."""
|
||||
const_source = """
|
||||
[EnableIfNot=blue]
|
||||
const int kMyConst1 = 1;
|
||||
[EnableIfNot=orange]
|
||||
const double kMyConst2 = 2;
|
||||
[EnableIfNot=orange]
|
||||
const int kMyConst3 = 3;
|
||||
"""
|
||||
expected_source = """
|
||||
[EnableIfNot=orange]
|
||||
const double kMyConst2 = 2;
|
||||
[EnableIfNot=orange]
|
||||
const int kMyConst3 = 3;
|
||||
"""
|
||||
self.parseAndAssertEqual(const_source, expected_source)
|
||||
|
||||
def testFilterEnum(self):
|
||||
"""Test that EnumValues are correctly filtered from an Enum."""
|
||||
enum_source = """
|
||||
|
@ -91,6 +130,24 @@ class ConditionalFeaturesTest(unittest.TestCase):
|
|||
"""
|
||||
self.parseAndAssertEqual(import_source, expected_source)
|
||||
|
||||
def testFilterIfNotImport(self):
|
||||
"""Test that imports are correctly filtered from a Mojom."""
|
||||
import_source = """
|
||||
[EnableIf=blue]
|
||||
import "foo.mojom";
|
||||
[EnableIfNot=purple]
|
||||
import "bar.mojom";
|
||||
[EnableIfNot=green]
|
||||
import "baz.mojom";
|
||||
"""
|
||||
expected_source = """
|
||||
[EnableIf=blue]
|
||||
import "foo.mojom";
|
||||
[EnableIfNot=purple]
|
||||
import "bar.mojom";
|
||||
"""
|
||||
self.parseAndAssertEqual(import_source, expected_source)
|
||||
|
||||
def testFilterInterface(self):
|
||||
"""Test that definitions are correctly filtered from an Interface."""
|
||||
interface_source = """
|
||||
|
@ -175,6 +232,50 @@ class ConditionalFeaturesTest(unittest.TestCase):
|
|||
"""
|
||||
self.parseAndAssertEqual(struct_source, expected_source)
|
||||
|
||||
def testFilterIfNotStruct(self):
|
||||
"""Test that definitions are correctly filtered from a Struct."""
|
||||
struct_source = """
|
||||
struct MyStruct {
|
||||
[EnableIf=blue]
|
||||
enum MyEnum {
|
||||
VALUE1,
|
||||
[EnableIfNot=red]
|
||||
VALUE2,
|
||||
};
|
||||
[EnableIfNot=yellow]
|
||||
const double kMyConst = 1.23;
|
||||
[EnableIf=green]
|
||||
int32 a;
|
||||
double b;
|
||||
[EnableIfNot=purple]
|
||||
int32 c;
|
||||
[EnableIf=blue]
|
||||
double d;
|
||||
int32 e;
|
||||
[EnableIfNot=red]
|
||||
double f;
|
||||
};
|
||||
"""
|
||||
expected_source = """
|
||||
struct MyStruct {
|
||||
[EnableIf=blue]
|
||||
enum MyEnum {
|
||||
VALUE1,
|
||||
};
|
||||
[EnableIfNot=yellow]
|
||||
const double kMyConst = 1.23;
|
||||
[EnableIf=green]
|
||||
int32 a;
|
||||
double b;
|
||||
[EnableIfNot=purple]
|
||||
int32 c;
|
||||
[EnableIf=blue]
|
||||
double d;
|
||||
int32 e;
|
||||
};
|
||||
"""
|
||||
self.parseAndAssertEqual(struct_source, expected_source)
|
||||
|
||||
def testFilterUnion(self):
|
||||
"""Test that UnionFields are correctly filtered from a Union."""
|
||||
union_source = """
|
||||
|
@ -216,6 +317,25 @@ class ConditionalFeaturesTest(unittest.TestCase):
|
|||
"""
|
||||
self.parseAndAssertEqual(mojom_source, expected_source)
|
||||
|
||||
def testFeaturesWithEnableIf(self):
|
||||
mojom_source = """
|
||||
feature Foo {
|
||||
const string name = "FooFeature";
|
||||
[EnableIf=red]
|
||||
const bool default_state = false;
|
||||
[EnableIf=yellow]
|
||||
const bool default_state = true;
|
||||
};
|
||||
"""
|
||||
expected_source = """
|
||||
feature Foo {
|
||||
const string name = "FooFeature";
|
||||
[EnableIf=red]
|
||||
const bool default_state = false;
|
||||
};
|
||||
"""
|
||||
self.parseAndAssertEqual(mojom_source, expected_source)
|
||||
|
||||
def testMultipleEnableIfs(self):
|
||||
source = """
|
||||
enum Foo {
|
||||
|
@ -228,6 +348,29 @@ class ConditionalFeaturesTest(unittest.TestCase):
|
|||
conditional_features.RemoveDisabledDefinitions,
|
||||
definition, ENABLED_FEATURES)
|
||||
|
||||
def testMultipleEnableIfs(self):
|
||||
source = """
|
||||
enum Foo {
|
||||
[EnableIf=red,EnableIfNot=yellow]
|
||||
kBarValue = 5,
|
||||
};
|
||||
"""
|
||||
definition = parser.Parse(source, "my_file.mojom")
|
||||
self.assertRaises(conditional_features.EnableIfError,
|
||||
conditional_features.RemoveDisabledDefinitions,
|
||||
definition, ENABLED_FEATURES)
|
||||
|
||||
def testMultipleEnableIfs(self):
|
||||
source = """
|
||||
enum Foo {
|
||||
[EnableIfNot=red,EnableIfNot=yellow]
|
||||
kBarValue = 5,
|
||||
};
|
||||
"""
|
||||
definition = parser.Parse(source, "my_file.mojom")
|
||||
self.assertRaises(conditional_features.EnableIfError,
|
||||
conditional_features.RemoveDisabledDefinitions,
|
||||
definition, ENABLED_FEATURES)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2014 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import imp
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
|
@ -22,7 +21,7 @@ class LexError(Error):
|
|||
|
||||
# We have methods which look like they could be functions:
|
||||
# pylint: disable=R0201
|
||||
class Lexer(object):
|
||||
class Lexer:
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
|
||||
|
@ -56,6 +55,7 @@ class Lexer(object):
|
|||
'PENDING_RECEIVER',
|
||||
'PENDING_ASSOCIATED_REMOTE',
|
||||
'PENDING_ASSOCIATED_RECEIVER',
|
||||
'FEATURE',
|
||||
)
|
||||
|
||||
keyword_map = {}
|
||||
|
@ -81,7 +81,6 @@ class Lexer(object):
|
|||
# Operators
|
||||
'MINUS',
|
||||
'PLUS',
|
||||
'AMP',
|
||||
'QSTN',
|
||||
|
||||
# Assignment
|
||||
|
@ -168,7 +167,6 @@ class Lexer(object):
|
|||
# Operators
|
||||
t_MINUS = r'-'
|
||||
t_PLUS = r'\+'
|
||||
t_AMP = r'&'
|
||||
t_QSTN = r'\?'
|
||||
|
||||
# =
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2014 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import imp
|
||||
import importlib.util
|
||||
import os.path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
def _GetDirAbove(dirname):
|
||||
"""Returns the directory "above" this file containing |dirname| (which must
|
||||
also be "above" this file)."""
|
||||
|
@ -18,17 +17,15 @@ def _GetDirAbove(dirname):
|
|||
if tail == dirname:
|
||||
return path
|
||||
|
||||
|
||||
sys.path.insert(1, os.path.join(_GetDirAbove("mojo"), "third_party"))
|
||||
from ply import lex
|
||||
|
||||
try:
|
||||
imp.find_module("mojom")
|
||||
importlib.util.find_spec("mojom")
|
||||
except ImportError:
|
||||
sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
|
||||
import mojom.parse.lexer
|
||||
|
||||
|
||||
# This (monkey-patching LexToken to make comparison value-based) is evil, but
|
||||
# we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing
|
||||
# for object identity.)
|
||||
|
@ -146,7 +143,6 @@ class LexerTest(unittest.TestCase):
|
|||
self._SingleTokenForInput("+"), _MakeLexToken("PLUS", "+"))
|
||||
self.assertEquals(
|
||||
self._SingleTokenForInput("-"), _MakeLexToken("MINUS", "-"))
|
||||
self.assertEquals(self._SingleTokenForInput("&"), _MakeLexToken("AMP", "&"))
|
||||
self.assertEquals(
|
||||
self._SingleTokenForInput("?"), _MakeLexToken("QSTN", "?"))
|
||||
self.assertEquals(
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2014 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Generates a syntax tree from a Mojo IDL file."""
|
||||
|
||||
# Breaking parser stanzas is unhelpful so allow longer lines.
|
||||
# pylint: disable=line-too-long
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
|
@ -33,7 +36,7 @@ class ParseError(Error):
|
|||
|
||||
# We have methods which look like they could be functions:
|
||||
# pylint: disable=R0201
|
||||
class Parser(object):
|
||||
class Parser:
|
||||
def __init__(self, lexer, source, filename):
|
||||
self.tokens = lexer.tokens
|
||||
self.source = source
|
||||
|
@ -111,7 +114,8 @@ class Parser(object):
|
|||
| union
|
||||
| interface
|
||||
| enum
|
||||
| const"""
|
||||
| const
|
||||
| feature"""
|
||||
p[0] = p[1]
|
||||
|
||||
def p_attribute_section_1(self, p):
|
||||
|
@ -140,12 +144,19 @@ class Parser(object):
|
|||
p[0].Append(p[3])
|
||||
|
||||
def p_attribute_1(self, p):
|
||||
"""attribute : NAME EQUALS evaled_literal
|
||||
| NAME EQUALS NAME"""
|
||||
p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))
|
||||
"""attribute : name_wrapped EQUALS identifier_wrapped"""
|
||||
p[0] = ast.Attribute(p[1],
|
||||
p[3][1],
|
||||
filename=self.filename,
|
||||
lineno=p.lineno(1))
|
||||
|
||||
def p_attribute_2(self, p):
|
||||
"""attribute : NAME"""
|
||||
"""attribute : name_wrapped EQUALS evaled_literal
|
||||
| name_wrapped EQUALS name_wrapped"""
|
||||
p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))
|
||||
|
||||
def p_attribute_3(self, p):
|
||||
"""attribute : name_wrapped"""
|
||||
p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1))
|
||||
|
||||
def p_evaled_literal(self, p):
|
||||
|
@ -161,11 +172,11 @@ class Parser(object):
|
|||
p[0] = eval(p[1])
|
||||
|
||||
def p_struct_1(self, p):
|
||||
"""struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI"""
|
||||
"""struct : attribute_section STRUCT name_wrapped LBRACE struct_body RBRACE SEMI"""
|
||||
p[0] = ast.Struct(p[3], p[1], p[5])
|
||||
|
||||
def p_struct_2(self, p):
|
||||
"""struct : attribute_section STRUCT NAME SEMI"""
|
||||
"""struct : attribute_section STRUCT name_wrapped SEMI"""
|
||||
p[0] = ast.Struct(p[3], p[1], None)
|
||||
|
||||
def p_struct_body_1(self, p):
|
||||
|
@ -180,11 +191,24 @@ class Parser(object):
|
|||
p[0].Append(p[2])
|
||||
|
||||
def p_struct_field(self, p):
|
||||
"""struct_field : attribute_section typename NAME ordinal default SEMI"""
|
||||
"""struct_field : attribute_section typename name_wrapped ordinal default SEMI"""
|
||||
p[0] = ast.StructField(p[3], p[1], p[4], p[2], p[5])
|
||||
|
||||
def p_feature(self, p):
|
||||
"""feature : attribute_section FEATURE NAME LBRACE feature_body RBRACE SEMI"""
|
||||
p[0] = ast.Feature(p[3], p[1], p[5])
|
||||
|
||||
def p_feature_body_1(self, p):
|
||||
"""feature_body : """
|
||||
p[0] = ast.FeatureBody()
|
||||
|
||||
def p_feature_body_2(self, p):
|
||||
"""feature_body : feature_body const"""
|
||||
p[0] = p[1]
|
||||
p[0].Append(p[2])
|
||||
|
||||
def p_union(self, p):
|
||||
"""union : attribute_section UNION NAME LBRACE union_body RBRACE SEMI"""
|
||||
"""union : attribute_section UNION name_wrapped LBRACE union_body RBRACE SEMI"""
|
||||
p[0] = ast.Union(p[3], p[1], p[5])
|
||||
|
||||
def p_union_body_1(self, p):
|
||||
|
@ -197,7 +221,7 @@ class Parser(object):
|
|||
p[1].Append(p[2])
|
||||
|
||||
def p_union_field(self, p):
|
||||
"""union_field : attribute_section typename NAME ordinal SEMI"""
|
||||
"""union_field : attribute_section typename name_wrapped ordinal SEMI"""
|
||||
p[0] = ast.UnionField(p[3], p[1], p[4], p[2])
|
||||
|
||||
def p_default_1(self, p):
|
||||
|
@ -209,8 +233,7 @@ class Parser(object):
|
|||
p[0] = p[2]
|
||||
|
||||
def p_interface(self, p):
|
||||
"""interface : attribute_section INTERFACE NAME LBRACE interface_body \
|
||||
RBRACE SEMI"""
|
||||
"""interface : attribute_section INTERFACE name_wrapped LBRACE interface_body RBRACE SEMI"""
|
||||
p[0] = ast.Interface(p[3], p[1], p[5])
|
||||
|
||||
def p_interface_body_1(self, p):
|
||||
|
@ -233,8 +256,7 @@ class Parser(object):
|
|||
p[0] = p[3]
|
||||
|
||||
def p_method(self, p):
|
||||
"""method : attribute_section NAME ordinal LPAREN parameter_list RPAREN \
|
||||
response SEMI"""
|
||||
"""method : attribute_section name_wrapped ordinal LPAREN parameter_list RPAREN response SEMI"""
|
||||
p[0] = ast.Method(p[2], p[1], p[3], p[5], p[7])
|
||||
|
||||
def p_parameter_list_1(self, p):
|
||||
|
@ -255,7 +277,7 @@ class Parser(object):
|
|||
p[0].Append(p[3])
|
||||
|
||||
def p_parameter(self, p):
|
||||
"""parameter : attribute_section typename NAME ordinal"""
|
||||
"""parameter : attribute_section typename name_wrapped ordinal"""
|
||||
p[0] = ast.Parameter(
|
||||
p[3], p[1], p[4], p[2], filename=self.filename, lineno=p.lineno(3))
|
||||
|
||||
|
@ -271,8 +293,7 @@ class Parser(object):
|
|||
"""nonnullable_typename : basictypename
|
||||
| array
|
||||
| fixed_array
|
||||
| associative_array
|
||||
| interfacerequest"""
|
||||
| associative_array"""
|
||||
p[0] = p[1]
|
||||
|
||||
def p_basictypename(self, p):
|
||||
|
@ -297,18 +318,16 @@ class Parser(object):
|
|||
p[0] = "rcv<%s>" % p[3]
|
||||
|
||||
def p_associatedremotetype(self, p):
|
||||
"""associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier \
|
||||
RANGLE"""
|
||||
"""associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier RANGLE"""
|
||||
p[0] = "rma<%s>" % p[3]
|
||||
|
||||
def p_associatedreceivertype(self, p):
|
||||
"""associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier \
|
||||
RANGLE"""
|
||||
"""associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier RANGLE"""
|
||||
p[0] = "rca<%s>" % p[3]
|
||||
|
||||
def p_handletype(self, p):
|
||||
"""handletype : HANDLE
|
||||
| HANDLE LANGLE NAME RANGLE"""
|
||||
| HANDLE LANGLE name_wrapped RANGLE"""
|
||||
if len(p) == 2:
|
||||
p[0] = p[1]
|
||||
else:
|
||||
|
@ -342,14 +361,6 @@ class Parser(object):
|
|||
"""associative_array : MAP LANGLE identifier COMMA typename RANGLE"""
|
||||
p[0] = p[5] + "{" + p[3] + "}"
|
||||
|
||||
def p_interfacerequest(self, p):
|
||||
"""interfacerequest : identifier AMP
|
||||
| ASSOCIATED identifier AMP"""
|
||||
if len(p) == 3:
|
||||
p[0] = p[1] + "&"
|
||||
else:
|
||||
p[0] = "asso<" + p[2] + "&>"
|
||||
|
||||
def p_ordinal_1(self, p):
|
||||
"""ordinal : """
|
||||
p[0] = None
|
||||
|
@ -366,15 +377,14 @@ class Parser(object):
|
|||
p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1))
|
||||
|
||||
def p_enum_1(self, p):
|
||||
"""enum : attribute_section ENUM NAME LBRACE enum_value_list \
|
||||
RBRACE SEMI
|
||||
| attribute_section ENUM NAME LBRACE nonempty_enum_value_list \
|
||||
COMMA RBRACE SEMI"""
|
||||
"""enum : attribute_section ENUM name_wrapped LBRACE enum_value_list RBRACE SEMI
|
||||
| attribute_section ENUM name_wrapped LBRACE \
|
||||
nonempty_enum_value_list COMMA RBRACE SEMI"""
|
||||
p[0] = ast.Enum(
|
||||
p[3], p[1], p[5], filename=self.filename, lineno=p.lineno(2))
|
||||
|
||||
def p_enum_2(self, p):
|
||||
"""enum : attribute_section ENUM NAME SEMI"""
|
||||
"""enum : attribute_section ENUM name_wrapped SEMI"""
|
||||
p[0] = ast.Enum(
|
||||
p[3], p[1], None, filename=self.filename, lineno=p.lineno(2))
|
||||
|
||||
|
@ -396,9 +406,9 @@ class Parser(object):
|
|||
p[0].Append(p[3])
|
||||
|
||||
def p_enum_value(self, p):
|
||||
"""enum_value : attribute_section NAME
|
||||
| attribute_section NAME EQUALS int
|
||||
| attribute_section NAME EQUALS identifier_wrapped"""
|
||||
"""enum_value : attribute_section name_wrapped
|
||||
| attribute_section name_wrapped EQUALS int
|
||||
| attribute_section name_wrapped EQUALS identifier_wrapped"""
|
||||
p[0] = ast.EnumValue(
|
||||
p[2],
|
||||
p[1],
|
||||
|
@ -407,7 +417,7 @@ class Parser(object):
|
|||
lineno=p.lineno(2))
|
||||
|
||||
def p_const(self, p):
|
||||
"""const : attribute_section CONST typename NAME EQUALS constant SEMI"""
|
||||
"""const : attribute_section CONST typename name_wrapped EQUALS constant SEMI"""
|
||||
p[0] = ast.Const(p[4], p[1], p[3], p[6])
|
||||
|
||||
def p_constant(self, p):
|
||||
|
@ -422,10 +432,16 @@ class Parser(object):
|
|||
# TODO(vtl): Make this produce a "wrapped" identifier (probably as an
|
||||
# |ast.Identifier|, to be added) and get rid of identifier_wrapped.
|
||||
def p_identifier(self, p):
|
||||
"""identifier : NAME
|
||||
| NAME DOT identifier"""
|
||||
"""identifier : name_wrapped
|
||||
| name_wrapped DOT identifier"""
|
||||
p[0] = ''.join(p[1:])
|
||||
|
||||
# Allow 'feature' to be a name literal not just a keyword.
|
||||
def p_name_wrapped(self, p):
|
||||
"""name_wrapped : NAME
|
||||
| FEATURE"""
|
||||
p[0] = p[1]
|
||||
|
||||
def p_literal(self, p):
|
||||
"""literal : int
|
||||
| float
|
||||
|
@ -458,6 +474,12 @@ class Parser(object):
|
|||
# TODO(vtl): Can we figure out what's missing?
|
||||
raise ParseError(self.filename, "Unexpected end of file")
|
||||
|
||||
if e.value == 'feature':
|
||||
raise ParseError(self.filename,
|
||||
"`feature` is reserved for a future mojom keyword",
|
||||
lineno=e.lineno,
|
||||
snippet=self._GetSnippet(e.lineno))
|
||||
|
||||
raise ParseError(
|
||||
self.filename,
|
||||
"Unexpected %r:" % e.value,
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
# Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2014 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import imp
|
||||
import os.path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from mojom.parse import ast
|
||||
from mojom.parse import lexer
|
||||
from mojom.parse import parser
|
||||
|
||||
|
||||
class ParserTest(unittest.TestCase):
|
||||
"""Tests |parser.Parse()|."""
|
||||
|
||||
|
@ -1086,7 +1082,7 @@ class ParserTest(unittest.TestCase):
|
|||
handle<data_pipe_producer>? k;
|
||||
handle<message_pipe>? l;
|
||||
handle<shared_buffer>? m;
|
||||
some_interface&? n;
|
||||
pending_receiver<some_interface>? n;
|
||||
handle<platform>? o;
|
||||
};
|
||||
"""
|
||||
|
@ -1110,7 +1106,7 @@ class ParserTest(unittest.TestCase):
|
|||
ast.StructField('l', None, None, 'handle<message_pipe>?', None),
|
||||
ast.StructField('m', None, None, 'handle<shared_buffer>?',
|
||||
None),
|
||||
ast.StructField('n', None, None, 'some_interface&?', None),
|
||||
ast.StructField('n', None, None, 'rcv<some_interface>?', None),
|
||||
ast.StructField('o', None, None, 'handle<platform>?', None)
|
||||
]))
|
||||
])
|
||||
|
@ -1138,16 +1134,6 @@ class ParserTest(unittest.TestCase):
|
|||
r" *handle\?<data_pipe_consumer> a;$"):
|
||||
parser.Parse(source2, "my_file.mojom")
|
||||
|
||||
source3 = """\
|
||||
struct MyStruct {
|
||||
some_interface?& a;
|
||||
};
|
||||
"""
|
||||
with self.assertRaisesRegexp(
|
||||
parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '&':\n"
|
||||
r" *some_interface\?& a;$"):
|
||||
parser.Parse(source3, "my_file.mojom")
|
||||
|
||||
def testSimpleUnion(self):
|
||||
"""Tests a simple .mojom source that just defines a union."""
|
||||
source = """\
|
||||
|
@ -1317,9 +1303,9 @@ class ParserTest(unittest.TestCase):
|
|||
source1 = """\
|
||||
struct MyStruct {
|
||||
associated MyInterface a;
|
||||
associated MyInterface& b;
|
||||
pending_associated_receiver<MyInterface> b;
|
||||
associated MyInterface? c;
|
||||
associated MyInterface&? d;
|
||||
pending_associated_receiver<MyInterface>? d;
|
||||
};
|
||||
"""
|
||||
expected1 = ast.Mojom(None, ast.ImportList(), [
|
||||
|
@ -1327,16 +1313,16 @@ class ParserTest(unittest.TestCase):
|
|||
'MyStruct', None,
|
||||
ast.StructBody([
|
||||
ast.StructField('a', None, None, 'asso<MyInterface>', None),
|
||||
ast.StructField('b', None, None, 'asso<MyInterface&>', None),
|
||||
ast.StructField('b', None, None, 'rca<MyInterface>', None),
|
||||
ast.StructField('c', None, None, 'asso<MyInterface>?', None),
|
||||
ast.StructField('d', None, None, 'asso<MyInterface&>?', None)
|
||||
ast.StructField('d', None, None, 'rca<MyInterface>?', None)
|
||||
]))
|
||||
])
|
||||
self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
|
||||
|
||||
source2 = """\
|
||||
interface MyInterface {
|
||||
MyMethod(associated A a) =>(associated B& b);
|
||||
MyMethod(associated A a) =>(pending_associated_receiver<B> b);
|
||||
};"""
|
||||
expected2 = ast.Mojom(None, ast.ImportList(), [
|
||||
ast.Interface(
|
||||
|
@ -1344,10 +1330,10 @@ class ParserTest(unittest.TestCase):
|
|||
ast.InterfaceBody(
|
||||
ast.Method(
|
||||
'MyMethod', None, None,
|
||||
ast.ParameterList(
|
||||
ast.Parameter('a', None, None, 'asso<A>')),
|
||||
ast.ParameterList(
|
||||
ast.Parameter('b', None, None, 'asso<B&>')))))
|
||||
ast.ParameterList(ast.Parameter('a', None, None,
|
||||
'asso<A>')),
|
||||
ast.ParameterList(ast.Parameter('b', None, None,
|
||||
'rca<B>')))))
|
||||
])
|
||||
self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
|
||||
|
||||
|
@ -1385,6 +1371,5 @@ class ParserTest(unittest.TestCase):
|
|||
r" *associated\? MyInterface& a;$"):
|
||||
parser.Parse(source3, "my_file.mojom")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2020 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Parses mojom IDL files.
|
||||
|
@ -11,6 +11,7 @@ generate usable language bindings.
|
|||
"""
|
||||
|
||||
import argparse
|
||||
import builtins
|
||||
import codecs
|
||||
import errno
|
||||
import json
|
||||
|
@ -19,6 +20,7 @@ import multiprocessing
|
|||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
|
||||
from mojom.generate import module
|
||||
|
@ -28,16 +30,12 @@ from mojom.parse import conditional_features
|
|||
|
||||
|
||||
# Disable this for easier debugging.
|
||||
# In Python 2, subprocesses just hang when exceptions are thrown :(.
|
||||
_ENABLE_MULTIPROCESSING = sys.version_info[0] > 2
|
||||
_ENABLE_MULTIPROCESSING = True
|
||||
|
||||
if sys.version_info < (3, 4):
|
||||
_MULTIPROCESSING_USES_FORK = sys.platform.startswith('linux')
|
||||
else:
|
||||
# https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725
|
||||
if __name__ == '__main__' and sys.platform == 'darwin':
|
||||
# https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725
|
||||
if __name__ == '__main__' and sys.platform == 'darwin':
|
||||
multiprocessing.set_start_method('fork')
|
||||
_MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'
|
||||
_MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'
|
||||
|
||||
|
||||
def _ResolveRelativeImportPath(path, roots):
|
||||
|
@ -63,7 +61,7 @@ def _ResolveRelativeImportPath(path, roots):
|
|||
raise ValueError('"%s" does not exist in any of %s' % (path, roots))
|
||||
|
||||
|
||||
def _RebaseAbsolutePath(path, roots):
|
||||
def RebaseAbsolutePath(path, roots):
|
||||
"""Rewrites an absolute file path as relative to an absolute directory path in
|
||||
roots.
|
||||
|
||||
|
@ -139,7 +137,7 @@ def _EnsureInputLoaded(mojom_abspath, module_path, abs_paths, asts,
|
|||
# Already done.
|
||||
return
|
||||
|
||||
for dep_abspath, dep_path in dependencies[mojom_abspath]:
|
||||
for dep_abspath, dep_path in sorted(dependencies[mojom_abspath]):
|
||||
if dep_abspath not in loaded_modules:
|
||||
_EnsureInputLoaded(dep_abspath, dep_path, abs_paths, asts, dependencies,
|
||||
loaded_modules, module_metadata)
|
||||
|
@ -159,11 +157,19 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
|
|||
|
||||
def collect(metadata_filename):
|
||||
processed_deps.add(metadata_filename)
|
||||
|
||||
# Paths in the metadata file are relative to the metadata file's dir.
|
||||
metadata_dir = os.path.abspath(os.path.dirname(metadata_filename))
|
||||
|
||||
def to_abs(s):
|
||||
return os.path.normpath(os.path.join(metadata_dir, s))
|
||||
|
||||
with open(metadata_filename) as f:
|
||||
metadata = json.load(f)
|
||||
allowed_imports.update(
|
||||
map(os.path.normcase, map(os.path.normpath, metadata['sources'])))
|
||||
[os.path.normcase(to_abs(s)) for s in metadata['sources']])
|
||||
for dep_metadata in metadata['deps']:
|
||||
dep_metadata = to_abs(dep_metadata)
|
||||
if dep_metadata not in processed_deps:
|
||||
collect(dep_metadata)
|
||||
|
||||
|
@ -172,8 +178,7 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
|
|||
|
||||
|
||||
# multiprocessing helper.
|
||||
def _ParseAstHelper(args):
|
||||
mojom_abspath, enabled_features = args
|
||||
def _ParseAstHelper(mojom_abspath, enabled_features):
|
||||
with codecs.open(mojom_abspath, encoding='utf-8') as f:
|
||||
ast = parser.Parse(f.read(), mojom_abspath)
|
||||
conditional_features.RemoveDisabledDefinitions(ast, enabled_features)
|
||||
|
@ -181,8 +186,7 @@ def _ParseAstHelper(args):
|
|||
|
||||
|
||||
# multiprocessing helper.
|
||||
def _SerializeHelper(args):
|
||||
mojom_abspath, mojom_path = args
|
||||
def _SerializeHelper(mojom_abspath, mojom_path):
|
||||
module_path = os.path.join(_SerializeHelper.output_root_path,
|
||||
_GetModuleFilename(mojom_path))
|
||||
module_dir = os.path.dirname(module_path)
|
||||
|
@ -199,12 +203,33 @@ def _SerializeHelper(args):
|
|||
_SerializeHelper.loaded_modules[mojom_abspath].Dump(f)
|
||||
|
||||
|
||||
def _Shard(target_func, args, processes=None):
|
||||
args = list(args)
|
||||
class _ExceptionWrapper:
|
||||
def __init__(self):
|
||||
# Do not capture exception object to ensure pickling works.
|
||||
self.formatted_trace = traceback.format_exc()
|
||||
|
||||
|
||||
class _FuncWrapper:
|
||||
"""Marshals exceptions and spreads args."""
|
||||
|
||||
def __init__(self, func):
|
||||
self._func = func
|
||||
|
||||
def __call__(self, args):
|
||||
# multiprocessing does not gracefully handle excptions.
|
||||
# https://crbug.com/1219044
|
||||
try:
|
||||
return self._func(*args)
|
||||
except: # pylint: disable=bare-except
|
||||
return _ExceptionWrapper()
|
||||
|
||||
|
||||
def _Shard(target_func, arg_list, processes=None):
|
||||
arg_list = list(arg_list)
|
||||
if processes is None:
|
||||
processes = multiprocessing.cpu_count()
|
||||
# Seems optimal to have each process perform at least 2 tasks.
|
||||
processes = min(processes, len(args) // 2)
|
||||
processes = min(processes, len(arg_list) // 2)
|
||||
|
||||
if sys.platform == 'win32':
|
||||
# TODO(crbug.com/1190269) - we can't use more than 56
|
||||
|
@ -213,13 +238,17 @@ def _Shard(target_func, args, processes=None):
|
|||
|
||||
# Don't spin up processes unless there is enough work to merit doing so.
|
||||
if not _ENABLE_MULTIPROCESSING or processes < 2:
|
||||
for result in map(target_func, args):
|
||||
yield result
|
||||
for arg_tuple in arg_list:
|
||||
yield target_func(*arg_tuple)
|
||||
return
|
||||
|
||||
pool = multiprocessing.Pool(processes=processes)
|
||||
try:
|
||||
for result in pool.imap_unordered(target_func, args):
|
||||
wrapped_func = _FuncWrapper(target_func)
|
||||
for result in pool.imap_unordered(wrapped_func, arg_list):
|
||||
if isinstance(result, _ExceptionWrapper):
|
||||
sys.stderr.write(result.formatted_trace)
|
||||
sys.exit(1)
|
||||
yield result
|
||||
finally:
|
||||
pool.close()
|
||||
|
@ -230,6 +259,7 @@ def _Shard(target_func, args, processes=None):
|
|||
def _ParseMojoms(mojom_files,
|
||||
input_root_paths,
|
||||
output_root_path,
|
||||
module_root_paths,
|
||||
enabled_features,
|
||||
module_metadata,
|
||||
allowed_imports=None):
|
||||
|
@ -245,8 +275,10 @@ def _ParseMojoms(mojom_files,
|
|||
are based on the mojom's relative path, rebased onto this path.
|
||||
Additionally, the script expects this root to contain already-generated
|
||||
modules for any transitive dependencies not listed in mojom_files.
|
||||
module_root_paths: A list of absolute filesystem paths which contain
|
||||
already-generated modules for any non-transitive dependencies.
|
||||
enabled_features: A list of enabled feature names, controlling which AST
|
||||
nodes are filtered by [EnableIf] attributes.
|
||||
nodes are filtered by [EnableIf] or [EnableIfNot] attributes.
|
||||
module_metadata: A list of 2-tuples representing metadata key-value pairs to
|
||||
attach to each compiled module output.
|
||||
|
||||
|
@ -262,7 +294,7 @@ def _ParseMojoms(mojom_files,
|
|||
loaded_modules = {}
|
||||
input_dependencies = defaultdict(set)
|
||||
mojom_files_to_parse = dict((os.path.normcase(abs_path),
|
||||
_RebaseAbsolutePath(abs_path, input_root_paths))
|
||||
RebaseAbsolutePath(abs_path, input_root_paths))
|
||||
for abs_path in mojom_files)
|
||||
abs_paths = dict(
|
||||
(path, abs_path) for abs_path, path in mojom_files_to_parse.items())
|
||||
|
@ -274,7 +306,7 @@ def _ParseMojoms(mojom_files,
|
|||
loaded_mojom_asts[mojom_abspath] = ast
|
||||
|
||||
logging.info('Processing dependencies')
|
||||
for mojom_abspath, ast in loaded_mojom_asts.items():
|
||||
for mojom_abspath, ast in sorted(loaded_mojom_asts.items()):
|
||||
invalid_imports = []
|
||||
for imp in ast.import_list:
|
||||
import_abspath = _ResolveRelativeImportPath(imp.import_filename,
|
||||
|
@ -295,8 +327,8 @@ def _ParseMojoms(mojom_files,
|
|||
# be parsed and have a module file sitting in a corresponding output
|
||||
# location.
|
||||
module_path = _GetModuleFilename(imp.import_filename)
|
||||
module_abspath = _ResolveRelativeImportPath(module_path,
|
||||
[output_root_path])
|
||||
module_abspath = _ResolveRelativeImportPath(
|
||||
module_path, module_root_paths + [output_root_path])
|
||||
with open(module_abspath, 'rb') as module_file:
|
||||
loaded_modules[import_abspath] = module.Module.Load(module_file)
|
||||
|
||||
|
@ -370,6 +402,15 @@ already present in the provided output root.""")
|
|||
'based on the relative input path, rebased onto this root. Note that '
|
||||
'ROOT is also searched for existing modules of any transitive imports '
|
||||
'which were not included in the set of inputs.')
|
||||
arg_parser.add_argument(
|
||||
'--module-root',
|
||||
default=[],
|
||||
action='append',
|
||||
metavar='ROOT',
|
||||
dest='module_root_paths',
|
||||
help='Adds ROOT to the set of root paths to search for existing modules '
|
||||
'of non-transitive imports. Provided root paths are always searched in '
|
||||
'order from longest absolute path to shortest.')
|
||||
arg_parser.add_argument(
|
||||
'--mojoms',
|
||||
nargs='+',
|
||||
|
@ -396,9 +437,9 @@ already present in the provided output root.""")
|
|||
help='Enables a named feature when parsing the given mojoms. Features '
|
||||
'are identified by arbitrary string values. Specifying this flag with a '
|
||||
'given FEATURE name will cause the parser to process any syntax elements '
|
||||
'tagged with an [EnableIf=FEATURE] attribute. If this flag is not '
|
||||
'provided for a given FEATURE, such tagged elements are discarded by the '
|
||||
'parser and will not be present in the compiled output.')
|
||||
'tagged with an [EnableIf=FEATURE] or [EnableIfNot] attribute. If this '
|
||||
'flag is not provided for a given FEATURE, such tagged elements are '
|
||||
'discarded by the parser and will not be present in the compiled output.')
|
||||
arg_parser.add_argument(
|
||||
'--check-imports',
|
||||
dest='build_metadata_filename',
|
||||
|
@ -436,6 +477,7 @@ already present in the provided output root.""")
|
|||
mojom_files = list(map(os.path.abspath, args.mojom_files))
|
||||
input_roots = list(map(os.path.abspath, args.input_root_paths))
|
||||
output_root = os.path.abspath(args.output_root_path)
|
||||
module_roots = list(map(os.path.abspath, args.module_root_paths))
|
||||
|
||||
if args.build_metadata_filename:
|
||||
allowed_imports = _CollectAllowedImportsFromBuildMetadata(
|
||||
|
@ -445,13 +487,16 @@ already present in the provided output root.""")
|
|||
|
||||
module_metadata = list(
|
||||
map(lambda kvp: tuple(kvp.split('=')), args.module_metadata))
|
||||
_ParseMojoms(mojom_files, input_roots, output_root, args.enabled_features,
|
||||
module_metadata, allowed_imports)
|
||||
_ParseMojoms(mojom_files, input_roots, output_root, module_roots,
|
||||
args.enabled_features, module_metadata, allowed_imports)
|
||||
logging.info('Finished')
|
||||
# Exit without running GC, which can save multiple seconds due the large
|
||||
# number of object created.
|
||||
os._exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
Run(sys.argv[1:])
|
||||
# Exit without running GC, which can save multiple seconds due to the large
|
||||
# number of object created. But flush is necessary as os._exit doesn't do
|
||||
# that.
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
os._exit(0)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2020 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
@ -20,7 +20,7 @@ class MojomParserTestCase(unittest.TestCase):
|
|||
resolution, and module serialization and deserialization."""
|
||||
|
||||
def __init__(self, method_name):
|
||||
super(MojomParserTestCase, self).__init__(method_name)
|
||||
super().__init__(method_name)
|
||||
self._temp_dir = None
|
||||
|
||||
def setUp(self):
|
||||
|
@ -67,7 +67,7 @@ class MojomParserTestCase(unittest.TestCase):
|
|||
self.ParseMojoms([filename])
|
||||
m = self.LoadModule(filename)
|
||||
definitions = {}
|
||||
for kinds in (m.enums, m.structs, m.unions, m.interfaces):
|
||||
for kinds in (m.enums, m.structs, m.unions, m.interfaces, m.features):
|
||||
for kind in kinds:
|
||||
definitions[kind.mojom_name] = kind
|
||||
return definitions
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2020 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import json
|
||||
|
||||
from mojom_parser_test_case import MojomParserTestCase
|
||||
|
||||
|
||||
|
@ -119,15 +121,22 @@ class MojomParserTest(MojomParserTestCase):
|
|||
c = 'c.mojom'
|
||||
c_metadata = 'out/c.build_metadata'
|
||||
self.WriteFile(a_metadata,
|
||||
'{"sources": ["%s"], "deps": []}\n' % self.GetPath(a))
|
||||
json.dumps({
|
||||
"sources": [self.GetPath(a)],
|
||||
"deps": []
|
||||
}))
|
||||
self.WriteFile(
|
||||
b_metadata,
|
||||
'{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(b),
|
||||
self.GetPath(a_metadata)))
|
||||
json.dumps({
|
||||
"sources": [self.GetPath(b)],
|
||||
"deps": [self.GetPath(a_metadata)]
|
||||
}))
|
||||
self.WriteFile(
|
||||
c_metadata,
|
||||
'{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(c),
|
||||
self.GetPath(b_metadata)))
|
||||
json.dumps({
|
||||
"sources": [self.GetPath(c)],
|
||||
"deps": [self.GetPath(b_metadata)]
|
||||
}))
|
||||
self.WriteFile(a, """\
|
||||
module a;
|
||||
struct Bar {};""")
|
||||
|
@ -154,9 +163,15 @@ class MojomParserTest(MojomParserTestCase):
|
|||
b = 'b.mojom'
|
||||
b_metadata = 'out/b.build_metadata'
|
||||
self.WriteFile(a_metadata,
|
||||
'{"sources": ["%s"], "deps": []}\n' % self.GetPath(a))
|
||||
json.dumps({
|
||||
"sources": [self.GetPath(a)],
|
||||
"deps": []
|
||||
}))
|
||||
self.WriteFile(b_metadata,
|
||||
'{"sources": ["%s"], "deps": []}\n' % self.GetPath(b))
|
||||
json.dumps({
|
||||
"sources": [self.GetPath(b)],
|
||||
"deps": []
|
||||
}))
|
||||
self.WriteFile(a, """\
|
||||
module a;
|
||||
struct Bar {};""")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2020 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
|
44
utils/ipc/mojo/public/tools/mojom/union_unittest.py
Normal file
44
utils/ipc/mojo/public/tools/mojom/union_unittest.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
from mojom_parser_test_case import MojomParserTestCase
|
||||
|
||||
|
||||
class UnionTest(MojomParserTestCase):
|
||||
"""Tests union parsing behavior."""
|
||||
|
||||
def testExtensibleMustHaveDefault(self):
|
||||
"""Verifies that extensible unions must have a default field."""
|
||||
mojom = 'foo.mojom'
|
||||
self.WriteFile(mojom, 'module foo; [Extensible] union U { bool x; };')
|
||||
with self.assertRaisesRegexp(Exception, 'must specify a \[Default\]'):
|
||||
self.ParseMojoms([mojom])
|
||||
|
||||
def testExtensibleSingleDefault(self):
|
||||
"""Verifies that extensible unions must not have multiple default fields."""
|
||||
mojom = 'foo.mojom'
|
||||
self.WriteFile(
|
||||
mojom, """\
|
||||
module foo;
|
||||
[Extensible] union U {
|
||||
[Default] bool x;
|
||||
[Default] bool y;
|
||||
};
|
||||
""")
|
||||
with self.assertRaisesRegexp(Exception, 'Multiple \[Default\] fields'):
|
||||
self.ParseMojoms([mojom])
|
||||
|
||||
def testExtensibleDefaultTypeValid(self):
|
||||
"""Verifies that an extensible union's default field must be nullable or
|
||||
integral type."""
|
||||
mojom = 'foo.mojom'
|
||||
self.WriteFile(
|
||||
mojom, """\
|
||||
module foo;
|
||||
[Extensible] union U {
|
||||
[Default] handle<message_pipe> p;
|
||||
};
|
||||
""")
|
||||
with self.assertRaisesRegexp(Exception, 'must be nullable or integral'):
|
||||
self.ParseMojoms([mojom])
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2020 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
@ -23,9 +23,12 @@ class VersionCompatibilityTest(MojomParserTestCase):
|
|||
|
||||
checker = module.BackwardCompatibilityChecker()
|
||||
compatibility_map = {}
|
||||
for name in old.keys():
|
||||
for name in old:
|
||||
try:
|
||||
compatibility_map[name] = checker.IsBackwardCompatible(
|
||||
new[name], old[name])
|
||||
except Exception:
|
||||
compatibility_map[name] = False
|
||||
return compatibility_map
|
||||
|
||||
def assertBackwardCompatible(self, old_mojom, new_mojom):
|
||||
|
@ -60,40 +63,48 @@ class VersionCompatibilityTest(MojomParserTestCase):
|
|||
"""Adding a value to an existing version is not allowed, even if the old
|
||||
enum was marked [Extensible]. Note that it is irrelevant whether or not the
|
||||
new enum is marked [Extensible]."""
|
||||
self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',
|
||||
self.assertNotBackwardCompatible(
|
||||
'[Extensible] enum E { [Default] kFoo, kBar };',
|
||||
'enum E { kFoo, kBar, kBaz };')
|
||||
self.assertNotBackwardCompatible(
|
||||
'[Extensible] enum E { kFoo, kBar };',
|
||||
'[Extensible] enum E { kFoo, kBar, kBaz };')
|
||||
'[Extensible] enum E { [Default] kFoo, kBar };',
|
||||
'[Extensible] enum E { [Default] kFoo, kBar, kBaz };')
|
||||
self.assertNotBackwardCompatible(
|
||||
'[Extensible] enum E { kFoo, [MinVersion=1] kBar };',
|
||||
'[Extensible] enum E { [Default] kFoo, [MinVersion=1] kBar };',
|
||||
'enum E { kFoo, [MinVersion=1] kBar, [MinVersion=1] kBaz };')
|
||||
|
||||
def testEnumValueRemoval(self):
|
||||
"""Removal of an enum value is never valid even for [Extensible] enums."""
|
||||
self.assertNotBackwardCompatible('enum E { kFoo, kBar };',
|
||||
'enum E { kFoo };')
|
||||
self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',
|
||||
'[Extensible] enum E { kFoo };')
|
||||
self.assertNotBackwardCompatible(
|
||||
'[Extensible] enum E { kA, [MinVersion=1] kB };',
|
||||
'[Extensible] enum E { kA, };')
|
||||
'[Extensible] enum E { [Default] kFoo, kBar };',
|
||||
'[Extensible] enum E { [Default] kFoo };')
|
||||
self.assertNotBackwardCompatible(
|
||||
'[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=1] kZ };',
|
||||
'[Extensible] enum E { kA, [MinVersion=1] kB };')
|
||||
'[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',
|
||||
'[Extensible] enum E { [Default] kA, };')
|
||||
self.assertNotBackwardCompatible(
|
||||
"""[Extensible] enum E {
|
||||
[Default] kA,
|
||||
[MinVersion=1] kB,
|
||||
[MinVersion=1] kZ };""",
|
||||
'[Extensible] enum E { [Default] kA, [MinVersion=1] kB };')
|
||||
|
||||
def testNewExtensibleEnumValueWithMinVersion(self):
|
||||
"""Adding a new and properly [MinVersion]'d value to an [Extensible] enum
|
||||
is a backward-compatible change. Note that it is irrelevant whether or not
|
||||
the new enum is marked [Extensible]."""
|
||||
self.assertBackwardCompatible('[Extensible] enum E { kA, kB };',
|
||||
self.assertBackwardCompatible('[Extensible] enum E { [Default] kA, kB };',
|
||||
'enum E { kA, kB, [MinVersion=1] kC };')
|
||||
self.assertBackwardCompatible(
|
||||
'[Extensible] enum E { kA, kB };',
|
||||
'[Extensible] enum E { kA, kB, [MinVersion=1] kC };')
|
||||
'[Extensible] enum E { [Default] kA, kB };',
|
||||
'[Extensible] enum E { [Default] kA, kB, [MinVersion=1] kC };')
|
||||
self.assertBackwardCompatible(
|
||||
'[Extensible] enum E { kA, [MinVersion=1] kB };',
|
||||
'[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=2] kC };')
|
||||
'[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',
|
||||
"""[Extensible] enum E {
|
||||
[Default] kA,
|
||||
[MinVersion=1] kB,
|
||||
[MinVersion=2] kC };""")
|
||||
|
||||
def testRenameEnumValue(self):
|
||||
"""Renaming an enum value does not affect backward-compatibility. Only
|
||||
|
@ -161,14 +172,17 @@ class VersionCompatibilityTest(MojomParserTestCase):
|
|||
'struct S {}; struct T { S s; };',
|
||||
'struct S { [MinVersion=1] int32 x; }; struct T { S s; };')
|
||||
self.assertBackwardCompatible(
|
||||
'[Extensible] enum E { kA }; struct S { E e; };',
|
||||
'[Extensible] enum E { kA, [MinVersion=1] kB }; struct S { E e; };')
|
||||
'[Extensible] enum E { [Default] kA }; struct S { E e; };',
|
||||
"""[Extensible] enum E {
|
||||
[Default] kA,
|
||||
[MinVersion=1] kB };
|
||||
struct S { E e; };""")
|
||||
self.assertNotBackwardCompatible(
|
||||
'struct S {}; struct T { S s; };',
|
||||
'struct S { int32 x; }; struct T { S s; };')
|
||||
self.assertNotBackwardCompatible(
|
||||
'[Extensible] enum E { kA }; struct S { E e; };',
|
||||
'[Extensible] enum E { kA, kB }; struct S { E e; };')
|
||||
'[Extensible] enum E { [Default] kA }; struct S { E e; };',
|
||||
'[Extensible] enum E { [Default] kA, kB }; struct S { E e; };')
|
||||
|
||||
def testNewStructFieldWithInvalidMinVersion(self):
|
||||
"""Adding a new field using an existing MinVersion breaks backward-
|
||||
|
@ -305,14 +319,17 @@ class VersionCompatibilityTest(MojomParserTestCase):
|
|||
'struct S {}; union U { S s; };',
|
||||
'struct S { [MinVersion=1] int32 x; }; union U { S s; };')
|
||||
self.assertBackwardCompatible(
|
||||
'[Extensible] enum E { kA }; union U { E e; };',
|
||||
'[Extensible] enum E { kA, [MinVersion=1] kB }; union U { E e; };')
|
||||
'[Extensible] enum E { [Default] kA }; union U { E e; };',
|
||||
"""[Extensible] enum E {
|
||||
[Default] kA,
|
||||
[MinVersion=1] kB };
|
||||
union U { E e; };""")
|
||||
self.assertNotBackwardCompatible(
|
||||
'struct S {}; union U { S s; };',
|
||||
'struct S { int32 x; }; union U { S s; };')
|
||||
self.assertNotBackwardCompatible(
|
||||
'[Extensible] enum E { kA }; union U { E e; };',
|
||||
'[Extensible] enum E { kA, kB }; union U { E e; };')
|
||||
'[Extensible] enum E { [Default] kA }; union U { E e; };',
|
||||
'[Extensible] enum E { [Default] kA, kB }; union U { E e; };')
|
||||
|
||||
def testNewUnionFieldWithInvalidMinVersion(self):
|
||||
"""Adding a new field using an existing MinVersion breaks backward-
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2020 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
@ -8,11 +8,13 @@ import sys
|
|||
|
||||
_TOOLS_DIR = os.path.dirname(__file__)
|
||||
_MOJOM_DIR = os.path.join(_TOOLS_DIR, 'mojom')
|
||||
_BINDINGS_DIR = os.path.join(_TOOLS_DIR, 'bindings')
|
||||
_SRC_DIR = os.path.join(_TOOLS_DIR, os.path.pardir, os.path.pardir,
|
||||
os.path.pardir)
|
||||
|
||||
# Ensure that the mojom library is discoverable.
|
||||
sys.path.append(_MOJOM_DIR)
|
||||
sys.path.append(_BINDINGS_DIR)
|
||||
|
||||
# Help Python find typ in //third_party/catapult/third_party/typ/
|
||||
sys.path.append(
|
||||
|
@ -21,7 +23,7 @@ import typ
|
|||
|
||||
|
||||
def Main():
|
||||
return typ.main(top_level_dir=_MOJOM_DIR)
|
||||
return typ.main(top_level_dirs=[_MOJOM_DIR, _BINDINGS_DIR])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
Files in this directory are imported from 9c138d992bfc of Chromium. Do not
|
||||
Files in this directory are imported from 9be4263648d7 of Chromium. Do not
|
||||
modify them manually.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
# Copyright 2019 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue