Add Bazel rules for Rust flatbuffers

This required exposing things as a provider to properly build up the
file structure, so I refactored the other languages instead of
duplicating the functionality.

logger_test's sha1s changed because we're generating the .fbs files in
the host configuration now so their paths are different.

Change-Id: Idd60c6360efacfa1e5a5b8658a9d5770f37b02c6
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
diff --git a/aos/BUILD b/aos/BUILD
index b04bb3d..2bd0ec8 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -396,7 +396,7 @@
     ],
     data = [
         "//aos/events:pingpong_config",
-        "//aos/events:pong.bfbs",
+        "//aos/events:pong_fbs_reflection_out",
         "//aos/testdata:test_configs",
     ],
     target_compatible_with = ["@platforms//os:linux"],
diff --git a/aos/events/logging/logger_test.cc b/aos/events/logging/logger_test.cc
index bb5b41e..5f265e2 100644
--- a/aos/events/logging/logger_test.cc
+++ b/aos/events/logging/logger_test.cc
@@ -3482,9 +3482,9 @@
 }
 
 constexpr std::string_view kCombinedConfigSha1(
-    "158a244107a7dc637fc5934ac161cb9e6c26195930fd8f82bb351c3ad7cce349");
+    "bcc66bc13a90a4a268649744e244129c5d024f5abd67587dcfbd7158d8abfc44");
 constexpr std::string_view kSplitConfigSha1(
-    "c73aa7913a9e116ee0a793d8280fac170b7eeea8e7350f45c6ac5bfc4ab018e1");
+    "d97e998164a6f1bf078aad77ef127329728ac9198a13a5ab8d5f30d84a932662");
 
 INSTANTIATE_TEST_SUITE_P(
     All, MultinodeLoggerTest,
diff --git a/third_party/flatbuffers/build_defs.bzl b/third_party/flatbuffers/build_defs.bzl
index 7eaa4d3..c3e2bb0 100644
--- a/third_party/flatbuffers/build_defs.bzl
+++ b/third_party/flatbuffers/build_defs.bzl
@@ -6,6 +6,8 @@
 """
 
 load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("@rules_rust//rust:defs.bzl", "rust_library")
+load("@rules_rust//rust:rust_common.bzl", "CrateInfo")
 load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
 load("@npm//@bazel/typescript:index.bzl", "ts_project")
 load("@rules_cc//cc:defs.bzl", "cc_library")
@@ -14,9 +16,6 @@
 
 DEFAULT_INCLUDE_PATHS = [
     "./",
-    "$(GENDIR)",
-    "$(BINDIR)",
-    "$(execpath @com_github_google_flatbuffers//:flatc).runfiles/com_github_google_flatbuffers",
 ]
 
 DEFAULT_FLATC_ARGS = [
@@ -28,7 +27,8 @@
     "--require-explicit-ids",
     "--gen-mutable",
     "--reflect-names",
-    "--cpp-ptr-type flatbuffers::unique_ptr",
+    "--cpp-ptr-type",
+    "flatbuffers::unique_ptr",
     "--force-empty",
     "--scoped-enums",
     "--gen-name-strings",
@@ -40,6 +40,11 @@
     "--require-explicit-ids",
 ]
 
+DEFAULT_FLATC_RUST_ARGS = [
+    "--gen-object-api",
+    "--require-explicit-ids",
+]
+
 DEFAULT_FLATC_TS_ARGS = [
     "--gen-object-api",
     "--gen-mutable",
@@ -49,12 +54,80 @@
     "--keep-prefix",
 ]
 
+"""Contains information about a set of flatbuffers which have their code for
+reading/writing generated in a single library-style rule.
+
+Fields:
+    srcs: [File], the .fbs source files
+"""
+FlatbufferLibraryInfo = provider()
+
+def _flatbuffer_library_compile_impl(ctx):
+    outs = []
+    commands = []
+    for src in ctx.files.srcs:
+        if ctx.attr.tables_for_filenames:
+            out_dir = None
+            for table in ctx.attr.tables_for_filenames:
+                out = ctx.actions.declare_file(ctx.attr.out_prefix + table + ctx.attr.output_suffix)
+                this_out_dir = "/".join(out.dirname.split("/")[:-(len(ctx.attr.out_prefix.split("/")) - 1)])
+                if out_dir:
+                    if this_out_dir != out_dir:
+                        fail("Trying to write to multiple directories")
+                else:
+                    out_dir = this_out_dir
+                outs.append(out)
+        else:
+            out = ctx.actions.declare_file(ctx.attr.out_prefix + src.basename.replace(".fbs", "") + ctx.attr.output_suffix)
+            outs.append(out)
+            out_dir = out.dirname
+        arguments = [ctx.executable._flatc.path]
+        for path in ctx.attr.include_paths:
+            for subpath in ["", ctx.bin_dir.path + "/"]:
+                arguments.append("-I")
+                arguments.append(subpath + path)
+        arguments.append("-I")
+        arguments.append("%s.runfiles/com_github_google_flatbuffers" % ctx.executable._flatc.path)
+        arguments.extend(ctx.attr.flatc_args)
+        arguments.extend(ctx.attr.language_flags)
+        arguments.extend([
+            "-o",
+            out_dir,
+        ])
+        arguments.append(src.path)
+        commands.append(arguments)
+    ctx.actions.run_shell(
+        outputs = outs,
+        inputs = ctx.files.srcs + ctx.files.includes,
+        tools = [ctx.executable._flatc],
+        command = " && ".join([" ".join(arguments) for arguments in commands]),
+        mnemonic = "Flatc",
+        progress_message = "Generating flatbuffer files for %{input}:",
+    )
+    return [DefaultInfo(files = depset(outs), runfiles = ctx.runfiles(files = outs)), FlatbufferLibraryInfo(srcs = ctx.files.srcs)]
+
+_flatbuffer_library_compile = rule(
+    implementation = _flatbuffer_library_compile_impl,
+    attrs = {
+        "srcs": attr.label_list(mandatory = True, allow_files = True),
+        "output_suffix": attr.string(mandatory = True),
+        "tables_for_filenames": attr.string_list(mandatory = False),
+        "language_flags": attr.string_list(mandatory = True),
+        "includes": attr.label_list(default = [], allow_files = True),
+        "include_paths": attr.string_list(default = []),
+        "flatc_args": attr.string_list(default = []),
+        "out_prefix": attr.string(default = ""),
+        "_flatc": attr.label(executable = True, cfg = "exec", default = Label(flatc_path)),
+    },
+)
+
 def flatbuffer_library_public(
         name,
         srcs,
-        outs,
+        output_suffix,
         language_flag,
         out_prefix = "",
+        tables_for_filenames = None,
         includes = [],
         include_paths = DEFAULT_INCLUDE_PATHS,
         flatc_args = DEFAULT_FLATC_ARGS,
@@ -63,14 +136,15 @@
         compatible_with = None,
         restricted_to = None,
         target_compatible_with = None,
-        output_to_bindir = False):
+        output_to_bindir = False,
+        visibility = None):
     """Generates code files for reading/writing the given flatbuffers in the
     requested language using the public compiler.
 
     Args:
       name: Rule name.
       srcs: Source .fbs files. Sent in order to the compiler.
-      outs: Output files from flatc.
+      output_suffix: Suffix for output files from flatc.
       language_flag: Target language flag. One of [-c, -j, -js].
       out_prefix: Prepend this path to the front of all generated files except on
           single source targets. Usually is a directory name.
@@ -94,73 +168,36 @@
     optionally a Fileset([reflection_name]) with all generated reflection
     binaries.
     """
-    include_paths_cmd = ["-I %s" % (s) for s in include_paths]
-
-    # '$(@D)' when given a single source target will give the appropriate
-    # directory. Appending 'out_prefix' is only necessary when given a build
-    # target with multiple sources.
-    output_directory = (
-        ("-o $(@D)/%s" % (out_prefix)) if len(srcs) > 1 else ("-o $(@D)")
-    )
-    genrule_cmd = " ".join([
-        "SRCS=($(SRCS));",
-        "for f in $${SRCS[@]:0:%s}; do" % len(srcs),
-        "$(location %s)" % (flatc_path),
-        " ".join(include_paths_cmd),
-        " ".join(flatc_args),
-        language_flag,
-        output_directory,
-        "$$f;",
-        "done",
-    ])
-    native.genrule(
+    _flatbuffer_library_compile(
         name = name,
-        srcs = srcs + includes,
-        outs = outs,
-        output_to_bindir = output_to_bindir,
-        tools = [flatc_path],
-        cmd = genrule_cmd,
+        srcs = srcs,
+        output_suffix = output_suffix,
+        language_flags = [language_flag],
+        includes = includes,
+        include_paths = include_paths,
+        flatc_args = flatc_args,
+        out_prefix = out_prefix,
+        tables_for_filenames = tables_for_filenames,
         compatible_with = compatible_with,
         target_compatible_with = target_compatible_with,
         restricted_to = restricted_to,
-        message = "Generating flatbuffer files for %s:" % (name),
+        visibility = visibility,
     )
+
     if reflection_name:
-        reflection_genrule_cmd = " ".join([
-            "SRCS=($(SRCS));",
-            "for f in $${SRCS[@]:0:%s}; do" % len(srcs),
-            "$(location %s)" % (flatc_path),
-            "-b --schema",
-            " ".join(flatc_args),
-            " ".join(include_paths_cmd),
-            language_flag,
-            output_directory,
-            "$$f;",
-            "done",
-        ])
-        reflection_outs = [
-            (out_prefix + "%s.bfbs") % (s.replace(".fbs", "").split("/")[-1])
-            for s in srcs
-        ]
-        native.genrule(
-            name = "%s_srcs" % reflection_name,
-            srcs = srcs + includes,
-            outs = reflection_outs,
-            output_to_bindir = output_to_bindir,
-            tools = [flatc_path],
-            compatible_with = compatible_with,
-            restricted_to = restricted_to,
-            target_compatible_with = target_compatible_with,
-            cmd = reflection_genrule_cmd,
-            message = "Generating flatbuffer reflection binary for %s:" % (name),
-            visibility = reflection_visibility,
-        )
-        native.filegroup(
+        _flatbuffer_library_compile(
             name = "%s_out" % reflection_name,
-            srcs = reflection_outs,
-            visibility = reflection_visibility,
+            srcs = srcs,
+            output_suffix = ".bfbs",
+            language_flags = ["-b", "--schema"],
+            includes = includes,
+            include_paths = include_paths,
+            flatc_args = flatc_args,
+            out_prefix = out_prefix,
             compatible_with = compatible_with,
+            target_compatible_with = target_compatible_with,
             restricted_to = restricted_to,
+            visibility = reflection_visibility,
         )
 
 def flatbuffer_cc_library(
@@ -220,10 +257,6 @@
       Fileset([name]_reflection): (Optional) all generated reflection binaries.
       cc_library([name]): library with sources and flatbuffers deps.
     """
-    output_headers = [
-        (out_prefix + "%s_generated.h") % (s.replace(".fbs", "").split("/")[-1].split(":")[-1])
-        for s in srcs
-    ]
     if deps and includes:
         # There is no inherent reason we couldn't support both, but this discourages
         # use of includes without good reason.
@@ -236,7 +269,7 @@
     flatbuffer_library_public(
         name = srcs_lib,
         srcs = srcs,
-        outs = output_headers,
+        output_suffix = "_generated.h",
         language_flag = "-c",
         out_prefix = out_prefix,
         includes = includes,
@@ -309,23 +342,27 @@
         been parsed. As such, we just force the user to manually specify
         things.
     """
-    python_files = ["%s/%s.py" % (namespace.replace(".", "/"), table) for table in tables]
 
     srcs_lib = "%s_srcs" % (name)
+    if not tables:
+        fail("Must specify the list of tables")
     flatbuffer_library_public(
         name = srcs_lib,
         srcs = srcs,
-        outs = python_files,
+        output_suffix = ".py",
+        out_prefix = namespace.replace(".", "/") + "/",
+        tables_for_filenames = tables,
         language_flag = "--python",
         includes = includes,
         include_paths = include_paths,
         flatc_args = flatc_args,
         compatible_with = compatible_with,
         target_compatible_with = target_compatible_with,
+        visibility = ["//visibility:private"],
     )
     native.py_library(
         name = name,
-        srcs = python_files,
+        srcs = [srcs_lib],
         visibility = visibility,
         compatible_with = compatible_with,
         target_compatible_with = target_compatible_with,
@@ -345,23 +382,23 @@
         visibility = None,
         srcs_filegroup_visibility = None):
     srcs_lib = "%s_srcs" % (name)
-    outs = ["%s_generated.go" % (s.replace(".fbs", "").split("/")[-1]) for s in srcs]
     flatc_args = flatc_args + ["--go-namespace", importpath.split("/")[-1]]
 
     flatbuffer_library_public(
         name = srcs_lib,
         srcs = srcs,
-        outs = outs,
+        output_suffix = "_generated.go",
         language_flag = "--go",
         includes = includes,
         include_paths = include_paths,
         flatc_args = flatc_args,
         compatible_with = compatible_with,
         target_compatible_with = target_compatible_with,
+        visibility = ["//visibility:private"],
     )
     go_library(
         name = name,
-        srcs = outs,
+        srcs = [srcs_lib],
         deps = ["@com_github_google_flatbuffers//go"],
         importpath = importpath,
         visibility = visibility,
@@ -369,6 +406,93 @@
         target_compatible_with = target_compatible_with,
     )
 
+def _flatbuffer_rust_lib_gen_impl(ctx):
+    # TODO(Brian): I think this needs changes to properly handle multiple .fbs files in a rule.
+    uses = []
+    for (dep, dep_srcs) in zip(ctx.attr.deps, ctx.attr.dep_srcs):
+        for dep_src in dep_srcs[FlatbufferLibraryInfo].srcs:
+            uses.append((dep[CrateInfo].name, dep_src.basename.replace(".fbs", "_generated")))
+    lib_rs_content = "\n".join(
+        [
+            "// Automatically generated by the Flatbuffers Bazel rules. Do not modify",
+            "#![allow(unused_imports)]",
+        ] + ["use %s as %s;" % (crate, use_as) for (crate, use_as) in uses] +
+        ["include!(\"%s\");" % src.basename for src in ctx.files.srcs_lib],
+    )
+    output = ctx.actions.declare_file(ctx.attr.name + "_lib.rs")
+    ctx.actions.write(
+        output = output,
+        content = lib_rs_content,
+    )
+    return [DefaultInfo(files = depset([output]))]
+
+"""Generates a lib.rs for a flatbuffer_rust_library.
+
+flatc generates individual .rs files for us. It can also generate a top-level mod.rs to be included
+in a crate, but that is laid out to include all flatbuffers files in a project. That's not a good
+fit for Bazel rules and monorepos, so we generate an alternative that imports all dependencies under
+their expected names."""
+_flatbuffer_rust_lib_gen = rule(
+    implementation = _flatbuffer_rust_lib_gen_impl,
+    attrs = {
+        "srcs_lib": attr.label(mandatory = True, doc = "The generated srcs for this rule"),
+        "dep_srcs": attr.label_list(mandatory = True, providers = [FlatbufferLibraryInfo], doc = "The _srcs rules for all our direct dependencies"),
+        "deps": attr.label_list(mandatory = True, providers = [CrateInfo]),
+    },
+)
+
+def flatbuffer_rust_library(
+        name,
+        srcs,
+        compatible_with = None,
+        target_compatible_with = None,
+        deps = [],
+        include_paths = DEFAULT_INCLUDE_PATHS,
+        flatc_args = DEFAULT_FLATC_RUST_ARGS,
+        include_reflection = True,
+        crate_name = None,
+        visibility = None,
+        srcs_filegroup_visibility = None):
+    includes = [d + "_includes" for d in deps]
+    srcs_lib = "%s_srcs" % (name)
+    lib_gen = "%s_lib_gen" % (name)
+    deps = list(deps)
+    if include_reflection:
+        deps.append("@com_github_google_flatbuffers//reflection:reflection_rust_fbs")
+
+    flatbuffer_library_public(
+        name = srcs_lib,
+        srcs = srcs,
+        language_flag = "--rust",
+        output_suffix = "_generated.rs",
+        includes = includes,
+        include_paths = include_paths,
+        flatc_args = flatc_args,
+        compatible_with = compatible_with,
+        target_compatible_with = target_compatible_with,
+        visibility = visibility,
+    )
+    _flatbuffer_rust_lib_gen(
+        name = lib_gen,
+        deps = deps,
+        dep_srcs = [dep + "_srcs" for dep in deps],
+        srcs_lib = srcs_lib,
+        visibility = ["//visibility:private"],
+        compatible_with = compatible_with,
+        target_compatible_with = target_compatible_with,
+    )
+    rust_library(
+        name = name,
+        srcs = [srcs_lib, lib_gen],
+        crate_root = lib_gen,
+        crate_name = crate_name,
+        deps = ["@com_github_google_flatbuffers//rust"] + deps,
+        edition = "2018",
+        visibility = visibility,
+        compatible_with = compatible_with,
+        target_compatible_with = target_compatible_with,
+    )
+
 def flatbuffer_ts_library(
         name,
         srcs,
@@ -411,13 +535,12 @@
     # third_party/.
     # TODO(james): There absolutely are better ways to do this, but this was the quick and dirty
     # one....
-    pre_outs = ["%s_pregenerated.ts" % (s.replace(".fbs", "").split("/")[-1]) for s in srcs]
     outs = ["%s_generated.ts" % (s.replace(".fbs", "").split("/")[-1]) for s in srcs]
     includes = [d + "_includes" for d in deps]
     flatbuffer_library_public(
         name = srcs_lib,
         srcs = srcs,
-        outs = pre_outs,
+        output_suffix = "_pregenerated.ts",
         language_flag = "--ts",
         includes = includes,
         include_paths = include_paths,
@@ -435,8 +558,8 @@
         "done",
     ])
     native.genrule(
-        name = name + "_reimporter",
-        srcs = pre_outs,
+        name = name + "_reimporter.ts",
+        srcs = [srcs_lib],
         outs = outs,
         cmd = genrule_cmd,
     )
diff --git a/third_party/flatbuffers/reflection/BUILD.bazel b/third_party/flatbuffers/reflection/BUILD.bazel
index aa421db..9b08734 100644
--- a/third_party/flatbuffers/reflection/BUILD.bazel
+++ b/third_party/flatbuffers/reflection/BUILD.bazel
@@ -1,4 +1,4 @@
-load("//:build_defs.bzl", "flatbuffer_ts_library")
+load("//:build_defs.bzl", "flatbuffer_rust_library", "flatbuffer_ts_library")
 
 filegroup(
     name = "reflection_fbs_schema",
@@ -13,3 +13,11 @@
     include_reflection = False,
     visibility = ["//visibility:public"],
 )
+
+flatbuffer_rust_library(
+    name = "reflection_rust_fbs",
+    srcs = ["reflection.fbs"],
+    crate_name = "flatbuffers_reflection",
+    include_reflection = False,
+    visibility = ["//visibility:public"],
+)
diff --git a/third_party/flatbuffers/rust/BUILD.bazel b/third_party/flatbuffers/rust/BUILD.bazel
new file mode 100644
index 0000000..246f5a0
--- /dev/null
+++ b/third_party/flatbuffers/rust/BUILD.bazel
@@ -0,0 +1,16 @@
+load("@rules_rust//rust:defs.bzl", "rust_library")
+
+rust_library(
+    name = "rust",
+    srcs = glob(["flatbuffers/**/*.rs"]),
+    crate_name = "flatbuffers",
+    crate_root = "flatbuffers/src/lib.rs",
+    edition = "2018",
+    version = "2.1.1",
+    visibility = ["//visibility:public"],
+    deps = [
+        "@//third_party/cargo:bitflags",
+        "@//third_party/cargo:smallvec",
+        "@//third_party/cargo:thiserror",
+    ],
+)
diff --git a/third_party/flatbuffers/src/idl_gen_rust.cpp b/third_party/flatbuffers/src/idl_gen_rust.cpp
index 17853a0..b15cdf5 100644
--- a/third_party/flatbuffers/src/idl_gen_rust.cpp
+++ b/third_party/flatbuffers/src/idl_gen_rust.cpp
@@ -366,6 +366,7 @@
       if (symbol.generated) continue;
       code_.Clear();
       code_ += "// " + std::string(FlatBuffersGeneratedWarning());
+      code_ += "#![allow(unused_imports)]";
       code_ += "extern crate flatbuffers;";
       code_ += "use std::mem;";
       code_ += "use std::cmp::Ordering;";