mirror of https://github.com/bazelbuild/rules_rust
Rust library group (#1848)
* Add rust_crate_group rule for grouping dependencies together. This PR introduces a rule `rust_crate_group` which groups together multiple dependencies, but does not itself have any sources or provide a crate. In other words, it is an alias for a set of other rust_library targets. For example, the following two build files are equivalent: No `rust_crate_group`: ```py rust_library( name = "foobar", deps = [ ":crate1", ":crate2", ], ... ) ``` With `rust_crate_group`: ```py rust_crate_group( name = "crate_group", deps = [ ":crate1", ":crate2", ], ) rust_library( name = "foobar", deps = [":crate_group"], ... ) ``` In some situations, especially when heavy macros are involved, this is the only to achieve certain things. We have a number of use cases where this is necessary, but they are complicated. I can try to come up with a simple one if you are interested in more motivation. It is possible to create an "alias group" with the C++ and Python rules by just having a `cc_library` or `py_library` with no sources, only deps. But this is not possible with Rust since `rust_library` does not transparently re-export its dependencies. * add rust_crate_group test * add runfiles to rust_crate_group * add rust-analyzer support * rename rust_crate_group -> rust_library_group * add test verifying that rust_library_group can be consumed by rust_test * add coverage support to rust_library_group --------- Co-authored-by: UebelAndre <github@uebelandre.com>
This commit is contained in:
parent
bc43f4841b
commit
a6b0a7f398
|
@ -30,6 +30,7 @@ load(
|
|||
"//rust/private:rust.bzl",
|
||||
_rust_binary = "rust_binary",
|
||||
_rust_library = "rust_library",
|
||||
_rust_library_group = "rust_library_group",
|
||||
_rust_proc_macro = "rust_proc_macro",
|
||||
_rust_shared_library = "rust_shared_library",
|
||||
_rust_static_library = "rust_static_library",
|
||||
|
@ -81,6 +82,9 @@ rust_proc_macro = _rust_proc_macro
|
|||
rust_binary = _rust_binary
|
||||
# See @rules_rust//rust/private:rust.bzl for a complete description.
|
||||
|
||||
rust_library_group = _rust_library_group
|
||||
# See @rules_rust//rust/private:rust.bzl for a complete description.
|
||||
|
||||
rust_test = _rust_test
|
||||
# See @rules_rust//rust/private:rust.bzl for a complete description.
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ which exports the `rust_common` struct.
|
|||
In the Bazel lingo, `rust_common` gives the access to the Rust Sandwich API.
|
||||
"""
|
||||
|
||||
load(":providers.bzl", "CrateInfo", "DepInfo", "StdLibInfo", "TestCrateInfo")
|
||||
load(":providers.bzl", "CrateGroupInfo", "CrateInfo", "DepInfo", "StdLibInfo", "TestCrateInfo")
|
||||
|
||||
# This constant only represents the default value for attributes and macros
|
||||
# defined in `rules_rust`. Like any attribute public attribute, it can be
|
||||
|
@ -61,5 +61,6 @@ rust_common = struct(
|
|||
dep_info = DepInfo,
|
||||
stdlib_info = StdLibInfo,
|
||||
test_crate_info = TestCrateInfo,
|
||||
crate_group_info = CrateGroupInfo,
|
||||
default_version = DEFAULT_RUST_VERSION,
|
||||
)
|
||||
|
|
|
@ -57,6 +57,14 @@ DepInfo = provider(
|
|||
},
|
||||
)
|
||||
|
||||
CrateGroupInfo = provider(
|
||||
doc = "A provider containing a group of crates.",
|
||||
fields = {
|
||||
"crate_infos": "List[CrateInfo]",
|
||||
"dep_infos": "List[DepInfo]",
|
||||
},
|
||||
)
|
||||
|
||||
BuildInfo = provider(
|
||||
doc = "A provider containing `rustc` build settings for a given Crate.",
|
||||
fields = {
|
||||
|
@ -72,11 +80,13 @@ BuildInfo = provider(
|
|||
DepVariantInfo = provider(
|
||||
doc = "A wrapper provider for a dependency of a crate. The dependency can be a Rust " +
|
||||
"dependency, in which case the `crate_info` and `dep_info` fields will be populated, " +
|
||||
"a Rust build script dependency, in which case `build_info` will be populated, or a " +
|
||||
"C/C++ dependency, in which case `cc_info` will be populated.",
|
||||
"a Rust build script dependency, in which case `build_info` will be populated, a C/C++" +
|
||||
"dependency, in which case `cc_info` will be populated, or a Rust crate group, in which" +
|
||||
"case `crate_group_info` will be populated.",
|
||||
fields = {
|
||||
"build_info": "BuildInfo: The BuildInfo of a Rust dependency",
|
||||
"cc_info": "CcInfo: The CcInfo of a C/C++ dependency",
|
||||
"crate_group_info": "CrateGroupInfo: The CrateGroupInfo of a Rust crate group dependency",
|
||||
"crate_info": "CrateInfo: The CrateInfo of a Rust dependency",
|
||||
"dep_info": "DepInfo: The DepInfo of a Rust dependency",
|
||||
},
|
||||
|
|
|
@ -490,6 +490,36 @@ def _rust_test_impl(ctx):
|
|||
|
||||
return providers
|
||||
|
||||
def _rust_library_group_impl(ctx):
|
||||
crate_infos = []
|
||||
dep_infos = []
|
||||
runfiles = []
|
||||
|
||||
for dep in ctx.attr.deps:
|
||||
if rust_common.crate_info in dep:
|
||||
crate_infos.append(dep[rust_common.crate_info])
|
||||
dep_infos.append(dep[rust_common.dep_info])
|
||||
elif rust_common.crate_group_info in dep:
|
||||
crate_infos.extend(dep[rust_common.crate_group_info].crate_infos)
|
||||
dep_infos.extend(dep[rust_common.crate_group_info].dep_infos)
|
||||
else:
|
||||
fail("crate_group_info targets can only depend on rust_library or rust_library_group targets.")
|
||||
|
||||
if dep[DefaultInfo].default_runfiles != None:
|
||||
runfiles.append(dep[DefaultInfo].default_runfiles)
|
||||
|
||||
return [
|
||||
rust_common.crate_group_info(
|
||||
crate_infos = crate_infos,
|
||||
dep_infos = dep_infos,
|
||||
),
|
||||
DefaultInfo(runfiles = ctx.runfiles().merge_all(runfiles)),
|
||||
coverage_common.instrumented_files_info(
|
||||
ctx,
|
||||
dependency_attributes = ["deps"],
|
||||
),
|
||||
]
|
||||
|
||||
def _stamp_attribute(default_value):
|
||||
return attr.int(
|
||||
doc = dedent("""\
|
||||
|
@ -1385,3 +1415,47 @@ def rust_test_suite(name, srcs, **kwargs):
|
|||
tests = tests,
|
||||
tags = kwargs.get("tags", None),
|
||||
)
|
||||
|
||||
rust_library_group = rule(
|
||||
implementation = _rust_library_group_impl,
|
||||
provides = [rust_common.crate_group_info],
|
||||
attrs = {
|
||||
"deps": attr.label_list(
|
||||
doc = "Other dependencies to forward through this crate group.",
|
||||
),
|
||||
},
|
||||
doc = dedent("""\
|
||||
Functions as an alias for a set of dependencies.
|
||||
|
||||
Specifically, the following are equivalent:
|
||||
|
||||
```rust
|
||||
rust_library_group(
|
||||
name = "crate_group",
|
||||
deps = [
|
||||
":crate1",
|
||||
":crate2",
|
||||
],
|
||||
)
|
||||
|
||||
rust_library(
|
||||
name = "foobar",
|
||||
deps = [":crate_group"],
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```rust
|
||||
rust_library(
|
||||
name = "foobar",
|
||||
deps = [
|
||||
":crate1",
|
||||
":crate2",
|
||||
],
|
||||
...
|
||||
)
|
||||
```
|
||||
"""),
|
||||
)
|
||||
|
|
|
@ -44,8 +44,17 @@ RustAnalyzerInfo = provider(
|
|||
},
|
||||
)
|
||||
|
||||
RustAnalyzerGroupInfo = provider(
|
||||
doc = "RustAnalyzerGroupInfo holds multiple RustAnalyzerInfos",
|
||||
fields = {
|
||||
"deps": "List[RustAnalyzerInfo]: direct dependencies",
|
||||
},
|
||||
)
|
||||
|
||||
def _rust_analyzer_aspect_impl(target, ctx):
|
||||
if rust_common.crate_info not in target and rust_common.test_crate_info not in target:
|
||||
if (rust_common.crate_info not in target and
|
||||
rust_common.test_crate_info not in target and
|
||||
rust_common.crate_group_info not in target):
|
||||
return []
|
||||
|
||||
toolchain = find_toolchain(ctx)
|
||||
|
@ -67,14 +76,33 @@ def _rust_analyzer_aspect_impl(target, ctx):
|
|||
build_info = dep[BuildInfo]
|
||||
dep_infos = [dep[RustAnalyzerInfo] for dep in ctx.rule.attr.deps if RustAnalyzerInfo in dep]
|
||||
|
||||
group_infos = [dep[RustAnalyzerGroupInfo] for dep in ctx.rule.attr.deps if RustAnalyzerGroupInfo in dep]
|
||||
for group_info in group_infos:
|
||||
dep_infos.extend(group_info.deps)
|
||||
|
||||
if hasattr(ctx.rule.attr, "proc_macro_deps"):
|
||||
dep_infos += [dep[RustAnalyzerInfo] for dep in ctx.rule.attr.proc_macro_deps if RustAnalyzerInfo in dep]
|
||||
if hasattr(ctx.rule.attr, "crate") and ctx.rule.attr.crate != None:
|
||||
dep_infos.append(ctx.rule.attr.crate[RustAnalyzerInfo])
|
||||
if hasattr(ctx.rule.attr, "actual") and ctx.rule.attr.actual != None and RustAnalyzerInfo in ctx.rule.attr.actual:
|
||||
dep_infos.append(ctx.rule.attr.actual[RustAnalyzerInfo])
|
||||
|
||||
crate_spec = ctx.actions.declare_file(ctx.label.name + ".rust_analyzer_crate_spec")
|
||||
group_infos = [dep[RustAnalyzerGroupInfo] for dep in ctx.rule.attr.proc_macro_deps if RustAnalyzerGroupInfo in dep]
|
||||
for group_info in group_infos:
|
||||
dep_infos.extend(group_info.deps)
|
||||
|
||||
if hasattr(ctx.rule.attr, "crate") and ctx.rule.attr.crate != None:
|
||||
if RustAnalyzerInfo in ctx.rule.attr.crate:
|
||||
dep_infos.append(ctx.rule.attr.crate[RustAnalyzerInfo])
|
||||
|
||||
if RustAnalyzerGroupInfo in ctx.rule.attr.crate:
|
||||
dep_infos.extend(ctx.rule.attr.crate[RustAnalyzerGroupInfo])
|
||||
|
||||
if hasattr(ctx.rule.attr, "actual") and ctx.rule.attr.actual != None:
|
||||
if RustAnalyzerInfo in ctx.rule.attr.actual:
|
||||
dep_infos.append(ctx.rule.attr.actual[RustAnalyzerInfo])
|
||||
|
||||
if RustAnalyzerGroupInfo in ctx.rule.attr.actual:
|
||||
dep_infos.extend(ctx.rule.attr.actul[RustAnalyzerGroupInfo])
|
||||
|
||||
if rust_common.crate_group_info in target:
|
||||
return [RustAnalyzerGroupInfo(deps = dep_infos)]
|
||||
|
||||
if rust_common.crate_info in target:
|
||||
crate_info = target[rust_common.crate_info]
|
||||
|
@ -83,6 +111,8 @@ def _rust_analyzer_aspect_impl(target, ctx):
|
|||
else:
|
||||
fail("Unexpected target type: {}".format(target))
|
||||
|
||||
crate_spec = ctx.actions.declare_file(ctx.label.name + ".rust_analyzer_crate_spec")
|
||||
|
||||
rust_analyzer_info = RustAnalyzerInfo(
|
||||
crate = crate_info,
|
||||
cfgs = cfgs,
|
||||
|
|
|
@ -219,8 +219,28 @@ def collect_deps(
|
|||
transitive_crate_outputs = []
|
||||
transitive_metadata_outputs = []
|
||||
|
||||
aliases = {k.label: v for k, v in aliases.items()}
|
||||
crate_deps = []
|
||||
for dep in depset(transitive = [deps, proc_macro_deps]).to_list():
|
||||
crate_group = None
|
||||
|
||||
if type(dep) == "Target" and rust_common.crate_group_info in dep:
|
||||
crate_group = dep[rust_common.crate_group_info]
|
||||
elif type(dep) == "struct" and hasattr(dep, "crate_group_info") and dep.crate_group_info != None:
|
||||
crate_group = dep.crate_group_info
|
||||
else:
|
||||
crate_deps.append(dep)
|
||||
|
||||
if crate_group:
|
||||
if len(crate_group.crate_infos) != len(crate_group.dep_infos):
|
||||
fail("CrateGroupInfo must have len(crate_infos) == len(dep_infos)")
|
||||
for (crate_info, dep_info) in zip(crate_group.crate_infos, crate_group.dep_infos):
|
||||
crate_deps.append(struct(
|
||||
crate_info = crate_info,
|
||||
dep_info = dep_info,
|
||||
))
|
||||
|
||||
aliases = {k.label: v for k, v in aliases.items()}
|
||||
for dep in crate_deps:
|
||||
(crate_info, dep_info) = _get_crate_and_dep_info(dep)
|
||||
cc_info = _get_cc_info(dep)
|
||||
dep_build_info = _get_build_info(dep)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"""Utility functions not specific to the rust toolchain."""
|
||||
|
||||
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", find_rules_cc_toolchain = "find_cpp_toolchain")
|
||||
load(":providers.bzl", "BuildInfo", "CrateInfo", "DepInfo", "DepVariantInfo")
|
||||
load(":providers.bzl", "BuildInfo", "CrateGroupInfo", "CrateInfo", "DepInfo", "DepVariantInfo")
|
||||
|
||||
UNSUPPORTED_FEATURES = [
|
||||
"thin_lto",
|
||||
|
@ -475,6 +475,7 @@ def transform_deps(deps):
|
|||
dep_info = dep[DepInfo] if DepInfo in dep else None,
|
||||
build_info = dep[BuildInfo] if BuildInfo in dep else None,
|
||||
cc_info = dep[CcInfo] if CcInfo in dep else None,
|
||||
crate_group_info = dep[CrateGroupInfo] if CrateGroupInfo in dep else None,
|
||||
) for dep in deps]
|
||||
|
||||
def get_import_macro_deps(ctx):
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
load("@rules_rust//rust:defs.bzl", "rust_library", "rust_library_group", "rust_test")
|
||||
|
||||
rust_library(
|
||||
name = "dep1",
|
||||
srcs = ["dep1.rs"],
|
||||
edition = "2021",
|
||||
)
|
||||
|
||||
rust_library(
|
||||
name = "dep2",
|
||||
srcs = ["dep2.rs"],
|
||||
edition = "2021",
|
||||
)
|
||||
|
||||
rust_library_group(
|
||||
name = "dep1_and_2",
|
||||
deps = [
|
||||
":dep1",
|
||||
":dep2",
|
||||
],
|
||||
)
|
||||
|
||||
rust_library(
|
||||
name = "library",
|
||||
srcs = ["lib.rs"],
|
||||
edition = "2021",
|
||||
deps = [":dep1_and_2"],
|
||||
)
|
||||
|
||||
rust_test(
|
||||
name = "test",
|
||||
srcs = ["test.rs"],
|
||||
edition = "2021",
|
||||
deps = [":dep1_and_2"],
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
pub fn dep1() {}
|
|
@ -0,0 +1 @@
|
|||
pub fn dep2() {}
|
|
@ -0,0 +1,4 @@
|
|||
fn _test() {
|
||||
dep1::dep1();
|
||||
dep2::dep2();
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
#[test]
|
||||
fn test() {
|
||||
dep1::dep1();
|
||||
dep2::dep2();
|
||||
}
|
Loading…
Reference in New Issue