diff --git a/rust/private/common.bzl b/rust/private/common.bzl index 62a833e8d6..1291ea9230 100644 --- a/rust/private/common.bzl +++ b/rust/private/common.bzl @@ -61,6 +61,8 @@ def _create_crate_info(**kwargs): kwargs.update({"rustc_env_files": []}) if not "data" in kwargs: kwargs.update({"data": depset([])}) + if not "root_path" in kwargs: + kwargs.update({"root_path": ""}) return CrateInfo(**kwargs) rust_common = struct( diff --git a/rust/private/providers.bzl b/rust/private/providers.bzl index a531884d19..e6ca1a4e62 100644 --- a/rust/private/providers.bzl +++ b/rust/private/providers.bzl @@ -36,6 +36,7 @@ CrateInfo = provider( "owner": "Label: The label of the target that produced this CrateInfo", "proc_macro_deps": "depset[DepVariantInfo]: This crate's rust proc_macro dependencies' providers.", "root": "File: The source File entrypoint to this crate, eg. lib.rs", + "root_path": "str: If root is a directory, path to the source entrypoint under it.", "rustc_env": "Dict[String, String]: Additional `\"key\": \"value\"` environment variables to set for rustc.", "rustc_env_files": "[File]: Files containing additional environment variables to set for rustc.", "rustc_output": "File: The output from rustc from producing the output file. It is optional.", diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl index 3543cd6ba3..0697c6c2f8 100644 --- a/rust/private/rust.bzl +++ b/rust/private/rust.bzl @@ -188,6 +188,27 @@ def _rust_proc_macro_impl(ctx): """ return _rust_library_common(ctx, "proc-macro") +def _validate_root_path(ctx): + """Validates that root_path is used iff there is no explicit crate_root and srcs is a single-element directory artifact.""" + if getattr(ctx.attr, "crate", None): + if getattr(ctx.attr, "root_path", ""): + fail("rust_test.crate and rust_test.root_path are mutually exclusive.") + return + + explicit_crate_root = getattr(ctx.file, "crate_root", None) != None + if explicit_crate_root and ctx.file.crate_root.is_directory: + fail("`crate_root` must be a file, not a directory. If you want to use a directory artifact as source, use `srcs` and `root_path` instead.") + + srcs = ctx.files.srcs + is_single_dir = len(srcs) == 1 and srcs[0].is_directory + should_use_root_path = (not explicit_crate_root) and is_single_dir + has_root_path = bool(getattr(ctx.attr, "root_path", "")) + + if should_use_root_path and not has_root_path: + fail("`root_path` must be specified when `srcs` is a single directory artifact and `crate_root` is not set.") + elif not should_use_root_path and has_root_path: + fail("`root_path` can only be used when `crate_root` is not set and `srcs` is a single directory artifact.") + def _rust_library_common(ctx, crate_type): """The common implementation of the library-like rules. @@ -198,6 +219,7 @@ def _rust_library_common(ctx, crate_type): Returns: list: A list of providers. See `rustc_compile_action` """ + _validate_root_path(ctx) _assert_no_deprecated_attributes(ctx) _assert_correct_dep_mapping(ctx) @@ -260,6 +282,7 @@ def _rust_library_common(ctx, crate_type): name = crate_name, type = crate_type, root = crate_root, + root_path = getattr(ctx.attr, "root_path", ""), srcs = srcs, deps = deps, proc_macro_deps = proc_macro_deps, @@ -290,6 +313,7 @@ def _rust_binary_impl(ctx): Returns: list: A list of providers. See `rustc_compile_action` """ + _validate_root_path(ctx) toolchain = find_toolchain(ctx) crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name) _assert_correct_dep_mapping(ctx) @@ -327,6 +351,7 @@ def _rust_binary_impl(ctx): name = crate_name, type = ctx.attr.crate_type, root = crate_root, + root_path = getattr(ctx.attr, "root_path", ""), srcs = srcs, deps = deps, proc_macro_deps = proc_macro_deps, @@ -383,6 +408,7 @@ def _rust_test_impl(ctx): Returns: list: The list of providers. See `rustc_compile_action` """ + _validate_root_path(ctx) _assert_no_deprecated_attributes(ctx) _assert_correct_dep_mapping(ctx) @@ -451,6 +477,7 @@ def _rust_test_impl(ctx): name = crate_name, type = crate_type, root = crate.root, + root_path = crate.root_path, srcs = srcs, deps = depset(deps, transitive = [crate.deps]).to_list(), proc_macro_deps = depset(proc_macro_deps, transitive = [crate.proc_macro_deps]).to_list(), @@ -508,6 +535,7 @@ def _rust_test_impl(ctx): name = crate_name, type = crate_type, root = crate_root, + root_path = getattr(ctx.attr, "root_path", ""), srcs = srcs, deps = deps, proc_macro_deps = proc_macro_deps, @@ -728,6 +756,9 @@ _COMMON_ATTRS = { If `crate_root` is not set, then this rule will look for a `lib.rs` file (or `main.rs` for rust_binary) or the single file in `srcs` if `srcs` contains only one file. + + If the `srcs` contains only one file and that file is a directory, + use `root_path` to specify the path to the crate root .rs file under that directory. """), allow_single_file = [".rs"], ), @@ -784,6 +815,9 @@ _COMMON_ATTRS = { values = [-1, 0, 1], default = -1, ), + "root_path": attr.string( + doc = """If the crate root (single member of the `srcs` list) is a directory, this is the path to the crate root `.rs` file under that directory.""", + ), "rustc_env": attr.string_dict( doc = dedent("""\ Dictionary of additional `"key": "value"` environment variables to set for rustc. diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index 71afd9c868..725a00aa41 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -14,6 +14,7 @@ """Functionality for constructing actions that invoke the Rust compiler""" +load("@bazel_skylib//lib:paths.bzl", "paths") load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") load( "@bazel_tools//tools/build_defs/cc:action_names.bzl", @@ -1156,7 +1157,7 @@ def construct_arguments( rustc_flags = ctx.actions.args() rustc_flags.set_param_file_format("multiline") rustc_flags.use_param_file("@%s", use_always = bool(ctx.executable._process_wrapper)) - rustc_flags.add(crate_info.root) + rustc_flags.add_all([(crate_info.root, crate_info.root_path)], map_each = _get_crate_root_path) rustc_flags.add(crate_info.name, format = "--crate-name=%s") rustc_flags.add(crate_info.type, format = "--crate-type=%s") @@ -2871,6 +2872,13 @@ def _add_native_link_flags( format_each = "-lstatic=%s", ) +def _get_crate_root_path(args): + file, root_path = args + if file.is_directory: + return paths.join(file.path, root_path) + else: + return file.path + def _get_dirname(file): """A helper function for `_add_native_link_flags`. diff --git a/rust/private/rustdoc.bzl b/rust/private/rustdoc.bzl index b3c21b8057..8c530a8a10 100644 --- a/rust/private/rustdoc.bzl +++ b/rust/private/rustdoc.bzl @@ -41,6 +41,7 @@ def _strip_crate_info_output(crate_info): name = crate_info.name, type = crate_info.type, root = crate_info.root, + root_path = crate_info.root_path, srcs = crate_info.srcs, deps = crate_info.deps, proc_macro_deps = crate_info.proc_macro_deps, diff --git a/rust/private/rustdoc_test.bzl b/rust/private/rustdoc_test.bzl index bf961b36a9..733444eff7 100644 --- a/rust/private/rustdoc_test.bzl +++ b/rust/private/rustdoc_test.bzl @@ -117,6 +117,7 @@ def _create_crate_info(ctx): name = crate.name, type = crate.type, root = crate.root, + root_path = crate.root_path, srcs = crate.srcs, deps = depset(deps, transitive = [crate.deps]), proc_macro_deps = depset(proc_macro_deps, transitive = [crate.proc_macro_deps]), diff --git a/test/root_path/BUILD.bazel b/test/root_path/BUILD.bazel new file mode 100644 index 0000000000..3c56567327 --- /dev/null +++ b/test/root_path/BUILD.bazel @@ -0,0 +1,31 @@ +load("@bazel_skylib//rules:build_test.bzl", "build_test") +load("//rust:defs.bzl", "rust_library", "rust_test") +load(":defs.bzl", "package_dir_artifact") + +package(default_visibility = ["//visibility:private"]) + +package_dir_artifact( + name = "dir_artifact", + srcs = [ + "src/lib.rs", + "src/submodule.rs", + ], +) + +rust_library( + name = "my_lib", + srcs = [":dir_artifact"], + root_path = "src/lib.rs", +) + +rust_test( + name = "my_lib_test", + crate = ":my_lib", +) + +build_test( + name = "build_test", + targets = [ + ":my_lib", + ], +) diff --git a/test/root_path/defs.bzl b/test/root_path/defs.bzl new file mode 100644 index 0000000000..e56b98b633 --- /dev/null +++ b/test/root_path/defs.bzl @@ -0,0 +1,31 @@ +"""Custom rule to package sources into a directory TreeArtifact for testing root_path.""" + +def _package_dir_artifact_impl(ctx): + outdir = ctx.actions.declare_directory(ctx.attr.name + ".dir") + + args = ctx.actions.args() + args.add(outdir.path) + for src in ctx.files.srcs: + args.add(src.path) + + ctx.actions.run_shell( + outputs = [outdir], + inputs = ctx.files.srcs, + command = 'out="$1"; shift; mkdir -p "$out/src"; cp "$@" "$out/src/"', + arguments = [args], + progress_message = "Packaging srcs into directory artifact %s" % outdir.short_path, + ) + + return [ + DefaultInfo(files = depset([outdir])), + ] + +package_dir_artifact = rule( + implementation = _package_dir_artifact_impl, + attrs = { + "srcs": attr.label_list( + allow_files = True, + mandatory = True, + ), + }, +) diff --git a/test/root_path/src/lib.rs b/test/root_path/src/lib.rs new file mode 100644 index 0000000000..aa21589f4b --- /dev/null +++ b/test/root_path/src/lib.rs @@ -0,0 +1,15 @@ +pub mod submodule; + +pub fn greeting() -> &'static str { + submodule::msg() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_greeting() { + assert_eq!(greeting(), "Hello from directory artifact!"); + } +} diff --git a/test/root_path/src/submodule.rs b/test/root_path/src/submodule.rs new file mode 100644 index 0000000000..bda56e17f4 --- /dev/null +++ b/test/root_path/src/submodule.rs @@ -0,0 +1,3 @@ +pub fn msg() -> &'static str { + "Hello from directory artifact!" +}