Support crates that have mix of generated and nongenerated inputs (#1340)
Rustc expects the crate's sources to exist next to the crate's root.
When a rust target specifies sources that are a mixture of generated and non-generated files, some of them will be located in the source directory, while others under `bazel-out`, which violates the above expectation.
This PR adds support for specifying both generated and non-generated sources in the same target by creating symlinks for the non-generated sources.
diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl
index cfc5985..81a86b1 100644
--- a/rust/private/rust.bzl
+++ b/rust/private/rust.bzl
@@ -123,11 +123,66 @@
else:
return toolchain.default_edition
-def crate_root_src(attr, srcs, crate_type):
- """Finds the source file for the crate root.
+def _transform_sources(ctx, srcs, crate_root):
+ """Creates symlinks of the source files if needed.
+
+ Rustc assumes that the source files are located next to the crate root.
+ In case of a mix between generated and non-generated source files, this
+ we violate this assumption, as part of the sources will be located under
+ bazel-out/... . In order to allow for targets that contain both generated
+ and non-generated source files, we generate symlinks for all non-generated
+ files.
Args:
- attr (struct): The attributes of the current target
+ ctx (struct): The current rule's context.
+ srcs (List[File]): The sources listed in the `srcs` attribute
+ crate_root (File): The file specified in the `crate_root` attribute,
+ if it exists, otherwise None
+
+ Returns:
+ Tuple(List[File], File): The transformed srcs and crate_root
+ """
+ has_generated_sources = len([src for src in srcs if not src.is_source]) > 0
+
+ if not has_generated_sources:
+ return srcs, crate_root
+
+ generated_sources = []
+
+ generated_root = crate_root
+
+ if crate_root and (crate_root.is_source or crate_root.root.path != ctx.bin_dir.path):
+ generated_root = ctx.actions.declare_file(crate_root.basename)
+ ctx.actions.symlink(
+ output = generated_root,
+ target_file = crate_root,
+ progress_message = "Creating symlink to source file: {}".format(crate_root.path),
+ )
+ if generated_root:
+ generated_sources.append(generated_root)
+
+ for src in srcs:
+ # We took care of the crate root above.
+ if src == crate_root:
+ continue
+ if src.is_source or src.root.path != ctx.bin_dir.path:
+ src_symlink = ctx.actions.declare_file(src.basename)
+ ctx.actions.symlink(
+ output = src_symlink,
+ target_file = src,
+ progress_message = "Creating symlink to source file: {}".format(src.path),
+ )
+ generated_sources.append(src_symlink)
+ else:
+ generated_sources.append(src)
+
+ return generated_sources, generated_root
+
+def crate_root_src(name, srcs, crate_type):
+ """Determines the source file for the crate root, should it not be specified in `attr.crate_root`.
+
+ Args:
+ name (str): The name of the target.
srcs (list): A list of all sources for the target Crate.
crate_type (str): The type of this crate ("bin", "lib", "rlib", "cdylib", etc).
@@ -138,19 +193,13 @@
"""
default_crate_root_filename = "main.rs" if crate_type == "bin" else "lib.rs"
- crate_root = None
- if hasattr(attr, "crate_root"):
- if attr.crate_root:
- crate_root = attr.crate_root.files.to_list()[0]
-
+ crate_root = (
+ (srcs[0] if len(srcs) == 1 else None) or
+ _shortest_src_with_basename(srcs, default_crate_root_filename) or
+ _shortest_src_with_basename(srcs, name + ".rs")
+ )
if not crate_root:
- crate_root = (
- (srcs[0] if len(srcs) == 1 else None) or
- _shortest_src_with_basename(srcs, default_crate_root_filename) or
- _shortest_src_with_basename(srcs, attr.name + ".rs")
- )
- if not crate_root:
- file_names = [default_crate_root_filename, attr.name + ".rs"]
+ file_names = [default_crate_root_filename, name + ".rs"]
fail("No {} source file found.".format(" or ".join(file_names)), "srcs")
return crate_root
@@ -240,8 +289,9 @@
list: A list of providers. See `rustc_compile_action`
"""
- # Find lib.rs
- crate_root = crate_root_src(ctx.attr, ctx.files.srcs, "lib")
+ srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, getattr(ctx.file, "crate_root", None))
+ if not crate_root:
+ crate_root = crate_root_src(ctx.attr.name, srcs, "lib")
_assert_no_deprecated_attributes(ctx)
_assert_correct_dep_mapping(ctx)
@@ -277,7 +327,7 @@
name = crate_name,
type = crate_type,
root = crate_root,
- srcs = depset(ctx.files.srcs),
+ srcs = depset(srcs),
deps = depset(deps),
proc_macro_deps = depset(proc_macro_deps),
aliases = ctx.attr.aliases,
@@ -309,6 +359,10 @@
deps = transform_deps(ctx.attr.deps)
proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
+ srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, getattr(ctx.file, "crate_root", None))
+ if not crate_root:
+ crate_root = crate_root_src(ctx.attr.name, srcs, ctx.attr.crate_type)
+
return rustc_compile_action(
ctx = ctx,
attr = ctx.attr,
@@ -316,8 +370,8 @@
crate_info = rust_common.create_crate_info(
name = crate_name,
type = ctx.attr.crate_type,
- root = crate_root_src(ctx.attr, ctx.files.srcs, ctx.attr.crate_type),
- srcs = depset(ctx.files.srcs),
+ root = crate_root,
+ srcs = depset(srcs),
deps = depset(deps),
proc_macro_deps = depset(proc_macro_deps),
aliases = ctx.attr.aliases,
@@ -344,6 +398,7 @@
_assert_no_deprecated_attributes(ctx)
_assert_correct_dep_mapping(ctx)
+ srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, getattr(ctx.file, "crate_root", None))
crate_type = "bin"
deps = transform_deps(ctx.attr.deps)
@@ -364,7 +419,7 @@
name = crate.name,
type = crate_type,
root = crate.root,
- srcs = depset(ctx.files.srcs, transitive = [crate.srcs]),
+ srcs = depset(srcs, transitive = [crate.srcs]),
deps = depset(deps, transitive = [crate.deps]),
proc_macro_deps = depset(proc_macro_deps, transitive = [crate.proc_macro_deps]),
aliases = ctx.attr.aliases,
@@ -377,12 +432,15 @@
owner = ctx.label,
)
else:
+ if not crate_root:
+ crate_root = crate_root_src(ctx.attr.name, ctx.files.srcs, "lib")
+
# Target is a standalone crate. Build the test binary as its own crate.
crate_info = rust_common.create_crate_info(
name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name),
type = crate_type,
- root = crate_root_src(ctx.attr, ctx.files.srcs, "lib"),
- srcs = depset(ctx.files.srcs),
+ root = crate_root,
+ srcs = depset(srcs),
deps = depset(deps),
proc_macro_deps = depset(proc_macro_deps),
aliases = ctx.attr.aliases,
diff --git a/test/generated_inputs/BUILD.bazel b/test/generated_inputs/BUILD.bazel
new file mode 100644
index 0000000..3ed32d9
--- /dev/null
+++ b/test/generated_inputs/BUILD.bazel
@@ -0,0 +1,123 @@
+load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
+load("@bazel_skylib//rules:write_file.bzl", "write_file")
+load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")
+load(":input_from_different_cfg.bzl", "input_from_different_cfg")
+
+write_file(
+ name = "generate_src",
+ out = "src.rs",
+ content = """
+#[cfg(not(generated_file_as_root))]
+pub fn forty_two() -> i32 { 42 }
+
+#[cfg(generated_file_as_root)]
+mod lib_for_src;
+
+#[cfg(generated_file_as_root)]
+pub fn get_forty_two_as_string() -> String {
+ format!("{}", lib_for_src::forty_two())
+}
+""".splitlines(),
+ newline = "unix",
+)
+
+rust_library(
+ name = "use_generated_src",
+ srcs = [
+ "lib.rs",
+ ":src.rs",
+ ],
+ edition = "2018",
+)
+
+rust_library(
+ name = "use_generated_src_with_crate_root_defined",
+ srcs = [
+ "lib.rs",
+ ":src.rs",
+ ],
+ crate_root = "lib.rs",
+ edition = "2018",
+)
+
+rust_library(
+ name = "use_generated_src_as_crate_root",
+ srcs = [
+ "lib.rs",
+ "lib_for_src.rs",
+ ":src.rs",
+ ],
+ crate_root = ":src.rs",
+ edition = "2018",
+ rustc_flags = ["--cfg=generated_file_as_root"],
+)
+
+# When no lib.rs, main.rs file exists, we try to use the file that carries
+# the target's name as a crate_root.
+rust_library(
+ name = "src",
+ srcs = [
+ "lib_for_src.rs",
+ ":src.rs",
+ ],
+ edition = "2018",
+ rustc_flags = ["--cfg=generated_file_as_root"],
+)
+
+rust_test(
+ name = "generated_src_test",
+ crate = ":use_generated_src_with_crate_root_defined",
+ edition = "2018",
+)
+
+rust_binary(
+ name = "print_42",
+ srcs = [
+ "main.rs",
+ ":src.rs",
+ ],
+ edition = "2018",
+)
+
+input_from_different_cfg(
+ name = "generated_in_different_cfg",
+)
+
+filegroup(
+ name = "input_from_different_cfg",
+ srcs = [":generated_in_different_cfg"],
+ output_group = "generated_file",
+)
+
+rust_library(
+ name = "generated_src_in_different_cfg_as_root",
+ srcs = [":input_from_different_cfg"],
+ edition = "2018",
+)
+
+rust_test(
+ name = "generated_src_in_different_cfg_as_root_test",
+ crate = "generated_src_in_different_cfg_as_root",
+ edition = "2018",
+)
+
+rust_library(
+ name = "generated_src_in_different_cfg",
+ srcs = [
+ "root.rs",
+ ":input_from_different_cfg",
+ ],
+ crate_root = "root.rs",
+ edition = "2018",
+)
+
+rust_test(
+ name = "generated_src_in_different_cfg_test",
+ crate = "generated_src_in_different_cfg",
+ edition = "2018",
+)
+
+bool_flag(
+ name = "change_cfg",
+ build_setting_default = False,
+)
diff --git a/test/generated_inputs/input_from_different_cfg.bzl b/test/generated_inputs/input_from_different_cfg.bzl
new file mode 100644
index 0000000..5fb2a68
--- /dev/null
+++ b/test/generated_inputs/input_from_different_cfg.bzl
@@ -0,0 +1,42 @@
+"""A custom rule that generates a .rs file in a different configuration."""
+
+def _change_cfg_impl(_settings, _attr):
+ return {"//test/generated_inputs:change_cfg": True}
+
+change_cfg_transition = transition(
+ implementation = _change_cfg_impl,
+ inputs = [],
+ outputs = ["//test/generated_inputs:change_cfg"],
+)
+
+def _input_from_different_cfg_impl(ctx):
+ rs_file = ctx.actions.declare_file(ctx.label.name + ".rs")
+
+ ctx.actions.write(
+ output = rs_file,
+ content = """
+pub fn generated_fn() -> String {
+ "Generated".to_owned()
+}
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn test_generated() {
+ assert_eq!(super::generated_fn(), "Generated".to_owned());
+ }
+}
+""",
+ )
+
+ return OutputGroupInfo(generated_file = depset([rs_file]))
+
+input_from_different_cfg = rule(
+ implementation = _input_from_different_cfg_impl,
+ attrs = {
+ "_allowlist_function_transition": attr.label(
+ default = Label("@bazel_tools//tools/allowlists/function_transition_allowlist"),
+ ),
+ },
+ cfg = change_cfg_transition,
+)
diff --git a/test/generated_inputs/lib.rs b/test/generated_inputs/lib.rs
new file mode 100644
index 0000000..5982f9d
--- /dev/null
+++ b/test/generated_inputs/lib.rs
@@ -0,0 +1,18 @@
+mod src;
+
+pub fn forty_two_as_string() -> String {
+ format!("{}", src::forty_two())
+}
+
+#[cfg(test)]
+mod test {
+ #[test]
+ fn test_forty_two() {
+ assert_eq!(super::src::forty_two(), 42);
+ }
+
+ #[test]
+ fn test_forty_two_as_string() {
+ assert_eq!(super::forty_two_as_string(), "42");
+ }
+}
diff --git a/test/generated_inputs/lib_for_src.rs b/test/generated_inputs/lib_for_src.rs
new file mode 100644
index 0000000..697bc9d
--- /dev/null
+++ b/test/generated_inputs/lib_for_src.rs
@@ -0,0 +1 @@
+pub fn forty_two() -> i32 { 42 }
diff --git a/test/generated_inputs/main.rs b/test/generated_inputs/main.rs
new file mode 100644
index 0000000..5fa52c3
--- /dev/null
+++ b/test/generated_inputs/main.rs
@@ -0,0 +1,4 @@
+mod src;
+fn main() {
+ println!("{}", &src::forty_two());
+}
diff --git a/test/generated_inputs/root.rs b/test/generated_inputs/root.rs
new file mode 100644
index 0000000..3c59428
--- /dev/null
+++ b/test/generated_inputs/root.rs
@@ -0,0 +1,13 @@
+mod generated_in_different_cfg;
+
+pub fn use_generated_fn() -> String {
+ "Using generated function: ".to_owned() + &generated_in_different_cfg::generated_fn()
+}
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn test_use_generated_fn() {
+ assert_eq!(super::use_generated_fn(), "Using generated function: Generated".to_owned());
+ }
+}