Generate rudimentary Rust mojom bindings
Create a mojom binding generator for Rust. The generated bindings are
not useful yet, and only even compile for basic structs. However, this
is a good starting point for iteration.
A simple test in Rust is added to check that the Rect bindings build
and have the expected fields.
Bug: 1274864
Change-Id: I4107e26c50d2ddb36067a0238d7d2e72aeecd9d2
Cq-Include-Trybots: luci.chromium.try:android-rust-arm-dbg,android-rust-arm-rel,linux-rust-x64-dbg,linux-rust-x64-rel
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4198179
Auto-Submit: Collin Baker <collinbaker@chromium.org>
Reviewed-by: Hans Wennborg <hans@chromium.org>
Commit-Queue: Hans Wennborg <hans@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1100917}
diff --git a/build/rust/rust_target.gni b/build/rust/rust_target.gni
index eaa318f..c25d7d32 100644
--- a/build/rust/rust_target.gni
+++ b/build/rust/rust_target.gni
@@ -156,6 +156,13 @@
invoker.build_native_rust_unit_tests && can_build_rust_unit_tests
}
+ _test_deps = []
+ if (_build_unit_tests && defined(invoker.test_deps)) {
+ _test_deps = invoker.test_deps
+ } else if (defined(invoker.test_deps)) {
+ not_needed(invoker, [ "test_deps" ])
+ }
+
# Declares that the Rust crate generates bindings between C++ and Rust via the
# Cxx crate. It may generate C++ headers and/or use the cxx crate macros to
# generate Rust code internally, depending on what bindings are declared. If
@@ -348,9 +355,7 @@
deps = _rust_deps + _public_deps
aliased_deps = _rust_aliased_deps
public_deps = [ ":${_target_name}" ]
- if (defined(invoker.test_deps)) {
- deps += invoker.test_deps
- }
+ deps += _test_deps
inputs = []
if (defined(invoker.inputs)) {
inputs += invoker.inputs
@@ -369,6 +374,7 @@
"_crate_root",
"_crate_name",
"_metadata",
+ "_test_deps",
])
}
}
diff --git a/build/toolchain/gcc_toolchain.gni b/build/toolchain/gcc_toolchain.gni
index 713c1b9..374b3fe4 100644
--- a/build/toolchain/gcc_toolchain.gni
+++ b/build/toolchain/gcc_toolchain.gni
@@ -710,6 +710,7 @@
rustc = "$rust_compiler_prefix${rustc_bin}"
rust_sysroot_relative_to_out = rebase_path(rust_sysroot, root_out_dir)
rustc_wrapper = rebase_path("//build/rust/rustc_wrapper.py")
+ gen_dir = rebase_path(root_gen_dir)
# RSP manipulation due to https://bugs.chromium.org/p/gn/issues/detail?id=249
tool("rust_staticlib") {
@@ -717,7 +718,7 @@
depfile = "{{output}}.d"
rspfile = "$rust_outfile.rsp"
rspfile_content = "{{rustdeps}} {{externs}}"
- command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile --rsp=$rspfile -- -Clinker=\"${invoker.cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS RUSTENV {{rustenv}}"
+ command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile --rsp=$rspfile -- -Clinker=\"${invoker.cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS RUSTENV GEN_DIR=\"$gen_dir\" {{rustenv}}"
description = "RUST $rust_outfile"
rust_sysroot = rust_sysroot_relative_to_out
outputs = [ rust_outfile ]
@@ -730,7 +731,7 @@
# Do not use rsp files in this (common) case because they occupy the
# ninja main thread, and {{rlibs}} have shorter command lines than
# fully linked targets.
- command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${invoker.cxx}\" $rustc_common_args {{rustdeps}} {{externs}} --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS RUSTENV {{rustenv}}"
+ command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile -- -Clinker=\"${invoker.cxx}\" $rustc_common_args {{rustdeps}} {{externs}} --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS RUSTENV GEN_DIR=\"$gen_dir\" {{rustenv}}"
description = "RUST $rust_outfile"
rust_sysroot = rust_sysroot_relative_to_out
outputs = [ rust_outfile ]
@@ -741,7 +742,7 @@
depfile = "{{output}}.d"
rspfile = "$rust_outfile.rsp"
rspfile_content = "{{rustdeps}} {{externs}}"
- command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile --rsp=$rspfile -- -Clinker=\"${invoker.cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} ${extra_ldflags} RUSTENV {{rustenv}}"
+ command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile --rsp=$rspfile -- -Clinker=\"${invoker.cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} ${extra_ldflags} RUSTENV GEN_DIR=\"$gen_dir\" {{rustenv}}"
description = "RUST $rust_outfile"
rust_sysroot = rust_sysroot_relative_to_out
outputs = [ rust_outfile ]
@@ -752,7 +753,7 @@
depfile = "{{output}}.d"
rspfile = "$rust_outfile.rsp"
rspfile_content = "{{rustdeps}} {{externs}}"
- command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile --rsp=$rspfile -- -Clinker=\"${invoker.cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} ${extra_ldflags} RUSTENV {{rustenv}}"
+ command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile --rsp=$rspfile -- -Clinker=\"${invoker.cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} ${extra_ldflags} RUSTENV GEN_DIR=\"$gen_dir\" {{rustenv}}"
description = "RUST $rust_outfile"
rust_sysroot = rust_sysroot_relative_to_out
outputs = [ rust_outfile ]
@@ -763,7 +764,7 @@
depfile = "{{output}}.d"
rspfile = "$rust_outfile.rsp"
rspfile_content = "{{rustdeps}} {{externs}}"
- command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile --rsp=$rspfile -- -Clinker=\"${invoker.cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} ${extra_ldflags} RUSTENV {{rustenv}}"
+ command = "$python_path \"$rustc_wrapper\" --rustc=$rustc --depfile=$depfile --rsp=$rspfile -- -Clinker=\"${invoker.cxx}\" $rustc_common_args --emit=dep-info=$depfile,link -o $rust_outfile LDFLAGS {{ldflags}} ${extra_ldflags} RUSTENV GEN_DIR=\"$gen_dir\" {{rustenv}}"
description = "RUST $rust_outfile"
rust_sysroot = rust_sysroot_relative_to_out
outputs = [ rust_outfile ]
diff --git a/mojo/public/rust/BUILD.gn b/mojo/public/rust/BUILD.gn
index f9a96d99..3645073 100644
--- a/mojo/public/rust/BUILD.gn
+++ b/mojo/public/rust/BUILD.gn
@@ -85,6 +85,11 @@
"//third_party/rust/bitflags/v1:lib",
]
+ # TODO(crbug.com/1274864): find a better way to reference generated bindings.
+ # Right now a gen/ file is `include!`ed directly in Rust, meaning there's no
+ # way to detect dependency issues.
+ test_deps = [ "//mojo/public/interfaces/bindings/tests:test_interfaces" ]
+
bindgen_output = get_target_outputs(":mojo_c_system_binding")
inputs = bindgen_output
rustenv = [ "BINDGEN_RS_FILE=" + rebase_path(bindgen_output[0]) ]
diff --git a/mojo/public/rust/bindings/mod.rs b/mojo/public/rust/bindings/mod.rs
index e90fb52..61cb583 100644
--- a/mojo/public/rust/bindings/mod.rs
+++ b/mojo/public/rust/bindings/mod.rs
@@ -11,3 +11,6 @@
pub mod message;
pub mod mojom;
pub mod run_loop;
+
+#[cfg(test)]
+mod tests;
diff --git a/mojo/public/rust/bindings/tests/basic_unittest.rs b/mojo/public/rust/bindings/tests/basic_unittest.rs
new file mode 100644
index 0000000..406338a8bf
--- /dev/null
+++ b/mojo/public/rust/bindings/tests/basic_unittest.rs
@@ -0,0 +1,18 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Simply test that we can compile some generated Rust struct bindings and that
+//! they have the expected fields.
+
+// TODO(crbug.com/1274864): find a better way to reference generated bindings.
+// This is not sustainable, but it's sufficient for a single test while
+// prototyping.
+mod rect_mojom {
+ include!(concat!(env!("GEN_DIR"), "/mojo/public/interfaces/bindings/tests/rect.mojom.rs"));
+}
+
+#[test]
+fn basic_struct_test() {
+ let _rect = rect_mojom::Rect { x: 1, y: 1, width: 1, height: 1 };
+}
diff --git a/mojo/public/rust/bindings/tests/mod.rs b/mojo/public/rust/bindings/tests/mod.rs
new file mode 100644
index 0000000..d40dfe2
--- /dev/null
+++ b/mojo/public/rust/bindings/tests/mod.rs
@@ -0,0 +1,5 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+pub mod basic_unittest;
diff --git a/mojo/public/tools/bindings/BUILD.gn b/mojo/public/tools/bindings/BUILD.gn
index 59700cb..3bf2e32d 100644
--- a/mojo/public/tools/bindings/BUILD.gn
+++ b/mojo/public/tools/bindings/BUILD.gn
@@ -90,6 +90,8 @@
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl",
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl",
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl",
+ "$mojom_generator_root/generators/rust_templates/module.tmpl",
+ "$mojom_generator_root/generators/rust_templates/struct.tmpl",
"$mojom_generator_root/generators/ts_templates/enum_definition.tmpl",
"$mojom_generator_root/generators/ts_templates/interface_definition.tmpl",
"$mojom_generator_root/generators/ts_templates/module_definition.tmpl",
@@ -105,6 +107,7 @@
"$target_gen_dir/js_interface_binder_templates.zip",
"$target_gen_dir/js_templates.zip",
"$target_gen_dir/mojolpm_templates.zip",
+ "$target_gen_dir/rust_templates.zip",
"$target_gen_dir/ts_templates.zip",
]
args = [
diff --git a/mojo/public/tools/bindings/generators/OWNERS b/mojo/public/tools/bindings/generators/OWNERS
index fb33ab8..170c0f8 100644
--- a/mojo/public/tools/bindings/generators/OWNERS
+++ b/mojo/public/tools/bindings/generators/OWNERS
@@ -1,2 +1,5 @@
# TypeScript bindings generator
per-file mojom_ts_generator.py=rbpotter@chromium.org
+
+# Rust bindings generator
+per-file mojom_rust_generator.py=file://build/rust/OWNERS
diff --git a/mojo/public/tools/bindings/generators/mojom_rust_generator.py b/mojo/public/tools/bindings/generators/mojom_rust_generator.py
new file mode 100644
index 0000000..238ddbb24
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/mojom_rust_generator.py
@@ -0,0 +1,67 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import mojom.generate.generator as generator
+import mojom.generate.module as mojom
+import mojom.generate.pack as pack
+from mojom.generate.template_expander import UseJinja, UseJinjaForImportedTemplate
+
+_kind_to_rust_type = {
+ mojom.BOOL: "bool",
+ mojom.INT8: "i8",
+ mojom.INT16: "i16",
+ mojom.INT32: "i32",
+ mojom.INT64: "i64",
+ mojom.UINT8: "u8",
+ mojom.UINT16: "u16",
+ mojom.UINT32: "u32",
+ mojom.UINT64: "u64",
+ mojom.FLOAT: "f32",
+ mojom.DOUBLE: "f64",
+}
+
+
+class Generator(generator.Generator):
+ def __init__(self, *args, **kwargs):
+ super(Generator, self).__init__(*args, **kwargs)
+
+ def _GetNameForKind(self, kind):
+ "::".join(kind.module.namespace.split("."))
+
+ def _GetRustFieldType(self, kind):
+ if mojom.IsEnumKind(kind) or mojom.IsStructKind(kind) or mojom.IsUnionKind(
+ kind):
+ return self._GetNameForKind(kind)
+ if mojom.IsArrayKind(kind):
+ return f"Vec<{self._GetRustFieldType(kind.kind)}>"
+ if mojom.IsMapKind(kind):
+ return (f"HashMap<"
+ f"{self._GetRustFieldType(kind.key_kind)}, "
+ f"{self._GetRustFieldType(kind.value_kind)}>")
+ if mojom.IsStringKind(kind):
+ return "String"
+ if mojom.IsAnyHandleKind(kind):
+ return "::mojo::UntypedHandle"
+ if mojom.IsReferenceKind(kind):
+ return "usize"
+ return _kind_to_rust_type[kind]
+
+ @staticmethod
+ def GetTemplatePrefix():
+ return "rust_templates"
+
+ def GetFilters(self):
+ rust_filters = {
+ "rust_field_type": self._GetRustFieldType,
+ }
+ return rust_filters
+
+ @UseJinja("module.tmpl")
+ def _GenerateModule(self):
+ return {"module": self.module}
+
+ def GenerateFiles(self, args):
+ self.module.Stylize(generator.Stylizer())
+
+ self.WriteWithComment(self._GenerateModule(), f"{self.module.path}.rs")
diff --git a/mojo/public/tools/bindings/generators/rust_templates/OWNERS b/mojo/public/tools/bindings/generators/rust_templates/OWNERS
new file mode 100644
index 0000000..ec1b1412
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/rust_templates/OWNERS
@@ -0,0 +1 @@
+file://build/rust/OWNERS
diff --git a/mojo/public/tools/bindings/generators/rust_templates/module.tmpl b/mojo/public/tools/bindings/generators/rust_templates/module.tmpl
new file mode 100644
index 0000000..46ccdbb
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/rust_templates/module.tmpl
@@ -0,0 +1,3 @@
+{%- for struct in module.structs %}
+{% include "struct.tmpl" %}
+{% endfor %}
diff --git a/mojo/public/tools/bindings/generators/rust_templates/struct.tmpl b/mojo/public/tools/bindings/generators/rust_templates/struct.tmpl
new file mode 100644
index 0000000..bc2edd9e
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/rust_templates/struct.tmpl
@@ -0,0 +1,6 @@
+#[derive(Debug)]
+pub struct {{struct.name}} {
+{%- for packed_field in struct.packed.packed_fields %}
+ pub {{packed_field.field.name}}: {{packed_field.field.kind|rust_field_type}},
+{%- endfor %}
+}
diff --git a/mojo/public/tools/bindings/mojom.gni b/mojo/public/tools/bindings/mojom.gni
index 43ff9f1..9c7454e 100644
--- a/mojo/public/tools/bindings/mojom.gni
+++ b/mojo/public/tools/bindings/mojom.gni
@@ -18,6 +18,7 @@
import("//build/config/chromeos/ui_mode.gni")
import("//build/config/features.gni")
import("//build/config/nacl/config.gni")
+import("//build/config/rust.gni")
import("//build/toolchain/kythe.gni")
import("//components/nacl/features.gni")
import("//third_party/jinja2/jinja2.gni")
@@ -44,6 +45,9 @@
# MojoLPM fuzzer targets. Off by default.
enable_mojom_fuzzer = false
+ # Enables generating mojom bindings for Rust targets.
+ enable_rust_bindings = enable_rust
+
# Enables Closure compilation of generated JS lite bindings. In environments
# where compilation is supported, any mojom target "foo" will also have a
# corresponding "foo_js_library_for_compile" target generated.
@@ -124,6 +128,11 @@
"$mojom_generator_script",
]
+if (enable_rust) {
+ mojom_generator_sources +=
+ [ "$mojom_generator_root/generators/mojom_rust_generator.py" ]
+}
+
if (enable_scrambled_message_ids) {
declare_args() {
# The path to a file whose contents can be used as the basis for a message
@@ -1622,6 +1631,51 @@
sources_target_name = output_target_name
}
+ if (enable_rust_bindings) {
+ rust_generator_target_name = "${output_target_name}_rust__generator"
+
+ if (sources_list != []) {
+ action(rust_generator_target_name) {
+ script = mojom_generator_script
+ inputs = mojom_generator_sources + jinja2_sources
+ sources = sources_list
+
+ deps = [
+ ":$parser_target_name",
+ "//mojo/public/tools/bindings:precompile_templates",
+ ]
+ if (defined(invoker.parser_deps)) {
+ deps += invoker.parser_deps
+ }
+
+ args = common_generator_args
+ filelist = []
+ outputs = []
+ foreach(source, sources_list) {
+ filelist += [ rebase_path("$source", root_build_dir) ]
+ }
+ foreach(base_path, output_file_base_paths) {
+ outputs += [ "$root_gen_dir/${base_path}${variant_suffix}.rs" ]
+ }
+
+ response_file_contents = filelist
+
+ args += [
+ "--filelist={{response_file_name}}",
+ "-g",
+ "rust",
+ ]
+ }
+ } else {
+ group(rust_generator_target_name) {
+ }
+ }
+
+ group("${output_target_name}_rust") {
+ deps = [ ":$rust_generator_target_name" ]
+ }
+ }
+
target(sources_target_type, sources_target_name) {
if (defined(output_name_override)) {
output_name = output_name_override
@@ -1649,6 +1703,11 @@
":$shared_cpp_library_target_name",
"//base",
]
+
+ if (enable_rust_bindings) {
+ deps += [ ":${output_target_name}_rust" ]
+ }
+
if (require_full_cpp_deps) {
public_deps += [ "//mojo/public/cpp/bindings" ]
} else {
diff --git a/mojo/public/tools/bindings/mojom_bindings_generator.py b/mojo/public/tools/bindings/mojom_bindings_generator.py
index 80cbf46..7edfcca 100755
--- a/mojo/public/tools/bindings/mojom_bindings_generator.py
+++ b/mojo/public/tools/bindings/mojom_bindings_generator.py
@@ -55,6 +55,7 @@
"javascript": "mojom_js_generator",
"java": "mojom_java_generator",
"mojolpm": "mojom_mojolpm_generator",
+ "rust": "mojom_rust_generator",
"typescript": "mojom_ts_generator",
}