Patch Detail
Show a patch.
GET /api/1.1/patches/9634/?format=api
{ "id": 9634, "url": "https://patchwork.libcamera.org/api/1.1/patches/9634/?format=api", "web_url": "https://patchwork.libcamera.org/patch/9634/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api", "name": "libcamera", "link_name": "libcamera", "list_id": "libcamera_core", "list_email": "libcamera-devel@lists.libcamera.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20200915142038.28757-3-paul.elder@ideasonboard.com>", "date": "2020-09-15T14:20:17", "name": "[libcamera-devel,02/23] utils: ipc: import mojo", "commit_ref": null, "pull_url": null, "state": "accepted", "archived": false, "hash": "66e2ff0dd3d933d7a8c74a0c7ccda9b19f4ed402", "submitter": { "id": 17, "url": "https://patchwork.libcamera.org/api/1.1/people/17/?format=api", "name": "Paul Elder", "email": "paul.elder@ideasonboard.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/9634/mbox/", "series": [ { "id": 1291, "url": "https://patchwork.libcamera.org/api/1.1/series/1291/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=1291", "date": "2020-09-15T14:20:16", "name": "IPA isolation implementation", "version": 1, "mbox": "https://patchwork.libcamera.org/series/1291/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/9634/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/9634/checks/", "tags": {}, "headers": { "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>", "X-Original-To": "parsemail@patchwork.libcamera.org", "Delivered-To": "parsemail@patchwork.libcamera.org", "Received": [ "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 9F3C6BF01C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 15 Sep 2020 14:21:51 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 62EAC62E31;\n\tTue, 15 Sep 2020 16:21:51 +0200 (CEST)", "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id E9F73603EE\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 15 Sep 2020 16:21:01 +0200 (CEST)", "from pyrite.rasen.tech (unknown\n\t[IPv6:2400:4051:61:600:2c71:1b79:d06d:5032])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 03997FD8;\n\tTue, 15 Sep 2020 16:20:54 +0200 (CEST)" ], "Authentication-Results": "lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"uUWIUCqZ\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1600179658;\n\tbh=hJh9ZMbNHdXhItHEdwNfm79bFOiu6aKvOrR+LQrE4lQ=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=uUWIUCqZU0VLfD4YcTeGmRHX/O+lLpx8ZSRdYjZfuE2s0ys0QrCgou7Md9gtMp0/A\n\tcbeEFLhNWjdC2x608kZSWxnM/GO5d97+me+fIUshua1RrqXUPSjRMGsSXBJmh5MsP6\n\tHYxVbN3U1wX2NVUK/A427pEba1S5xLEd9LswlecY=", "From": "Paul Elder <paul.elder@ideasonboard.com>", "To": "libcamera-devel@lists.libcamera.org", "Date": "Tue, 15 Sep 2020 23:20:17 +0900", "Message-Id": "<20200915142038.28757-3-paul.elder@ideasonboard.com>", "X-Mailer": "git-send-email 2.27.0", "In-Reply-To": "<20200915142038.28757-1-paul.elder@ideasonboard.com>", "References": "<20200915142038.28757-1-paul.elder@ideasonboard.com>", "MIME-Version": "1.0", "X-Mailman-Approved-At": "Tue, 15 Sep 2020 16:21:50 +0200", "Subject": "[libcamera-devel] [PATCH 02/23] utils: ipc: import mojo", "X-BeenThere": "libcamera-devel@lists.libcamera.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "<libcamera-devel.lists.libcamera.org>", "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>", "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>", "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>", "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>", "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>", "Content-Type": "text/plain; charset=\"us-ascii\"", "Content-Transfer-Encoding": "7bit", "Errors-To": "libcamera-devel-bounces@lists.libcamera.org", "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>" }, "content": "Import mojo from Chromium repository, so that we can use it for\ngenerating code for the IPC mechanism.\n\nThis tree has been pruned somewhat; for example, the templates for code\ngeneration for unused languages are removed.\n\nSigned-off-by: Paul Elder <paul.elder@ideasonboard.com>\n---\n utils/ipc/mojo/public/LICENSE | 27 +\n utils/ipc/mojo/public/tools/.style.yapf | 6 +\n utils/ipc/mojo/public/tools/BUILD.gn | 18 +\n utils/ipc/mojo/public/tools/bindings/BUILD.gn | 108 +\n .../ipc/mojo/public/tools/bindings/README.md | 816 +++++++\n .../chromium_bindings_configuration.gni | 51 +\n .../tools/bindings/compile_typescript.py | 27 +\n .../tools/bindings/concatenate-files.py | 54 +\n ...concatenate_and_replace_closure_exports.py | 73 +\n .../bindings/format_typemap_generator_args.py | 36 +\n .../tools/bindings/gen_data_files_list.py | 52 +\n .../tools/bindings/generate_type_mappings.py | 187 ++\n .../ipc/mojo/public/tools/bindings/mojom.gni | 1941 +++++++++++++++++\n .../bindings/mojom_bindings_generator.py | 390 ++++\n .../mojom_bindings_generator_unittest.py | 62 +\n .../tools/bindings/mojom_types_downgrader.py | 119 +\n .../tools/bindings/validate_typemap_config.py | 57 +\n utils/ipc/mojo/public/tools/mojom/README.md | 14 +\n .../mojom/check_stable_mojom_compatibility.py | 170 ++\n ...eck_stable_mojom_compatibility_unittest.py | 260 +++\n .../mojo/public/tools/mojom/const_unittest.py | 90 +\n .../mojo/public/tools/mojom/enum_unittest.py | 92 +\n .../mojo/public/tools/mojom/mojom/BUILD.gn | 43 +\n .../mojo/public/tools/mojom/mojom/__init__.py | 0\n .../mojo/public/tools/mojom/mojom/error.py | 28 +\n .../mojo/public/tools/mojom/mojom/fileutil.py | 45 +\n .../tools/mojom/mojom/fileutil_unittest.py | 40 +\n .../tools/mojom/mojom/generate/__init__.py | 0\n .../mojom/mojom/generate/constant_resolver.py | 93 +\n .../tools/mojom/mojom/generate/generator.py | 325 +++\n .../mojom/generate/generator_unittest.py | 74 +\n .../tools/mojom/mojom/generate/module.py | 1635 ++++++++++++++\n .../mojom/mojom/generate/module_unittest.py | 31 +\n .../public/tools/mojom/mojom/generate/pack.py | 258 +++\n .../mojom/mojom/generate/pack_unittest.py | 225 ++\n .../mojom/mojom/generate/template_expander.py | 83 +\n .../tools/mojom/mojom/generate/translate.py | 854 ++++++++\n .../mojom/generate/translate_unittest.py | 73 +\n .../tools/mojom/mojom/parse/__init__.py | 0\n .../public/tools/mojom/mojom/parse/ast.py | 427 ++++\n .../tools/mojom/mojom/parse/ast_unittest.py | 121 +\n .../mojom/mojom/parse/conditional_features.py | 82 +\n .../parse/conditional_features_unittest.py | 233 ++\n .../public/tools/mojom/mojom/parse/lexer.py | 251 +++\n .../tools/mojom/mojom/parse/lexer_unittest.py | 198 ++\n .../public/tools/mojom/mojom/parse/parser.py | 488 +++++\n .../mojom/mojom/parse/parser_unittest.py | 1390 ++++++++++++\n .../mojo/public/tools/mojom/mojom_parser.py | 361 +++\n .../tools/mojom/mojom_parser_test_case.py | 73 +\n .../tools/mojom/mojom_parser_unittest.py | 171 ++\n .../tools/mojom/stable_attribute_unittest.py | 127 ++\n .../mojom/version_compatibility_unittest.py | 397 ++++\n .../public/tools/run_all_python_unittests.py | 28 +\n utils/ipc/tools/diagnosis/crbug_1001171.py | 51 +\n 54 files changed, 12855 insertions(+)\n create mode 100644 utils/ipc/mojo/public/LICENSE\n create mode 100644 utils/ipc/mojo/public/tools/.style.yapf\n create mode 100644 utils/ipc/mojo/public/tools/BUILD.gn\n create mode 100644 utils/ipc/mojo/public/tools/bindings/BUILD.gn\n create mode 100644 utils/ipc/mojo/public/tools/bindings/README.md\n create mode 100644 utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni\n create mode 100644 utils/ipc/mojo/public/tools/bindings/compile_typescript.py\n create mode 100755 utils/ipc/mojo/public/tools/bindings/concatenate-files.py\n create mode 100755 utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py\n create mode 100755 utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py\n create mode 100644 utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py\n create mode 100755 utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py\n create mode 100644 utils/ipc/mojo/public/tools/bindings/mojom.gni\n create mode 100755 utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py\n create mode 100644 utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py\n create mode 100755 utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py\n create mode 100755 utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/README.md\n create mode 100755 utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py\n create mode 100755 utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/const_unittest.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/enum_unittest.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/__init__.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/error.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py\n create mode 100755 utils/ipc/mojo/public/tools/mojom/mojom_parser.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py\n create mode 100644 utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py\n create mode 100755 utils/ipc/mojo/public/tools/run_all_python_unittests.py\n create mode 100644 utils/ipc/tools/diagnosis/crbug_1001171.py", "diff": "diff --git a/utils/ipc/mojo/public/LICENSE b/utils/ipc/mojo/public/LICENSE\nnew file mode 100644\nindex 00000000..972bb2ed\n--- /dev/null\n+++ b/utils/ipc/mojo/public/LICENSE\n@@ -0,0 +1,27 @@\n+// Copyright 2014 The Chromium Authors. All rights reserved.\n+//\n+// Redistribution and use in source and binary forms, with or without\n+// modification, are permitted provided that the following conditions are\n+// met:\n+//\n+// * Redistributions of source code must retain the above copyright\n+// notice, this list of conditions and the following disclaimer.\n+// * Redistributions in binary form must reproduce the above\n+// copyright notice, this list of conditions and the following disclaimer\n+// in the documentation and/or other materials provided with the\n+// distribution.\n+// * Neither the name of Google Inc. nor the names of its\n+// contributors may be used to endorse or promote products derived from\n+// this software without specific prior written permission.\n+//\n+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n+// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\ndiff --git a/utils/ipc/mojo/public/tools/.style.yapf b/utils/ipc/mojo/public/tools/.style.yapf\nnew file mode 100644\nindex 00000000..b4ebbe24\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/.style.yapf\n@@ -0,0 +1,6 @@\n+[style]\n+based_on_style = pep8\n+\n+# New directories should use a .style.yapf that does not include the following:\n+column_limit = 80\n+indent_width = 2\ndiff --git a/utils/ipc/mojo/public/tools/BUILD.gn b/utils/ipc/mojo/public/tools/BUILD.gn\nnew file mode 100644\nindex 00000000..4c68350b\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/BUILD.gn\n@@ -0,0 +1,18 @@\n+# Copyright 2020 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+# The main target used to aggregate all unit tests for Python-based Mojo tools.\n+# This is used to generate a complete isolate which can be pushed to bots to run\n+# the tests.\n+group(\"mojo_python_unittests\") {\n+ data = [\n+ \"run_all_python_unittests.py\",\n+ \"//testing/scripts/common.py\",\n+ \"//testing/scripts/run_isolated_script_test.py\",\n+ \"//testing/test_env.py\",\n+ \"//testing/xvfb.py\",\n+ ]\n+ deps = [ \"//mojo/public/tools/mojom/mojom:tests\" ]\n+ data_deps = [ \"//third_party/catapult/third_party/typ/\" ]\n+}\ndiff --git a/utils/ipc/mojo/public/tools/bindings/BUILD.gn b/utils/ipc/mojo/public/tools/bindings/BUILD.gn\nnew file mode 100644\nindex 00000000..8ba6e922\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/bindings/BUILD.gn\n@@ -0,0 +1,108 @@\n+# Copyright 2016 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import(\"//mojo/public/tools/bindings/mojom.gni\")\n+import(\"//third_party/jinja2/jinja2.gni\")\n+\n+action(\"precompile_templates\") {\n+ sources = mojom_generator_sources\n+ sources += [\n+ \"$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/enum_serialization_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/interface_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/interface_definition.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/interface_macros.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/module-forward.h.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/module-import-headers.h.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/module-params-data.h.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/module-shared-internal.h.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/module-shared-message-ids.h.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/module-test-utils.cc.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/module-test-utils.h.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/module.cc.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/module.h.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/struct_data_view_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/struct_data_view_definition.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/struct_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/struct_definition.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/struct_macros.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/struct_serialization_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/struct_traits_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/struct_traits_definition.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/struct_unserialized_message_context.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/union_data_view_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/union_data_view_definition.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/union_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/union_definition.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/union_serialization_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/union_traits_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/union_traits_definition.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/validation_macros.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/wrapper_class_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/wrapper_class_definition.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/wrapper_class_template_definition.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/wrapper_union_class_declaration.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/wrapper_union_class_definition.tmpl\",\n+ \"$mojom_generator_root/generators/cpp_templates/wrapper_union_class_template_definition.tmpl\",\n+ \"$mojom_generator_root/generators/java_templates/constant_definition.tmpl\",\n+ \"$mojom_generator_root/generators/java_templates/constants.java.tmpl\",\n+ \"$mojom_generator_root/generators/java_templates/data_types_definition.tmpl\",\n+ \"$mojom_generator_root/generators/java_templates/enum.java.tmpl\",\n+ \"$mojom_generator_root/generators/java_templates/enum_definition.tmpl\",\n+ \"$mojom_generator_root/generators/java_templates/header.java.tmpl\",\n+ \"$mojom_generator_root/generators/java_templates/interface.java.tmpl\",\n+ \"$mojom_generator_root/generators/java_templates/interface_definition.tmpl\",\n+ \"$mojom_generator_root/generators/java_templates/interface_internal.java.tmpl\",\n+ \"$mojom_generator_root/generators/java_templates/struct.java.tmpl\",\n+ \"$mojom_generator_root/generators/java_templates/union.java.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/enum_definition.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/externs/interface_definition.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/externs/module.externs.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/externs/struct_definition.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/fuzzing.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/interface_definition.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/lite/interface_definition.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/lite/module_definition.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/lite/mojom-lite.js.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/lite/struct_definition.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/lite/union_definition.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/module.amd.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/module_definition.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/struct_definition.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/union_definition.tmpl\",\n+ \"$mojom_generator_root/generators/js_templates/validation_macros.tmpl\",\n+ \"$mojom_generator_root/generators/mojolpm_templates/mojolpm.cc.tmpl\",\n+ \"$mojom_generator_root/generators/mojolpm_templates/mojolpm.h.tmpl\",\n+ \"$mojom_generator_root/generators/mojolpm_templates/mojolpm.proto.tmpl\",\n+ \"$mojom_generator_root/generators/mojolpm_templates/mojolpm_from_proto_macros.tmpl\",\n+ \"$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl\",\n+ \"$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl\",\n+ \"$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl\",\n+ \"$mojom_generator_root/generators/ts_templates/module_definition.tmpl\",\n+ \"$mojom_generator_root/generators/ts_templates/mojom.tmpl\",\n+ ]\n+ script = mojom_generator_script\n+\n+ inputs = jinja2_sources\n+ outputs = [\n+ \"$target_gen_dir/cpp_templates.zip\",\n+ \"$target_gen_dir/java_templates.zip\",\n+ \"$target_gen_dir/mojolpm_templates.zip\",\n+ \"$target_gen_dir/js_templates.zip\",\n+ \"$target_gen_dir/ts_templates.zip\",\n+ ]\n+ args = [\n+ \"-o\",\n+ rebase_path(target_gen_dir, root_build_dir),\n+ \"--use_bundled_pylibs\",\n+ \"precompile\",\n+ ]\n+}\ndiff --git a/utils/ipc/mojo/public/tools/bindings/README.md b/utils/ipc/mojo/public/tools/bindings/README.md\nnew file mode 100644\nindex 00000000..1a3d5c58\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/bindings/README.md\n@@ -0,0 +1,816 @@\n+# Mojom Interface Definition Language (IDL)\n+This document is a subset of the [Mojo documentation](/mojo/README.md).\n+\n+[TOC]\n+\n+## Overview\n+\n+Mojom is the IDL for Mojo interfaces. Given a `.mojom` file, the\n+[bindings\n+generator](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings/) can\n+output bindings for any supported language: **C++**, **JavaScript**, or\n+**Java**.\n+\n+For a trivial example consider the following hypothetical Mojom file we write to\n+`//services/widget/public/mojom/frobinator.mojom`:\n+\n+```\n+module widget.mojom;\n+\n+interface Frobinator {\n+ Frobinate();\n+};\n+```\n+\n+This defines a single [interface](#Interfaces) named `Frobinator` in a\n+[module](#Modules) named `widget.mojom` (and thus fully qualified in Mojom as\n+`widget.mojom.Frobinator`.) Note that many interfaces and/or other types of\n+definitions (structs, enums, *etc.*) may be included in a single Mojom file.\n+\n+If we add a corresponding GN target to\n+`//services/widget/public/mojom/BUILD.gn`:\n+\n+```\n+import(\"mojo/public/tools/bindings/mojom.gni\")\n+\n+mojom(\"mojom\") {\n+ sources = [\n+ \"frobinator.mojom\",\n+ ]\n+}\n+```\n+\n+and then build this target:\n+\n+```\n+ninja -C out/r services/widget/public/mojom\n+```\n+\n+we'll find several generated sources in our output directory:\n+\n+```\n+out/r/gen/services/widget/public/mojom/frobinator.mojom.cc\n+out/r/gen/services/widget/public/mojom/frobinator.mojom.h\n+out/r/gen/services/widget/public/mojom/frobinator.mojom-shared.h\n+etc...\n+```\n+\n+Each of these generated source modules includes a set of definitions\n+representing the Mojom contents in C++. You can also build or depend on suffixed\n+target names to get bindings for other languages. For example,\n+\n+```\n+ninja -C out/r services/widget/public/mojom:mojom_js\n+ninja -C out/r services/widget/public/mojom:mojom_java\n+```\n+\n+would generate JavaScript and Java bindings respectively, in the same generated\n+output directory.\n+\n+For more details regarding the generated\n+outputs please see\n+[documentation for individual target languages](#Generated-Code-For-Target-Languages).\n+\n+## Mojom Syntax\n+\n+Mojom IDL allows developers to define **structs**, **unions**, **interfaces**,\n+**constants**, and **enums**, all within the context of a **module**. These\n+definitions are used to generate code in the supported target languages at build\n+time.\n+\n+Mojom files may **import** other Mojom files in order to reference their\n+definitions.\n+\n+### Primitive Types\n+Mojom supports a few basic data types which may be composed into structs or used\n+for message parameters.\n+\n+| Type | Description\n+|-------------------------------|-------------------------------------------------------|\n+| `bool` | Boolean type (`true` or `false`.)\n+| `int8`, `uint8` | Signed or unsigned 8-bit integer.\n+| `int16`, `uint16` | Signed or unsigned 16-bit integer.\n+| `int32`, `uint32` | Signed or unsigned 32-bit integer.\n+| `int64`, `uint64` | Signed or unsigned 64-bit integer.\n+| `float`, `double` | 32- or 64-bit floating point number.\n+| `string` | UTF-8 encoded string.\n+| `array<T>` | Array of any Mojom type *T*; for example, `array<uint8>` or `array<array<string>>`.\n+| `array<T, N>` | Fixed-length array of any Mojom type *T*. The parameter *N* must be an integral constant.\n+| `map<S, T>` | Associated array maping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type.\n+| `handle` | Generic Mojo handle. May be any type of handle, including a wrapped native platform handle.\n+| `handle<message_pipe>` | Generic message pipe handle.\n+| `handle<shared_buffer>` | Shared buffer handle.\n+| `handle<data_pipe_producer>` | Data pipe producer handle.\n+| `handle<data_pipe_consumer>` | Data pipe consumer handle.\n+| `handle<platform>` | A native platform/OS handle.\n+| *`pending_remote<InterfaceType>`* | Any user-defined Mojom interface type. This is sugar for a strongly-typed message pipe handle which should eventually be used to make outgoing calls on the interface.\n+| *`pending_receiver<InterfaceType>`* | A pending receiver for any user-defined Mojom interface type. This is sugar for a more strongly-typed message pipe handle which is expected to receive request messages and should therefore eventually be bound to an implementation of the interface.\n+| *`pending_associated_remote<InterfaceType>`* | An associated interface handle. See [Associated Interfaces](#Associated-Interfaces)\n+| *`pending_associated_receiver<InterfaceType>`* | A pending associated receiver. See [Associated Interfaces](#Associated-Interfaces)\n+| *T*? | An optional (nullable) value. Primitive numeric types (integers, floats, booleans, and enums) are not nullable. All other types are nullable.\n+\n+### Modules\n+\n+Every Mojom file may optionally specify a single **module** to which it belongs.\n+\n+This is used strictly for aggregaging all defined symbols therein within a\n+common Mojom namespace. The specific impact this has on generated binidngs code\n+varies for each target language. For example, if the following Mojom is used to\n+generate bindings:\n+\n+```\n+module business.stuff;\n+\n+interface MoneyGenerator {\n+ GenerateMoney();\n+};\n+```\n+\n+Generated C++ bindings will define a class interface `MoneyGenerator` in the\n+`business::stuff` namespace, while Java bindings will define an interface\n+`MoneyGenerator` in the `org.chromium.business.stuff` package. JavaScript\n+bindings at this time are unaffected by module declarations.\n+\n+**NOTE:** By convention in the Chromium codebase, **all** Mojom files should\n+declare a module name with at least (and preferrably exactly) one top-level name\n+as well as an inner `mojom` module suffix. *e.g.*, `chrome.mojom`,\n+`business.mojom`, *etc.*\n+\n+This convention makes it easy to tell which symbols are generated by Mojom when\n+reading non-Mojom code, and it also avoids namespace collisions in the fairly\n+common scenario where you have a real C++ or Java `Foo` along with a\n+corresponding Mojom `Foo` for its serialized representation.\n+\n+### Imports\n+\n+If your Mojom references definitions from other Mojom files, you must **import**\n+those files. Import syntax is as follows:\n+\n+```\n+import \"services/widget/public/mojom/frobinator.mojom\";\n+```\n+\n+Import paths are always relative to the top-level directory.\n+\n+Note that circular imports are **not** supported.\n+\n+### Structs\n+\n+Structs are defined using the **struct** keyword, and they provide a way to\n+group related fields together:\n+\n+``` cpp\n+struct StringPair {\n+ string first;\n+ string second;\n+};\n+```\n+\n+Struct fields may be comprised of any of the types listed above in the\n+[Primitive Types](#Primitive-Types) section.\n+\n+Default values may be specified as long as they are constant:\n+\n+``` cpp\n+struct Request {\n+ int32 id = -1;\n+ string details;\n+};\n+```\n+\n+What follows is a fairly\n+comprehensive example using the supported field types:\n+\n+``` cpp\n+struct StringPair {\n+ string first;\n+ string second;\n+};\n+\n+enum AnEnum {\n+ YES,\n+ NO\n+};\n+\n+interface SampleInterface {\n+ DoStuff();\n+};\n+\n+struct AllTheThings {\n+ // Note that these types can never be marked nullable!\n+ bool boolean_value;\n+ int8 signed_8bit_value = 42;\n+ uint8 unsigned_8bit_value;\n+ int16 signed_16bit_value;\n+ uint16 unsigned_16bit_value;\n+ int32 signed_32bit_value;\n+ uint32 unsigned_32bit_value;\n+ int64 signed_64bit_value;\n+ uint64 unsigned_64bit_value;\n+ float float_value_32bit;\n+ double float_value_64bit;\n+ AnEnum enum_value = AnEnum.YES;\n+\n+ // Strings may be nullable.\n+ string? maybe_a_string_maybe_not;\n+\n+ // Structs may contain other structs. These may also be nullable.\n+ StringPair some_strings;\n+ StringPair? maybe_some_more_strings;\n+\n+ // In fact structs can also be nested, though in practice you must always make\n+ // such fields nullable -- otherwise messages would need to be infinitely long\n+ // in order to pass validation!\n+ AllTheThings? more_things;\n+\n+ // Arrays may be templated over any Mojom type, and are always nullable:\n+ array<int32> numbers;\n+ array<int32>? maybe_more_numbers;\n+\n+ // Arrays of arrays of arrays... are fine.\n+ array<array<array<AnEnum>>> this_works_but_really_plz_stop;\n+\n+ // The element type may be nullable if it's a type which is allowed to be\n+ // nullable.\n+ array<AllTheThings?> more_maybe_things;\n+\n+ // Fixed-size arrays get some extra validation on the receiving end to ensure\n+ // that the correct number of elements is always received.\n+ array<uint64, 2> uuid;\n+\n+ // Maps follow many of the same rules as arrays. Key types may be any\n+ // non-handle, non-collection type, and value types may be any supported\n+ // struct field type. Maps may also be nullable.\n+ map<string, int32> one_map;\n+ map<AnEnum, string>? maybe_another_map;\n+ map<StringPair, AllTheThings?>? maybe_a_pretty_weird_but_valid_map;\n+ map<StringPair, map<int32, array<map<string, string>?>?>?> ridiculous;\n+\n+ // And finally, all handle types are valid as struct fields and may be\n+ // nullable. Note that interfaces and interface requests (the \"Foo\" and\n+ // \"Foo&\" type syntax respectively) are just strongly-typed message pipe\n+ // handles.\n+ handle generic_handle;\n+ handle<data_pipe_consumer> reader;\n+ handle<data_pipe_producer>? maybe_writer;\n+ handle<shared_buffer> dumping_ground;\n+ handle<message_pipe> raw_message_pipe;\n+ pending_remote<SampleInterface>? maybe_a_sample_interface_client_pipe;\n+ pending_receiver<SampleInterface> non_nullable_sample_pending_receiver;\n+ pending_receiver<SampleInterface>? nullable_sample_pending_receiver;\n+ pending_associated_remote<SampleInterface> associated_interface_client;\n+ pending_associated_receiver<SampleInterface> associated_pending_receiver;\n+ pending_associated_receiver<SampleInterface>? maybe_another_pending_receiver;\n+};\n+```\n+\n+For details on how all of these different types translate to usable generated\n+code, see\n+[documentation for individual target languages](#Generated-Code-For-Target-Languages).\n+\n+### Unions\n+\n+Mojom supports tagged unions using the **union** keyword. A union is a\n+collection of fields which may taken the value of any single one of those fields\n+at a time. Thus they provide a way to represent a variant value type while\n+minimizing storage requirements.\n+\n+Union fields may be of any type supported by [struct](#Structs) fields. For\n+example:\n+\n+```cpp\n+union ExampleUnion {\n+ string str;\n+ StringPair pair;\n+ int64 id;\n+ array<uint64, 2> guid;\n+ SampleInterface iface;\n+};\n+```\n+\n+For details on how unions like this translate to generated bindings code, see\n+[documentation for individual target languages](#Generated-Code-For-Target-Languages).\n+\n+### Enumeration Types\n+\n+Enumeration types may be defined using the **enum** keyword either directly\n+within a module or nested within the namespace of some struct or interface:\n+\n+```\n+module business.mojom;\n+\n+enum Department {\n+ SALES = 0,\n+ DEV,\n+};\n+\n+struct Employee {\n+ enum Type {\n+ FULL_TIME,\n+ PART_TIME,\n+ };\n+\n+ Type type;\n+ // ...\n+};\n+```\n+\n+Similar to C-style enums, individual values may be explicitly assigned within an\n+enum definition. By default, values are based at zero and increment by\n+1 sequentially.\n+\n+The effect of nested definitions on generated bindings varies depending on the\n+target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages)\n+\n+### Constants\n+\n+Constants may be defined using the **const** keyword either directly within a\n+module or nested within the namespace of some struct or interface:\n+\n+```\n+module business.mojom;\n+\n+const string kServiceName = \"business\";\n+\n+struct Employee {\n+ const uint64 kInvalidId = 0;\n+\n+ enum Type {\n+ FULL_TIME,\n+ PART_TIME,\n+ };\n+\n+ uint64 id = kInvalidId;\n+ Type type;\n+};\n+```\n+\n+The effect of nested definitions on generated bindings varies depending on the\n+target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages)\n+\n+### Interfaces\n+\n+An **interface** is a logical bundle of parameterized request messages. Each\n+request message may optionally define a parameterized response message. Here's\n+an example to define an interface `Foo` with various kinds of requests:\n+\n+```\n+interface Foo {\n+ // A request which takes no arguments and expects no response.\n+ MyMessage();\n+\n+ // A request which has some arguments and expects no response.\n+ MyOtherMessage(string name, array<uint8> bytes);\n+\n+ // A request which expects a single-argument response.\n+ MyMessageWithResponse(string command) => (bool success);\n+\n+ // A request which expects a response with multiple arguments.\n+ MyMessageWithMoarResponse(string a, string b) => (int8 c, int8 d);\n+};\n+```\n+\n+Anything which is a valid struct field type (see [Structs](#Structs)) is also a\n+valid request or response argument type. The type notation is the same for both.\n+\n+### Attributes\n+\n+Mojom definitions may have their meaning altered by **attributes**, specified\n+with a syntax similar to Java or C# attributes. There are a handle of\n+interesting attributes supported today.\n+\n+**`[Sync]`**\n+: The `Sync` attribute may be specified for any interface method which expects\n+ a response. This makes it so that callers of the method can wait\n+ synchronously for a response. See\n+ [Synchronous Calls](/mojo/public/cpp/bindings/README.md#Synchronous-Calls)\n+ in the C++ bindings documentation. Note that sync methods are only actually\n+ synchronous when called from C++.\n+\n+**`[Extensible]`**\n+: The `Extensible` attribute may be specified for any enum definition. This\n+ essentially disables builtin range validation when receiving values of the\n+ enum type in a message, allowing older bindings to tolerate unrecognized\n+ values from newer versions of the enum.\n+\n+**`[Native]`**\n+: The `Native` attribute may be specified for an empty struct declaration to\n+ provide a nominal bridge between Mojo IPC and legacy `IPC::ParamTraits` or\n+ `IPC_STRUCT_TRAITS*` macros.\n+ See\n+ [Repurposing Legacy IPC Traits](/docs/mojo_ipc_conversion.md#repurposing-and-invocations)\n+ for more details. Note support for this attribute is strictly limited to C++\n+ bindings generation.\n+\n+**`[MinVersion=N]`**\n+: The `MinVersion` attribute is used to specify the version at which a given\n+ field, enum value, interface method, or method parameter was introduced.\n+ See [Versioning](#Versioning) for more details.\n+\n+**`[Stable]`**\n+: The `Stable` attribute specifies that a given mojom type or interface\n+ definition can be considered stable over time, meaning it is safe to use for\n+ things like persistent storage or communication between independent\n+ version-skewed binaries. Stable definitions may only depend on builtin mojom\n+ types or other stable definitions, and changes to such definitions MUST\n+ preserve backward-compatibility through appropriate use of versioning.\n+ Backward-compatibility of changes is enforced in the Chromium tree using a\n+ strict presubmit check. See [Versioning](#Versioning) for more details on\n+ backward-compatibility constraints.\n+\n+**`[EnableIf=value]`**\n+: The `EnableIf` attribute is used to conditionally enable definitions when\n+ the mojom is parsed. If the `mojom` target in the GN file does not include\n+ the matching `value` in the list of `enabled_features`, the definition\n+ will be disabled. This is useful for mojom definitions that only make\n+ sense on one platform. Note that the `EnableIf` attribute can only be set\n+ once per definition.\n+\n+## Generated Code For Target Languages\n+\n+When the bindings generator successfully processes an input Mojom file, it emits\n+corresponding code for each supported target language. For more details on how\n+Mojom concepts translate to a given target langauge, please refer to the\n+bindings API documentation for that language:\n+\n+* [C++ Bindings](/mojo/public/cpp/bindings/README.md)\n+* [JavaScript Bindings](/mojo/public/js/README.md)\n+* [Java Bindings](/mojo/public/java/bindings/README.md)\n+\n+## Message Validation\n+\n+Regardless of target language, all interface messages are validated during\n+deserialization before they are dispatched to a receiving implementation of the\n+interface. This helps to ensure consitent validation across interfaces without\n+leaving the burden to developers and security reviewers every time a new message\n+is added.\n+\n+If a message fails validation, it is never dispatched. Instead a **connection\n+error** is raised on the binding object (see\n+[C++ Connection Errors](/mojo/public/cpp/bindings/README.md#Connection-Errors),\n+[Java Connection Errors](/mojo/public/java/bindings/README.md#Connection-Errors),\n+or\n+[JavaScript Connection Errors](/mojo/public/js/README.md#Connection-Errors) for\n+details.)\n+\n+Some baseline level of validation is done automatically for primitive Mojom\n+types.\n+\n+### Non-Nullable Objects\n+\n+Mojom fields or parameter values (*e.g.*, structs, interfaces, arrays, *etc.*)\n+may be marked nullable in Mojom definitions (see\n+[Primitive Types](#Primitive-Types).) If a field or parameter is **not** marked\n+nullable but a message is received with a null value in its place, that message\n+will fail validation.\n+\n+### Enums\n+\n+Enums declared in Mojom are automatically validated against the range of legal\n+values. For example if a Mojom declares the enum:\n+\n+``` cpp\n+enum AdvancedBoolean {\n+ TRUE = 0,\n+ FALSE = 1,\n+ FILE_NOT_FOUND = 2,\n+};\n+```\n+\n+and a message is received with the integral value 3 (or anything other than 0,\n+1, or 2) in place of some `AdvancedBoolean` field or parameter, the message will\n+fail validation.\n+\n+*** note\n+NOTE: It's possible to avoid this type of validation error by explicitly marking\n+an enum as [Extensible](#Attributes) if you anticipate your enum being exchanged\n+between two different versions of the binding interface. See\n+[Versioning](#Versioning).\n+***\n+\n+### Other failures\n+\n+There are a host of internal validation errors that may occur when a malformed\n+message is received, but developers should not be concerned with these\n+specifically; in general they can only result from internal bindings bugs,\n+compromised processes, or some remote endpoint making a dubious effort to\n+manually encode their own bindings messages.\n+\n+### Custom Validation\n+\n+It's also possible for developers to define custom validation logic for specific\n+Mojom struct types by exploiting the\n+[type mapping](/mojo/public/cpp/bindings/README.md#Type-Mapping) system for C++\n+bindings. Messages rejected by custom validation logic trigger the same\n+validation failure behavior as the built-in type validation routines.\n+\n+## Associated Interfaces\n+\n+As mentioned in the [Primitive Types](#Primitive-Types) section above, pending_remote\n+and pending_receiver fields and parameters may be marked as `associated`. This\n+essentially means that they are piggy-backed on some other interface's message\n+pipe.\n+\n+Because individual interface message pipes operate independently there can be no\n+relative ordering guarantees among them. Associated interfaces are useful when\n+one interface needs to guarantee strict FIFO ordering with respect to one or\n+more other interfaces, as they allow interfaces to share a single pipe.\n+\n+Currently associated interfaces are only supported in generated C++ bindings.\n+See the documentation for\n+[C++ Associated Interfaces](/mojo/public/cpp/bindings/README.md#Associated-Interfaces).\n+\n+## Versioning\n+\n+### Overview\n+\n+*** note\n+**NOTE:** You don't need to worry about versioning if you don't care about\n+backwards compatibility. Specifically, all parts of Chrome are updated\n+atomically today and there is not yet any possibility of any two Chrome\n+processes communicating with two different versions of any given Mojom\n+interface.\n+***\n+\n+Services extend their interfaces to support new features over time, and clients\n+want to use those new features when they are available. If services and clients\n+are not updated at the same time, it's important for them to be able to\n+communicate with each other using different snapshots (versions) of their\n+interfaces.\n+\n+This document shows how to extend Mojom interfaces in a backwards-compatible\n+way. Changing interfaces in a non-backwards-compatible way is not discussed,\n+because in that case communication between different interface versions is\n+impossible anyway.\n+\n+### Versioned Structs\n+\n+You can use the `MinVersion` [attribute](#Attributes) to indicate from which\n+version a struct field is introduced. Assume you have the following struct:\n+\n+``` cpp\n+struct Employee {\n+ uint64 employee_id;\n+ string name;\n+};\n+```\n+\n+and you would like to add a birthday field. You can do:\n+\n+``` cpp\n+struct Employee {\n+ uint64 employee_id;\n+ string name;\n+ [MinVersion=1] Date? birthday;\n+};\n+```\n+\n+By default, fields belong to version 0. New fields must be appended to the\n+struct definition (*i.e*., existing fields must not change **ordinal value**)\n+with the `MinVersion` attribute set to a number greater than any previous\n+existing versions.\n+\n+*** note\n+**NOTE:** do not change existing fields in versioned structs, as this is\n+not backwards-compatible. Instead, rename the old field to make its\n+deprecation clear and add a new field with the new version number.\n+***\n+\n+**Ordinal value** refers to the relative positional layout of a struct's fields\n+(and an interface's methods) when encoded in a message. Implicitly, ordinal\n+numbers are assigned to fields according to lexical position. In the example\n+above, `employee_id` has an ordinal value of 0 and `name` has an ordinal value\n+of 1.\n+\n+Ordinal values can be specified explicitly using `**@**` notation, subject to\n+the following hard constraints:\n+\n+* For any given struct or interface, if any field or method explicitly specifies\n+ an ordinal value, all fields or methods must explicitly specify an ordinal\n+ value.\n+* For an *N*-field struct or *N*-method interface, the set of explicitly\n+ assigned ordinal values must be limited to the range *[0, N-1]*. Interfaces\n+ should include placeholder methods to fill the ordinal positions of removed\n+ methods (for example \"Unused_Message_7@7()\" or \"RemovedMessage@42()\", etc).\n+\n+You may reorder fields, but you must ensure that the ordinal values of existing\n+fields remain unchanged. For example, the following struct remains\n+backwards-compatible:\n+\n+``` cpp\n+struct Employee {\n+ uint64 employee_id@0;\n+ [MinVersion=1] Date? birthday@2;\n+ string name@1;\n+};\n+```\n+\n+*** note\n+**NOTE:** Newly added fields of Mojo object or handle types MUST be nullable.\n+See [Primitive Types](#Primitive-Types).\n+***\n+\n+### Versioned Interfaces\n+\n+There are two dimensions on which an interface can be extended\n+\n+**Appending New Parameters To Existing Methods**\n+: Parameter lists are treated as structs internally, so all the rules of\n+ versioned structs apply to method parameter lists. The only difference is\n+ that the version number is scoped to the whole interface rather than to any\n+ individual parameter list.\n+\n+ Please note that adding a response to a message which did not previously\n+ expect a response is a not a backwards-compatible change.\n+\n+**Appending New Methods**\n+: Similarly, you can reorder methods with explicit ordinal values as long as\n+ the ordinal values of existing methods are unchanged.\n+\n+For example:\n+\n+``` cpp\n+// Old version:\n+interface HumanResourceDatabase {\n+ AddEmployee(Employee employee) => (bool success);\n+ QueryEmployee(uint64 id) => (Employee? employee);\n+};\n+\n+// New version:\n+interface HumanResourceDatabase {\n+ AddEmployee(Employee employee) => (bool success);\n+\n+ QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)\n+ => (Employee? employee,\n+ [MinVersion=1] array<uint8>? finger_print);\n+\n+ [MinVersion=1]\n+ AttachFingerPrint(uint64 id, array<uint8> finger_print)\n+ => (bool success);\n+};\n+```\n+\n+Similar to [versioned structs](#Versioned-Structs), when you pass the parameter\n+list of a request or response method to a destination using an older version of\n+an interface, unrecognized fields are silently discarded. However, if the method\n+call itself is not recognized, it is considered a validation error and the\n+receiver will close its end of the interface pipe. For example, if a client on\n+version 1 of the above interface sends an `AttachFingerPrint` request to an\n+implementation of version 0, the client will be disconnected.\n+\n+Bindings target languages that support versioning expose means to query or\n+assert the remote version from a client handle (*e.g.*, an\n+`mojo::Remote<T>` in C++ bindings.)\n+\n+See\n+[C++ Versioning Considerations](/mojo/public/cpp/bindings/README.md#Versioning-Considerations)\n+and\n+[Java Versioning Considerations](/mojo/public/java/bindings/README.md#Versioning-Considerations)\n+\n+### Versioned Enums\n+\n+**By default, enums are non-extensible**, which means that generated message\n+validation code does not expect to see new values in the future. When an unknown\n+value is seen for a non-extensible enum field or parameter, a validation error\n+is raised.\n+\n+If you want an enum to be extensible in the future, you can apply the\n+`[Extensible]` [attribute](#Attributes):\n+\n+``` cpp\n+[Extensible]\n+enum Department {\n+ SALES,\n+ DEV,\n+};\n+```\n+\n+And later you can extend this enum without breaking backwards compatibility:\n+\n+``` cpp\n+[Extensible]\n+enum Department {\n+ SALES,\n+ DEV,\n+ [MinVersion=1] RESEARCH,\n+};\n+```\n+\n+*** note\n+**NOTE:** For versioned enum definitions, the use of a `[MinVersion]` attribute\n+is strictly for documentation purposes. It has no impact on the generated code.\n+***\n+\n+With extensible enums, bound interface implementations may receive unknown enum\n+values and will need to deal with them gracefully. See\n+[C++ Versioning Considerations](/mojo/public/cpp/bindings/README.md#Versioning-Considerations)\n+for details.\n+\n+## Grammar Reference\n+\n+Below is the (BNF-ish) context-free grammar of the Mojom language:\n+\n+```\n+MojomFile = StatementList\n+StatementList = Statement StatementList | Statement\n+Statement = ModuleStatement | ImportStatement | Definition\n+\n+ModuleStatement = AttributeSection \"module\" Identifier \";\"\n+ImportStatement = \"import\" StringLiteral \";\"\n+Definition = Struct Union Interface Enum Const\n+\n+AttributeSection = <empty> | \"[\" AttributeList \"]\"\n+AttributeList = <empty> | NonEmptyAttributeList\n+NonEmptyAttributeList = Attribute\n+ | Attribute \",\" NonEmptyAttributeList\n+Attribute = Name\n+ | Name \"=\" Name\n+ | Name \"=\" Literal\n+\n+Struct = AttributeSection \"struct\" Name \"{\" StructBody \"}\" \";\"\n+ | AttributeSection \"struct\" Name \";\"\n+StructBody = <empty>\n+ | StructBody Const\n+ | StructBody Enum\n+ | StructBody StructField\n+StructField = AttributeSection TypeSpec Name Ordinal Default \";\"\n+\n+Union = AttributeSection \"union\" Name \"{\" UnionBody \"}\" \";\"\n+UnionBody = <empty> | UnionBody UnionField\n+UnionField = AttributeSection TypeSpec Name Ordinal \";\"\n+\n+Interface = AttributeSection \"interface\" Name \"{\" InterfaceBody \"}\" \";\"\n+InterfaceBody = <empty>\n+ | InterfaceBody Const\n+ | InterfaceBody Enum\n+ | InterfaceBody Method\n+Method = AttributeSection Name Ordinal \"(\" ParamterList \")\" Response \";\"\n+ParameterList = <empty> | NonEmptyParameterList\n+NonEmptyParameterList = Parameter\n+ | Parameter \",\" NonEmptyParameterList\n+Parameter = AttributeSection TypeSpec Name Ordinal\n+Response = <empty> | \"=>\" \"(\" ParameterList \")\"\n+\n+TypeSpec = TypeName \"?\" | TypeName\n+TypeName = BasicTypeName\n+ | Array\n+ | FixedArray\n+ | Map\n+ | InterfaceRequest\n+BasicTypeName = Identifier | \"associated\" Identifier | HandleType | NumericType\n+NumericType = \"bool\" | \"int8\" | \"uint8\" | \"int16\" | \"uint16\" | \"int32\"\n+ | \"uint32\" | \"int64\" | \"uint64\" | \"float\" | \"double\"\n+HandleType = \"handle\" | \"handle\" \"<\" SpecificHandleType \">\"\n+SpecificHandleType = \"message_pipe\"\n+ | \"shared_buffer\"\n+ | \"data_pipe_consumer\"\n+ | \"data_pipe_producer\"\n+ | \"platform\"\n+Array = \"array\" \"<\" TypeSpec \">\"\n+FixedArray = \"array\" \"<\" TypeSpec \",\" IntConstDec \">\"\n+Map = \"map\" \"<\" Identifier \",\" TypeSpec \">\"\n+InterfaceRequest = Identifier \"&\" | \"associated\" Identifier \"&\"\n+\n+Ordinal = <empty> | OrdinalValue\n+\n+Default = <empty> | \"=\" Constant\n+\n+Enum = AttributeSection \"enum\" Name \"{\" NonEmptyEnumValueList \"}\" \";\"\n+ | AttributeSection \"enum\" Name \"{\" NonEmptyEnumValueList \",\" \"}\" \";\"\n+NonEmptyEnumValueList = EnumValue | NonEmptyEnumValueList \",\" EnumValue\n+EnumValue = AttributeSection Name\n+ | AttributeSection Name \"=\" Integer\n+ | AttributeSection Name \"=\" Identifier\n+\n+Const = \"const\" TypeSpec Name \"=\" Constant \";\"\n+\n+Constant = Literal | Identifier \";\"\n+\n+Identifier = Name | Name \".\" Identifier\n+\n+Literal = Integer | Float | \"true\" | \"false\" | \"default\" | StringLiteral\n+\n+Integer = IntConst | \"+\" IntConst | \"-\" IntConst\n+IntConst = IntConstDec | IntConstHex\n+\n+Float = FloatConst | \"+\" FloatConst | \"-\" FloatConst\n+\n+; The rules below are for tokens matched strictly according to the given regexes\n+\n+Identifier = /[a-zA-Z_][0-9a-zA-Z_]*/\n+IntConstDec = /0|(1-9[0-9]*)/\n+IntConstHex = /0[xX][0-9a-fA-F]+/\n+OrdinalValue = /@(0|(1-9[0-9]*))/\n+FloatConst = ... # Imagine it's close enough to C-style float syntax.\n+StringLiteral = ... # Imagine it's close enough to C-style string literals, including escapes.\n+```\n+\n+## Additional Documentation\n+\n+[Mojom Message Format](https://docs.google.com/document/d/13pv9cFh5YKuBggDBQ1-AL8VReF-IYpFOFpRfvWFrwio/edit)\n+: Describes the wire format used by Mojo bindings interfaces over message\n+ pipes.\n+\n+[Input Format of Mojom Message Validation Tests](https://docs.google.com/document/d/1-y-2IYctyX2NPaLxJjpJfzVNWCC2SR2MJAD9MpIytHQ/edit)\n+: Describes a text format used to facilitate bindings message validation\n+ tests.\ndiff --git a/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni\nnew file mode 100644\nindex 00000000..d8a13874\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni\n@@ -0,0 +1,51 @@\n+# Copyright 2016 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+_typemap_imports = [\n+ \"//chrome/chrome_cleaner/mojom/typemaps/typemaps.gni\",\n+ \"//chrome/common/importer/typemaps.gni\",\n+ \"//chrome/common/media_router/mojom/typemaps.gni\",\n+ \"//chrome/typemaps.gni\",\n+ \"//chromecast/typemaps.gni\",\n+ \"//chromeos/typemaps.gni\",\n+ \"//chromeos/components/multidevice/mojom/typemaps.gni\",\n+ \"//chromeos/services/cros_healthd/public/mojom/typemaps.gni\",\n+ \"//chromeos/services/device_sync/public/mojom/typemaps.gni\",\n+ \"//chromeos/services/network_config/public/mojom/typemaps.gni\",\n+ \"//chromeos/services/secure_channel/public/mojom/typemaps.gni\",\n+ \"//components/arc/mojom/typemaps.gni\",\n+ \"//components/chromeos_camera/common/typemaps.gni\",\n+ \"//components/services/storage/public/cpp/filesystem/typemaps.gni\",\n+ \"//components/sync/mojom/typemaps.gni\",\n+ \"//components/typemaps.gni\",\n+ \"//content/browser/typemaps.gni\",\n+ \"//content/public/common/typemaps.gni\",\n+ \"//sandbox/mac/mojom/typemaps.gni\",\n+ \"//services/media_session/public/cpp/typemaps.gni\",\n+ \"//services/proxy_resolver/public/cpp/typemaps.gni\",\n+ \"//services/resource_coordinator/public/cpp/typemaps.gni\",\n+ \"//services/service_manager/public/cpp/typemaps.gni\",\n+ \"//services/tracing/public/mojom/typemaps.gni\",\n+]\n+\n+_typemaps = []\n+foreach(typemap_import, _typemap_imports) {\n+ # Avoid reassignment error by assigning to empty scope first.\n+ _imported = {\n+ }\n+ _imported = read_file(typemap_import, \"scope\")\n+ _typemaps += _imported.typemaps\n+}\n+\n+typemaps = []\n+foreach(typemap, _typemaps) {\n+ typemaps += [\n+ {\n+ filename = typemap\n+ config = read_file(typemap, \"scope\")\n+ },\n+ ]\n+}\n+\n+component_macro_suffix = \"\"\ndiff --git a/utils/ipc/mojo/public/tools/bindings/compile_typescript.py b/utils/ipc/mojo/public/tools/bindings/compile_typescript.py\nnew file mode 100644\nindex 00000000..a978901b\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/bindings/compile_typescript.py\n@@ -0,0 +1,27 @@\n+# Copyright 2019 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import os\n+import sys\n+import argparse\n+\n+_HERE_PATH = os.path.dirname(__file__)\n+_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))\n+\n+sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))\n+import node\n+import node_modules\n+\n+def main(argv):\n+ parser = argparse.ArgumentParser()\n+ parser.add_argument('--tsconfig_path', required=True)\n+ args = parser.parse_args(argv)\n+\n+ result = node.RunNode([node_modules.PathToTypescript()] +\n+ ['--project', args.tsconfig_path])\n+ if len(result) != 0:\n+ raise RuntimeError('Failed to compile Typescript: \\n%s' % result)\n+\n+if __name__ == '__main__':\n+ main(sys.argv[1:])\ndiff --git a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py\nnew file mode 100755\nindex 00000000..48bc66fd\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py\n@@ -0,0 +1,54 @@\n+#!/usr/bin/env python\n+# Copyright 2019 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+#\n+# This utility concatenates several files into one. On Unix-like systems\n+# it is equivalent to:\n+# cat file1 file2 file3 ...files... > target\n+#\n+# The reason for writing a separate utility is that 'cat' is not available\n+# on all supported build platforms, but Python is, and hence this provides\n+# us with an easy and uniform way of doing this on all platforms.\n+\n+# for py2/py3 compatibility\n+from __future__ import print_function\n+\n+import optparse\n+\n+\n+def Concatenate(filenames):\n+ \"\"\"Concatenate files.\n+\n+ Args:\n+ files: Array of file names.\n+ The last name is the target; all earlier ones are sources.\n+\n+ Returns:\n+ True, if the operation was successful.\n+ \"\"\"\n+ if len(filenames) < 2:\n+ print(\"An error occurred generating %s:\\nNothing to do.\" % filenames[-1])\n+ return False\n+\n+ try:\n+ with open(filenames[-1], \"wb\") as target:\n+ for filename in filenames[:-1]:\n+ with open(filename, \"rb\") as current:\n+ target.write(current.read())\n+ return True\n+ except IOError as e:\n+ print(\"An error occurred when writing %s:\\n%s\" % (filenames[-1], e))\n+ return False\n+\n+\n+def main():\n+ parser = optparse.OptionParser()\n+ parser.set_usage(\"\"\"Concatenate several files into one.\n+ Equivalent to: cat file1 ... > target.\"\"\")\n+ (_options, args) = parser.parse_args()\n+ exit(0 if Concatenate(args) else 1)\n+\n+\n+if __name__ == \"__main__\":\n+ main()\ndiff --git a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py b/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py\nnew file mode 100755\nindex 00000000..be8985ce\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py\n@@ -0,0 +1,73 @@\n+#!/usr/bin/env python\n+# Copyright 2018 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+\"\"\"Simple utility which concatenates a set of files into a single output file\n+while also stripping any goog.provide or goog.require lines. This allows us to\n+provide a very primitive sort of \"compilation\" without any extra toolchain\n+support and without having to modify otherwise compilable sources in the tree\n+which use these directives.\n+\n+goog.provide lines are replaced with an equivalent invocation of\n+mojo.internal.exportModule, which accomplishes essentially the same thing in an\n+uncompiled context. A singular exception is made for the 'mojo.internal' export,\n+which is instead replaced with an inlined assignment to initialize the\n+namespace.\n+\"\"\"\n+\n+from __future__ import print_function\n+\n+import optparse\n+import re\n+\n+\n+_MOJO_INTERNAL_MODULE_NAME = \"mojo.internal\"\n+_MOJO_EXPORT_MODULE_SYMBOL = \"mojo.internal.exportModule\"\n+\n+\n+def FilterLine(filename, line, output):\n+ if line.startswith(\"goog.require\"):\n+ return\n+\n+ if line.startswith(\"goog.provide\"):\n+ match = re.match(\"goog.provide\\('([^']+)'\\);\", line)\n+ if not match:\n+ print(\"Invalid goog.provide line in %s:\\n%s\" % (filename, line))\n+ exit(1)\n+\n+ module_name = match.group(1)\n+ if module_name == _MOJO_INTERNAL_MODULE_NAME:\n+ output.write(\"self.mojo = { internal: {} };\")\n+ else:\n+ output.write(\"%s('%s');\\n\" % (_MOJO_EXPORT_MODULE_SYMBOL, module_name))\n+ return\n+\n+ output.write(line)\n+\n+def ConcatenateAndReplaceExports(filenames):\n+ if (len(filenames) < 2):\n+ print(\"At least two filenames (one input and the output) are required.\")\n+ return False\n+\n+ try:\n+ with open(filenames[-1], \"w\") as target:\n+ for filename in filenames[:-1]:\n+ with open(filename, \"r\") as current:\n+ for line in current.readlines():\n+ FilterLine(filename, line, target)\n+ return True\n+ except IOError as e:\n+ print(\"Error generating %s\\n: %s\" % (filenames[-1], e))\n+ return False\n+\n+def main():\n+ parser = optparse.OptionParser()\n+ parser.set_usage(\"\"\"file1 [file2...] outfile\n+ Concatenate several files into one, stripping Closure provide and\n+ require directives along the way.\"\"\")\n+ (_, args) = parser.parse_args()\n+ exit(0 if ConcatenateAndReplaceExports(args) else 1)\n+\n+if __name__ == \"__main__\":\n+ main()\ndiff --git a/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py b/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py\nnew file mode 100755\nindex 00000000..7ac4af5f\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py\n@@ -0,0 +1,36 @@\n+#!/usr/bin/env python\n+# Copyright 2016 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+from __future__ import print_function\n+\n+import sys\n+\n+# This utility converts mojom dependencies into their corresponding typemap\n+# paths and formats them to be consumed by generate_type_mappings.py.\n+\n+\n+def FormatTypemap(typemap_filename):\n+ # A simple typemap is valid Python with a minor alteration.\n+ with open(typemap_filename) as f:\n+ typemap_content = f.read().replace('=\\n', '=')\n+ typemap = {}\n+ exec typemap_content in typemap\n+\n+ for header in typemap.get('public_headers', []):\n+ yield 'public_headers=%s' % header\n+ for header in typemap.get('traits_headers', []):\n+ yield 'traits_headers=%s' % header\n+ for header in typemap.get('type_mappings', []):\n+ yield 'type_mappings=%s' % header\n+\n+\n+def main():\n+ typemaps = sys.argv[1:]\n+ print(' '.join('--start-typemap %s' % ' '.join(FormatTypemap(typemap))\n+ for typemap in typemaps))\n+\n+\n+if __name__ == '__main__':\n+ sys.exit(main())\ndiff --git a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py\nnew file mode 100644\nindex 00000000..79c9e50e\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py\n@@ -0,0 +1,52 @@\n+# Copyright 2017 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\"\"\"Generates a list of all files in a directory.\n+\n+This script takes in a directory and an output file name as input.\n+It then reads the directory and creates a list of all file names\n+in that directory. The list is written to the output file.\n+There is also an option to pass in '-p' or '--pattern'\n+which will check each file name against a regular expression\n+pattern that is passed in. Only files which match the regex\n+will be written to the list.\n+\"\"\"\n+\n+from __future__ import print_function\n+\n+import os\n+import re\n+import sys\n+\n+from cStringIO import StringIO\n+from optparse import OptionParser\n+\n+sys.path.insert(\n+ 0,\n+ os.path.join(\n+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))), \"mojom\"))\n+\n+from mojom.generate.generator import WriteFile\n+\n+\n+def main():\n+ parser = OptionParser()\n+ parser.add_option('-d', '--directory', help='Read files from DIRECTORY')\n+ parser.add_option('-o', '--output', help='Write list to FILE')\n+ parser.add_option('-p',\n+ '--pattern',\n+ help='Only reads files that name matches PATTERN',\n+ default=\".\")\n+ (options, _) = parser.parse_args()\n+ pattern = re.compile(options.pattern)\n+ files = [f for f in os.listdir(options.directory) if pattern.match(f)]\n+\n+ stream = StringIO()\n+ for f in files:\n+ print(f, file=stream)\n+\n+ WriteFile(stream.getvalue(), options.output)\n+ stream.close()\n+\n+if __name__ == '__main__':\n+ sys.exit(main())\ndiff --git a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py\nnew file mode 100755\nindex 00000000..64ca048f\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py\n@@ -0,0 +1,187 @@\n+#!/usr/bin/env python\n+# Copyright 2016 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\"\"\"Generates a JSON typemap from its command-line arguments and dependencies.\n+\n+Each typemap should be specified in an command-line argument of the form\n+key=value, with an argument of \"--start-typemap\" preceding each typemap.\n+\n+For example,\n+generate_type_mappings.py --output=foo.typemap --start-typemap \\\\\n+ public_headers=foo.h traits_headers=foo_traits.h \\\\\n+ type_mappings=mojom.Foo=FooImpl\n+\n+generates a foo.typemap containing\n+{\n+ \"c++\": {\n+ \"mojom.Foo\": {\n+ \"typename\": \"FooImpl\",\n+ \"traits_headers\": [\n+ \"foo_traits.h\"\n+ ],\n+ \"public_headers\": [\n+ \"foo.h\"\n+ ]\n+ }\n+ }\n+}\n+\n+Then,\n+generate_type_mappings.py --dependency foo.typemap --output=bar.typemap \\\\\n+ --start-typemap public_headers=bar.h traits_headers=bar_traits.h \\\\\n+ type_mappings=mojom.Bar=BarImpl\n+\n+generates a bar.typemap containing\n+{\n+ \"c++\": {\n+ \"mojom.Bar\": {\n+ \"typename\": \"BarImpl\",\n+ \"traits_headers\": [\n+ \"bar_traits.h\"\n+ ],\n+ \"public_headers\": [\n+ \"bar.h\"\n+ ]\n+ },\n+ \"mojom.Foo\": {\n+ \"typename\": \"FooImpl\",\n+ \"traits_headers\": [\n+ \"foo_traits.h\"\n+ ],\n+ \"public_headers\": [\n+ \"foo.h\"\n+ ]\n+ }\n+ }\n+}\n+\"\"\"\n+\n+import argparse\n+import json\n+import os\n+import re\n+import sys\n+\n+sys.path.insert(\n+ 0,\n+ os.path.join(\n+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))), \"mojom\"))\n+\n+from mojom.generate.generator import WriteFile\n+\n+def ReadTypemap(path):\n+ with open(path) as f:\n+ return json.load(f)['c++']\n+\n+\n+def ParseTypemapArgs(args):\n+ typemaps = [s for s in '\\n'.join(args).split('--start-typemap\\n') if s]\n+ result = {}\n+ for typemap in typemaps:\n+ result.update(ParseTypemap(typemap))\n+ return result\n+\n+\n+def LoadCppTypemapConfig(path):\n+ configs = {}\n+ with open(path) as f:\n+ for config in json.load(f):\n+ for entry in config['types']:\n+ configs[entry['mojom']] = {\n+ 'typename': entry['cpp'],\n+ 'public_headers': config.get('traits_headers', []),\n+ 'traits_headers': config.get('traits_private_headers', []),\n+ 'copyable_pass_by_value': entry.get('copyable_pass_by_value',\n+ False),\n+ 'force_serialize': entry.get('force_serialize', False),\n+ 'hashable': entry.get('hashable', False),\n+ 'move_only': entry.get('move_only', False),\n+ 'nullable_is_same_type': entry.get('nullable_is_same_type', False),\n+ 'non_copyable_non_movable': False,\n+ }\n+ return configs\n+\n+\n+def ParseTypemap(typemap):\n+ values = {'type_mappings': [], 'public_headers': [], 'traits_headers': []}\n+ for line in typemap.split('\\n'):\n+ if not line:\n+ continue\n+ key, _, value = line.partition('=')\n+ values[key].append(value.lstrip('/'))\n+ result = {}\n+ mapping_pattern = \\\n+ re.compile(r\"\"\"^([^=]+) # mojom type\n+ =\n+ ([^[]+) # native type\n+ (?:\\[([^]]+)\\])?$ # optional attribute in square brackets\n+ \"\"\", re.X)\n+ for typename in values['type_mappings']:\n+ match_result = mapping_pattern.match(typename)\n+ assert match_result, (\n+ \"Cannot parse entry in the \\\"type_mappings\\\" section: %s\" % typename)\n+\n+ mojom_type = match_result.group(1)\n+ native_type = match_result.group(2)\n+ attributes = []\n+ if match_result.group(3):\n+ attributes = match_result.group(3).split(',')\n+\n+ assert mojom_type not in result, (\n+ \"Cannot map multiple native types (%s, %s) to the same mojom type: %s\" %\n+ (result[mojom_type]['typename'], native_type, mojom_type))\n+\n+ result[mojom_type] = {\n+ 'public_headers': values['public_headers'],\n+ 'traits_headers': values['traits_headers'],\n+ 'typename': native_type,\n+\n+ # Attributes supported for individual mappings.\n+ 'copyable_pass_by_value': 'copyable_pass_by_value' in attributes,\n+ 'force_serialize': 'force_serialize' in attributes,\n+ 'hashable': 'hashable' in attributes,\n+ 'move_only': 'move_only' in attributes,\n+ 'non_copyable_non_movable': 'non_copyable_non_movable' in attributes,\n+ 'nullable_is_same_type': 'nullable_is_same_type' in attributes,\n+ }\n+ return result\n+\n+\n+def main():\n+ parser = argparse.ArgumentParser(\n+ description=__doc__,\n+ formatter_class=argparse.RawDescriptionHelpFormatter)\n+ parser.add_argument(\n+ '--dependency',\n+ type=str,\n+ action='append',\n+ default=[],\n+ help=('A path to another JSON typemap to merge into the output. '\n+ 'This may be repeated to merge multiple typemaps.'))\n+ parser.add_argument(\n+ '--cpp-typemap-config',\n+ type=str,\n+ action='store',\n+ dest='cpp_config_path',\n+ help=('A path to a single JSON-formatted typemap config as emitted by'\n+ 'GN when processing a mojom_cpp_typemap build rule.'))\n+ parser.add_argument('--output',\n+ type=str,\n+ required=True,\n+ help='The path to which to write the generated JSON.')\n+ params, typemap_params = parser.parse_known_args()\n+ typemaps = ParseTypemapArgs(typemap_params)\n+ if params.cpp_config_path:\n+ typemaps.update(LoadCppTypemapConfig(params.cpp_config_path))\n+ missing = [path for path in params.dependency if not os.path.exists(path)]\n+ if missing:\n+ raise IOError('Missing dependencies: %s' % ', '.join(missing))\n+ for path in params.dependency:\n+ typemaps.update(ReadTypemap(path))\n+\n+ WriteFile(json.dumps({'c++': typemaps}, indent=2), params.output)\n+\n+\n+if __name__ == '__main__':\n+ main()\ndiff --git a/utils/ipc/mojo/public/tools/bindings/mojom.gni b/utils/ipc/mojo/public/tools/bindings/mojom.gni\nnew file mode 100644\nindex 00000000..a739fa6e\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/bindings/mojom.gni\n@@ -0,0 +1,1941 @@\n+# Copyright 2014 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import(\"//build/config/jumbo.gni\")\n+import(\"//third_party/closure_compiler/closure_args.gni\")\n+import(\"//third_party/closure_compiler/compile_js.gni\")\n+import(\"//third_party/protobuf/proto_library.gni\")\n+import(\"//ui/webui/webui_features.gni\")\n+\n+# TODO(rockot): Maybe we can factor these dependencies out of //mojo. They're\n+# used to conditionally enable message ID scrambling in a way which is\n+# consistent across toolchains and which is affected by branded vs non-branded\n+# Chrome builds. Ideally we could create some generic knobs here that could be\n+# flipped elsewhere though.\n+import(\"//build/config/chrome_build.gni\")\n+import(\"//build/config/chromecast_build.gni\")\n+import(\"//build/config/chromeos/ui_mode.gni\")\n+import(\"//build/config/nacl/config.gni\")\n+import(\"//build/toolchain/kythe.gni\")\n+import(\"//components/nacl/features.gni\")\n+import(\"//third_party/jinja2/jinja2.gni\")\n+import(\"//tools/ipc_fuzzer/ipc_fuzzer.gni\")\n+declare_args() {\n+ # Indicates whether typemapping should be supported in this build\n+ # configuration. This may be disabled when building external projects which\n+ # depend on //mojo but which do not need/want all of the Chromium tree\n+ # dependencies that come with typemapping.\n+ #\n+ # Note that (perhaps obviously) a huge amount of Chromium code will not build\n+ # with typemapping disabled, so it is never valid to set this to |false| in\n+ # any Chromium build configuration.\n+ enable_mojom_typemapping = true\n+\n+ # Controls message ID scrambling behavior. If |true|, message IDs are\n+ # scrambled (i.e. randomized based on the contents of //chrome/VERSION) on\n+ # non-Chrome OS desktop platforms. Set to |false| to disable message ID\n+ # scrambling on all platforms.\n+ enable_mojom_message_id_scrambling = true\n+\n+ # Enables Closure compilation of generated JS lite bindings. In environments\n+ # where compilation is supported, any mojom target \"foo\" will also have a\n+ # corresponding \"foo_js_library_for_compile\" target generated.\n+ enable_mojom_closure_compile = enable_js_type_check && optimize_webui\n+\n+ # Enables generating Typescript bindings and compiling them to JS bindings.\n+ enable_typescript_bindings = false\n+\n+ # Enables generating javascript fuzzing-related code and the bindings for the\n+ # MojoLPM fuzzer targets. Off by default.\n+ enable_mojom_fuzzer = false\n+}\n+\n+# NOTE: We would like to avoid scrambling message IDs where it doesn't add\n+# value, so we limit the behavior to desktop builds for now. There is some\n+# redundancy in the conditions here, but it is tolerated for clarity:\n+# We're explicit about Mac, Windows, and Linux desktop support, but it's\n+# also necessary to ensure that bindings in alternate toolchains (e.g.\n+# NaCl IRT) are always consistent with the default toolchain; for that\n+# reason we always enable scrambling within NaCl toolchains when possible,\n+# as well as within the default toolchain when NaCl is enabled.\n+#\n+# Finally, because we *cannot* enable scrambling on Chrome OS (it would break\n+# ARC) we have to explicitly opt out there even when NaCl is enabled (and\n+# consequently also when building for NaCl toolchains.) For this reason we\n+# check |target_os| explicitly, as it's consistent across all toolchains.\n+enable_scrambled_message_ids =\n+ enable_mojom_message_id_scrambling &&\n+ (is_mac || is_win || (is_linux && !is_chromeos && !is_chromecast &&\n+ !chromeos_is_browser_only) ||\n+ ((enable_nacl || is_nacl || is_nacl_nonsfi) &&\n+ (target_os != \"chromeos\" && !chromeos_is_browser_only)))\n+\n+_mojom_tools_root = \"//mojo/public/tools\"\n+_mojom_library_root = \"$_mojom_tools_root/mojom/mojom\"\n+mojom_parser_script = \"$_mojom_tools_root/mojom/mojom_parser.py\"\n+mojom_parser_sources = [\n+ \"$_mojom_library_root/__init__.py\",\n+ \"$_mojom_library_root/error.py\",\n+ \"$_mojom_library_root/generate/__init__.py\",\n+ \"$_mojom_library_root/generate/constant_resolver.py\",\n+ \"$_mojom_library_root/generate/generator.py\",\n+ \"$_mojom_library_root/generate/module.py\",\n+ \"$_mojom_library_root/generate/pack.py\",\n+ \"$_mojom_library_root/generate/template_expander.py\",\n+ \"$_mojom_library_root/generate/translate.py\",\n+ \"$_mojom_library_root/parse/__init__.py\",\n+ \"$_mojom_library_root/parse/ast.py\",\n+ \"$_mojom_library_root/parse/lexer.py\",\n+ \"$_mojom_library_root/parse/parser.py\",\n+]\n+\n+mojom_generator_root = \"$_mojom_tools_root/bindings\"\n+mojom_generator_script = \"$mojom_generator_root/mojom_bindings_generator.py\"\n+mojom_generator_sources =\n+ mojom_parser_sources + [\n+ \"$mojom_generator_root/generators/mojom_cpp_generator.py\",\n+ \"$mojom_generator_root/generators/mojom_java_generator.py\",\n+ \"$mojom_generator_root/generators/mojom_mojolpm_generator.py\",\n+ \"$mojom_generator_root/generators/mojom_js_generator.py\",\n+ \"$mojom_generator_root/generators/mojom_ts_generator.py\",\n+ \"$mojom_generator_script\",\n+ ]\n+\n+if (enable_scrambled_message_ids) {\n+ declare_args() {\n+ # The path to a file whose contents can be used as the basis for a message\n+ # ID scrambling salt.\n+ mojom_message_id_salt_path = \"//chrome/VERSION\"\n+\n+ # The path to a file whose contents will be concatenated to the contents of\n+ # the file at |mojom_message_id_salt_path| to form a complete salt for\n+ # message ID scrambling. May be the empty string, in which case the contents\n+ # of the above file alone are used as the complete salt.\n+ if (is_chrome_branded) {\n+ mojom_message_id_salt_suffix_path =\n+ \"//mojo/internal/chrome-message-id-salt-suffix\"\n+ } else {\n+ mojom_message_id_salt_suffix_path = \"\"\n+ }\n+ }\n+\n+ assert(mojom_message_id_salt_path != \"\")\n+ message_scrambling_args = [\n+ \"--scrambled_message_id_salt_path\",\n+ rebase_path(mojom_message_id_salt_path, root_build_dir),\n+ ]\n+ message_scrambling_inputs = [ mojom_message_id_salt_path ]\n+\n+ if (mojom_message_id_salt_suffix_path != \"\") {\n+ message_scrambling_args += [\n+ \"--scrambled_message_id_salt_path\",\n+ rebase_path(mojom_message_id_salt_suffix_path, root_build_dir),\n+ ]\n+ message_scrambling_inputs += [ mojom_message_id_salt_suffix_path ]\n+ }\n+} else {\n+ message_scrambling_args = []\n+ message_scrambling_inputs = []\n+}\n+\n+if (enable_mojom_typemapping) {\n+ _bindings_configuration_files =\n+ [ \"//mojo/public/tools/bindings/chromium_bindings_configuration.gni\" ]\n+ _bindings_configurations = []\n+ foreach(config_file, _bindings_configuration_files) {\n+ _bindings_configurations += [ read_file(config_file, \"scope\") ]\n+ }\n+ foreach(configuration, _bindings_configurations) {\n+ # Check that the mojom field of each typemap refers to a mojom that exists.\n+ foreach(typemap, configuration.typemaps) {\n+ _typemap_config = {\n+ }\n+ _typemap_config = typemap.config\n+ read_file(_typemap_config.mojom, \"\")\n+ }\n+ }\n+} else {\n+ _bindings_configuration_files = []\n+ _bindings_configurations = [\n+ {\n+ typemaps = []\n+ component_macro_suffix = \"\"\n+ },\n+ ]\n+}\n+\n+if (!is_ios) {\n+ _bindings_configurations += [\n+ {\n+ variant = \"blink\"\n+ component_macro_suffix = \"_BLINK\"\n+ for_blink = true\n+ typemaps = []\n+ },\n+ ]\n+}\n+\n+# Generates targets for building C++, JavaScript and Java bindings from mojom\n+# files. The output files will go under the generated file directory tree with\n+# the same path as each input file.\n+#\n+# Other targets should depend on one of these generated targets (where \"foo\"\n+# is the target name):\n+#\n+# foo\n+# C++ bindings.\n+#\n+# foo_blink\n+# C++ bindings using Blink standard types.\n+#\n+# foo_java\n+# Java bindings.\n+#\n+# foo_js\n+# JavaScript bindings; used as compile-time dependency.\n+#\n+# foo_js_data_deps\n+# JavaScript bindings; used as run-time dependency.\n+#\n+# Parameters:\n+#\n+# sources (optional if one of the deps sets listed below is present)\n+# List of source .mojom files to compile.\n+#\n+# deps (optional)\n+# Note: this can contain only other mojom targets.\n+#\n+# DEPRECATED: This is synonymous with public_deps because all mojom\n+# dependencies must be public by design. Please use public_deps.\n+#\n+# public_deps (optional)\n+# Note: this can contain only other mojom targets.\n+#\n+# parser_deps (optional)\n+# List of non-mojom targets required for the mojom sources to be parsed.\n+#\n+# import_dirs (optional)\n+# List of import directories that will get added when processing sources.\n+#\n+# testonly (optional)\n+#\n+# visibility (optional)\n+#\n+# visibility_blink (optional)\n+# The value to use for visibility for the blink variant. If unset,\n+# |visibility| is used.\n+#\n+# cpp_only (optional)\n+# If set to true, only the C++ bindings targets will be generated.\n+#\n+# NOTE: If the global |enable_mojom_fuzzer| build arg is true, JS bindings\n+# will still be generated even when |cpp_only| is set to |true|, unless\n+# you also set |enable_fuzzing| to |false| in your mojom target.\n+#\n+# cpp_typemaps (optional)\n+# A list of typemaps to be applied to the generated C++ bindings for this\n+# mojom target. Note that this only applies to the non-Blink variant of\n+# generated C++ bindings.\n+#\n+# Every typemap is a GN scope describing how one or more mojom types maps\n+# to a non-mojom C++ type, including necessary deps and headers required\n+# for the mapping to work. See the Typemaps section below.\n+#\n+# blink_cpp_typemaps (optional)\n+# Same as above, but for the Blink variant of generated C++ bindings.\n+#\n+# generate_java (optional)\n+# If set to true, Java bindings are generated for Android builds. If\n+# |cpp_only| is set to true, it overrides this to prevent generation of\n+# Java bindings.\n+#\n+# enable_fuzzing (optional)\n+# Enables generation of fuzzing sources for the target if the global build\n+# arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|. If\n+# fuzzing generation is enabled for a target, the target will always\n+# generate JS bindings even if |cpp_only| is set to |true|. See note\n+# above.\n+#\n+# support_lazy_serialization (optional)\n+# If set to |true|, generated C++ bindings will effectively prefer to\n+# transmit messages in an unserialized form when going between endpoints\n+# in the same process. This avoids the runtime cost of serialization,\n+# deserialization, and validation logic at the expensive of increased\n+# code size. Defaults to |false|.\n+#\n+# disable_variants (optional)\n+# If |true|, no variant sources will be generated for the target. Defaults\n+# to |false|.\n+#\n+# disallow_native_types (optional)\n+# If set to |true|, mojoms in this target may not apply the [Native]\n+# attribute to struct or enum declarations. This avoids emitting code\n+# which depends on legacy IPC serialization. Default is |false|, meaning\n+# [Native] types are allowed.\n+#\n+# disallow_interfaces (optional)\n+# If set to |true|, mojoms in this target may not define interfaces.\n+# Generates bindings with a smaller set of dependencies. Defaults to\n+# |false|.\n+#\n+# scramble_message_ids (optional)\n+# If set to |true| (the default), generated mojom interfaces will use\n+# scrambled ordinal identifiers in encoded messages.\n+#\n+# component_output_prefix (optional)\n+# The prefix to use for the output_name of any component library emitted\n+# for generated C++ bindings. If this is omitted, C++ bindings targets are\n+# emitted as source_sets instead. Because this controls the name of the\n+# output shared library binary in the root output directory, it must be\n+# unique across the entire build configuration.\n+#\n+# This is required if |component_macro_prefix| is specified.\n+#\n+# component_macro_prefix (optional)\n+# This specifies a macro prefix to use for component export macros and\n+# should therefore be globally unique in the project. For example if this\n+# is \"FOO_BAR\", then the generated C++ sources will be built with\n+# IS_FOO_BAR_{suffix}_IMPL defined, and the generated public headers will\n+# annotate public symbol definitions with\n+# COMPONENT_EXPORT(FOO_BAR_{suffix}). \"suffix\" in this case depends on\n+# which internal subtarget is generating the code (e.g. \"SHARED\", or a\n+# variant name like \"BLINK\").\n+#\n+# enabled_features (optional)\n+# Definitions in a mojom file can be guarded by an EnableIf attribute. If\n+# the value specified by the attribute does not match any items in the\n+# list of enabled_features, the definition will be disabled, with no code\n+# emitted for it.\n+#\n+# generate_closure_exports (optional)\n+# Generates JS lite bindings will use goog.provide and goog.require\n+# annotations to export its symbols and import core Mojo bindings support\n+# and other mojom dependency modules. Use this if you plan to compile your\n+# bindings into a larger JS binary. Defaults to |false|, instead\n+# generating JS lite bindings which assume they will be manually loaded in\n+# correct dependency order. Note that this only has an effect if\n+# the |enable_mojom_closure_compile| global arg is set to |true| as well.\n+#\n+# use_typescript_sources (optional)\n+# Uses the Typescript generator to generate JavaScript bindings.\n+#\n+# js_generate_struct_deserializers (optional)\n+# Generates JS deerialize methods for structs.\n+#\n+# extra_cpp_template_paths (optional)\n+# List of extra C++ templates that are used to generate additional source\n+# and/or header files. The templates should end with extension \".tmpl\".\n+#\n+# The following parameters are used to support the component build. They are\n+# needed so that bindings which are linked with a component can use the same\n+# export settings for classes. The first three are for the chromium variant, and\n+# the last three are for the blink variant. These parameters can also override\n+# |component_macro_prefix| for a specific variant, allowing e.g. one variant\n+# to be linked into a larger non-mojom component target, while all other\n+# variants get their own unique component target.\n+# export_class_attribute (optional)\n+# The attribute to add to the class declaration. e.g. \"CONTENT_EXPORT\"\n+# export_define (optional)\n+# A define to be added to the source_set which is needed by the export\n+# header. e.g. \"CONTENT_IMPLEMENTATION=1\"\n+# export_header (optional)\n+# A header to be added to the generated bindings to support the component\n+# build. e.g. \"content/common/content_export.h\"\n+# export_class_attribute_blink (optional)\n+# export_define_blink (optional)\n+# export_header_blink (optional)\n+# These three parameters are the blink variants of the previous 3.\n+#\n+# The following parameters are used to correct component build dependencies.\n+# They are needed so mojom-mojom dependencies follow the rule that dependencies\n+# on a source set in another component are replaced by a dependency on the\n+# containing component. The first two are for the chromium variant; the other\n+# two are for the blink variant.\n+# overridden_deps (optional)\n+# The list of mojom deps to be overridden.\n+# component_deps (optional)\n+# The list of component deps to add to replace overridden_deps.\n+# overridden_deps_blink (optional)\n+# component_deps_blink (optional)\n+# These two parameters are the blink variants of the previous two.\n+#\n+# check_includes_blink (optional)\n+# Overrides the check_includes variable for the blink variant.\n+# If check_includes_blink is not defined, the check_includes variable\n+# retains its original value.\n+#\n+# Typemaps\n+# ========\n+# The cpp_typemaps and blink_cpp_typemaps each specify an optional list of\n+# typemapping configurations. Each configuration is a GN scope with metadata\n+# describing what and how to map.\n+#\n+# Typemap scope parameters:\n+# types\n+# A list of type specifications for this typemap. Each type specification\n+# is a nested GN scope which can be expressed with the following syntax:\n+#\n+# {\n+# mojom = \"foo.mojom.Bar\"\n+# cpp = \"::foo::LegitBar\"\n+# move_only = true\n+# # etc...\n+# }\n+#\n+# Each type specification supports the following values:\n+#\n+# mojom (required)\n+# The fully qualified name of a mojom type to be mapped. This is a\n+# string like \"foo.mojom.Bar\".\n+#\n+# cpp (required)\n+# The fully qualified name of the C++ type to which the mojom type\n+# should be mapped in generated bindings. This is a string like\n+# \"::base::Value\" or \"std::vector<::base::Value>\".\n+#\n+# move_only (optional)\n+# A boolean value (default false) which indicates whether the C++\n+# type is move-only. If true, generated bindings will pass the type\n+# by value and use std::move() at call sites.\n+#\n+# copyable_pass_by_value (optional)\n+# A boolean value (default false) which effectively indicates\n+# whether the C++ type is very cheap to copy. If so, generated\n+# bindings will pass by value but not use std::move() at call sites.\n+#\n+# nullable_is_same_type (optional)\n+# A boolean value (default false) which indicates that the C++ type\n+# has some baked-in semantic notion of a \"null\" state. If true, the\n+# traits for the type must define IsNull and SetToNull methods.\n+#\n+# When false, nullable fields are represented by wrapping the C++\n+# type with base::Optional, and null values are simply\n+# base::nullopt.\n+#\n+# hashable (optional)\n+# A boolean value (default false) indicating whether the C++ type is\n+# hashable. Set to true if true AND needed (i.e. you need to use the\n+# type as the key of a mojom map).\n+#\n+# force_serialize (optional)\n+# A boolean value (default false) which disables lazy serialization\n+# of the typemapped type if lazy serialization is enabled for the\n+# mojom target applying this typemap.\n+#\n+# Additional typemap scope parameters:\n+#\n+# traits_headers (optional)\n+# Headers which must be included in the generated mojom in order for\n+# serialization to be possible. This generally means including at least\n+# the header for the corresponding mojom traits definitions.\n+#\n+# traits_private_headers (optional)\n+# Headers which must be included in generated C++ serialization code for\n+# a mojom using the typemap. This should be used only when including a\n+# header in |traits_headers| is problematic for compilation, as is\n+# sometimes the case with legacy IPC message headers.\n+#\n+# traits_sources (optional)\n+# The references to the source files (typically a single .cc and .h file)\n+# defining an appropriate set of EnumTraits or StructTraits, etc for the\n+# the type-mapping. Using this will cause the listed sources to be\n+# integrated directly into the dependent mojom's generated type-mapping\n+# targets.\n+#\n+# Prefer using |traits_public_deps| over inlined |traits_sources|, as this\n+# will generally lead to easier build maintenance over time.\n+#\n+# NOTE: If a typemap is shared by Blink and non-Blink bindings, you cannot\n+# use this and MUST use |traits_public_deps| to reference traits built\n+# within a separate target.\n+#\n+# traits_deps / traits_public_deps (optional)\n+# Any dependencies of sources in |traits_headers| or |traits_sources| must\n+# be listed here.\n+#\n+template(\"mojom\") {\n+ assert(\n+ defined(invoker.sources) || defined(invoker.deps) ||\n+ defined(invoker.public_deps),\n+ \"\\\"sources\\\" or \\\"deps\\\" must be defined for the $target_name template.\")\n+\n+ if (defined(invoker.export_class_attribute) ||\n+ defined(invoker.export_define) || defined(invoker.export_header)) {\n+ assert(defined(invoker.export_class_attribute))\n+ assert(defined(invoker.export_define))\n+ assert(defined(invoker.export_header))\n+ }\n+ if (defined(invoker.export_class_attribute_blink) ||\n+ defined(invoker.export_define_blink) ||\n+ defined(invoker.export_header_blink)) {\n+ assert(defined(invoker.export_class_attribute_blink))\n+ assert(defined(invoker.export_define_blink))\n+ assert(defined(invoker.export_header_blink))\n+\n+ # Not all platforms use the Blink variant, so make sure GN doesn't complain\n+ # about these values being inconsequential.\n+ not_needed(invoker,\n+ [\n+ \"export_class_attribute_blink\",\n+ \"export_define_blink\",\n+ \"export_header_blink\",\n+ ])\n+ }\n+ if (defined(invoker.overridden_deps) || defined(invoker.component_deps)) {\n+ assert(defined(invoker.overridden_deps))\n+ assert(defined(invoker.component_deps))\n+ }\n+\n+ if (defined(invoker.overridden_deps_blink) ||\n+ defined(invoker.component_deps_blink)) {\n+ assert(defined(invoker.overridden_deps_blink))\n+ assert(defined(invoker.component_deps_blink))\n+ }\n+\n+ # Type-mapping may be disabled or we may not generate C++ bindings.\n+ not_needed(invoker,\n+ [\n+ \"cpp_typemaps\",\n+ \"blink_cpp_typemaps\",\n+ ])\n+\n+ require_full_cpp_deps =\n+ !defined(invoker.disallow_native_types) ||\n+ !invoker.disallow_native_types || !defined(invoker.disallow_interfaces) ||\n+ !invoker.disallow_interfaces\n+\n+ all_deps = []\n+ if (defined(invoker.deps)) {\n+ all_deps += invoker.deps\n+ }\n+ if (defined(invoker.public_deps)) {\n+ all_deps += invoker.public_deps\n+ }\n+\n+ if (defined(invoker.component_macro_prefix)) {\n+ assert(defined(invoker.component_output_prefix))\n+ }\n+\n+ group(\"${target_name}__is_mojom\") {\n+ }\n+\n+ # Explicitly ensure that all dependencies (invoker.deps and\n+ # invoker.public_deps) are mojom targets.\n+ group(\"${target_name}__check_deps_are_all_mojom\") {\n+ deps = []\n+ foreach(d, all_deps) {\n+ name = get_label_info(d, \"label_no_toolchain\")\n+ toolchain = get_label_info(d, \"toolchain\")\n+ deps += [ \"${name}__is_mojom(${toolchain})\" ]\n+ }\n+ }\n+\n+ sources_list = []\n+ if (defined(invoker.sources)) {\n+ sources_list = invoker.sources\n+ }\n+\n+ # Reset sources_assignment_filter for the BUILD.gn file to prevent\n+ # regression during the migration of Chromium away from the feature.\n+ # See docs/no_sources_assignment_filter.md for more information.\n+ # TODO(crbug.com/1018739): remove this when migration is done.\n+ set_sources_assignment_filter([])\n+\n+ # Listed sources may be relative to the current target dir, or they may be\n+ # absolute paths, including paths to generated mojom files. While those are\n+ # fine as-is for input references, deriving output paths can be more subtle.\n+ #\n+ # Here we rewrite all source paths to be relative to the root build dir and\n+ # strip any root_gen_dir prefixes.\n+ #\n+ # So for a target in //foo/bar with:\n+ #\n+ # sources = [\n+ # \"a.mojom\",\n+ # \"b/c.mojom\",\n+ # \"//baz/d.mojom\",\n+ # \"$target_gen_dir/e.mojom\",\n+ # ]\n+ #\n+ # output_file_base_paths will be:\n+ #\n+ # [\n+ # \"foo/bar/a.mojom\",\n+ # \"foo/bar/b/c.mojom\",\n+ # \"baz/d.mojom\",\n+ # \"foo/bar/e.mojom\",\n+ # ]\n+ #\n+ # This result is essentially a list of base filename paths which are suitable\n+ # for the naming of any generated output files derived from their\n+ # corresponding input mojoms. These paths are always considered to be relative\n+ # to root_gen_dir.\n+ source_abspaths = rebase_path(sources_list, \"//\")\n+ output_file_base_paths = []\n+ foreach(path, source_abspaths) {\n+ output_file_base_paths +=\n+ [ string_replace(path, rebase_path(root_gen_dir, \"//\") + \"/\", \"\") ]\n+ }\n+\n+ # Sanity check that either all input files have a .mojom extension, or\n+ # all input files have a .test-mojom extension AND |testonly| is |true|.\n+ sources_list_filenames =\n+ process_file_template(sources_list, \"{{source_file_part}}\")\n+ sources_list_filenames_with_mojom_extension =\n+ process_file_template(sources_list, \"{{source_name_part}}.mojom\")\n+ if (sources_list_filenames != sources_list_filenames_with_mojom_extension) {\n+ sources_list_filenames_with_test_mojom_extension =\n+ process_file_template(sources_list, \"{{source_name_part}}.test-mojom\")\n+ if (sources_list_filenames ==\n+ sources_list_filenames_with_test_mojom_extension) {\n+ assert(\n+ defined(invoker.testonly) && invoker.testonly,\n+ \"mojom targets for .test-mojom files must set |testonly| to |true|\")\n+ } else {\n+ assert(\n+ false,\n+ \"One or more mojom files has an invalid extension. The only \" +\n+ \"allowed extensions are .mojom and .test-mojom, and any given \" +\n+ \"mojom target must use one or the other exclusively.\")\n+ }\n+ }\n+\n+ build_metadata_filename = \"$target_gen_dir/$target_name.build_metadata\"\n+ build_metadata = {\n+ }\n+ build_metadata.sources = rebase_path(sources_list)\n+ build_metadata.deps = []\n+ foreach(dep, all_deps) {\n+ dep_target_gen_dir = get_label_info(dep, \"target_gen_dir\")\n+ dep_name = get_label_info(dep, \"name\")\n+ build_metadata.deps +=\n+ [ rebase_path(\"$dep_target_gen_dir/$dep_name.build_metadata\") ]\n+ }\n+ write_file(build_metadata_filename, build_metadata, \"json\")\n+\n+ generate_fuzzing =\n+ (!defined(invoker.enable_fuzzing) || invoker.enable_fuzzing) &&\n+ enable_mojom_fuzzer && (!defined(invoker.testonly) || !invoker.testonly)\n+\n+ parser_target_name = \"${target_name}__parser\"\n+ parser_deps = []\n+ foreach(dep, all_deps) {\n+ _label = get_label_info(dep, \"label_no_toolchain\")\n+ parser_deps += [ \"${_label}__parser\" ]\n+ }\n+ if (defined(invoker.parser_deps)) {\n+ parser_deps += invoker.parser_deps\n+ }\n+ if (sources_list == []) {\n+ # Even without sources we generate a parser target to at least forward\n+ # other parser dependencies.\n+ group(parser_target_name) {\n+ public_deps = parser_deps\n+ }\n+ } else {\n+ enabled_features = []\n+ if (defined(invoker.enabled_features)) {\n+ enabled_features += invoker.enabled_features\n+ }\n+ if (is_posix) {\n+ enabled_features += [ \"is_posix\" ]\n+ }\n+ if (is_android) {\n+ enabled_features += [ \"is_android\" ]\n+ } else if (is_chromeos) {\n+ enabled_features += [ \"is_chromeos\" ]\n+ } else if (is_fuchsia) {\n+ enabled_features += [ \"is_fuchsia\" ]\n+ } else if (is_ios) {\n+ enabled_features += [ \"is_ios\" ]\n+ } else if (is_linux) {\n+ enabled_features += [ \"is_linux\" ]\n+ } else if (is_mac) {\n+ enabled_features += [ \"is_mac\" ]\n+ } else if (is_win) {\n+ enabled_features += [ \"is_win\" ]\n+ }\n+\n+ action(parser_target_name) {\n+ script = mojom_parser_script\n+ inputs = mojom_parser_sources + [ build_metadata_filename ]\n+ sources = sources_list\n+ deps = parser_deps\n+ outputs = []\n+ foreach(base_path, output_file_base_paths) {\n+ filename = get_path_info(base_path, \"file\")\n+ dirname = get_path_info(base_path, \"dir\")\n+ outputs += [ \"$root_gen_dir/$dirname/${filename}-module\" ]\n+ }\n+\n+ filelist = []\n+ foreach(source, sources_list) {\n+ filelist += [ rebase_path(source) ]\n+ }\n+ response_file_contents = filelist\n+\n+ args = [\n+ # Resolve relative input mojom paths against both the root src dir and\n+ # the root gen dir.\n+ \"--input-root\",\n+ rebase_path(\"//\"),\n+ \"--input-root\",\n+ rebase_path(root_gen_dir),\n+\n+ \"--output-root\",\n+ rebase_path(root_gen_dir),\n+\n+ \"--mojom-file-list={{response_file_name}}\",\n+\n+ \"--check-imports\",\n+ rebase_path(build_metadata_filename),\n+ ]\n+\n+ foreach(enabled_feature, enabled_features) {\n+ args += [\n+ \"--enable-feature\",\n+ enabled_feature,\n+ ]\n+ }\n+ }\n+ }\n+\n+ generator_cpp_message_ids_target_name = \"${target_name}__generate_message_ids\"\n+\n+ # Generate code that is shared by different variants.\n+ if (sources_list != []) {\n+ common_generator_args = [\n+ \"--use_bundled_pylibs\",\n+ \"-o\",\n+ rebase_path(root_gen_dir, root_build_dir),\n+ \"generate\",\n+ \"-d\",\n+ rebase_path(\"//\", root_build_dir),\n+ \"-I\",\n+ rebase_path(\"//\", root_build_dir),\n+ \"--bytecode_path\",\n+ rebase_path(\"$root_gen_dir/mojo/public/tools/bindings\", root_build_dir),\n+ ]\n+\n+ if (defined(invoker.disallow_native_types) &&\n+ invoker.disallow_native_types) {\n+ common_generator_args += [ \"--disallow_native_types\" ]\n+ }\n+\n+ if (defined(invoker.disallow_interfaces) && invoker.disallow_interfaces) {\n+ common_generator_args += [ \"--disallow_interfaces\" ]\n+ }\n+\n+ if (defined(invoker.import_dirs)) {\n+ foreach(import_dir, invoker.import_dirs) {\n+ common_generator_args += [\n+ \"-I\",\n+ rebase_path(import_dir, root_build_dir),\n+ ]\n+ }\n+ }\n+\n+ if (defined(invoker.component_macro_prefix)) {\n+ shared_component_export_macro =\n+ \"COMPONENT_EXPORT(${invoker.component_macro_prefix}_SHARED)\"\n+ shared_component_impl_macro =\n+ \"IS_${invoker.component_macro_prefix}_SHARED_IMPL\"\n+ shared_component_output_name = \"${invoker.component_output_prefix}_shared\"\n+ } else if (defined(invoker.export_class_attribute_shared) ||\n+ defined(invoker.export_class_attribute)) {\n+ if (defined(invoker.export_class_attribute_shared)) {\n+ assert(defined(invoker.export_header_shared))\n+ shared_component_export_macro = invoker.export_class_attribute_shared\n+ shared_component_impl_macro = invoker.export_define_shared\n+ } else {\n+ assert(!defined(invoker.export_header_shared))\n+\n+ # If no explicit shared attribute/define was provided by the invoker,\n+ # we derive some reasonable settings frorm the default variant.\n+ shared_component_export_macro = \"COMPONENT_EXPORT(MOJOM_SHARED_\" +\n+ invoker.export_class_attribute + \")\"\n+ shared_component_impl_macro =\n+ \"IS_MOJOM_SHARED_\" + invoker.export_class_attribute + \"_IMPL\"\n+ }\n+\n+ if (defined(invoker.component_output_prefix)) {\n+ shared_component_output_name =\n+ \"${invoker.component_output_prefix}_shared\"\n+ } else {\n+ shared_component_output_name = \"${target_name}_shared\"\n+ }\n+ }\n+\n+ action(generator_cpp_message_ids_target_name) {\n+ script = mojom_generator_script\n+ inputs = mojom_generator_sources + jinja2_sources\n+ sources = sources_list\n+ deps = [\n+ \":$parser_target_name\",\n+ \"//mojo/public/tools/bindings:precompile_templates\",\n+ ]\n+ if (defined(invoker.parser_deps)) {\n+ deps += invoker.parser_deps\n+ }\n+ outputs = []\n+ args = common_generator_args\n+ filelist = []\n+ foreach(source, sources_list) {\n+ filelist += [ rebase_path(\"$source\", root_build_dir) ]\n+ }\n+ foreach(base_path, output_file_base_paths) {\n+ outputs += [ \"$root_gen_dir/$base_path-shared-message-ids.h\" ]\n+ }\n+\n+ response_file_contents = filelist\n+\n+ args += [\n+ \"--filelist={{response_file_name}}\",\n+ \"--generate_non_variant_code\",\n+ \"--generate_message_ids\",\n+ \"-g\",\n+ \"c++\",\n+ ]\n+\n+ if (!defined(invoker.scramble_message_ids) ||\n+ invoker.scramble_message_ids) {\n+ inputs += message_scrambling_inputs\n+ args += message_scrambling_args\n+ }\n+ }\n+\n+ generator_shared_target_name = \"${target_name}_shared__generator\"\n+ action(generator_shared_target_name) {\n+ visibility = [ \":*\" ]\n+ script = mojom_generator_script\n+ inputs = mojom_generator_sources + jinja2_sources\n+ sources = sources_list\n+ deps = [\n+ \":$parser_target_name\",\n+ \"//mojo/public/tools/bindings:precompile_templates\",\n+ ]\n+ if (defined(invoker.parser_deps)) {\n+ deps += invoker.parser_deps\n+ }\n+\n+ outputs = []\n+ args = common_generator_args\n+ filelist = []\n+ foreach(source, sources_list) {\n+ filelist += [ rebase_path(\"$source\", root_build_dir) ]\n+ }\n+ foreach(base_path, output_file_base_paths) {\n+ outputs += [\n+ \"$root_gen_dir/$base_path-params-data.h\",\n+ \"$root_gen_dir/$base_path-shared-internal.h\",\n+ \"$root_gen_dir/$base_path-shared.cc\",\n+ \"$root_gen_dir/$base_path-shared.h\",\n+ ]\n+ }\n+\n+ response_file_contents = filelist\n+\n+ args += [\n+ \"--filelist={{response_file_name}}\",\n+ \"--generate_non_variant_code\",\n+ \"-g\",\n+ \"c++\",\n+ ]\n+\n+ if (defined(shared_component_export_macro)) {\n+ args += [\n+ \"--export_attribute\",\n+ shared_component_export_macro,\n+ \"--export_header\",\n+ \"base/component_export.h\",\n+ ]\n+ }\n+\n+ # Enable adding annotations to generated C++ headers that are used for\n+ # cross-references in CodeSearch.\n+ if (enable_kythe_annotations) {\n+ args += [ \"--enable_kythe_annotations\" ]\n+ }\n+ }\n+ } else {\n+ group(generator_cpp_message_ids_target_name) {\n+ }\n+ }\n+\n+ shared_cpp_sources_target_name = \"${target_name}_shared_cpp_sources\"\n+ jumbo_source_set(shared_cpp_sources_target_name) {\n+ if (defined(invoker.testonly)) {\n+ testonly = invoker.testonly\n+ }\n+ deps = []\n+ public_deps = []\n+ if (output_file_base_paths != []) {\n+ sources = []\n+ foreach(base_path, output_file_base_paths) {\n+ sources += [\n+ \"$root_gen_dir/$base_path-params-data.h\",\n+ \"$root_gen_dir/$base_path-shared-internal.h\",\n+ \"$root_gen_dir/$base_path-shared.cc\",\n+ \"$root_gen_dir/$base_path-shared.h\",\n+ ]\n+ }\n+ public_deps += [ \":$generator_shared_target_name\" ]\n+ }\n+ if (require_full_cpp_deps) {\n+ public_deps += [ \"//mojo/public/cpp/bindings\" ]\n+ } else {\n+ public_deps += [ \"//mojo/public/cpp/bindings:bindings_base\" ]\n+ }\n+ foreach(d, all_deps) {\n+ # Resolve the name, so that a target //mojo/something becomes\n+ # //mojo/something:something and we can append shared_cpp_sources_suffix\n+ # to get the cpp dependency name.\n+ full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n+ public_deps += [ \"${full_name}_shared\" ]\n+ }\n+ if (defined(shared_component_impl_macro)) {\n+ defines = [ shared_component_impl_macro ]\n+ }\n+ }\n+\n+ shared_cpp_library_target_name = \"${target_name}_shared\"\n+ if (defined(shared_component_output_name)) {\n+ component(shared_cpp_library_target_name) {\n+ if (defined(invoker.testonly)) {\n+ testonly = invoker.testonly\n+ }\n+ output_name = \"$shared_component_output_name\"\n+ public_deps = [ \":$shared_cpp_sources_target_name\" ]\n+ }\n+ } else {\n+ group(shared_cpp_library_target_name) {\n+ if (defined(invoker.testonly)) {\n+ testonly = invoker.testonly\n+ }\n+ public_deps = [ \":$shared_cpp_sources_target_name\" ]\n+ }\n+ }\n+\n+ if (generate_fuzzing) {\n+ # This block generates the proto files used for the MojoLPM fuzzer,\n+ # and the corresponding proto targets that will be linked in the fuzzer\n+ # targets. These are independent of the typemappings, and can be done\n+ # separately here.\n+\n+ generator_mojolpm_proto_target_name =\n+ \"${target_name}_mojolpm_proto_generator\"\n+ action(generator_mojolpm_proto_target_name) {\n+ script = mojom_generator_script\n+ inputs = mojom_generator_sources + jinja2_sources\n+ sources = invoker.sources\n+ deps = [\n+ \":$parser_target_name\",\n+ \"//mojo/public/tools/bindings:precompile_templates\",\n+ ]\n+\n+ outputs = []\n+ args = common_generator_args\n+ filelist = []\n+ foreach(source, invoker.sources) {\n+ filelist += [ rebase_path(\"$source\", root_build_dir) ]\n+ outputs += [ \"$target_gen_dir/$source.mojolpm.proto\" ]\n+ }\n+\n+ response_file_contents = filelist\n+\n+ args += [\n+ \"--filelist={{response_file_name}}\",\n+ \"--generate_non_variant_code\",\n+ \"-g\",\n+ \"mojolpm\",\n+ ]\n+ }\n+\n+ mojolpm_proto_target_name = \"${target_name}_mojolpm_proto\"\n+ if (defined(invoker.sources)) {\n+ proto_library(mojolpm_proto_target_name) {\n+ testonly = true\n+ generate_python = false\n+ sources = process_file_template(\n+ invoker.sources,\n+ [ \"{{source_gen_dir}}/{{source_file_part}}.mojolpm.proto\" ])\n+ import_dirs = [ \"${root_gen_dir}\" ]\n+ proto_in_dir = \"${root_gen_dir}\"\n+ proto_out_dir = \".\"\n+ proto_deps = [ \"//mojo/public/tools/fuzzers:mojolpm_proto_copy\" ]\n+ proto_deps += [ \":$generator_mojolpm_proto_target_name\" ]\n+ link_deps = [ \"//mojo/public/tools/fuzzers:mojolpm_proto\" ]\n+\n+ foreach(d, all_deps) {\n+ # Resolve the name, so that a target //mojo/something becomes\n+ # //mojo/something:something and we can append mojolpm_proto_suffix\n+ # to get the proto dependency name.\n+ full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n+ proto_deps += [ \"${full_name}_mojolpm_proto\" ]\n+ link_deps += [ \"${full_name}_mojolpm_proto\" ]\n+ }\n+ }\n+ } else {\n+ group(mojolpm_proto_target_name) {\n+ testonly = true\n+ public_deps = [ \"//mojo/public/tools/fuzzers:mojolpm_proto\" ]\n+ if (defined(generator_shared_target_name)) {\n+ public_deps += [ \":$generator_shared_target_name\" ]\n+ }\n+ foreach(d, all_deps) {\n+ # Resolve the name, so that a target //mojo/something becomes\n+ # //mojo/something:something and we can append #mojolpm_proto_suffix\n+ # to get the proto dependency name.\n+ full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n+ public_deps += [ \"${full_name}_mojolpm_proto\" ]\n+ }\n+ }\n+ }\n+ }\n+\n+ # Generate code for variants.\n+ if (!defined(invoker.disable_variants) || !invoker.disable_variants) {\n+ enabled_configurations = _bindings_configurations\n+ } else {\n+ first_config = _bindings_configurations[0]\n+ assert(!defined(first_config.variant))\n+ enabled_configurations = [ first_config ]\n+ }\n+ foreach(bindings_configuration, enabled_configurations) {\n+ cpp_only = false\n+ if (defined(invoker.cpp_only)) {\n+ cpp_only = invoker.cpp_only\n+ }\n+ variant_suffix = \"\"\n+ if (defined(bindings_configuration.variant)) {\n+ variant = bindings_configuration.variant\n+ variant_suffix = \"_${variant}\"\n+ cpp_only = true\n+ }\n+\n+ cpp_typemap_configs = []\n+ export_defines = []\n+ export_defines_overridden = false\n+ force_source_set = false\n+ if (defined(bindings_configuration.for_blink) &&\n+ bindings_configuration.for_blink) {\n+ if (defined(invoker.blink_cpp_typemaps)) {\n+ cpp_typemap_configs = invoker.blink_cpp_typemaps\n+ }\n+ if (defined(invoker.export_define_blink)) {\n+ export_defines_overridden = true\n+ export_defines = [ invoker.export_define_blink ]\n+ force_source_set = true\n+ }\n+ } else {\n+ if (defined(invoker.cpp_typemaps)) {\n+ cpp_typemap_configs = invoker.cpp_typemaps\n+ }\n+\n+ if (defined(invoker.export_define)) {\n+ export_defines_overridden = true\n+ export_defines = [ invoker.export_define ]\n+ force_source_set = true\n+ }\n+ }\n+ not_needed([ \"cpp_typemap_configs\" ])\n+\n+ if (!export_defines_overridden && defined(invoker.component_macro_prefix)) {\n+ output_name_override =\n+ \"${invoker.component_output_prefix}${variant_suffix}\"\n+ export_defines =\n+ [ \"IS_${invoker.component_macro_prefix}\" +\n+ \"${bindings_configuration.component_macro_suffix}_IMPL\" ]\n+ }\n+\n+ export_args = []\n+ export_args_overridden = false\n+ if (defined(bindings_configuration.for_blink) &&\n+ bindings_configuration.for_blink) {\n+ if (defined(invoker.export_class_attribute_blink)) {\n+ export_args_overridden = true\n+ export_args += [\n+ \"--export_attribute\",\n+ invoker.export_class_attribute_blink,\n+ \"--export_header\",\n+ invoker.export_header_blink,\n+ ]\n+ }\n+ } else if (defined(invoker.export_class_attribute)) {\n+ export_args_overridden = true\n+ export_args += [\n+ \"--export_attribute\",\n+ invoker.export_class_attribute,\n+ \"--export_header\",\n+ invoker.export_header,\n+ ]\n+ }\n+\n+ if (!export_args_overridden && defined(invoker.component_macro_prefix)) {\n+ export_args += [\n+ \"--export_attribute\",\n+ \"COMPONENT_EXPORT(${invoker.component_macro_prefix}\" +\n+ \"${bindings_configuration.component_macro_suffix})\",\n+ \"--export_header\",\n+ \"base/component_export.h\",\n+ ]\n+ }\n+\n+ generate_java = false\n+ if (!cpp_only && defined(invoker.generate_java)) {\n+ generate_java = invoker.generate_java\n+ }\n+ type_mappings_target_name = \"${target_name}${variant_suffix}__type_mappings\"\n+ type_mappings_path =\n+ \"$target_gen_dir/${target_name}${variant_suffix}__type_mappings\"\n+ active_typemaps = []\n+ if (sources_list != []) {\n+ generator_cpp_output_suffixes = []\n+ variant_dash_suffix = \"\"\n+ if (defined(variant)) {\n+ variant_dash_suffix = \"-${variant}\"\n+ }\n+ generator_cpp_output_suffixes += [\n+ \"${variant_dash_suffix}-forward.h\",\n+ \"${variant_dash_suffix}-import-headers.h\",\n+ \"${variant_dash_suffix}-test-utils.cc\",\n+ \"${variant_dash_suffix}-test-utils.h\",\n+ \"${variant_dash_suffix}.cc\",\n+ \"${variant_dash_suffix}.h\",\n+ ]\n+ foreach(source, sources_list) {\n+ # TODO(sammc): Use a map instead of a linear scan when GN supports maps.\n+ foreach(typemap, bindings_configuration.typemaps) {\n+ _typemap_config = {\n+ }\n+ _typemap_config = typemap.config\n+ if (get_path_info(source, \"abspath\") == _typemap_config.mojom) {\n+ active_typemaps += [ typemap ]\n+ }\n+ }\n+ }\n+\n+ generator_target_name = \"${target_name}${variant_suffix}__generator\"\n+ action(generator_target_name) {\n+ visibility = [ \":*\" ]\n+ script = mojom_generator_script\n+ inputs = mojom_generator_sources + jinja2_sources\n+ sources = sources_list\n+ deps = [\n+ \":$parser_target_name\",\n+ \":$type_mappings_target_name\",\n+ \"//mojo/public/tools/bindings:precompile_templates\",\n+ ]\n+ if (defined(invoker.parser_deps)) {\n+ deps += invoker.parser_deps\n+ }\n+ outputs = []\n+ args = common_generator_args + export_args\n+ filelist = []\n+ foreach(source, sources_list) {\n+ filelist += [ rebase_path(\"$source\", root_build_dir) ]\n+ }\n+ foreach(base_path, output_file_base_paths) {\n+ outputs += [\n+ \"$root_gen_dir/${base_path}${variant_dash_suffix}-forward.h\",\n+ \"$root_gen_dir/${base_path}${variant_dash_suffix}-import-headers.h\",\n+ \"$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.cc\",\n+ \"$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.h\",\n+ \"$root_gen_dir/${base_path}${variant_dash_suffix}.cc\",\n+ \"$root_gen_dir/${base_path}${variant_dash_suffix}.h\",\n+ ]\n+ if (generate_fuzzing && !defined(bindings_configuration.variant)) {\n+ outputs += [\n+ \"$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.cc\",\n+ \"$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.h\",\n+ ]\n+ }\n+ }\n+\n+ response_file_contents = filelist\n+\n+ args += [\n+ \"--filelist={{response_file_name}}\",\n+ \"-g\",\n+ ]\n+\n+ if (generate_fuzzing && !defined(bindings_configuration.variant)) {\n+ args += [ \"c++,mojolpm\" ]\n+ } else {\n+ args += [ \"c++\" ]\n+ }\n+\n+ if (defined(bindings_configuration.variant)) {\n+ args += [\n+ \"--variant\",\n+ bindings_configuration.variant,\n+ ]\n+ }\n+\n+ args += [\n+ \"--typemap\",\n+ rebase_path(type_mappings_path, root_build_dir),\n+ ]\n+\n+ if (defined(bindings_configuration.for_blink) &&\n+ bindings_configuration.for_blink) {\n+ args += [ \"--for_blink\" ]\n+ }\n+\n+ if (defined(invoker.support_lazy_serialization) &&\n+ invoker.support_lazy_serialization) {\n+ args += [ \"--support_lazy_serialization\" ]\n+ }\n+\n+ if (enable_kythe_annotations) {\n+ args += [ \"--enable_kythe_annotations\" ]\n+ }\n+\n+ if (!defined(invoker.scramble_message_ids) ||\n+ invoker.scramble_message_ids) {\n+ inputs += message_scrambling_inputs\n+ args += message_scrambling_args\n+ }\n+\n+ if (defined(invoker.extra_cpp_template_paths)) {\n+ foreach(extra_cpp_template, invoker.extra_cpp_template_paths) {\n+ args += [\n+ \"--extra_cpp_template_paths\",\n+ rebase_path(extra_cpp_template, root_build_dir),\n+ ]\n+ assert(\n+ get_path_info(extra_cpp_template, \"extension\") == \"tmpl\",\n+ \"--extra_cpp_template_paths only accepts template files ending in extension .tmpl\")\n+ foreach(base_path, output_file_base_paths) {\n+ template_file_name = get_path_info(\"$extra_cpp_template\", \"name\")\n+ outputs += [ \"$root_gen_dir/${base_path}${variant_dash_suffix}-${template_file_name}\" ]\n+ }\n+ }\n+ }\n+ }\n+ }\n+\n+ if (generate_fuzzing && !defined(variant)) {\n+ # This block contains the C++ targets for the MojoLPM fuzzer, we need to\n+ # do this here so that we can use the typemap configuration for the\n+ # empty-variant Mojo target.\n+\n+ mojolpm_target_name = \"${target_name}_mojolpm\"\n+ mojolpm_generator_target_name = \"${target_name}__generator\"\n+ source_set(mojolpm_target_name) {\n+ # There are still a few missing header dependencies between mojo targets\n+ # with typemaps and the dependencies of their typemap headers. It would\n+ # be good to enable include checking for these in the future though.\n+ check_includes = false\n+ testonly = true\n+ if (defined(invoker.sources)) {\n+ sources = process_file_template(\n+ invoker.sources,\n+ [\n+ \"{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc\",\n+ \"{{source_gen_dir}}/{{source_file_part}}-mojolpm.h\",\n+ ])\n+ deps = []\n+ } else {\n+ sources = []\n+ deps = []\n+ }\n+\n+ public_deps = [\n+ \":$generator_shared_target_name\",\n+\n+ # NB: hardcoded dependency on the no-variant variant generator, since\n+ # mojolpm only uses the no-variant type.\n+ \":$mojolpm_generator_target_name\",\n+ \":$mojolpm_proto_target_name\",\n+ \"//mojo/public/tools/fuzzers:mojolpm\",\n+ ]\n+\n+ foreach(d, all_deps) {\n+ # Resolve the name, so that a target //mojo/something becomes\n+ # //mojo/something:something and we can append variant_suffix to\n+ # get the cpp dependency name.\n+ full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n+ public_deps += [ \"${full_name}_mojolpm\" ]\n+ }\n+\n+ foreach(typemap, active_typemaps) {\n+ _typemap_config = {\n+ }\n+ _typemap_config = typemap.config\n+\n+ if (defined(_typemap_config.deps)) {\n+ deps += _typemap_config.deps\n+ }\n+ if (defined(_typemap_config.public_deps)) {\n+ public_deps += _typemap_config.public_deps\n+ }\n+ }\n+ foreach(config, cpp_typemap_configs) {\n+ if (defined(config.traits_deps)) {\n+ deps += config.traits_deps\n+ }\n+ if (defined(config.traits_public_deps)) {\n+ public_deps += config.traits_public_deps\n+ }\n+ }\n+ }\n+ }\n+\n+ # Write the typemapping configuration for this target out to a file to be\n+ # validated by a Python script. This helps catch mistakes that can't\n+ # be caught by logic in GN.\n+ _typemap_config_filename =\n+ \"$target_gen_dir/${target_name}${variant_suffix}.typemap_config\"\n+ _typemap_stamp_filename = \"${_typemap_config_filename}.validated\"\n+ _typemap_validator_target_name = \"${type_mappings_target_name}__validator\"\n+ _rebased_typemap_configs = []\n+ foreach(config, cpp_typemap_configs) {\n+ _rebased_config = {\n+ }\n+ _rebased_config = config\n+ if (defined(config.traits_headers)) {\n+ _rebased_config.traits_headers = []\n+ _rebased_config.traits_headers =\n+ rebase_path(config.traits_headers, \"//\")\n+ }\n+ if (defined(config.traits_private_headers)) {\n+ _rebased_config.traits_private_headers = []\n+ _rebased_config.traits_private_headers =\n+ rebase_path(config.traits_private_headers, \"//\")\n+ }\n+ _rebased_typemap_configs += [ _rebased_config ]\n+ }\n+ write_file(_typemap_config_filename, _rebased_typemap_configs, \"json\")\n+ _mojom_target_name = target_name\n+ action(_typemap_validator_target_name) {\n+ script = \"$mojom_generator_root/validate_typemap_config.py\"\n+ inputs = [ _typemap_config_filename ]\n+ outputs = [ _typemap_stamp_filename ]\n+ args = [\n+ get_label_info(_mojom_target_name, \"label_no_toolchain\"),\n+ rebase_path(_typemap_config_filename),\n+ rebase_path(_typemap_stamp_filename),\n+ ]\n+ }\n+\n+ action(type_mappings_target_name) {\n+ inputs = _bindings_configuration_files + mojom_generator_sources +\n+ jinja2_sources + [ _typemap_stamp_filename ]\n+ outputs = [ type_mappings_path ]\n+ script = \"$mojom_generator_root/generate_type_mappings.py\"\n+ deps = [ \":$_typemap_validator_target_name\" ]\n+ args = [\n+ \"--output\",\n+ rebase_path(type_mappings_path, root_build_dir),\n+ ]\n+\n+ foreach(d, all_deps) {\n+ name = get_label_info(d, \"label_no_toolchain\")\n+ toolchain = get_label_info(d, \"toolchain\")\n+ dependency_output = \"${name}${variant_suffix}__type_mappings\"\n+ dependency_target = \"${dependency_output}(${toolchain})\"\n+ deps += [ dependency_target ]\n+ dependency_output_dir =\n+ get_label_info(dependency_output, \"target_gen_dir\")\n+ dependency_name = get_label_info(dependency_output, \"name\")\n+ dependency_path =\n+ rebase_path(\"$dependency_output_dir/${dependency_name}\",\n+ root_build_dir)\n+ args += [\n+ \"--dependency\",\n+ dependency_path,\n+ ]\n+ }\n+\n+ if (sources_list != []) {\n+ # TODO(sammc): Pass the typemap description in a file to avoid command\n+ # line length limitations.\n+ typemap_description = []\n+ foreach(typemap, active_typemaps) {\n+ _typemap_config = {\n+ }\n+ _typemap_config = typemap.config\n+\n+ typemap_description += [ \"--start-typemap\" ]\n+ if (defined(_typemap_config.public_headers)) {\n+ foreach(value, _typemap_config.public_headers) {\n+ typemap_description += [ \"public_headers=$value\" ]\n+ }\n+ }\n+ if (defined(_typemap_config.traits_headers)) {\n+ foreach(value, _typemap_config.traits_headers) {\n+ typemap_description += [ \"traits_headers=$value\" ]\n+ }\n+ }\n+ foreach(value, _typemap_config.type_mappings) {\n+ typemap_description += [ \"type_mappings=$value\" ]\n+ }\n+\n+ # The typemap configuration files are not actually used as inputs here\n+ # but this establishes a necessary build dependency to ensure that\n+ # typemap changes force a rebuild of affected targets.\n+ if (defined(typemap.filename)) {\n+ inputs += [ typemap.filename ]\n+ }\n+ }\n+ args += typemap_description\n+\n+ # Newer GN-based typemaps are aggregated into a single config.\n+ inputs += [ _typemap_config_filename ]\n+ args += [\n+ \"--cpp-typemap-config\",\n+ rebase_path(_typemap_config_filename, root_build_dir),\n+ ]\n+ }\n+ }\n+\n+ group(\"${target_name}${variant_suffix}_headers\") {\n+ public_deps = []\n+ if (sources_list != []) {\n+ public_deps += [\n+ \":$generator_cpp_message_ids_target_name\",\n+ \":$generator_shared_target_name\",\n+ \":$generator_target_name\",\n+ ]\n+ }\n+ foreach(d, all_deps) {\n+ full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n+ public_deps += [ \"${full_name}${variant_suffix}_headers\" ]\n+ }\n+ }\n+\n+ if (!force_source_set && defined(invoker.component_macro_prefix)) {\n+ output_target_type = \"component\"\n+ } else {\n+ output_target_type = \"source_set\"\n+ }\n+\n+ js_data_deps_target_name = target_name + \"_js_data_deps\"\n+ not_needed([ \"js_data_deps_target_name\" ])\n+\n+ target(\"jumbo_\" + output_target_type, \"${target_name}${variant_suffix}\") {\n+ if (defined(output_name_override)) {\n+ output_name = output_name_override\n+ }\n+ if (defined(bindings_configuration.for_blink) &&\n+ bindings_configuration.for_blink &&\n+ defined(invoker.visibility_blink)) {\n+ visibility = invoker.visibility_blink\n+ } else if (defined(invoker.visibility)) {\n+ visibility = invoker.visibility\n+ }\n+ if (defined(invoker.testonly)) {\n+ testonly = invoker.testonly\n+ }\n+ defines = export_defines\n+ if (output_file_base_paths != []) {\n+ sources = []\n+ foreach(base_path, output_file_base_paths) {\n+ foreach(suffix, generator_cpp_output_suffixes) {\n+ sources += [ \"$root_gen_dir/${base_path}$suffix\" ]\n+ }\n+ }\n+ }\n+ deps = [\n+ \":$generator_cpp_message_ids_target_name\",\n+ \"//mojo/public/cpp/bindings:struct_traits\",\n+ \"//mojo/public/interfaces/bindings:bindings_headers\",\n+ ]\n+ public_deps = [\n+ \":$shared_cpp_library_target_name\",\n+ \"//base\",\n+ ]\n+ if (require_full_cpp_deps) {\n+ public_deps += [ \"//mojo/public/cpp/bindings\" ]\n+ } else {\n+ public_deps += [ \"//mojo/public/cpp/bindings:bindings_base\" ]\n+ }\n+\n+ if (sources_list != []) {\n+ public_deps += [ \":$generator_target_name\" ]\n+ }\n+ foreach(d, all_deps) {\n+ # Resolve the name, so that a target //mojo/something becomes\n+ # //mojo/something:something and we can append variant_suffix to\n+ # get the cpp dependency name.\n+ full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n+ public_deps += [ \"${full_name}${variant_suffix}\" ]\n+ }\n+ if (defined(bindings_configuration.for_blink) &&\n+ bindings_configuration.for_blink) {\n+ if (defined(invoker.overridden_deps_blink)) {\n+ foreach(d, invoker.overridden_deps_blink) {\n+ # Resolve the name, so that a target //mojo/something becomes\n+ # //mojo/something:something and we can append variant_suffix\n+ # to get the cpp dependency name.\n+ full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n+ public_deps -= [ \"${full_name}${variant_suffix}\" ]\n+ }\n+ public_deps += invoker.component_deps_blink\n+ }\n+ if (defined(invoker.check_includes_blink)) {\n+ check_includes = invoker.check_includes_blink\n+ }\n+ } else {\n+ if (defined(invoker.check_includes_blink)) {\n+ not_needed(invoker, [ \"check_includes_blink\" ])\n+ }\n+ if (defined(invoker.overridden_deps)) {\n+ foreach(d, invoker.overridden_deps) {\n+ # Resolve the name, so that a target //mojo/something becomes\n+ # //mojo/something:something and we can append variant_suffix\n+ # to get the cpp dependency name.\n+ full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n+ public_deps -= [ \"${full_name}${variant_suffix}\" ]\n+ }\n+ public_deps += invoker.component_deps\n+ }\n+ }\n+ foreach(typemap, active_typemaps) {\n+ _typemap_config = {\n+ }\n+ _typemap_config = typemap.config\n+ if (defined(_typemap_config.sources)) {\n+ sources += _typemap_config.sources\n+ }\n+ if (defined(_typemap_config.public_deps)) {\n+ public_deps += _typemap_config.public_deps\n+ }\n+ if (defined(_typemap_config.deps)) {\n+ deps += _typemap_config.deps\n+ }\n+ }\n+ foreach(config, cpp_typemap_configs) {\n+ if (defined(config.traits_sources)) {\n+ sources += config.traits_sources\n+ }\n+ if (defined(config.traits_deps)) {\n+ deps += config.traits_deps\n+ }\n+ if (defined(config.traits_public_deps)) {\n+ public_deps += config.traits_public_deps\n+ }\n+ }\n+ if (defined(invoker.export_header)) {\n+ sources += [ \"//\" + invoker.export_header ]\n+ }\n+ if (defined(bindings_configuration.for_blink) &&\n+ bindings_configuration.for_blink) {\n+ public_deps += [ \"//mojo/public/cpp/bindings:wtf_support\" ]\n+ }\n+\n+ if (generate_fuzzing) {\n+ # Generate JS bindings by default if IPC fuzzer is enabled.\n+ public_deps += [ \":$js_data_deps_target_name\" ]\n+ }\n+ }\n+\n+ if (generate_java && is_android) {\n+ import(\"//build/config/android/rules.gni\")\n+\n+ java_generator_target_name = target_name + \"_java__generator\"\n+ if (sources_list != []) {\n+ action(java_generator_target_name) {\n+ script = mojom_generator_script\n+ inputs = mojom_generator_sources + jinja2_sources\n+ sources = sources_list\n+ deps = [\n+ \":$parser_target_name\",\n+ \":$type_mappings_target_name\",\n+ \"//mojo/public/tools/bindings:precompile_templates\",\n+ ]\n+ outputs = []\n+ args = common_generator_args\n+ filelist = []\n+ foreach(source, sources_list) {\n+ filelist += [ rebase_path(\"$source\", root_build_dir) ]\n+ }\n+ foreach(base_path, output_file_base_paths) {\n+ outputs += [ \"$root_gen_dir/$base_path.srcjar\" ]\n+ }\n+\n+ response_file_contents = filelist\n+\n+ args += [\n+ \"--filelist={{response_file_name}}\",\n+ \"-g\",\n+ \"java\",\n+ ]\n+\n+ if (!defined(invoker.scramble_message_ids) ||\n+ invoker.scramble_message_ids) {\n+ inputs += message_scrambling_inputs\n+ args += message_scrambling_args\n+ }\n+ }\n+ } else {\n+ group(java_generator_target_name) {\n+ }\n+ }\n+\n+ java_srcjar_target_name = target_name + \"_java_sources\"\n+ action(java_srcjar_target_name) {\n+ script = \"//build/android/gyp/zip.py\"\n+ inputs = []\n+ if (output_file_base_paths != []) {\n+ foreach(base_path, output_file_base_paths) {\n+ inputs += [ \"$root_gen_dir/${base_path}.srcjar\" ]\n+ }\n+ }\n+ output = \"$target_gen_dir/$target_name.srcjar\"\n+ outputs = [ output ]\n+ rebase_inputs = rebase_path(inputs, root_build_dir)\n+ rebase_output = rebase_path(output, root_build_dir)\n+ args = [\n+ \"--input-zips=$rebase_inputs\",\n+ \"--output=$rebase_output\",\n+ ]\n+ deps = []\n+ if (sources_list != []) {\n+ deps = [ \":$java_generator_target_name\" ]\n+ }\n+ }\n+\n+ java_target_name = target_name + \"_java\"\n+ android_library(java_target_name) {\n+ forward_variables_from(invoker, [ \"enable_bytecode_checks\" ])\n+ deps = [\n+ \"//base:base_java\",\n+ \"//mojo/public/java:bindings_java\",\n+ \"//mojo/public/java:system_java\",\n+ ]\n+\n+ # Disable warnings/checks on these generated files.\n+ chromium_code = false\n+\n+ foreach(d, all_deps) {\n+ # Resolve the name, so that a target //mojo/something becomes\n+ # //mojo/something:something and we can append \"_java\" to get the java\n+ # dependency name.\n+ full_name = get_label_info(d, \"label_no_toolchain\")\n+ deps += [ \"${full_name}_java\" ]\n+ }\n+\n+ srcjar_deps = [ \":$java_srcjar_target_name\" ]\n+ }\n+ }\n+ }\n+\n+ use_typescript_for_target =\n+ enable_typescript_bindings && defined(invoker.use_typescript_sources) &&\n+ invoker.use_typescript_sources\n+\n+ if (!use_typescript_for_target && defined(invoker.use_typescript_sources)) {\n+ not_needed(invoker, [ \"use_typescript_sources\" ])\n+ }\n+\n+ if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&\n+ !use_typescript_for_target) {\n+ if (sources_list != []) {\n+ generator_js_target_name = \"${target_name}_js__generator\"\n+ action(generator_js_target_name) {\n+ script = mojom_generator_script\n+ inputs = mojom_generator_sources + jinja2_sources\n+ sources = sources_list\n+ deps = [\n+ \":$parser_target_name\",\n+ \"//mojo/public/tools/bindings:precompile_templates\",\n+ ]\n+ if (defined(invoker.parser_deps)) {\n+ deps += invoker.parser_deps\n+ }\n+ outputs = []\n+ args = common_generator_args\n+ filelist = []\n+ foreach(source, sources_list) {\n+ filelist += [ rebase_path(\"$source\", root_build_dir) ]\n+ }\n+ foreach(base_path, output_file_base_paths) {\n+ outputs += [\n+ \"$root_gen_dir/$base_path.js\",\n+ \"$root_gen_dir/$base_path.externs.js\",\n+ \"$root_gen_dir/$base_path-lite.js\",\n+ \"$root_gen_dir/$base_path.html\",\n+ \"$root_gen_dir/$base_path-lite-for-compile.js\",\n+ ]\n+ }\n+\n+ response_file_contents = filelist\n+\n+ args += [\n+ \"--filelist={{response_file_name}}\",\n+ \"-g\",\n+ \"javascript\",\n+ \"--js_bindings_mode=new\",\n+ ]\n+\n+ if (defined(invoker.js_generate_struct_deserializers) &&\n+ invoker.js_generate_struct_deserializers) {\n+ args += [ \"--js_generate_struct_deserializers\" ]\n+ }\n+\n+ if (!defined(invoker.scramble_message_ids) ||\n+ invoker.scramble_message_ids) {\n+ inputs += message_scrambling_inputs\n+ args += message_scrambling_args\n+ }\n+\n+ if (generate_fuzzing) {\n+ args += [ \"--generate_fuzzing\" ]\n+ }\n+ }\n+ }\n+\n+ js_target_name = target_name + \"_js\"\n+ group(js_target_name) {\n+ public_deps = []\n+ if (sources_list != []) {\n+ public_deps += [ \":$generator_js_target_name\" ]\n+ }\n+\n+ foreach(d, all_deps) {\n+ full_name = get_label_info(d, \"label_no_toolchain\")\n+ public_deps += [ \"${full_name}_js\" ]\n+ }\n+ }\n+\n+ group(js_data_deps_target_name) {\n+ deps = []\n+ if (sources_list != []) {\n+ data = []\n+ foreach(base_path, output_file_base_paths) {\n+ data += [\n+ \"$root_gen_dir/${base_path}.js\",\n+ \"$root_gen_dir/${base_path}-lite.js\",\n+ ]\n+ }\n+ deps += [ \":$generator_js_target_name\" ]\n+ }\n+\n+ data_deps = []\n+ foreach(d, all_deps) {\n+ full_name = get_label_info(d, \"label_no_toolchain\")\n+ data_deps += [ \"${full_name}_js_data_deps\" ]\n+ }\n+ }\n+\n+ js_library_target_name = \"${target_name}_js_library\"\n+ if (sources_list != []) {\n+ js_library(js_library_target_name) {\n+ extra_public_deps = [ \":$generator_js_target_name\" ]\n+ sources = []\n+ foreach(base_path, output_file_base_paths) {\n+ sources += [ \"$root_gen_dir/${base_path}-lite.js\" ]\n+ }\n+ externs_list = [\n+ \"${externs_path}/mojo_core.js\",\n+ \"${externs_path}/pending.js\",\n+ ]\n+\n+ deps = []\n+ foreach(d, all_deps) {\n+ full_name = get_label_info(d, \"label_no_toolchain\")\n+ deps += [ \"${full_name}_js_library\" ]\n+ }\n+ }\n+ } else {\n+ group(js_library_target_name) {\n+ }\n+ }\n+\n+ js_library_for_compile_target_name = \"${target_name}_js_library_for_compile\"\n+ if (sources_list != []) {\n+ js_library(js_library_for_compile_target_name) {\n+ extra_public_deps = [ \":$generator_js_target_name\" ]\n+ sources = []\n+ foreach(base_path, output_file_base_paths) {\n+ sources += [ \"$root_gen_dir/${base_path}-lite-for-compile.js\" ]\n+ }\n+ externs_list = [\n+ \"${externs_path}/mojo_core.js\",\n+ \"${externs_path}/pending.js\",\n+ ]\n+ deps = []\n+ if (!defined(invoker.disallow_native_types)) {\n+ deps += [ \"//mojo/public/js:bindings_lite_sources\" ]\n+ }\n+ foreach(d, all_deps) {\n+ full_name = get_label_info(d, \"label_no_toolchain\")\n+ deps += [ \"${full_name}_js_library_for_compile\" ]\n+ }\n+ }\n+ } else {\n+ group(js_library_for_compile_target_name) {\n+ }\n+ }\n+ }\n+ if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&\n+ use_typescript_for_target) {\n+ generator_js_target_names = []\n+ source_filelist = []\n+ foreach(source, sources_list) {\n+ source_filelist += [ rebase_path(\"$source\", root_build_dir) ]\n+ }\n+\n+ dependency_types = [\n+ {\n+ name = \"regular\"\n+ ts_extension = \".ts\"\n+ js_extension = \".js\"\n+ },\n+ {\n+ name = \"es_modules\"\n+ ts_extension = \".m.ts\"\n+ js_extension = \".m.js\"\n+ },\n+ ]\n+\n+ foreach(dependency_type, dependency_types) {\n+ ts_outputs = []\n+ js_outputs = []\n+\n+ foreach(base_path, output_file_base_paths) {\n+ ts_outputs +=\n+ [ \"$root_gen_dir/$base_path-lite${dependency_type.ts_extension}\" ]\n+ js_outputs +=\n+ [ \"$root_gen_dir/$base_path-lite${dependency_type.js_extension}\" ]\n+ }\n+\n+ # Generate Typescript bindings.\n+ generator_ts_target_name =\n+ \"${target_name}_${dependency_type.name}__ts__generator\"\n+ action(generator_ts_target_name) {\n+ script = mojom_generator_script\n+ inputs = mojom_generator_sources + jinja2_sources\n+ sources = sources_list\n+ deps = [\n+ \":$parser_target_name\",\n+ \"//mojo/public/tools/bindings:precompile_templates\",\n+ ]\n+\n+ outputs = ts_outputs\n+ args = common_generator_args\n+ response_file_contents = source_filelist\n+\n+ args += [\n+ \"--filelist={{response_file_name}}\",\n+ \"-g\",\n+ \"typescript\",\n+ ]\n+\n+ if (dependency_type.name == \"es_modules\") {\n+ args += [ \"--ts_use_es_modules\" ]\n+ }\n+\n+ # TODO(crbug.com/1007587): Support scramble_message_ids.\n+ # TODO(crbug.com/1007591): Support generate_fuzzing.\n+ }\n+\n+ # Create tsconfig.json for the generated Typescript.\n+ tsconfig_filename =\n+ \"$target_gen_dir/$target_name-${dependency_type.name}-tsconfig.json\"\n+ tsconfig = {\n+ }\n+ tsconfig.compilerOptions = {\n+ composite = true\n+ target = \"es6\"\n+ module = \"es6\"\n+ lib = [\n+ \"es6\",\n+ \"esnext.bigint\",\n+ ]\n+ strict = true\n+ }\n+ tsconfig.files = []\n+ foreach(base_path, output_file_base_paths) {\n+ tsconfig.files += [ rebase_path(\n+ \"$root_gen_dir/$base_path-lite${dependency_type.ts_extension}\",\n+ target_gen_dir,\n+ root_gen_dir) ]\n+ }\n+ tsconfig.references = []\n+\n+ # Get tsconfigs for deps.\n+ foreach(d, all_deps) {\n+ dep_target_gen_dir = rebase_path(get_label_info(d, \"target_gen_dir\"))\n+ dep_name = get_label_info(d, \"name\")\n+ reference = {\n+ }\n+ reference.path = \"$dep_target_gen_dir/$dep_name-${dependency_type.name}-tsconfig.json\"\n+ tsconfig.references += [ reference ]\n+ }\n+ write_file(tsconfig_filename, tsconfig, \"json\")\n+\n+ # Compile previously generated Typescript to Javascript.\n+ generator_js_target_name =\n+ \"${target_name}_${dependency_type.name}__js__generator\"\n+ generator_js_target_names += [ generator_js_target_name ]\n+\n+ action(generator_js_target_name) {\n+ script = \"$mojom_generator_root/compile_typescript.py\"\n+ sources = ts_outputs\n+ outputs = js_outputs\n+ public_deps = [ \":$generator_ts_target_name\" ]\n+ foreach(d, all_deps) {\n+ full_name = get_label_info(d, \"label_no_toolchain\")\n+ public_deps +=\n+ [ \"${full_name}_${dependency_type.name}__js__generator\" ]\n+ }\n+\n+ absolute_tsconfig_path =\n+ rebase_path(tsconfig_filename, \"\", target_gen_dir)\n+ args = [ \"--tsconfig_path=$absolute_tsconfig_path\" ]\n+ }\n+ }\n+\n+ js_target_name = target_name + \"_js\"\n+ group(js_target_name) {\n+ public_deps = []\n+ if (sources_list != []) {\n+ foreach(generator_js_target_name, generator_js_target_names) {\n+ public_deps += [ \":$generator_js_target_name\" ]\n+ }\n+ }\n+\n+ foreach(d, all_deps) {\n+ full_name = get_label_info(d, \"label_no_toolchain\")\n+ public_deps += [ \"${full_name}_js\" ]\n+ }\n+ }\n+\n+ group(js_data_deps_target_name) {\n+ data = js_outputs\n+ deps = []\n+ foreach(generator_js_target_name, generator_js_target_names) {\n+ deps += [ \":$generator_js_target_name\" ]\n+ }\n+ data_deps = []\n+ foreach(d, all_deps) {\n+ full_name = get_label_info(d, \"label_no_toolchain\")\n+ data_deps += [ \"${full_name}_js_data_deps\" ]\n+ }\n+ }\n+ }\n+}\n+\n+# A helper for the mojom() template above when component libraries are desired\n+# for generated C++ bindings units. Supports all the same arguments as mojom()\n+# except for the optional |component_output_prefix| and |component_macro_prefix|\n+# arguments. These are instead shortened to |output_prefix| and |macro_prefix|\n+# and are *required*.\n+template(\"mojom_component\") {\n+ assert(defined(invoker.output_prefix) && defined(invoker.macro_prefix))\n+\n+ mojom(target_name) {\n+ forward_variables_from(invoker,\n+ \"*\",\n+ [\n+ \"output_prefix\",\n+ \"macro_prefix\",\n+ ])\n+ component_output_prefix = invoker.output_prefix\n+ component_macro_prefix = invoker.macro_prefix\n+ }\n+}\ndiff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py\nnew file mode 100755\nindex 00000000..da9efc71\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py\n@@ -0,0 +1,390 @@\n+#!/usr/bin/env python\n+# Copyright 2013 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+\"\"\"The frontend for the Mojo bindings system.\"\"\"\n+\n+from __future__ import print_function\n+\n+import argparse\n+\n+import hashlib\n+import importlib\n+import json\n+import os\n+import pprint\n+import re\n+import struct\n+import sys\n+\n+# Disable lint check for finding modules:\n+# pylint: disable=F0401\n+\n+def _GetDirAbove(dirname):\n+ \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n+ also be \"above\" this file).\"\"\"\n+ path = os.path.abspath(__file__)\n+ while True:\n+ path, tail = os.path.split(path)\n+ assert tail\n+ if tail == dirname:\n+ return path\n+\n+\n+sys.path.insert(\n+ 0,\n+ os.path.join(\n+ os.path.dirname(os.path.dirname(os.path.abspath(__file__))), \"mojom\"))\n+\n+from mojom.error import Error\n+import mojom.fileutil as fileutil\n+from mojom.generate.module import Module\n+from mojom.generate import template_expander\n+from mojom.generate import translate\n+from mojom.generate.generator import WriteFile\n+\n+sys.path.append(\n+ os.path.join(_GetDirAbove(\"mojo\"), \"tools\", \"diagnosis\"))\n+import crbug_1001171\n+\n+\n+_BUILTIN_GENERATORS = {\n+ \"c++\": \"mojom_cpp_generator\",\n+ \"javascript\": \"mojom_js_generator\",\n+ \"java\": \"mojom_java_generator\",\n+ \"mojolpm\": \"mojom_mojolpm_generator\",\n+ \"typescript\": \"mojom_ts_generator\",\n+}\n+\n+\n+def LoadGenerators(generators_string):\n+ if not generators_string:\n+ return [] # No generators.\n+\n+ generators = {}\n+ for generator_name in [s.strip() for s in generators_string.split(\",\")]:\n+ language = generator_name.lower()\n+ if language not in _BUILTIN_GENERATORS:\n+ print(\"Unknown generator name %s\" % generator_name)\n+ sys.exit(1)\n+ generator_module = importlib.import_module(\n+ \"generators.%s\" % _BUILTIN_GENERATORS[language])\n+ generators[language] = generator_module\n+ return generators\n+\n+\n+def MakeImportStackMessage(imported_filename_stack):\n+ \"\"\"Make a (human-readable) message listing a chain of imports. (Returned\n+ string begins with a newline (if nonempty) and does not end with one.)\"\"\"\n+ return ''.join(\n+ reversed([\"\\n %s was imported by %s\" % (a, b) for (a, b) in \\\n+ zip(imported_filename_stack[1:], imported_filename_stack)]))\n+\n+\n+class RelativePath(object):\n+ \"\"\"Represents a path relative to the source tree or generated output dir.\"\"\"\n+\n+ def __init__(self, path, source_root, output_dir):\n+ self.path = path\n+ if path.startswith(source_root):\n+ self.root = source_root\n+ elif path.startswith(output_dir):\n+ self.root = output_dir\n+ else:\n+ raise Exception(\"Invalid input path %s\" % path)\n+\n+ def relative_path(self):\n+ return os.path.relpath(\n+ os.path.abspath(self.path), os.path.abspath(self.root))\n+\n+\n+def _GetModulePath(path, output_dir):\n+ return os.path.join(output_dir, path.relative_path() + '-module')\n+\n+\n+def ScrambleMethodOrdinals(interfaces, salt):\n+ already_generated = set()\n+ for interface in interfaces:\n+ i = 0\n+ already_generated.clear()\n+ for method in interface.methods:\n+ if method.explicit_ordinal is not None:\n+ continue\n+ while True:\n+ i = i + 1\n+ if i == 1000000:\n+ raise Exception(\"Could not generate %d method ordinals for %s\" %\n+ (len(interface.methods), interface.mojom_name))\n+ # Generate a scrambled method.ordinal value. The algorithm doesn't have\n+ # to be very strong, cryptographically. It just needs to be non-trivial\n+ # to guess the results without the secret salt, in order to make it\n+ # harder for a compromised process to send fake Mojo messages.\n+ sha256 = hashlib.sha256(salt)\n+ sha256.update(interface.mojom_name.encode('utf-8'))\n+ sha256.update(str(i).encode('utf-8'))\n+ # Take the first 4 bytes as a little-endian uint32.\n+ ordinal = struct.unpack('<L', sha256.digest()[:4])[0]\n+ # Trim to 31 bits, so it always fits into a Java (signed) int.\n+ ordinal = ordinal & 0x7fffffff\n+ if ordinal in already_generated:\n+ continue\n+ already_generated.add(ordinal)\n+ method.ordinal = ordinal\n+ method.ordinal_comment = (\n+ 'The %s value is based on sha256(salt + \"%s%d\").' %\n+ (ordinal, interface.mojom_name, i))\n+ break\n+\n+\n+def ReadFileContents(filename):\n+ with open(filename, 'rb') as f:\n+ return f.read()\n+\n+\n+class MojomProcessor(object):\n+ \"\"\"Takes parsed mojom modules and generates language bindings from them.\n+\n+ Attributes:\n+ _processed_files: {Dict[str, mojom.generate.module.Module]} Mapping from\n+ relative mojom filename paths to the module AST for that mojom file.\n+ \"\"\"\n+ def __init__(self, should_generate):\n+ self._should_generate = should_generate\n+ self._processed_files = {}\n+ self._typemap = {}\n+\n+ def LoadTypemaps(self, typemaps):\n+ # Support some very simple single-line comments in typemap JSON.\n+ comment_expr = r\"^\\s*//.*$\"\n+ def no_comments(line):\n+ return not re.match(comment_expr, line)\n+ for filename in typemaps:\n+ with open(filename) as f:\n+ typemaps = json.loads(\"\".join(filter(no_comments, f.readlines())))\n+ for language, typemap in typemaps.items():\n+ language_map = self._typemap.get(language, {})\n+ language_map.update(typemap)\n+ self._typemap[language] = language_map\n+ if 'c++' in self._typemap:\n+ self._typemap['mojolpm'] = self._typemap['c++']\n+\n+ def _GenerateModule(self, args, remaining_args, generator_modules,\n+ rel_filename, imported_filename_stack):\n+ # Return the already-generated module.\n+ if rel_filename.path in self._processed_files:\n+ return self._processed_files[rel_filename.path]\n+\n+ if rel_filename.path in imported_filename_stack:\n+ print(\"%s: Error: Circular dependency\" % rel_filename.path + \\\n+ MakeImportStackMessage(imported_filename_stack + [rel_filename.path]))\n+ sys.exit(1)\n+\n+ module_path = _GetModulePath(rel_filename, args.output_dir)\n+ with open(module_path, 'rb') as f:\n+ module = Module.Load(f)\n+\n+ if args.scrambled_message_id_salt_paths:\n+ salt = b''.join(\n+ map(ReadFileContents, args.scrambled_message_id_salt_paths))\n+ ScrambleMethodOrdinals(module.interfaces, salt)\n+\n+ if self._should_generate(rel_filename.path):\n+ for language, generator_module in generator_modules.items():\n+ generator = generator_module.Generator(\n+ module, args.output_dir, typemap=self._typemap.get(language, {}),\n+ variant=args.variant, bytecode_path=args.bytecode_path,\n+ for_blink=args.for_blink,\n+ js_bindings_mode=args.js_bindings_mode,\n+ js_generate_struct_deserializers=\\\n+ args.js_generate_struct_deserializers,\n+ export_attribute=args.export_attribute,\n+ export_header=args.export_header,\n+ generate_non_variant_code=args.generate_non_variant_code,\n+ support_lazy_serialization=args.support_lazy_serialization,\n+ disallow_native_types=args.disallow_native_types,\n+ disallow_interfaces=args.disallow_interfaces,\n+ generate_message_ids=args.generate_message_ids,\n+ generate_fuzzing=args.generate_fuzzing,\n+ enable_kythe_annotations=args.enable_kythe_annotations,\n+ extra_cpp_template_paths=args.extra_cpp_template_paths,\n+ generate_extra_cpp_only=args.generate_extra_cpp_only)\n+ filtered_args = []\n+ if hasattr(generator_module, 'GENERATOR_PREFIX'):\n+ prefix = '--' + generator_module.GENERATOR_PREFIX + '_'\n+ filtered_args = [arg for arg in remaining_args\n+ if arg.startswith(prefix)]\n+ generator.GenerateFiles(filtered_args)\n+\n+ # Save result.\n+ self._processed_files[rel_filename.path] = module\n+ return module\n+\n+\n+def _Generate(args, remaining_args):\n+ if args.variant == \"none\":\n+ args.variant = None\n+\n+ for idx, import_dir in enumerate(args.import_directories):\n+ tokens = import_dir.split(\":\")\n+ if len(tokens) >= 2:\n+ args.import_directories[idx] = RelativePath(tokens[0], tokens[1],\n+ args.output_dir)\n+ else:\n+ args.import_directories[idx] = RelativePath(tokens[0], args.depth,\n+ args.output_dir)\n+ generator_modules = LoadGenerators(args.generators_string)\n+\n+ fileutil.EnsureDirectoryExists(args.output_dir)\n+\n+ processor = MojomProcessor(lambda filename: filename in args.filename)\n+ processor.LoadTypemaps(set(args.typemaps))\n+\n+ if args.filelist:\n+ with open(args.filelist) as f:\n+ args.filename.extend(f.read().split())\n+\n+ for filename in args.filename:\n+ processor._GenerateModule(\n+ args, remaining_args, generator_modules,\n+ RelativePath(filename, args.depth, args.output_dir), [])\n+\n+ return 0\n+\n+\n+def _Precompile(args, _):\n+ generator_modules = LoadGenerators(\",\".join(_BUILTIN_GENERATORS.keys()))\n+\n+ template_expander.PrecompileTemplates(generator_modules, args.output_dir)\n+ return 0\n+\n+\n+def main():\n+ parser = argparse.ArgumentParser(\n+ description=\"Generate bindings from mojom files.\")\n+ parser.add_argument(\"--use_bundled_pylibs\", action=\"store_true\",\n+ help=\"use Python modules bundled in the SDK\")\n+ parser.add_argument(\n+ \"-o\",\n+ \"--output_dir\",\n+ dest=\"output_dir\",\n+ default=\".\",\n+ help=\"output directory for generated files\")\n+\n+ subparsers = parser.add_subparsers()\n+\n+ generate_parser = subparsers.add_parser(\n+ \"generate\", description=\"Generate bindings from mojom files.\")\n+ generate_parser.add_argument(\"filename\", nargs=\"*\",\n+ help=\"mojom input file\")\n+ generate_parser.add_argument(\"--filelist\", help=\"mojom input file list\")\n+ generate_parser.add_argument(\"-d\", \"--depth\", dest=\"depth\", default=\".\",\n+ help=\"depth from source root\")\n+ generate_parser.add_argument(\"-g\",\n+ \"--generators\",\n+ dest=\"generators_string\",\n+ metavar=\"GENERATORS\",\n+ default=\"c++,javascript,java,mojolpm\",\n+ help=\"comma-separated list of generators\")\n+ generate_parser.add_argument(\n+ \"--gen_dir\", dest=\"gen_directories\", action=\"append\", metavar=\"directory\",\n+ default=[], help=\"add a directory to be searched for the syntax trees.\")\n+ generate_parser.add_argument(\n+ \"-I\", dest=\"import_directories\", action=\"append\", metavar=\"directory\",\n+ default=[],\n+ help=\"add a directory to be searched for import files. The depth from \"\n+ \"source root can be specified for each import by appending it after \"\n+ \"a colon\")\n+ generate_parser.add_argument(\"--typemap\", action=\"append\", metavar=\"TYPEMAP\",\n+ default=[], dest=\"typemaps\",\n+ help=\"apply TYPEMAP to generated output\")\n+ generate_parser.add_argument(\"--variant\", dest=\"variant\", default=None,\n+ help=\"output a named variant of the bindings\")\n+ generate_parser.add_argument(\n+ \"--bytecode_path\", required=True, help=(\n+ \"the path from which to load template bytecode; to generate template \"\n+ \"bytecode, run %s precompile BYTECODE_PATH\" % os.path.basename(\n+ sys.argv[0])))\n+ generate_parser.add_argument(\"--for_blink\", action=\"store_true\",\n+ help=\"Use WTF types as generated types for mojo \"\n+ \"string/array/map.\")\n+ generate_parser.add_argument(\n+ \"--js_bindings_mode\", choices=[\"new\", \"old\"], default=\"old\",\n+ help=\"This option only affects the JavaScript bindings. The value could \"\n+ \"be \\\"new\\\" to generate new-style lite JS bindings in addition to the \"\n+ \"old, or \\\"old\\\" to only generate old bindings.\")\n+ generate_parser.add_argument(\n+ \"--js_generate_struct_deserializers\", action=\"store_true\",\n+ help=\"Generate javascript deserialize methods for structs in \"\n+ \"mojom-lite.js file\")\n+ generate_parser.add_argument(\n+ \"--export_attribute\", default=\"\",\n+ help=\"Optional attribute to specify on class declaration to export it \"\n+ \"for the component build.\")\n+ generate_parser.add_argument(\n+ \"--export_header\", default=\"\",\n+ help=\"Optional header to include in the generated headers to support the \"\n+ \"component build.\")\n+ generate_parser.add_argument(\n+ \"--generate_non_variant_code\", action=\"store_true\",\n+ help=\"Generate code that is shared by different variants.\")\n+ generate_parser.add_argument(\n+ \"--scrambled_message_id_salt_path\",\n+ dest=\"scrambled_message_id_salt_paths\",\n+ help=\"If non-empty, the path to a file whose contents should be used as\"\n+ \"a salt for generating scrambled message IDs. If this switch is specified\"\n+ \"more than once, the contents of all salt files are concatenated to form\"\n+ \"the salt value.\", default=[], action=\"append\")\n+ generate_parser.add_argument(\n+ \"--support_lazy_serialization\",\n+ help=\"If set, generated bindings will serialize lazily when possible.\",\n+ action=\"store_true\")\n+ generate_parser.add_argument(\n+ \"--extra_cpp_template_paths\",\n+ dest=\"extra_cpp_template_paths\",\n+ action=\"append\",\n+ metavar=\"path_to_template\",\n+ default=[],\n+ help=\"Provide a path to a new template (.tmpl) that is used to generate \"\n+ \"additional C++ source/header files \")\n+ generate_parser.add_argument(\n+ \"--generate_extra_cpp_only\",\n+ help=\"If set and extra_cpp_template_paths provided, will only generate\"\n+ \"extra_cpp_template related C++ bindings\",\n+ action=\"store_true\")\n+ generate_parser.add_argument(\n+ \"--disallow_native_types\",\n+ help=\"Disallows the [Native] attribute to be specified on structs or \"\n+ \"enums within the mojom file.\", action=\"store_true\")\n+ generate_parser.add_argument(\n+ \"--disallow_interfaces\",\n+ help=\"Disallows interface definitions within the mojom file. It is an \"\n+ \"error to specify this flag when processing a mojom file which defines \"\n+ \"any interface.\", action=\"store_true\")\n+ generate_parser.add_argument(\n+ \"--generate_message_ids\",\n+ help=\"Generates only the message IDs header for C++ bindings. Note that \"\n+ \"this flag only matters if --generate_non_variant_code is also \"\n+ \"specified.\", action=\"store_true\")\n+ generate_parser.add_argument(\n+ \"--generate_fuzzing\",\n+ action=\"store_true\",\n+ help=\"Generates additional bindings for fuzzing in JS.\")\n+ generate_parser.add_argument(\n+ \"--enable_kythe_annotations\",\n+ action=\"store_true\",\n+ help=\"Adds annotations for kythe metadata generation.\")\n+\n+ generate_parser.set_defaults(func=_Generate)\n+\n+ precompile_parser = subparsers.add_parser(\"precompile\",\n+ description=\"Precompile templates for the mojom bindings generator.\")\n+ precompile_parser.set_defaults(func=_Precompile)\n+\n+ args, remaining_args = parser.parse_known_args()\n+ return args.func(args, remaining_args)\n+\n+\n+if __name__ == \"__main__\":\n+ with crbug_1001171.DumpStateOnLookupError():\n+ sys.exit(main())\ndiff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py\nnew file mode 100644\nindex 00000000..bddbe3f4\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py\n@@ -0,0 +1,62 @@\n+# Copyright 2014 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import unittest\n+\n+from mojom_bindings_generator import MakeImportStackMessage\n+from mojom_bindings_generator import ScrambleMethodOrdinals\n+\n+\n+class FakeIface(object):\n+ def __init__(self):\n+ self.mojom_name = None\n+ self.methods = None\n+\n+\n+class FakeMethod(object):\n+ def __init__(self, explicit_ordinal=None):\n+ self.explicit_ordinal = explicit_ordinal\n+ self.ordinal = explicit_ordinal\n+ self.ordinal_comment = None\n+\n+\n+class MojoBindingsGeneratorTest(unittest.TestCase):\n+ \"\"\"Tests mojo_bindings_generator.\"\"\"\n+\n+ def testMakeImportStackMessage(self):\n+ \"\"\"Tests MakeImportStackMessage().\"\"\"\n+ self.assertEqual(MakeImportStackMessage([\"x\"]), \"\")\n+ self.assertEqual(MakeImportStackMessage([\"x\", \"y\"]),\n+ \"\\n y was imported by x\")\n+ self.assertEqual(MakeImportStackMessage([\"x\", \"y\", \"z\"]),\n+ \"\\n z was imported by y\\n y was imported by x\")\n+\n+ def testScrambleMethodOrdinals(self):\n+ \"\"\"Tests ScrambleMethodOrdinals().\"\"\"\n+ interface = FakeIface()\n+ interface.mojom_name = 'RendererConfiguration'\n+ interface.methods = [\n+ FakeMethod(),\n+ FakeMethod(),\n+ FakeMethod(),\n+ FakeMethod(explicit_ordinal=42)\n+ ]\n+ ScrambleMethodOrdinals([interface], \"foo\".encode('utf-8'))\n+ # These next three values are hard-coded. If the generation algorithm\n+ # changes from being based on sha256(seed + interface.name + str(i)) then\n+ # these numbers will obviously need to change too.\n+ #\n+ # Note that hashlib.sha256('fooRendererConfiguration1').digest()[:4] is\n+ # '\\xa5\\xbc\\xf9\\xca' and that hex(1257880741) = '0x4af9bca5'. The\n+ # difference in 0x4a vs 0xca is because we only take 31 bits.\n+ self.assertEqual(interface.methods[0].ordinal, 1257880741)\n+ self.assertEqual(interface.methods[1].ordinal, 631133653)\n+ self.assertEqual(interface.methods[2].ordinal, 549336076)\n+\n+ # Explicit method ordinals should not be scrambled.\n+ self.assertEqual(interface.methods[3].ordinal, 42)\n+\n+\n+if __name__ == \"__main__\":\n+ unittest.main()\ndiff --git a/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py b/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py\nnew file mode 100755\nindex 00000000..15f0e3ba\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py\n@@ -0,0 +1,119 @@\n+#!/usr/bin/env python\n+# Copyright 2020 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\"\"\"Downgrades *.mojom files to the old mojo types for remotes and receivers.\"\"\"\n+\n+import argparse\n+import fnmatch\n+import os\n+import re\n+import shutil\n+import sys\n+import tempfile\n+\n+# List of patterns and replacements to match and use against the contents of a\n+# mojo file. Each replacement string will be used with Python string's format()\n+# function, so the '{}' substring is used to mark where the mojo type should go.\n+_MOJO_REPLACEMENTS = {\n+ r'pending_remote': r'{}',\n+ r'pending_receiver': r'{}&',\n+ r'pending_associated_remote': r'associated {}',\n+ r'pending_associated_receiver': r'associated {}&',\n+}\n+\n+# Pre-compiled regular expression that matches against any of the replacements.\n+_REGEXP_PATTERN = re.compile(\n+ r'|'.join(\n+ ['{}\\s*<\\s*(.*?)\\s*>'.format(k) for k in _MOJO_REPLACEMENTS.keys()]),\n+ flags=re.DOTALL)\n+\n+\n+def ReplaceFunction(match_object):\n+ \"\"\"Returns the right replacement for the string matched against the regexp.\"\"\"\n+ for index, (match, repl) in enumerate(_MOJO_REPLACEMENTS.items(), 1):\n+ if match_object.group(0).startswith(match):\n+ return repl.format(match_object.group(index))\n+\n+\n+def DowngradeFile(path, output_dir=None):\n+ \"\"\"Downgrades the mojom file specified by |path| to the old mojo types.\n+\n+ Optionally pass |output_dir| to place the result under a separate output\n+ directory, preserving the relative path to the file included in |path|.\n+ \"\"\"\n+ # Use a temporary file to dump the new contents after replacing the patterns.\n+ with open(path) as src_mojo_file:\n+ with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp_mojo_file:\n+ tmp_contents = _REGEXP_PATTERN.sub(ReplaceFunction, src_mojo_file.read())\n+ tmp_mojo_file.write(tmp_contents)\n+\n+ # Files should be placed in the desired output directory\n+ if output_dir:\n+ output_filepath = os.path.join(output_dir, os.path.basename(path))\n+ if not os.path.exists(output_dir):\n+ os.makedirs(output_dir)\n+ else:\n+ output_filepath = path\n+\n+ # Write the new contents preserving the original file's attributes.\n+ shutil.copystat(path, tmp_mojo_file.name)\n+ shutil.move(tmp_mojo_file.name, output_filepath)\n+\n+ # Make sure to \"touch\" the new file so that access, modify and change times\n+ # are always newer than the source file's, otherwise Modify time will be kept\n+ # as per the call to shutil.copystat(), causing unnecessary generations of the\n+ # output file in subsequent builds due to ninja considering it dirty.\n+ os.utime(output_filepath, None)\n+\n+\n+def DowngradeDirectory(path, output_dir=None):\n+ \"\"\"Downgrades mojom files inside directory |path| to the old mojo types.\n+\n+ Optionally pass |output_dir| to place the result under a separate output\n+ directory, preserving the relative path to the file included in |path|.\n+ \"\"\"\n+ # We don't have recursive glob.glob() nor pathlib.Path.rglob() in Python 2.7\n+ mojom_filepaths = []\n+ for dir_path, _, filenames in os.walk(path):\n+ for filename in fnmatch.filter(filenames, \"*mojom\"):\n+ mojom_filepaths.append(os.path.join(dir_path, filename))\n+\n+ for path in mojom_filepaths:\n+ absolute_dirpath = os.path.dirname(os.path.abspath(path))\n+ if output_dir:\n+ dest_dirpath = output_dir + absolute_dirpath\n+ else:\n+ dest_dirpath = absolute_dirpath\n+ DowngradeFile(path, dest_dirpath)\n+\n+\n+def DowngradePath(src_path, output_dir=None):\n+ \"\"\"Downgrades the mojom files pointed by |src_path| to the old mojo types.\n+\n+ Optionally pass |output_dir| to place the result under a separate output\n+ directory, preserving the relative path to the file included in |path|.\n+ \"\"\"\n+ if os.path.isdir(src_path):\n+ DowngradeDirectory(src_path, output_dir)\n+ elif os.path.isfile(src_path):\n+ DowngradeFile(src_path, output_dir)\n+ else:\n+ print(\">>> {} not pointing to a valid file or directory\".format(src_path))\n+ sys.exit(1)\n+\n+\n+def main():\n+ parser = argparse.ArgumentParser(\n+ description=\"Downgrade *.mojom files to use the old mojo types.\")\n+ parser.add_argument(\n+ \"srcpath\", help=\"path to the file or directory to apply the conversion\")\n+ parser.add_argument(\n+ \"--outdir\", help=\"the directory to place the converted file(s) under\")\n+ args = parser.parse_args()\n+\n+ DowngradePath(args.srcpath, args.outdir)\n+\n+\n+if __name__ == \"__main__\":\n+ sys.exit(main())\ndiff --git a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py\nnew file mode 100755\nindex 00000000..f1783d59\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py\n@@ -0,0 +1,57 @@\n+#!/usr/bin/env python\n+# Copyright 2020 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import argparse\n+import json\n+import os\n+import re\n+import sys\n+\n+\n+def CheckCppTypemapConfigs(target_name, config_filename, out_filename):\n+ _SUPPORTED_CONFIG_KEYS = set([\n+ 'types', 'traits_headers', 'traits_private_headers', 'traits_sources',\n+ 'traits_deps', 'traits_public_deps'\n+ ])\n+ _SUPPORTED_TYPE_KEYS = set([\n+ 'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable',\n+ 'move_only', 'nullable_is_same_type'\n+ ])\n+ with open(config_filename, 'r') as f:\n+ for config in json.load(f):\n+ for key in config.keys():\n+ if key not in _SUPPORTED_CONFIG_KEYS:\n+ raise ValueError('Invalid typemap property \"%s\" when processing %s' %\n+ (key, target_name))\n+\n+ types = config.get('types')\n+ if not types:\n+ raise ValueError('Typemap for %s must specify at least one type to map'\n+ % target_name)\n+\n+ for entry in types:\n+ for key in entry.keys():\n+ if key not in _SUPPORTED_TYPE_KEYS:\n+ raise IOError(\n+ 'Invalid type property \"%s\" in typemap for \"%s\" on target %s' %\n+ (key, entry.get('mojom', '(unknown)'), target_name))\n+\n+ with open(out_filename, 'w') as f:\n+ f.truncate(0)\n+\n+\n+def main():\n+ parser = argparse.ArgumentParser()\n+ _, args = parser.parse_known_args()\n+ if len(args) != 3:\n+ print('Usage: validate_typemap_config.py target_name config_filename '\n+ 'stamp_filename')\n+ sys.exit(1)\n+\n+ CheckCppTypemapConfigs(args[0], args[1], args[2])\n+\n+\n+if __name__ == '__main__':\n+ main()\ndiff --git a/utils/ipc/mojo/public/tools/mojom/README.md b/utils/ipc/mojo/public/tools/mojom/README.md\nnew file mode 100644\nindex 00000000..6a4ff78a\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/README.md\n@@ -0,0 +1,14 @@\n+# The Mojom Parser\n+\n+The Mojom format is an interface definition language (IDL) for describing\n+interprocess communication (IPC) messages and data types for use with the\n+low-level cross-platform\n+[Mojo IPC library](https://chromium.googlesource.com/chromium/src/+/master/mojo/public/c/system/README.md).\n+\n+This directory consists of a `mojom` Python module, its tests, and supporting\n+command-line tools. The Python module implements the parser used by the\n+command-line tools and exposes an API to help external bindings generators emit\n+useful code from the parser's outputs.\n+\n+TODO(https://crbug.com/1060464): Fill out this documentation once the library\n+and tools have stabilized.\ndiff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py\nnew file mode 100755\nindex 00000000..7e746112\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py\n@@ -0,0 +1,170 @@\n+#!/usr/bin/env python\n+# Copyright 2020 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\"\"\"Verifies backward-compatibility of mojom type changes.\n+\n+Given a set of pre- and post-diff mojom file contents, and a root directory\n+for a project, this tool verifies that any changes to [Stable] mojom types are\n+backward-compatible with the previous version.\n+\n+This can be used e.g. by a presubmit check to prevent developers from making\n+breaking changes to stable mojoms.\"\"\"\n+\n+import argparse\n+import errno\n+import io\n+import json\n+import os\n+import os.path\n+import shutil\n+import six\n+import sys\n+import tempfile\n+\n+from mojom.generate import module\n+from mojom.generate import translate\n+from mojom.parse import parser\n+\n+\n+class ParseError(Exception):\n+ pass\n+\n+\n+def _ValidateDelta(root, delta):\n+ \"\"\"Parses all modified mojoms (including all transitive mojom dependencies,\n+ even if unmodified) to perform backward-compatibility checks on any types\n+ marked with the [Stable] attribute.\n+\n+ Note that unlike the normal build-time parser in mojom_parser.py, this does\n+ not produce or rely on cached module translations, but instead parses the full\n+ transitive closure of a mojom's input dependencies all at once.\n+ \"\"\"\n+\n+ # First build a map of all files covered by the delta\n+ affected_files = set()\n+ old_files = {}\n+ new_files = {}\n+ for change in delta:\n+ # TODO(crbug.com/953884): Use pathlib once we're migrated fully to Python 3.\n+ filename = change['filename'].replace('\\\\', '/')\n+ affected_files.add(filename)\n+ if change['old']:\n+ old_files[filename] = change['old']\n+ if change['new']:\n+ new_files[filename] = change['new']\n+\n+ # Parse and translate all mojoms relevant to the delta, including transitive\n+ # imports that weren't modified.\n+ unmodified_modules = {}\n+\n+ def parseMojom(mojom, file_overrides, override_modules):\n+ if mojom in unmodified_modules or mojom in override_modules:\n+ return\n+\n+ contents = file_overrides.get(mojom)\n+ if contents:\n+ modules = override_modules\n+ else:\n+ modules = unmodified_modules\n+ with io.open(os.path.join(root, mojom), encoding='utf-8') as f:\n+ contents = f.read()\n+\n+ try:\n+ ast = parser.Parse(contents, mojom)\n+ except Exception as e:\n+ six.reraise(\n+ ParseError,\n+ 'encountered exception {0} while parsing {1}'.format(e, mojom),\n+ sys.exc_info()[2])\n+ for imp in ast.import_list:\n+ parseMojom(imp.import_filename, file_overrides, override_modules)\n+\n+ # Now that the transitive set of dependencies has been imported and parsed\n+ # above, translate each mojom AST into a Module so that all types are fully\n+ # defined and can be inspected.\n+ all_modules = {}\n+ all_modules.update(unmodified_modules)\n+ all_modules.update(override_modules)\n+ modules[mojom] = translate.OrderedModule(ast, mojom, all_modules)\n+\n+ old_modules = {}\n+ for mojom in old_files.keys():\n+ parseMojom(mojom, old_files, old_modules)\n+ new_modules = {}\n+ for mojom in new_files.keys():\n+ parseMojom(mojom, new_files, new_modules)\n+\n+ # At this point we have a complete set of translated Modules from both the\n+ # pre- and post-diff mojom contents. Now we can analyze backward-compatibility\n+ # of the deltas.\n+ #\n+ # Note that for backward-compatibility checks we only care about types which\n+ # were marked [Stable] before the diff. Types newly marked as [Stable] are not\n+ # checked.\n+ def collectTypes(modules):\n+ types = {}\n+ for m in modules.values():\n+ for kinds in (m.enums, m.structs, m.unions, m.interfaces):\n+ for kind in kinds:\n+ types[kind.qualified_name] = kind\n+ return types\n+\n+ old_types = collectTypes(old_modules)\n+ new_types = collectTypes(new_modules)\n+\n+ # Collect any renamed types so they can be compared accordingly.\n+ renamed_types = {}\n+ for name, kind in new_types.items():\n+ old_name = kind.attributes and kind.attributes.get('RenamedFrom')\n+ if old_name:\n+ renamed_types[old_name] = name\n+\n+ for qualified_name, kind in old_types.items():\n+ if not kind.stable:\n+ continue\n+\n+ new_name = renamed_types.get(qualified_name, qualified_name)\n+ if new_name not in new_types:\n+ raise Exception(\n+ 'Stable type %s appears to be deleted by this change. If it was '\n+ 'renamed, please add a [RenamedFrom] attribute to the new type. This '\n+ 'can be deleted by a subsequent change.' % qualified_name)\n+\n+ if not new_types[new_name].IsBackwardCompatible(kind):\n+ raise Exception('Stable type %s appears to have changed in a way which '\n+ 'breaks backward-compatibility. Please fix!\\n\\nIf you '\n+ 'believe this assessment to be incorrect, please file a '\n+ 'Chromium bug against the \"Internals>Mojo>Bindings\" '\n+ 'component.' % qualified_name)\n+\n+\n+def Run(command_line, delta=None):\n+ \"\"\"Runs the tool with the given command_line. Normally this will read the\n+ change description from stdin as a JSON-encoded list, but tests may pass a\n+ delta directly for convenience.\"\"\"\n+ arg_parser = argparse.ArgumentParser(\n+ description='Verifies backward-compatibility of mojom type changes.',\n+ epilog=\"\"\"\n+This tool reads a change description from stdin and verifies that all modified\n+[Stable] mojom types will retain backward-compatibility. The change description\n+must be a JSON-encoded list of objects, each with a \"filename\" key (path to a\n+changed mojom file, relative to ROOT); an \"old\" key whose value is a string of\n+the full file contents before the change, or null if the file is being added;\n+and a \"new\" key whose value is a string of the full file contents after the\n+change, or null if the file is being deleted.\"\"\")\n+ arg_parser.add_argument(\n+ '--src-root',\n+ required=True,\n+ action='store',\n+ metavar='ROOT',\n+ help='The root of the source tree in which the checked mojoms live.')\n+\n+ args, _ = arg_parser.parse_known_args(command_line)\n+ if not delta:\n+ delta = json.load(sys.stdin)\n+ _ValidateDelta(args.src_root, delta)\n+\n+\n+if __name__ == '__main__':\n+ Run(sys.argv[1:])\ndiff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py\nnew file mode 100755\nindex 00000000..9f51ea77\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py\n@@ -0,0 +1,260 @@\n+#!/usr/bin/env python\n+# Copyright 2020 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import json\n+import os\n+import os.path\n+import shutil\n+import tempfile\n+import unittest\n+\n+import check_stable_mojom_compatibility\n+\n+from mojom.generate import module\n+\n+\n+class Change(object):\n+ \"\"\"Helper to clearly define a mojom file delta to be analyzed.\"\"\"\n+\n+ def __init__(self, filename, old=None, new=None):\n+ \"\"\"If old is None, this is a file addition. If new is None, this is a file\n+ deletion. Otherwise it's a file change.\"\"\"\n+ self.filename = filename\n+ self.old = old\n+ self.new = new\n+\n+\n+class UnchangedFile(Change):\n+ def __init__(self, filename, contents):\n+ super(UnchangedFile, self).__init__(filename, old=contents, new=contents)\n+\n+\n+class CheckStableMojomCompatibilityTest(unittest.TestCase):\n+ \"\"\"Tests covering the behavior of the compatibility checking tool. Note that\n+ details of different compatibility checks and relevant failure modes are NOT\n+ covered by these tests. Those are instead covered by unittests in\n+ version_compatibility_unittest.py. Additionally, the tests which ensure a\n+ given set of [Stable] mojom definitions are indeed plausibly stable (i.e. they\n+ have no unstable dependencies) are covered by stable_attribute_unittest.py.\n+\n+ These tests cover higher-level concerns of the compatibility checking tool,\n+ like file or symbol, renames, changes spread over multiple files, etc.\"\"\"\n+\n+ def verifyBackwardCompatibility(self, changes):\n+ \"\"\"Helper for implementing assertBackwardCompatible and\n+ assertNotBackwardCompatible\"\"\"\n+\n+ temp_dir = tempfile.mkdtemp()\n+ for change in changes:\n+ if change.old:\n+ # Populate the old file on disk in our temporary fake source root\n+ file_path = os.path.join(temp_dir, change.filename)\n+ dir_path = os.path.dirname(file_path)\n+ if not os.path.exists(dir_path):\n+ os.makedirs(dir_path)\n+ with open(file_path, 'w') as f:\n+ f.write(change.old)\n+\n+ delta = []\n+ for change in changes:\n+ if change.old != change.new:\n+ delta.append({\n+ 'filename': change.filename,\n+ 'old': change.old,\n+ 'new': change.new\n+ })\n+\n+ try:\n+ check_stable_mojom_compatibility.Run(['--src-root', temp_dir],\n+ delta=delta)\n+ finally:\n+ shutil.rmtree(temp_dir)\n+\n+ def assertBackwardCompatible(self, changes):\n+ self.verifyBackwardCompatibility(changes)\n+\n+ def assertNotBackwardCompatible(self, changes):\n+ try:\n+ self.verifyBackwardCompatibility(changes)\n+ except Exception:\n+ return\n+\n+ raise Exception('Change unexpectedly passed a backward-compatibility check')\n+\n+ def testBasicCompatibility(self):\n+ \"\"\"Minimal smoke test to verify acceptance of a simple valid change.\"\"\"\n+ self.assertBackwardCompatible([\n+ Change('foo/foo.mojom',\n+ old='[Stable] struct S {};',\n+ new='[Stable] struct S { [MinVersion=1] int32 x; };')\n+ ])\n+\n+ def testBasicIncompatibility(self):\n+ \"\"\"Minimal smoke test to verify rejection of a simple invalid change.\"\"\"\n+ self.assertNotBackwardCompatible([\n+ Change('foo/foo.mojom',\n+ old='[Stable] struct S {};',\n+ new='[Stable] struct S { int32 x; };')\n+ ])\n+\n+ def testIgnoreIfNotStable(self):\n+ \"\"\"We don't care about types not marked [Stable]\"\"\"\n+ self.assertBackwardCompatible([\n+ Change('foo/foo.mojom',\n+ old='struct S {};',\n+ new='struct S { int32 x; };')\n+ ])\n+\n+ def testRename(self):\n+ \"\"\"We can do checks for renamed types.\"\"\"\n+ self.assertBackwardCompatible([\n+ Change('foo/foo.mojom',\n+ old='[Stable] struct S {};',\n+ new='[Stable, RenamedFrom=\"S\"] struct T {};')\n+ ])\n+ self.assertNotBackwardCompatible([\n+ Change('foo/foo.mojom',\n+ old='[Stable] struct S {};',\n+ new='[Stable, RenamedFrom=\"S\"] struct T { int32 x; };')\n+ ])\n+ self.assertBackwardCompatible([\n+ Change('foo/foo.mojom',\n+ old='[Stable] struct S {};',\n+ new=\"\"\"\\\n+ [Stable, RenamedFrom=\"S\"]\n+ struct T { [MinVersion=1] int32 x; };\n+ \"\"\")\n+ ])\n+\n+ def testNewlyStable(self):\n+ \"\"\"We don't care about types newly marked as [Stable].\"\"\"\n+ self.assertBackwardCompatible([\n+ Change('foo/foo.mojom',\n+ old='struct S {};',\n+ new='[Stable] struct S { int32 x; };')\n+ ])\n+\n+ def testFileRename(self):\n+ \"\"\"Make sure we can still do compatibility checks after a file rename.\"\"\"\n+ self.assertBackwardCompatible([\n+ Change('foo/foo.mojom', old='[Stable] struct S {};', new=None),\n+ Change('bar/bar.mojom',\n+ old=None,\n+ new='[Stable] struct S { [MinVersion=1] int32 x; };')\n+ ])\n+ self.assertNotBackwardCompatible([\n+ Change('foo/foo.mojom', old='[Stable] struct S {};', new=None),\n+ Change('bar/bar.mojom', old=None, new='[Stable] struct S { int32 x; };')\n+ ])\n+\n+ def testWithImport(self):\n+ \"\"\"Ensure that cross-module dependencies do not break the compatibility\n+ checking tool.\"\"\"\n+ self.assertBackwardCompatible([\n+ Change('foo/foo.mojom',\n+ old=\"\"\"\\\n+ module foo;\n+ [Stable] struct S {};\n+ \"\"\",\n+ new=\"\"\"\\\n+ module foo;\n+ [Stable] struct S { [MinVersion=2] int32 x; };\n+ \"\"\"),\n+ Change('bar/bar.mojom',\n+ old=\"\"\"\\\n+ module bar;\n+ import \"foo/foo.mojom\";\n+ [Stable] struct T { foo.S s; };\n+ \"\"\",\n+ new=\"\"\"\\\n+ module bar;\n+ import \"foo/foo.mojom\";\n+ [Stable] struct T { foo.S s; [MinVersion=1] int32 y; };\n+ \"\"\")\n+ ])\n+\n+ def testWithMovedDefinition(self):\n+ \"\"\"If a definition moves from one file to another, we should still be able\n+ to check compatibility accurately.\"\"\"\n+ self.assertBackwardCompatible([\n+ Change('foo/foo.mojom',\n+ old=\"\"\"\\\n+ module foo;\n+ [Stable] struct S {};\n+ \"\"\",\n+ new=\"\"\"\\\n+ module foo;\n+ \"\"\"),\n+ Change('bar/bar.mojom',\n+ old=\"\"\"\\\n+ module bar;\n+ import \"foo/foo.mojom\";\n+ [Stable] struct T { foo.S s; };\n+ \"\"\",\n+ new=\"\"\"\\\n+ module bar;\n+ import \"foo/foo.mojom\";\n+ [Stable, RenamedFrom=\"foo.S\"] struct S {\n+ [MinVersion=2] int32 x;\n+ };\n+ [Stable] struct T { S s; [MinVersion=1] int32 y; };\n+ \"\"\")\n+ ])\n+\n+ self.assertNotBackwardCompatible([\n+ Change('foo/foo.mojom',\n+ old=\"\"\"\\\n+ module foo;\n+ [Stable] struct S {};\n+ \"\"\",\n+ new=\"\"\"\\\n+ module foo;\n+ \"\"\"),\n+ Change('bar/bar.mojom',\n+ old=\"\"\"\\\n+ module bar;\n+ import \"foo/foo.mojom\";\n+ [Stable] struct T { foo.S s; };\n+ \"\"\",\n+ new=\"\"\"\\\n+ module bar;\n+ import \"foo/foo.mojom\";\n+ [Stable, RenamedFrom=\"foo.S\"] struct S { int32 x; };\n+ [Stable] struct T { S s; [MinVersion=1] int32 y; };\n+ \"\"\")\n+ ])\n+\n+ def testWithUnmodifiedImport(self):\n+ \"\"\"Unchanged files in the filesystem are still parsed by the compatibility\n+ checking tool if they're imported by a changed file.\"\"\"\n+ self.assertBackwardCompatible([\n+ UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),\n+ Change('bar/bar.mojom',\n+ old=\"\"\"\\\n+ module bar;\n+ import \"foo/foo.mojom\";\n+ [Stable] struct T { foo.S s; };\n+ \"\"\",\n+ new=\"\"\"\\\n+ module bar;\n+ import \"foo/foo.mojom\";\n+ [Stable] struct T { foo.S s; [MinVersion=1] int32 x; };\n+ \"\"\")\n+ ])\n+\n+ self.assertNotBackwardCompatible([\n+ UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),\n+ Change('bar/bar.mojom',\n+ old=\"\"\"\\\n+ module bar;\n+ import \"foo/foo.mojom\";\n+ [Stable] struct T { foo.S s; };\n+ \"\"\",\n+ new=\"\"\"\\\n+ module bar;\n+ import \"foo/foo.mojom\";\n+ [Stable] struct T { foo.S s; int32 x; };\n+ \"\"\")\n+ ])\ndiff --git a/utils/ipc/mojo/public/tools/mojom/const_unittest.py b/utils/ipc/mojo/public/tools/mojom/const_unittest.py\nnew file mode 100644\nindex 00000000..cb42dfac\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/const_unittest.py\n@@ -0,0 +1,90 @@\n+# Copyright 2020 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+from mojom_parser_test_case import MojomParserTestCase\n+from mojom.generate import module as mojom\n+\n+\n+class ConstTest(MojomParserTestCase):\n+ \"\"\"Tests constant parsing behavior.\"\"\"\n+\n+ def testLiteralInt(self):\n+ a_mojom = 'a.mojom'\n+ self.WriteFile(a_mojom, 'const int32 k = 42;')\n+ self.ParseMojoms([a_mojom])\n+ a = self.LoadModule(a_mojom)\n+ self.assertEqual(1, len(a.constants))\n+ self.assertEqual('k', a.constants[0].mojom_name)\n+ self.assertEqual('42', a.constants[0].value)\n+\n+ def testLiteralFloat(self):\n+ a_mojom = 'a.mojom'\n+ self.WriteFile(a_mojom, 'const float k = 42.5;')\n+ self.ParseMojoms([a_mojom])\n+ a = self.LoadModule(a_mojom)\n+ self.assertEqual(1, len(a.constants))\n+ self.assertEqual('k', a.constants[0].mojom_name)\n+ self.assertEqual('42.5', a.constants[0].value)\n+\n+ def testLiteralString(self):\n+ a_mojom = 'a.mojom'\n+ self.WriteFile(a_mojom, 'const string k = \"woot\";')\n+ self.ParseMojoms([a_mojom])\n+ a = self.LoadModule(a_mojom)\n+ self.assertEqual(1, len(a.constants))\n+ self.assertEqual('k', a.constants[0].mojom_name)\n+ self.assertEqual('\"woot\"', a.constants[0].value)\n+\n+ def testEnumConstant(self):\n+ a_mojom = 'a.mojom'\n+ self.WriteFile(a_mojom, 'module a; enum E { kA = 41, kB };')\n+ b_mojom = 'b.mojom'\n+ self.WriteFile(\n+ b_mojom, \"\"\"\\\n+ import \"a.mojom\";\n+ const a.E kE1 = a.E.kB;\n+\n+ // We also allow value names to be unqualified, implying scope from the\n+ // constant's type.\n+ const a.E kE2 = kB;\n+ \"\"\")\n+ self.ParseMojoms([a_mojom, b_mojom])\n+ a = self.LoadModule(a_mojom)\n+ b = self.LoadModule(b_mojom)\n+ self.assertEqual(1, len(a.enums))\n+ self.assertEqual('E', a.enums[0].mojom_name)\n+ self.assertEqual(2, len(b.constants))\n+ self.assertEqual('kE1', b.constants[0].mojom_name)\n+ self.assertEqual(a.enums[0], b.constants[0].kind)\n+ self.assertEqual(a.enums[0].fields[1], b.constants[0].value.field)\n+ self.assertEqual(42, b.constants[0].value.field.numeric_value)\n+ self.assertEqual('kE2', b.constants[1].mojom_name)\n+ self.assertEqual(a.enums[0].fields[1], b.constants[1].value.field)\n+ self.assertEqual(42, b.constants[1].value.field.numeric_value)\n+\n+ def testConstantReference(self):\n+ a_mojom = 'a.mojom'\n+ self.WriteFile(a_mojom, 'const int32 kA = 42; const int32 kB = kA;')\n+ self.ParseMojoms([a_mojom])\n+ a = self.LoadModule(a_mojom)\n+ self.assertEqual(2, len(a.constants))\n+ self.assertEqual('kA', a.constants[0].mojom_name)\n+ self.assertEqual('42', a.constants[0].value)\n+ self.assertEqual('kB', a.constants[1].mojom_name)\n+ self.assertEqual('42', a.constants[1].value)\n+\n+ def testImportedConstantReference(self):\n+ a_mojom = 'a.mojom'\n+ self.WriteFile(a_mojom, 'const int32 kA = 42;')\n+ b_mojom = 'b.mojom'\n+ self.WriteFile(b_mojom, 'import \"a.mojom\"; const int32 kB = kA;')\n+ self.ParseMojoms([a_mojom, b_mojom])\n+ a = self.LoadModule(a_mojom)\n+ b = self.LoadModule(b_mojom)\n+ self.assertEqual(1, len(a.constants))\n+ self.assertEqual(1, len(b.constants))\n+ self.assertEqual('kA', a.constants[0].mojom_name)\n+ self.assertEqual('42', a.constants[0].value)\n+ self.assertEqual('kB', b.constants[0].mojom_name)\n+ self.assertEqual('42', b.constants[0].value)\ndiff --git a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py\nnew file mode 100644\nindex 00000000..d9005078\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py\n@@ -0,0 +1,92 @@\n+# Copyright 2020 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+from mojom_parser_test_case import MojomParserTestCase\n+\n+\n+class EnumTest(MojomParserTestCase):\n+ \"\"\"Tests enum parsing behavior.\"\"\"\n+\n+ def testExplicitValues(self):\n+ \"\"\"Verifies basic parsing of assigned integral values.\"\"\"\n+ types = self.ExtractTypes('enum E { kFoo=0, kBar=2, kBaz };')\n+ self.assertEqual('kFoo', types['E'].fields[0].mojom_name)\n+ self.assertEqual(0, types['E'].fields[0].numeric_value)\n+ self.assertEqual('kBar', types['E'].fields[1].mojom_name)\n+ self.assertEqual(2, types['E'].fields[1].numeric_value)\n+ self.assertEqual('kBaz', types['E'].fields[2].mojom_name)\n+ self.assertEqual(3, types['E'].fields[2].numeric_value)\n+\n+ def testImplicitValues(self):\n+ \"\"\"Verifies basic automatic assignment of integral values at parse time.\"\"\"\n+ types = self.ExtractTypes('enum E { kFoo, kBar, kBaz };')\n+ self.assertEqual('kFoo', types['E'].fields[0].mojom_name)\n+ self.assertEqual(0, types['E'].fields[0].numeric_value)\n+ self.assertEqual('kBar', types['E'].fields[1].mojom_name)\n+ self.assertEqual(1, types['E'].fields[1].numeric_value)\n+ self.assertEqual('kBaz', types['E'].fields[2].mojom_name)\n+ self.assertEqual(2, types['E'].fields[2].numeric_value)\n+\n+ def testSameEnumReference(self):\n+ \"\"\"Verifies that an enum value can be assigned from the value of another\n+ field within the same enum.\"\"\"\n+ types = self.ExtractTypes('enum E { kA, kB, kFirst=kA };')\n+ self.assertEqual('kA', types['E'].fields[0].mojom_name)\n+ self.assertEqual(0, types['E'].fields[0].numeric_value)\n+ self.assertEqual('kB', types['E'].fields[1].mojom_name)\n+ self.assertEqual(1, types['E'].fields[1].numeric_value)\n+ self.assertEqual('kFirst', types['E'].fields[2].mojom_name)\n+ self.assertEqual(0, types['E'].fields[2].numeric_value)\n+\n+ def testSameModuleOtherEnumReference(self):\n+ \"\"\"Verifies that an enum value can be assigned from the value of a field\n+ in another enum within the same module.\"\"\"\n+ types = self.ExtractTypes('enum E { kA, kB }; enum F { kA = E.kB };')\n+ self.assertEqual(1, types['F'].fields[0].numeric_value)\n+\n+ def testImportedEnumReference(self):\n+ \"\"\"Verifies that an enum value can be assigned from the value of a field\n+ in another enum within a different module.\"\"\"\n+ a_mojom = 'a.mojom'\n+ self.WriteFile(a_mojom, 'module a; enum E { kFoo=42, kBar };')\n+ b_mojom = 'b.mojom'\n+ self.WriteFile(b_mojom,\n+ 'module b; import \"a.mojom\"; enum F { kFoo = a.E.kBar };')\n+ self.ParseMojoms([a_mojom, b_mojom])\n+ b = self.LoadModule(b_mojom)\n+\n+ self.assertEqual('F', b.enums[0].mojom_name)\n+ self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name)\n+ self.assertEqual(43, b.enums[0].fields[0].numeric_value)\n+\n+ def testConstantReference(self):\n+ \"\"\"Verifies that an enum value can be assigned from the value of an\n+ integral constant within the same module.\"\"\"\n+ types = self.ExtractTypes('const int32 kFoo = 42; enum E { kA = kFoo };')\n+ self.assertEqual(42, types['E'].fields[0].numeric_value)\n+\n+ def testInvalidConstantReference(self):\n+ \"\"\"Verifies that enum values cannot be assigned from the value of\n+ non-integral constants.\"\"\"\n+ with self.assertRaisesRegexp(ValueError, 'not an integer'):\n+ self.ExtractTypes('const float kFoo = 1.0; enum E { kA = kFoo };')\n+ with self.assertRaisesRegexp(ValueError, 'not an integer'):\n+ self.ExtractTypes('const double kFoo = 1.0; enum E { kA = kFoo };')\n+ with self.assertRaisesRegexp(ValueError, 'not an integer'):\n+ self.ExtractTypes('const string kFoo = \"lol\"; enum E { kA = kFoo };')\n+\n+ def testImportedConstantReference(self):\n+ \"\"\"Verifies that an enum value can be assigned from the value of an integral\n+ constant within an imported module.\"\"\"\n+ a_mojom = 'a.mojom'\n+ self.WriteFile(a_mojom, 'module a; const int32 kFoo = 37;')\n+ b_mojom = 'b.mojom'\n+ self.WriteFile(b_mojom,\n+ 'module b; import \"a.mojom\"; enum F { kFoo = a.kFoo };')\n+ self.ParseMojoms([a_mojom, b_mojom])\n+ b = self.LoadModule(b_mojom)\n+\n+ self.assertEqual('F', b.enums[0].mojom_name)\n+ self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name)\n+ self.assertEqual(37, b.enums[0].fields[0].numeric_value)\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn\nnew file mode 100644\nindex 00000000..7416ef19\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn\n@@ -0,0 +1,43 @@\n+# Copyright 2020 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+group(\"mojom\") {\n+ data = [\n+ \"__init__.py\",\n+ \"error.py\",\n+ \"fileutil.py\",\n+ \"generate/__init__.py\",\n+ \"generate/constant_resolver.py\",\n+ \"generate/generator.py\",\n+ \"generate/module.py\",\n+ \"generate/pack.py\",\n+ \"generate/template_expander.py\",\n+ \"generate/translate.py\",\n+ \"parse/__init__.py\",\n+ \"parse/ast.py\",\n+ \"parse/conditional_features.py\",\n+ \"parse/lexer.py\",\n+ \"parse/parser.py\",\n+\n+ # Third-party module dependencies\n+ \"//third_party/jinja2/\",\n+ \"//third_party/ply/\",\n+ ]\n+}\n+\n+group(\"tests\") {\n+ data = [\n+ \"fileutil_unittest.py\",\n+ \"generate/generator_unittest.py\",\n+ \"generate/module_unittest.py\",\n+ \"generate/pack_unittest.py\",\n+ \"generate/translate_unittest.py\",\n+ \"parse/ast_unittest.py\",\n+ \"parse/conditional_features_unittest.py\",\n+ \"parse/lexer_unittest.py\",\n+ \"parse/parser_unittest.py\",\n+ ]\n+\n+ public_deps = [ \":mojom\" ]\n+}\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/__init__.py b/utils/ipc/mojo/public/tools/mojom/mojom/__init__.py\nnew file mode 100644\nindex 00000000..e69de29b\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/error.py b/utils/ipc/mojo/public/tools/mojom/mojom/error.py\nnew file mode 100644\nindex 00000000..8a1e03da\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/error.py\n@@ -0,0 +1,28 @@\n+# Copyright 2014 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+\n+class Error(Exception):\n+ \"\"\"Base class for Mojo IDL bindings parser/generator errors.\"\"\"\n+\n+ def __init__(self, filename, message, lineno=None, addenda=None, **kwargs):\n+ \"\"\"|filename| is the (primary) file which caused the error, |message| is the\n+ error message, |lineno| is the 1-based line number (or |None| if not\n+ applicable/available), and |addenda| is a list of additional lines to append\n+ to the final error message.\"\"\"\n+ Exception.__init__(self, **kwargs)\n+ self.filename = filename\n+ self.message = message\n+ self.lineno = lineno\n+ self.addenda = addenda\n+\n+ def __str__(self):\n+ if self.lineno:\n+ s = \"%s:%d: Error: %s\" % (self.filename, self.lineno, self.message)\n+ else:\n+ s = \"%s: Error: %s\" % (self.filename, self.message)\n+ return \"\\n\".join([s] + self.addenda) if self.addenda else s\n+\n+ def __repr__(self):\n+ return str(self)\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py\nnew file mode 100644\nindex 00000000..bf626f54\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py\n@@ -0,0 +1,45 @@\n+# Copyright 2015 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import errno\n+import imp\n+import os.path\n+import sys\n+\n+\n+def _GetDirAbove(dirname):\n+ \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n+ also be \"above\" this file).\"\"\"\n+ path = os.path.abspath(__file__)\n+ while True:\n+ path, tail = os.path.split(path)\n+ if not tail:\n+ return None\n+ if tail == dirname:\n+ return path\n+\n+\n+def EnsureDirectoryExists(path, always_try_to_create=False):\n+ \"\"\"A wrapper for os.makedirs that does not error if the directory already\n+ exists. A different process could be racing to create this directory.\"\"\"\n+\n+ if not os.path.exists(path) or always_try_to_create:\n+ try:\n+ os.makedirs(path)\n+ except OSError as e:\n+ # There may have been a race to create this directory.\n+ if e.errno != errno.EEXIST:\n+ raise\n+\n+\n+def AddLocalRepoThirdPartyDirToModulePath():\n+ \"\"\"Helper function to find the top-level directory of this script's repository\n+ assuming the script falls somewhere within a 'mojo' directory, and insert the\n+ top-level 'third_party' directory early in the module search path. Used to\n+ ensure that third-party dependencies provided within the repository itself\n+ (e.g. Chromium sources include snapshots of jinja2 and ply) are preferred over\n+ locally installed system library packages.\"\"\"\n+ toplevel_dir = _GetDirAbove('mojo')\n+ if toplevel_dir:\n+ sys.path.insert(1, os.path.join(toplevel_dir, 'third_party'))\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py\nnew file mode 100644\nindex 00000000..ff5753a2\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py\n@@ -0,0 +1,40 @@\n+# Copyright 2015 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import imp\n+import os.path\n+import shutil\n+import sys\n+import tempfile\n+import unittest\n+\n+from mojom import fileutil\n+\n+\n+class FileUtilTest(unittest.TestCase):\n+ def testEnsureDirectoryExists(self):\n+ \"\"\"Test that EnsureDirectoryExists fuctions correctly.\"\"\"\n+\n+ temp_dir = tempfile.mkdtemp()\n+ try:\n+ self.assertTrue(os.path.exists(temp_dir))\n+\n+ # Directory does not exist, yet.\n+ full = os.path.join(temp_dir, \"foo\", \"bar\")\n+ self.assertFalse(os.path.exists(full))\n+\n+ # Create the directory.\n+ fileutil.EnsureDirectoryExists(full)\n+ self.assertTrue(os.path.exists(full))\n+\n+ # Trying to create it again does not cause an error.\n+ fileutil.EnsureDirectoryExists(full)\n+ self.assertTrue(os.path.exists(full))\n+\n+ # Bypass check for directory existence to tickle error handling that\n+ # occurs in response to a race.\n+ fileutil.EnsureDirectoryExists(full, always_try_to_create=True)\n+ self.assertTrue(os.path.exists(full))\n+ finally:\n+ shutil.rmtree(temp_dir)\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py\nnew file mode 100644\nindex 00000000..e69de29b\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py\nnew file mode 100644\nindex 00000000..0dfd996e\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py\n@@ -0,0 +1,93 @@\n+# Copyright 2015 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\"\"\"Resolves the values used for constants and enums.\"\"\"\n+\n+from itertools import ifilter\n+\n+from mojom.generate import module as mojom\n+\n+\n+def ResolveConstants(module, expression_to_text):\n+ in_progress = set()\n+ computed = set()\n+\n+ def GetResolvedValue(named_value):\n+ assert isinstance(named_value, (mojom.EnumValue, mojom.ConstantValue))\n+ if isinstance(named_value, mojom.EnumValue):\n+ field = next(\n+ ifilter(lambda field: field.name == named_value.name,\n+ named_value.enum.fields), None)\n+ if not field:\n+ raise RuntimeError(\n+ 'Unable to get computed value for field %s of enum %s' %\n+ (named_value.name, named_value.enum.name))\n+ if field not in computed:\n+ ResolveEnum(named_value.enum)\n+ return field.resolved_value\n+ else:\n+ ResolveConstant(named_value.constant)\n+ named_value.resolved_value = named_value.constant.resolved_value\n+ return named_value.resolved_value\n+\n+ def ResolveConstant(constant):\n+ if constant in computed:\n+ return\n+ if constant in in_progress:\n+ raise RuntimeError('Circular dependency for constant: %s' % constant.name)\n+ in_progress.add(constant)\n+ if isinstance(constant.value, (mojom.EnumValue, mojom.ConstantValue)):\n+ resolved_value = GetResolvedValue(constant.value)\n+ else:\n+ resolved_value = expression_to_text(constant.value)\n+ constant.resolved_value = resolved_value\n+ in_progress.remove(constant)\n+ computed.add(constant)\n+\n+ def ResolveEnum(enum):\n+ def ResolveEnumField(enum, field, default_value):\n+ if field in computed:\n+ return\n+ if field in in_progress:\n+ raise RuntimeError('Circular dependency for enum: %s' % enum.name)\n+ in_progress.add(field)\n+ if field.value:\n+ if isinstance(field.value, mojom.EnumValue):\n+ resolved_value = GetResolvedValue(field.value)\n+ elif isinstance(field.value, str):\n+ resolved_value = int(field.value, 0)\n+ else:\n+ raise RuntimeError('Unexpected value: %s' % field.value)\n+ else:\n+ resolved_value = default_value\n+ field.resolved_value = resolved_value\n+ in_progress.remove(field)\n+ computed.add(field)\n+\n+ current_value = 0\n+ for field in enum.fields:\n+ ResolveEnumField(enum, field, current_value)\n+ current_value = field.resolved_value + 1\n+\n+ for constant in module.constants:\n+ ResolveConstant(constant)\n+\n+ for enum in module.enums:\n+ ResolveEnum(enum)\n+\n+ for struct in module.structs:\n+ for constant in struct.constants:\n+ ResolveConstant(constant)\n+ for enum in struct.enums:\n+ ResolveEnum(enum)\n+ for field in struct.fields:\n+ if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)):\n+ field.default.resolved_value = GetResolvedValue(field.default)\n+\n+ for interface in module.interfaces:\n+ for constant in interface.constants:\n+ ResolveConstant(constant)\n+ for enum in interface.enums:\n+ ResolveEnum(enum)\n+\n+ return module\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py\nnew file mode 100644\nindex 00000000..de62260a\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py\n@@ -0,0 +1,325 @@\n+# Copyright 2013 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\"\"\"Code shared by the various language-specific code generators.\"\"\"\n+\n+from __future__ import print_function\n+\n+from functools import partial\n+import os.path\n+import re\n+\n+from mojom import fileutil\n+from mojom.generate import module as mojom\n+from mojom.generate import pack\n+\n+\n+def ExpectedArraySize(kind):\n+ if mojom.IsArrayKind(kind):\n+ return kind.length\n+ return None\n+\n+\n+def SplitCamelCase(identifier):\n+ \"\"\"Splits a camel-cased |identifier| and returns a list of lower-cased\n+ strings.\n+ \"\"\"\n+ # Add underscores after uppercase letters when appropriate. An uppercase\n+ # letter is considered the end of a word if it is followed by an upper and a\n+ # lower. E.g. URLLoaderFactory -> URL_LoaderFactory\n+ identifier = re.sub('([A-Z][0-9]*)(?=[A-Z][0-9]*[a-z])', r'\\1_', identifier)\n+ # Add underscores after lowercase letters when appropriate. A lowercase letter\n+ # is considered the end of a word if it is followed by an upper.\n+ # E.g. URLLoaderFactory -> URLLoader_Factory\n+ identifier = re.sub('([a-z][0-9]*)(?=[A-Z])', r'\\1_', identifier)\n+ return [x.lower() for x in identifier.split('_')]\n+\n+\n+def ToCamel(identifier, lower_initial=False, digits_split=False, delimiter='_'):\n+ \"\"\"Splits |identifier| using |delimiter|, makes the first character of each\n+ word uppercased (but makes the first character of the first word lowercased\n+ if |lower_initial| is set to True), and joins the words. Please note that for\n+ each word, all the characters except the first one are untouched.\n+ \"\"\"\n+ result = ''\n+ capitalize_next = True\n+ for i in range(len(identifier)):\n+ if identifier[i] == delimiter:\n+ capitalize_next = True\n+ elif digits_split and identifier[i].isdigit():\n+ capitalize_next = True\n+ result += identifier[i]\n+ elif capitalize_next:\n+ capitalize_next = False\n+ result += identifier[i].upper()\n+ else:\n+ result += identifier[i]\n+\n+ if lower_initial and result:\n+ result = result[0].lower() + result[1:]\n+\n+ return result\n+\n+\n+def _ToSnakeCase(identifier, upper=False):\n+ \"\"\"Splits camel-cased |identifier| into lower case words, removes the first\n+ word if it's \"k\" and joins them using \"_\" e.g. for \"URLLoaderFactory\", returns\n+ \"URL_LOADER_FACTORY\" if upper, otherwise \"url_loader_factory\".\n+ \"\"\"\n+ words = SplitCamelCase(identifier)\n+ if words[0] == 'k' and len(words) > 1:\n+ words = words[1:]\n+\n+ # Variables cannot start with a digit\n+ if (words[0][0].isdigit()):\n+ words[0] = '_' + words[0]\n+\n+\n+ if upper:\n+ words = map(lambda x: x.upper(), words)\n+\n+ return '_'.join(words)\n+\n+\n+def ToUpperSnakeCase(identifier):\n+ \"\"\"Splits camel-cased |identifier| into lower case words, removes the first\n+ word if it's \"k\" and joins them using \"_\" e.g. for \"URLLoaderFactory\", returns\n+ \"URL_LOADER_FACTORY\".\n+ \"\"\"\n+ return _ToSnakeCase(identifier, upper=True)\n+\n+\n+def ToLowerSnakeCase(identifier):\n+ \"\"\"Splits camel-cased |identifier| into lower case words, removes the first\n+ word if it's \"k\" and joins them using \"_\" e.g. for \"URLLoaderFactory\", returns\n+ \"url_loader_factory\".\n+ \"\"\"\n+ return _ToSnakeCase(identifier, upper=False)\n+\n+\n+class Stylizer(object):\n+ \"\"\"Stylizers specify naming rules to map mojom names to names in generated\n+ code. For example, if you would like method_name in mojom to be mapped to\n+ MethodName in the generated code, you need to define a subclass of Stylizer\n+ and override StylizeMethod to do the conversion.\"\"\"\n+\n+ def StylizeConstant(self, mojom_name):\n+ return mojom_name\n+\n+ def StylizeField(self, mojom_name):\n+ return mojom_name\n+\n+ def StylizeStruct(self, mojom_name):\n+ return mojom_name\n+\n+ def StylizeUnion(self, mojom_name):\n+ return mojom_name\n+\n+ def StylizeParameter(self, mojom_name):\n+ return mojom_name\n+\n+ def StylizeMethod(self, mojom_name):\n+ return mojom_name\n+\n+ def StylizeInterface(self, mojom_name):\n+ return mojom_name\n+\n+ def StylizeEnumField(self, mojom_name):\n+ return mojom_name\n+\n+ def StylizeEnum(self, mojom_name):\n+ return mojom_name\n+\n+ def StylizeModule(self, mojom_namespace):\n+ return mojom_namespace\n+\n+\n+def WriteFile(contents, full_path):\n+ # If |contents| is same with the file content, we skip updating.\n+ if os.path.isfile(full_path):\n+ with open(full_path, 'rb') as destination_file:\n+ if destination_file.read() == contents:\n+ return\n+\n+ # Make sure the containing directory exists.\n+ full_dir = os.path.dirname(full_path)\n+ fileutil.EnsureDirectoryExists(full_dir)\n+\n+ # Dump the data to disk.\n+ with open(full_path, \"wb\") as f:\n+ if not isinstance(contents, bytes):\n+ f.write(contents.encode('utf-8'))\n+ else:\n+ f.write(contents)\n+\n+\n+def AddComputedData(module):\n+ \"\"\"Adds computed data to the given module. The data is computed once and\n+ used repeatedly in the generation process.\"\"\"\n+\n+ def _AddStructComputedData(exported, struct):\n+ struct.packed = pack.PackedStruct(struct)\n+ struct.bytes = pack.GetByteLayout(struct.packed)\n+ struct.versions = pack.GetVersionInfo(struct.packed)\n+ struct.exported = exported\n+\n+ def _AddInterfaceComputedData(interface):\n+ interface.version = 0\n+ for method in interface.methods:\n+ # this field is never scrambled\n+ method.sequential_ordinal = method.ordinal\n+\n+ if method.min_version is not None:\n+ interface.version = max(interface.version, method.min_version)\n+\n+ method.param_struct = _GetStructFromMethod(method)\n+ if interface.stable:\n+ method.param_struct.attributes[mojom.ATTRIBUTE_STABLE] = True\n+ if method.explicit_ordinal is None:\n+ raise Exception(\n+ 'Stable interfaces must declare explicit method ordinals. The '\n+ 'method %s on stable interface %s does not declare an explicit '\n+ 'ordinal.' % (method.mojom_name, interface.qualified_name))\n+ interface.version = max(interface.version,\n+ method.param_struct.versions[-1].version)\n+\n+ if method.response_parameters is not None:\n+ method.response_param_struct = _GetResponseStructFromMethod(method)\n+ if interface.stable:\n+ method.response_param_struct.attributes[mojom.ATTRIBUTE_STABLE] = True\n+ interface.version = max(\n+ interface.version,\n+ method.response_param_struct.versions[-1].version)\n+ else:\n+ method.response_param_struct = None\n+\n+ def _GetStructFromMethod(method):\n+ \"\"\"Converts a method's parameters into the fields of a struct.\"\"\"\n+ params_class = \"%s_%s_Params\" % (method.interface.mojom_name,\n+ method.mojom_name)\n+ struct = mojom.Struct(params_class,\n+ module=method.interface.module,\n+ attributes={})\n+ for param in method.parameters:\n+ struct.AddField(\n+ param.mojom_name,\n+ param.kind,\n+ param.ordinal,\n+ attributes=param.attributes)\n+ _AddStructComputedData(False, struct)\n+ return struct\n+\n+ def _GetResponseStructFromMethod(method):\n+ \"\"\"Converts a method's response_parameters into the fields of a struct.\"\"\"\n+ params_class = \"%s_%s_ResponseParams\" % (method.interface.mojom_name,\n+ method.mojom_name)\n+ struct = mojom.Struct(params_class,\n+ module=method.interface.module,\n+ attributes={})\n+ for param in method.response_parameters:\n+ struct.AddField(\n+ param.mojom_name,\n+ param.kind,\n+ param.ordinal,\n+ attributes=param.attributes)\n+ _AddStructComputedData(False, struct)\n+ return struct\n+\n+ for struct in module.structs:\n+ _AddStructComputedData(True, struct)\n+ for interface in module.interfaces:\n+ _AddInterfaceComputedData(interface)\n+\n+\n+class Generator(object):\n+ # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all\n+ # files to stdout.\n+ def __init__(self,\n+ module,\n+ output_dir=None,\n+ typemap=None,\n+ variant=None,\n+ bytecode_path=None,\n+ for_blink=False,\n+ js_bindings_mode=\"new\",\n+ js_generate_struct_deserializers=False,\n+ export_attribute=None,\n+ export_header=None,\n+ generate_non_variant_code=False,\n+ support_lazy_serialization=False,\n+ disallow_native_types=False,\n+ disallow_interfaces=False,\n+ generate_message_ids=False,\n+ generate_fuzzing=False,\n+ enable_kythe_annotations=False,\n+ extra_cpp_template_paths=None,\n+ generate_extra_cpp_only=False):\n+ self.module = module\n+ self.output_dir = output_dir\n+ self.typemap = typemap or {}\n+ self.variant = variant\n+ self.bytecode_path = bytecode_path\n+ self.for_blink = for_blink\n+ self.js_bindings_mode = js_bindings_mode\n+ self.js_generate_struct_deserializers = js_generate_struct_deserializers\n+ self.export_attribute = export_attribute\n+ self.export_header = export_header\n+ self.generate_non_variant_code = generate_non_variant_code\n+ self.support_lazy_serialization = support_lazy_serialization\n+ self.disallow_native_types = disallow_native_types\n+ self.disallow_interfaces = disallow_interfaces\n+ self.generate_message_ids = generate_message_ids\n+ self.generate_fuzzing = generate_fuzzing\n+ self.enable_kythe_annotations = enable_kythe_annotations\n+ self.extra_cpp_template_paths = extra_cpp_template_paths\n+ self.generate_extra_cpp_only = generate_extra_cpp_only\n+\n+ def Write(self, contents, filename):\n+ if self.output_dir is None:\n+ print(contents)\n+ return\n+ full_path = os.path.join(self.output_dir, filename)\n+ WriteFile(contents, full_path)\n+\n+ def OptimizeEmpty(self, contents):\n+ # Look for .cc files that contain no actual code. There are many of these\n+ # and they collectively take a while to compile.\n+ lines = contents.splitlines()\n+\n+ for line in lines:\n+ if line.startswith('#') or line.startswith('//'):\n+ continue\n+ if re.match(r'namespace .* {', line) or re.match(r'}.*//.*namespace',\n+ line):\n+ continue\n+ if line.strip():\n+ # There is some actual code - return the unmodified contents.\n+ return contents\n+\n+ # If we reach here then we have a .cc file with no actual code. The\n+ # includes are therefore unneeded and can be removed.\n+ new_lines = [line for line in lines if not line.startswith('#include')]\n+ if len(new_lines) < len(lines):\n+ new_lines.append('')\n+ new_lines.append('// Includes removed due to no code being generated.')\n+ return '\\n'.join(new_lines)\n+\n+ def WriteWithComment(self, contents, filename):\n+ generator_name = \"mojom_bindings_generator.py\"\n+ comment = r\"// %s is auto generated by %s, do not edit\" % (filename,\n+ generator_name)\n+ contents = comment + '\\n' + '\\n' + contents;\n+ if filename.endswith('.cc'):\n+ contents = self.OptimizeEmpty(contents)\n+ self.Write(contents, filename)\n+\n+ def GenerateFiles(self, args):\n+ raise NotImplementedError(\"Subclasses must override/implement this method\")\n+\n+ def GetJinjaParameters(self):\n+ \"\"\"Returns default constructor parameters for the jinja environment.\"\"\"\n+ return {}\n+\n+ def GetGlobals(self):\n+ \"\"\"Returns global mappings for the template generation.\"\"\"\n+ return {}\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py\nnew file mode 100644\nindex 00000000..32c884a8\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py\n@@ -0,0 +1,74 @@\n+# Copyright 2014 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import imp\n+import os.path\n+import sys\n+import unittest\n+\n+\n+def _GetDirAbove(dirname):\n+ \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n+ also be \"above\" this file).\"\"\"\n+ path = os.path.abspath(__file__)\n+ while True:\n+ path, tail = os.path.split(path)\n+ assert tail\n+ if tail == dirname:\n+ return path\n+\n+\n+try:\n+ imp.find_module(\"mojom\")\n+except ImportError:\n+ sys.path.append(os.path.join(_GetDirAbove(\"pylib\"), \"pylib\"))\n+from mojom.generate import generator\n+\n+\n+class StringManipulationTest(unittest.TestCase):\n+ \"\"\"generator contains some string utilities, this tests only those.\"\"\"\n+\n+ def testSplitCamelCase(self):\n+ self.assertEquals([\"camel\", \"case\"], generator.SplitCamelCase(\"CamelCase\"))\n+ self.assertEquals([\"url\", \"loader\", \"factory\"],\n+ generator.SplitCamelCase('URLLoaderFactory'))\n+ self.assertEquals([\"get99\", \"entries\"],\n+ generator.SplitCamelCase('Get99Entries'))\n+ self.assertEquals([\"get99entries\"],\n+ generator.SplitCamelCase('Get99entries'))\n+\n+ def testToCamel(self):\n+ self.assertEquals(\"CamelCase\", generator.ToCamel(\"camel_case\"))\n+ self.assertEquals(\"CAMELCASE\", generator.ToCamel(\"CAMEL_CASE\"))\n+ self.assertEquals(\"camelCase\",\n+ generator.ToCamel(\"camel_case\", lower_initial=True))\n+ self.assertEquals(\"CamelCase\", generator.ToCamel(\n+ \"camel case\", delimiter=' '))\n+ self.assertEquals(\"CaMelCaSe\", generator.ToCamel(\"caMel_caSe\"))\n+ self.assertEquals(\"L2Tp\", generator.ToCamel(\"l2tp\", digits_split=True))\n+ self.assertEquals(\"l2tp\", generator.ToCamel(\"l2tp\", lower_initial=True))\n+\n+ def testToSnakeCase(self):\n+ self.assertEquals(\"snake_case\", generator.ToLowerSnakeCase(\"SnakeCase\"))\n+ self.assertEquals(\"snake_case\", generator.ToLowerSnakeCase(\"snakeCase\"))\n+ self.assertEquals(\"snake_case\", generator.ToLowerSnakeCase(\"SnakeCASE\"))\n+ self.assertEquals(\"snake_d3d11_case\",\n+ generator.ToLowerSnakeCase(\"SnakeD3D11Case\"))\n+ self.assertEquals(\"snake_d3d11_case\",\n+ generator.ToLowerSnakeCase(\"SnakeD3d11Case\"))\n+ self.assertEquals(\"snake_d3d11_case\",\n+ generator.ToLowerSnakeCase(\"snakeD3d11Case\"))\n+ self.assertEquals(\"SNAKE_CASE\", generator.ToUpperSnakeCase(\"SnakeCase\"))\n+ self.assertEquals(\"SNAKE_CASE\", generator.ToUpperSnakeCase(\"snakeCase\"))\n+ self.assertEquals(\"SNAKE_CASE\", generator.ToUpperSnakeCase(\"SnakeCASE\"))\n+ self.assertEquals(\"SNAKE_D3D11_CASE\",\n+ generator.ToUpperSnakeCase(\"SnakeD3D11Case\"))\n+ self.assertEquals(\"SNAKE_D3D11_CASE\",\n+ generator.ToUpperSnakeCase(\"SnakeD3d11Case\"))\n+ self.assertEquals(\"SNAKE_D3D11_CASE\",\n+ generator.ToUpperSnakeCase(\"snakeD3d11Case\"))\n+\n+\n+if __name__ == \"__main__\":\n+ unittest.main()\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py\nnew file mode 100644\nindex 00000000..8547ff64\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py\n@@ -0,0 +1,1635 @@\n+# Copyright 2013 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+# This module's classes provide an interface to mojo modules. Modules are\n+# collections of interfaces and structs to be used by mojo ipc clients and\n+# servers.\n+#\n+# A simple interface would be created this way:\n+# module = mojom.generate.module.Module('Foo')\n+# interface = module.AddInterface('Bar')\n+# method = interface.AddMethod('Tat', 0)\n+# method.AddParameter('baz', 0, mojom.INT32)\n+\n+import pickle\n+\n+# We use our own version of __repr__ when displaying the AST, as the\n+# AST currently doesn't capture which nodes are reference (e.g. to\n+# types) and which nodes are definitions. This allows us to e.g. print\n+# the definition of a struct when it's defined inside a module, but\n+# only print its name when it's referenced in e.g. a method parameter.\n+def Repr(obj, as_ref=True):\n+ \"\"\"A version of __repr__ that can distinguish references.\n+\n+ Sometimes we like to print an object's full representation\n+ (e.g. with its fields) and sometimes we just want to reference an\n+ object that was printed in full elsewhere. This function allows us\n+ to make that distinction.\n+\n+ Args:\n+ obj: The object whose string representation we compute.\n+ as_ref: If True, use the short reference representation.\n+\n+ Returns:\n+ A str representation of |obj|.\n+ \"\"\"\n+ if hasattr(obj, 'Repr'):\n+ return obj.Repr(as_ref=as_ref)\n+ # Since we cannot implement Repr for existing container types, we\n+ # handle them here.\n+ elif isinstance(obj, list):\n+ if not obj:\n+ return '[]'\n+ else:\n+ return ('[\\n%s\\n]' % (',\\n'.join(\n+ ' %s' % Repr(elem, as_ref).replace('\\n', '\\n ')\n+ for elem in obj)))\n+ elif isinstance(obj, dict):\n+ if not obj:\n+ return '{}'\n+ else:\n+ return ('{\\n%s\\n}' % (',\\n'.join(\n+ ' %s: %s' % (Repr(key, as_ref).replace('\\n', '\\n '),\n+ Repr(val, as_ref).replace('\\n', '\\n '))\n+ for key, val in obj.items())))\n+ else:\n+ return repr(obj)\n+\n+\n+def GenericRepr(obj, names):\n+ \"\"\"Compute generic Repr for |obj| based on the attributes in |names|.\n+\n+ Args:\n+ obj: The object to compute a Repr for.\n+ names: A dict from attribute names to include, to booleans\n+ specifying whether those attributes should be shown as\n+ references or not.\n+\n+ Returns:\n+ A str representation of |obj|.\n+ \"\"\"\n+\n+ def ReprIndent(name, as_ref):\n+ return ' %s=%s' % (name, Repr(getattr(obj, name), as_ref).replace(\n+ '\\n', '\\n '))\n+\n+ return '%s(\\n%s\\n)' % (obj.__class__.__name__, ',\\n'.join(\n+ ReprIndent(name, as_ref) for (name, as_ref) in names.items()))\n+\n+\n+class Kind(object):\n+ \"\"\"Kind represents a type (e.g. int8, string).\n+\n+ Attributes:\n+ spec: A string uniquely identifying the type. May be None.\n+ module: {Module} The defining module. Set to None for built-in types.\n+ parent_kind: The enclosing type. For example, an enum defined\n+ inside an interface has that interface as its parent. May be None.\n+ \"\"\"\n+\n+ def __init__(self, spec=None, module=None):\n+ self.spec = spec\n+ self.module = module\n+ self.parent_kind = None\n+\n+ def Repr(self, as_ref=True):\n+ # pylint: disable=unused-argument\n+ return '<%s spec=%r>' % (self.__class__.__name__, self.spec)\n+\n+ def __repr__(self):\n+ # Gives us a decent __repr__ for all kinds.\n+ return self.Repr()\n+\n+ def __eq__(self, rhs):\n+ # pylint: disable=unidiomatic-typecheck\n+ return (type(self) == type(rhs)\n+ and (self.spec, self.parent_kind) == (rhs.spec, rhs.parent_kind))\n+\n+ def __hash__(self):\n+ # TODO(crbug.com/1060471): Remove this and other __hash__ methods on Kind\n+ # and its subclasses. This is to support existing generator code which uses\n+ # some primitive Kinds as dict keys. The default hash (object identity)\n+ # breaks these dicts when a pickled Module instance is unpickled and used\n+ # during a subsequent run of the parser.\n+ return hash((self.spec, self.parent_kind))\n+\n+\n+class ReferenceKind(Kind):\n+ \"\"\"ReferenceKind represents pointer and handle types.\n+\n+ A type is nullable if null (for pointer types) or invalid handle (for handle\n+ types) is a legal value for the type.\n+\n+ Attributes:\n+ is_nullable: True if the type is nullable.\n+ \"\"\"\n+\n+ def __init__(self, spec=None, is_nullable=False, module=None):\n+ assert spec is None or is_nullable == spec.startswith('?')\n+ Kind.__init__(self, spec, module)\n+ self.is_nullable = is_nullable\n+ self.shared_definition = {}\n+\n+ def Repr(self, as_ref=True):\n+ return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec,\n+ self.is_nullable)\n+\n+ def MakeNullableKind(self):\n+ assert not self.is_nullable\n+\n+ if self == STRING:\n+ return NULLABLE_STRING\n+ if self == HANDLE:\n+ return NULLABLE_HANDLE\n+ if self == DCPIPE:\n+ return NULLABLE_DCPIPE\n+ if self == DPPIPE:\n+ return NULLABLE_DPPIPE\n+ if self == MSGPIPE:\n+ return NULLABLE_MSGPIPE\n+ if self == SHAREDBUFFER:\n+ return NULLABLE_SHAREDBUFFER\n+ if self == PLATFORMHANDLE:\n+ return NULLABLE_PLATFORMHANDLE\n+\n+ nullable_kind = type(self)()\n+ nullable_kind.shared_definition = self.shared_definition\n+ if self.spec is not None:\n+ nullable_kind.spec = '?' + self.spec\n+ nullable_kind.is_nullable = True\n+ nullable_kind.parent_kind = self.parent_kind\n+ nullable_kind.module = self.module\n+\n+ return nullable_kind\n+\n+ @classmethod\n+ def AddSharedProperty(cls, name):\n+ \"\"\"Adds a property |name| to |cls|, which accesses the corresponding item in\n+ |shared_definition|.\n+\n+ The reason of adding such indirection is to enable sharing definition\n+ between a reference kind and its nullable variation. For example:\n+ a = Struct('test_struct_1')\n+ b = a.MakeNullableKind()\n+ a.name = 'test_struct_2'\n+ print(b.name) # Outputs 'test_struct_2'.\n+ \"\"\"\n+\n+ def Get(self):\n+ try:\n+ return self.shared_definition[name]\n+ except KeyError: # Must raise AttributeError if property doesn't exist.\n+ raise AttributeError\n+\n+ def Set(self, value):\n+ self.shared_definition[name] = value\n+\n+ setattr(cls, name, property(Get, Set))\n+\n+ def __eq__(self, rhs):\n+ return (isinstance(rhs, ReferenceKind)\n+ and super(ReferenceKind, self).__eq__(rhs)\n+ and self.is_nullable == rhs.is_nullable)\n+\n+ def __hash__(self):\n+ return hash((super(ReferenceKind, self).__hash__(), self.is_nullable))\n+\n+\n+# Initialize the set of primitive types. These can be accessed by clients.\n+BOOL = Kind('b')\n+INT8 = Kind('i8')\n+INT16 = Kind('i16')\n+INT32 = Kind('i32')\n+INT64 = Kind('i64')\n+UINT8 = Kind('u8')\n+UINT16 = Kind('u16')\n+UINT32 = Kind('u32')\n+UINT64 = Kind('u64')\n+FLOAT = Kind('f')\n+DOUBLE = Kind('d')\n+STRING = ReferenceKind('s')\n+HANDLE = ReferenceKind('h')\n+DCPIPE = ReferenceKind('h:d:c')\n+DPPIPE = ReferenceKind('h:d:p')\n+MSGPIPE = ReferenceKind('h:m')\n+SHAREDBUFFER = ReferenceKind('h:s')\n+PLATFORMHANDLE = ReferenceKind('h:p')\n+NULLABLE_STRING = ReferenceKind('?s', True)\n+NULLABLE_HANDLE = ReferenceKind('?h', True)\n+NULLABLE_DCPIPE = ReferenceKind('?h:d:c', True)\n+NULLABLE_DPPIPE = ReferenceKind('?h:d:p', True)\n+NULLABLE_MSGPIPE = ReferenceKind('?h:m', True)\n+NULLABLE_SHAREDBUFFER = ReferenceKind('?h:s', True)\n+NULLABLE_PLATFORMHANDLE = ReferenceKind('?h:p', True)\n+\n+# Collection of all Primitive types\n+PRIMITIVES = (\n+ BOOL,\n+ INT8,\n+ INT16,\n+ INT32,\n+ INT64,\n+ UINT8,\n+ UINT16,\n+ UINT32,\n+ UINT64,\n+ FLOAT,\n+ DOUBLE,\n+ STRING,\n+ HANDLE,\n+ DCPIPE,\n+ DPPIPE,\n+ MSGPIPE,\n+ SHAREDBUFFER,\n+ PLATFORMHANDLE,\n+ NULLABLE_STRING,\n+ NULLABLE_HANDLE,\n+ NULLABLE_DCPIPE,\n+ NULLABLE_DPPIPE,\n+ NULLABLE_MSGPIPE,\n+ NULLABLE_SHAREDBUFFER,\n+ NULLABLE_PLATFORMHANDLE,\n+)\n+\n+ATTRIBUTE_MIN_VERSION = 'MinVersion'\n+ATTRIBUTE_EXTENSIBLE = 'Extensible'\n+ATTRIBUTE_STABLE = 'Stable'\n+ATTRIBUTE_SYNC = 'Sync'\n+\n+\n+class NamedValue(object):\n+ def __init__(self, module, parent_kind, mojom_name):\n+ self.module = module\n+ self.parent_kind = parent_kind\n+ self.mojom_name = mojom_name\n+\n+ def GetSpec(self):\n+ return (self.module.GetNamespacePrefix() +\n+ (self.parent_kind and\n+ (self.parent_kind.mojom_name + '.') or \"\") + self.mojom_name)\n+\n+ def __eq__(self, rhs):\n+ return (isinstance(rhs, NamedValue)\n+ and (self.parent_kind, self.mojom_name) == (rhs.parent_kind,\n+ rhs.mojom_name))\n+\n+\n+class BuiltinValue(object):\n+ def __init__(self, value):\n+ self.value = value\n+\n+ def __eq__(self, rhs):\n+ return isinstance(rhs, BuiltinValue) and self.value == rhs.value\n+\n+\n+class ConstantValue(NamedValue):\n+ def __init__(self, module, parent_kind, constant):\n+ NamedValue.__init__(self, module, parent_kind, constant.mojom_name)\n+ self.constant = constant\n+\n+ @property\n+ def name(self):\n+ return self.constant.name\n+\n+\n+class EnumValue(NamedValue):\n+ def __init__(self, module, enum, field):\n+ NamedValue.__init__(self, module, enum.parent_kind, field.mojom_name)\n+ self.field = field\n+ self.enum = enum\n+\n+ def GetSpec(self):\n+ return (self.module.GetNamespacePrefix() +\n+ (self.parent_kind and (self.parent_kind.mojom_name + '.') or \"\") +\n+ self.enum.mojom_name + '.' + self.mojom_name)\n+\n+ @property\n+ def name(self):\n+ return self.field.name\n+\n+\n+class Constant(object):\n+ def __init__(self, mojom_name=None, kind=None, value=None, parent_kind=None):\n+ self.mojom_name = mojom_name\n+ self.name = None\n+ self.kind = kind\n+ self.value = value\n+ self.parent_kind = parent_kind\n+\n+ def Stylize(self, stylizer):\n+ self.name = stylizer.StylizeConstant(self.mojom_name)\n+\n+ def __eq__(self, rhs):\n+ return (isinstance(rhs, Constant)\n+ and (self.mojom_name, self.kind, self.value,\n+ self.parent_kind) == (rhs.mojom_name, rhs.kind, rhs.value,\n+ rhs.parent_kind))\n+\n+\n+class Field(object):\n+ def __init__(self,\n+ mojom_name=None,\n+ kind=None,\n+ ordinal=None,\n+ default=None,\n+ attributes=None):\n+ if self.__class__.__name__ == 'Field':\n+ raise Exception()\n+ self.mojom_name = mojom_name\n+ self.name = None\n+ self.kind = kind\n+ self.ordinal = ordinal\n+ self.default = default\n+ self.attributes = attributes\n+\n+ def Repr(self, as_ref=True):\n+ # pylint: disable=unused-argument\n+ # Fields are only referenced by objects which define them and thus\n+ # they are always displayed as non-references.\n+ return GenericRepr(self, {'mojom_name': False, 'kind': True})\n+\n+ def Stylize(self, stylizer):\n+ self.name = stylizer.StylizeField(self.mojom_name)\n+\n+ @property\n+ def min_version(self):\n+ return self.attributes.get(ATTRIBUTE_MIN_VERSION) \\\n+ if self.attributes else None\n+\n+ def __eq__(self, rhs):\n+ return (isinstance(rhs, Field)\n+ and (self.mojom_name, self.kind, self.ordinal, self.default,\n+ self.attributes) == (rhs.mojom_name, rhs.kind, rhs.ordinal,\n+ rhs.default, rhs.attributes))\n+\n+ def __hash__(self):\n+ return hash((self.mojom_name, self.kind, self.ordinal, self.default))\n+\n+\n+class StructField(Field):\n+ pass\n+\n+\n+class UnionField(Field):\n+ pass\n+\n+\n+def _IsFieldBackwardCompatible(new_field, old_field):\n+ if (new_field.min_version or 0) != (old_field.min_version or 0):\n+ return False\n+\n+ if isinstance(new_field.kind, (Enum, Struct, Union)):\n+ return new_field.kind.IsBackwardCompatible(old_field.kind)\n+\n+ return new_field.kind == old_field.kind\n+\n+\n+class Struct(ReferenceKind):\n+ \"\"\"A struct with typed fields.\n+\n+ Attributes:\n+ mojom_name: {str} The name of the struct type as defined in mojom.\n+ name: {str} The stylized name.\n+ native_only: {bool} Does the struct have a body (i.e. any fields) or is it\n+ purely a native struct.\n+ custom_serializer: {bool} Should we generate a serializer for the struct or\n+ will one be provided by non-generated code.\n+ fields: {List[StructField]} The members of the struct.\n+ enums: {List[Enum]} The enums defined in the struct scope.\n+ constants: {List[Constant]} The constants defined in the struct scope.\n+ attributes: {dict} Additional information about the struct, such as\n+ if it's a native struct.\n+ \"\"\"\n+\n+ ReferenceKind.AddSharedProperty('mojom_name')\n+ ReferenceKind.AddSharedProperty('name')\n+ ReferenceKind.AddSharedProperty('native_only')\n+ ReferenceKind.AddSharedProperty('custom_serializer')\n+ ReferenceKind.AddSharedProperty('fields')\n+ ReferenceKind.AddSharedProperty('enums')\n+ ReferenceKind.AddSharedProperty('constants')\n+ ReferenceKind.AddSharedProperty('attributes')\n+\n+ def __init__(self, mojom_name=None, module=None, attributes=None):\n+ if mojom_name is not None:\n+ spec = 'x:' + mojom_name\n+ else:\n+ spec = None\n+ ReferenceKind.__init__(self, spec, False, module)\n+ self.mojom_name = mojom_name\n+ self.name = None\n+ self.native_only = False\n+ self.custom_serializer = False\n+ self.fields = []\n+ self.enums = []\n+ self.constants = []\n+ self.attributes = attributes\n+\n+ def Repr(self, as_ref=True):\n+ if as_ref:\n+ return '<%s mojom_name=%r module=%s>' % (self.__class__.__name__,\n+ self.mojom_name,\n+ Repr(self.module, as_ref=True))\n+ else:\n+ return GenericRepr(self, {\n+ 'mojom_name': False,\n+ 'fields': False,\n+ 'module': True\n+ })\n+\n+ def AddField(self,\n+ mojom_name,\n+ kind,\n+ ordinal=None,\n+ default=None,\n+ attributes=None):\n+ field = StructField(mojom_name, kind, ordinal, default, attributes)\n+ self.fields.append(field)\n+ return field\n+\n+ def Stylize(self, stylizer):\n+ self.name = stylizer.StylizeStruct(self.mojom_name)\n+ for field in self.fields:\n+ field.Stylize(stylizer)\n+ for enum in self.enums:\n+ enum.Stylize(stylizer)\n+ for constant in self.constants:\n+ constant.Stylize(stylizer)\n+\n+ def IsBackwardCompatible(self, older_struct):\n+ \"\"\"This struct is backward-compatible with older_struct if and only if all\n+ of the following conditions hold:\n+ - Any newly added field is tagged with a [MinVersion] attribute specifying\n+ a version number greater than all previously used [MinVersion]\n+ attributes within the struct.\n+ - All fields present in older_struct remain present in the new struct,\n+ with the same ordinal position, same optional or non-optional status,\n+ same (or backward-compatible) type and where applicable, the same\n+ [MinVersion] attribute value.\n+ - All [MinVersion] attributes must be non-decreasing in ordinal order.\n+ - All reference-typed (string, array, map, struct, or union) fields tagged\n+ with a [MinVersion] greater than zero must be optional.\n+ \"\"\"\n+\n+ def buildOrdinalFieldMap(struct):\n+ fields_by_ordinal = {}\n+ for field in struct.fields:\n+ if field.ordinal in fields_by_ordinal:\n+ raise Exception('Multiple fields with ordinal %s in struct %s.' %\n+ (field.ordinal, struct.mojom_name))\n+ fields_by_ordinal[field.ordinal] = field\n+ return fields_by_ordinal\n+\n+ new_fields = buildOrdinalFieldMap(self)\n+ old_fields = buildOrdinalFieldMap(older_struct)\n+ if len(new_fields) < len(old_fields):\n+ # At least one field was removed, which is not OK.\n+ return False\n+\n+ # If there are N fields, existing ordinal values must exactly cover the\n+ # range from 0 to N-1.\n+ num_old_ordinals = len(old_fields)\n+ max_old_min_version = 0\n+ for ordinal in range(num_old_ordinals):\n+ new_field = new_fields[ordinal]\n+ old_field = old_fields[ordinal]\n+ if (old_field.min_version or 0) > max_old_min_version:\n+ max_old_min_version = old_field.min_version\n+ if not _IsFieldBackwardCompatible(new_field, old_field):\n+ # Type or min-version mismatch between old and new versions of the same\n+ # ordinal field.\n+ return False\n+\n+ # At this point we know all old fields are intact in the new struct\n+ # definition. Now verify that all new fields have a high enough min version\n+ # and are appropriately optional where required.\n+ num_new_ordinals = len(new_fields)\n+ last_min_version = max_old_min_version\n+ for ordinal in range(num_old_ordinals, num_new_ordinals):\n+ new_field = new_fields[ordinal]\n+ min_version = new_field.min_version or 0\n+ if min_version <= max_old_min_version:\n+ # A new field is being added to an existing version, which is not OK.\n+ return False\n+ if min_version < last_min_version:\n+ # The [MinVersion] of a field cannot be lower than the [MinVersion] of\n+ # a field with lower ordinal value.\n+ return False\n+ if IsReferenceKind(new_field.kind) and not IsNullableKind(new_field.kind):\n+ # New fields whose type can be nullable MUST be nullable.\n+ return False\n+\n+ return True\n+\n+ @property\n+ def stable(self):\n+ return self.attributes.get(ATTRIBUTE_STABLE, False) \\\n+ if self.attributes else False\n+\n+ @property\n+ def qualified_name(self):\n+ if self.parent_kind:\n+ prefix = self.parent_kind.qualified_name + '.'\n+ else:\n+ prefix = self.module.GetNamespacePrefix()\n+ return '%s%s' % (prefix, self.mojom_name)\n+\n+ def __eq__(self, rhs):\n+ return (isinstance(rhs, Struct) and\n+ (self.mojom_name, self.native_only, self.fields, self.constants,\n+ self.attributes) == (rhs.mojom_name, rhs.native_only, rhs.fields,\n+ rhs.constants, rhs.attributes))\n+\n+ def __hash__(self):\n+ return id(self)\n+\n+\n+class Union(ReferenceKind):\n+ \"\"\"A union of several kinds.\n+\n+ Attributes:\n+ mojom_name: {str} The name of the union type as defined in mojom.\n+ name: {str} The stylized name.\n+ fields: {List[UnionField]} The members of the union.\n+ attributes: {dict} Additional information about the union, such as\n+ which Java class name to use to represent it in the generated\n+ bindings.\n+ \"\"\"\n+ ReferenceKind.AddSharedProperty('mojom_name')\n+ ReferenceKind.AddSharedProperty('name')\n+ ReferenceKind.AddSharedProperty('fields')\n+ ReferenceKind.AddSharedProperty('attributes')\n+\n+ def __init__(self, mojom_name=None, module=None, attributes=None):\n+ if mojom_name is not None:\n+ spec = 'x:' + mojom_name\n+ else:\n+ spec = None\n+ ReferenceKind.__init__(self, spec, False, module)\n+ self.mojom_name = mojom_name\n+ self.name = None\n+ self.fields = []\n+ self.attributes = attributes\n+\n+ def Repr(self, as_ref=True):\n+ if as_ref:\n+ return '<%s spec=%r is_nullable=%r fields=%s>' % (\n+ self.__class__.__name__, self.spec, self.is_nullable, Repr(\n+ self.fields))\n+ else:\n+ return GenericRepr(self, {'fields': True, 'is_nullable': False})\n+\n+ def AddField(self, mojom_name, kind, ordinal=None, attributes=None):\n+ field = UnionField(mojom_name, kind, ordinal, None, attributes)\n+ self.fields.append(field)\n+ return field\n+\n+ def Stylize(self, stylizer):\n+ self.name = stylizer.StylizeUnion(self.mojom_name)\n+ for field in self.fields:\n+ field.Stylize(stylizer)\n+\n+ def IsBackwardCompatible(self, older_union):\n+ \"\"\"This union is backward-compatible with older_union if and only if all\n+ of the following conditions hold:\n+ - Any newly added field is tagged with a [MinVersion] attribute specifying\n+ a version number greater than all previously used [MinVersion]\n+ attributes within the union.\n+ - All fields present in older_union remain present in the new union,\n+ with the same ordinal value, same optional or non-optional status,\n+ same (or backward-compatible) type, and where applicable, the same\n+ [MinVersion] attribute value.\n+ \"\"\"\n+\n+ def buildOrdinalFieldMap(union):\n+ fields_by_ordinal = {}\n+ for field in union.fields:\n+ if field.ordinal in fields_by_ordinal:\n+ raise Exception('Multiple fields with ordinal %s in union %s.' %\n+ (field.ordinal, union.mojom_name))\n+ fields_by_ordinal[field.ordinal] = field\n+ return fields_by_ordinal\n+\n+ new_fields = buildOrdinalFieldMap(self)\n+ old_fields = buildOrdinalFieldMap(older_union)\n+ if len(new_fields) < len(old_fields):\n+ # At least one field was removed, which is not OK.\n+ return False\n+\n+ max_old_min_version = 0\n+ for ordinal, old_field in old_fields.items():\n+ new_field = new_fields.get(ordinal)\n+ if not new_field:\n+ # A field was removed, which is not OK.\n+ return False\n+ if not _IsFieldBackwardCompatible(new_field, old_field):\n+ # An field changed its type or MinVersion, which is not OK.\n+ return False\n+ old_min_version = old_field.min_version or 0\n+ if old_min_version > max_old_min_version:\n+ max_old_min_version = old_min_version\n+\n+ new_ordinals = set(new_fields.keys()) - set(old_fields.keys())\n+ for ordinal in new_ordinals:\n+ if (new_fields[ordinal].min_version or 0) <= max_old_min_version:\n+ # New fields must use a MinVersion greater than any old fields.\n+ return False\n+\n+ return True\n+\n+ @property\n+ def stable(self):\n+ return self.attributes.get(ATTRIBUTE_STABLE, False) \\\n+ if self.attributes else False\n+\n+ @property\n+ def qualified_name(self):\n+ if self.parent_kind:\n+ prefix = self.parent_kind.qualified_name + '.'\n+ else:\n+ prefix = self.module.GetNamespacePrefix()\n+ return '%s%s' % (prefix, self.mojom_name)\n+\n+ def __eq__(self, rhs):\n+ return (isinstance(rhs, Union) and\n+ (self.mojom_name, self.fields,\n+ self.attributes) == (rhs.mojom_name, rhs.fields, rhs.attributes))\n+\n+ def __hash__(self):\n+ return id(self)\n+\n+\n+class Array(ReferenceKind):\n+ \"\"\"An array.\n+\n+ Attributes:\n+ kind: {Kind} The type of the elements. May be None.\n+ length: The number of elements. None if unknown.\n+ \"\"\"\n+\n+ ReferenceKind.AddSharedProperty('kind')\n+ ReferenceKind.AddSharedProperty('length')\n+\n+ def __init__(self, kind=None, length=None):\n+ if kind is not None:\n+ if length is not None:\n+ spec = 'a%d:%s' % (length, kind.spec)\n+ else:\n+ spec = 'a:%s' % kind.spec\n+\n+ ReferenceKind.__init__(self, spec)\n+ else:\n+ ReferenceKind.__init__(self)\n+ self.kind = kind\n+ self.length = length\n+\n+ def Repr(self, as_ref=True):\n+ if as_ref:\n+ return '<%s spec=%r is_nullable=%r kind=%s length=%r>' % (\n+ self.__class__.__name__, self.spec, self.is_nullable, Repr(\n+ self.kind), self.length)\n+ else:\n+ return GenericRepr(self, {\n+ 'kind': True,\n+ 'length': False,\n+ 'is_nullable': False\n+ })\n+\n+ def __eq__(self, rhs):\n+ return (isinstance(rhs, Array)\n+ and (self.kind, self.length) == (rhs.kind, rhs.length))\n+\n+ def __hash__(self):\n+ return id(self)\n+\n+\n+class Map(ReferenceKind):\n+ \"\"\"A map.\n+\n+ Attributes:\n+ key_kind: {Kind} The type of the keys. May be None.\n+ value_kind: {Kind} The type of the elements. May be None.\n+ \"\"\"\n+ ReferenceKind.AddSharedProperty('key_kind')\n+ ReferenceKind.AddSharedProperty('value_kind')\n+\n+ def __init__(self, key_kind=None, value_kind=None):\n+ if (key_kind is not None and value_kind is not None):\n+ ReferenceKind.__init__(\n+ self, 'm[' + key_kind.spec + '][' + value_kind.spec + ']')\n+ if IsNullableKind(key_kind):\n+ raise Exception(\"Nullable kinds cannot be keys in maps.\")\n+ if IsAnyHandleKind(key_kind):\n+ raise Exception(\"Handles cannot be keys in maps.\")\n+ if IsAnyInterfaceKind(key_kind):\n+ raise Exception(\"Interfaces cannot be keys in maps.\")\n+ if IsArrayKind(key_kind):\n+ raise Exception(\"Arrays cannot be keys in maps.\")\n+ else:\n+ ReferenceKind.__init__(self)\n+\n+ self.key_kind = key_kind\n+ self.value_kind = value_kind\n+\n+ def Repr(self, as_ref=True):\n+ if as_ref:\n+ return '<%s spec=%r is_nullable=%r key_kind=%s value_kind=%s>' % (\n+ self.__class__.__name__, self.spec, self.is_nullable,\n+ Repr(self.key_kind), Repr(self.value_kind))\n+ else:\n+ return GenericRepr(self, {'key_kind': True, 'value_kind': True})\n+\n+ def __eq__(self, rhs):\n+ return (isinstance(rhs, Map) and\n+ (self.key_kind, self.value_kind) == (rhs.key_kind, rhs.value_kind))\n+\n+ def __hash__(self):\n+ return id(self)\n+\n+\n+class PendingRemote(ReferenceKind):\n+ ReferenceKind.AddSharedProperty('kind')\n+\n+ def __init__(self, kind=None):\n+ if kind is not None:\n+ if not isinstance(kind, Interface):\n+ raise Exception(\n+ 'pending_remote<T> requires T to be an interface type. Got %r' %\n+ kind.spec)\n+ ReferenceKind.__init__(self, 'rmt:' + kind.spec)\n+ else:\n+ ReferenceKind.__init__(self)\n+ self.kind = kind\n+\n+ def __eq__(self, rhs):\n+ return isinstance(rhs, PendingRemote) and self.kind == rhs.kind\n+\n+ def __hash__(self):\n+ return id(self)\n+\n+\n+class PendingReceiver(ReferenceKind):\n+ ReferenceKind.AddSharedProperty('kind')\n+\n+ def __init__(self, kind=None):\n+ if kind is not None:\n+ if not isinstance(kind, Interface):\n+ raise Exception(\n+ 'pending_receiver<T> requires T to be an interface type. Got %r' %\n+ kind.spec)\n+ ReferenceKind.__init__(self, 'rcv:' + kind.spec)\n+ else:\n+ ReferenceKind.__init__(self)\n+ self.kind = kind\n+\n+ def __eq__(self, rhs):\n+ return isinstance(rhs, PendingReceiver) and self.kind == rhs.kind\n+\n+ def __hash__(self):\n+ return id(self)\n+\n+\n+class PendingAssociatedRemote(ReferenceKind):\n+ ReferenceKind.AddSharedProperty('kind')\n+\n+ def __init__(self, kind=None):\n+ if kind is not None:\n+ if not isinstance(kind, Interface):\n+ raise Exception(\n+ 'pending_associated_remote<T> requires T to be an interface ' +\n+ 'type. Got %r' % kind.spec)\n+ ReferenceKind.__init__(self, 'rma:' + kind.spec)\n+ else:\n+ ReferenceKind.__init__(self)\n+ self.kind = kind\n+\n+ def __eq__(self, rhs):\n+ return isinstance(rhs, PendingAssociatedRemote) and self.kind == rhs.kind\n+\n+ def __hash__(self):\n+ return id(self)\n+\n+\n+class PendingAssociatedReceiver(ReferenceKind):\n+ ReferenceKind.AddSharedProperty('kind')\n+\n+ def __init__(self, kind=None):\n+ if kind is not None:\n+ if not isinstance(kind, Interface):\n+ raise Exception(\n+ 'pending_associated_receiver<T> requires T to be an interface' +\n+ 'type. Got %r' % kind.spec)\n+ ReferenceKind.__init__(self, 'rca:' + kind.spec)\n+ else:\n+ ReferenceKind.__init__(self)\n+ self.kind = kind\n+\n+ def __eq__(self, rhs):\n+ return isinstance(rhs, PendingAssociatedReceiver) and self.kind == rhs.kind\n+\n+ def __hash__(self):\n+ return id(self)\n+\n+\n+class InterfaceRequest(ReferenceKind):\n+ ReferenceKind.AddSharedProperty('kind')\n+\n+ def __init__(self, kind=None):\n+ if kind is not None:\n+ if not isinstance(kind, Interface):\n+ raise Exception(\n+ \"Interface request requires %r to be an interface.\" % kind.spec)\n+ ReferenceKind.__init__(self, 'r:' + kind.spec)\n+ else:\n+ ReferenceKind.__init__(self)\n+ self.kind = kind\n+\n+ def __eq__(self, rhs):\n+ return isinstance(rhs, InterfaceRequest) and self.kind == rhs.kind\n+\n+ def __hash__(self):\n+ return id(self)\n+\n+\n+class AssociatedInterfaceRequest(ReferenceKind):\n+ ReferenceKind.AddSharedProperty('kind')\n+\n+ def __init__(self, kind=None):\n+ if kind is not None:\n+ if not isinstance(kind, InterfaceRequest):\n+ raise Exception(\n+ \"Associated interface request requires %r to be an interface \"\n+ \"request.\" % kind.spec)\n+ assert not kind.is_nullable\n+ ReferenceKind.__init__(self, 'asso:' + kind.spec)\n+ else:\n+ ReferenceKind.__init__(self)\n+ self.kind = kind.kind if kind is not None else None\n+\n+ def __eq__(self, rhs):\n+ return isinstance(rhs, AssociatedInterfaceRequest) and self.kind == rhs.kind\n+\n+ def __hash__(self):\n+ return id(self)\n+\n+\n+class Parameter(object):\n+ def __init__(self,\n+ mojom_name=None,\n+ kind=None,\n+ ordinal=None,\n+ default=None,\n+ attributes=None):\n+ self.mojom_name = mojom_name\n+ self.name = None\n+ self.ordinal = ordinal\n+ self.kind = kind\n+ self.default = default\n+ self.attributes = attributes\n+\n+ def Repr(self, as_ref=True):\n+ # pylint: disable=unused-argument\n+ return '<%s mojom_name=%r kind=%s>' % (\n+ self.__class__.__name__, self.mojom_name, self.kind.Repr(as_ref=True))\n+\n+ def Stylize(self, stylizer):\n+ self.name = stylizer.StylizeParameter(self.mojom_name)\n+\n+ @property\n+ def min_version(self):\n+ return self.attributes.get(ATTRIBUTE_MIN_VERSION) \\\n+ if self.attributes else None\n+\n+ def __eq__(self, rhs):\n+ return (isinstance(rhs, Parameter)\n+ and (self.mojom_name, self.ordinal, self.kind, self.default,\n+ self.attributes) == (rhs.mojom_name, rhs.ordinal, rhs.kind,\n+ rhs.default, rhs.attributes))\n+\n+\n+class Method(object):\n+ def __init__(self, interface, mojom_name, ordinal=None, attributes=None):\n+ self.interface = interface\n+ self.mojom_name = mojom_name\n+ self.name = None\n+ self.explicit_ordinal = ordinal\n+ self.ordinal = ordinal\n+ self.parameters = []\n+ self.param_struct = None\n+ self.response_parameters = None\n+ self.response_param_struct = None\n+ self.attributes = attributes\n+\n+ def Repr(self, as_ref=True):\n+ if as_ref:\n+ return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)\n+ else:\n+ return GenericRepr(self, {\n+ 'mojom_name': False,\n+ 'parameters': True,\n+ 'response_parameters': True\n+ })\n+\n+ def AddParameter(self,\n+ mojom_name,\n+ kind,\n+ ordinal=None,\n+ default=None,\n+ attributes=None):\n+ parameter = Parameter(mojom_name, kind, ordinal, default, attributes)\n+ self.parameters.append(parameter)\n+ return parameter\n+\n+ def AddResponseParameter(self,\n+ mojom_name,\n+ kind,\n+ ordinal=None,\n+ default=None,\n+ attributes=None):\n+ if self.response_parameters == None:\n+ self.response_parameters = []\n+ parameter = Parameter(mojom_name, kind, ordinal, default, attributes)\n+ self.response_parameters.append(parameter)\n+ return parameter\n+\n+ def Stylize(self, stylizer):\n+ self.name = stylizer.StylizeMethod(self.mojom_name)\n+ for param in self.parameters:\n+ param.Stylize(stylizer)\n+ if self.response_parameters is not None:\n+ for param in self.response_parameters:\n+ param.Stylize(stylizer)\n+\n+ if self.param_struct:\n+ self.param_struct.Stylize(stylizer)\n+ if self.response_param_struct:\n+ self.response_param_struct.Stylize(stylizer)\n+\n+ @property\n+ def min_version(self):\n+ return self.attributes.get(ATTRIBUTE_MIN_VERSION) \\\n+ if self.attributes else None\n+\n+ @property\n+ def sync(self):\n+ return self.attributes.get(ATTRIBUTE_SYNC) \\\n+ if self.attributes else None\n+\n+ def __eq__(self, rhs):\n+ return (isinstance(rhs, Method) and\n+ (self.mojom_name, self.ordinal, self.parameters,\n+ self.response_parameters,\n+ self.attributes) == (rhs.mojom_name, rhs.ordinal, rhs.parameters,\n+ rhs.response_parameters, rhs.attributes))\n+\n+\n+class Interface(ReferenceKind):\n+ ReferenceKind.AddSharedProperty('mojom_name')\n+ ReferenceKind.AddSharedProperty('name')\n+ ReferenceKind.AddSharedProperty('methods')\n+ ReferenceKind.AddSharedProperty('enums')\n+ ReferenceKind.AddSharedProperty('constants')\n+ ReferenceKind.AddSharedProperty('attributes')\n+\n+ def __init__(self, mojom_name=None, module=None, attributes=None):\n+ if mojom_name is not None:\n+ spec = 'x:' + mojom_name\n+ else:\n+ spec = None\n+ ReferenceKind.__init__(self, spec, False, module)\n+ self.mojom_name = mojom_name\n+ self.name = None\n+ self.methods = []\n+ self.enums = []\n+ self.constants = []\n+ self.attributes = attributes\n+\n+ def Repr(self, as_ref=True):\n+ if as_ref:\n+ return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)\n+ else:\n+ return GenericRepr(self, {\n+ 'mojom_name': False,\n+ 'attributes': False,\n+ 'methods': False\n+ })\n+\n+ def AddMethod(self, mojom_name, ordinal=None, attributes=None):\n+ method = Method(self, mojom_name, ordinal, attributes)\n+ self.methods.append(method)\n+ return method\n+\n+ def Stylize(self, stylizer):\n+ self.name = stylizer.StylizeInterface(self.mojom_name)\n+ for method in self.methods:\n+ method.Stylize(stylizer)\n+ for enum in self.enums:\n+ enum.Stylize(stylizer)\n+ for constant in self.constants:\n+ constant.Stylize(stylizer)\n+\n+ def IsBackwardCompatible(self, older_interface):\n+ \"\"\"This interface is backward-compatible with older_interface if and only\n+ if all of the following conditions hold:\n+ - All defined methods in older_interface (when identified by ordinal) have\n+ backward-compatible definitions in this interface. For each method this\n+ means:\n+ - The parameter list is backward-compatible, according to backward-\n+ compatibility rules for structs, where each parameter is essentially\n+ a struct field.\n+ - If the old method definition does not specify a reply message, the\n+ new method definition must not specify a reply message.\n+ - If the old method definition specifies a reply message, the new\n+ method definition must also specify a reply message with a parameter\n+ list that is backward-compatible according to backward-compatibility\n+ rules for structs.\n+ - All newly introduced methods in this interface have a [MinVersion]\n+ attribute specifying a version greater than any method in\n+ older_interface.\n+ \"\"\"\n+\n+ def buildOrdinalMethodMap(interface):\n+ methods_by_ordinal = {}\n+ for method in interface.methods:\n+ if method.ordinal in methods_by_ordinal:\n+ raise Exception('Multiple methods with ordinal %s in interface %s.' %\n+ (method.ordinal, interface.mojom_name))\n+ methods_by_ordinal[method.ordinal] = method\n+ return methods_by_ordinal\n+\n+ new_methods = buildOrdinalMethodMap(self)\n+ old_methods = buildOrdinalMethodMap(older_interface)\n+ max_old_min_version = 0\n+ for ordinal, old_method in old_methods.items():\n+ new_method = new_methods.get(ordinal)\n+ if not new_method:\n+ # A method was removed, which is not OK.\n+ return False\n+\n+ if not new_method.param_struct.IsBackwardCompatible(\n+ old_method.param_struct):\n+ # The parameter list is not backward-compatible, which is not OK.\n+ return False\n+\n+ if old_method.response_param_struct is None:\n+ if new_method.response_param_struct is not None:\n+ # A reply was added to a message which didn't have one before, and\n+ # this is not OK.\n+ return False\n+ else:\n+ if new_method.response_param_struct is None:\n+ # A reply was removed from a message, which is not OK.\n+ return False\n+ if not new_method.response_param_struct.IsBackwardCompatible(\n+ old_method.response_param_struct):\n+ # The new message's reply is not backward-compatible with the old\n+ # message's reply, which is not OK.\n+ return False\n+\n+ if (old_method.min_version or 0) > max_old_min_version:\n+ max_old_min_version = old_method.min_version\n+\n+ # All the old methods are compatible with their new counterparts. Now verify\n+ # that newly added methods are properly versioned.\n+ new_ordinals = set(new_methods.keys()) - set(old_methods.keys())\n+ for ordinal in new_ordinals:\n+ new_method = new_methods[ordinal]\n+ if (new_method.min_version or 0) <= max_old_min_version:\n+ # A method was added to an existing version, which is not OK.\n+ return False\n+\n+ return True\n+\n+ @property\n+ def stable(self):\n+ return self.attributes.get(ATTRIBUTE_STABLE, False) \\\n+ if self.attributes else False\n+\n+ @property\n+ def qualified_name(self):\n+ if self.parent_kind:\n+ prefix = self.parent_kind.qualified_name + '.'\n+ else:\n+ prefix = self.module.GetNamespacePrefix()\n+ return '%s%s' % (prefix, self.mojom_name)\n+\n+ def __eq__(self, rhs):\n+ return (isinstance(rhs, Interface)\n+ and (self.mojom_name, self.methods, self.enums, self.constants,\n+ self.attributes) == (rhs.mojom_name, rhs.methods, rhs.enums,\n+ rhs.constants, rhs.attributes))\n+\n+ def __hash__(self):\n+ return id(self)\n+\n+\n+class AssociatedInterface(ReferenceKind):\n+ ReferenceKind.AddSharedProperty('kind')\n+\n+ def __init__(self, kind=None):\n+ if kind is not None:\n+ if not isinstance(kind, Interface):\n+ raise Exception(\n+ \"Associated interface requires %r to be an interface.\" % kind.spec)\n+ assert not kind.is_nullable\n+ ReferenceKind.__init__(self, 'asso:' + kind.spec)\n+ else:\n+ ReferenceKind.__init__(self)\n+ self.kind = kind\n+\n+ def __eq__(self, rhs):\n+ return isinstance(rhs, AssociatedInterface) and self.kind == rhs.kind\n+\n+ def __hash__(self):\n+ return id(self)\n+\n+\n+class EnumField(object):\n+ def __init__(self,\n+ mojom_name=None,\n+ value=None,\n+ attributes=None,\n+ numeric_value=None):\n+ self.mojom_name = mojom_name\n+ self.name = None\n+ self.value = value\n+ self.attributes = attributes\n+ self.numeric_value = numeric_value\n+\n+ def Stylize(self, stylizer):\n+ self.name = stylizer.StylizeEnumField(self.mojom_name)\n+\n+ @property\n+ def min_version(self):\n+ return self.attributes.get(ATTRIBUTE_MIN_VERSION) \\\n+ if self.attributes else None\n+\n+ def __eq__(self, rhs):\n+ return (isinstance(rhs, EnumField)\n+ and (self.mojom_name, self.value, self.attributes,\n+ self.numeric_value) == (rhs.mojom_name, rhs.value,\n+ rhs.attributes, rhs.numeric_value))\n+\n+\n+class Enum(Kind):\n+ def __init__(self, mojom_name=None, module=None, attributes=None):\n+ self.mojom_name = mojom_name\n+ self.name = None\n+ self.native_only = False\n+ if mojom_name is not None:\n+ spec = 'x:' + mojom_name\n+ else:\n+ spec = None\n+ Kind.__init__(self, spec, module)\n+ self.fields = []\n+ self.attributes = attributes\n+ self.min_value = None\n+ self.max_value = None\n+\n+ def Repr(self, as_ref=True):\n+ if as_ref:\n+ return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)\n+ else:\n+ return GenericRepr(self, {'mojom_name': False, 'fields': False})\n+\n+ def Stylize(self, stylizer):\n+ self.name = stylizer.StylizeEnum(self.mojom_name)\n+ for field in self.fields:\n+ field.Stylize(stylizer)\n+\n+ @property\n+ def extensible(self):\n+ return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \\\n+ if self.attributes else False\n+\n+ @property\n+ def stable(self):\n+ return self.attributes.get(ATTRIBUTE_STABLE, False) \\\n+ if self.attributes else False\n+\n+ @property\n+ def qualified_name(self):\n+ if self.parent_kind:\n+ prefix = self.parent_kind.qualified_name + '.'\n+ else:\n+ prefix = self.module.GetNamespacePrefix()\n+ return '%s%s' % (prefix, self.mojom_name)\n+\n+ def IsBackwardCompatible(self, older_enum):\n+ \"\"\"This enum is backward-compatible with older_enum if and only if one of\n+ the following conditions holds:\n+ - Neither enum is [Extensible] and both have the exact same set of valid\n+ numeric values. Field names and aliases for the same numeric value do\n+ not affect compatibility.\n+ - older_enum is [Extensible], and for every version defined by\n+ older_enum, this enum has the exact same set of valid numeric values.\n+ \"\"\"\n+\n+ def buildVersionFieldMap(enum):\n+ fields_by_min_version = {}\n+ for field in enum.fields:\n+ if field.min_version not in fields_by_min_version:\n+ fields_by_min_version[field.min_version] = set()\n+ fields_by_min_version[field.min_version].add(field.numeric_value)\n+ return fields_by_min_version\n+\n+ old_fields = buildVersionFieldMap(older_enum)\n+ new_fields = buildVersionFieldMap(self)\n+\n+ if new_fields.keys() != old_fields.keys() and not older_enum.extensible:\n+ return False\n+\n+ for min_version, valid_values in old_fields.items():\n+ if (min_version not in new_fields\n+ or new_fields[min_version] != valid_values):\n+ return False\n+\n+ return True\n+\n+ def __eq__(self, rhs):\n+ return (isinstance(rhs, Enum) and\n+ (self.mojom_name, self.native_only, self.fields, self.attributes,\n+ self.min_value,\n+ self.max_value) == (rhs.mojom_name, rhs.native_only, rhs.fields,\n+ rhs.attributes, rhs.min_value, rhs.max_value))\n+\n+ def __hash__(self):\n+ return id(self)\n+\n+\n+class Module(object):\n+ def __init__(self, path=None, mojom_namespace=None, attributes=None):\n+ self.path = path\n+ self.mojom_namespace = mojom_namespace\n+ self.namespace = None\n+ self.structs = []\n+ self.unions = []\n+ self.interfaces = []\n+ self.enums = []\n+ self.constants = []\n+ self.kinds = {}\n+ self.attributes = attributes\n+ self.imports = []\n+ self.imported_kinds = {}\n+\n+ def __repr__(self):\n+ # Gives us a decent __repr__ for modules.\n+ return self.Repr()\n+\n+ def __eq__(self, rhs):\n+ return (isinstance(rhs, Module) and\n+ (self.path, self.attributes, self.mojom_namespace, self.imports,\n+ self.constants, self.enums, self.structs, self.unions,\n+ self.interfaces) == (rhs.path, rhs.attributes, rhs.mojom_namespace,\n+ rhs.imports, rhs.constants, rhs.enums,\n+ rhs.structs, rhs.unions, rhs.interfaces))\n+\n+ def Repr(self, as_ref=True):\n+ if as_ref:\n+ return '<%s path=%r mojom_namespace=%r>' % (\n+ self.__class__.__name__, self.path, self.mojom_namespace)\n+ else:\n+ return GenericRepr(\n+ self, {\n+ 'path': False,\n+ 'mojom_namespace': False,\n+ 'attributes': False,\n+ 'structs': False,\n+ 'interfaces': False,\n+ 'unions': False\n+ })\n+\n+ def GetNamespacePrefix(self):\n+ return '%s.' % self.mojom_namespace if self.mojom_namespace else ''\n+\n+ def AddInterface(self, mojom_name, attributes=None):\n+ interface = Interface(mojom_name, self, attributes)\n+ self.interfaces.append(interface)\n+ return interface\n+\n+ def AddStruct(self, mojom_name, attributes=None):\n+ struct = Struct(mojom_name, self, attributes)\n+ self.structs.append(struct)\n+ return struct\n+\n+ def AddUnion(self, mojom_name, attributes=None):\n+ union = Union(mojom_name, self, attributes)\n+ self.unions.append(union)\n+ return union\n+\n+ def Stylize(self, stylizer):\n+ self.namespace = stylizer.StylizeModule(self.mojom_namespace)\n+ for struct in self.structs:\n+ struct.Stylize(stylizer)\n+ for union in self.unions:\n+ union.Stylize(stylizer)\n+ for interface in self.interfaces:\n+ interface.Stylize(stylizer)\n+ for enum in self.enums:\n+ enum.Stylize(stylizer)\n+ for constant in self.constants:\n+ constant.Stylize(stylizer)\n+\n+ for imported_module in self.imports:\n+ imported_module.Stylize(stylizer)\n+\n+ def Dump(self, f):\n+ pickle.dump(self, f, 2)\n+\n+ @classmethod\n+ def Load(cls, f):\n+ result = pickle.load(f)\n+ assert isinstance(result, Module)\n+ return result\n+\n+\n+def IsBoolKind(kind):\n+ return kind.spec == BOOL.spec\n+\n+\n+def IsFloatKind(kind):\n+ return kind.spec == FLOAT.spec\n+\n+\n+def IsDoubleKind(kind):\n+ return kind.spec == DOUBLE.spec\n+\n+\n+def IsIntegralKind(kind):\n+ return (kind.spec == BOOL.spec or kind.spec == INT8.spec\n+ or kind.spec == INT16.spec or kind.spec == INT32.spec\n+ or kind.spec == INT64.spec or kind.spec == UINT8.spec\n+ or kind.spec == UINT16.spec or kind.spec == UINT32.spec\n+ or kind.spec == UINT64.spec)\n+\n+\n+def IsStringKind(kind):\n+ return kind.spec == STRING.spec or kind.spec == NULLABLE_STRING.spec\n+\n+\n+def IsGenericHandleKind(kind):\n+ return kind.spec == HANDLE.spec or kind.spec == NULLABLE_HANDLE.spec\n+\n+\n+def IsDataPipeConsumerKind(kind):\n+ return kind.spec == DCPIPE.spec or kind.spec == NULLABLE_DCPIPE.spec\n+\n+\n+def IsDataPipeProducerKind(kind):\n+ return kind.spec == DPPIPE.spec or kind.spec == NULLABLE_DPPIPE.spec\n+\n+\n+def IsMessagePipeKind(kind):\n+ return kind.spec == MSGPIPE.spec or kind.spec == NULLABLE_MSGPIPE.spec\n+\n+\n+def IsSharedBufferKind(kind):\n+ return (kind.spec == SHAREDBUFFER.spec\n+ or kind.spec == NULLABLE_SHAREDBUFFER.spec)\n+\n+\n+def IsPlatformHandleKind(kind):\n+ return (kind.spec == PLATFORMHANDLE.spec\n+ or kind.spec == NULLABLE_PLATFORMHANDLE.spec)\n+\n+\n+def IsStructKind(kind):\n+ return isinstance(kind, Struct)\n+\n+\n+def IsUnionKind(kind):\n+ return isinstance(kind, Union)\n+\n+\n+def IsArrayKind(kind):\n+ return isinstance(kind, Array)\n+\n+\n+def IsInterfaceKind(kind):\n+ return isinstance(kind, Interface)\n+\n+\n+def IsAssociatedInterfaceKind(kind):\n+ return isinstance(kind, AssociatedInterface)\n+\n+\n+def IsInterfaceRequestKind(kind):\n+ return isinstance(kind, InterfaceRequest)\n+\n+\n+def IsAssociatedInterfaceRequestKind(kind):\n+ return isinstance(kind, AssociatedInterfaceRequest)\n+\n+\n+def IsPendingRemoteKind(kind):\n+ return isinstance(kind, PendingRemote)\n+\n+\n+def IsPendingReceiverKind(kind):\n+ return isinstance(kind, PendingReceiver)\n+\n+\n+def IsPendingAssociatedRemoteKind(kind):\n+ return isinstance(kind, PendingAssociatedRemote)\n+\n+\n+def IsPendingAssociatedReceiverKind(kind):\n+ return isinstance(kind, PendingAssociatedReceiver)\n+\n+\n+def IsEnumKind(kind):\n+ return isinstance(kind, Enum)\n+\n+\n+def IsReferenceKind(kind):\n+ return isinstance(kind, ReferenceKind)\n+\n+\n+def IsNullableKind(kind):\n+ return IsReferenceKind(kind) and kind.is_nullable\n+\n+\n+def IsMapKind(kind):\n+ return isinstance(kind, Map)\n+\n+\n+def IsObjectKind(kind):\n+ return IsPointerKind(kind) or IsUnionKind(kind)\n+\n+\n+def IsPointerKind(kind):\n+ return (IsStructKind(kind) or IsArrayKind(kind) or IsStringKind(kind)\n+ or IsMapKind(kind))\n+\n+\n+# Please note that it doesn't include any interface kind.\n+def IsAnyHandleKind(kind):\n+ return (IsGenericHandleKind(kind) or IsDataPipeConsumerKind(kind)\n+ or IsDataPipeProducerKind(kind) or IsMessagePipeKind(kind)\n+ or IsSharedBufferKind(kind) or IsPlatformHandleKind(kind))\n+\n+\n+def IsAnyInterfaceKind(kind):\n+ return (IsInterfaceKind(kind) or IsInterfaceRequestKind(kind)\n+ or IsAssociatedKind(kind) or IsPendingRemoteKind(kind)\n+ or IsPendingReceiverKind(kind))\n+\n+\n+def IsAnyHandleOrInterfaceKind(kind):\n+ return IsAnyHandleKind(kind) or IsAnyInterfaceKind(kind)\n+\n+\n+def IsAssociatedKind(kind):\n+ return (IsAssociatedInterfaceKind(kind)\n+ or IsAssociatedInterfaceRequestKind(kind)\n+ or IsPendingAssociatedRemoteKind(kind)\n+ or IsPendingAssociatedReceiverKind(kind))\n+\n+\n+def HasCallbacks(interface):\n+ for method in interface.methods:\n+ if method.response_parameters != None:\n+ return True\n+ return False\n+\n+\n+# Finds out whether an interface passes associated interfaces and associated\n+# interface requests.\n+def PassesAssociatedKinds(interface):\n+ visited_kinds = set()\n+ for method in interface.methods:\n+ if MethodPassesAssociatedKinds(method, visited_kinds):\n+ return True\n+ return False\n+\n+\n+def _AnyMethodParameterRecursive(method, predicate, visited_kinds=None):\n+ def _HasProperty(kind):\n+ if kind in visited_kinds:\n+ # No need to examine the kind again.\n+ return False\n+ visited_kinds.add(kind)\n+ if predicate(kind):\n+ return True\n+ if IsArrayKind(kind):\n+ return _HasProperty(kind.kind)\n+ if IsStructKind(kind) or IsUnionKind(kind):\n+ for field in kind.fields:\n+ if _HasProperty(field.kind):\n+ return True\n+ if IsMapKind(kind):\n+ if _HasProperty(kind.key_kind) or _HasProperty(kind.value_kind):\n+ return True\n+ return False\n+\n+ if visited_kinds is None:\n+ visited_kinds = set()\n+\n+ for param in method.parameters:\n+ if _HasProperty(param.kind):\n+ return True\n+ if method.response_parameters != None:\n+ for param in method.response_parameters:\n+ if _HasProperty(param.kind):\n+ return True\n+ return False\n+\n+\n+# Finds out whether a method passes associated interfaces and associated\n+# interface requests.\n+def MethodPassesAssociatedKinds(method, visited_kinds=None):\n+ return _AnyMethodParameterRecursive(\n+ method, IsAssociatedKind, visited_kinds=visited_kinds)\n+\n+\n+# Determines whether a method passes interfaces.\n+def MethodPassesInterfaces(method):\n+ return _AnyMethodParameterRecursive(method, IsInterfaceKind)\n+\n+\n+def HasSyncMethods(interface):\n+ for method in interface.methods:\n+ if method.sync:\n+ return True\n+ return False\n+\n+\n+def ContainsHandlesOrInterfaces(kind):\n+ \"\"\"Check if the kind contains any handles.\n+\n+ This check is recursive so it checks all struct fields, containers elements,\n+ etc.\n+\n+ Args:\n+ struct: {Kind} The kind to check.\n+\n+ Returns:\n+ {bool}: True if the kind contains handles.\n+ \"\"\"\n+ # We remember the types we already checked to avoid infinite recursion when\n+ # checking recursive (or mutually recursive) types:\n+ checked = set()\n+\n+ def Check(kind):\n+ if kind.spec in checked:\n+ return False\n+ checked.add(kind.spec)\n+ if IsStructKind(kind):\n+ return any(Check(field.kind) for field in kind.fields)\n+ elif IsUnionKind(kind):\n+ return any(Check(field.kind) for field in kind.fields)\n+ elif IsAnyHandleKind(kind):\n+ return True\n+ elif IsAnyInterfaceKind(kind):\n+ return True\n+ elif IsArrayKind(kind):\n+ return Check(kind.kind)\n+ elif IsMapKind(kind):\n+ return Check(kind.key_kind) or Check(kind.value_kind)\n+ else:\n+ return False\n+\n+ return Check(kind)\n+\n+\n+def ContainsNativeTypes(kind):\n+ \"\"\"Check if the kind contains any native type (struct or enum).\n+\n+ This check is recursive so it checks all struct fields, scoped interface\n+ enums, etc.\n+\n+ Args:\n+ struct: {Kind} The kind to check.\n+\n+ Returns:\n+ {bool}: True if the kind contains native types.\n+ \"\"\"\n+ # We remember the types we already checked to avoid infinite recursion when\n+ # checking recursive (or mutually recursive) types:\n+ checked = set()\n+\n+ def Check(kind):\n+ if kind.spec in checked:\n+ return False\n+ checked.add(kind.spec)\n+ if IsEnumKind(kind):\n+ return kind.native_only\n+ elif IsStructKind(kind):\n+ if kind.native_only:\n+ return True\n+ if any(enum.native_only for enum in kind.enums):\n+ return True\n+ return any(Check(field.kind) for field in kind.fields)\n+ elif IsUnionKind(kind):\n+ return any(Check(field.kind) for field in kind.fields)\n+ elif IsInterfaceKind(kind):\n+ return any(enum.native_only for enum in kind.enums)\n+ elif IsArrayKind(kind):\n+ return Check(kind.kind)\n+ elif IsMapKind(kind):\n+ return Check(kind.key_kind) or Check(kind.value_kind)\n+ else:\n+ return False\n+\n+ return Check(kind)\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py\nnew file mode 100644\nindex 00000000..e8fd4936\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py\n@@ -0,0 +1,31 @@\n+# Copyright 2014 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import sys\n+import unittest\n+\n+from mojom.generate import module as mojom\n+\n+\n+class ModuleTest(unittest.TestCase):\n+ def testNonInterfaceAsInterfaceRequest(self):\n+ \"\"\"Tests that a non-interface cannot be used for interface requests.\"\"\"\n+ module = mojom.Module('test_module', 'test_namespace')\n+ struct = mojom.Struct('TestStruct', module=module)\n+ with self.assertRaises(Exception) as e:\n+ mojom.InterfaceRequest(struct)\n+ self.assertEquals(\n+ e.exception.__str__(),\n+ 'Interface request requires \\'x:TestStruct\\' to be an interface.')\n+\n+ def testNonInterfaceAsAssociatedInterface(self):\n+ \"\"\"Tests that a non-interface type cannot be used for associated interfaces.\n+ \"\"\"\n+ module = mojom.Module('test_module', 'test_namespace')\n+ struct = mojom.Struct('TestStruct', module=module)\n+ with self.assertRaises(Exception) as e:\n+ mojom.AssociatedInterface(struct)\n+ self.assertEquals(\n+ e.exception.__str__(),\n+ 'Associated interface requires \\'x:TestStruct\\' to be an interface.')\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py\nnew file mode 100644\nindex 00000000..88b77c98\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py\n@@ -0,0 +1,258 @@\n+# Copyright 2013 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+from mojom.generate import module as mojom\n+\n+# This module provides a mechanism for determining the packed order and offsets\n+# of a mojom.Struct.\n+#\n+# ps = pack.PackedStruct(struct)\n+# ps.packed_fields will access a list of PackedField objects, each of which\n+# will have an offset, a size and a bit (for mojom.BOOLs).\n+\n+# Size of struct header in bytes: num_bytes [4B] + version [4B].\n+HEADER_SIZE = 8\n+\n+\n+class PackedField(object):\n+ kind_to_size = {\n+ mojom.BOOL: 1,\n+ mojom.INT8: 1,\n+ mojom.UINT8: 1,\n+ mojom.INT16: 2,\n+ mojom.UINT16: 2,\n+ mojom.INT32: 4,\n+ mojom.UINT32: 4,\n+ mojom.FLOAT: 4,\n+ mojom.HANDLE: 4,\n+ mojom.MSGPIPE: 4,\n+ mojom.SHAREDBUFFER: 4,\n+ mojom.PLATFORMHANDLE: 4,\n+ mojom.DCPIPE: 4,\n+ mojom.DPPIPE: 4,\n+ mojom.NULLABLE_HANDLE: 4,\n+ mojom.NULLABLE_MSGPIPE: 4,\n+ mojom.NULLABLE_SHAREDBUFFER: 4,\n+ mojom.NULLABLE_PLATFORMHANDLE: 4,\n+ mojom.NULLABLE_DCPIPE: 4,\n+ mojom.NULLABLE_DPPIPE: 4,\n+ mojom.INT64: 8,\n+ mojom.UINT64: 8,\n+ mojom.DOUBLE: 8,\n+ mojom.STRING: 8,\n+ mojom.NULLABLE_STRING: 8\n+ }\n+\n+ @classmethod\n+ def GetSizeForKind(cls, kind):\n+ if isinstance(kind, (mojom.Array, mojom.Map, mojom.Struct, mojom.Interface,\n+ mojom.AssociatedInterface, mojom.PendingRemote,\n+ mojom.PendingAssociatedRemote)):\n+ return 8\n+ if isinstance(kind, mojom.Union):\n+ return 16\n+ if isinstance(kind, (mojom.InterfaceRequest, mojom.PendingReceiver)):\n+ kind = mojom.MSGPIPE\n+ if isinstance(\n+ kind,\n+ (mojom.AssociatedInterfaceRequest, mojom.PendingAssociatedReceiver)):\n+ return 4\n+ if isinstance(kind, mojom.Enum):\n+ # TODO(mpcomplete): what about big enums?\n+ return cls.kind_to_size[mojom.INT32]\n+ if not kind in cls.kind_to_size:\n+ raise Exception(\"Undefined type: %s. Did you forget to import the file \"\n+ \"containing the definition?\" % kind.spec)\n+ return cls.kind_to_size[kind]\n+\n+ @classmethod\n+ def GetAlignmentForKind(cls, kind):\n+ if isinstance(kind, (mojom.Interface, mojom.AssociatedInterface,\n+ mojom.PendingRemote, mojom.PendingAssociatedRemote)):\n+ return 4\n+ if isinstance(kind, mojom.Union):\n+ return 8\n+ return cls.GetSizeForKind(kind)\n+\n+ def __init__(self, field, index, ordinal):\n+ \"\"\"\n+ Args:\n+ field: the original field.\n+ index: the position of the original field in the struct.\n+ ordinal: the ordinal of the field for serialization.\n+ \"\"\"\n+ self.field = field\n+ self.index = index\n+ self.ordinal = ordinal\n+ self.size = self.GetSizeForKind(field.kind)\n+ self.alignment = self.GetAlignmentForKind(field.kind)\n+ self.offset = None\n+ self.bit = None\n+ self.min_version = None\n+\n+\n+def GetPad(offset, alignment):\n+ \"\"\"Returns the pad necessary to reserve space so that |offset + pad| equals to\n+ some multiple of |alignment|.\"\"\"\n+ return (alignment - (offset % alignment)) % alignment\n+\n+\n+def GetFieldOffset(field, last_field):\n+ \"\"\"Returns a 2-tuple of the field offset and bit (for BOOLs).\"\"\"\n+ if (field.field.kind == mojom.BOOL and last_field.field.kind == mojom.BOOL\n+ and last_field.bit < 7):\n+ return (last_field.offset, last_field.bit + 1)\n+\n+ offset = last_field.offset + last_field.size\n+ pad = GetPad(offset, field.alignment)\n+ return (offset + pad, 0)\n+\n+\n+def GetPayloadSizeUpToField(field):\n+ \"\"\"Returns the payload size (not including struct header) if |field| is the\n+ last field.\n+ \"\"\"\n+ if not field:\n+ return 0\n+ offset = field.offset + field.size\n+ pad = GetPad(offset, 8)\n+ return offset + pad\n+\n+\n+class PackedStruct(object):\n+ def __init__(self, struct):\n+ self.struct = struct\n+ # |packed_fields| contains all the fields, in increasing offset order.\n+ self.packed_fields = []\n+ # |packed_fields_in_ordinal_order| refers to the same fields as\n+ # |packed_fields|, but in ordinal order.\n+ self.packed_fields_in_ordinal_order = []\n+\n+ # No fields.\n+ if (len(struct.fields) == 0):\n+ return\n+\n+ # Start by sorting by ordinal.\n+ src_fields = self.packed_fields_in_ordinal_order\n+ ordinal = 0\n+ for index, field in enumerate(struct.fields):\n+ if field.ordinal is not None:\n+ ordinal = field.ordinal\n+ src_fields.append(PackedField(field, index, ordinal))\n+ ordinal += 1\n+ src_fields.sort(key=lambda field: field.ordinal)\n+\n+ # Set |min_version| for each field.\n+ next_min_version = 0\n+ for packed_field in src_fields:\n+ if packed_field.field.min_version is None:\n+ assert next_min_version == 0\n+ else:\n+ assert packed_field.field.min_version >= next_min_version\n+ next_min_version = packed_field.field.min_version\n+ packed_field.min_version = next_min_version\n+\n+ if (packed_field.min_version != 0\n+ and mojom.IsReferenceKind(packed_field.field.kind)\n+ and not packed_field.field.kind.is_nullable):\n+ raise Exception(\"Non-nullable fields are only allowed in version 0 of \"\n+ \"a struct. %s.%s is defined with [MinVersion=%d].\" %\n+ (self.struct.name, packed_field.field.name,\n+ packed_field.min_version))\n+\n+ src_field = src_fields[0]\n+ src_field.offset = 0\n+ src_field.bit = 0\n+ dst_fields = self.packed_fields\n+ dst_fields.append(src_field)\n+\n+ # Then find first slot that each field will fit.\n+ for src_field in src_fields[1:]:\n+ last_field = dst_fields[0]\n+ for i in range(1, len(dst_fields)):\n+ next_field = dst_fields[i]\n+ offset, bit = GetFieldOffset(src_field, last_field)\n+ if offset + src_field.size <= next_field.offset:\n+ # Found hole.\n+ src_field.offset = offset\n+ src_field.bit = bit\n+ dst_fields.insert(i, src_field)\n+ break\n+ last_field = next_field\n+ if src_field.offset is None:\n+ # Add to end\n+ src_field.offset, src_field.bit = GetFieldOffset(src_field, last_field)\n+ dst_fields.append(src_field)\n+\n+\n+class ByteInfo(object):\n+ def __init__(self):\n+ self.is_padding = False\n+ self.packed_fields = []\n+\n+\n+def GetByteLayout(packed_struct):\n+ total_payload_size = GetPayloadSizeUpToField(\n+ packed_struct.packed_fields[-1] if packed_struct.packed_fields else None)\n+ byte_info = [ByteInfo() for i in range(total_payload_size)]\n+\n+ limit_of_previous_field = 0\n+ for packed_field in packed_struct.packed_fields:\n+ for i in range(limit_of_previous_field, packed_field.offset):\n+ byte_info[i].is_padding = True\n+ byte_info[packed_field.offset].packed_fields.append(packed_field)\n+ limit_of_previous_field = packed_field.offset + packed_field.size\n+\n+ for i in range(limit_of_previous_field, len(byte_info)):\n+ byte_info[i].is_padding = True\n+\n+ for byte in byte_info:\n+ # A given byte cannot both be padding and have a fields packed into it.\n+ assert not (byte.is_padding and byte.packed_fields)\n+\n+ return byte_info\n+\n+\n+class VersionInfo(object):\n+ def __init__(self, version, num_fields, num_bytes):\n+ self.version = version\n+ self.num_fields = num_fields\n+ self.num_bytes = num_bytes\n+\n+\n+def GetVersionInfo(packed_struct):\n+ \"\"\"Get version information for a struct.\n+\n+ Args:\n+ packed_struct: A PackedStruct instance.\n+\n+ Returns:\n+ A non-empty list of VersionInfo instances, sorted by version in increasing\n+ order.\n+ Note: The version numbers may not be consecutive.\n+ \"\"\"\n+ versions = []\n+ last_version = 0\n+ last_num_fields = 0\n+ last_payload_size = 0\n+\n+ for packed_field in packed_struct.packed_fields_in_ordinal_order:\n+ if packed_field.min_version != last_version:\n+ versions.append(\n+ VersionInfo(last_version, last_num_fields,\n+ last_payload_size + HEADER_SIZE))\n+ last_version = packed_field.min_version\n+\n+ last_num_fields += 1\n+ # The fields are iterated in ordinal order here. However, the size of a\n+ # version is determined by the last field of that version in pack order,\n+ # instead of ordinal order. Therefore, we need to calculate the max value.\n+ last_payload_size = max(\n+ GetPayloadSizeUpToField(packed_field), last_payload_size)\n+\n+ assert len(versions) == 0 or last_num_fields != versions[-1].num_fields\n+ versions.append(\n+ VersionInfo(last_version, last_num_fields,\n+ last_payload_size + HEADER_SIZE))\n+ return versions\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py\nnew file mode 100644\nindex 00000000..98c705ad\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py\n@@ -0,0 +1,225 @@\n+# Copyright 2013 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import sys\n+import unittest\n+\n+from mojom.generate import module as mojom\n+from mojom.generate import pack\n+\n+\n+class PackTest(unittest.TestCase):\n+ def testOrdinalOrder(self):\n+ struct = mojom.Struct('test')\n+ struct.AddField('testfield1', mojom.INT32, 2)\n+ struct.AddField('testfield2', mojom.INT32, 1)\n+ ps = pack.PackedStruct(struct)\n+\n+ self.assertEqual(2, len(ps.packed_fields))\n+ self.assertEqual('testfield2', ps.packed_fields[0].field.mojom_name)\n+ self.assertEqual('testfield1', ps.packed_fields[1].field.mojom_name)\n+\n+ def testZeroFields(self):\n+ struct = mojom.Struct('test')\n+ ps = pack.PackedStruct(struct)\n+ self.assertEqual(0, len(ps.packed_fields))\n+\n+ def testOneField(self):\n+ struct = mojom.Struct('test')\n+ struct.AddField('testfield1', mojom.INT8)\n+ ps = pack.PackedStruct(struct)\n+ self.assertEqual(1, len(ps.packed_fields))\n+\n+ def _CheckPackSequence(self, kinds, fields, offsets):\n+ \"\"\"Checks the pack order and offsets of a sequence of mojom.Kinds.\n+\n+ Args:\n+ kinds: A sequence of mojom.Kinds that specify the fields that are to be\n+ created.\n+ fields: The expected order of the resulting fields, with the integer \"1\"\n+ first.\n+ offsets: The expected order of offsets, with the integer \"0\" first.\n+ \"\"\"\n+ struct = mojom.Struct('test')\n+ index = 1\n+ for kind in kinds:\n+ struct.AddField('%d' % index, kind)\n+ index += 1\n+ ps = pack.PackedStruct(struct)\n+ num_fields = len(ps.packed_fields)\n+ self.assertEqual(len(kinds), num_fields)\n+ for i in range(num_fields):\n+ self.assertEqual('%d' % fields[i], ps.packed_fields[i].field.mojom_name)\n+ self.assertEqual(offsets[i], ps.packed_fields[i].offset)\n+\n+ def testPaddingPackedInOrder(self):\n+ return self._CheckPackSequence((mojom.INT8, mojom.UINT8, mojom.INT32),\n+ (1, 2, 3), (0, 1, 4))\n+\n+ def testPaddingPackedOutOfOrder(self):\n+ return self._CheckPackSequence((mojom.INT8, mojom.INT32, mojom.UINT8),\n+ (1, 3, 2), (0, 1, 4))\n+\n+ def testPaddingPackedOverflow(self):\n+ kinds = (mojom.INT8, mojom.INT32, mojom.INT16, mojom.INT8, mojom.INT8)\n+ # 2 bytes should be packed together first, followed by short, then by int.\n+ fields = (1, 4, 3, 2, 5)\n+ offsets = (0, 1, 2, 4, 8)\n+ return self._CheckPackSequence(kinds, fields, offsets)\n+\n+ def testNullableTypes(self):\n+ kinds = (mojom.STRING.MakeNullableKind(), mojom.HANDLE.MakeNullableKind(),\n+ mojom.Struct('test_struct').MakeNullableKind(),\n+ mojom.DCPIPE.MakeNullableKind(), mojom.Array().MakeNullableKind(),\n+ mojom.DPPIPE.MakeNullableKind(),\n+ mojom.Array(length=5).MakeNullableKind(),\n+ mojom.MSGPIPE.MakeNullableKind(),\n+ mojom.Interface('test_interface').MakeNullableKind(),\n+ mojom.SHAREDBUFFER.MakeNullableKind(),\n+ mojom.InterfaceRequest().MakeNullableKind())\n+ fields = (1, 2, 4, 3, 5, 6, 8, 7, 9, 10, 11)\n+ offsets = (0, 8, 12, 16, 24, 32, 36, 40, 48, 56, 60)\n+ return self._CheckPackSequence(kinds, fields, offsets)\n+\n+ def testAllTypes(self):\n+ return self._CheckPackSequence(\n+ (mojom.BOOL, mojom.INT8, mojom.STRING, mojom.UINT8, mojom.INT16,\n+ mojom.DOUBLE, mojom.UINT16, mojom.INT32, mojom.UINT32, mojom.INT64,\n+ mojom.FLOAT, mojom.STRING, mojom.HANDLE, mojom.UINT64,\n+ mojom.Struct('test'), mojom.Array(), mojom.STRING.MakeNullableKind()),\n+ (1, 2, 4, 5, 7, 3, 6, 8, 9, 10, 11, 13, 12, 14, 15, 16, 17, 18),\n+ (0, 1, 2, 4, 6, 8, 16, 24, 28, 32, 40, 44, 48, 56, 64, 72, 80, 88))\n+\n+ def testPaddingPackedOutOfOrderByOrdinal(self):\n+ struct = mojom.Struct('test')\n+ struct.AddField('testfield1', mojom.INT8)\n+ struct.AddField('testfield3', mojom.UINT8, 3)\n+ struct.AddField('testfield2', mojom.INT32, 2)\n+ ps = pack.PackedStruct(struct)\n+ self.assertEqual(3, len(ps.packed_fields))\n+\n+ # Second byte should be packed in behind first, altering order.\n+ self.assertEqual('testfield1', ps.packed_fields[0].field.mojom_name)\n+ self.assertEqual('testfield3', ps.packed_fields[1].field.mojom_name)\n+ self.assertEqual('testfield2', ps.packed_fields[2].field.mojom_name)\n+\n+ # Second byte should be packed with first.\n+ self.assertEqual(0, ps.packed_fields[0].offset)\n+ self.assertEqual(1, ps.packed_fields[1].offset)\n+ self.assertEqual(4, ps.packed_fields[2].offset)\n+\n+ def testBools(self):\n+ struct = mojom.Struct('test')\n+ struct.AddField('bit0', mojom.BOOL)\n+ struct.AddField('bit1', mojom.BOOL)\n+ struct.AddField('int', mojom.INT32)\n+ struct.AddField('bit2', mojom.BOOL)\n+ struct.AddField('bit3', mojom.BOOL)\n+ struct.AddField('bit4', mojom.BOOL)\n+ struct.AddField('bit5', mojom.BOOL)\n+ struct.AddField('bit6', mojom.BOOL)\n+ struct.AddField('bit7', mojom.BOOL)\n+ struct.AddField('bit8', mojom.BOOL)\n+ ps = pack.PackedStruct(struct)\n+ self.assertEqual(10, len(ps.packed_fields))\n+\n+ # First 8 bits packed together.\n+ for i in range(8):\n+ pf = ps.packed_fields[i]\n+ self.assertEqual(0, pf.offset)\n+ self.assertEqual(\"bit%d\" % i, pf.field.mojom_name)\n+ self.assertEqual(i, pf.bit)\n+\n+ # Ninth bit goes into second byte.\n+ self.assertEqual(\"bit8\", ps.packed_fields[8].field.mojom_name)\n+ self.assertEqual(1, ps.packed_fields[8].offset)\n+ self.assertEqual(0, ps.packed_fields[8].bit)\n+\n+ # int comes last.\n+ self.assertEqual(\"int\", ps.packed_fields[9].field.mojom_name)\n+ self.assertEqual(4, ps.packed_fields[9].offset)\n+\n+ def testMinVersion(self):\n+ \"\"\"Tests that |min_version| is properly set for packed fields.\"\"\"\n+ struct = mojom.Struct('test')\n+ struct.AddField('field_2', mojom.BOOL, 2)\n+ struct.AddField('field_0', mojom.INT32, 0)\n+ struct.AddField('field_1', mojom.INT64, 1)\n+ ps = pack.PackedStruct(struct)\n+\n+ self.assertEqual('field_0', ps.packed_fields[0].field.mojom_name)\n+ self.assertEqual('field_2', ps.packed_fields[1].field.mojom_name)\n+ self.assertEqual('field_1', ps.packed_fields[2].field.mojom_name)\n+\n+ self.assertEqual(0, ps.packed_fields[0].min_version)\n+ self.assertEqual(0, ps.packed_fields[1].min_version)\n+ self.assertEqual(0, ps.packed_fields[2].min_version)\n+\n+ struct.fields[0].attributes = {'MinVersion': 1}\n+ ps = pack.PackedStruct(struct)\n+\n+ self.assertEqual(0, ps.packed_fields[0].min_version)\n+ self.assertEqual(1, ps.packed_fields[1].min_version)\n+ self.assertEqual(0, ps.packed_fields[2].min_version)\n+\n+ def testGetVersionInfoEmptyStruct(self):\n+ \"\"\"Tests that pack.GetVersionInfo() never returns an empty list, even for\n+ empty structs.\n+ \"\"\"\n+ struct = mojom.Struct('test')\n+ ps = pack.PackedStruct(struct)\n+\n+ versions = pack.GetVersionInfo(ps)\n+ self.assertEqual(1, len(versions))\n+ self.assertEqual(0, versions[0].version)\n+ self.assertEqual(0, versions[0].num_fields)\n+ self.assertEqual(8, versions[0].num_bytes)\n+\n+ def testGetVersionInfoComplexOrder(self):\n+ \"\"\"Tests pack.GetVersionInfo() using a struct whose definition order,\n+ ordinal order and pack order for fields are all different.\n+ \"\"\"\n+ struct = mojom.Struct('test')\n+ struct.AddField(\n+ 'field_3', mojom.BOOL, ordinal=3, attributes={'MinVersion': 3})\n+ struct.AddField('field_0', mojom.INT32, ordinal=0)\n+ struct.AddField(\n+ 'field_1', mojom.INT64, ordinal=1, attributes={'MinVersion': 2})\n+ struct.AddField(\n+ 'field_2', mojom.INT64, ordinal=2, attributes={'MinVersion': 3})\n+ ps = pack.PackedStruct(struct)\n+\n+ versions = pack.GetVersionInfo(ps)\n+ self.assertEqual(3, len(versions))\n+\n+ self.assertEqual(0, versions[0].version)\n+ self.assertEqual(1, versions[0].num_fields)\n+ self.assertEqual(16, versions[0].num_bytes)\n+\n+ self.assertEqual(2, versions[1].version)\n+ self.assertEqual(2, versions[1].num_fields)\n+ self.assertEqual(24, versions[1].num_bytes)\n+\n+ self.assertEqual(3, versions[2].version)\n+ self.assertEqual(4, versions[2].num_fields)\n+ self.assertEqual(32, versions[2].num_bytes)\n+\n+ def testInterfaceAlignment(self):\n+ \"\"\"Tests that interfaces are aligned on 4-byte boundaries, although the size\n+ of an interface is 8 bytes.\n+ \"\"\"\n+ kinds = (mojom.INT32, mojom.Interface('test_interface'))\n+ fields = (1, 2)\n+ offsets = (0, 4)\n+ self._CheckPackSequence(kinds, fields, offsets)\n+\n+ def testAssociatedInterfaceAlignment(self):\n+ \"\"\"Tests that associated interfaces are aligned on 4-byte boundaries,\n+ although the size of an associated interface is 8 bytes.\n+ \"\"\"\n+ kinds = (mojom.INT32,\n+ mojom.AssociatedInterface(mojom.Interface('test_interface')))\n+ fields = (1, 2)\n+ offsets = (0, 4)\n+ self._CheckPackSequence(kinds, fields, offsets)\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py\nnew file mode 100644\nindex 00000000..7a300560\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py\n@@ -0,0 +1,83 @@\n+# Copyright 2013 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+# Based on third_party/WebKit/Source/build/scripts/template_expander.py.\n+\n+import os.path\n+import sys\n+\n+from mojom import fileutil\n+\n+fileutil.AddLocalRepoThirdPartyDirToModulePath()\n+import jinja2\n+\n+\n+def ApplyTemplate(mojo_generator, path_to_template, params, **kwargs):\n+ loader = jinja2.ModuleLoader(\n+ os.path.join(mojo_generator.bytecode_path,\n+ \"%s.zip\" % mojo_generator.GetTemplatePrefix()))\n+ final_kwargs = dict(mojo_generator.GetJinjaParameters())\n+ final_kwargs.update(kwargs)\n+\n+ jinja_env = jinja2.Environment(\n+ loader=loader, keep_trailing_newline=True, **final_kwargs)\n+ jinja_env.globals.update(mojo_generator.GetGlobals())\n+ jinja_env.filters.update(mojo_generator.GetFilters())\n+ template = jinja_env.get_template(path_to_template)\n+ return template.render(params)\n+\n+\n+def UseJinja(path_to_template, **kwargs):\n+ def RealDecorator(generator):\n+ def GeneratorInternal(*args, **kwargs2):\n+ parameters = generator(*args, **kwargs2)\n+ return ApplyTemplate(args[0], path_to_template, parameters, **kwargs)\n+\n+ GeneratorInternal.__name__ = generator.__name__\n+ return GeneratorInternal\n+\n+ return RealDecorator\n+\n+\n+def ApplyImportedTemplate(mojo_generator, path_to_template, filename, params,\n+ **kwargs):\n+ loader = jinja2.FileSystemLoader(searchpath=path_to_template)\n+ final_kwargs = dict(mojo_generator.GetJinjaParameters())\n+ final_kwargs.update(kwargs)\n+\n+ jinja_env = jinja2.Environment(\n+ loader=loader, keep_trailing_newline=True, **final_kwargs)\n+ jinja_env.globals.update(mojo_generator.GetGlobals())\n+ jinja_env.filters.update(mojo_generator.GetFilters())\n+ template = jinja_env.get_template(filename)\n+ return template.render(params)\n+\n+\n+def UseJinjaForImportedTemplate(func):\n+ def wrapper(*args, **kwargs):\n+ parameters = func(*args, **kwargs)\n+ path_to_template = args[1]\n+ filename = args[2]\n+ return ApplyImportedTemplate(args[0], path_to_template, filename,\n+ parameters)\n+\n+ wrapper.__name__ = func.__name__\n+ return wrapper\n+\n+\n+def PrecompileTemplates(generator_modules, output_dir):\n+ for module in generator_modules.values():\n+ generator = module.Generator(None)\n+ jinja_env = jinja2.Environment(\n+ loader=jinja2.FileSystemLoader([\n+ os.path.join(\n+ os.path.dirname(module.__file__), generator.GetTemplatePrefix())\n+ ]))\n+ jinja_env.filters.update(generator.GetFilters())\n+ jinja_env.compile_templates(\n+ os.path.join(output_dir, \"%s.zip\" % generator.GetTemplatePrefix()),\n+ extensions=[\"tmpl\"],\n+ zip=\"stored\",\n+ py_compile=True,\n+ ignore_errors=False)\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py\nnew file mode 100644\nindex 00000000..d6df3ca6\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py\n@@ -0,0 +1,854 @@\n+# Copyright 2013 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\"\"\"Convert parse tree to AST.\n+\n+This module converts the parse tree to the AST we use for code generation. The\n+main entry point is OrderedModule, which gets passed the parser\n+representation of a mojom file. When called it's assumed that all imports have\n+already been parsed and converted to ASTs before.\n+\"\"\"\n+\n+import itertools\n+import os\n+import re\n+import sys\n+\n+from mojom.generate import generator\n+from mojom.generate import module as mojom\n+from mojom.parse import ast\n+\n+\n+def _IsStrOrUnicode(x):\n+ if sys.version_info[0] < 3:\n+ return isinstance(x, (unicode, str))\n+ return isinstance(x, str)\n+\n+\n+def _DuplicateName(values):\n+ \"\"\"Returns the 'mojom_name' of the first entry in |values| whose 'mojom_name'\n+ has already been encountered. If there are no duplicates, returns None.\"\"\"\n+ names = set()\n+ for value in values:\n+ if value.mojom_name in names:\n+ return value.mojom_name\n+ names.add(value.mojom_name)\n+ return None\n+\n+\n+def _ElemsOfType(elems, elem_type, scope):\n+ \"\"\"Find all elements of the given type.\n+\n+ Args:\n+ elems: {Sequence[Any]} Sequence of elems.\n+ elem_type: {Type[C]} Extract all elems of this type.\n+ scope: {str} The name of the surrounding scope (e.g. struct\n+ definition). Used in error messages.\n+\n+ Returns:\n+ {List[C]} All elems of matching type.\n+ \"\"\"\n+ assert isinstance(elem_type, type)\n+ result = [elem for elem in elems if isinstance(elem, elem_type)]\n+ duplicate_name = _DuplicateName(result)\n+ if duplicate_name:\n+ raise Exception('Names in mojom must be unique within a scope. The name '\n+ '\"%s\" is used more than once within the scope \"%s\".' %\n+ (duplicate_name, scope))\n+ return result\n+\n+\n+def _ProcessElements(scope, elements, operations_by_type):\n+ \"\"\"Iterates over the given elements, running a function from\n+ operations_by_type for any element that matches a key in that dict. The scope\n+ is the name of the surrounding scope, such as a filename or struct name, used\n+ only in error messages.\"\"\"\n+ names_in_this_scope = set()\n+ for element in elements:\n+ # pylint: disable=unidiomatic-typecheck\n+ element_type = type(element)\n+ if element_type in operations_by_type:\n+ if element.mojom_name in names_in_this_scope:\n+ raise Exception('Names must be unique within a scope. The name \"%s\" is '\n+ 'used more than once within the scope \"%s\".' %\n+ (duplicate_name, scope))\n+ operations_by_type[element_type](element)\n+\n+\n+def _MapKind(kind):\n+ map_to_kind = {\n+ 'bool': 'b',\n+ 'int8': 'i8',\n+ 'int16': 'i16',\n+ 'int32': 'i32',\n+ 'int64': 'i64',\n+ 'uint8': 'u8',\n+ 'uint16': 'u16',\n+ 'uint32': 'u32',\n+ 'uint64': 'u64',\n+ 'float': 'f',\n+ 'double': 'd',\n+ 'string': 's',\n+ 'handle': 'h',\n+ 'handle<data_pipe_consumer>': 'h:d:c',\n+ 'handle<data_pipe_producer>': 'h:d:p',\n+ 'handle<message_pipe>': 'h:m',\n+ 'handle<shared_buffer>': 'h:s',\n+ 'handle<platform>': 'h:p'\n+ }\n+ if kind.endswith('?'):\n+ base_kind = _MapKind(kind[0:-1])\n+ # NOTE: This doesn't rule out enum types. Those will be detected later, when\n+ # cross-reference is established.\n+ reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso', 'rmt', 'rcv',\n+ 'rma', 'rca')\n+ if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds:\n+ raise Exception('A type (spec \"%s\") cannot be made nullable' % base_kind)\n+ return '?' + base_kind\n+ if kind.endswith('}'):\n+ lbracket = kind.rfind('{')\n+ value = kind[0:lbracket]\n+ return 'm[' + _MapKind(kind[lbracket + 1:-1]) + '][' + _MapKind(value) + ']'\n+ if kind.endswith(']'):\n+ lbracket = kind.rfind('[')\n+ typename = kind[0:lbracket]\n+ return 'a' + kind[lbracket + 1:-1] + ':' + _MapKind(typename)\n+ if kind.endswith('&'):\n+ return 'r:' + _MapKind(kind[0:-1])\n+ if kind.startswith('asso<'):\n+ assert kind.endswith('>')\n+ return 'asso:' + _MapKind(kind[5:-1])\n+ if kind.startswith('rmt<'):\n+ assert kind.endswith('>')\n+ return 'rmt:' + _MapKind(kind[4:-1])\n+ if kind.startswith('rcv<'):\n+ assert kind.endswith('>')\n+ return 'rcv:' + _MapKind(kind[4:-1])\n+ if kind.startswith('rma<'):\n+ assert kind.endswith('>')\n+ return 'rma:' + _MapKind(kind[4:-1])\n+ if kind.startswith('rca<'):\n+ assert kind.endswith('>')\n+ return 'rca:' + _MapKind(kind[4:-1])\n+ if kind in map_to_kind:\n+ return map_to_kind[kind]\n+ return 'x:' + kind\n+\n+\n+def _AttributeListToDict(attribute_list):\n+ if attribute_list is None:\n+ return None\n+ assert isinstance(attribute_list, ast.AttributeList)\n+ # TODO(vtl): Check for duplicate keys here.\n+ return dict(\n+ [(attribute.key, attribute.value) for attribute in attribute_list])\n+\n+\n+builtin_values = frozenset([\n+ \"double.INFINITY\", \"double.NEGATIVE_INFINITY\", \"double.NAN\",\n+ \"float.INFINITY\", \"float.NEGATIVE_INFINITY\", \"float.NAN\"\n+])\n+\n+\n+def _IsBuiltinValue(value):\n+ return value in builtin_values\n+\n+\n+def _LookupKind(kinds, spec, scope):\n+ \"\"\"Tries to find which Kind a spec refers to, given the scope in which its\n+ referenced. Starts checking from the narrowest scope to most general. For\n+ example, given a struct field like\n+ Foo.Bar x;\n+ Foo.Bar could refer to the type 'Bar' in the 'Foo' namespace, or an inner\n+ type 'Bar' in the struct 'Foo' in the current namespace.\n+\n+ |scope| is a tuple that looks like (namespace, struct/interface), referring\n+ to the location where the type is referenced.\"\"\"\n+ if spec.startswith('x:'):\n+ mojom_name = spec[2:]\n+ for i in range(len(scope), -1, -1):\n+ test_spec = 'x:'\n+ if i > 0:\n+ test_spec += '.'.join(scope[:i]) + '.'\n+ test_spec += mojom_name\n+ kind = kinds.get(test_spec)\n+ if kind:\n+ return kind\n+\n+ return kinds.get(spec)\n+\n+\n+def _GetScopeForKind(module, kind):\n+ \"\"\"For a given kind, returns a tuple of progressively more specific names\n+ used to qualify the kind. For example if kind is an enum named Bar nested in a\n+ struct Foo within module 'foo', this would return ('foo', 'Foo', 'Bar')\"\"\"\n+ if isinstance(kind, mojom.Enum) and kind.parent_kind:\n+ # Enums may be nested in other kinds.\n+ return _GetScopeForKind(module, kind.parent_kind) + (kind.mojom_name, )\n+\n+ module_fragment = (module.mojom_namespace, ) if module.mojom_namespace else ()\n+ kind_fragment = (kind.mojom_name, ) if kind else ()\n+ return module_fragment + kind_fragment\n+\n+\n+def _LookupValueInScope(module, kind, identifier):\n+ \"\"\"Given a kind and an identifier, this attempts to resolve the given\n+ identifier to a concrete NamedValue within the scope of the given kind.\"\"\"\n+ scope = _GetScopeForKind(module, kind)\n+ for i in reversed(range(len(scope) + 1)):\n+ qualified_name = '.'.join(scope[:i] + (identifier, ))\n+ value = module.values.get(qualified_name)\n+ if value:\n+ return value\n+ return None\n+\n+\n+def _LookupValue(module, parent_kind, implied_kind, ast_leaf_node):\n+ \"\"\"Resolves a leaf node in the form ('IDENTIFIER', 'x') to a constant value\n+ identified by 'x' in some mojom definition. parent_kind is used as context\n+ when resolving the identifier. If the given leaf node is not an IDENTIFIER\n+ (e.g. already a constant value), it is returned as-is.\n+\n+ If implied_kind is provided, the parsed identifier may also be resolved within\n+ its scope as fallback. This can be useful for more concise value references\n+ when assigning enum-typed constants or field values.\"\"\"\n+ if not isinstance(ast_leaf_node, tuple) or ast_leaf_node[0] != 'IDENTIFIER':\n+ return ast_leaf_node\n+\n+ # First look for a known user-defined identifier to resolve this within the\n+ # enclosing scope.\n+ identifier = ast_leaf_node[1]\n+\n+ value = _LookupValueInScope(module, parent_kind, identifier)\n+ if value:\n+ return value\n+\n+ # Next look in the scope of implied_kind, if provided.\n+ value = (implied_kind and implied_kind.module and _LookupValueInScope(\n+ implied_kind.module, implied_kind, identifier))\n+ if value:\n+ return value\n+\n+ # Fall back on defined builtin symbols\n+ if _IsBuiltinValue(identifier):\n+ return mojom.BuiltinValue(identifier)\n+\n+ raise ValueError('Unknown identifier %s' % identifier)\n+\n+\n+def _Kind(kinds, spec, scope):\n+ \"\"\"Convert a type name into a mojom.Kind object.\n+\n+ As a side-effect this function adds the result to 'kinds'.\n+\n+ Args:\n+ kinds: {Dict[str, mojom.Kind]} All known kinds up to this point, indexed by\n+ their names.\n+ spec: {str} A name uniquely identifying a type.\n+ scope: {Tuple[str, str]} A tuple that looks like (namespace,\n+ struct/interface), referring to the location where the type is\n+ referenced.\n+\n+ Returns:\n+ {mojom.Kind} The type corresponding to 'spec'.\n+ \"\"\"\n+ kind = _LookupKind(kinds, spec, scope)\n+ if kind:\n+ return kind\n+\n+ if spec.startswith('?'):\n+ kind = _Kind(kinds, spec[1:], scope).MakeNullableKind()\n+ elif spec.startswith('a:'):\n+ kind = mojom.Array(_Kind(kinds, spec[2:], scope))\n+ elif spec.startswith('asso:'):\n+ inner_kind = _Kind(kinds, spec[5:], scope)\n+ if isinstance(inner_kind, mojom.InterfaceRequest):\n+ kind = mojom.AssociatedInterfaceRequest(inner_kind)\n+ else:\n+ kind = mojom.AssociatedInterface(inner_kind)\n+ elif spec.startswith('a'):\n+ colon = spec.find(':')\n+ length = int(spec[1:colon])\n+ kind = mojom.Array(_Kind(kinds, spec[colon + 1:], scope), length)\n+ elif spec.startswith('r:'):\n+ kind = mojom.InterfaceRequest(_Kind(kinds, spec[2:], scope))\n+ elif spec.startswith('rmt:'):\n+ kind = mojom.PendingRemote(_Kind(kinds, spec[4:], scope))\n+ elif spec.startswith('rcv:'):\n+ kind = mojom.PendingReceiver(_Kind(kinds, spec[4:], scope))\n+ elif spec.startswith('rma:'):\n+ kind = mojom.PendingAssociatedRemote(_Kind(kinds, spec[4:], scope))\n+ elif spec.startswith('rca:'):\n+ kind = mojom.PendingAssociatedReceiver(_Kind(kinds, spec[4:], scope))\n+ elif spec.startswith('m['):\n+ # Isolate the two types from their brackets.\n+\n+ # It is not allowed to use map as key, so there shouldn't be nested ']'s\n+ # inside the key type spec.\n+ key_end = spec.find(']')\n+ assert key_end != -1 and key_end < len(spec) - 1\n+ assert spec[key_end + 1] == '[' and spec[-1] == ']'\n+\n+ first_kind = spec[2:key_end]\n+ second_kind = spec[key_end + 2:-1]\n+\n+ kind = mojom.Map(\n+ _Kind(kinds, first_kind, scope), _Kind(kinds, second_kind, scope))\n+ else:\n+ kind = mojom.Kind(spec)\n+\n+ kinds[spec] = kind\n+ return kind\n+\n+\n+def _Import(module, import_module):\n+ # Copy the struct kinds from our imports into the current module.\n+ importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface)\n+ for kind in import_module.kinds.values():\n+ if (isinstance(kind, importable_kinds)\n+ and kind.module.path == import_module.path):\n+ module.kinds[kind.spec] = kind\n+ # Ditto for values.\n+ for value in import_module.values.values():\n+ if value.module.path == import_module.path:\n+ module.values[value.GetSpec()] = value\n+\n+ return import_module\n+\n+\n+def _Struct(module, parsed_struct):\n+ \"\"\"\n+ Args:\n+ module: {mojom.Module} Module currently being constructed.\n+ parsed_struct: {ast.Struct} Parsed struct.\n+\n+ Returns:\n+ {mojom.Struct} AST struct.\n+ \"\"\"\n+ struct = mojom.Struct(module=module)\n+ struct.mojom_name = parsed_struct.mojom_name\n+ struct.native_only = parsed_struct.body is None\n+ struct.spec = 'x:' + module.GetNamespacePrefix() + struct.mojom_name\n+ module.kinds[struct.spec] = struct\n+ struct.enums = []\n+ struct.constants = []\n+ struct.fields_data = []\n+ if not struct.native_only:\n+ _ProcessElements(\n+ parsed_struct.mojom_name, parsed_struct.body, {\n+ ast.Enum:\n+ lambda enum: struct.enums.append(_Enum(module, enum, struct)),\n+ ast.Const:\n+ lambda const: struct.constants.append(\n+ _Constant(module, const, struct)),\n+ ast.StructField:\n+ struct.fields_data.append,\n+ })\n+\n+ struct.attributes = _AttributeListToDict(parsed_struct.attribute_list)\n+\n+ # Enforce that a [Native] attribute is set to make native-only struct\n+ # declarations more explicit.\n+ if struct.native_only:\n+ if not struct.attributes or not struct.attributes.get('Native', False):\n+ raise Exception(\"Native-only struct declarations must include a \" +\n+ \"Native attribute.\")\n+\n+ if struct.attributes and struct.attributes.get('CustomSerializer', False):\n+ struct.custom_serializer = True\n+\n+ return struct\n+\n+\n+def _Union(module, parsed_union):\n+ \"\"\"\n+ Args:\n+ module: {mojom.Module} Module currently being constructed.\n+ parsed_union: {ast.Union} Parsed union.\n+\n+ Returns:\n+ {mojom.Union} AST union.\n+ \"\"\"\n+ union = mojom.Union(module=module)\n+ union.mojom_name = parsed_union.mojom_name\n+ union.spec = 'x:' + module.GetNamespacePrefix() + union.mojom_name\n+ module.kinds[union.spec] = union\n+ # Stash fields parsed_union here temporarily.\n+ union.fields_data = []\n+ _ProcessElements(parsed_union.mojom_name, parsed_union.body,\n+ {ast.UnionField: union.fields_data.append})\n+ union.attributes = _AttributeListToDict(parsed_union.attribute_list)\n+ return union\n+\n+\n+def _StructField(module, parsed_field, struct):\n+ \"\"\"\n+ Args:\n+ module: {mojom.Module} Module currently being constructed.\n+ parsed_field: {ast.StructField} Parsed struct field.\n+ struct: {mojom.Struct} Struct this field belongs to.\n+\n+ Returns:\n+ {mojom.StructField} AST struct field.\n+ \"\"\"\n+ field = mojom.StructField()\n+ field.mojom_name = parsed_field.mojom_name\n+ field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename),\n+ (module.mojom_namespace, struct.mojom_name))\n+ field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None\n+ field.default = _LookupValue(module, struct, field.kind,\n+ parsed_field.default_value)\n+ field.attributes = _AttributeListToDict(parsed_field.attribute_list)\n+ return field\n+\n+\n+def _UnionField(module, parsed_field, union):\n+ \"\"\"\n+ Args:\n+ module: {mojom.Module} Module currently being constructed.\n+ parsed_field: {ast.UnionField} Parsed union field.\n+ union: {mojom.Union} Union this fields belong to.\n+\n+ Returns:\n+ {mojom.UnionField} AST union.\n+ \"\"\"\n+ field = mojom.UnionField()\n+ field.mojom_name = parsed_field.mojom_name\n+ field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename),\n+ (module.mojom_namespace, union.mojom_name))\n+ field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None\n+ field.default = None\n+ field.attributes = _AttributeListToDict(parsed_field.attribute_list)\n+ return field\n+\n+\n+def _Parameter(module, parsed_param, interface):\n+ \"\"\"\n+ Args:\n+ module: {mojom.Module} Module currently being constructed.\n+ parsed_param: {ast.Parameter} Parsed parameter.\n+ union: {mojom.Interface} Interface this parameter belongs to.\n+\n+ Returns:\n+ {mojom.Parameter} AST parameter.\n+ \"\"\"\n+ parameter = mojom.Parameter()\n+ parameter.mojom_name = parsed_param.mojom_name\n+ parameter.kind = _Kind(module.kinds, _MapKind(parsed_param.typename),\n+ (module.mojom_namespace, interface.mojom_name))\n+ parameter.ordinal = (parsed_param.ordinal.value\n+ if parsed_param.ordinal else None)\n+ parameter.default = None # TODO(tibell): We never have these. Remove field?\n+ parameter.attributes = _AttributeListToDict(parsed_param.attribute_list)\n+ return parameter\n+\n+\n+def _Method(module, parsed_method, interface):\n+ \"\"\"\n+ Args:\n+ module: {mojom.Module} Module currently being constructed.\n+ parsed_method: {ast.Method} Parsed method.\n+ interface: {mojom.Interface} Interface this method belongs to.\n+\n+ Returns:\n+ {mojom.Method} AST method.\n+ \"\"\"\n+ method = mojom.Method(\n+ interface,\n+ parsed_method.mojom_name,\n+ ordinal=parsed_method.ordinal.value if parsed_method.ordinal else None)\n+ method.parameters = list(\n+ map(lambda parameter: _Parameter(module, parameter, interface),\n+ parsed_method.parameter_list))\n+ if parsed_method.response_parameter_list is not None:\n+ method.response_parameters = list(\n+ map(lambda parameter: _Parameter(module, parameter, interface),\n+ parsed_method.response_parameter_list))\n+ method.attributes = _AttributeListToDict(parsed_method.attribute_list)\n+\n+ # Enforce that only methods with response can have a [Sync] attribute.\n+ if method.sync and method.response_parameters is None:\n+ raise Exception(\"Only methods with response can include a [Sync] \"\n+ \"attribute. If no response parameters are needed, you \"\n+ \"could use an empty response parameter list, i.e., \"\n+ \"\\\"=> ()\\\".\")\n+\n+ return method\n+\n+\n+def _Interface(module, parsed_iface):\n+ \"\"\"\n+ Args:\n+ module: {mojom.Module} Module currently being constructed.\n+ parsed_iface: {ast.Interface} Parsed interface.\n+\n+ Returns:\n+ {mojom.Interface} AST interface.\n+ \"\"\"\n+ interface = mojom.Interface(module=module)\n+ interface.mojom_name = parsed_iface.mojom_name\n+ interface.spec = 'x:' + module.GetNamespacePrefix() + interface.mojom_name\n+ module.kinds[interface.spec] = interface\n+ interface.attributes = _AttributeListToDict(parsed_iface.attribute_list)\n+ interface.enums = []\n+ interface.constants = []\n+ interface.methods_data = []\n+ _ProcessElements(\n+ parsed_iface.mojom_name, parsed_iface.body, {\n+ ast.Enum:\n+ lambda enum: interface.enums.append(_Enum(module, enum, interface)),\n+ ast.Const:\n+ lambda const: interface.constants.append(\n+ _Constant(module, const, interface)),\n+ ast.Method:\n+ interface.methods_data.append,\n+ })\n+ return interface\n+\n+\n+def _EnumField(module, enum, parsed_field):\n+ \"\"\"\n+ Args:\n+ module: {mojom.Module} Module currently being constructed.\n+ enum: {mojom.Enum} Enum this field belongs to.\n+ parsed_field: {ast.EnumValue} Parsed enum value.\n+\n+ Returns:\n+ {mojom.EnumField} AST enum field.\n+ \"\"\"\n+ field = mojom.EnumField()\n+ field.mojom_name = parsed_field.mojom_name\n+ field.value = _LookupValue(module, enum, None, parsed_field.value)\n+ field.attributes = _AttributeListToDict(parsed_field.attribute_list)\n+ value = mojom.EnumValue(module, enum, field)\n+ module.values[value.GetSpec()] = value\n+ return field\n+\n+\n+def _ResolveNumericEnumValues(enum):\n+ \"\"\"\n+ Given a reference to a mojom.Enum, resolves and assigns the numeric value of\n+ each field, and also computes the min_value and max_value of the enum.\n+ \"\"\"\n+\n+ # map of <mojom_name> -> integral value\n+ prev_value = -1\n+ min_value = None\n+ max_value = None\n+ for field in enum.fields:\n+ # This enum value is +1 the previous enum value (e.g: BEGIN).\n+ if field.value is None:\n+ prev_value += 1\n+\n+ # Integral value (e.g: BEGIN = -0x1).\n+ elif _IsStrOrUnicode(field.value):\n+ prev_value = int(field.value, 0)\n+\n+ # Reference to a previous enum value (e.g: INIT = BEGIN).\n+ elif isinstance(field.value, mojom.EnumValue):\n+ prev_value = field.value.field.numeric_value\n+ elif isinstance(field.value, mojom.ConstantValue):\n+ constant = field.value.constant\n+ kind = constant.kind\n+ if not mojom.IsIntegralKind(kind) or mojom.IsBoolKind(kind):\n+ raise ValueError('Enum values must be integers. %s is not an integer.' %\n+ constant.mojom_name)\n+ prev_value = int(constant.value, 0)\n+ else:\n+ raise Exception('Unresolved enum value for %s' % field.value.GetSpec())\n+\n+ #resolved_enum_values[field.mojom_name] = prev_value\n+ field.numeric_value = prev_value\n+ if min_value is None or prev_value < min_value:\n+ min_value = prev_value\n+ if max_value is None or prev_value > max_value:\n+ max_value = prev_value\n+\n+ enum.min_value = min_value\n+ enum.max_value = max_value\n+\n+\n+def _Enum(module, parsed_enum, parent_kind):\n+ \"\"\"\n+ Args:\n+ module: {mojom.Module} Module currently being constructed.\n+ parsed_enum: {ast.Enum} Parsed enum.\n+\n+ Returns:\n+ {mojom.Enum} AST enum.\n+ \"\"\"\n+ enum = mojom.Enum(module=module)\n+ enum.mojom_name = parsed_enum.mojom_name\n+ enum.native_only = parsed_enum.enum_value_list is None\n+ mojom_name = enum.mojom_name\n+ if parent_kind:\n+ mojom_name = parent_kind.mojom_name + '.' + mojom_name\n+ enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name)\n+ enum.parent_kind = parent_kind\n+ enum.attributes = _AttributeListToDict(parsed_enum.attribute_list)\n+\n+ if not enum.native_only:\n+ enum.fields = list(\n+ map(lambda field: _EnumField(module, enum, field),\n+ parsed_enum.enum_value_list))\n+ _ResolveNumericEnumValues(enum)\n+\n+ module.kinds[enum.spec] = enum\n+\n+ # Enforce that a [Native] attribute is set to make native-only enum\n+ # declarations more explicit.\n+ if enum.native_only:\n+ if not enum.attributes or not enum.attributes.get('Native', False):\n+ raise Exception(\"Native-only enum declarations must include a \" +\n+ \"Native attribute.\")\n+\n+ return enum\n+\n+\n+def _Constant(module, parsed_const, parent_kind):\n+ \"\"\"\n+ Args:\n+ module: {mojom.Module} Module currently being constructed.\n+ parsed_const: {ast.Const} Parsed constant.\n+\n+ Returns:\n+ {mojom.Constant} AST constant.\n+ \"\"\"\n+ constant = mojom.Constant()\n+ constant.mojom_name = parsed_const.mojom_name\n+ if parent_kind:\n+ scope = (module.mojom_namespace, parent_kind.mojom_name)\n+ else:\n+ scope = (module.mojom_namespace, )\n+ # TODO(mpcomplete): maybe we should only support POD kinds.\n+ constant.kind = _Kind(module.kinds, _MapKind(parsed_const.typename), scope)\n+ constant.parent_kind = parent_kind\n+ constant.value = _LookupValue(module, parent_kind, constant.kind,\n+ parsed_const.value)\n+\n+ # Iteratively resolve this constant reference to a concrete value\n+ while isinstance(constant.value, mojom.ConstantValue):\n+ constant.value = constant.value.constant.value\n+\n+ value = mojom.ConstantValue(module, parent_kind, constant)\n+ module.values[value.GetSpec()] = value\n+ return constant\n+\n+\n+def _CollectReferencedKinds(module, all_defined_kinds):\n+ \"\"\"\n+ Takes a {mojom.Module} object and a list of all defined kinds within that\n+ module, and enumerates the complete dict of user-defined mojom types\n+ (as {mojom.Kind} objects) referenced by the module's own defined kinds (i.e.\n+ as types of struct or union or interface parameters. The returned dict is\n+ keyed by kind spec.\n+ \"\"\"\n+\n+ def extract_referenced_user_kinds(kind):\n+ if mojom.IsArrayKind(kind):\n+ return extract_referenced_user_kinds(kind.kind)\n+ if mojom.IsMapKind(kind):\n+ return (extract_referenced_user_kinds(kind.key_kind) +\n+ extract_referenced_user_kinds(kind.value_kind))\n+ if mojom.IsInterfaceRequestKind(kind) or mojom.IsAssociatedKind(kind):\n+ return [kind.kind]\n+ if mojom.IsStructKind(kind):\n+ return [kind]\n+ if (mojom.IsInterfaceKind(kind) or mojom.IsEnumKind(kind)\n+ or mojom.IsUnionKind(kind)):\n+ return [kind]\n+ return []\n+\n+ def sanitize_kind(kind):\n+ \"\"\"Removes nullability from a kind\"\"\"\n+ if kind.spec.startswith('?'):\n+ return _Kind(module.kinds, kind.spec[1:], (module.mojom_namespace, ''))\n+ return kind\n+\n+ referenced_user_kinds = {}\n+ for defined_kind in all_defined_kinds:\n+ if mojom.IsStructKind(defined_kind) or mojom.IsUnionKind(defined_kind):\n+ for field in defined_kind.fields:\n+ for referenced_kind in extract_referenced_user_kinds(field.kind):\n+ sanitized_kind = sanitize_kind(referenced_kind)\n+ referenced_user_kinds[sanitized_kind.spec] = sanitized_kind\n+\n+ # Also scan for references in parameter lists\n+ for interface in module.interfaces:\n+ for method in interface.methods:\n+ for param in itertools.chain(method.parameters or [],\n+ method.response_parameters or []):\n+ if (mojom.IsStructKind(param.kind) or mojom.IsUnionKind(param.kind)\n+ or mojom.IsEnumKind(param.kind)\n+ or mojom.IsAnyInterfaceKind(param.kind)):\n+ for referenced_kind in extract_referenced_user_kinds(param.kind):\n+ sanitized_kind = sanitize_kind(referenced_kind)\n+ referenced_user_kinds[sanitized_kind.spec] = sanitized_kind\n+\n+ return referenced_user_kinds\n+\n+\n+def _AssignDefaultOrdinals(items):\n+ \"\"\"Assigns default ordinal values to a sequence of items if necessary.\"\"\"\n+ next_ordinal = 0\n+ for item in items:\n+ if item.ordinal is not None:\n+ next_ordinal = item.ordinal + 1\n+ else:\n+ item.ordinal = next_ordinal\n+ next_ordinal += 1\n+\n+\n+def _AssertTypeIsStable(kind):\n+ \"\"\"Raises an error if a type is not stable, meaning it is composed of at least\n+ one type that is not marked [Stable].\"\"\"\n+\n+ def assertDependencyIsStable(dependency):\n+ if (mojom.IsEnumKind(dependency) or mojom.IsStructKind(dependency)\n+ or mojom.IsUnionKind(dependency) or mojom.IsInterfaceKind(dependency)):\n+ if not dependency.stable:\n+ raise Exception(\n+ '%s is marked [Stable] but cannot be stable because it depends on '\n+ '%s, which is not marked [Stable].' %\n+ (kind.mojom_name, dependency.mojom_name))\n+ elif mojom.IsArrayKind(dependency) or mojom.IsAnyInterfaceKind(dependency):\n+ assertDependencyIsStable(dependency.kind)\n+ elif mojom.IsMapKind(dependency):\n+ assertDependencyIsStable(dependency.key_kind)\n+ assertDependencyIsStable(dependency.value_kind)\n+\n+ if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):\n+ for field in kind.fields:\n+ assertDependencyIsStable(field.kind)\n+ elif mojom.IsInterfaceKind(kind):\n+ for method in kind.methods:\n+ for param in method.param_struct.fields:\n+ assertDependencyIsStable(param.kind)\n+ if method.response_param_struct:\n+ for response_param in method.response_param_struct.fields:\n+ assertDependencyIsStable(response_param.kind)\n+\n+\n+def _Module(tree, path, imports):\n+ \"\"\"\n+ Args:\n+ tree: {ast.Mojom} The parse tree.\n+ path: {str} The path to the mojom file.\n+ imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in\n+ the import list, to already processed modules. Used to process imports.\n+\n+ Returns:\n+ {mojom.Module} An AST for the mojom.\n+ \"\"\"\n+ module = mojom.Module(path=path)\n+ module.kinds = {}\n+ for kind in mojom.PRIMITIVES:\n+ module.kinds[kind.spec] = kind\n+\n+ module.values = {}\n+\n+ module.mojom_namespace = tree.module.mojom_namespace[1] if tree.module else ''\n+ # Imports must come first, because they add to module.kinds which is used\n+ # by by the others.\n+ module.imports = [\n+ _Import(module, imports[imp.import_filename]) for imp in tree.import_list\n+ ]\n+ if tree.module and tree.module.attribute_list:\n+ assert isinstance(tree.module.attribute_list, ast.AttributeList)\n+ # TODO(vtl): Check for duplicate keys here.\n+ module.attributes = dict((attribute.key, attribute.value)\n+ for attribute in tree.module.attribute_list)\n+\n+ filename = os.path.basename(path)\n+ # First pass collects kinds.\n+ module.constants = []\n+ module.enums = []\n+ module.structs = []\n+ module.unions = []\n+ module.interfaces = []\n+ _ProcessElements(\n+ filename, tree.definition_list, {\n+ ast.Const:\n+ lambda const: module.constants.append(_Constant(module, const, None)),\n+ ast.Enum:\n+ lambda enum: module.enums.append(_Enum(module, enum, None)),\n+ ast.Struct:\n+ lambda struct: module.structs.append(_Struct(module, struct)),\n+ ast.Union:\n+ lambda union: module.unions.append(_Union(module, union)),\n+ ast.Interface:\n+ lambda interface: module.interfaces.append(\n+ _Interface(module, interface)),\n+ })\n+\n+ # Second pass expands fields and methods. This allows fields and parameters\n+ # to refer to kinds defined anywhere in the mojom.\n+ all_defined_kinds = {}\n+ for struct in module.structs:\n+ struct.fields = list(\n+ map(lambda field: _StructField(module, field, struct),\n+ struct.fields_data))\n+ _AssignDefaultOrdinals(struct.fields)\n+ del struct.fields_data\n+ all_defined_kinds[struct.spec] = struct\n+ for enum in struct.enums:\n+ all_defined_kinds[enum.spec] = enum\n+\n+ for union in module.unions:\n+ union.fields = list(\n+ map(lambda field: _UnionField(module, field, union), union.fields_data))\n+ _AssignDefaultOrdinals(union.fields)\n+ del union.fields_data\n+ all_defined_kinds[union.spec] = union\n+\n+ for interface in module.interfaces:\n+ interface.methods = list(\n+ map(lambda method: _Method(module, method, interface),\n+ interface.methods_data))\n+ _AssignDefaultOrdinals(interface.methods)\n+ del interface.methods_data\n+ all_defined_kinds[interface.spec] = interface\n+ for enum in interface.enums:\n+ all_defined_kinds[enum.spec] = enum\n+ for enum in module.enums:\n+ all_defined_kinds[enum.spec] = enum\n+\n+ all_referenced_kinds = _CollectReferencedKinds(module,\n+ all_defined_kinds.values())\n+ imported_kind_specs = set(all_referenced_kinds.keys()).difference(\n+ set(all_defined_kinds.keys()))\n+ module.imported_kinds = dict(\n+ (spec, all_referenced_kinds[spec]) for spec in imported_kind_specs)\n+\n+ generator.AddComputedData(module)\n+ for iface in module.interfaces:\n+ for method in iface.methods:\n+ if method.param_struct:\n+ _AssignDefaultOrdinals(method.param_struct.fields)\n+ if method.response_param_struct:\n+ _AssignDefaultOrdinals(method.response_param_struct.fields)\n+\n+ # Ensure that all types marked [Stable] are actually stable. Enums are\n+ # automatically OK since they don't depend on other definitions.\n+ for kinds in (module.structs, module.unions, module.interfaces):\n+ for kind in kinds:\n+ if kind.stable:\n+ _AssertTypeIsStable(kind)\n+\n+ return module\n+\n+\n+def OrderedModule(tree, path, imports):\n+ \"\"\"Convert parse tree to AST module.\n+\n+ Args:\n+ tree: {ast.Mojom} The parse tree.\n+ path: {str} The path to the mojom file.\n+ imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in\n+ the import list, to already processed modules. Used to process imports.\n+\n+ Returns:\n+ {mojom.Module} An AST for the mojom.\n+ \"\"\"\n+ module = _Module(tree, path, imports)\n+ return module\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py\nnew file mode 100644\nindex 00000000..19905c8a\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py\n@@ -0,0 +1,73 @@\n+# Copyright 2014 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import imp\n+import os.path\n+import sys\n+import unittest\n+\n+from mojom.generate import module as mojom\n+from mojom.generate import translate\n+from mojom.parse import ast\n+\n+\n+class TranslateTest(unittest.TestCase):\n+ \"\"\"Tests |parser.Parse()|.\"\"\"\n+\n+ def testSimpleArray(self):\n+ \"\"\"Tests a simple int32[].\"\"\"\n+ # pylint: disable=W0212\n+ self.assertEquals(translate._MapKind(\"int32[]\"), \"a:i32\")\n+\n+ def testAssociativeArray(self):\n+ \"\"\"Tests a simple uint8{string}.\"\"\"\n+ # pylint: disable=W0212\n+ self.assertEquals(translate._MapKind(\"uint8{string}\"), \"m[s][u8]\")\n+\n+ def testLeftToRightAssociativeArray(self):\n+ \"\"\"Makes sure that parsing is done from right to left on the internal kinds\n+ in the presence of an associative array.\"\"\"\n+ # pylint: disable=W0212\n+ self.assertEquals(translate._MapKind(\"uint8[]{string}\"), \"m[s][a:u8]\")\n+\n+ def testTranslateSimpleUnions(self):\n+ \"\"\"Makes sure that a simple union is translated correctly.\"\"\"\n+ tree = ast.Mojom(None, ast.ImportList(), [\n+ ast.Union(\n+ \"SomeUnion\", None,\n+ ast.UnionBody([\n+ ast.UnionField(\"a\", None, None, \"int32\"),\n+ ast.UnionField(\"b\", None, None, \"string\")\n+ ]))\n+ ])\n+\n+ translation = translate.OrderedModule(tree, \"mojom_tree\", [])\n+ self.assertEqual(1, len(translation.unions))\n+\n+ union = translation.unions[0]\n+ self.assertTrue(isinstance(union, mojom.Union))\n+ self.assertEqual(\"SomeUnion\", union.mojom_name)\n+ self.assertEqual(2, len(union.fields))\n+ self.assertEqual(\"a\", union.fields[0].mojom_name)\n+ self.assertEqual(mojom.INT32.spec, union.fields[0].kind.spec)\n+ self.assertEqual(\"b\", union.fields[1].mojom_name)\n+ self.assertEqual(mojom.STRING.spec, union.fields[1].kind.spec)\n+\n+ def testMapKindRaisesWithDuplicate(self):\n+ \"\"\"Verifies _MapTreeForType() raises when passed two values with the same\n+ name.\"\"\"\n+ methods = [\n+ ast.Method('dup', None, None, ast.ParameterList(), None),\n+ ast.Method('dup', None, None, ast.ParameterList(), None)\n+ ]\n+ with self.assertRaises(Exception):\n+ translate._ElemsOfType(methods, ast.Method, 'scope')\n+\n+ def testAssociatedKinds(self):\n+ \"\"\"Tests type spec translation of associated interfaces and requests.\"\"\"\n+ # pylint: disable=W0212\n+ self.assertEquals(\n+ translate._MapKind(\"asso<SomeInterface>?\"), \"?asso:x:SomeInterface\")\n+ self.assertEquals(\n+ translate._MapKind(\"asso<SomeInterface&>?\"), \"?asso:r:x:SomeInterface\")\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py\nnew file mode 100644\nindex 00000000..e69de29b\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py\nnew file mode 100644\nindex 00000000..1f0db200\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py\n@@ -0,0 +1,427 @@\n+# Copyright 2014 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\"\"\"Node classes for the AST for a Mojo IDL file.\"\"\"\n+\n+# Note: For convenience of testing, you probably want to define __eq__() methods\n+# for all node types; it's okay to be slightly lax (e.g., not compare filename\n+# and lineno). You may also define __repr__() to help with analyzing test\n+# failures, especially for more complex types.\n+\n+\n+import sys\n+\n+\n+def _IsStrOrUnicode(x):\n+ if sys.version_info[0] < 3:\n+ return isinstance(x, (unicode, str))\n+ return isinstance(x, str)\n+\n+\n+class NodeBase(object):\n+ \"\"\"Base class for nodes in the AST.\"\"\"\n+\n+ def __init__(self, filename=None, lineno=None):\n+ self.filename = filename\n+ self.lineno = lineno\n+\n+ def __eq__(self, other):\n+ # We want strict comparison of the two object's types. Disable pylint's\n+ # insistence upon recommending isinstance().\n+ # pylint: disable=unidiomatic-typecheck\n+ return type(self) == type(other)\n+\n+ # Make != the inverse of ==. (Subclasses shouldn't have to override this.)\n+ def __ne__(self, other):\n+ return not self == other\n+\n+\n+# TODO(vtl): Some of this is complicated enough that it should be tested.\n+class NodeListBase(NodeBase):\n+ \"\"\"Represents a list of other nodes, all having the same type. (This is meant\n+ to be subclassed, with subclasses defining _list_item_type to be the class (or\n+ classes, in a tuple) of the members of the list.)\"\"\"\n+\n+ def __init__(self, item_or_items=None, **kwargs):\n+ super(NodeListBase, self).__init__(**kwargs)\n+ self.items = []\n+ if item_or_items is None:\n+ pass\n+ elif isinstance(item_or_items, list):\n+ for item in item_or_items:\n+ assert isinstance(item, self._list_item_type)\n+ self.Append(item)\n+ else:\n+ assert isinstance(item_or_items, self._list_item_type)\n+ self.Append(item_or_items)\n+\n+ # Support iteration. For everything else, users should just access |items|\n+ # directly. (We intentionally do NOT supply |__len__()| or |__nonzero__()|, so\n+ # |bool(NodeListBase())| is true.)\n+ def __iter__(self):\n+ return self.items.__iter__()\n+\n+ def __eq__(self, other):\n+ return super(NodeListBase, self).__eq__(other) and \\\n+ self.items == other.items\n+\n+ # Implement this so that on failure, we get slightly more sensible output.\n+ def __repr__(self):\n+ return self.__class__.__name__ + \"([\" + \\\n+ \", \".join([repr(elem) for elem in self.items]) + \"])\"\n+\n+ def Insert(self, item):\n+ \"\"\"Inserts item at the front of the list.\"\"\"\n+\n+ assert isinstance(item, self._list_item_type)\n+ self.items.insert(0, item)\n+ self._UpdateFilenameAndLineno()\n+\n+ def Append(self, item):\n+ \"\"\"Appends item to the end of the list.\"\"\"\n+\n+ assert isinstance(item, self._list_item_type)\n+ self.items.append(item)\n+ self._UpdateFilenameAndLineno()\n+\n+ def _UpdateFilenameAndLineno(self):\n+ if self.items:\n+ self.filename = self.items[0].filename\n+ self.lineno = self.items[0].lineno\n+\n+\n+class Definition(NodeBase):\n+ \"\"\"Represents a definition of anything that has a global name (e.g., enums,\n+ enum values, consts, structs, struct fields, interfaces). (This does not\n+ include parameter definitions.) This class is meant to be subclassed.\"\"\"\n+\n+ def __init__(self, mojom_name, **kwargs):\n+ assert _IsStrOrUnicode(mojom_name)\n+ NodeBase.__init__(self, **kwargs)\n+ self.mojom_name = mojom_name\n+\n+\n+################################################################################\n+\n+\n+class Attribute(NodeBase):\n+ \"\"\"Represents an attribute.\"\"\"\n+\n+ def __init__(self, key, value, **kwargs):\n+ assert _IsStrOrUnicode(key)\n+ super(Attribute, self).__init__(**kwargs)\n+ self.key = key\n+ self.value = value\n+\n+ def __eq__(self, other):\n+ return super(Attribute, self).__eq__(other) and \\\n+ self.key == other.key and \\\n+ self.value == other.value\n+\n+\n+class AttributeList(NodeListBase):\n+ \"\"\"Represents a list attributes.\"\"\"\n+\n+ _list_item_type = Attribute\n+\n+\n+class Const(Definition):\n+ \"\"\"Represents a const definition.\"\"\"\n+\n+ def __init__(self, mojom_name, attribute_list, typename, value, **kwargs):\n+ assert attribute_list is None or isinstance(attribute_list, AttributeList)\n+ # The typename is currently passed through as a string.\n+ assert _IsStrOrUnicode(typename)\n+ # The value is either a literal (currently passed through as a string) or a\n+ # \"wrapped identifier\".\n+ assert _IsStrOrUnicode or isinstance(value, tuple)\n+ super(Const, self).__init__(mojom_name, **kwargs)\n+ self.attribute_list = attribute_list\n+ self.typename = typename\n+ self.value = value\n+\n+ def __eq__(self, other):\n+ return super(Const, self).__eq__(other) and \\\n+ self.attribute_list == other.attribute_list and \\\n+ self.typename == other.typename and \\\n+ self.value == other.value\n+\n+\n+class Enum(Definition):\n+ \"\"\"Represents an enum definition.\"\"\"\n+\n+ def __init__(self, mojom_name, attribute_list, enum_value_list, **kwargs):\n+ assert attribute_list is None or isinstance(attribute_list, AttributeList)\n+ assert enum_value_list is None or isinstance(enum_value_list, EnumValueList)\n+ super(Enum, self).__init__(mojom_name, **kwargs)\n+ self.attribute_list = attribute_list\n+ self.enum_value_list = enum_value_list\n+\n+ def __eq__(self, other):\n+ return super(Enum, self).__eq__(other) and \\\n+ self.attribute_list == other.attribute_list and \\\n+ self.enum_value_list == other.enum_value_list\n+\n+\n+class EnumValue(Definition):\n+ \"\"\"Represents a definition of an enum value.\"\"\"\n+\n+ def __init__(self, mojom_name, attribute_list, value, **kwargs):\n+ # The optional value is either an int (which is current a string) or a\n+ # \"wrapped identifier\".\n+ assert attribute_list is None or isinstance(attribute_list, AttributeList)\n+ assert value is None or _IsStrOrUnicode(value) or isinstance(value, tuple)\n+ super(EnumValue, self).__init__(mojom_name, **kwargs)\n+ self.attribute_list = attribute_list\n+ self.value = value\n+\n+ def __eq__(self, other):\n+ return super(EnumValue, self).__eq__(other) and \\\n+ self.attribute_list == other.attribute_list and \\\n+ self.value == other.value\n+\n+\n+class EnumValueList(NodeListBase):\n+ \"\"\"Represents a list of enum value definitions (i.e., the \"body\" of an enum\n+ definition).\"\"\"\n+\n+ _list_item_type = EnumValue\n+\n+\n+class Import(NodeBase):\n+ \"\"\"Represents an import statement.\"\"\"\n+\n+ def __init__(self, attribute_list, import_filename, **kwargs):\n+ assert attribute_list is None or isinstance(attribute_list, AttributeList)\n+ assert _IsStrOrUnicode(import_filename)\n+ super(Import, self).__init__(**kwargs)\n+ self.attribute_list = attribute_list\n+ self.import_filename = import_filename\n+\n+ def __eq__(self, other):\n+ return super(Import, self).__eq__(other) and \\\n+ self.attribute_list == other.attribute_list and \\\n+ self.import_filename == other.import_filename\n+\n+\n+class ImportList(NodeListBase):\n+ \"\"\"Represents a list (i.e., sequence) of import statements.\"\"\"\n+\n+ _list_item_type = Import\n+\n+\n+class Interface(Definition):\n+ \"\"\"Represents an interface definition.\"\"\"\n+\n+ def __init__(self, mojom_name, attribute_list, body, **kwargs):\n+ assert attribute_list is None or isinstance(attribute_list, AttributeList)\n+ assert isinstance(body, InterfaceBody)\n+ super(Interface, self).__init__(mojom_name, **kwargs)\n+ self.attribute_list = attribute_list\n+ self.body = body\n+\n+ def __eq__(self, other):\n+ return super(Interface, self).__eq__(other) and \\\n+ self.attribute_list == other.attribute_list and \\\n+ self.body == other.body\n+\n+\n+class Method(Definition):\n+ \"\"\"Represents a method definition.\"\"\"\n+\n+ def __init__(self, mojom_name, attribute_list, ordinal, parameter_list,\n+ response_parameter_list, **kwargs):\n+ assert attribute_list is None or isinstance(attribute_list, AttributeList)\n+ assert ordinal is None or isinstance(ordinal, Ordinal)\n+ assert isinstance(parameter_list, ParameterList)\n+ assert response_parameter_list is None or \\\n+ isinstance(response_parameter_list, ParameterList)\n+ super(Method, self).__init__(mojom_name, **kwargs)\n+ self.attribute_list = attribute_list\n+ self.ordinal = ordinal\n+ self.parameter_list = parameter_list\n+ self.response_parameter_list = response_parameter_list\n+\n+ def __eq__(self, other):\n+ return super(Method, self).__eq__(other) and \\\n+ self.attribute_list == other.attribute_list and \\\n+ self.ordinal == other.ordinal and \\\n+ self.parameter_list == other.parameter_list and \\\n+ self.response_parameter_list == other.response_parameter_list\n+\n+\n+# This needs to be declared after |Method|.\n+class InterfaceBody(NodeListBase):\n+ \"\"\"Represents the body of (i.e., list of definitions inside) an interface.\"\"\"\n+\n+ _list_item_type = (Const, Enum, Method)\n+\n+\n+class Module(NodeBase):\n+ \"\"\"Represents a module statement.\"\"\"\n+\n+ def __init__(self, mojom_namespace, attribute_list, **kwargs):\n+ # |mojom_namespace| is either none or a \"wrapped identifier\".\n+ assert mojom_namespace is None or isinstance(mojom_namespace, tuple)\n+ assert attribute_list is None or isinstance(attribute_list, AttributeList)\n+ super(Module, self).__init__(**kwargs)\n+ self.mojom_namespace = mojom_namespace\n+ self.attribute_list = attribute_list\n+\n+ def __eq__(self, other):\n+ return super(Module, self).__eq__(other) and \\\n+ self.mojom_namespace == other.mojom_namespace and \\\n+ self.attribute_list == other.attribute_list\n+\n+\n+class Mojom(NodeBase):\n+ \"\"\"Represents an entire .mojom file. (This is the root node.)\"\"\"\n+\n+ def __init__(self, module, import_list, definition_list, **kwargs):\n+ assert module is None or isinstance(module, Module)\n+ assert isinstance(import_list, ImportList)\n+ assert isinstance(definition_list, list)\n+ super(Mojom, self).__init__(**kwargs)\n+ self.module = module\n+ self.import_list = import_list\n+ self.definition_list = definition_list\n+\n+ def __eq__(self, other):\n+ return super(Mojom, self).__eq__(other) and \\\n+ self.module == other.module and \\\n+ self.import_list == other.import_list and \\\n+ self.definition_list == other.definition_list\n+\n+ def __repr__(self):\n+ return \"%s(%r, %r, %r)\" % (self.__class__.__name__, self.module,\n+ self.import_list, self.definition_list)\n+\n+\n+class Ordinal(NodeBase):\n+ \"\"\"Represents an ordinal value labeling, e.g., a struct field.\"\"\"\n+\n+ def __init__(self, value, **kwargs):\n+ assert isinstance(value, int)\n+ super(Ordinal, self).__init__(**kwargs)\n+ self.value = value\n+\n+ def __eq__(self, other):\n+ return super(Ordinal, self).__eq__(other) and \\\n+ self.value == other.value\n+\n+\n+class Parameter(NodeBase):\n+ \"\"\"Represents a method request or response parameter.\"\"\"\n+\n+ def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):\n+ assert _IsStrOrUnicode(mojom_name)\n+ assert attribute_list is None or isinstance(attribute_list, AttributeList)\n+ assert ordinal is None or isinstance(ordinal, Ordinal)\n+ assert _IsStrOrUnicode(typename)\n+ super(Parameter, self).__init__(**kwargs)\n+ self.mojom_name = mojom_name\n+ self.attribute_list = attribute_list\n+ self.ordinal = ordinal\n+ self.typename = typename\n+\n+ def __eq__(self, other):\n+ return super(Parameter, self).__eq__(other) and \\\n+ self.mojom_name == other.mojom_name and \\\n+ self.attribute_list == other.attribute_list and \\\n+ self.ordinal == other.ordinal and \\\n+ self.typename == other.typename\n+\n+\n+class ParameterList(NodeListBase):\n+ \"\"\"Represents a list of (method request or response) parameters.\"\"\"\n+\n+ _list_item_type = Parameter\n+\n+\n+class Struct(Definition):\n+ \"\"\"Represents a struct definition.\"\"\"\n+\n+ def __init__(self, mojom_name, attribute_list, body, **kwargs):\n+ assert attribute_list is None or isinstance(attribute_list, AttributeList)\n+ assert isinstance(body, StructBody) or body is None\n+ super(Struct, self).__init__(mojom_name, **kwargs)\n+ self.attribute_list = attribute_list\n+ self.body = body\n+\n+ def __eq__(self, other):\n+ return super(Struct, self).__eq__(other) and \\\n+ self.attribute_list == other.attribute_list and \\\n+ self.body == other.body\n+\n+\n+class StructField(Definition):\n+ \"\"\"Represents a struct field definition.\"\"\"\n+\n+ def __init__(self, mojom_name, attribute_list, ordinal, typename,\n+ default_value, **kwargs):\n+ assert _IsStrOrUnicode(mojom_name)\n+ assert attribute_list is None or isinstance(attribute_list, AttributeList)\n+ assert ordinal is None or isinstance(ordinal, Ordinal)\n+ assert _IsStrOrUnicode(typename)\n+ # The optional default value is currently either a value as a string or a\n+ # \"wrapped identifier\".\n+ assert default_value is None or _IsStrOrUnicode(default_value) or \\\n+ isinstance(default_value, tuple)\n+ super(StructField, self).__init__(mojom_name, **kwargs)\n+ self.attribute_list = attribute_list\n+ self.ordinal = ordinal\n+ self.typename = typename\n+ self.default_value = default_value\n+\n+ def __eq__(self, other):\n+ return super(StructField, self).__eq__(other) and \\\n+ self.attribute_list == other.attribute_list and \\\n+ self.ordinal == other.ordinal and \\\n+ self.typename == other.typename and \\\n+ self.default_value == other.default_value\n+\n+\n+# This needs to be declared after |StructField|.\n+class StructBody(NodeListBase):\n+ \"\"\"Represents the body of (i.e., list of definitions inside) a struct.\"\"\"\n+\n+ _list_item_type = (Const, Enum, StructField)\n+\n+\n+class Union(Definition):\n+ \"\"\"Represents a union definition.\"\"\"\n+\n+ def __init__(self, mojom_name, attribute_list, body, **kwargs):\n+ assert attribute_list is None or isinstance(attribute_list, AttributeList)\n+ assert isinstance(body, UnionBody)\n+ super(Union, self).__init__(mojom_name, **kwargs)\n+ self.attribute_list = attribute_list\n+ self.body = body\n+\n+ def __eq__(self, other):\n+ return super(Union, self).__eq__(other) and \\\n+ self.attribute_list == other.attribute_list and \\\n+ self.body == other.body\n+\n+\n+class UnionField(Definition):\n+ def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):\n+ assert _IsStrOrUnicode(mojom_name)\n+ assert attribute_list is None or isinstance(attribute_list, AttributeList)\n+ assert ordinal is None or isinstance(ordinal, Ordinal)\n+ assert _IsStrOrUnicode(typename)\n+ super(UnionField, self).__init__(mojom_name, **kwargs)\n+ self.attribute_list = attribute_list\n+ self.ordinal = ordinal\n+ self.typename = typename\n+\n+ def __eq__(self, other):\n+ return super(UnionField, self).__eq__(other) and \\\n+ self.attribute_list == other.attribute_list and \\\n+ self.ordinal == other.ordinal and \\\n+ self.typename == other.typename\n+\n+\n+class UnionBody(NodeListBase):\n+\n+ _list_item_type = UnionField\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py\nnew file mode 100644\nindex 00000000..62798631\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py\n@@ -0,0 +1,121 @@\n+# Copyright 2014 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import imp\n+import os.path\n+import sys\n+import unittest\n+\n+from mojom.parse import ast\n+\n+\n+class _TestNode(ast.NodeBase):\n+ \"\"\"Node type for tests.\"\"\"\n+\n+ def __init__(self, value, **kwargs):\n+ super(_TestNode, self).__init__(**kwargs)\n+ self.value = value\n+\n+ def __eq__(self, other):\n+ return super(_TestNode, self).__eq__(other) and self.value == other.value\n+\n+\n+class _TestNodeList(ast.NodeListBase):\n+ \"\"\"Node list type for tests.\"\"\"\n+\n+ _list_item_type = _TestNode\n+\n+\n+class ASTTest(unittest.TestCase):\n+ \"\"\"Tests various AST classes.\"\"\"\n+\n+ def testNodeBase(self):\n+ # Test |__eq__()|; this is only used for testing, where we want to do\n+ # comparison by value and ignore filenames/line numbers (for convenience).\n+ node1 = ast.NodeBase(filename=\"hello.mojom\", lineno=123)\n+ node2 = ast.NodeBase()\n+ self.assertEquals(node1, node2)\n+ self.assertEquals(node2, node1)\n+\n+ # Check that |__ne__()| just defers to |__eq__()| properly.\n+ self.assertFalse(node1 != node2)\n+ self.assertFalse(node2 != node1)\n+\n+ # Check that |filename| and |lineno| are set properly (and are None by\n+ # default).\n+ self.assertEquals(node1.filename, \"hello.mojom\")\n+ self.assertEquals(node1.lineno, 123)\n+ self.assertIsNone(node2.filename)\n+ self.assertIsNone(node2.lineno)\n+\n+ # |NodeBase|'s |__eq__()| should compare types (and a subclass's |__eq__()|\n+ # should first defer to its superclass's).\n+ node3 = _TestNode(123)\n+ self.assertNotEqual(node1, node3)\n+ self.assertNotEqual(node3, node1)\n+ # Also test |__eq__()| directly.\n+ self.assertFalse(node1 == node3)\n+ self.assertFalse(node3 == node1)\n+\n+ node4 = _TestNode(123, filename=\"world.mojom\", lineno=123)\n+ self.assertEquals(node4, node3)\n+ node5 = _TestNode(456)\n+ self.assertNotEquals(node5, node4)\n+\n+ def testNodeListBase(self):\n+ node1 = _TestNode(1, filename=\"foo.mojom\", lineno=1)\n+ # Equal to, but not the same as, |node1|:\n+ node1b = _TestNode(1, filename=\"foo.mojom\", lineno=1)\n+ node2 = _TestNode(2, filename=\"foo.mojom\", lineno=2)\n+\n+ nodelist1 = _TestNodeList() # Contains: (empty).\n+ self.assertEquals(nodelist1, nodelist1)\n+ self.assertEquals(nodelist1.items, [])\n+ self.assertIsNone(nodelist1.filename)\n+ self.assertIsNone(nodelist1.lineno)\n+\n+ nodelist2 = _TestNodeList(node1) # Contains: 1.\n+ self.assertEquals(nodelist2, nodelist2)\n+ self.assertEquals(nodelist2.items, [node1])\n+ self.assertNotEqual(nodelist2, nodelist1)\n+ self.assertEquals(nodelist2.filename, \"foo.mojom\")\n+ self.assertEquals(nodelist2.lineno, 1)\n+\n+ nodelist3 = _TestNodeList([node2]) # Contains: 2.\n+ self.assertEquals(nodelist3.items, [node2])\n+ self.assertNotEqual(nodelist3, nodelist1)\n+ self.assertNotEqual(nodelist3, nodelist2)\n+ self.assertEquals(nodelist3.filename, \"foo.mojom\")\n+ self.assertEquals(nodelist3.lineno, 2)\n+\n+ nodelist1.Append(node1b) # Contains: 1.\n+ self.assertEquals(nodelist1.items, [node1])\n+ self.assertEquals(nodelist1, nodelist2)\n+ self.assertNotEqual(nodelist1, nodelist3)\n+ self.assertEquals(nodelist1.filename, \"foo.mojom\")\n+ self.assertEquals(nodelist1.lineno, 1)\n+\n+ nodelist1.Append(node2) # Contains: 1, 2.\n+ self.assertEquals(nodelist1.items, [node1, node2])\n+ self.assertNotEqual(nodelist1, nodelist2)\n+ self.assertNotEqual(nodelist1, nodelist3)\n+ self.assertEquals(nodelist1.lineno, 1)\n+\n+ nodelist2.Append(node2) # Contains: 1, 2.\n+ self.assertEquals(nodelist2.items, [node1, node2])\n+ self.assertEquals(nodelist2, nodelist1)\n+ self.assertNotEqual(nodelist2, nodelist3)\n+ self.assertEquals(nodelist2.lineno, 1)\n+\n+ nodelist3.Insert(node1) # Contains: 1, 2.\n+ self.assertEquals(nodelist3.items, [node1, node2])\n+ self.assertEquals(nodelist3, nodelist1)\n+ self.assertEquals(nodelist3, nodelist2)\n+ self.assertEquals(nodelist3.lineno, 1)\n+\n+ # Test iteration:\n+ i = 1\n+ for item in nodelist1:\n+ self.assertEquals(item.value, i)\n+ i += 1\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py\nnew file mode 100644\nindex 00000000..3cb73c5d\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py\n@@ -0,0 +1,82 @@\n+# Copyright 2018 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\"\"\"Helpers for processing conditionally enabled features in a mojom.\"\"\"\n+\n+from mojom.error import Error\n+from mojom.parse import ast\n+\n+\n+class EnableIfError(Error):\n+ \"\"\" Class for errors from .\"\"\"\n+\n+ def __init__(self, filename, message, lineno=None):\n+ Error.__init__(self, filename, message, lineno=lineno, addenda=None)\n+\n+\n+def _IsEnabled(definition, enabled_features):\n+ \"\"\"Returns true if a definition is enabled.\n+\n+ A definition is enabled if it has no EnableIf attribute, or if the value of\n+ the EnableIf attribute is in enabled_features.\n+ \"\"\"\n+ if not hasattr(definition, \"attribute_list\"):\n+ return True\n+ if not definition.attribute_list:\n+ return True\n+\n+ already_defined = False\n+ for a in definition.attribute_list:\n+ if a.key == 'EnableIf':\n+ if already_defined:\n+ raise EnableIfError(\n+ definition.filename,\n+ \"EnableIf attribute may only be defined once per field.\",\n+ definition.lineno)\n+ already_defined = True\n+\n+ for attribute in definition.attribute_list:\n+ if attribute.key == 'EnableIf' and attribute.value not in enabled_features:\n+ return False\n+ return True\n+\n+\n+def _FilterDisabledFromNodeList(node_list, enabled_features):\n+ if not node_list:\n+ return\n+ assert isinstance(node_list, ast.NodeListBase)\n+ node_list.items = [\n+ item for item in node_list.items if _IsEnabled(item, enabled_features)\n+ ]\n+ for item in node_list.items:\n+ _FilterDefinition(item, enabled_features)\n+\n+\n+def _FilterDefinition(definition, enabled_features):\n+ \"\"\"Filters definitions with a body.\"\"\"\n+ if isinstance(definition, ast.Enum):\n+ _FilterDisabledFromNodeList(definition.enum_value_list, enabled_features)\n+ elif isinstance(definition, ast.Interface):\n+ _FilterDisabledFromNodeList(definition.body, enabled_features)\n+ elif isinstance(definition, ast.Method):\n+ _FilterDisabledFromNodeList(definition.parameter_list, enabled_features)\n+ _FilterDisabledFromNodeList(definition.response_parameter_list,\n+ enabled_features)\n+ elif isinstance(definition, ast.Struct):\n+ _FilterDisabledFromNodeList(definition.body, enabled_features)\n+ elif isinstance(definition, ast.Union):\n+ _FilterDisabledFromNodeList(definition.body, enabled_features)\n+\n+\n+def RemoveDisabledDefinitions(mojom, enabled_features):\n+ \"\"\"Removes conditionally disabled definitions from a Mojom node.\"\"\"\n+ mojom.import_list = ast.ImportList([\n+ imported_file for imported_file in mojom.import_list\n+ if _IsEnabled(imported_file, enabled_features)\n+ ])\n+ mojom.definition_list = [\n+ definition for definition in mojom.definition_list\n+ if _IsEnabled(definition, enabled_features)\n+ ]\n+ for definition in mojom.definition_list:\n+ _FilterDefinition(definition, enabled_features)\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py\nnew file mode 100644\nindex 00000000..aa609be7\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py\n@@ -0,0 +1,233 @@\n+# Copyright 2018 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import imp\n+import os\n+import sys\n+import unittest\n+\n+\n+def _GetDirAbove(dirname):\n+ \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n+ also be \"above\" this file).\"\"\"\n+ path = os.path.abspath(__file__)\n+ while True:\n+ path, tail = os.path.split(path)\n+ assert tail\n+ if tail == dirname:\n+ return path\n+\n+\n+try:\n+ imp.find_module('mojom')\n+except ImportError:\n+ sys.path.append(os.path.join(_GetDirAbove('pylib'), 'pylib'))\n+import mojom.parse.ast as ast\n+import mojom.parse.conditional_features as conditional_features\n+import mojom.parse.parser as parser\n+\n+ENABLED_FEATURES = frozenset({'red', 'green', 'blue'})\n+\n+\n+class ConditionalFeaturesTest(unittest.TestCase):\n+ \"\"\"Tests |mojom.parse.conditional_features|.\"\"\"\n+\n+ def parseAndAssertEqual(self, source, expected_source):\n+ definition = parser.Parse(source, \"my_file.mojom\")\n+ conditional_features.RemoveDisabledDefinitions(definition, ENABLED_FEATURES)\n+ expected = parser.Parse(expected_source, \"my_file.mojom\")\n+ self.assertEquals(definition, expected)\n+\n+ def testFilterConst(self):\n+ \"\"\"Test that Consts are correctly filtered.\"\"\"\n+ const_source = \"\"\"\n+ [EnableIf=blue]\n+ const int kMyConst1 = 1;\n+ [EnableIf=orange]\n+ const double kMyConst2 = 2;\n+ const int kMyConst3 = 3;\n+ \"\"\"\n+ expected_source = \"\"\"\n+ [EnableIf=blue]\n+ const int kMyConst1 = 1;\n+ const int kMyConst3 = 3;\n+ \"\"\"\n+ self.parseAndAssertEqual(const_source, expected_source)\n+\n+ def testFilterEnum(self):\n+ \"\"\"Test that EnumValues are correctly filtered from an Enum.\"\"\"\n+ enum_source = \"\"\"\n+ enum MyEnum {\n+ [EnableIf=purple]\n+ VALUE1,\n+ [EnableIf=blue]\n+ VALUE2,\n+ VALUE3,\n+ };\n+ \"\"\"\n+ expected_source = \"\"\"\n+ enum MyEnum {\n+ [EnableIf=blue]\n+ VALUE2,\n+ VALUE3\n+ };\n+ \"\"\"\n+ self.parseAndAssertEqual(enum_source, expected_source)\n+\n+ def testFilterImport(self):\n+ \"\"\"Test that imports are correctly filtered from a Mojom.\"\"\"\n+ import_source = \"\"\"\n+ [EnableIf=blue]\n+ import \"foo.mojom\";\n+ import \"bar.mojom\";\n+ [EnableIf=purple]\n+ import \"baz.mojom\";\n+ \"\"\"\n+ expected_source = \"\"\"\n+ [EnableIf=blue]\n+ import \"foo.mojom\";\n+ import \"bar.mojom\";\n+ \"\"\"\n+ self.parseAndAssertEqual(import_source, expected_source)\n+\n+ def testFilterInterface(self):\n+ \"\"\"Test that definitions are correctly filtered from an Interface.\"\"\"\n+ interface_source = \"\"\"\n+ interface MyInterface {\n+ [EnableIf=blue]\n+ enum MyEnum {\n+ [EnableIf=purple]\n+ VALUE1,\n+ VALUE2,\n+ };\n+ [EnableIf=blue]\n+ const int32 kMyConst = 123;\n+ [EnableIf=purple]\n+ MyMethod();\n+ };\n+ \"\"\"\n+ expected_source = \"\"\"\n+ interface MyInterface {\n+ [EnableIf=blue]\n+ enum MyEnum {\n+ VALUE2,\n+ };\n+ [EnableIf=blue]\n+ const int32 kMyConst = 123;\n+ };\n+ \"\"\"\n+ self.parseAndAssertEqual(interface_source, expected_source)\n+\n+ def testFilterMethod(self):\n+ \"\"\"Test that Parameters are correctly filtered from a Method.\"\"\"\n+ method_source = \"\"\"\n+ interface MyInterface {\n+ [EnableIf=blue]\n+ MyMethod([EnableIf=purple] int32 a) => ([EnableIf=red] int32 b);\n+ };\n+ \"\"\"\n+ expected_source = \"\"\"\n+ interface MyInterface {\n+ [EnableIf=blue]\n+ MyMethod() => ([EnableIf=red] int32 b);\n+ };\n+ \"\"\"\n+ self.parseAndAssertEqual(method_source, expected_source)\n+\n+ def testFilterStruct(self):\n+ \"\"\"Test that definitions are correctly filtered from a Struct.\"\"\"\n+ struct_source = \"\"\"\n+ struct MyStruct {\n+ [EnableIf=blue]\n+ enum MyEnum {\n+ VALUE1,\n+ [EnableIf=purple]\n+ VALUE2,\n+ };\n+ [EnableIf=yellow]\n+ const double kMyConst = 1.23;\n+ [EnableIf=green]\n+ int32 a;\n+ double b;\n+ [EnableIf=purple]\n+ int32 c;\n+ [EnableIf=blue]\n+ double d;\n+ int32 e;\n+ [EnableIf=orange]\n+ double f;\n+ };\n+ \"\"\"\n+ expected_source = \"\"\"\n+ struct MyStruct {\n+ [EnableIf=blue]\n+ enum MyEnum {\n+ VALUE1,\n+ };\n+ [EnableIf=green]\n+ int32 a;\n+ double b;\n+ [EnableIf=blue]\n+ double d;\n+ int32 e;\n+ };\n+ \"\"\"\n+ self.parseAndAssertEqual(struct_source, expected_source)\n+\n+ def testFilterUnion(self):\n+ \"\"\"Test that UnionFields are correctly filtered from a Union.\"\"\"\n+ union_source = \"\"\"\n+ union MyUnion {\n+ [EnableIf=yellow]\n+ int32 a;\n+ [EnableIf=red]\n+ bool b;\n+ };\n+ \"\"\"\n+ expected_source = \"\"\"\n+ union MyUnion {\n+ [EnableIf=red]\n+ bool b;\n+ };\n+ \"\"\"\n+ self.parseAndAssertEqual(union_source, expected_source)\n+\n+ def testSameNameFields(self):\n+ mojom_source = \"\"\"\n+ enum Foo {\n+ [EnableIf=red]\n+ VALUE1 = 5,\n+ [EnableIf=yellow]\n+ VALUE1 = 6,\n+ };\n+ [EnableIf=red]\n+ const double kMyConst = 1.23;\n+ [EnableIf=yellow]\n+ const double kMyConst = 4.56;\n+ \"\"\"\n+ expected_source = \"\"\"\n+ enum Foo {\n+ [EnableIf=red]\n+ VALUE1 = 5,\n+ };\n+ [EnableIf=red]\n+ const double kMyConst = 1.23;\n+ \"\"\"\n+ self.parseAndAssertEqual(mojom_source, expected_source)\n+\n+ def testMultipleEnableIfs(self):\n+ source = \"\"\"\n+ enum Foo {\n+ [EnableIf=red,EnableIf=yellow]\n+ kBarValue = 5,\n+ };\n+ \"\"\"\n+ definition = parser.Parse(source, \"my_file.mojom\")\n+ self.assertRaises(conditional_features.EnableIfError,\n+ conditional_features.RemoveDisabledDefinitions,\n+ definition, ENABLED_FEATURES)\n+\n+\n+if __name__ == '__main__':\n+ unittest.main()\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py\nnew file mode 100644\nindex 00000000..3e084bbf\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py\n@@ -0,0 +1,251 @@\n+# Copyright 2014 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import imp\n+import os.path\n+import sys\n+\n+from mojom import fileutil\n+from mojom.error import Error\n+\n+fileutil.AddLocalRepoThirdPartyDirToModulePath()\n+from ply.lex import TOKEN\n+\n+\n+class LexError(Error):\n+ \"\"\"Class for errors from the lexer.\"\"\"\n+\n+ def __init__(self, filename, message, lineno):\n+ Error.__init__(self, filename, message, lineno=lineno)\n+\n+\n+# We have methods which look like they could be functions:\n+# pylint: disable=R0201\n+class Lexer(object):\n+ def __init__(self, filename):\n+ self.filename = filename\n+\n+ ######################-- PRIVATE --######################\n+\n+ ##\n+ ## Internal auxiliary methods\n+ ##\n+ def _error(self, msg, token):\n+ raise LexError(self.filename, msg, token.lineno)\n+\n+ ##\n+ ## Reserved keywords\n+ ##\n+ keywords = (\n+ 'HANDLE',\n+ 'IMPORT',\n+ 'MODULE',\n+ 'STRUCT',\n+ 'UNION',\n+ 'INTERFACE',\n+ 'ENUM',\n+ 'CONST',\n+ 'TRUE',\n+ 'FALSE',\n+ 'DEFAULT',\n+ 'ARRAY',\n+ 'MAP',\n+ 'ASSOCIATED',\n+ 'PENDING_REMOTE',\n+ 'PENDING_RECEIVER',\n+ 'PENDING_ASSOCIATED_REMOTE',\n+ 'PENDING_ASSOCIATED_RECEIVER',\n+ )\n+\n+ keyword_map = {}\n+ for keyword in keywords:\n+ keyword_map[keyword.lower()] = keyword\n+\n+ ##\n+ ## All the tokens recognized by the lexer\n+ ##\n+ tokens = keywords + (\n+ # Identifiers\n+ 'NAME',\n+\n+ # Constants\n+ 'ORDINAL',\n+ 'INT_CONST_DEC',\n+ 'INT_CONST_HEX',\n+ 'FLOAT_CONST',\n+\n+ # String literals\n+ 'STRING_LITERAL',\n+\n+ # Operators\n+ 'MINUS',\n+ 'PLUS',\n+ 'AMP',\n+ 'QSTN',\n+\n+ # Assignment\n+ 'EQUALS',\n+\n+ # Request / response\n+ 'RESPONSE',\n+\n+ # Delimiters\n+ 'LPAREN',\n+ 'RPAREN', # ( )\n+ 'LBRACKET',\n+ 'RBRACKET', # [ ]\n+ 'LBRACE',\n+ 'RBRACE', # { }\n+ 'LANGLE',\n+ 'RANGLE', # < >\n+ 'SEMI', # ;\n+ 'COMMA',\n+ 'DOT' # , .\n+ )\n+\n+ ##\n+ ## Regexes for use in tokens\n+ ##\n+\n+ # valid C identifiers (K&R2: A.2.3)\n+ identifier = r'[a-zA-Z_][0-9a-zA-Z_]*'\n+\n+ hex_prefix = '0[xX]'\n+ hex_digits = '[0-9a-fA-F]+'\n+\n+ # integer constants (K&R2: A.2.5.1)\n+ decimal_constant = '0|([1-9][0-9]*)'\n+ hex_constant = hex_prefix + hex_digits\n+ # Don't allow octal constants (even invalid octal).\n+ octal_constant_disallowed = '0[0-9]+'\n+\n+ # character constants (K&R2: A.2.5.2)\n+ # Note: a-zA-Z and '.-~^_!=&;,' are allowed as escape chars to support #line\n+ # directives with Windows paths as filenames (..\\..\\dir\\file)\n+ # For the same reason, decimal_escape allows all digit sequences. We want to\n+ # parse all correct code, even if it means to sometimes parse incorrect\n+ # code.\n+ #\n+ simple_escape = r\"\"\"([a-zA-Z._~!=&\\^\\-\\\\?'\"])\"\"\"\n+ decimal_escape = r\"\"\"(\\d+)\"\"\"\n+ hex_escape = r\"\"\"(x[0-9a-fA-F]+)\"\"\"\n+ bad_escape = r\"\"\"([\\\\][^a-zA-Z._~^!=&\\^\\-\\\\?'\"x0-7])\"\"\"\n+\n+ escape_sequence = \\\n+ r\"\"\"(\\\\(\"\"\"+simple_escape+'|'+decimal_escape+'|'+hex_escape+'))'\n+\n+ # string literals (K&R2: A.2.6)\n+ string_char = r\"\"\"([^\"\\\\\\n]|\"\"\" + escape_sequence + ')'\n+ string_literal = '\"' + string_char + '*\"'\n+ bad_string_literal = '\"' + string_char + '*' + bad_escape + string_char + '*\"'\n+\n+ # floating constants (K&R2: A.2.5.3)\n+ exponent_part = r\"\"\"([eE][-+]?[0-9]+)\"\"\"\n+ fractional_constant = r\"\"\"([0-9]*\\.[0-9]+)|([0-9]+\\.)\"\"\"\n+ floating_constant = \\\n+ '(((('+fractional_constant+')'+ \\\n+ exponent_part+'?)|([0-9]+'+exponent_part+')))'\n+\n+ # Ordinals\n+ ordinal = r'@[0-9]+'\n+ missing_ordinal_value = r'@'\n+ # Don't allow ordinal values in octal (even invalid octal, like 09) or\n+ # hexadecimal.\n+ octal_or_hex_ordinal_disallowed = (\n+ r'@((0[0-9]+)|(' + hex_prefix + hex_digits + '))')\n+\n+ ##\n+ ## Rules for the normal state\n+ ##\n+ t_ignore = ' \\t\\r'\n+\n+ # Newlines\n+ def t_NEWLINE(self, t):\n+ r'\\n+'\n+ t.lexer.lineno += len(t.value)\n+\n+ # Operators\n+ t_MINUS = r'-'\n+ t_PLUS = r'\\+'\n+ t_AMP = r'&'\n+ t_QSTN = r'\\?'\n+\n+ # =\n+ t_EQUALS = r'='\n+\n+ # =>\n+ t_RESPONSE = r'=>'\n+\n+ # Delimiters\n+ t_LPAREN = r'\\('\n+ t_RPAREN = r'\\)'\n+ t_LBRACKET = r'\\['\n+ t_RBRACKET = r'\\]'\n+ t_LBRACE = r'\\{'\n+ t_RBRACE = r'\\}'\n+ t_LANGLE = r'<'\n+ t_RANGLE = r'>'\n+ t_COMMA = r','\n+ t_DOT = r'\\.'\n+ t_SEMI = r';'\n+\n+ t_STRING_LITERAL = string_literal\n+\n+ # The following floating and integer constants are defined as\n+ # functions to impose a strict order (otherwise, decimal\n+ # is placed before the others because its regex is longer,\n+ # and this is bad)\n+ #\n+ @TOKEN(floating_constant)\n+ def t_FLOAT_CONST(self, t):\n+ return t\n+\n+ @TOKEN(hex_constant)\n+ def t_INT_CONST_HEX(self, t):\n+ return t\n+\n+ @TOKEN(octal_constant_disallowed)\n+ def t_OCTAL_CONSTANT_DISALLOWED(self, t):\n+ msg = \"Octal values not allowed\"\n+ self._error(msg, t)\n+\n+ @TOKEN(decimal_constant)\n+ def t_INT_CONST_DEC(self, t):\n+ return t\n+\n+ # unmatched string literals are caught by the preprocessor\n+\n+ @TOKEN(bad_string_literal)\n+ def t_BAD_STRING_LITERAL(self, t):\n+ msg = \"String contains invalid escape code\"\n+ self._error(msg, t)\n+\n+ # Handle ordinal-related tokens in the right order:\n+ @TOKEN(octal_or_hex_ordinal_disallowed)\n+ def t_OCTAL_OR_HEX_ORDINAL_DISALLOWED(self, t):\n+ msg = \"Octal and hexadecimal ordinal values not allowed\"\n+ self._error(msg, t)\n+\n+ @TOKEN(ordinal)\n+ def t_ORDINAL(self, t):\n+ return t\n+\n+ @TOKEN(missing_ordinal_value)\n+ def t_BAD_ORDINAL(self, t):\n+ msg = \"Missing ordinal value\"\n+ self._error(msg, t)\n+\n+ @TOKEN(identifier)\n+ def t_NAME(self, t):\n+ t.type = self.keyword_map.get(t.value, \"NAME\")\n+ return t\n+\n+ # Ignore C and C++ style comments\n+ def t_COMMENT(self, t):\n+ r'(/\\*(.|\\n)*?\\*/)|(//.*(\\n[ \\t]*//.*)*)'\n+ t.lexer.lineno += t.value.count(\"\\n\")\n+\n+ def t_error(self, t):\n+ msg = \"Illegal character %s\" % repr(t.value[0])\n+ self._error(msg, t)\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py\nnew file mode 100644\nindex 00000000..eadc6587\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py\n@@ -0,0 +1,198 @@\n+# Copyright 2014 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import imp\n+import os.path\n+import sys\n+import unittest\n+\n+\n+def _GetDirAbove(dirname):\n+ \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n+ also be \"above\" this file).\"\"\"\n+ path = os.path.abspath(__file__)\n+ while True:\n+ path, tail = os.path.split(path)\n+ assert tail\n+ if tail == dirname:\n+ return path\n+\n+\n+sys.path.insert(1, os.path.join(_GetDirAbove(\"mojo\"), \"third_party\"))\n+from ply import lex\n+\n+try:\n+ imp.find_module(\"mojom\")\n+except ImportError:\n+ sys.path.append(os.path.join(_GetDirAbove(\"pylib\"), \"pylib\"))\n+import mojom.parse.lexer\n+\n+\n+# This (monkey-patching LexToken to make comparison value-based) is evil, but\n+# we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing\n+# for object identity.)\n+def _LexTokenEq(self, other):\n+ return self.type == other.type and self.value == other.value and \\\n+ self.lineno == other.lineno and self.lexpos == other.lexpos\n+\n+\n+setattr(lex.LexToken, '__eq__', _LexTokenEq)\n+\n+\n+def _MakeLexToken(token_type, value, lineno=1, lexpos=0):\n+ \"\"\"Makes a LexToken with the given parameters. (Note that lineno is 1-based,\n+ but lexpos is 0-based.)\"\"\"\n+ rv = lex.LexToken()\n+ rv.type, rv.value, rv.lineno, rv.lexpos = token_type, value, lineno, lexpos\n+ return rv\n+\n+\n+def _MakeLexTokenForKeyword(keyword, **kwargs):\n+ \"\"\"Makes a LexToken for the given keyword.\"\"\"\n+ return _MakeLexToken(keyword.upper(), keyword.lower(), **kwargs)\n+\n+\n+class LexerTest(unittest.TestCase):\n+ \"\"\"Tests |mojom.parse.lexer.Lexer|.\"\"\"\n+\n+ def __init__(self, *args, **kwargs):\n+ unittest.TestCase.__init__(self, *args, **kwargs)\n+ # Clone all lexer instances from this one, since making a lexer is slow.\n+ self._zygote_lexer = lex.lex(mojom.parse.lexer.Lexer(\"my_file.mojom\"))\n+\n+ def testValidKeywords(self):\n+ \"\"\"Tests valid keywords.\"\"\"\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"handle\"), _MakeLexTokenForKeyword(\"handle\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"import\"), _MakeLexTokenForKeyword(\"import\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"module\"), _MakeLexTokenForKeyword(\"module\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"struct\"), _MakeLexTokenForKeyword(\"struct\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"union\"), _MakeLexTokenForKeyword(\"union\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"interface\"),\n+ _MakeLexTokenForKeyword(\"interface\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"enum\"), _MakeLexTokenForKeyword(\"enum\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"const\"), _MakeLexTokenForKeyword(\"const\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"true\"), _MakeLexTokenForKeyword(\"true\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"false\"), _MakeLexTokenForKeyword(\"false\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"default\"),\n+ _MakeLexTokenForKeyword(\"default\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"array\"), _MakeLexTokenForKeyword(\"array\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"map\"), _MakeLexTokenForKeyword(\"map\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"associated\"),\n+ _MakeLexTokenForKeyword(\"associated\"))\n+\n+ def testValidIdentifiers(self):\n+ \"\"\"Tests identifiers.\"\"\"\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"abcd\"), _MakeLexToken(\"NAME\", \"abcd\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"AbC_d012_\"),\n+ _MakeLexToken(\"NAME\", \"AbC_d012_\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"_0123\"), _MakeLexToken(\"NAME\", \"_0123\"))\n+\n+ def testInvalidIdentifiers(self):\n+ with self.assertRaisesRegexp(\n+ mojom.parse.lexer.LexError,\n+ r\"^my_file\\.mojom:1: Error: Illegal character '\\$'$\"):\n+ self._TokensForInput(\"$abc\")\n+ with self.assertRaisesRegexp(\n+ mojom.parse.lexer.LexError,\n+ r\"^my_file\\.mojom:1: Error: Illegal character '\\$'$\"):\n+ self._TokensForInput(\"a$bc\")\n+\n+ def testDecimalIntegerConstants(self):\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"0\"), _MakeLexToken(\"INT_CONST_DEC\", \"0\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"1\"), _MakeLexToken(\"INT_CONST_DEC\", \"1\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"123\"), _MakeLexToken(\"INT_CONST_DEC\", \"123\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"10\"), _MakeLexToken(\"INT_CONST_DEC\", \"10\"))\n+\n+ def testValidTokens(self):\n+ \"\"\"Tests valid tokens (which aren't tested elsewhere).\"\"\"\n+ # Keywords tested in |testValidKeywords|.\n+ # NAME tested in |testValidIdentifiers|.\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"@123\"), _MakeLexToken(\"ORDINAL\", \"@123\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"456\"), _MakeLexToken(\"INT_CONST_DEC\", \"456\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"0x01aB2eF3\"),\n+ _MakeLexToken(\"INT_CONST_HEX\", \"0x01aB2eF3\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"123.456\"),\n+ _MakeLexToken(\"FLOAT_CONST\", \"123.456\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"\\\"hello\\\"\"),\n+ _MakeLexToken(\"STRING_LITERAL\", \"\\\"hello\\\"\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"+\"), _MakeLexToken(\"PLUS\", \"+\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"-\"), _MakeLexToken(\"MINUS\", \"-\"))\n+ self.assertEquals(self._SingleTokenForInput(\"&\"), _MakeLexToken(\"AMP\", \"&\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"?\"), _MakeLexToken(\"QSTN\", \"?\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"=\"), _MakeLexToken(\"EQUALS\", \"=\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"=>\"), _MakeLexToken(\"RESPONSE\", \"=>\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"(\"), _MakeLexToken(\"LPAREN\", \"(\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\")\"), _MakeLexToken(\"RPAREN\", \")\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"[\"), _MakeLexToken(\"LBRACKET\", \"[\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"]\"), _MakeLexToken(\"RBRACKET\", \"]\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"{\"), _MakeLexToken(\"LBRACE\", \"{\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"}\"), _MakeLexToken(\"RBRACE\", \"}\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\"<\"), _MakeLexToken(\"LANGLE\", \"<\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\">\"), _MakeLexToken(\"RANGLE\", \">\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\";\"), _MakeLexToken(\"SEMI\", \";\"))\n+ self.assertEquals(\n+ self._SingleTokenForInput(\",\"), _MakeLexToken(\"COMMA\", \",\"))\n+ self.assertEquals(self._SingleTokenForInput(\".\"), _MakeLexToken(\"DOT\", \".\"))\n+\n+ def _TokensForInput(self, input_string):\n+ \"\"\"Gets a list of tokens for the given input string.\"\"\"\n+ lexer = self._zygote_lexer.clone()\n+ lexer.input(input_string)\n+ rv = []\n+ while True:\n+ tok = lexer.token()\n+ if not tok:\n+ return rv\n+ rv.append(tok)\n+\n+ def _SingleTokenForInput(self, input_string):\n+ \"\"\"Gets the single token for the given input string. (Raises an exception if\n+ the input string does not result in exactly one token.)\"\"\"\n+ toks = self._TokensForInput(input_string)\n+ assert len(toks) == 1\n+ return toks[0]\n+\n+\n+if __name__ == \"__main__\":\n+ unittest.main()\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py\nnew file mode 100644\nindex 00000000..b3b803d6\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py\n@@ -0,0 +1,488 @@\n+# Copyright 2014 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\"\"\"Generates a syntax tree from a Mojo IDL file.\"\"\"\n+\n+import os.path\n+import sys\n+\n+from mojom import fileutil\n+from mojom.error import Error\n+from mojom.parse import ast\n+from mojom.parse.lexer import Lexer\n+\n+fileutil.AddLocalRepoThirdPartyDirToModulePath()\n+from ply import lex\n+from ply import yacc\n+\n+_MAX_ORDINAL_VALUE = 0xffffffff\n+_MAX_ARRAY_SIZE = 0xffffffff\n+\n+\n+class ParseError(Error):\n+ \"\"\"Class for errors from the parser.\"\"\"\n+\n+ def __init__(self, filename, message, lineno=None, snippet=None):\n+ Error.__init__(\n+ self,\n+ filename,\n+ message,\n+ lineno=lineno,\n+ addenda=([snippet] if snippet else None))\n+\n+\n+# We have methods which look like they could be functions:\n+# pylint: disable=R0201\n+class Parser(object):\n+ def __init__(self, lexer, source, filename):\n+ self.tokens = lexer.tokens\n+ self.source = source\n+ self.filename = filename\n+\n+ # Names of functions\n+ #\n+ # In general, we name functions after the left-hand-side of the rule(s) that\n+ # they handle. E.g., |p_foo_bar| for a rule |foo_bar : ...|.\n+ #\n+ # There may be multiple functions handling rules for the same left-hand-side;\n+ # then we name the functions |p_foo_bar_N| (for left-hand-side |foo_bar|),\n+ # where N is a number (numbered starting from 1). Note that using multiple\n+ # functions is actually more efficient than having single functions handle\n+ # multiple rules (and, e.g., distinguishing them by examining |len(p)|).\n+ #\n+ # It's also possible to have a function handling multiple rules with different\n+ # left-hand-sides. We do not do this.\n+ #\n+ # See http://www.dabeaz.com/ply/ply.html#ply_nn25 for more details.\n+\n+ # TODO(vtl): Get rid of the braces in the module \"statement\". (Consider\n+ # renaming \"module\" -> \"package\".) Then we'll be able to have a single rule\n+ # for root (by making module \"optional\").\n+ def p_root_1(self, p):\n+ \"\"\"root : \"\"\"\n+ p[0] = ast.Mojom(None, ast.ImportList(), [])\n+\n+ def p_root_2(self, p):\n+ \"\"\"root : root module\"\"\"\n+ if p[1].module is not None:\n+ raise ParseError(\n+ self.filename,\n+ \"Multiple \\\"module\\\" statements not allowed:\",\n+ p[2].lineno,\n+ snippet=self._GetSnippet(p[2].lineno))\n+ if p[1].import_list.items or p[1].definition_list:\n+ raise ParseError(\n+ self.filename,\n+ \"\\\"module\\\" statements must precede imports and definitions:\",\n+ p[2].lineno,\n+ snippet=self._GetSnippet(p[2].lineno))\n+ p[0] = p[1]\n+ p[0].module = p[2]\n+\n+ def p_root_3(self, p):\n+ \"\"\"root : root import\"\"\"\n+ if p[1].definition_list:\n+ raise ParseError(\n+ self.filename,\n+ \"\\\"import\\\" statements must precede definitions:\",\n+ p[2].lineno,\n+ snippet=self._GetSnippet(p[2].lineno))\n+ p[0] = p[1]\n+ p[0].import_list.Append(p[2])\n+\n+ def p_root_4(self, p):\n+ \"\"\"root : root definition\"\"\"\n+ p[0] = p[1]\n+ p[0].definition_list.append(p[2])\n+\n+ def p_import(self, p):\n+ \"\"\"import : attribute_section IMPORT STRING_LITERAL SEMI\"\"\"\n+ # 'eval' the literal to strip the quotes.\n+ # TODO(vtl): This eval is dubious. We should unquote/unescape ourselves.\n+ p[0] = ast.Import(\n+ p[1], eval(p[3]), filename=self.filename, lineno=p.lineno(2))\n+\n+ def p_module(self, p):\n+ \"\"\"module : attribute_section MODULE identifier_wrapped SEMI\"\"\"\n+ p[0] = ast.Module(p[3], p[1], filename=self.filename, lineno=p.lineno(2))\n+\n+ def p_definition(self, p):\n+ \"\"\"definition : struct\n+ | union\n+ | interface\n+ | enum\n+ | const\"\"\"\n+ p[0] = p[1]\n+\n+ def p_attribute_section_1(self, p):\n+ \"\"\"attribute_section : \"\"\"\n+ p[0] = None\n+\n+ def p_attribute_section_2(self, p):\n+ \"\"\"attribute_section : LBRACKET attribute_list RBRACKET\"\"\"\n+ p[0] = p[2]\n+\n+ def p_attribute_list_1(self, p):\n+ \"\"\"attribute_list : \"\"\"\n+ p[0] = ast.AttributeList()\n+\n+ def p_attribute_list_2(self, p):\n+ \"\"\"attribute_list : nonempty_attribute_list\"\"\"\n+ p[0] = p[1]\n+\n+ def p_nonempty_attribute_list_1(self, p):\n+ \"\"\"nonempty_attribute_list : attribute\"\"\"\n+ p[0] = ast.AttributeList(p[1])\n+\n+ def p_nonempty_attribute_list_2(self, p):\n+ \"\"\"nonempty_attribute_list : nonempty_attribute_list COMMA attribute\"\"\"\n+ p[0] = p[1]\n+ p[0].Append(p[3])\n+\n+ def p_attribute_1(self, p):\n+ \"\"\"attribute : NAME EQUALS evaled_literal\n+ | NAME EQUALS NAME\"\"\"\n+ p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))\n+\n+ def p_attribute_2(self, p):\n+ \"\"\"attribute : NAME\"\"\"\n+ p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1))\n+\n+ def p_evaled_literal(self, p):\n+ \"\"\"evaled_literal : literal\"\"\"\n+ # 'eval' the literal to strip the quotes. Handle keywords \"true\" and \"false\"\n+ # specially since they cannot directly be evaluated to python boolean\n+ # values.\n+ if p[1] == \"true\":\n+ p[0] = True\n+ elif p[1] == \"false\":\n+ p[0] = False\n+ else:\n+ p[0] = eval(p[1])\n+\n+ def p_struct_1(self, p):\n+ \"\"\"struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI\"\"\"\n+ p[0] = ast.Struct(p[3], p[1], p[5])\n+\n+ def p_struct_2(self, p):\n+ \"\"\"struct : attribute_section STRUCT NAME SEMI\"\"\"\n+ p[0] = ast.Struct(p[3], p[1], None)\n+\n+ def p_struct_body_1(self, p):\n+ \"\"\"struct_body : \"\"\"\n+ p[0] = ast.StructBody()\n+\n+ def p_struct_body_2(self, p):\n+ \"\"\"struct_body : struct_body const\n+ | struct_body enum\n+ | struct_body struct_field\"\"\"\n+ p[0] = p[1]\n+ p[0].Append(p[2])\n+\n+ def p_struct_field(self, p):\n+ \"\"\"struct_field : attribute_section typename NAME ordinal default SEMI\"\"\"\n+ p[0] = ast.StructField(p[3], p[1], p[4], p[2], p[5])\n+\n+ def p_union(self, p):\n+ \"\"\"union : attribute_section UNION NAME LBRACE union_body RBRACE SEMI\"\"\"\n+ p[0] = ast.Union(p[3], p[1], p[5])\n+\n+ def p_union_body_1(self, p):\n+ \"\"\"union_body : \"\"\"\n+ p[0] = ast.UnionBody()\n+\n+ def p_union_body_2(self, p):\n+ \"\"\"union_body : union_body union_field\"\"\"\n+ p[0] = p[1]\n+ p[1].Append(p[2])\n+\n+ def p_union_field(self, p):\n+ \"\"\"union_field : attribute_section typename NAME ordinal SEMI\"\"\"\n+ p[0] = ast.UnionField(p[3], p[1], p[4], p[2])\n+\n+ def p_default_1(self, p):\n+ \"\"\"default : \"\"\"\n+ p[0] = None\n+\n+ def p_default_2(self, p):\n+ \"\"\"default : EQUALS constant\"\"\"\n+ p[0] = p[2]\n+\n+ def p_interface(self, p):\n+ \"\"\"interface : attribute_section INTERFACE NAME LBRACE interface_body \\\n+ RBRACE SEMI\"\"\"\n+ p[0] = ast.Interface(p[3], p[1], p[5])\n+\n+ def p_interface_body_1(self, p):\n+ \"\"\"interface_body : \"\"\"\n+ p[0] = ast.InterfaceBody()\n+\n+ def p_interface_body_2(self, p):\n+ \"\"\"interface_body : interface_body const\n+ | interface_body enum\n+ | interface_body method\"\"\"\n+ p[0] = p[1]\n+ p[0].Append(p[2])\n+\n+ def p_response_1(self, p):\n+ \"\"\"response : \"\"\"\n+ p[0] = None\n+\n+ def p_response_2(self, p):\n+ \"\"\"response : RESPONSE LPAREN parameter_list RPAREN\"\"\"\n+ p[0] = p[3]\n+\n+ def p_method(self, p):\n+ \"\"\"method : attribute_section NAME ordinal LPAREN parameter_list RPAREN \\\n+ response SEMI\"\"\"\n+ p[0] = ast.Method(p[2], p[1], p[3], p[5], p[7])\n+\n+ def p_parameter_list_1(self, p):\n+ \"\"\"parameter_list : \"\"\"\n+ p[0] = ast.ParameterList()\n+\n+ def p_parameter_list_2(self, p):\n+ \"\"\"parameter_list : nonempty_parameter_list\"\"\"\n+ p[0] = p[1]\n+\n+ def p_nonempty_parameter_list_1(self, p):\n+ \"\"\"nonempty_parameter_list : parameter\"\"\"\n+ p[0] = ast.ParameterList(p[1])\n+\n+ def p_nonempty_parameter_list_2(self, p):\n+ \"\"\"nonempty_parameter_list : nonempty_parameter_list COMMA parameter\"\"\"\n+ p[0] = p[1]\n+ p[0].Append(p[3])\n+\n+ def p_parameter(self, p):\n+ \"\"\"parameter : attribute_section typename NAME ordinal\"\"\"\n+ p[0] = ast.Parameter(\n+ p[3], p[1], p[4], p[2], filename=self.filename, lineno=p.lineno(3))\n+\n+ def p_typename(self, p):\n+ \"\"\"typename : nonnullable_typename QSTN\n+ | nonnullable_typename\"\"\"\n+ if len(p) == 2:\n+ p[0] = p[1]\n+ else:\n+ p[0] = p[1] + \"?\"\n+\n+ def p_nonnullable_typename(self, p):\n+ \"\"\"nonnullable_typename : basictypename\n+ | array\n+ | fixed_array\n+ | associative_array\n+ | interfacerequest\"\"\"\n+ p[0] = p[1]\n+\n+ def p_basictypename(self, p):\n+ \"\"\"basictypename : remotetype\n+ | receivertype\n+ | associatedremotetype\n+ | associatedreceivertype\n+ | identifier\n+ | ASSOCIATED identifier\n+ | handletype\"\"\"\n+ if len(p) == 2:\n+ p[0] = p[1]\n+ else:\n+ p[0] = \"asso<\" + p[2] + \">\"\n+\n+ def p_remotetype(self, p):\n+ \"\"\"remotetype : PENDING_REMOTE LANGLE identifier RANGLE\"\"\"\n+ p[0] = \"rmt<%s>\" % p[3]\n+\n+ def p_receivertype(self, p):\n+ \"\"\"receivertype : PENDING_RECEIVER LANGLE identifier RANGLE\"\"\"\n+ p[0] = \"rcv<%s>\" % p[3]\n+\n+ def p_associatedremotetype(self, p):\n+ \"\"\"associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier \\\n+ RANGLE\"\"\"\n+ p[0] = \"rma<%s>\" % p[3]\n+\n+ def p_associatedreceivertype(self, p):\n+ \"\"\"associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier \\\n+ RANGLE\"\"\"\n+ p[0] = \"rca<%s>\" % p[3]\n+\n+ def p_handletype(self, p):\n+ \"\"\"handletype : HANDLE\n+ | HANDLE LANGLE NAME RANGLE\"\"\"\n+ if len(p) == 2:\n+ p[0] = p[1]\n+ else:\n+ if p[3] not in ('data_pipe_consumer', 'data_pipe_producer',\n+ 'message_pipe', 'shared_buffer', 'platform'):\n+ # Note: We don't enable tracking of line numbers for everything, so we\n+ # can't use |p.lineno(3)|.\n+ raise ParseError(\n+ self.filename,\n+ \"Invalid handle type %r:\" % p[3],\n+ lineno=p.lineno(1),\n+ snippet=self._GetSnippet(p.lineno(1)))\n+ p[0] = \"handle<\" + p[3] + \">\"\n+\n+ def p_array(self, p):\n+ \"\"\"array : ARRAY LANGLE typename RANGLE\"\"\"\n+ p[0] = p[3] + \"[]\"\n+\n+ def p_fixed_array(self, p):\n+ \"\"\"fixed_array : ARRAY LANGLE typename COMMA INT_CONST_DEC RANGLE\"\"\"\n+ value = int(p[5])\n+ if value == 0 or value > _MAX_ARRAY_SIZE:\n+ raise ParseError(\n+ self.filename,\n+ \"Fixed array size %d invalid:\" % value,\n+ lineno=p.lineno(5),\n+ snippet=self._GetSnippet(p.lineno(5)))\n+ p[0] = p[3] + \"[\" + p[5] + \"]\"\n+\n+ def p_associative_array(self, p):\n+ \"\"\"associative_array : MAP LANGLE identifier COMMA typename RANGLE\"\"\"\n+ p[0] = p[5] + \"{\" + p[3] + \"}\"\n+\n+ def p_interfacerequest(self, p):\n+ \"\"\"interfacerequest : identifier AMP\n+ | ASSOCIATED identifier AMP\"\"\"\n+ if len(p) == 3:\n+ p[0] = p[1] + \"&\"\n+ else:\n+ p[0] = \"asso<\" + p[2] + \"&>\"\n+\n+ def p_ordinal_1(self, p):\n+ \"\"\"ordinal : \"\"\"\n+ p[0] = None\n+\n+ def p_ordinal_2(self, p):\n+ \"\"\"ordinal : ORDINAL\"\"\"\n+ value = int(p[1][1:])\n+ if value > _MAX_ORDINAL_VALUE:\n+ raise ParseError(\n+ self.filename,\n+ \"Ordinal value %d too large:\" % value,\n+ lineno=p.lineno(1),\n+ snippet=self._GetSnippet(p.lineno(1)))\n+ p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1))\n+\n+ def p_enum_1(self, p):\n+ \"\"\"enum : attribute_section ENUM NAME LBRACE enum_value_list \\\n+ RBRACE SEMI\n+ | attribute_section ENUM NAME LBRACE nonempty_enum_value_list \\\n+ COMMA RBRACE SEMI\"\"\"\n+ p[0] = ast.Enum(\n+ p[3], p[1], p[5], filename=self.filename, lineno=p.lineno(2))\n+\n+ def p_enum_2(self, p):\n+ \"\"\"enum : attribute_section ENUM NAME SEMI\"\"\"\n+ p[0] = ast.Enum(\n+ p[3], p[1], None, filename=self.filename, lineno=p.lineno(2))\n+\n+ def p_enum_value_list_1(self, p):\n+ \"\"\"enum_value_list : \"\"\"\n+ p[0] = ast.EnumValueList()\n+\n+ def p_enum_value_list_2(self, p):\n+ \"\"\"enum_value_list : nonempty_enum_value_list\"\"\"\n+ p[0] = p[1]\n+\n+ def p_nonempty_enum_value_list_1(self, p):\n+ \"\"\"nonempty_enum_value_list : enum_value\"\"\"\n+ p[0] = ast.EnumValueList(p[1])\n+\n+ def p_nonempty_enum_value_list_2(self, p):\n+ \"\"\"nonempty_enum_value_list : nonempty_enum_value_list COMMA enum_value\"\"\"\n+ p[0] = p[1]\n+ p[0].Append(p[3])\n+\n+ def p_enum_value(self, p):\n+ \"\"\"enum_value : attribute_section NAME\n+ | attribute_section NAME EQUALS int\n+ | attribute_section NAME EQUALS identifier_wrapped\"\"\"\n+ p[0] = ast.EnumValue(\n+ p[2],\n+ p[1],\n+ p[4] if len(p) == 5 else None,\n+ filename=self.filename,\n+ lineno=p.lineno(2))\n+\n+ def p_const(self, p):\n+ \"\"\"const : attribute_section CONST typename NAME EQUALS constant SEMI\"\"\"\n+ p[0] = ast.Const(p[4], p[1], p[3], p[6])\n+\n+ def p_constant(self, p):\n+ \"\"\"constant : literal\n+ | identifier_wrapped\"\"\"\n+ p[0] = p[1]\n+\n+ def p_identifier_wrapped(self, p):\n+ \"\"\"identifier_wrapped : identifier\"\"\"\n+ p[0] = ('IDENTIFIER', p[1])\n+\n+ # TODO(vtl): Make this produce a \"wrapped\" identifier (probably as an\n+ # |ast.Identifier|, to be added) and get rid of identifier_wrapped.\n+ def p_identifier(self, p):\n+ \"\"\"identifier : NAME\n+ | NAME DOT identifier\"\"\"\n+ p[0] = ''.join(p[1:])\n+\n+ def p_literal(self, p):\n+ \"\"\"literal : int\n+ | float\n+ | TRUE\n+ | FALSE\n+ | DEFAULT\n+ | STRING_LITERAL\"\"\"\n+ p[0] = p[1]\n+\n+ def p_int(self, p):\n+ \"\"\"int : int_const\n+ | PLUS int_const\n+ | MINUS int_const\"\"\"\n+ p[0] = ''.join(p[1:])\n+\n+ def p_int_const(self, p):\n+ \"\"\"int_const : INT_CONST_DEC\n+ | INT_CONST_HEX\"\"\"\n+ p[0] = p[1]\n+\n+ def p_float(self, p):\n+ \"\"\"float : FLOAT_CONST\n+ | PLUS FLOAT_CONST\n+ | MINUS FLOAT_CONST\"\"\"\n+ p[0] = ''.join(p[1:])\n+\n+ def p_error(self, e):\n+ if e is None:\n+ # Unexpected EOF.\n+ # TODO(vtl): Can we figure out what's missing?\n+ raise ParseError(self.filename, \"Unexpected end of file\")\n+\n+ raise ParseError(\n+ self.filename,\n+ \"Unexpected %r:\" % e.value,\n+ lineno=e.lineno,\n+ snippet=self._GetSnippet(e.lineno))\n+\n+ def _GetSnippet(self, lineno):\n+ return self.source.split('\\n')[lineno - 1]\n+\n+\n+def Parse(source, filename):\n+ \"\"\"Parse source file to AST.\n+\n+ Args:\n+ source: The source text as a str (Python 2 or 3) or unicode (Python 2).\n+ filename: The filename that |source| originates from.\n+\n+ Returns:\n+ The AST as a mojom.parse.ast.Mojom object.\n+ \"\"\"\n+ lexer = Lexer(filename)\n+ parser = Parser(lexer, source, filename)\n+\n+ lex.lex(object=lexer)\n+ yacc.yacc(module=parser, debug=0, write_tables=0)\n+\n+ tree = yacc.parse(source)\n+ return tree\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py\nnew file mode 100644\nindex 00000000..6d6b7153\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py\n@@ -0,0 +1,1390 @@\n+# Copyright 2014 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import imp\n+import os.path\n+import sys\n+import unittest\n+\n+from mojom.parse import ast\n+from mojom.parse import lexer\n+from mojom.parse import parser\n+\n+\n+class ParserTest(unittest.TestCase):\n+ \"\"\"Tests |parser.Parse()|.\"\"\"\n+\n+ def testTrivialValidSource(self):\n+ \"\"\"Tests a trivial, but valid, .mojom source.\"\"\"\n+\n+ source = \"\"\"\\\n+ // This is a comment.\n+\n+ module my_module;\n+ \"\"\"\n+ expected = ast.Mojom(\n+ ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [])\n+ self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n+\n+ def testSourceWithCrLfs(self):\n+ \"\"\"Tests a .mojom source with CR-LFs instead of LFs.\"\"\"\n+\n+ source = \"// This is a comment.\\r\\n\\r\\nmodule my_module;\\r\\n\"\n+ expected = ast.Mojom(\n+ ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [])\n+ self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n+\n+ def testUnexpectedEOF(self):\n+ \"\"\"Tests a \"truncated\" .mojom source.\"\"\"\n+\n+ source = \"\"\"\\\n+ // This is a comment.\n+\n+ module my_module\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom: Error: Unexpected end of file$\"):\n+ parser.Parse(source, \"my_file.mojom\")\n+\n+ def testCommentLineNumbers(self):\n+ \"\"\"Tests that line numbers are correctly tracked when comments are\n+ present.\"\"\"\n+\n+ source1 = \"\"\"\\\n+ // Isolated C++-style comments.\n+\n+ // Foo.\n+ asdf1\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError,\n+ r\"^my_file\\.mojom:4: Error: Unexpected 'asdf1':\\n *asdf1$\"):\n+ parser.Parse(source1, \"my_file.mojom\")\n+\n+ source2 = \"\"\"\\\n+ // Consecutive C++-style comments.\n+ // Foo.\n+ // Bar.\n+\n+ struct Yada { // Baz.\n+ // Quux.\n+ int32 x;\n+ };\n+\n+ asdf2\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError,\n+ r\"^my_file\\.mojom:10: Error: Unexpected 'asdf2':\\n *asdf2$\"):\n+ parser.Parse(source2, \"my_file.mojom\")\n+\n+ source3 = \"\"\"\\\n+ /* Single-line C-style comments. */\n+ /* Foobar. */\n+\n+ /* Baz. */\n+ asdf3\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError,\n+ r\"^my_file\\.mojom:5: Error: Unexpected 'asdf3':\\n *asdf3$\"):\n+ parser.Parse(source3, \"my_file.mojom\")\n+\n+ source4 = \"\"\"\\\n+ /* Multi-line C-style comments.\n+ */\n+ /*\n+ Foo.\n+ Bar.\n+ */\n+\n+ /* Baz\n+ Quux. */\n+ asdf4\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError,\n+ r\"^my_file\\.mojom:10: Error: Unexpected 'asdf4':\\n *asdf4$\"):\n+ parser.Parse(source4, \"my_file.mojom\")\n+\n+ def testSimpleStruct(self):\n+ \"\"\"Tests a simple .mojom source that just defines a struct.\"\"\"\n+\n+ source = \"\"\"\\\n+ module my_module;\n+\n+ struct MyStruct {\n+ int32 a;\n+ double b;\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(\n+ ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n+ ast.Struct(\n+ 'MyStruct', None,\n+ ast.StructBody([\n+ ast.StructField('a', None, None, 'int32', None),\n+ ast.StructField('b', None, None, 'double', None)\n+ ]))\n+ ])\n+ self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n+\n+ def testSimpleStructWithoutModule(self):\n+ \"\"\"Tests a simple struct without an explict module statement.\"\"\"\n+\n+ source = \"\"\"\\\n+ struct MyStruct {\n+ int32 a;\n+ double b;\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(None, ast.ImportList(), [\n+ ast.Struct(\n+ 'MyStruct', None,\n+ ast.StructBody([\n+ ast.StructField('a', None, None, 'int32', None),\n+ ast.StructField('b', None, None, 'double', None)\n+ ]))\n+ ])\n+ self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n+\n+ def testValidStructDefinitions(self):\n+ \"\"\"Tests all types of definitions that can occur in a struct.\"\"\"\n+\n+ source = \"\"\"\\\n+ struct MyStruct {\n+ enum MyEnum { VALUE };\n+ const double kMyConst = 1.23;\n+ int32 a;\n+ SomeOtherStruct b; // Invalidity detected at another stage.\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(None, ast.ImportList(), [\n+ ast.Struct(\n+ 'MyStruct', None,\n+ ast.StructBody([\n+ ast.Enum('MyEnum', None,\n+ ast.EnumValueList(ast.EnumValue('VALUE', None, None))),\n+ ast.Const('kMyConst', None, 'double', '1.23'),\n+ ast.StructField('a', None, None, 'int32', None),\n+ ast.StructField('b', None, None, 'SomeOtherStruct', None)\n+ ]))\n+ ])\n+ self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n+\n+ def testInvalidStructDefinitions(self):\n+ \"\"\"Tests that definitions that aren't allowed in a struct are correctly\n+ detected.\"\"\"\n+\n+ source1 = \"\"\"\\\n+ struct MyStruct {\n+ MyMethod(int32 a);\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected '\\(':\\n\"\n+ r\" *MyMethod\\(int32 a\\);$\"):\n+ parser.Parse(source1, \"my_file.mojom\")\n+\n+ source2 = \"\"\"\\\n+ struct MyStruct {\n+ struct MyInnerStruct {\n+ int32 a;\n+ };\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'struct':\\n\"\n+ r\" *struct MyInnerStruct {$\"):\n+ parser.Parse(source2, \"my_file.mojom\")\n+\n+ source3 = \"\"\"\\\n+ struct MyStruct {\n+ interface MyInterface {\n+ MyMethod(int32 a);\n+ };\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError,\n+ r\"^my_file\\.mojom:2: Error: Unexpected 'interface':\\n\"\n+ r\" *interface MyInterface {$\"):\n+ parser.Parse(source3, \"my_file.mojom\")\n+\n+ def testMissingModuleName(self):\n+ \"\"\"Tests an (invalid) .mojom with a missing module name.\"\"\"\n+\n+ source1 = \"\"\"\\\n+ // Missing module name.\n+ module ;\n+ struct MyStruct {\n+ int32 a;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError,\n+ r\"^my_file\\.mojom:2: Error: Unexpected ';':\\n *module ;$\"):\n+ parser.Parse(source1, \"my_file.mojom\")\n+\n+ # Another similar case, but make sure that line-number tracking/reporting\n+ # is correct.\n+ source2 = \"\"\"\\\n+ module\n+ // This line intentionally left unblank.\n+\n+ struct MyStruct {\n+ int32 a;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:4: Error: Unexpected 'struct':\\n\"\n+ r\" *struct MyStruct {$\"):\n+ parser.Parse(source2, \"my_file.mojom\")\n+\n+ def testMultipleModuleStatements(self):\n+ \"\"\"Tests an (invalid) .mojom with multiple module statements.\"\"\"\n+\n+ source = \"\"\"\\\n+ module foo;\n+ module bar;\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError,\n+ r\"^my_file\\.mojom:2: Error: Multiple \\\"module\\\" statements not \"\n+ r\"allowed:\\n *module bar;$\"):\n+ parser.Parse(source, \"my_file.mojom\")\n+\n+ def testModuleStatementAfterImport(self):\n+ \"\"\"Tests an (invalid) .mojom with a module statement after an import.\"\"\"\n+\n+ source = \"\"\"\\\n+ import \"foo.mojom\";\n+ module foo;\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError,\n+ r\"^my_file\\.mojom:2: Error: \\\"module\\\" statements must precede imports \"\n+ r\"and definitions:\\n *module foo;$\"):\n+ parser.Parse(source, \"my_file.mojom\")\n+\n+ def testModuleStatementAfterDefinition(self):\n+ \"\"\"Tests an (invalid) .mojom with a module statement after a definition.\"\"\"\n+\n+ source = \"\"\"\\\n+ struct MyStruct {\n+ int32 a;\n+ };\n+ module foo;\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError,\n+ r\"^my_file\\.mojom:4: Error: \\\"module\\\" statements must precede imports \"\n+ r\"and definitions:\\n *module foo;$\"):\n+ parser.Parse(source, \"my_file.mojom\")\n+\n+ def testImportStatementAfterDefinition(self):\n+ \"\"\"Tests an (invalid) .mojom with an import statement after a definition.\"\"\"\n+\n+ source = \"\"\"\\\n+ struct MyStruct {\n+ int32 a;\n+ };\n+ import \"foo.mojom\";\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError,\n+ r\"^my_file\\.mojom:4: Error: \\\"import\\\" statements must precede \"\n+ r\"definitions:\\n *import \\\"foo.mojom\\\";$\"):\n+ parser.Parse(source, \"my_file.mojom\")\n+\n+ def testEnums(self):\n+ \"\"\"Tests that enum statements are correctly parsed.\"\"\"\n+\n+ source = \"\"\"\\\n+ module my_module;\n+ enum MyEnum1 { VALUE1, VALUE2 }; // No trailing comma.\n+ enum MyEnum2 {\n+ VALUE1 = -1,\n+ VALUE2 = 0,\n+ VALUE3 = + 987, // Check that space is allowed.\n+ VALUE4 = 0xAF12,\n+ VALUE5 = -0x09bcd,\n+ VALUE6 = VALUE5,\n+ VALUE7, // Leave trailing comma.\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(\n+ ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n+ ast.Enum(\n+ 'MyEnum1', None,\n+ ast.EnumValueList([\n+ ast.EnumValue('VALUE1', None, None),\n+ ast.EnumValue('VALUE2', None, None)\n+ ])),\n+ ast.Enum(\n+ 'MyEnum2', None,\n+ ast.EnumValueList([\n+ ast.EnumValue('VALUE1', None, '-1'),\n+ ast.EnumValue('VALUE2', None, '0'),\n+ ast.EnumValue('VALUE3', None, '+987'),\n+ ast.EnumValue('VALUE4', None, '0xAF12'),\n+ ast.EnumValue('VALUE5', None, '-0x09bcd'),\n+ ast.EnumValue('VALUE6', None, ('IDENTIFIER', 'VALUE5')),\n+ ast.EnumValue('VALUE7', None, None)\n+ ]))\n+ ])\n+ self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n+\n+ def testInvalidEnumInitializers(self):\n+ \"\"\"Tests that invalid enum initializers are correctly detected.\"\"\"\n+\n+ # Floating point value.\n+ source2 = \"enum MyEnum { VALUE = 0.123 };\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:1: Error: Unexpected '0\\.123':\\n\"\n+ r\"enum MyEnum { VALUE = 0\\.123 };$\"):\n+ parser.Parse(source2, \"my_file.mojom\")\n+\n+ # Boolean value.\n+ source2 = \"enum MyEnum { VALUE = true };\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:1: Error: Unexpected 'true':\\n\"\n+ r\"enum MyEnum { VALUE = true };$\"):\n+ parser.Parse(source2, \"my_file.mojom\")\n+\n+ def testConsts(self):\n+ \"\"\"Tests some constants and struct members initialized with them.\"\"\"\n+\n+ source = \"\"\"\\\n+ module my_module;\n+\n+ struct MyStruct {\n+ const int8 kNumber = -1;\n+ int8 number@0 = kNumber;\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(\n+ ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n+ ast.Struct(\n+ 'MyStruct', None,\n+ ast.StructBody([\n+ ast.Const('kNumber', None, 'int8', '-1'),\n+ ast.StructField('number', None, ast.Ordinal(0), 'int8',\n+ ('IDENTIFIER', 'kNumber'))\n+ ]))\n+ ])\n+ self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n+\n+ def testNoConditionals(self):\n+ \"\"\"Tests that ?: is not allowed.\"\"\"\n+\n+ source = \"\"\"\\\n+ module my_module;\n+\n+ enum MyEnum {\n+ MY_ENUM_1 = 1 ? 2 : 3\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:4: Error: Unexpected '\\?':\\n\"\n+ r\" *MY_ENUM_1 = 1 \\? 2 : 3$\"):\n+ parser.Parse(source, \"my_file.mojom\")\n+\n+ def testSimpleOrdinals(self):\n+ \"\"\"Tests that (valid) ordinal values are scanned correctly.\"\"\"\n+\n+ source = \"\"\"\\\n+ module my_module;\n+\n+ // This isn't actually valid .mojom, but the problem (missing ordinals)\n+ // should be handled at a different level.\n+ struct MyStruct {\n+ int32 a0@0;\n+ int32 a1@1;\n+ int32 a2@2;\n+ int32 a9@9;\n+ int32 a10 @10;\n+ int32 a11 @11;\n+ int32 a29 @29;\n+ int32 a1234567890 @1234567890;\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(\n+ ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n+ ast.Struct(\n+ 'MyStruct', None,\n+ ast.StructBody([\n+ ast.StructField('a0', None, ast.Ordinal(0), 'int32', None),\n+ ast.StructField('a1', None, ast.Ordinal(1), 'int32', None),\n+ ast.StructField('a2', None, ast.Ordinal(2), 'int32', None),\n+ ast.StructField('a9', None, ast.Ordinal(9), 'int32', None),\n+ ast.StructField('a10', None, ast.Ordinal(10), 'int32',\n+ None),\n+ ast.StructField('a11', None, ast.Ordinal(11), 'int32',\n+ None),\n+ ast.StructField('a29', None, ast.Ordinal(29), 'int32',\n+ None),\n+ ast.StructField('a1234567890', None,\n+ ast.Ordinal(1234567890), 'int32', None)\n+ ]))\n+ ])\n+ self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n+\n+ def testInvalidOrdinals(self):\n+ \"\"\"Tests that (lexically) invalid ordinals are correctly detected.\"\"\"\n+\n+ source1 = \"\"\"\\\n+ module my_module;\n+\n+ struct MyStruct {\n+ int32 a_missing@;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ lexer.LexError, r\"^my_file\\.mojom:4: Error: Missing ordinal value$\"):\n+ parser.Parse(source1, \"my_file.mojom\")\n+\n+ source2 = \"\"\"\\\n+ module my_module;\n+\n+ struct MyStruct {\n+ int32 a_octal@01;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ lexer.LexError, r\"^my_file\\.mojom:4: Error: \"\n+ r\"Octal and hexadecimal ordinal values not allowed$\"):\n+ parser.Parse(source2, \"my_file.mojom\")\n+\n+ source3 = \"\"\"\\\n+ module my_module; struct MyStruct { int32 a_invalid_octal@08; };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ lexer.LexError, r\"^my_file\\.mojom:1: Error: \"\n+ r\"Octal and hexadecimal ordinal values not allowed$\"):\n+ parser.Parse(source3, \"my_file.mojom\")\n+\n+ source4 = \"module my_module; struct MyStruct { int32 a_hex@0x1aB9; };\"\n+ with self.assertRaisesRegexp(\n+ lexer.LexError, r\"^my_file\\.mojom:1: Error: \"\n+ r\"Octal and hexadecimal ordinal values not allowed$\"):\n+ parser.Parse(source4, \"my_file.mojom\")\n+\n+ source5 = \"module my_module; struct MyStruct { int32 a_hex@0X0; };\"\n+ with self.assertRaisesRegexp(\n+ lexer.LexError, r\"^my_file\\.mojom:1: Error: \"\n+ r\"Octal and hexadecimal ordinal values not allowed$\"):\n+ parser.Parse(source5, \"my_file.mojom\")\n+\n+ source6 = \"\"\"\\\n+ struct MyStruct {\n+ int32 a_too_big@999999999999;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: \"\n+ r\"Ordinal value 999999999999 too large:\\n\"\n+ r\" *int32 a_too_big@999999999999;$\"):\n+ parser.Parse(source6, \"my_file.mojom\")\n+\n+ def testNestedNamespace(self):\n+ \"\"\"Tests that \"nested\" namespaces work.\"\"\"\n+\n+ source = \"\"\"\\\n+ module my.mod;\n+\n+ struct MyStruct {\n+ int32 a;\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(\n+ ast.Module(('IDENTIFIER', 'my.mod'), None), ast.ImportList(), [\n+ ast.Struct(\n+ 'MyStruct', None,\n+ ast.StructBody(ast.StructField('a', None, None, 'int32', None)))\n+ ])\n+ self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n+\n+ def testValidHandleTypes(self):\n+ \"\"\"Tests (valid) handle types.\"\"\"\n+\n+ source = \"\"\"\\\n+ struct MyStruct {\n+ handle a;\n+ handle<data_pipe_consumer> b;\n+ handle <data_pipe_producer> c;\n+ handle < message_pipe > d;\n+ handle\n+ < shared_buffer\n+ > e;\n+ handle\n+ <platform\n+\n+ > f;\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(None, ast.ImportList(), [\n+ ast.Struct(\n+ 'MyStruct', None,\n+ ast.StructBody([\n+ ast.StructField('a', None, None, 'handle', None),\n+ ast.StructField('b', None, None, 'handle<data_pipe_consumer>',\n+ None),\n+ ast.StructField('c', None, None, 'handle<data_pipe_producer>',\n+ None),\n+ ast.StructField('d', None, None, 'handle<message_pipe>', None),\n+ ast.StructField('e', None, None, 'handle<shared_buffer>', None),\n+ ast.StructField('f', None, None, 'handle<platform>', None)\n+ ]))\n+ ])\n+ self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n+\n+ def testInvalidHandleType(self):\n+ \"\"\"Tests an invalid (unknown) handle type.\"\"\"\n+\n+ source = \"\"\"\\\n+ struct MyStruct {\n+ handle<wtf_is_this> foo;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: \"\n+ r\"Invalid handle type 'wtf_is_this':\\n\"\n+ r\" *handle<wtf_is_this> foo;$\"):\n+ parser.Parse(source, \"my_file.mojom\")\n+\n+ def testValidDefaultValues(self):\n+ \"\"\"Tests default values that are valid (to the parser).\"\"\"\n+\n+ source = \"\"\"\\\n+ struct MyStruct {\n+ int16 a0 = 0;\n+ uint16 a1 = 0x0;\n+ uint16 a2 = 0x00;\n+ uint16 a3 = 0x01;\n+ uint16 a4 = 0xcd;\n+ int32 a5 = 12345;\n+ int64 a6 = -12345;\n+ int64 a7 = +12345;\n+ uint32 a8 = 0x12cd3;\n+ uint32 a9 = -0x12cD3;\n+ uint32 a10 = +0x12CD3;\n+ bool a11 = true;\n+ bool a12 = false;\n+ float a13 = 1.2345;\n+ float a14 = -1.2345;\n+ float a15 = +1.2345;\n+ float a16 = 123.;\n+ float a17 = .123;\n+ double a18 = 1.23E10;\n+ double a19 = 1.E-10;\n+ double a20 = .5E+10;\n+ double a21 = -1.23E10;\n+ double a22 = +.123E10;\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(None, ast.ImportList(), [\n+ ast.Struct(\n+ 'MyStruct', None,\n+ ast.StructBody([\n+ ast.StructField('a0', None, None, 'int16', '0'),\n+ ast.StructField('a1', None, None, 'uint16', '0x0'),\n+ ast.StructField('a2', None, None, 'uint16', '0x00'),\n+ ast.StructField('a3', None, None, 'uint16', '0x01'),\n+ ast.StructField('a4', None, None, 'uint16', '0xcd'),\n+ ast.StructField('a5', None, None, 'int32', '12345'),\n+ ast.StructField('a6', None, None, 'int64', '-12345'),\n+ ast.StructField('a7', None, None, 'int64', '+12345'),\n+ ast.StructField('a8', None, None, 'uint32', '0x12cd3'),\n+ ast.StructField('a9', None, None, 'uint32', '-0x12cD3'),\n+ ast.StructField('a10', None, None, 'uint32', '+0x12CD3'),\n+ ast.StructField('a11', None, None, 'bool', 'true'),\n+ ast.StructField('a12', None, None, 'bool', 'false'),\n+ ast.StructField('a13', None, None, 'float', '1.2345'),\n+ ast.StructField('a14', None, None, 'float', '-1.2345'),\n+ ast.StructField('a15', None, None, 'float', '+1.2345'),\n+ ast.StructField('a16', None, None, 'float', '123.'),\n+ ast.StructField('a17', None, None, 'float', '.123'),\n+ ast.StructField('a18', None, None, 'double', '1.23E10'),\n+ ast.StructField('a19', None, None, 'double', '1.E-10'),\n+ ast.StructField('a20', None, None, 'double', '.5E+10'),\n+ ast.StructField('a21', None, None, 'double', '-1.23E10'),\n+ ast.StructField('a22', None, None, 'double', '+.123E10')\n+ ]))\n+ ])\n+ self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n+\n+ def testValidFixedSizeArray(self):\n+ \"\"\"Tests parsing a fixed size array.\"\"\"\n+\n+ source = \"\"\"\\\n+ struct MyStruct {\n+ array<int32> normal_array;\n+ array<int32, 1> fixed_size_array_one_entry;\n+ array<int32, 10> fixed_size_array_ten_entries;\n+ array<array<array<int32, 1>>, 2> nested_arrays;\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(None, ast.ImportList(), [\n+ ast.Struct(\n+ 'MyStruct', None,\n+ ast.StructBody([\n+ ast.StructField('normal_array', None, None, 'int32[]', None),\n+ ast.StructField('fixed_size_array_one_entry', None, None,\n+ 'int32[1]', None),\n+ ast.StructField('fixed_size_array_ten_entries', None, None,\n+ 'int32[10]', None),\n+ ast.StructField('nested_arrays', None, None, 'int32[1][][2]',\n+ None)\n+ ]))\n+ ])\n+ self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n+\n+ def testValidNestedArray(self):\n+ \"\"\"Tests parsing a nested array.\"\"\"\n+\n+ source = \"struct MyStruct { array<array<int32>> nested_array; };\"\n+ expected = ast.Mojom(None, ast.ImportList(), [\n+ ast.Struct(\n+ 'MyStruct', None,\n+ ast.StructBody(\n+ ast.StructField('nested_array', None, None, 'int32[][]', None)))\n+ ])\n+ self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n+\n+ def testInvalidFixedArraySize(self):\n+ \"\"\"Tests that invalid fixed array bounds are correctly detected.\"\"\"\n+\n+ source1 = \"\"\"\\\n+ struct MyStruct {\n+ array<int32, 0> zero_size_array;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError,\n+ r\"^my_file\\.mojom:2: Error: Fixed array size 0 invalid:\\n\"\n+ r\" *array<int32, 0> zero_size_array;$\"):\n+ parser.Parse(source1, \"my_file.mojom\")\n+\n+ source2 = \"\"\"\\\n+ struct MyStruct {\n+ array<int32, 999999999999> too_big_array;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError,\n+ r\"^my_file\\.mojom:2: Error: Fixed array size 999999999999 invalid:\\n\"\n+ r\" *array<int32, 999999999999> too_big_array;$\"):\n+ parser.Parse(source2, \"my_file.mojom\")\n+\n+ source3 = \"\"\"\\\n+ struct MyStruct {\n+ array<int32, abcdefg> not_a_number;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'abcdefg':\\n\"\n+ r\" *array<int32, abcdefg> not_a_number;\"):\n+ parser.Parse(source3, \"my_file.mojom\")\n+\n+ def testValidAssociativeArrays(self):\n+ \"\"\"Tests that we can parse valid associative array structures.\"\"\"\n+\n+ source1 = \"struct MyStruct { map<string, uint8> data; };\"\n+ expected1 = ast.Mojom(None, ast.ImportList(), [\n+ ast.Struct(\n+ 'MyStruct', None,\n+ ast.StructBody(\n+ [ast.StructField('data', None, None, 'uint8{string}', None)]))\n+ ])\n+ self.assertEquals(parser.Parse(source1, \"my_file.mojom\"), expected1)\n+\n+ source2 = \"interface MyInterface { MyMethod(map<string, uint8> a); };\"\n+ expected2 = ast.Mojom(None, ast.ImportList(), [\n+ ast.Interface(\n+ 'MyInterface', None,\n+ ast.InterfaceBody(\n+ ast.Method(\n+ 'MyMethod', None, None,\n+ ast.ParameterList(\n+ ast.Parameter('a', None, None, 'uint8{string}')),\n+ None)))\n+ ])\n+ self.assertEquals(parser.Parse(source2, \"my_file.mojom\"), expected2)\n+\n+ source3 = \"struct MyStruct { map<string, array<uint8>> data; };\"\n+ expected3 = ast.Mojom(None, ast.ImportList(), [\n+ ast.Struct(\n+ 'MyStruct', None,\n+ ast.StructBody(\n+ [ast.StructField('data', None, None, 'uint8[]{string}', None)]))\n+ ])\n+ self.assertEquals(parser.Parse(source3, \"my_file.mojom\"), expected3)\n+\n+ def testValidMethod(self):\n+ \"\"\"Tests parsing method declarations.\"\"\"\n+\n+ source1 = \"interface MyInterface { MyMethod(int32 a); };\"\n+ expected1 = ast.Mojom(None, ast.ImportList(), [\n+ ast.Interface(\n+ 'MyInterface', None,\n+ ast.InterfaceBody(\n+ ast.Method(\n+ 'MyMethod', None, None,\n+ ast.ParameterList(ast.Parameter('a', None, None, 'int32')),\n+ None)))\n+ ])\n+ self.assertEquals(parser.Parse(source1, \"my_file.mojom\"), expected1)\n+\n+ source2 = \"\"\"\\\n+ interface MyInterface {\n+ MyMethod1@0(int32 a@0, int64 b@1);\n+ MyMethod2@1() => ();\n+ };\n+ \"\"\"\n+ expected2 = ast.Mojom(None, ast.ImportList(), [\n+ ast.Interface(\n+ 'MyInterface', None,\n+ ast.InterfaceBody([\n+ ast.Method(\n+ 'MyMethod1', None, ast.Ordinal(0),\n+ ast.ParameterList([\n+ ast.Parameter('a', None, ast.Ordinal(0), 'int32'),\n+ ast.Parameter('b', None, ast.Ordinal(1), 'int64')\n+ ]), None),\n+ ast.Method('MyMethod2', None, ast.Ordinal(1),\n+ ast.ParameterList(), ast.ParameterList())\n+ ]))\n+ ])\n+ self.assertEquals(parser.Parse(source2, \"my_file.mojom\"), expected2)\n+\n+ source3 = \"\"\"\\\n+ interface MyInterface {\n+ MyMethod(string a) => (int32 a, bool b);\n+ };\n+ \"\"\"\n+ expected3 = ast.Mojom(None, ast.ImportList(), [\n+ ast.Interface(\n+ 'MyInterface', None,\n+ ast.InterfaceBody(\n+ ast.Method(\n+ 'MyMethod', None, None,\n+ ast.ParameterList(ast.Parameter('a', None, None, 'string')),\n+ ast.ParameterList([\n+ ast.Parameter('a', None, None, 'int32'),\n+ ast.Parameter('b', None, None, 'bool')\n+ ]))))\n+ ])\n+ self.assertEquals(parser.Parse(source3, \"my_file.mojom\"), expected3)\n+\n+ def testInvalidMethods(self):\n+ \"\"\"Tests that invalid method declarations are correctly detected.\"\"\"\n+\n+ # No trailing commas.\n+ source1 = \"\"\"\\\n+ interface MyInterface {\n+ MyMethod(string a,);\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected '\\)':\\n\"\n+ r\" *MyMethod\\(string a,\\);$\"):\n+ parser.Parse(source1, \"my_file.mojom\")\n+\n+ # No leading commas.\n+ source2 = \"\"\"\\\n+ interface MyInterface {\n+ MyMethod(, string a);\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected ',':\\n\"\n+ r\" *MyMethod\\(, string a\\);$\"):\n+ parser.Parse(source2, \"my_file.mojom\")\n+\n+ def testValidInterfaceDefinitions(self):\n+ \"\"\"Tests all types of definitions that can occur in an interface.\"\"\"\n+\n+ source = \"\"\"\\\n+ interface MyInterface {\n+ enum MyEnum { VALUE };\n+ const int32 kMyConst = 123;\n+ MyMethod(int32 x) => (MyEnum y);\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(None, ast.ImportList(), [\n+ ast.Interface(\n+ 'MyInterface', None,\n+ ast.InterfaceBody([\n+ ast.Enum('MyEnum', None,\n+ ast.EnumValueList(ast.EnumValue('VALUE', None, None))),\n+ ast.Const('kMyConst', None, 'int32', '123'),\n+ ast.Method(\n+ 'MyMethod', None, None,\n+ ast.ParameterList(ast.Parameter('x', None, None, 'int32')),\n+ ast.ParameterList(ast.Parameter('y', None, None, 'MyEnum')))\n+ ]))\n+ ])\n+ self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n+\n+ def testInvalidInterfaceDefinitions(self):\n+ \"\"\"Tests that definitions that aren't allowed in an interface are correctly\n+ detected.\"\"\"\n+\n+ source1 = \"\"\"\\\n+ interface MyInterface {\n+ struct MyStruct {\n+ int32 a;\n+ };\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'struct':\\n\"\n+ r\" *struct MyStruct {$\"):\n+ parser.Parse(source1, \"my_file.mojom\")\n+\n+ source2 = \"\"\"\\\n+ interface MyInterface {\n+ interface MyInnerInterface {\n+ MyMethod(int32 x);\n+ };\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError,\n+ r\"^my_file\\.mojom:2: Error: Unexpected 'interface':\\n\"\n+ r\" *interface MyInnerInterface {$\"):\n+ parser.Parse(source2, \"my_file.mojom\")\n+\n+ source3 = \"\"\"\\\n+ interface MyInterface {\n+ int32 my_field;\n+ };\n+ \"\"\"\n+ # The parser thinks that \"int32\" is a plausible name for a method, so it's\n+ # \"my_field\" that gives it away.\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'my_field':\\n\"\n+ r\" *int32 my_field;$\"):\n+ parser.Parse(source3, \"my_file.mojom\")\n+\n+ def testValidAttributes(self):\n+ \"\"\"Tests parsing attributes (and attribute lists).\"\"\"\n+\n+ # Note: We use structs because they have (optional) attribute lists.\n+\n+ # Empty attribute list.\n+ source1 = \"[] struct MyStruct {};\"\n+ expected1 = ast.Mojom(\n+ None, ast.ImportList(),\n+ [ast.Struct('MyStruct', ast.AttributeList(), ast.StructBody())])\n+ self.assertEquals(parser.Parse(source1, \"my_file.mojom\"), expected1)\n+\n+ # One-element attribute list, with name value.\n+ source2 = \"[MyAttribute=MyName] struct MyStruct {};\"\n+ expected2 = ast.Mojom(None, ast.ImportList(), [\n+ ast.Struct('MyStruct',\n+ ast.AttributeList(ast.Attribute(\"MyAttribute\", \"MyName\")),\n+ ast.StructBody())\n+ ])\n+ self.assertEquals(parser.Parse(source2, \"my_file.mojom\"), expected2)\n+\n+ # Two-element attribute list, with one string value and one integer value.\n+ source3 = \"[MyAttribute1 = \\\"hello\\\", MyAttribute2 = 5] struct MyStruct {};\"\n+ expected3 = ast.Mojom(None, ast.ImportList(), [\n+ ast.Struct(\n+ 'MyStruct',\n+ ast.AttributeList([\n+ ast.Attribute(\"MyAttribute1\", \"hello\"),\n+ ast.Attribute(\"MyAttribute2\", 5)\n+ ]), ast.StructBody())\n+ ])\n+ self.assertEquals(parser.Parse(source3, \"my_file.mojom\"), expected3)\n+\n+ # Various places that attribute list is allowed.\n+ source4 = \"\"\"\\\n+ [Attr0=0] module my_module;\n+\n+ [Attr1=1] import \"my_import\";\n+\n+ [Attr2=2] struct MyStruct {\n+ [Attr3=3] int32 a;\n+ };\n+ [Attr4=4] union MyUnion {\n+ [Attr5=5] int32 a;\n+ };\n+ [Attr6=6] enum MyEnum {\n+ [Attr7=7] a\n+ };\n+ [Attr8=8] interface MyInterface {\n+ [Attr9=9] MyMethod([Attr10=10] int32 a) => ([Attr11=11] bool b);\n+ };\n+ [Attr12=12] const double kMyConst = 1.23;\n+ \"\"\"\n+ expected4 = ast.Mojom(\n+ ast.Module(('IDENTIFIER', 'my_module'),\n+ ast.AttributeList([ast.Attribute(\"Attr0\", 0)])),\n+ ast.ImportList(\n+ ast.Import(\n+ ast.AttributeList([ast.Attribute(\"Attr1\", 1)]), \"my_import\")),\n+ [\n+ ast.Struct(\n+ 'MyStruct', ast.AttributeList(ast.Attribute(\"Attr2\", 2)),\n+ ast.StructBody(\n+ ast.StructField(\n+ 'a', ast.AttributeList([ast.Attribute(\"Attr3\", 3)]),\n+ None, 'int32', None))),\n+ ast.Union(\n+ 'MyUnion', ast.AttributeList(ast.Attribute(\"Attr4\", 4)),\n+ ast.UnionBody(\n+ ast.UnionField(\n+ 'a', ast.AttributeList([ast.Attribute(\"Attr5\", 5)]),\n+ None, 'int32'))),\n+ ast.Enum(\n+ 'MyEnum', ast.AttributeList(ast.Attribute(\"Attr6\", 6)),\n+ ast.EnumValueList(\n+ ast.EnumValue(\n+ 'VALUE', ast.AttributeList([ast.Attribute(\"Attr7\", 7)]),\n+ None))),\n+ ast.Interface(\n+ 'MyInterface', ast.AttributeList(ast.Attribute(\"Attr8\", 8)),\n+ ast.InterfaceBody(\n+ ast.Method(\n+ 'MyMethod', ast.AttributeList(\n+ ast.Attribute(\"Attr9\", 9)), None,\n+ ast.ParameterList(\n+ ast.Parameter(\n+ 'a',\n+ ast.AttributeList([ast.Attribute(\"Attr10\", 10)\n+ ]), None, 'int32')),\n+ ast.ParameterList(\n+ ast.Parameter(\n+ 'b',\n+ ast.AttributeList([ast.Attribute(\"Attr11\", 11)\n+ ]), None, 'bool'))))),\n+ ast.Const('kMyConst', ast.AttributeList(\n+ ast.Attribute(\"Attr12\", 12)), 'double', '1.23')\n+ ])\n+ self.assertEquals(parser.Parse(source4, \"my_file.mojom\"), expected4)\n+\n+ # TODO(vtl): Boolean attributes don't work yet. (In fact, we just |eval()|\n+ # literal (non-name) values, which is extremely dubious.)\n+\n+ def testInvalidAttributes(self):\n+ \"\"\"Tests that invalid attributes and attribute lists are correctly\n+ detected.\"\"\"\n+\n+ # Trailing commas not allowed.\n+ source1 = \"[MyAttribute=MyName,] struct MyStruct {};\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:1: Error: Unexpected '\\]':\\n\"\n+ r\"\\[MyAttribute=MyName,\\] struct MyStruct {};$\"):\n+ parser.Parse(source1, \"my_file.mojom\")\n+\n+ # Missing value.\n+ source2 = \"[MyAttribute=] struct MyStruct {};\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:1: Error: Unexpected '\\]':\\n\"\n+ r\"\\[MyAttribute=\\] struct MyStruct {};$\"):\n+ parser.Parse(source2, \"my_file.mojom\")\n+\n+ # Missing key.\n+ source3 = \"[=MyName] struct MyStruct {};\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:1: Error: Unexpected '=':\\n\"\n+ r\"\\[=MyName\\] struct MyStruct {};$\"):\n+ parser.Parse(source3, \"my_file.mojom\")\n+\n+ def testValidImports(self):\n+ \"\"\"Tests parsing import statements.\"\"\"\n+\n+ # One import (no module statement).\n+ source1 = \"import \\\"somedir/my.mojom\\\";\"\n+ expected1 = ast.Mojom(None,\n+ ast.ImportList(ast.Import(None, \"somedir/my.mojom\")),\n+ [])\n+ self.assertEquals(parser.Parse(source1, \"my_file.mojom\"), expected1)\n+\n+ # Two imports (no module statement).\n+ source2 = \"\"\"\\\n+ import \"somedir/my1.mojom\";\n+ import \"somedir/my2.mojom\";\n+ \"\"\"\n+ expected2 = ast.Mojom(\n+ None,\n+ ast.ImportList([\n+ ast.Import(None, \"somedir/my1.mojom\"),\n+ ast.Import(None, \"somedir/my2.mojom\")\n+ ]), [])\n+ self.assertEquals(parser.Parse(source2, \"my_file.mojom\"), expected2)\n+\n+ # Imports with module statement.\n+ source3 = \"\"\"\\\n+ module my_module;\n+ import \"somedir/my1.mojom\";\n+ import \"somedir/my2.mojom\";\n+ \"\"\"\n+ expected3 = ast.Mojom(\n+ ast.Module(('IDENTIFIER', 'my_module'), None),\n+ ast.ImportList([\n+ ast.Import(None, \"somedir/my1.mojom\"),\n+ ast.Import(None, \"somedir/my2.mojom\")\n+ ]), [])\n+ self.assertEquals(parser.Parse(source3, \"my_file.mojom\"), expected3)\n+\n+ def testInvalidImports(self):\n+ \"\"\"Tests that invalid import statements are correctly detected.\"\"\"\n+\n+ source1 = \"\"\"\\\n+ // Make the error occur on line 2.\n+ import invalid\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'invalid':\\n\"\n+ r\" *import invalid$\"):\n+ parser.Parse(source1, \"my_file.mojom\")\n+\n+ source2 = \"\"\"\\\n+ import // Missing string.\n+ struct MyStruct {\n+ int32 a;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'struct':\\n\"\n+ r\" *struct MyStruct {$\"):\n+ parser.Parse(source2, \"my_file.mojom\")\n+\n+ source3 = \"\"\"\\\n+ import \"foo.mojom\" // Missing semicolon.\n+ struct MyStruct {\n+ int32 a;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'struct':\\n\"\n+ r\" *struct MyStruct {$\"):\n+ parser.Parse(source3, \"my_file.mojom\")\n+\n+ def testValidNullableTypes(self):\n+ \"\"\"Tests parsing nullable types.\"\"\"\n+\n+ source = \"\"\"\\\n+ struct MyStruct {\n+ int32? a; // This is actually invalid, but handled at a different\n+ // level.\n+ string? b;\n+ array<int32> ? c;\n+ array<string ? > ? d;\n+ array<array<int32>?>? e;\n+ array<int32, 1>? f;\n+ array<string?, 1>? g;\n+ some_struct? h;\n+ handle? i;\n+ handle<data_pipe_consumer>? j;\n+ handle<data_pipe_producer>? k;\n+ handle<message_pipe>? l;\n+ handle<shared_buffer>? m;\n+ some_interface&? n;\n+ handle<platform>? o;\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(None, ast.ImportList(), [\n+ ast.Struct(\n+ 'MyStruct', None,\n+ ast.StructBody([\n+ ast.StructField('a', None, None, 'int32?', None),\n+ ast.StructField('b', None, None, 'string?', None),\n+ ast.StructField('c', None, None, 'int32[]?', None),\n+ ast.StructField('d', None, None, 'string?[]?', None),\n+ ast.StructField('e', None, None, 'int32[]?[]?', None),\n+ ast.StructField('f', None, None, 'int32[1]?', None),\n+ ast.StructField('g', None, None, 'string?[1]?', None),\n+ ast.StructField('h', None, None, 'some_struct?', None),\n+ ast.StructField('i', None, None, 'handle?', None),\n+ ast.StructField('j', None, None, 'handle<data_pipe_consumer>?',\n+ None),\n+ ast.StructField('k', None, None, 'handle<data_pipe_producer>?',\n+ None),\n+ ast.StructField('l', None, None, 'handle<message_pipe>?', None),\n+ ast.StructField('m', None, None, 'handle<shared_buffer>?',\n+ None),\n+ ast.StructField('n', None, None, 'some_interface&?', None),\n+ ast.StructField('o', None, None, 'handle<platform>?', None)\n+ ]))\n+ ])\n+ self.assertEquals(parser.Parse(source, \"my_file.mojom\"), expected)\n+\n+ def testInvalidNullableTypes(self):\n+ \"\"\"Tests that invalid nullable types are correctly detected.\"\"\"\n+ source1 = \"\"\"\\\n+ struct MyStruct {\n+ string?? a;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected '\\?':\\n\"\n+ r\" *string\\?\\? a;$\"):\n+ parser.Parse(source1, \"my_file.mojom\")\n+\n+ source2 = \"\"\"\\\n+ struct MyStruct {\n+ handle?<data_pipe_consumer> a;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected '<':\\n\"\n+ r\" *handle\\?<data_pipe_consumer> a;$\"):\n+ parser.Parse(source2, \"my_file.mojom\")\n+\n+ source3 = \"\"\"\\\n+ struct MyStruct {\n+ some_interface?& a;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected '&':\\n\"\n+ r\" *some_interface\\?& a;$\"):\n+ parser.Parse(source3, \"my_file.mojom\")\n+\n+ def testSimpleUnion(self):\n+ \"\"\"Tests a simple .mojom source that just defines a union.\"\"\"\n+ source = \"\"\"\\\n+ module my_module;\n+\n+ union MyUnion {\n+ int32 a;\n+ double b;\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(\n+ ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n+ ast.Union(\n+ 'MyUnion', None,\n+ ast.UnionBody([\n+ ast.UnionField('a', None, None, 'int32'),\n+ ast.UnionField('b', None, None, 'double')\n+ ]))\n+ ])\n+ actual = parser.Parse(source, \"my_file.mojom\")\n+ self.assertEquals(actual, expected)\n+\n+ def testUnionWithOrdinals(self):\n+ \"\"\"Test that ordinals are assigned to fields.\"\"\"\n+ source = \"\"\"\\\n+ module my_module;\n+\n+ union MyUnion {\n+ int32 a @10;\n+ double b @30;\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(\n+ ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n+ ast.Union(\n+ 'MyUnion', None,\n+ ast.UnionBody([\n+ ast.UnionField('a', None, ast.Ordinal(10), 'int32'),\n+ ast.UnionField('b', None, ast.Ordinal(30), 'double')\n+ ]))\n+ ])\n+ actual = parser.Parse(source, \"my_file.mojom\")\n+ self.assertEquals(actual, expected)\n+\n+ def testUnionWithStructMembers(self):\n+ \"\"\"Test that struct members are accepted.\"\"\"\n+ source = \"\"\"\\\n+ module my_module;\n+\n+ union MyUnion {\n+ SomeStruct s;\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(\n+ ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n+ ast.Union(\n+ 'MyUnion', None,\n+ ast.UnionBody([ast.UnionField('s', None, None, 'SomeStruct')]))\n+ ])\n+ actual = parser.Parse(source, \"my_file.mojom\")\n+ self.assertEquals(actual, expected)\n+\n+ def testUnionWithArrayMember(self):\n+ \"\"\"Test that array members are accepted.\"\"\"\n+ source = \"\"\"\\\n+ module my_module;\n+\n+ union MyUnion {\n+ array<int32> a;\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(\n+ ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n+ ast.Union(\n+ 'MyUnion', None,\n+ ast.UnionBody([ast.UnionField('a', None, None, 'int32[]')]))\n+ ])\n+ actual = parser.Parse(source, \"my_file.mojom\")\n+ self.assertEquals(actual, expected)\n+\n+ def testUnionWithMapMember(self):\n+ \"\"\"Test that map members are accepted.\"\"\"\n+ source = \"\"\"\\\n+ module my_module;\n+\n+ union MyUnion {\n+ map<int32, string> m;\n+ };\n+ \"\"\"\n+ expected = ast.Mojom(\n+ ast.Module(('IDENTIFIER', 'my_module'), None), ast.ImportList(), [\n+ ast.Union(\n+ 'MyUnion', None,\n+ ast.UnionBody(\n+ [ast.UnionField('m', None, None, 'string{int32}')]))\n+ ])\n+ actual = parser.Parse(source, \"my_file.mojom\")\n+ self.assertEquals(actual, expected)\n+\n+ def testUnionDisallowNestedStruct(self):\n+ \"\"\"Tests that structs cannot be nested in unions.\"\"\"\n+ source = \"\"\"\\\n+ module my_module;\n+\n+ union MyUnion {\n+ struct MyStruct {\n+ int32 a;\n+ };\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:4: Error: Unexpected 'struct':\\n\"\n+ r\" *struct MyStruct {$\"):\n+ parser.Parse(source, \"my_file.mojom\")\n+\n+ def testUnionDisallowNestedInterfaces(self):\n+ \"\"\"Tests that interfaces cannot be nested in unions.\"\"\"\n+ source = \"\"\"\\\n+ module my_module;\n+\n+ union MyUnion {\n+ interface MyInterface {\n+ MyMethod(int32 a);\n+ };\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError,\n+ r\"^my_file\\.mojom:4: Error: Unexpected 'interface':\\n\"\n+ r\" *interface MyInterface {$\"):\n+ parser.Parse(source, \"my_file.mojom\")\n+\n+ def testUnionDisallowNestedUnion(self):\n+ \"\"\"Tests that unions cannot be nested in unions.\"\"\"\n+ source = \"\"\"\\\n+ module my_module;\n+\n+ union MyUnion {\n+ union MyOtherUnion {\n+ int32 a;\n+ };\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:4: Error: Unexpected 'union':\\n\"\n+ r\" *union MyOtherUnion {$\"):\n+ parser.Parse(source, \"my_file.mojom\")\n+\n+ def testUnionDisallowNestedEnum(self):\n+ \"\"\"Tests that enums cannot be nested in unions.\"\"\"\n+ source = \"\"\"\\\n+ module my_module;\n+\n+ union MyUnion {\n+ enum MyEnum {\n+ A,\n+ };\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:4: Error: Unexpected 'enum':\\n\"\n+ r\" *enum MyEnum {$\"):\n+ parser.Parse(source, \"my_file.mojom\")\n+\n+ def testValidAssociatedKinds(self):\n+ \"\"\"Tests parsing associated interfaces and requests.\"\"\"\n+ source1 = \"\"\"\\\n+ struct MyStruct {\n+ associated MyInterface a;\n+ associated MyInterface& b;\n+ associated MyInterface? c;\n+ associated MyInterface&? d;\n+ };\n+ \"\"\"\n+ expected1 = ast.Mojom(None, ast.ImportList(), [\n+ ast.Struct(\n+ 'MyStruct', None,\n+ ast.StructBody([\n+ ast.StructField('a', None, None, 'asso<MyInterface>', None),\n+ ast.StructField('b', None, None, 'asso<MyInterface&>', None),\n+ ast.StructField('c', None, None, 'asso<MyInterface>?', None),\n+ ast.StructField('d', None, None, 'asso<MyInterface&>?', None)\n+ ]))\n+ ])\n+ self.assertEquals(parser.Parse(source1, \"my_file.mojom\"), expected1)\n+\n+ source2 = \"\"\"\\\n+ interface MyInterface {\n+ MyMethod(associated A a) =>(associated B& b);\n+ };\"\"\"\n+ expected2 = ast.Mojom(None, ast.ImportList(), [\n+ ast.Interface(\n+ 'MyInterface', None,\n+ ast.InterfaceBody(\n+ ast.Method(\n+ 'MyMethod', None, None,\n+ ast.ParameterList(\n+ ast.Parameter('a', None, None, 'asso<A>')),\n+ ast.ParameterList(\n+ ast.Parameter('b', None, None, 'asso<B&>')))))\n+ ])\n+ self.assertEquals(parser.Parse(source2, \"my_file.mojom\"), expected2)\n+\n+ def testInvalidAssociatedKinds(self):\n+ \"\"\"Tests that invalid associated interfaces and requests are correctly\n+ detected.\"\"\"\n+ source1 = \"\"\"\\\n+ struct MyStruct {\n+ associated associated SomeInterface a;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError,\n+ r\"^my_file\\.mojom:2: Error: Unexpected 'associated':\\n\"\n+ r\" *associated associated SomeInterface a;$\"):\n+ parser.Parse(source1, \"my_file.mojom\")\n+\n+ source2 = \"\"\"\\\n+ struct MyStruct {\n+ associated handle a;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected 'handle':\\n\"\n+ r\" *associated handle a;$\"):\n+ parser.Parse(source2, \"my_file.mojom\")\n+\n+ source3 = \"\"\"\\\n+ struct MyStruct {\n+ associated? MyInterface& a;\n+ };\n+ \"\"\"\n+ with self.assertRaisesRegexp(\n+ parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected '\\?':\\n\"\n+ r\" *associated\\? MyInterface& a;$\"):\n+ parser.Parse(source3, \"my_file.mojom\")\n+\n+\n+if __name__ == \"__main__\":\n+ unittest.main()\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py\nnew file mode 100755\nindex 00000000..12adbfb9\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py\n@@ -0,0 +1,361 @@\n+#!/usr/bin/env python\n+# Copyright 2020 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\"\"\"Parses mojom IDL files.\n+\n+This script parses one or more input mojom files and produces corresponding\n+module files fully describing the definitions contained within each mojom. The\n+module data is pickled and can be easily consumed by other tools to, e.g.,\n+generate usable language bindings.\n+\"\"\"\n+\n+import argparse\n+import codecs\n+import errno\n+import json\n+import os\n+import os.path\n+import sys\n+from collections import defaultdict\n+\n+from mojom.generate import module\n+from mojom.generate import translate\n+from mojom.parse import parser\n+from mojom.parse import conditional_features\n+\n+\n+def _ResolveRelativeImportPath(path, roots):\n+ \"\"\"Attempts to resolve a relative import path against a set of possible roots.\n+\n+ Args:\n+ path: The relative import path to resolve.\n+ roots: A list of absolute paths which will be checked in descending length\n+ order for a match against path.\n+\n+ Returns:\n+ A normalized absolute path combining one of the roots with the input path if\n+ and only if such a file exists.\n+\n+ Raises:\n+ ValueError: The path could not be resolved against any of the given roots.\n+ \"\"\"\n+ for root in reversed(sorted(roots, key=len)):\n+ abs_path = os.path.join(root, path)\n+ if os.path.isfile(abs_path):\n+ return os.path.normcase(os.path.normpath(abs_path))\n+\n+ raise ValueError('\"%s\" does not exist in any of %s' % (path, roots))\n+\n+\n+def _RebaseAbsolutePath(path, roots):\n+ \"\"\"Rewrites an absolute file path as relative to an absolute directory path in\n+ roots.\n+\n+ Args:\n+ path: The absolute path of an existing file.\n+ roots: A list of absolute directory paths. The given path argument must fall\n+ within one of these directories.\n+\n+ Returns:\n+ A path equivalent to the input path, but relative to one of the provided\n+ roots. If the input path falls within multiple roots, the longest root is\n+ chosen (and thus the shortest relative path is returned).\n+\n+ Paths returned by this method always use forward slashes as a separator to\n+ mirror mojom import syntax.\n+\n+ Raises:\n+ ValueError if the given path does not fall within any of the listed roots.\n+ \"\"\"\n+ assert os.path.isabs(path)\n+ assert os.path.isfile(path)\n+ assert all(map(os.path.isabs, roots))\n+\n+ sorted_roots = list(reversed(sorted(roots, key=len)))\n+\n+ def try_rebase_path(path, root):\n+ head, rebased_path = os.path.split(path)\n+ while head != root:\n+ head, tail = os.path.split(head)\n+ if not tail:\n+ return None\n+ rebased_path = os.path.join(tail, rebased_path)\n+ return rebased_path\n+\n+ for root in sorted_roots:\n+ relative_path = try_rebase_path(path, root)\n+ if relative_path:\n+ # TODO(crbug.com/953884): Use pathlib for this kind of thing once we're\n+ # fully migrated to Python 3.\n+ return relative_path.replace('\\\\', '/')\n+\n+ raise ValueError('%s does not fall within any of %s' % (path, sorted_roots))\n+\n+\n+def _GetModuleFilename(mojom_filename):\n+ return mojom_filename + '-module'\n+\n+\n+def _EnsureInputLoaded(mojom_abspath, module_path, abs_paths, asts,\n+ dependencies, loaded_modules):\n+ \"\"\"Recursively ensures that a module and its dependencies are loaded.\n+\n+ Args:\n+ mojom_abspath: An absolute file path pointing to a mojom file to load.\n+ module_path: The relative path used to identify mojom_abspath.\n+ abs_paths: A mapping from module paths to absolute file paths for all\n+ inputs given to this execution of the script.\n+ asts: A map from each input mojom's absolute path to its parsed AST.\n+ dependencies: A mapping of which input mojoms depend on each other, indexed\n+ by absolute file path.\n+ loaded_modules: A mapping of all modules loaded so far, including non-input\n+ modules that were pulled in as transitive dependencies of the inputs.\n+ import_set: The working set of mojom imports processed so far in this\n+ call stack. Used to detect circular dependencies.\n+ import_stack: An ordered list of imports processed so far in this call\n+ stack. Used to report circular dependencies.\n+\n+ Returns:\n+ None\n+\n+ On return, loaded_modules will be populated with the loaded input mojom's\n+ Module as well as the Modules of all of its transitive dependencies.\"\"\"\n+\n+ if mojom_abspath in loaded_modules:\n+ # Already done.\n+ return\n+\n+ for dep_abspath, dep_path in dependencies[mojom_abspath]:\n+ if dep_abspath not in loaded_modules:\n+ _EnsureInputLoaded(dep_abspath, dep_path, abs_paths, asts, dependencies,\n+ loaded_modules)\n+\n+ imports = {}\n+ for imp in asts[mojom_abspath].import_list:\n+ path = imp.import_filename\n+ imports[path] = loaded_modules[abs_paths[path]]\n+ loaded_modules[mojom_abspath] = translate.OrderedModule(\n+ asts[mojom_abspath], module_path, imports)\n+\n+\n+def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):\n+ allowed_imports = set()\n+ processed_deps = set()\n+\n+ def collect(metadata_filename):\n+ processed_deps.add(metadata_filename)\n+ with open(metadata_filename) as f:\n+ metadata = json.load(f)\n+ allowed_imports.update(\n+ map(os.path.normcase, map(os.path.normpath, metadata['sources'])))\n+ for dep_metadata in metadata['deps']:\n+ if dep_metadata not in processed_deps:\n+ collect(dep_metadata)\n+\n+ collect(build_metadata_filename)\n+ return allowed_imports\n+\n+\n+def _ParseMojoms(mojom_files,\n+ input_root_paths,\n+ output_root_path,\n+ enabled_features,\n+ allowed_imports=None):\n+ \"\"\"Parses a set of mojom files and produces serialized module outputs.\n+\n+ Args:\n+ mojom_files: A list of mojom files to process. Paths must be absolute paths\n+ which fall within one of the input or output root paths.\n+ input_root_paths: A list of absolute filesystem paths which may be used to\n+ resolve relative mojom file paths.\n+ output_root_path: An absolute filesystem path which will service as the root\n+ for all emitted artifacts. Artifacts produced from a given mojom file\n+ are based on the mojom's relative path, rebased onto this path.\n+ Additionally, the script expects this root to contain already-generated\n+ modules for any transitive dependencies not listed in mojom_files.\n+ enabled_features: A list of enabled feature names, controlling which AST\n+ nodes are filtered by [EnableIf] attributes.\n+\n+ Returns:\n+ None.\n+\n+ Upon completion, a mojom-module file will be saved for each input mojom.\n+ \"\"\"\n+ assert input_root_paths\n+ assert output_root_path\n+\n+ loaded_mojom_asts = {}\n+ loaded_modules = {}\n+ input_dependencies = defaultdict(set)\n+ mojom_files_to_parse = dict((os.path.normcase(abs_path),\n+ _RebaseAbsolutePath(abs_path, input_root_paths))\n+ for abs_path in mojom_files)\n+ abs_paths = dict(\n+ (path, abs_path) for abs_path, path in mojom_files_to_parse.items())\n+ for mojom_abspath, _ in mojom_files_to_parse.items():\n+ with codecs.open(mojom_abspath, encoding='utf-8') as f:\n+ ast = parser.Parse(''.join(f.readlines()), mojom_abspath)\n+ conditional_features.RemoveDisabledDefinitions(ast, enabled_features)\n+ loaded_mojom_asts[mojom_abspath] = ast\n+ invalid_imports = []\n+ for imp in ast.import_list:\n+ import_abspath = _ResolveRelativeImportPath(imp.import_filename,\n+ input_root_paths)\n+ if allowed_imports and import_abspath not in allowed_imports:\n+ invalid_imports.append(imp.import_filename)\n+\n+ abs_paths[imp.import_filename] = import_abspath\n+ if import_abspath in mojom_files_to_parse:\n+ # This import is in the input list, so we're going to translate it\n+ # into a module below; however it's also a dependency of another input\n+ # module. We retain record of dependencies to help with input\n+ # processing later.\n+ input_dependencies[mojom_abspath].add((import_abspath,\n+ imp.import_filename))\n+ else:\n+ # We have an import that isn't being parsed right now. It must already\n+ # be parsed and have a module file sitting in a corresponding output\n+ # location.\n+ module_path = _GetModuleFilename(imp.import_filename)\n+ module_abspath = _ResolveRelativeImportPath(module_path,\n+ [output_root_path])\n+ with open(module_abspath, 'rb') as module_file:\n+ loaded_modules[import_abspath] = module.Module.Load(module_file)\n+\n+ if invalid_imports:\n+ raise ValueError(\n+ '\\nThe file %s imports the following files not allowed by build '\n+ 'dependencies:\\n\\n%s\\n' % (mojom_abspath,\n+ '\\n'.join(invalid_imports)))\n+\n+\n+ # At this point all transitive imports not listed as inputs have been loaded\n+ # and we have a complete dependency tree of the unprocessed inputs. Now we can\n+ # load all the inputs, resolving dependencies among them recursively as we go.\n+ num_existing_modules_loaded = len(loaded_modules)\n+ for mojom_abspath, mojom_path in mojom_files_to_parse.items():\n+ _EnsureInputLoaded(mojom_abspath, mojom_path, abs_paths, loaded_mojom_asts,\n+ input_dependencies, loaded_modules)\n+ assert (num_existing_modules_loaded +\n+ len(mojom_files_to_parse) == len(loaded_modules))\n+\n+ # Now we have fully translated modules for every input and every transitive\n+ # dependency. We can dump the modules to disk for other tools to use.\n+ for mojom_abspath, mojom_path in mojom_files_to_parse.items():\n+ module_path = os.path.join(output_root_path, _GetModuleFilename(mojom_path))\n+ module_dir = os.path.dirname(module_path)\n+ if not os.path.exists(module_dir):\n+ try:\n+ # Python 2 doesn't support exist_ok on makedirs(), so we just ignore\n+ # that failure if it happens. It's possible during build due to races\n+ # among build steps with module outputs in the same directory.\n+ os.makedirs(module_dir)\n+ except OSError as e:\n+ if e.errno != errno.EEXIST:\n+ raise\n+ with open(module_path, 'wb') as f:\n+ loaded_modules[mojom_abspath].Dump(f)\n+\n+\n+def Run(command_line):\n+ arg_parser = argparse.ArgumentParser(\n+ description=\"\"\"\n+Parses one or more mojom files and produces corresponding module outputs fully\n+describing the definitions therein. The output is exhaustive, stable, and\n+sufficient for another tool to consume and emit e.g. usable language\n+bindings based on the original mojoms.\"\"\",\n+ epilog=\"\"\"\n+Note that each transitive import dependency reachable from the input mojoms must\n+either also be listed as an input or must have its corresponding compiled module\n+already present in the provided output root.\"\"\")\n+\n+ arg_parser.add_argument(\n+ '--input-root',\n+ default=[],\n+ action='append',\n+ metavar='ROOT',\n+ dest='input_root_paths',\n+ help='Adds ROOT to the set of root paths against which relative input '\n+ 'paths should be resolved. Provided root paths are always searched '\n+ 'in order from longest absolute path to shortest.')\n+ arg_parser.add_argument(\n+ '--output-root',\n+ action='store',\n+ required=True,\n+ dest='output_root_path',\n+ metavar='ROOT',\n+ help='Use ROOT as the root path in which the parser should emit compiled '\n+ 'modules for each processed input mojom. The path of emitted module is '\n+ 'based on the relative input path, rebased onto this root. Note that '\n+ 'ROOT is also searched for existing modules of any transitive imports '\n+ 'which were not included in the set of inputs.')\n+ arg_parser.add_argument(\n+ '--mojoms',\n+ nargs='+',\n+ dest='mojom_files',\n+ default=[],\n+ metavar='MOJOM_FILE',\n+ help='Input mojom filename(s). Each filename must be either an absolute '\n+ 'path which falls within one of the given input or output roots, or a '\n+ 'relative path the parser will attempt to resolve using each of those '\n+ 'roots in unspecified order.')\n+ arg_parser.add_argument(\n+ '--mojom-file-list',\n+ action='store',\n+ metavar='LIST_FILENAME',\n+ help='Input file whose contents are a list of mojoms to process. This '\n+ 'may be provided in lieu of --mojoms to avoid hitting command line '\n+ 'length limtations')\n+ arg_parser.add_argument(\n+ '--enable-feature',\n+ dest='enabled_features',\n+ default=[],\n+ action='append',\n+ metavar='FEATURE',\n+ help='Enables a named feature when parsing the given mojoms. Features '\n+ 'are identified by arbitrary string values. Specifying this flag with a '\n+ 'given FEATURE name will cause the parser to process any syntax elements '\n+ 'tagged with an [EnableIf=FEATURE] attribute. If this flag is not '\n+ 'provided for a given FEATURE, such tagged elements are discarded by the '\n+ 'parser and will not be present in the compiled output.')\n+ arg_parser.add_argument(\n+ '--check-imports',\n+ dest='build_metadata_filename',\n+ action='store',\n+ metavar='METADATA_FILENAME',\n+ help='Instructs the parser to check imports against a set of allowed '\n+ 'imports. Allowed imports are based on build metadata within '\n+ 'METADATA_FILENAME. This is a JSON file with a `sources` key listing '\n+ 'paths to the set of input mojom files being processed by this parser '\n+ 'run, and a `deps` key listing paths to metadata files for any '\n+ 'dependencies of these inputs. This feature can be used to implement '\n+ 'build-time dependency checking for mojom imports, where each build '\n+ 'metadata file corresponds to a build target in the dependency graph of '\n+ 'a typical build system.')\n+\n+ args, _ = arg_parser.parse_known_args(command_line)\n+ if args.mojom_file_list:\n+ with open(args.mojom_file_list) as f:\n+ args.mojom_files.extend(f.read().split())\n+\n+ if not args.mojom_files:\n+ raise ValueError(\n+ 'Must list at least one mojom file via --mojoms or --mojom-file-list')\n+\n+ mojom_files = list(map(os.path.abspath, args.mojom_files))\n+ input_roots = list(map(os.path.abspath, args.input_root_paths))\n+ output_root = os.path.abspath(args.output_root_path)\n+\n+ if args.build_metadata_filename:\n+ allowed_imports = _CollectAllowedImportsFromBuildMetadata(\n+ args.build_metadata_filename)\n+ else:\n+ allowed_imports = None\n+\n+ _ParseMojoms(mojom_files, input_roots, output_root, args.enabled_features,\n+ allowed_imports)\n+\n+\n+if __name__ == '__main__':\n+ Run(sys.argv[1:])\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py\nnew file mode 100644\nindex 00000000..e213fbfa\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py\n@@ -0,0 +1,73 @@\n+# Copyright 2020 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import json\n+import os\n+import os.path\n+import shutil\n+import tempfile\n+import unittest\n+\n+import mojom_parser\n+\n+from mojom.generate import module\n+\n+\n+class MojomParserTestCase(unittest.TestCase):\n+ \"\"\"Tests covering the behavior defined by the main mojom_parser.py script.\n+ This includes behavior around input and output path manipulation, dependency\n+ resolution, and module serialization and deserialization.\"\"\"\n+\n+ def __init__(self, method_name):\n+ super(MojomParserTestCase, self).__init__(method_name)\n+ self._temp_dir = None\n+\n+ def setUp(self):\n+ self._temp_dir = tempfile.mkdtemp()\n+\n+ def tearDown(self):\n+ shutil.rmtree(self._temp_dir)\n+ self._temp_dir = None\n+\n+ def GetPath(self, path):\n+ assert not os.path.isabs(path)\n+ return os.path.join(self._temp_dir, path)\n+\n+ def GetModulePath(self, path):\n+ assert not os.path.isabs(path)\n+ return os.path.join(self.GetPath('out'), path) + '-module'\n+\n+ def WriteFile(self, path, contents):\n+ full_path = self.GetPath(path)\n+ dirname = os.path.dirname(full_path)\n+ if not os.path.exists(dirname):\n+ os.makedirs(dirname)\n+ with open(full_path, 'w') as f:\n+ f.write(contents)\n+\n+ def LoadModule(self, mojom_path):\n+ with open(self.GetModulePath(mojom_path), 'rb') as f:\n+ return module.Module.Load(f)\n+\n+ def ParseMojoms(self, mojoms, metadata=None):\n+ \"\"\"Parse all input mojoms relative the temp dir.\"\"\"\n+ out_dir = self.GetPath('out')\n+ args = [\n+ '--input-root', self._temp_dir, '--input-root', out_dir,\n+ '--output-root', out_dir, '--mojoms'\n+ ] + list(map(lambda mojom: os.path.join(self._temp_dir, mojom), mojoms))\n+ if metadata:\n+ args.extend(['--check-imports', self.GetPath(metadata)])\n+ mojom_parser.Run(args)\n+\n+ def ExtractTypes(self, mojom):\n+ filename = 'test.mojom'\n+ self.WriteFile(filename, mojom)\n+ self.ParseMojoms([filename])\n+ m = self.LoadModule(filename)\n+ definitions = {}\n+ for kinds in (m.enums, m.structs, m.unions, m.interfaces):\n+ for kind in kinds:\n+ definitions[kind.mojom_name] = kind\n+ return definitions\ndiff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py\nnew file mode 100644\nindex 00000000..a93f34ba\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py\n@@ -0,0 +1,171 @@\n+# Copyright 2020 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+from mojom_parser_test_case import MojomParserTestCase\n+\n+\n+class MojomParserTest(MojomParserTestCase):\n+ \"\"\"Tests covering the behavior defined by the main mojom_parser.py script.\n+ This includes behavior around input and output path manipulation, dependency\n+ resolution, and module serialization and deserialization.\"\"\"\n+\n+ def testBasicParse(self):\n+ \"\"\"Basic test to verify that we can parse a mojom file and get a module.\"\"\"\n+ mojom = 'foo/bar.mojom'\n+ self.WriteFile(\n+ mojom, \"\"\"\\\n+ module test;\n+ enum TestEnum { kFoo };\n+ \"\"\")\n+ self.ParseMojoms([mojom])\n+\n+ m = self.LoadModule(mojom)\n+ self.assertEqual('foo/bar.mojom', m.path)\n+ self.assertEqual('test', m.mojom_namespace)\n+ self.assertEqual(1, len(m.enums))\n+\n+ def testBasicParseWithAbsolutePaths(self):\n+ \"\"\"Verifies that we can parse a mojom file given an absolute path input.\"\"\"\n+ mojom = 'foo/bar.mojom'\n+ self.WriteFile(\n+ mojom, \"\"\"\\\n+ module test;\n+ enum TestEnum { kFoo };\n+ \"\"\")\n+ self.ParseMojoms([self.GetPath(mojom)])\n+\n+ m = self.LoadModule(mojom)\n+ self.assertEqual('foo/bar.mojom', m.path)\n+ self.assertEqual('test', m.mojom_namespace)\n+ self.assertEqual(1, len(m.enums))\n+\n+ def testImport(self):\n+ \"\"\"Verify imports within the same set of mojom inputs.\"\"\"\n+ a = 'a.mojom'\n+ b = 'b.mojom'\n+ self.WriteFile(\n+ a, \"\"\"\\\n+ module a;\n+ import \"b.mojom\";\n+ struct Foo { b.Bar bar; };\"\"\")\n+ self.WriteFile(b, \"\"\"\\\n+ module b;\n+ struct Bar {};\"\"\")\n+ self.ParseMojoms([a, b])\n+\n+ ma = self.LoadModule(a)\n+ mb = self.LoadModule(b)\n+ self.assertEqual('a.mojom', ma.path)\n+ self.assertEqual('b.mojom', mb.path)\n+ self.assertEqual(1, len(ma.imports))\n+ self.assertEqual(mb, ma.imports[0])\n+\n+ def testPreProcessedImport(self):\n+ \"\"\"Verify imports processed by a previous parser execution can be loaded\n+ properly when parsing a dependent mojom.\"\"\"\n+ a = 'a.mojom'\n+ self.WriteFile(a, \"\"\"\\\n+ module a;\n+ struct Bar {};\"\"\")\n+ self.ParseMojoms([a])\n+\n+ b = 'b.mojom'\n+ self.WriteFile(\n+ b, \"\"\"\\\n+ module b;\n+ import \"a.mojom\";\n+ struct Foo { a.Bar bar; };\"\"\")\n+ self.ParseMojoms([b])\n+\n+ def testMissingImport(self):\n+ \"\"\"Verify that an import fails if the imported mojom does not exist.\"\"\"\n+ a = 'a.mojom'\n+ self.WriteFile(\n+ a, \"\"\"\\\n+ module a;\n+ import \"non-existent.mojom\";\n+ struct Bar {};\"\"\")\n+ with self.assertRaisesRegexp(ValueError, \"does not exist\"):\n+ self.ParseMojoms([a])\n+\n+ def testUnparsedImport(self):\n+ \"\"\"Verify that an import fails if the imported mojom is not in the set of\n+ mojoms provided to the parser on this execution AND there is no pre-existing\n+ parsed output module already on disk for it.\"\"\"\n+ a = 'a.mojom'\n+ b = 'b.mojom'\n+ self.WriteFile(a, \"\"\"\\\n+ module a;\n+ struct Bar {};\"\"\")\n+ self.WriteFile(\n+ b, \"\"\"\\\n+ module b;\n+ import \"a.mojom\";\n+ struct Foo { a.Bar bar; };\"\"\")\n+\n+ # a.mojom has not been parsed yet, so its import will fail when processing\n+ # b.mojom here.\n+ with self.assertRaisesRegexp(ValueError, \"does not exist\"):\n+ self.ParseMojoms([b])\n+\n+ def testCheckImportsBasic(self):\n+ \"\"\"Verify that the parser can handle --check-imports with a valid set of\n+ inputs, including support for transitive dependency resolution.\"\"\"\n+ a = 'a.mojom'\n+ a_metadata = 'out/a.build_metadata'\n+ b = 'b.mojom'\n+ b_metadata = 'out/b.build_metadata'\n+ c = 'c.mojom'\n+ c_metadata = 'out/c.build_metadata'\n+ self.WriteFile(a_metadata,\n+ '{\"sources\": [\"%s\"], \"deps\": []}\\n' % self.GetPath(a))\n+ self.WriteFile(\n+ b_metadata,\n+ '{\"sources\": [\"%s\"], \"deps\": [\"%s\"]}\\n' % (self.GetPath(b),\n+ self.GetPath(a_metadata)))\n+ self.WriteFile(\n+ c_metadata,\n+ '{\"sources\": [\"%s\"], \"deps\": [\"%s\"]}\\n' % (self.GetPath(c),\n+ self.GetPath(b_metadata)))\n+ self.WriteFile(a, \"\"\"\\\n+ module a;\n+ struct Bar {};\"\"\")\n+ self.WriteFile(\n+ b, \"\"\"\\\n+ module b;\n+ import \"a.mojom\";\n+ struct Foo { a.Bar bar; };\"\"\")\n+ self.WriteFile(\n+ c, \"\"\"\\\n+ module c;\n+ import \"a.mojom\";\n+ import \"b.mojom\";\n+ struct Baz { b.Foo foo; };\"\"\")\n+ self.ParseMojoms([a], metadata=a_metadata)\n+ self.ParseMojoms([b], metadata=b_metadata)\n+ self.ParseMojoms([c], metadata=c_metadata)\n+\n+ def testCheckImportsMissing(self):\n+ \"\"\"Verify that the parser rejects valid input mojoms when imports don't\n+ agree with build metadata given via --check-imports.\"\"\"\n+ a = 'a.mojom'\n+ a_metadata = 'out/a.build_metadata'\n+ b = 'b.mojom'\n+ b_metadata = 'out/b.build_metadata'\n+ self.WriteFile(a_metadata,\n+ '{\"sources\": [\"%s\"], \"deps\": []}\\n' % self.GetPath(a))\n+ self.WriteFile(b_metadata,\n+ '{\"sources\": [\"%s\"], \"deps\": []}\\n' % self.GetPath(b))\n+ self.WriteFile(a, \"\"\"\\\n+ module a;\n+ struct Bar {};\"\"\")\n+ self.WriteFile(\n+ b, \"\"\"\\\n+ module b;\n+ import \"a.mojom\";\n+ struct Foo { a.Bar bar; };\"\"\")\n+\n+ self.ParseMojoms([a], metadata=a_metadata)\n+ with self.assertRaisesRegexp(ValueError, \"not allowed by build\"):\n+ self.ParseMojoms([b], metadata=b_metadata)\ndiff --git a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py\nnew file mode 100644\nindex 00000000..d45ec586\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py\n@@ -0,0 +1,127 @@\n+# Copyright 2020 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+from mojom_parser_test_case import MojomParserTestCase\n+\n+from mojom.generate import module\n+\n+\n+class StableAttributeTest(MojomParserTestCase):\n+ \"\"\"Tests covering usage of the [Stable] attribute.\"\"\"\n+\n+ def testStableAttributeTagging(self):\n+ \"\"\"Verify that we recognize the [Stable] attribute on relevant definitions\n+ and the resulting parser outputs are tagged accordingly.\"\"\"\n+ mojom = 'test.mojom'\n+ self.WriteFile(\n+ mojom, \"\"\"\\\n+ [Stable] enum TestEnum { kFoo };\n+ enum UnstableEnum { kBar };\n+ [Stable] struct TestStruct { TestEnum a; };\n+ struct UnstableStruct { UnstableEnum a; };\n+ [Stable] union TestUnion { TestEnum a; TestStruct b; };\n+ union UnstableUnion { UnstableEnum a; UnstableStruct b; };\n+ [Stable] interface TestInterface { Foo@0(TestUnion x) => (); };\n+ interface UnstableInterface { Foo(UnstableUnion x) => (); };\n+ \"\"\")\n+ self.ParseMojoms([mojom])\n+\n+ m = self.LoadModule(mojom)\n+ self.assertEqual(2, len(m.enums))\n+ self.assertTrue(m.enums[0].stable)\n+ self.assertFalse(m.enums[1].stable)\n+ self.assertEqual(2, len(m.structs))\n+ self.assertTrue(m.structs[0].stable)\n+ self.assertFalse(m.structs[1].stable)\n+ self.assertEqual(2, len(m.unions))\n+ self.assertTrue(m.unions[0].stable)\n+ self.assertFalse(m.unions[1].stable)\n+ self.assertEqual(2, len(m.interfaces))\n+ self.assertTrue(m.interfaces[0].stable)\n+ self.assertFalse(m.interfaces[1].stable)\n+\n+ def testStableStruct(self):\n+ \"\"\"A [Stable] struct is valid if all its fields are also stable.\"\"\"\n+ self.ExtractTypes('[Stable] struct S {};')\n+ self.ExtractTypes('[Stable] struct S { int32 x; bool b; };')\n+ self.ExtractTypes('[Stable] enum E { A }; [Stable] struct S { E e; };')\n+ self.ExtractTypes('[Stable] struct S {}; [Stable] struct T { S s; };')\n+ self.ExtractTypes(\n+ '[Stable] struct S {}; [Stable] struct T { array<S> ss; };')\n+ self.ExtractTypes(\n+ '[Stable] interface F {}; [Stable] struct T { pending_remote<F> f; };')\n+\n+ with self.assertRaisesRegexp(Exception, 'because it depends on E'):\n+ self.ExtractTypes('enum E { A }; [Stable] struct S { E e; };')\n+ with self.assertRaisesRegexp(Exception, 'because it depends on X'):\n+ self.ExtractTypes('struct X {}; [Stable] struct S { X x; };')\n+ with self.assertRaisesRegexp(Exception, 'because it depends on T'):\n+ self.ExtractTypes('struct T {}; [Stable] struct S { array<T> xs; };')\n+ with self.assertRaisesRegexp(Exception, 'because it depends on T'):\n+ self.ExtractTypes('struct T {}; [Stable] struct S { map<int32, T> xs; };')\n+ with self.assertRaisesRegexp(Exception, 'because it depends on T'):\n+ self.ExtractTypes('struct T {}; [Stable] struct S { map<T, int32> xs; };')\n+ with self.assertRaisesRegexp(Exception, 'because it depends on F'):\n+ self.ExtractTypes(\n+ 'interface F {}; [Stable] struct S { pending_remote<F> f; };')\n+ with self.assertRaisesRegexp(Exception, 'because it depends on F'):\n+ self.ExtractTypes(\n+ 'interface F {}; [Stable] struct S { pending_receiver<F> f; };')\n+\n+ def testStableUnion(self):\n+ \"\"\"A [Stable] union is valid if all its fields' types are also stable.\"\"\"\n+ self.ExtractTypes('[Stable] union U {};')\n+ self.ExtractTypes('[Stable] union U { int32 x; bool b; };')\n+ self.ExtractTypes('[Stable] enum E { A }; [Stable] union U { E e; };')\n+ self.ExtractTypes('[Stable] struct S {}; [Stable] union U { S s; };')\n+ self.ExtractTypes(\n+ '[Stable] struct S {}; [Stable] union U { array<S> ss; };')\n+ self.ExtractTypes(\n+ '[Stable] interface F {}; [Stable] union U { pending_remote<F> f; };')\n+\n+ with self.assertRaisesRegexp(Exception, 'because it depends on E'):\n+ self.ExtractTypes('enum E { A }; [Stable] union U { E e; };')\n+ with self.assertRaisesRegexp(Exception, 'because it depends on X'):\n+ self.ExtractTypes('struct X {}; [Stable] union U { X x; };')\n+ with self.assertRaisesRegexp(Exception, 'because it depends on T'):\n+ self.ExtractTypes('struct T {}; [Stable] union U { array<T> xs; };')\n+ with self.assertRaisesRegexp(Exception, 'because it depends on T'):\n+ self.ExtractTypes('struct T {}; [Stable] union U { map<int32, T> xs; };')\n+ with self.assertRaisesRegexp(Exception, 'because it depends on T'):\n+ self.ExtractTypes('struct T {}; [Stable] union U { map<T, int32> xs; };')\n+ with self.assertRaisesRegexp(Exception, 'because it depends on F'):\n+ self.ExtractTypes(\n+ 'interface F {}; [Stable] union U { pending_remote<F> f; };')\n+ with self.assertRaisesRegexp(Exception, 'because it depends on F'):\n+ self.ExtractTypes(\n+ 'interface F {}; [Stable] union U { pending_receiver<F> f; };')\n+\n+ def testStableInterface(self):\n+ \"\"\"A [Stable] interface is valid if all its methods' parameter types are\n+ stable, including response parameters where applicable.\"\"\"\n+ self.ExtractTypes('[Stable] interface F {};')\n+ self.ExtractTypes('[Stable] interface F { A@0(int32 x); };')\n+ self.ExtractTypes('[Stable] interface F { A@0(int32 x) => (bool b); };')\n+ self.ExtractTypes(\"\"\"\\\n+ [Stable] enum E { A, B, C };\n+ [Stable] struct S {};\n+ [Stable] interface F { A@0(E e, S s) => (bool b, array<S> s); };\n+ \"\"\")\n+\n+ with self.assertRaisesRegexp(Exception, 'because it depends on E'):\n+ self.ExtractTypes(\n+ 'enum E { A, B, C }; [Stable] interface F { A@0(E e); };')\n+ with self.assertRaisesRegexp(Exception, 'because it depends on E'):\n+ self.ExtractTypes(\n+ 'enum E { A, B, C }; [Stable] interface F { A@0(int32 x) => (E e); };'\n+ )\n+ with self.assertRaisesRegexp(Exception, 'because it depends on S'):\n+ self.ExtractTypes(\n+ 'struct S {}; [Stable] interface F { A@0(int32 x) => (S s); };')\n+ with self.assertRaisesRegexp(Exception, 'because it depends on S'):\n+ self.ExtractTypes(\n+ 'struct S {}; [Stable] interface F { A@0(S s) => (bool b); };')\n+\n+ with self.assertRaisesRegexp(Exception, 'explicit method ordinals'):\n+ self.ExtractTypes('[Stable] interface F { A() => (); };')\ndiff --git a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py\nnew file mode 100644\nindex 00000000..a0ee150e\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py\n@@ -0,0 +1,397 @@\n+# Copyright 2020 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+from mojom_parser_test_case import MojomParserTestCase\n+\n+\n+class VersionCompatibilityTest(MojomParserTestCase):\n+ \"\"\"Tests covering compatibility between two versions of the same mojom type\n+ definition. This coverage ensures that we can reliably detect unsafe changes\n+ to definitions that are expected to tolerate version skew in production\n+ environments.\"\"\"\n+\n+ def _GetTypeCompatibilityMap(self, old_mojom, new_mojom):\n+ \"\"\"Helper to support the implementation of assertBackwardCompatible and\n+ assertNotBackwardCompatible.\"\"\"\n+\n+ old = self.ExtractTypes(old_mojom)\n+ new = self.ExtractTypes(new_mojom)\n+ self.assertEqual(set(old.keys()), set(new.keys()),\n+ 'Old and new test mojoms should use the same type names.')\n+\n+ compatibility_map = {}\n+ for name in old.keys():\n+ compatibility_map[name] = new[name].IsBackwardCompatible(old[name])\n+ return compatibility_map\n+\n+ def assertBackwardCompatible(self, old_mojom, new_mojom):\n+ compatibility_map = self._GetTypeCompatibilityMap(old_mojom, new_mojom)\n+ for name, compatible in compatibility_map.items():\n+ if not compatible:\n+ raise AssertionError(\n+ 'Given the old mojom:\\n\\n %s\\n\\nand the new mojom:\\n\\n %s\\n\\n'\n+ 'The new definition of %s should pass a backward-compatibiity '\n+ 'check, but it does not.' % (old_mojom, new_mojom, name))\n+\n+ def assertNotBackwardCompatible(self, old_mojom, new_mojom):\n+ compatibility_map = self._GetTypeCompatibilityMap(old_mojom, new_mojom)\n+ if all(compatibility_map.values()):\n+ raise AssertionError(\n+ 'Given the old mojom:\\n\\n %s\\n\\nand the new mojom:\\n\\n %s\\n\\n'\n+ 'The new mojom should fail a backward-compatibility check, but it '\n+ 'does not.' % (old_mojom, new_mojom))\n+\n+ def testNewNonExtensibleEnumValue(self):\n+ \"\"\"Adding a value to a non-extensible enum breaks backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('enum E { kFoo, kBar };',\n+ 'enum E { kFoo, kBar, kBaz };')\n+\n+ def testNewNonExtensibleEnumValueWithMinVersion(self):\n+ \"\"\"Adding a value to a non-extensible enum breaks backward-compatibility,\n+ even with a new [MinVersion] specified for the value.\"\"\"\n+ self.assertNotBackwardCompatible(\n+ 'enum E { kFoo, kBar };', 'enum E { kFoo, kBar, [MinVersion=1] kBaz };')\n+\n+ def testNewValueInExistingVersion(self):\n+ \"\"\"Adding a value to an existing version is not allowed, even if the old\n+ enum was marked [Extensible]. Note that it is irrelevant whether or not the\n+ new enum is marked [Extensible].\"\"\"\n+ self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',\n+ 'enum E { kFoo, kBar, kBaz };')\n+ self.assertNotBackwardCompatible(\n+ '[Extensible] enum E { kFoo, kBar };',\n+ '[Extensible] enum E { kFoo, kBar, kBaz };')\n+ self.assertNotBackwardCompatible(\n+ '[Extensible] enum E { kFoo, [MinVersion=1] kBar };',\n+ 'enum E { kFoo, [MinVersion=1] kBar, [MinVersion=1] kBaz };')\n+\n+ def testEnumValueRemoval(self):\n+ \"\"\"Removal of an enum value is never valid even for [Extensible] enums.\"\"\"\n+ self.assertNotBackwardCompatible('enum E { kFoo, kBar };',\n+ 'enum E { kFoo };')\n+ self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',\n+ '[Extensible] enum E { kFoo };')\n+ self.assertNotBackwardCompatible(\n+ '[Extensible] enum E { kA, [MinVersion=1] kB };',\n+ '[Extensible] enum E { kA, };')\n+ self.assertNotBackwardCompatible(\n+ '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=1] kZ };',\n+ '[Extensible] enum E { kA, [MinVersion=1] kB };')\n+\n+ def testNewExtensibleEnumValueWithMinVersion(self):\n+ \"\"\"Adding a new and properly [MinVersion]'d value to an [Extensible] enum\n+ is a backward-compatible change. Note that it is irrelevant whether or not\n+ the new enum is marked [Extensible].\"\"\"\n+ self.assertBackwardCompatible('[Extensible] enum E { kA, kB };',\n+ 'enum E { kA, kB, [MinVersion=1] kC };')\n+ self.assertBackwardCompatible(\n+ '[Extensible] enum E { kA, kB };',\n+ '[Extensible] enum E { kA, kB, [MinVersion=1] kC };')\n+ self.assertBackwardCompatible(\n+ '[Extensible] enum E { kA, [MinVersion=1] kB };',\n+ '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=2] kC };')\n+\n+ def testRenameEnumValue(self):\n+ \"\"\"Renaming an enum value does not affect backward-compatibility. Only\n+ numeric value is relevant.\"\"\"\n+ self.assertBackwardCompatible('enum E { kA, kB };', 'enum E { kX, kY };')\n+\n+ def testAddEnumValueAlias(self):\n+ \"\"\"Adding new enum fields does not affect backward-compatibility if it does\n+ not introduce any new numeric values.\"\"\"\n+ self.assertBackwardCompatible(\n+ 'enum E { kA, kB };', 'enum E { kA, kB, kC = kA, kD = 1, kE = kD };')\n+\n+ def testEnumIdentity(self):\n+ \"\"\"An unchanged enum is obviously backward-compatible.\"\"\"\n+ self.assertBackwardCompatible('enum E { kA, kB, kC };',\n+ 'enum E { kA, kB, kC };')\n+\n+ def testNewStructFieldUnversioned(self):\n+ \"\"\"Adding a new field to a struct without a new (i.e. higher than any\n+ existing version) [MinVersion] tag breaks backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('struct S { string a; };',\n+ 'struct S { string a; string b; };')\n+\n+ def testStructFieldRemoval(self):\n+ \"\"\"Removing a field from a struct breaks backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('struct S { string a; string b; };',\n+ 'struct S { string a; };')\n+\n+ def testStructFieldTypeChange(self):\n+ \"\"\"Changing the type of an existing field always breaks\n+ backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('struct S { string a; };',\n+ 'struct S { array<int32> a; };')\n+\n+ def testStructFieldBecomingOptional(self):\n+ \"\"\"Changing a field from non-optional to optional breaks\n+ backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('struct S { string a; };',\n+ 'struct S { string? a; };')\n+\n+ def testStructFieldBecomingNonOptional(self):\n+ \"\"\"Changing a field from optional to non-optional breaks\n+ backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('struct S { string? a; };',\n+ 'struct S { string a; };')\n+\n+ def testStructFieldOrderChange(self):\n+ \"\"\"Changing the order of fields breaks backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('struct S { string a; bool b; };',\n+ 'struct S { bool b; string a; };')\n+ self.assertNotBackwardCompatible('struct S { string a@0; bool b@1; };',\n+ 'struct S { string a@1; bool b@0; };')\n+\n+ def testStructFieldMinVersionChange(self):\n+ \"\"\"Changing the MinVersion of a field breaks backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible(\n+ 'struct S { string a; [MinVersion=1] string? b; };',\n+ 'struct S { string a; [MinVersion=2] string? b; };')\n+\n+ def testStructFieldTypeChange(self):\n+ \"\"\"If a struct field's own type definition changes, the containing struct\n+ is backward-compatible if and only if the field type's change is\n+ backward-compatible.\"\"\"\n+ self.assertBackwardCompatible(\n+ 'struct S {}; struct T { S s; };',\n+ 'struct S { [MinVersion=1] int32 x; }; struct T { S s; };')\n+ self.assertBackwardCompatible(\n+ '[Extensible] enum E { kA }; struct S { E e; };',\n+ '[Extensible] enum E { kA, [MinVersion=1] kB }; struct S { E e; };')\n+ self.assertNotBackwardCompatible(\n+ 'struct S {}; struct T { S s; };',\n+ 'struct S { int32 x; }; struct T { S s; };')\n+ self.assertNotBackwardCompatible(\n+ '[Extensible] enum E { kA }; struct S { E e; };',\n+ '[Extensible] enum E { kA, kB }; struct S { E e; };')\n+\n+ def testNewStructFieldWithInvalidMinVersion(self):\n+ \"\"\"Adding a new field using an existing MinVersion breaks backward-\n+ compatibility.\"\"\"\n+ self.assertNotBackwardCompatible(\n+ \"\"\"\\\n+ struct S {\n+ string a;\n+ [MinVersion=1] string? b;\n+ };\n+ \"\"\", \"\"\"\\\n+ struct S {\n+ string a;\n+ [MinVersion=1] string? b;\n+ [MinVersion=1] string? c;\n+ };\"\"\")\n+\n+ def testNewStructFieldWithValidMinVersion(self):\n+ \"\"\"Adding a new field is safe if tagged with a MinVersion greater than any\n+ previously used MinVersion in the struct.\"\"\"\n+ self.assertBackwardCompatible(\n+ 'struct S { int32 a; };',\n+ 'struct S { int32 a; [MinVersion=1] int32 b; };')\n+ self.assertBackwardCompatible(\n+ 'struct S { int32 a; [MinVersion=1] int32 b; };',\n+ 'struct S { int32 a; [MinVersion=1] int32 b; [MinVersion=2] bool c; };')\n+\n+ def testNewStructFieldNullableReference(self):\n+ \"\"\"Adding a new nullable reference-typed field is fine if versioned\n+ properly.\"\"\"\n+ self.assertBackwardCompatible(\n+ 'struct S { int32 a; };',\n+ 'struct S { int32 a; [MinVersion=1] string? b; };')\n+\n+ def testStructFieldRename(self):\n+ \"\"\"Renaming a field has no effect on backward-compatibility.\"\"\"\n+ self.assertBackwardCompatible('struct S { int32 x; bool b; };',\n+ 'struct S { int32 a; bool b; };')\n+\n+ def testStructFieldReorderWithExplicitOrdinals(self):\n+ \"\"\"Reordering fields has no effect on backward-compatibility when field\n+ ordinals are explicitly labeled and remain unchanged.\"\"\"\n+ self.assertBackwardCompatible('struct S { bool b@1; int32 a@0; };',\n+ 'struct S { int32 a@0; bool b@1; };')\n+\n+ def testNewUnionFieldUnversioned(self):\n+ \"\"\"Adding a new field to a union without a new (i.e. higher than any\n+ existing version) [MinVersion] tag breaks backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('union U { string a; };',\n+ 'union U { string a; string b; };')\n+\n+ def testUnionFieldRemoval(self):\n+ \"\"\"Removing a field from a union breaks backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('union U { string a; string b; };',\n+ 'union U { string a; };')\n+\n+ def testUnionFieldTypeChange(self):\n+ \"\"\"Changing the type of an existing field always breaks\n+ backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('union U { string a; };',\n+ 'union U { array<int32> a; };')\n+\n+ def testUnionFieldBecomingOptional(self):\n+ \"\"\"Changing a field from non-optional to optional breaks\n+ backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('union U { string a; };',\n+ 'union U { string? a; };')\n+\n+ def testUnionFieldBecomingNonOptional(self):\n+ \"\"\"Changing a field from optional to non-optional breaks\n+ backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('union U { string? a; };',\n+ 'union U { string a; };')\n+\n+ def testUnionFieldOrderChange(self):\n+ \"\"\"Changing the order of fields breaks backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('union U { string a; bool b; };',\n+ 'union U { bool b; string a; };')\n+ self.assertNotBackwardCompatible('union U { string a@0; bool b@1; };',\n+ 'union U { string a@1; bool b@0; };')\n+\n+ def testUnionFieldMinVersionChange(self):\n+ \"\"\"Changing the MinVersion of a field breaks backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible(\n+ 'union U { string a; [MinVersion=1] string b; };',\n+ 'union U { string a; [MinVersion=2] string b; };')\n+\n+ def testUnionFieldTypeChange(self):\n+ \"\"\"If a union field's own type definition changes, the containing union\n+ is backward-compatible if and only if the field type's change is\n+ backward-compatible.\"\"\"\n+ self.assertBackwardCompatible(\n+ 'struct S {}; union U { S s; };',\n+ 'struct S { [MinVersion=1] int32 x; }; union U { S s; };')\n+ self.assertBackwardCompatible(\n+ '[Extensible] enum E { kA }; union U { E e; };',\n+ '[Extensible] enum E { kA, [MinVersion=1] kB }; union U { E e; };')\n+ self.assertNotBackwardCompatible(\n+ 'struct S {}; union U { S s; };',\n+ 'struct S { int32 x; }; union U { S s; };')\n+ self.assertNotBackwardCompatible(\n+ '[Extensible] enum E { kA }; union U { E e; };',\n+ '[Extensible] enum E { kA, kB }; union U { E e; };')\n+\n+ def testNewUnionFieldWithInvalidMinVersion(self):\n+ \"\"\"Adding a new field using an existing MinVersion breaks backward-\n+ compatibility.\"\"\"\n+ self.assertNotBackwardCompatible(\n+ \"\"\"\\\n+ union U {\n+ string a;\n+ [MinVersion=1] string b;\n+ };\n+ \"\"\", \"\"\"\\\n+ union U {\n+ string a;\n+ [MinVersion=1] string b;\n+ [MinVersion=1] string c;\n+ };\"\"\")\n+\n+ def testNewUnionFieldWithValidMinVersion(self):\n+ \"\"\"Adding a new field is safe if tagged with a MinVersion greater than any\n+ previously used MinVersion in the union.\"\"\"\n+ self.assertBackwardCompatible(\n+ 'union U { int32 a; };',\n+ 'union U { int32 a; [MinVersion=1] int32 b; };')\n+ self.assertBackwardCompatible(\n+ 'union U { int32 a; [MinVersion=1] int32 b; };',\n+ 'union U { int32 a; [MinVersion=1] int32 b; [MinVersion=2] bool c; };')\n+\n+ def testUnionFieldRename(self):\n+ \"\"\"Renaming a field has no effect on backward-compatibility.\"\"\"\n+ self.assertBackwardCompatible('union U { int32 x; bool b; };',\n+ 'union U { int32 a; bool b; };')\n+\n+ def testUnionFieldReorderWithExplicitOrdinals(self):\n+ \"\"\"Reordering fields has no effect on backward-compatibility when field\n+ ordinals are explicitly labeled and remain unchanged.\"\"\"\n+ self.assertBackwardCompatible('union U { bool b@1; int32 a@0; };',\n+ 'union U { int32 a@0; bool b@1; };')\n+\n+ def testNewInterfaceMethodUnversioned(self):\n+ \"\"\"Adding a new method to an interface without a new (i.e. higher than any\n+ existing version) [MinVersion] tag breaks backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('interface F { A(); };',\n+ 'interface F { A(); B(); };')\n+\n+ def testInterfaceMethodRemoval(self):\n+ \"\"\"Removing a method from an interface breaks backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('interface F { A(); B(); };',\n+ 'interface F { A(); };')\n+\n+ def testInterfaceMethodParamsChanged(self):\n+ \"\"\"Changes to the parameter list are only backward-compatible if they meet\n+ backward-compatibility requirements of an equivalent struct definition.\"\"\"\n+ self.assertNotBackwardCompatible('interface F { A(); };',\n+ 'interface F { A(int32 x); };')\n+ self.assertNotBackwardCompatible('interface F { A(int32 x); };',\n+ 'interface F { A(bool x); };')\n+ self.assertNotBackwardCompatible(\n+ 'interface F { A(int32 x, [MinVersion=1] string? s); };', \"\"\"\\\n+ interface F {\n+ A(int32 x, [MinVersion=1] string? s, [MinVersion=1] int32 y);\n+ };\"\"\")\n+\n+ self.assertBackwardCompatible('interface F { A(int32 x); };',\n+ 'interface F { A(int32 a); };')\n+ self.assertBackwardCompatible(\n+ 'interface F { A(int32 x); };',\n+ 'interface F { A(int32 x, [MinVersion=1] string? s); };')\n+\n+ self.assertBackwardCompatible(\n+ 'struct S {}; interface F { A(S s); };',\n+ 'struct S { [MinVersion=1] int32 x; }; interface F { A(S s); };')\n+ self.assertBackwardCompatible(\n+ 'struct S {}; struct T {}; interface F { A(S s); };',\n+ 'struct S {}; struct T {}; interface F { A(T s); };')\n+ self.assertNotBackwardCompatible(\n+ 'struct S {}; struct T { int32 x; }; interface F { A(S s); };',\n+ 'struct S {}; struct T { int32 x; }; interface F { A(T t); };')\n+\n+ def testInterfaceMethodReplyAdded(self):\n+ \"\"\"Adding a reply to a message breaks backward-compatibilty.\"\"\"\n+ self.assertNotBackwardCompatible('interface F { A(); };',\n+ 'interface F { A() => (); };')\n+\n+ def testInterfaceMethodReplyRemoved(self):\n+ \"\"\"Removing a reply from a message breaks backward-compatibility.\"\"\"\n+ self.assertNotBackwardCompatible('interface F { A() => (); };',\n+ 'interface F { A(); };')\n+\n+ def testInterfaceMethodReplyParamsChanged(self):\n+ \"\"\"Similar to request parameters, a change to reply parameters is considered\n+ backward-compatible if it meets the same backward-compatibility\n+ requirements imposed on equivalent struct changes.\"\"\"\n+ self.assertNotBackwardCompatible('interface F { A() => (); };',\n+ 'interface F { A() => (int32 x); };')\n+ self.assertNotBackwardCompatible('interface F { A() => (int32 x); };',\n+ 'interface F { A() => (); };')\n+ self.assertNotBackwardCompatible('interface F { A() => (bool x); };',\n+ 'interface F { A() => (int32 x); };')\n+\n+ self.assertBackwardCompatible('interface F { A() => (int32 a); };',\n+ 'interface F { A() => (int32 x); };')\n+ self.assertBackwardCompatible(\n+ 'interface F { A() => (int32 x); };',\n+ 'interface F { A() => (int32 x, [MinVersion] string? s); };')\n+\n+ def testNewInterfaceMethodWithInvalidMinVersion(self):\n+ \"\"\"Adding a new method to an existing version is not backward-compatible.\"\"\"\n+ self.assertNotBackwardCompatible(\n+ \"\"\"\\\n+ interface F {\n+ A();\n+ [MinVersion=1] B();\n+ };\n+ \"\"\", \"\"\"\\\n+ interface F {\n+ A();\n+ [MinVersion=1] B();\n+ [MinVersion=1] C();\n+ };\n+ \"\"\")\n+\n+ def testNewInterfaceMethodWithValidMinVersion(self):\n+ \"\"\"Adding a new method is fine as long as its MinVersion exceeds that of any\n+ method on the old interface definition.\"\"\"\n+ self.assertBackwardCompatible('interface F { A(); };',\n+ 'interface F { A(); [MinVersion=1] B(); };')\ndiff --git a/utils/ipc/mojo/public/tools/run_all_python_unittests.py b/utils/ipc/mojo/public/tools/run_all_python_unittests.py\nnew file mode 100755\nindex 00000000..b2010958\n--- /dev/null\n+++ b/utils/ipc/mojo/public/tools/run_all_python_unittests.py\n@@ -0,0 +1,28 @@\n+#!/usr/bin/env python\n+# Copyright 2020 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+import os.path\n+import sys\n+\n+_TOOLS_DIR = os.path.dirname(__file__)\n+_MOJOM_DIR = os.path.join(_TOOLS_DIR, 'mojom')\n+_SRC_DIR = os.path.join(_TOOLS_DIR, os.path.pardir, os.path.pardir,\n+ os.path.pardir)\n+\n+# Ensure that the mojom library is discoverable.\n+sys.path.append(_MOJOM_DIR)\n+\n+# Help Python find typ in //third_party/catapult/third_party/typ/\n+sys.path.append(\n+ os.path.join(_SRC_DIR, 'third_party', 'catapult', 'third_party', 'typ'))\n+import typ\n+\n+\n+def Main():\n+ return typ.main(top_level_dir=_MOJOM_DIR)\n+\n+\n+if __name__ == '__main__':\n+ sys.exit(Main())\ndiff --git a/utils/ipc/tools/diagnosis/crbug_1001171.py b/utils/ipc/tools/diagnosis/crbug_1001171.py\nnew file mode 100644\nindex 00000000..478fb8c1\n--- /dev/null\n+++ b/utils/ipc/tools/diagnosis/crbug_1001171.py\n@@ -0,0 +1,51 @@\n+# Copyright 2019 The Chromium Authors. All rights reserved.\n+# Use of this source code is governed by a BSD-style license that can be\n+# found in the LICENSE file.\n+\n+\"\"\"Helper context wrapper for diagnosing crbug.com/1001171.\n+\n+This module and all uses thereof can and should be removed once\n+crbug.com/1001171 has been resolved.\n+\"\"\"\n+\n+from __future__ import print_function\n+\n+import contextlib\n+import os\n+import sys\n+\n+\n+@contextlib.contextmanager\n+def DumpStateOnLookupError():\n+ \"\"\"Prints potentially useful state info in the event of a LookupError.\"\"\"\n+ try:\n+ yield\n+ except LookupError:\n+ print('LookupError diagnosis for crbug.com/1001171:')\n+ for path_index, path_entry in enumerate(sys.path):\n+ desc = 'unknown'\n+ if not os.path.exists(path_entry):\n+ desc = 'missing'\n+ elif os.path.islink(path_entry):\n+ desc = 'link -> %s' % os.path.realpath(path_entry)\n+ elif os.path.isfile(path_entry):\n+ desc = 'file'\n+ elif os.path.isdir(path_entry):\n+ desc = 'dir'\n+ print(' sys.path[%d]: %s (%s)' % (path_index, path_entry, desc))\n+\n+ real_path_entry = os.path.realpath(path_entry)\n+ if (path_entry.endswith(os.path.join('lib', 'python2.7'))\n+ and os.path.isdir(real_path_entry)):\n+ encodings_dir = os.path.realpath(\n+ os.path.join(real_path_entry, 'encodings'))\n+ if os.path.exists(encodings_dir):\n+ if os.path.isdir(encodings_dir):\n+ print(' %s contents: %s' % (encodings_dir,\n+ str(os.listdir(encodings_dir))))\n+ else:\n+ print(' %s exists but is not a directory' % encodings_dir)\n+ else:\n+ print(' %s missing' % encodings_dir)\n+\n+ raise\n", "prefixes": [ "libcamera-devel", "02/23" ] }