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",
),