Skip to content

Commit

Permalink
Generate rudimentary Rust mojom bindings
Browse files Browse the repository at this point in the history
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}
  • Loading branch information
chbaker0 authored and Chromium LUCI CQ committed Feb 3, 2023
1 parent 0efb0a0 commit aa78626
Show file tree
Hide file tree
Showing 14 changed files with 189 additions and 8 deletions.
12 changes: 9 additions & 3 deletions build/rust/rust_target.gni
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,13 @@ template("rust_target") {
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
Expand Down Expand Up @@ -348,9 +355,7 @@ template("rust_target") {
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
Expand All @@ -369,6 +374,7 @@ template("rust_target") {
"_crate_root",
"_crate_name",
"_metadata",
"_test_deps",
])
}
}
Expand Down
11 changes: 6 additions & 5 deletions build/toolchain/gcc_toolchain.gni
Original file line number Diff line number Diff line change
Expand Up @@ -710,14 +710,15 @@ template("single_gcc_toolchain") {
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") {
rust_outfile = "{{output_dir}}/{{target_output_name}}.a"
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 ]
Expand All @@ -730,7 +731,7 @@ template("single_gcc_toolchain") {
# 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 ]
Expand All @@ -741,7 +742,7 @@ template("single_gcc_toolchain") {
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 ]
Expand All @@ -752,7 +753,7 @@ template("single_gcc_toolchain") {
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 ]
Expand All @@ -763,7 +764,7 @@ template("single_gcc_toolchain") {
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 ]
Expand Down
5 changes: 5 additions & 0 deletions mojo/public/rust/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ rust_static_library("mojo_rust") {
"//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]) ]
Expand Down
3 changes: 3 additions & 0 deletions mojo/public/rust/bindings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ pub mod encoding;
pub mod message;
pub mod mojom;
pub mod run_loop;

#[cfg(test)]
mod tests;
18 changes: 18 additions & 0 deletions mojo/public/rust/bindings/tests/basic_unittest.rs
Original file line number Diff line number Diff line change
@@ -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 };
}
5 changes: 5 additions & 0 deletions mojo/public/rust/bindings/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
3 changes: 3 additions & 0 deletions mojo/public/tools/bindings/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ action("precompile_templates") {
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl",
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl",
"$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl",
"$mojom_generator_root/generators/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",
Expand All @@ -105,6 +107,7 @@ action("precompile_templates") {
"$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 = [
Expand Down
3 changes: 3 additions & 0 deletions mojo/public/tools/bindings/generators/OWNERS
Original file line number Diff line number Diff line change
@@ -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
67 changes: 67 additions & 0 deletions mojo/public/tools/bindings/generators/mojom_rust_generator.py
Original file line number Diff line number Diff line change
@@ -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")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
file://build/rust/OWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{%- for struct in module.structs %}
{% include "struct.tmpl" %}
{% endfor %}
Original file line number Diff line number Diff line change
@@ -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 %}
}
59 changes: 59 additions & 0 deletions mojo/public/tools/bindings/mojom.gni
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import("//build/config/chromecast_build.gni")
import("//build/config/chromeos/ui_mode.gni")
import("//build/config/features.gni")
import("//build/config/nacl/config.gni")
import("//build/config/rust.gni")
import("//build/toolchain/kythe.gni")
import("//components/nacl/features.gni")
import("//third_party/jinja2/jinja2.gni")
Expand All @@ -44,6 +45,9 @@ declare_args() {
# 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.
Expand Down Expand Up @@ -124,6 +128,11 @@ mojom_generator_sources =
"$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
Expand Down Expand Up @@ -1622,6 +1631,51 @@ template("mojom") {
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
Expand Down Expand Up @@ -1649,6 +1703,11 @@ template("mojom") {
":$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 {
Expand Down
1 change: 1 addition & 0 deletions mojo/public/tools/bindings/mojom_bindings_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def _GetDirAbove(dirname):
"javascript": "mojom_js_generator",
"java": "mojom_java_generator",
"mojolpm": "mojom_mojolpm_generator",
"rust": "mojom_rust_generator",
"typescript": "mojom_ts_generator",
}

Expand Down

0 comments on commit aa78626

Please sign in to comment.