Format .rs files with `rs_tokens_to_formatted_string`.

PiperOrigin-RevId: 487832692
diff --git a/cc_bindings_from_rs/BUILD b/cc_bindings_from_rs/BUILD
index ca2d5a4..8c81ed0 100644
--- a/cc_bindings_from_rs/BUILD
+++ b/cc_bindings_from_rs/BUILD
@@ -91,7 +91,9 @@
 sh_test(
     name = "cc_bindings_from_rs_sh_test",
     srcs = ["cc_bindings_from_rs_sh_test.sh"],
-    data = [":cc_bindings_from_rs_legacy_toolchain_runner.sar"],
+    data = [
+        ":cc_bindings_from_rs_legacy_toolchain_runner.sar",
+    ],
     deps = [
         "//util/shell/gbash",
         "//util/shell/gbash:unit",
diff --git a/cc_bindings_from_rs/bazel_support/cc_bindings_from_rust_rule.bzl b/cc_bindings_from_rs/bazel_support/cc_bindings_from_rust_rule.bzl
index da18b08..71b24d8 100644
--- a/cc_bindings_from_rs/bazel_support/cc_bindings_from_rust_rule.bzl
+++ b/cc_bindings_from_rs/bazel_support/cc_bindings_from_rust_rule.bzl
@@ -21,10 +21,12 @@
     crubit_args = ctx.actions.args()
     crubit_args.add("--h-out", h_out_file)
     crubit_args.add("--rs-out", rs_out_file)
+    crubit_args.add("--rustfmt-exe-path", ctx.file._rustfmt)
+    crubit_args.add("--rustfmt-config-path", ctx.file._rustfmt_cfg)
 
     ctx.actions.run(
         outputs = [h_out_file, rs_out_file],
-        inputs = [crate_root],
+        inputs = [crate_root, ctx.file._rustfmt, ctx.file._rustfmt_cfg],
         executable = ctx.executable._cc_bindings_from_rs_tool,
         mnemonic = "CcBindingsFromRust",
         progress_message = "Generating C++ bindings from Rust: %s" % h_out_file,
@@ -98,6 +100,16 @@
         "_cc_toolchain": attr.label(
             default = "@bazel_tools//tools/cpp:current_cc_toolchain",
         ),
+        "_rustfmt": attr.label(
+            default = "//third_party/unsupported_toolchains/rust/toolchains/nightly:bin/rustfmt",
+            executable = True,
+            allow_single_file = True,
+            cfg = "exec",
+        ),
+        "_rustfmt_cfg": attr.label(
+            default = "//nowhere:rustfmt.toml",
+            allow_single_file = True,
+        ),
     },
     fragments = ["cpp"],
 )
diff --git a/cc_bindings_from_rs/cc_bindings_from_rs.rs b/cc_bindings_from_rs/cc_bindings_from_rs.rs
index 5efab52..6226426 100644
--- a/cc_bindings_from_rs/cc_bindings_from_rs.rs
+++ b/cc_bindings_from_rs/cc_bindings_from_rs.rs
@@ -29,7 +29,9 @@
 
 use bindings::GeneratedBindings;
 use cmdline::Cmdline;
-use token_stream_printer::cc_tokens_to_formatted_string;
+use token_stream_printer::{
+    cc_tokens_to_formatted_string, rs_tokens_to_formatted_string, RustfmtConfig,
+};
 
 /// This mostly wraps and simplifies a subset of APIs from the `rustc_driver`
 /// module.
@@ -159,14 +161,18 @@
 
 fn run_with_tcx(cmdline: &Cmdline, tcx: TyCtxt) -> anyhow::Result<()> {
     let bindings = GeneratedBindings::generate(tcx)?;
-    write_file(&cmdline.h_out, cc_tokens_to_formatted_string(bindings.h_body)?.as_str())?;
 
-    // TODO(b/254097223): Format `bindings.rs_body` via
-    // `rs_tokens_to_formatted_string`.  (This requires accepting a
-    // mandatory`--rustfmt-exe-path` and an optional `--rustfmt-config-path`
-    // as cmdline args).  `bindings.rs_body` is mostly empty now so using
-    // `cc_tokens_to_formatted_string` doesn't hurt that much in the short term.
-    write_file(&cmdline.rs_out, cc_tokens_to_formatted_string(bindings.rs_body)?.as_str())?;
+    {
+        let h_body = cc_tokens_to_formatted_string(bindings.h_body)?;
+        write_file(&cmdline.h_out, &h_body)?;
+    }
+
+    {
+        let rustfmt_config =
+            RustfmtConfig::new(&cmdline.rustfmt_exe_path, cmdline.rustfmt_config_path.as_deref());
+        let rs_body = rs_tokens_to_formatted_string(bindings.rs_body, &rustfmt_config)?;
+        write_file(&cmdline.rs_out, &rs_body)?;
+    }
 
     Ok(())
 }
@@ -217,6 +223,7 @@
     use itertools::Itertools;
     use std::path::PathBuf;
     use tempfile::{tempdir, TempDir};
+    use token_stream_printer::RUSTFMT_EXE_PATH_FOR_TESTING;
 
     /// Test data builder (see also
     /// https://testing.googleblog.com/2018/02/testing-on-toilet-cleanly-create-test.html).
@@ -317,6 +324,7 @@
                 "cc_bindings_from_rs_unittest_executable".to_string(),
                 format!("--h-out={}", h_path.display()),
                 format!("--rs-out={}", rs_path.display()),
+                format!("--rustfmt-exe-path={RUSTFMT_EXE_PATH_FOR_TESTING}"),
             ];
             args.extend(self.extra_crubit_args.iter().cloned());
             args.extend([
diff --git a/cc_bindings_from_rs/cc_bindings_from_rs_sh_test.sh b/cc_bindings_from_rs/cc_bindings_from_rs_sh_test.sh
index 9f0e7bd..1ceb541 100755
--- a/cc_bindings_from_rs/cc_bindings_from_rs_sh_test.sh
+++ b/cc_bindings_from_rs/cc_bindings_from_rs_sh_test.sh
@@ -20,6 +20,8 @@
   rm -rf "$STDERR_PATH" "$STDOUT_PATH" "$H_OUT_PATH" "$RS_OUT_PATH"
 }
 
+readonly DEFAULT_RUSTFMT_EXE_PATH="third_party/unsupported_toolchains/rust/toolchains/nightly/bin/rustfmt"
+
 # This tests a simple happy, errors-free code path.
 function test::happy_path() {
   local RS_INPUT_PATH="${TEST_TMPDIR}/crate_name.rs"
@@ -35,8 +37,9 @@
   delete_all_test_outputs
   EXPECT_SUCCEED \
     "\"${CC_BINDINGS_FROM_RS}\" >\"$STDOUT_PATH\" 2>\"$STDERR_PATH\" \
-        \"--h-out=$H_OUT_PATH\" \
-        \"--rs-out=$RS_OUT_PATH\" \
+        \"--h-out=${H_OUT_PATH}\" \
+        \"--rs-out=${RS_OUT_PATH}\" \
+        \"--rustfmt-exe-path=${DEFAULT_RUSTFMT_EXE_PATH}\" \
         -- \
         \"$RS_INPUT_PATH\" \
         --crate-type=lib \
@@ -118,6 +121,7 @@
     "\"${CC_BINDINGS_FROM_RS}\" >\"$STDOUT_PATH\" 2>\"$STDERR_PATH\" \
         --h-out=../.. \
         --rs-out=blah \
+        \"--rustfmt-exe-path=${DEFAULT_RUSTFMT_EXE_PATH}\" \
         -- \
         \"$RS_INPUT_PATH\" \
         --crate-type=lib \
diff --git a/cc_bindings_from_rs/cmdline.rs b/cc_bindings_from_rs/cmdline.rs
index 6ec2ee3..140a950 100644
--- a/cc_bindings_from_rs/cmdline.rs
+++ b/cc_bindings_from_rs/cmdline.rs
@@ -18,6 +18,16 @@
     #[clap(long, value_parser, value_name = "FILE")]
     pub rs_out: PathBuf,
 
+    /// Path to a rustfmt executable that will be used to format the
+    /// .rs files generated by the tool.
+    #[clap(long, value_parser, value_name = "FILE")]
+    pub rustfmt_exe_path: PathBuf,
+
+    /// Path to a rustfmt.toml file that should replace the
+    /// default formatting of the .rs files generated by the tool.
+    #[clap(long, value_parser, value_name = "FILE")]
+    pub rustfmt_config_path: Option<PathBuf>,
+
     /// Command line arguments of the Rust compiler.
     #[clap(last = true, value_parser)]
     pub rustc_args: Vec<String>,
@@ -72,16 +82,22 @@
 
     #[test]
     fn test_happy_path() {
-        let cmdline =
-            new_cmdline(["--h-out=foo.h", "--rs-out=foo_impl.rs"]).expect("This is a happy path");
+        let cmdline = new_cmdline([
+            "--h-out=foo.h",
+            "--rs-out=foo_impl.rs",
+            "--rustfmt-exe-path=rustfmt.exe",
+        ])
+        .expect("This is a happy path");
         assert_eq!(Path::new("foo.h"), cmdline.h_out);
         assert_eq!(Path::new("foo_impl.rs"), cmdline.rs_out);
+        assert_eq!(Path::new("rustfmt.exe"), cmdline.rustfmt_exe_path);
+        assert!(cmdline.rustfmt_config_path.is_none());
     }
 
     #[test]
     fn test_rustc_args_happy_path() {
         // Note that this test would fail without the `--` separator.
-        let cmdline = new_cmdline(["--h-out=foo.h", "--rs-out=foo_impl.rs", "--", "test.rs", "--crate-type=lib"])
+        let cmdline = new_cmdline(["--h-out=foo.h", "--rs-out=foo_impl.rs", "--rustfmt-exe-path=rustfmt.exe", "--", "test.rs", "--crate-type=lib"])
             .expect("This is a happy path");
         let rustc_args = &cmdline.rustc_args;
         assert!(
@@ -117,15 +133,19 @@
 Generates C++ bindings for a Rust crate
 
 USAGE:
-    cc_bindings_from_rs_unittest_executable --h-out <FILE> --rs-out <FILE> [-- <RUSTC_ARGS>...]
+    cc_bindings_from_rs_unittest_executable [OPTIONS] --h-out <FILE> --rs-out <FILE> --rustfmt-exe-path <FILE> [-- <RUSTC_ARGS>...]
 
 ARGS:
     <RUSTC_ARGS>...    Command line arguments of the Rust compiler
 
 OPTIONS:
-        --h-out <FILE>     Output path for C++ header file with bindings
-    -h, --help             Print help information
-        --rs-out <FILE>    Output path for Rust implementation of the bindings
+        --h-out <FILE>                  Output path for C++ header file with bindings
+    -h, --help                          Print help information
+        --rs-out <FILE>                 Output path for Rust implementation of the bindings
+        --rustfmt-config-path <FILE>    Path to a rustfmt.toml file that should replace the default
+                                        formatting of the .rs files generated by the tool
+        --rustfmt-exe-path <FILE>       Path to a rustfmt executable that will be used to format
+                                        the .rs files generated by the tool
 "#;
         assert_eq!(expected_msg, clap_err.to_string());
     }
@@ -136,7 +156,7 @@
         let tmpfile = tmpdir.path().join("herefile");
         std::fs::write(
             &tmpfile,
-            ["--h-out=foo.h", "--rs-out=foo_impl.rs", "--", "test.rs", "--crate-type=lib"].join("\n"),
+            ["--h-out=foo.h", "--rs-out=foo_impl.rs", "--rustfmt-exe-path=rustfmt.exe", "--", "test.rs", "--crate-type=lib"].join("\n"),
         )?;
 
         let flag_file_arg = format!("@{}", tmpfile.display());
diff --git a/common/token_stream_printer.rs b/common/token_stream_printer.rs
index b86ff7d..fb203ec 100644
--- a/common/token_stream_printer.rs
+++ b/common/token_stream_printer.rs
@@ -22,7 +22,7 @@
     cmdline_args: Vec<OsString>,
 }
 
-const RUSTFMT_EXE_PATH_FOR_TESTING: &str =
+pub const RUSTFMT_EXE_PATH_FOR_TESTING: &str =
     "third_party/unsupported_toolchains/rust/toolchains/nightly/bin/rustfmt";
 
 impl RustfmtConfig {