Actually compile the generated `..._cc_api_impl.rs` file.

PiperOrigin-RevId: 488744487
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 71b24d8..52793d5 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
@@ -8,13 +8,38 @@
 not be used yet.
 """
 
+# buildifier: disable=bzl-visibility
+load(
+    "@rules_rust//rust/private:providers.bzl",
+    "DepInfo",
+    "DepVariantInfo",
+)
 load(
     "@rules_rust//rust:rust_common.bzl",
     "CrateInfo",
 )
+load(
+    "//rs_bindings_from_cc/bazel_support:compile_rust.bzl",
+    "compile_rust",
+)
 load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
 
-def _generate_bindings(ctx, basename, crate_root, rustc_args):
+def _generate_bindings(ctx, basename, inputs, rustc_args):
+    """Invokes the `cc_bindings_from_rs` tool to generate C++ bindings for a Rust crate.
+
+    Args:
+      ctx: The rule context.
+      basename: The basename for the generated files
+      rustc_args: `rustc` flags to pass to `cc_bindings_from_rs`.
+           TODO(b/258449205): Extract `rustc_args` from the target `crate`.
+      inputs: `cc_bindings_from_rs` inputs specific to the target `crate`
+           TODO(b/258449205): Extract `inputs` from the target `crate`.
+
+    Returns:
+      A pair of files:
+      - h_out_file (named "<basename>_cc_api.h")
+      - rs_out_file (named "<basename>_cc_api_impl.rs")
+    """
     h_out_file = ctx.actions.declare_file(basename + "_cc_api.h")
     rs_out_file = ctx.actions.declare_file(basename + "_cc_api_impl.rs")
 
@@ -26,7 +51,7 @@
 
     ctx.actions.run(
         outputs = [h_out_file, rs_out_file],
-        inputs = [crate_root, ctx.file._rustfmt, ctx.file._rustfmt_cfg],
+        inputs = inputs + [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,
@@ -35,25 +60,19 @@
 
     return (h_out_file, rs_out_file)
 
-def _cc_bindings_from_rust_rule_impl(ctx):
-    basename = ctx.attr.crate.label.name
+def _make_cc_info_for_h_out_file(ctx, h_out_file, linking_contexts):
+    """Creates and returns CcInfo for the generated ..._cc_api.h header file.
 
-    crate_root = ctx.attr.crate[CrateInfo].root
+    Args:
+      ctx: The rule context.
+      h_out_file: The generated "..._cc_api.h" header file
+      linking_contexts: Linking contexts - should include both:
+          1) the target `crate` and
+          2) the compiled Rust glue crate (`..._cc_api_impl.rs` file).
 
