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());
+    }
+}