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:
William Smith 2023-06-19 08:23:23 -07:00 committed by GitHub
parent bc43f4841b
commit a6b0a7f398
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 197 additions and 11 deletions

View File

@ -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.

View File

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

View File

@ -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",
},

View File

@ -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",
],
...
)
```
"""),
)

View File

@ -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,

View File

@ -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)

View File

@ -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):

View File

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

View File

@ -0,0 +1 @@
pub fn dep1() {}

View File

@ -0,0 +1 @@
pub fn dep2() {}

View File

@ -0,0 +1,4 @@
fn _test() {
dep1::dep1();
dep2::dep2();
}

View File

@ -0,0 +1,5 @@
#[test]
fn test() {
dep1::dep1();
dep2::dep2();
}