Support C++ and Rust interop with autocxx

Change-Id: Id2c852bf48ed58d6284e7ab8a388ac38419fc474
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
diff --git a/aos/README.md b/aos/README.md
index b99f51d..356c259 100644
--- a/aos/README.md
+++ b/aos/README.md
@@ -14,6 +14,46 @@
 aos_graph_nodes | dot -Tx11
 ```
 
+### Rust
+
+AOS has experimental rust support. This involves creating Rust wrappers for all of the relevant
+C++ types. There must be exactly one wrapper for each type, or you will get confusing errors about
+trying to convert Rust types with very similar names (in different crates though) when you try
+using them together. To standardize this, we have some conventions.
+
+We use autocxx to generate the raw wrappers. Sometimes autocxx needs tweaked C++ signatures to
+generate usable Rust bindings. These go in a separate C++ file with a `_for_rust` suffix, and have
+functions with `ForRust` suffixes.
+
+We want to pass around pointers and references to the autocxx-generated flatbuffers types so we can
+create byte slices to use with the Rust versions, but we ignore many of the flatbuffers types needed
+to wrap individual methods. Some of them are tricky to wrap.
+
+Converting between the autocxx-generated and rustc-generated flatbuffers types is tricky. The Rust
+flatbuffers API is based on slices, but the C++ API that autocxx is wrapping just uses pointers. We
+can convert from a Rust flatbuffer to its C++ equivalent pretty easily, but going the other way
+doesn't work. To maximize flexibility, each C++ wrapper module exposes APIs that take
+autocxx-generated types and provide convenient conversions for the types belonging to that module.
+Flatbuffers returned from C++ by value (typically in a `aos::Flatbuffer`) get returned as Rust
+`aos_flatbuffers::Flatbuffer` objects, while ones being returned from C++ by pointer (or reference)
+are exposed as the autocxx types.
+
+For the file `aos/xyz.fbs`, Rust wrappers go in `aos/xyz.rs`. The Rust flatbuffers generated
+code will be in `aos/xyz_fbs.rs`.
+
+For the file `aos/abc.h`, Rust wrappers go in `aos/abc.rs`. These wrappers may be more sophisticated
+than simple unsafe wrappers, but they should avoid adding additional functionality. Any additional
+C++ code goes in `aos/abc_for_rust.h`/`aos/abc_for_rust.cc`.
+
+All Rust functions intended to be called from other files gets exported outside of the `ffi`
+module. In some cases, this is just giving the raw autocxx wrappers a new name. In other cases,
+these wrappers can attach lifetimes etc and be safe. This makes it clear which functions and
+types are being exported, because autocxx generates a lot of wrappers. Do not just make the
+entire `ffi` module, or any of its submodules, public.
+
+Rust modules map to Bazel rules. This means we end up lots of Rust modules. We name them like
+`aos_events_event_loop` for all the code in `aos/events/event_loop.rs`.
+
 ### NOTES
 
 Some functions need to be in separate translation units in order for them to be guaranteed to work. As the C standard says,
diff --git a/build_tests/BUILD b/build_tests/BUILD
index dc76f45..e7f236a 100644
--- a/build_tests/BUILD
+++ b/build_tests/BUILD
@@ -5,6 +5,7 @@
 load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")
 load("@npm//@bazel/typescript:index.bzl", "ts_library")
 load("@npm//@bazel/concatjs:index.bzl", "karma_web_test_suite")
+load("//tools/build_rules:autocxx.bzl", "autocxx_library")
 
 cc_test(
     name = "gflags_build_test",
@@ -119,7 +120,11 @@
 
 go_library(
     name = "build_tests_lib",
-    srcs = ["hello.go"],
+    srcs = [
+        "hello.go",
+        # Not sure why gazelle wants this here?
+        "hello_autocxx.h",
+    ],
     importpath = "github.com/frc971/971-Robot-Code/build_tests",
     target_compatible_with = ["@platforms//cpu:x86_64"],
     visibility = ["//visibility:private"],
@@ -191,3 +196,27 @@
     target_compatible_with = ["@platforms//os:linux"],
     deps = [":rust_in_cc_rs"],
 )
+
+cc_library(
+    name = "hello_autocxx_cc",
+    hdrs = [
+        "hello_autocxx.h",
+    ],
+)
+
+autocxx_library(
+    name = "hello_autocxx",
+    srcs = ["hello_autocxx.rs"],
+    libs = [":hello_autocxx_cc"],
+    override_cc_toolchain = "@llvm_toolchain//:cc-clang-x86_64-linux",
+    target_compatible_with = ["//tools/platforms/rust:has_support"],
+)
+
+rust_test(
+    name = "hello_autocxx_test",
+    crate = ":hello_autocxx",
+    # TODO: Make Rust play happy with pic vs nopic. Details at:
+    # https://github.com/bazelbuild/rules_rust/issues/118
+    rustc_flags = ["-Crelocation-model=static"],
+    target_compatible_with = ["//tools/platforms/rust:has_support"],
+)
diff --git a/build_tests/hello_autocxx.h b/build_tests/hello_autocxx.h
new file mode 100644
index 0000000..8b6e285
--- /dev/null
+++ b/build_tests/hello_autocxx.h
@@ -0,0 +1,8 @@
+#ifndef BUILD_TESTS_HELLO_AUTOCXX_H_
+#define BUILD_TESTS_HELLO_AUTOCXX_H_
+
+#include <stdlib.h>
+
+int plain_function() { return 971; }
+
+#endif  // BUILD_TESTS_HELLO_AUTOCXX_H_
diff --git a/build_tests/hello_autocxx.rs b/build_tests/hello_autocxx.rs
new file mode 100644
index 0000000..483f8be
--- /dev/null
+++ b/build_tests/hello_autocxx.rs
@@ -0,0 +1,16 @@
+use autocxx::include_cpp;
+
+include_cpp! (
+#include "build_tests/hello_autocxx.h"
+generate!("plain_function")
+);
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_plain_function() {
+        assert_eq!(autocxx::c_int::from(971), unsafe { ffi::plain_function() });
+    }
+}
diff --git a/third_party/autocxx/BUILD b/third_party/autocxx/BUILD
new file mode 100644
index 0000000..8a26c11
--- /dev/null
+++ b/third_party/autocxx/BUILD
@@ -0,0 +1,32 @@
+load("@rules_rust//rust:defs.bzl", "rust_library")
+
+package(default_visibility = ["//visibility:public"])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+rust_library(
+    name = "autocxx",
+    srcs = glob(["**/*.rs"]),
+    compile_data = ["README.md"],
+    crate_root = "src/lib.rs",
+    edition = "2021",
+    proc_macro_deps = [
+        "//third_party/cargo:aquamarine",
+        "//third_party/autocxx/macro:autocxx_macro",
+    ],
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=autocxx",
+        "manual",
+    ],
+    version = "0.16.0",
+    deps = [
+        "//third_party/cargo:cxx",
+        "//third_party/cargo:moveit",
+    ],
+)
diff --git a/third_party/autocxx/engine/BUILD b/third_party/autocxx/engine/BUILD
new file mode 100644
index 0000000..b3335d2
--- /dev/null
+++ b/third_party/autocxx/engine/BUILD
@@ -0,0 +1,51 @@
+load("@rules_rust//rust:defs.bzl", "rust_library")
+
+package(default_visibility = ["//visibility:public"])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+rust_library(
+    name = "autocxx_engine",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+        "default",
+        "reproduction_case",
+        "serde_json",
+    ],
+    crate_root = "src/lib.rs",
+    edition = "2021",
+    proc_macro_deps = [
+        "//third_party/cargo:indoc",
+        "//third_party/cargo:aquamarine",
+        "//third_party/cargo:strum_macros",
+    ],
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=autocxx-engine",
+        "manual",
+    ],
+    version = "0.16.0",
+    deps = [
+        "//third_party/autocxx/parser:autocxx_parser",
+        "//third_party/cargo:autocxx_bindgen",
+        "//third_party/cargo:cxx_gen",
+        "//third_party/cargo:indexmap",
+        "//third_party/cargo:itertools",
+        "//third_party/cargo:log",
+        "//third_party/cargo:miette",
+        "//third_party/cargo:once_cell",
+        "//third_party/cargo:proc_macro2",
+        "//third_party/cargo:quote",
+        "//third_party/cargo:regex",
+        "//third_party/cargo:serde_json",
+        "//third_party/cargo:syn",
+        "//third_party/cargo:tempfile",
+        "//third_party/cargo:thiserror",
+        "//third_party/cargo:version_check",
+    ],
+)
diff --git a/third_party/autocxx/gen/cmd/BUILD b/third_party/autocxx/gen/cmd/BUILD
new file mode 100644
index 0000000..2cee72d
--- /dev/null
+++ b/third_party/autocxx/gen/cmd/BUILD
@@ -0,0 +1,32 @@
+load("@rules_rust//rust:defs.bzl", "rust_binary")
+
+package(default_visibility = ["//visibility:public"])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+rust_binary(
+    name = "gen",
+    srcs = glob(["**/*.rs"]),
+    crate_root = "src/main.rs",
+    edition = "2021",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=autocxx-gen",
+        "manual",
+    ],
+    version = "0.16.0",
+    deps = [
+        "//third_party/autocxx/engine:autocxx_engine",
+        "@//third_party/cargo:clap",
+        "@//third_party/cargo:env_logger",
+        "@//third_party/cargo:indexmap",
+        "@//third_party/cargo:miette",
+        "@//third_party/cargo:pathdiff",
+        "@//third_party/cargo:proc_macro2",
+    ],
+)
diff --git a/third_party/autocxx/macro/BUILD b/third_party/autocxx/macro/BUILD
new file mode 100644
index 0000000..a924a69
--- /dev/null
+++ b/third_party/autocxx/macro/BUILD
@@ -0,0 +1,30 @@
+load("@rules_rust//rust:defs.bzl", "rust_proc_macro")
+
+package(default_visibility = ["//visibility:public"])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+rust_proc_macro(
+    name = "autocxx_macro",
+    srcs = glob(["**/*.rs"]),
+    crate_root = "src/lib.rs",
+    edition = "2021",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=autocxx-macro",
+        "manual",
+    ],
+    version = "0.16.0",
+    deps = [
+        "//third_party/autocxx/parser:autocxx_parser",
+        "@//third_party/cargo:proc_macro2",
+        "@//third_party/cargo:proc_macro_error",
+        "@//third_party/cargo:quote",
+        "@//third_party/cargo:syn",
+    ],
+)
diff --git a/third_party/autocxx/parser/BUILD b/third_party/autocxx/parser/BUILD
new file mode 100644
index 0000000..c91ea85
--- /dev/null
+++ b/third_party/autocxx/parser/BUILD
@@ -0,0 +1,38 @@
+load("@rules_rust//rust:defs.bzl", "rust_library")
+
+package(default_visibility = ["//visibility:public"])
+
+licenses([
+    "notice",  # MIT from expression "MIT OR Apache-2.0"
+])
+
+rust_library(
+    name = "autocxx_parser",
+    srcs = glob(["**/*.rs"]),
+    crate_features = [
+        "reproduction_case",
+    ],
+    crate_root = "src/lib.rs",
+    edition = "2021",
+    rustc_flags = [
+        "--cap-lints=allow",
+    ],
+    tags = [
+        "cargo-raze",
+        "crate-name=autocxx-parser",
+        "manual",
+    ],
+    version = "0.16.0",
+    deps = [
+        "@//third_party/cargo:indexmap",
+        "@//third_party/cargo:itertools",
+        "@//third_party/cargo:log",
+        "@//third_party/cargo:once_cell",
+        "@//third_party/cargo:proc_macro2",
+        "@//third_party/cargo:quote",
+        "@//third_party/cargo:serde",
+        "@//third_party/cargo:serde_json",
+        "@//third_party/cargo:syn",
+        "@//third_party/cargo:thiserror",
+    ],
+)
diff --git a/tools/build_rules/autocxx.bzl b/tools/build_rules/autocxx.bzl
new file mode 100644
index 0000000..ee8b4d4
--- /dev/null
+++ b/tools/build_rules/autocxx.bzl
@@ -0,0 +1,345 @@
+load("@rules_rust//rust:defs.bzl", "rust_library")
+load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain")
+load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
+
+def _cc_toolchain_flags(ctx, cc_toolchain):
+    feature_configuration = cc_common.configure_features(
+        ctx = ctx,
+        cc_toolchain = cc_toolchain,
+        requested_features = ctx.features,
+        unsupported_features = ctx.disabled_features,
+    )
+    compiler_path = cc_common.get_tool_for_action(
+        feature_configuration = feature_configuration,
+        action_name = ACTION_NAMES.cpp_compile,
+    )
+    compile_variables = cc_common.create_compile_variables(
+        feature_configuration = feature_configuration,
+        cc_toolchain = cc_toolchain,
+        user_compile_flags = ctx.fragments.cpp.copts + ctx.fragments.cpp.cxxopts,
+    )
+    command_line = cc_common.get_memory_inefficient_command_line(
+        feature_configuration = feature_configuration,
+        action_name = ACTION_NAMES.cpp_compile,
+        variables = compile_variables,
+    )
+    env = cc_common.get_environment_variables(
+        feature_configuration = feature_configuration,
+        action_name = ACTION_NAMES.cpp_compile,
+        variables = compile_variables,
+    )
+    return command_line, env
+
+# All the stuff about AUTOCXX_RS_FILE and--fix-rs-include-name only applies when
+# using --gen-rs-include. We use --gen-rs-archive so none of that matters.
+#
+# The generated .rs file uses `include!` on the top-level C++ headers imported
+# via `#include` in `include_cpp!`. This always operates relative to the source
+# file (I don't see any way to change it), nor does autocxx have a way to change
+# the path. There are headers involved which use `#pragma once`, so just copying
+# them is a bad idea. Instead, we generate forwarding headers.
+def _autocxx_library_gen_impl(ctx):
+    rust_toolchain = ctx.toolchains[Label("@rules_rust//rust:toolchain")]
+
+    # TODO(Brian): Provide some way to override this globally in WORKSPACE? Need
+    # a real strategy for coordinating toolchains and flags, see the TODO below
+    # where cc_command_line is used for details.
+    if ctx.attr.override_cc_toolchain:
+        cc_toolchain = ctx.attr.override_cc_toolchain[cc_common.CcToolchainInfo]
+    else:
+        cc_toolchain = find_cc_toolchain(ctx)
+
+    # The directory where we generate files. Needs to be unique and in our package.
+    gendir = ctx.label.name + "__dir"
+
+    cc_command_line, cc_env = _cc_toolchain_flags(ctx, cc_toolchain)
+
+    includes = []
+    in_headers = []
+    forwarding_headers = []
+    for lib in ctx.attr.libs:
+        compilation = lib[CcInfo].compilation_context
+
+        # TODO(Brian): How should we be juggling includes, quote_includes, and system_includes?
+        includes.append(compilation.includes)
+        includes.append(compilation.quote_includes)
+        includes.append(compilation.system_includes)
+        in_headers.append(compilation.headers)
+        for header in compilation.direct_public_headers:
+            # TODO(Brian): This doesn't work if it's being `#include`ed (via
+            # `include_cpp!`) using one of the includes paths. Maybe we should
+            # add each `includes` prefixed with `gendir` to solve that?
+            forwarding = ctx.actions.declare_file("%s/%s" % (gendir, header.short_path))
+            forwarding_headers.append(forwarding)
+            ctx.actions.write(forwarding, '#include "%s"' % header.short_path)
+    includes = depset(transitive = includes)
+    action_inputs = depset(
+        direct = ctx.files.srcs + ctx.files.cxxbridge_srcs,
+        transitive = in_headers + [cc_toolchain.all_files],
+    )
+
+    # This is always the name with --gen-rs-archive, regardless of other flags.
+    out_rs_json = ctx.actions.declare_file("%s/gen.rs.json" % gendir)
+    out_env_file = ctx.actions.declare_file("%s/rustc_env" % gendir)
+    ctx.actions.write(
+        output = out_env_file,
+        content = "AUTOCXX_RS_JSON_ARCHIVE=%s" % out_rs_json.path,
+    )
+
+    out_h = ctx.actions.declare_file("%s_cxxgen.h" % ctx.label.name.rstrip("__gen"))
+    out_h_guard = out_h.short_path.replace("/", "_").replace(".", "_")
+    out_h_contents = [
+        "#ifndef %s" % out_h_guard,
+        "#define %s" % out_h_guard,
+        "// GENERATED FILE, DO NOT EDIT",
+        "//",
+        "// #includes all of the declarations exported to C++ from %s" % ctx.label,
+    ]
+    out_cc = []
+
+    # See `gen --help` for details on the naming of these outputs.
+    for cc_index in range(ctx.attr.sections_to_generate):
+        out_cc.append(ctx.actions.declare_file("%s/gen%d.cc" % (gendir, cc_index)))
+        gen_h = ctx.actions.declare_file("%s/gen%d.h" % (gendir, cc_index))
+        out_cc.append(gen_h)
+        out_h_contents.append("#include \"%s\"" % gen_h.short_path)
+        autocxxgen_h = ctx.actions.declare_file("%s/autocxxgen%d.h" % (gendir, cc_index))
+        out_cc.append(autocxxgen_h)
+        out_h_contents.append("#include \"%s\"" % autocxxgen_h.short_path)
+
+    cxxbridge_cc_srcs = []
+    for src in ctx.files.cxxbridge_srcs:
+        cxxbridge_cc = ctx.actions.declare_file("%s/cxxbridge.cc" % gendir)
+        cxxbridge_cc_srcs.append(cxxbridge_cc)
+        cxxbridge_h = ctx.actions.declare_file("%s/cxxbridge.h" % gendir)
+        cxxbridge_cc_srcs.append(cxxbridge_h)
+        out_h_contents.append("#include \"%s\"" % cxxbridge_h.short_path)
+        ctx.actions.run(
+            mnemonic = "CxxCodegen",
+            executable = ctx.executable._cxx_codegen,
+            inputs = [src],
+            outputs = [cxxbridge_cc, cxxbridge_h],
+            arguments = [src.path, "--output", cxxbridge_h.path, "--output", cxxbridge_cc.path],
+        )
+
+    out_h_contents.append("#endif  // %s" % out_h_guard)
+    ctx.actions.write(
+        output = out_h,
+        content = "\n".join(out_h_contents),
+    )
+
+    gen_rs = ctx.actions.args()
+    gen_rs.add_all(["--outdir", out_rs_json.dirname])
+    gen_rs.add("--gen-rs-archive")
+    gen_rs.add("--gen-cpp")
+
+    gen_rs.add_all(["--generate-exact", ctx.attr.sections_to_generate])
+
+    gen_rs.add_all(ctx.files.srcs)
+    gen_rs.add_all(ctx.files.cxxbridge_srcs)
+
+    # TODO: Do these go before or after the --? They're partially redundant with
+    # cc_command_line too.
+    gen_rs.add_all(includes, before_each = "-I")
+    gen_rs.add("--")
+
+    # TODO: These are flags for the cc_toolchain, not the libclang they're being passed to.
+    # Figure out how to handle that nicely. Maybe just require they're compatible, and direct
+    # people to overriding the toolchain in use instead?
+    gen_rs.add_all(cc_command_line)
+
+    gen_rs.add("-Wno-unused-private-field")
+    env = dict(cc_env)
+    env.update(
+        LIBCLANG_PATH = ctx.file._libclang.path,
+    )
+    if ctx.attr.gen_debug:
+        env.update(
+            RUST_BACKTRACE = "full",
+            RUST_LOG = "autocxx_engine=info",
+        )
+    ctx.actions.run(
+        arguments = [gen_rs],
+        outputs = [out_rs_json] + out_cc,
+        tools = [ctx.file._libclang],
+        inputs = action_inputs,
+        env = env,
+        executable = ctx.executable._autocxx_gen,
+        mnemonic = "AutocxxGen",
+    )
+
+    return [
+        OutputGroupInfo(
+            cc_srcs = out_cc + cxxbridge_cc_srcs,
+            hdr_srcs = [out_h],
+            compile_data = forwarding_headers + [out_rs_json],
+            env_files = [out_env_file],
+        ),
+    ]
+
+_autocxx_library_gen = rule(
+    implementation = _autocxx_library_gen_impl,
+    attrs = {
+        "libs": attr.label_list(
+            mandatory = True,
+            providers = [CcInfo],
+            doc = "C++ libraries to let Rust use headers from",
+        ),
+        "srcs": attr.label_list(
+            allow_files = [".rs"],
+            mandatory = False,
+            doc = "Rust sources with `include_cpp!` macros",
+            default = [],
+        ),
+        # TODO(Brian): Do we need to support this? Or just throw them in srcs?
+        "cxxbridge_srcs": attr.label_list(
+            allow_files = [".rs"],
+            mandatory = False,
+            doc = "Rust sources with only [cxx::bridge] annotations",
+            default = [],
+        ),
+        "sections_to_generate": attr.int(
+            default = 20,
+            doc = (
+                "The number of `cxx::bridge` sections to support," +
+                " including ones created by `autocxx::include_cpp!`." +
+                " The default is sufficient for most use cases." +
+                " Setting this too large has a small performance impact, setting it" +
+                " too low will result in a build failure."
+            ),
+        ),
+        "gen_debug": attr.bool(
+            default = False,
+            doc = "Print (lots of) debug info about autocxx's codegen at build time.",
+        ),
+        "_autocxx_gen": attr.label(
+            executable = True,
+            default = Label("@//third_party/autocxx/gen/cmd:gen"),
+            cfg = "exec",
+        ),
+        "_cxx_codegen": attr.label(
+            executable = True,
+            default = Label("@//third_party/cargo:cargo_bin_cxxbridge"),
+            cfg = "exec",
+        ),
+        "_libclang": attr.label(
+            cfg = "exec",
+            default = Label("@llvm_k8//:libclang"),
+            allow_single_file = True,
+        ),
+        "override_cc_toolchain": attr.label(mandatory = False, providers = [cc_common.CcToolchainInfo]),
+        "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
+    },
+    toolchains = [
+        "@rules_rust//rust:toolchain",
+        "@bazel_tools//tools/cpp:toolchain_type",
+    ],
+    fragments = ["cpp"],
+)
+
+def autocxx_library(
+        name,
+        visibility = None,
+        target_compatible_with = None,
+        libs = [],
+        srcs = [],
+        cxxbridge_srcs = [],
+        override_cc_toolchain = None,
+        deps = [],
+        rs_deps = [],
+        testonly = None,
+        crate_features = None,
+        crate_name = None,
+        gen_debug = None):
+    """A macro to generate Rust <-> C++ interop code with autocxx.
+
+    Creates the following rules:
+      * A rust_library with the given name, which includes the given srcs. Note that it will not
+        include them directly due to how autocxx works, instead they will be copied into a
+        generated file along with changes from autocxx.
+      * A cc_library with the given name + `_cc`. This is for C++ code that wants to use APIs
+        from the given Rust code. Rust dependencies should _not_ depend on this. The header for C++
+        to #include will be named by the given name + `_cxxgen.h`.
+
+      `deps` is for other `autocxx_library` rules. `rs_deps` is for dependencies of the Rust code.
+    """
+    library_gen_name = "%s__gen" % name
+    _autocxx_library_gen(
+        name = library_gen_name,
+        visibility = ["//visibility:private"],
+        target_compatible_with = target_compatible_with,
+        testonly = testonly,
+        libs = libs,
+        srcs = srcs,
+        cxxbridge_srcs = cxxbridge_srcs,
+        override_cc_toolchain = override_cc_toolchain,
+        gen_debug = gen_debug,
+    )
+    gen_cc_srcs_name = "%s__cc_srcs" % name
+    native.filegroup(
+        name = gen_cc_srcs_name,
+        visibility = ["//visibility:private"],
+        target_compatible_with = target_compatible_with,
+        testonly = testonly,
+        srcs = [library_gen_name],
+        output_group = "cc_srcs",
+    )
+    gen_hdr_srcs_name = "%s__hdr_srcs" % name
+    native.filegroup(
+        name = gen_hdr_srcs_name,
+        visibility = ["//visibility:private"],
+        target_compatible_with = target_compatible_with,
+        testonly = testonly,
+        srcs = [library_gen_name],
+        output_group = "hdr_srcs",
+    )
+    gen_compile_data_name = "%s__compile_data" % name
+    native.filegroup(
+        name = gen_compile_data_name,
+        visibility = ["//visibility:private"],
+        target_compatible_with = target_compatible_with,
+        testonly = testonly,
+        srcs = [library_gen_name],
+        output_group = "compile_data",
+    )
+    gen_env_files_name = "%s__env_files" % name
+    native.filegroup(
+        name = gen_env_files_name,
+        visibility = ["//visibility:private"],
+        target_compatible_with = target_compatible_with,
+        testonly = testonly,
+        srcs = [library_gen_name],
+        output_group = "env_files",
+    )
+    cc_library_name = "%s__cc" % name
+    native.cc_library(
+        name = cc_library_name,
+        visibility = visibility,
+        target_compatible_with = target_compatible_with,
+        testonly = testonly,
+        deps = deps + libs + [
+            "//third_party/cargo:cxx_cc",
+        ],
+        srcs = [gen_cc_srcs_name],
+        hdrs = [gen_hdr_srcs_name],
+    )
+
+    rust_library(
+        name = name,
+        visibility = visibility,
+        target_compatible_with = target_compatible_with,
+        testonly = testonly,
+        srcs = srcs + cxxbridge_srcs,
+        proc_macro_deps = [
+            "//third_party/cargo:cxxbridge_macro",
+        ],
+        crate_features = crate_features,
+        crate_name = crate_name,
+        deps = deps + rs_deps + [
+            cc_library_name,
+            "//third_party/cargo:cxx",
+            "//third_party/autocxx",
+        ],
+        compile_data = [gen_compile_data_name],
+        rustc_env_files = [gen_env_files_name],
+    )