-    # TODO(b/258449205): Extract `rustc_args` from the target `crate` (instead
-    # of figouring out the `crate_root` and hard-coding `--crate-type`,
-    # `panic=abort`, etc.).  It seems that `BuildInfo` from
-    # @rules_rust//rust/private/providers.bzl is not
-    # exposed publicly?
-    rustc_args = ctx.actions.args()
-    rustc_args.add(crate_root)
-    rustc_args.add("--crate-type", "lib")
-    rustc_args.add("--codegen", "panic=abort")
-
-    # TODO(b/254097223): Retrieve `rs_out_file`, compile it, and include it in
-    # the `linking_context`.
-    (h_out_file, _) = _generate_bindings(ctx, basename, crate_root, rustc_args)
-
+    Returns:
+      A CcInfo provider.
+    """
     cc_toolchain = find_cpp_toolchain(ctx)
     feature_configuration = cc_common.configure_features(
         ctx = ctx,
@@ -72,12 +91,60 @@
         feature_configuration = feature_configuration,
         cc_toolchain = cc_toolchain,
         compilation_outputs = compilation_outputs,
-        linking_contexts = [ctx.attr.crate[CcInfo].linking_context],
+        linking_contexts = linking_contexts,
     )
-    return [CcInfo(
+    return CcInfo(
         compilation_context = compilation_context,
         linking_context = linking_context,
+    )
+
+def _compile_rs_out_file(ctx, rs_out_file, target_crate):
+    """Compiles the generated "..._cc_api_impl.rs" file.
+
+    Args:
+      ctx: The rule context.
+      rs_out_file: The generated "..._cc_api_impl.rs" file
+      target_crate: The value of `ctx.attr.crate`
+
+    Returns:
+      LinkingContext for linking in the generated "..._cc_api_impl.rs".
+    """
+    deps = [DepVariantInfo(
+        crate_info = target_crate[CrateInfo],
+        dep_info = target_crate[DepInfo],
+        cc_info = target_crate[CcInfo],
+        build_info = None,
     )]
+    dep_variant_info = compile_rust(ctx, ctx.attr, rs_out_file, [], deps)
+    return dep_variant_info.cc_info.linking_context
+
+def _cc_bindings_from_rust_rule_impl(ctx):
+    basename = ctx.attr.crate.label.name
+
+    crate_root = ctx.attr.crate[CrateInfo].root
+
+    # TODO(b/258449205): Extract `rustc_args` from the target `crate` (instead
+    # of figouring out the `crate_root` and hard-coding `--crate-type`,
+    # `panic=abort`, etc.).  It seems that `BuildInfo` from
+    # @rules_rust//rust/private/providers.bzl is not
+    # exposed publicly?
+    rustc_args = ctx.actions.args()
+    rustc_args.add(crate_root)
+    rustc_args.add("--crate-type", "lib")
+    rustc_args.add("--codegen", "panic=abort")
+
+    (h_out_file, rs_out_file) = _generate_bindings(ctx, basename, [crate_root], rustc_args)
+
+    impl_linking_context = _compile_rs_out_file(ctx, rs_out_file, ctx.attr.crate)
+
+    target_cc_info = ctx.attr.crate[CcInfo]
+    target_crate_linking_context = target_cc_info.linking_context
+    cc_info = _make_cc_info_for_h_out_file(
+        ctx,
+        h_out_file,
+        [target_crate_linking_context, impl_linking_context],
+    )
+    return [cc_info]
 
 # TODO(b/257283134): Register actions via an `aspect`, rather than directly
 # from the `rule` implementation?
@@ -100,6 +167,12 @@
         "_cc_toolchain": attr.label(
             default = "@bazel_tools//tools/cpp:current_cc_toolchain",
         ),
+        "_process_wrapper": attr.label(
+            default = "@rules_rust//util/process_wrapper",
+            executable = True,
+            allow_single_file = True,
+            cfg = "exec",
+        ),
         "_rustfmt": attr.label(
             default = "//third_party/unsupported_toolchains/rust/toolchains/nightly:bin/rustfmt",
             executable = True,
@@ -111,5 +184,8 @@
             allow_single_file = True,
         ),
     },
+    toolchains = [
+        "@rules_rust//rust:toolchain",
+    ],
     fragments = ["cpp"],
 )
diff --git a/cc_bindings_from_rs/test/bazel_unit_tests/generating_files/generating_files_test.bzl b/cc_bindings_from_rs/test/bazel_unit_tests/generating_files/generating_files_test.bzl
index 2564e5b..90232fb 100644
--- a/cc_bindings_from_rs/test/bazel_unit_tests/generating_files/generating_files_test.bzl
+++ b/cc_bindings_from_rs/test/bazel_unit_tests/generating_files/generating_files_test.bzl
@@ -14,15 +14,90 @@
     "cc_bindings_from_rust",
 )
 
+def _find_action_by_mnemonic(env, expected_mnemonic):
+    """Searches `target_actions` for a single one with `expected_mnemonic`.
+
+    Will throw or assert if there are no matching actions, or if there is
+    more than one matching action.
+
+    Args:
+      env: A test environment struct received from `analysistest.begin(ctx)`
+      expected_mnemonic: string to be compared against `Action.mnemonic`
+                         (see https://bazel.build/rules/lib/Action#mnemonic)
+
+    Returns:
+      Action  - the path of `f` with `extension` removed
+    """
+    matching_actions = [
+        a
+        for a in analysistest.target_actions(env)
+        if a.mnemonic == expected_mnemonic
+    ]
+    asserts.equals(env, 1, len(matching_actions))
+    return matching_actions[0]
+
+def _remove_ext(f):
+    """Takes a File and returns its `path` with `extension` removed.
+
+    Args:
+      f: File - https://bazel.build/rules/lib/File
+
+    Returns:
+      string - the path of `f` with `extension` removed
+    """
+    f.path.removesuffix(f.extension)
+
 def _header_generation_test_impl(ctx):
     env = analysistest.begin(ctx)
-    target_under_test = analysistest.target_under_test(env)
 
+    # Verify that `CcBindingsFromRust` propagates inputs and rustc flags from the
+    # target create.
+    generate_action = _find_action_by_mnemonic(env, "CcBindingsFromRust")
+    asserts.true(env, "rusty_lib_crate_root.rs" in [i.basename for i in generate_action.inputs.to_list()])
+    generate_cmdline = " ".join(generate_action.argv)
+    asserts.true(env, "rusty_lib_crate_root.rs" in generate_cmdline)
+    asserts.true(env, "--crate-type lib" in generate_cmdline)
+    asserts.true(env, "--codegen panic=abort" in generate_cmdline)
+
+    # Verify that `CcBindingsFromRust` generates:
+    # 1) `generated_header` ("..._cc_api.h")
+    # 2) `generated_impl` ("..._cc_api_impl.rs")
+    generated_outputs = generate_action.outputs.to_list()
+    asserts.equals(env, 2, len(generated_outputs))
+    generated_header = generated_outputs[0]
+    asserts.equals(env, "rusty_lib_cc_api.h", generated_header.basename)
+    generated_impl = generated_outputs[1]
+    asserts.equals(env, "rusty_lib_cc_api_impl.rs", generated_impl.basename)
+
+    # Verify that `generated_impl` is an input for `rustc_action`.
+    rustc_action = _find_action_by_mnemonic(env, "Rustc")
+    rustc_input_paths = [i.path for i in rustc_action.inputs.to_list()]
+    asserts.true(env, generated_impl.path in rustc_input_paths)
+
+    # Extract `rustc_rlib` output (and verify it has `rlib` extension).
+    rustc_outputs = rustc_action.outputs.to_list()
+    asserts.equals(env, 1, len(rustc_outputs))
+    rustc_rlib = rustc_outputs[0]
+    asserts.equals(env, "rlib", rustc_rlib.extension)
+
+    # Verify that `cc_info.compilation_context.direct_headers` contains `generated_header`.
+    target_under_test = analysistest.target_under_test(env)
     asserts.true(env, CcInfo in target_under_test)
     cc_info = target_under_test[CcInfo]
     asserts.true(env, len(cc_info.compilation_context.direct_headers) == 1)
-    generated_header = cc_info.compilation_context.direct_headers[0]
-    asserts.true(env, generated_header.path.endswith("rusty_lib_cc_api.h"))
+    cc_info_header = cc_info.compilation_context.direct_headers[0]
+    asserts.equals(env, generated_header, cc_info_header)
+
+    # Verify that `cc_info.linker_input.linker_inputs` contains `rustc_rlib`.
+    cc_info_links_rustc_output = False
+    for linker_input in cc_info.linking_context.linker_inputs.to_list():
+        static_libs = [lib.static_library for lib in linker_input.libraries if lib.static_library]
+        for static_lib in static_libs:
+            # Using `_remove_ext` because `rustc_rlib` has `.rlib`
+            # extension, but `static_library` has `.a` extension.
+            if _remove_ext(rustc_rlib) == _remove_ext(static_lib):
+                cc_info_links_rustc_output = True
+    asserts.true(env, cc_info_links_rustc_output)
 
     return analysistest.end(env)
 
@@ -31,7 +106,7 @@
 def _tests():
     rust_library(
         name = "rusty_lib",
-        srcs = ["lib.rs"],
+        srcs = ["rusty_lib_crate_root.rs"],
         tags = ["manual"],
     )
 
diff --git a/cc_bindings_from_rs/test/bazel_unit_tests/generating_files/lib.rs b/cc_bindings_from_rs/test/bazel_unit_tests/generating_files/rusty_lib_crate_root.rs
similarity index 100%
rename from cc_bindings_from_rs/test/bazel_unit_tests/generating_files/lib.rs
rename to cc_bindings_from_rs/test/bazel_unit_tests/generating_files/rusty_lib_crate_root.rs
diff --git a/rs_bindings_from_cc/bazel_support/rust_bindings_from_cc_utils.bzl b/rs_bindings_from_cc/bazel_support/rust_bindings_from_cc_utils.bzl
index 13983dc..320a1e7 100644
--- a/rs_bindings_from_cc/bazel_support/rust_bindings_from_cc_utils.bzl
+++ b/rs_bindings_from_cc/bazel_support/rust_bindings_from_cc_utils.bzl
@@ -143,6 +143,9 @@
         default = "//nowhere:rustfmt.toml",
         allow_single_file = True,
     ),
+    # TODO(hlopko): Either 1) remove the unneeded `_error_format` and
+    # `_extra_rustc_flags` attributes below *or* 2) actually start using them
+    # (both for `rs_bindings_from_cc` and for `cc_bindings_from_rs`).
     "_error_format": attr.label(
         default = "@rules_rust//:error_format",
     ),