Skip to content

Commit ee70df0

Browse files
committed
Better handling of self-edges
1 parent 22d8ba4 commit ee70df0

10 files changed

Lines changed: 102 additions & 6 deletions

File tree

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,17 @@ use_repo(crate, "crates")
134134

135135
`crate.spec` and vendoring mode are currently unsupported.
136136

137+
### Self dev-dependencies
138+
139+
Cargo workspaces sometimes use a self-referencing dev-dependency to enable extra features for tests:
140+
141+
```toml
142+
[dev-dependencies]
143+
mycrate = { path = ".", features = ["test-utils"] }
144+
```
145+
146+
`rules_rs` suppresses the generated self-edge in `aliases()` and `all_crate_deps()` so this pattern does not create a Bazel dependency cycle. The requested features are still part of the workspace feature resolution, so they may be enabled on the first-party crate more broadly than Cargo would enable them for a single targeted test command. If you need separate normal and test feature variants, model them as separate Bazel targets, with the test-only variant setting the extra `crate_features` and `testonly = True`.
147+
137148
### Exec vs target triple caveats
138149

139150
- Windows: the default Windows **exec** toolchain is MSVC-flavored. The upstream `gnullvm` toolchain dynamically links `libunwind`, which may not exist on a stock Windows machine.

rs/extensions.bzl

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,7 @@ RESOLVED_PLATFORMS = select({{
10121012
build_deps = {triple: set() for triple in platform_triples}
10131013
dev_deps = {triple: set() for triple in platform_triples}
10141014
package_dir = _manifest_package_dir(package["manifest_path"], repo_root)
1015+
package_manifest_dir = _normalize_path(package["manifest_path"]).removesuffix("/Cargo.toml")
10151016
binaries = {}
10161017
shared_libraries = {}
10171018
feature_resolutions = feature_resolutions_by_fq_crate.get(_fq_crate(package["name"], package["version"]))
@@ -1036,18 +1037,21 @@ RESOLVED_PLATFORMS = select({{
10361037

10371038
for dep in package["dependencies"]:
10381039
bazel_target = dep.get("bazel_target")
1040+
dep_path = dep.get("path")
10391041
if not bazel_target:
1040-
dep_path = dep.get("path")
10411042
if not dep_path:
10421043
# Git or registry deps without a bazel_target are resolved
10431044
# elsewhere (e.g., as external crate repos). Skip here.
10441045
continue
10451046
bazel_target = "//" + paths.join(workspace_package, _normalize_path(dep_path).removeprefix(repo_root + "/"))
10461047

1047-
if dep.get("rename"):
1048-
aliases[bazel_target] = dep["rename"].replace("-", "_")
1049-
elif dep.get("path"):
1050-
aliases[bazel_target] = dep["name"].replace("-", "_")
1048+
is_self_dep = dep_path and _normalize_path(dep_path) == package_manifest_dir
1049+
1050+
if not is_self_dep:
1051+
if dep.get("rename"):
1052+
aliases[bazel_target] = dep["rename"].replace("-", "_")
1053+
elif dep_path:
1054+
aliases[bazel_target] = dep["name"].replace("-", "_")
10511055

10521056
target = dep.get("target")
10531057
match_info = _cfg_match_info_for_target(target, platform_cfg_attrs, cfg_match_cache)
@@ -1068,6 +1072,9 @@ RESOLVED_PLATFORMS = select({{
10681072
if dep_name not in triple_features and ("dep:" + dep_name) not in triple_features:
10691073
continue
10701074

1075+
if is_self_dep:
1076+
continue
1077+
10711078
target_deps[triple].add(bazel_target)
10721079

10731080
if feature_resolutions:

test/BUILD.bazel

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ load("@bazel_lib//lib:transitions.bzl", "platform_transition_filegroup")
22
load("@build_script_aliases//:data.bzl", build_script_aliases_dep_data = "DEP_DATA")
33
load("@rules_rs//rs:rust_binary.bzl", "rust_binary")
44
load("@rules_shell//shell:sh_test.bzl", "sh_test")
5+
load("@self_dev_dependency//:data.bzl", self_dev_dependency_dep_data = "DEP_DATA")
6+
load("@self_dev_dependency//:defs.bzl", self_dev_dependency_aliases = "aliases")
57
load("@target_workspace_dep_feature//:data.bzl", target_workspace_dep_feature_dep_data = "DEP_DATA")
68
load("@workspace_default_features//:data.bzl", workspace_default_features_dep_data = "DEP_DATA")
79
load("@workspace_hyphen_dep_aliases//:defs.bzl", workspace_hyphen_dep_aliases_aliases = "aliases")
810
load("@workspace_renamed_path_dep_aliases//:defs.bzl", workspace_renamed_path_dep_aliases_aliases = "aliases")
9-
load("//:verify_aliases.bzl", "verify_alias", "verify_crate_feature_absent", "verify_crate_feature_present", "verify_dep_absent", "verify_dep_present")
11+
load("//:verify_aliases.bzl", "verify_alias", "verify_alias_absent", "verify_crate_feature_absent", "verify_crate_feature_present", "verify_dep_absent", "verify_dep_present", "verify_dev_dep_absent")
1012

1113
sh_test(
1214
name = "dummy_test",
@@ -146,6 +148,24 @@ verify_alias(
146148
expected_label = "//workspace_renamed_path_dep_aliases/dep-crate",
147149
)
148150

151+
verify_alias_absent(
152+
name = "verify_self_dev_dependency_no_self_alias",
153+
aliases = self_dev_dependency_aliases(package_name = "self_dev_dependency/mycrate"),
154+
unexpected_label = "//self_dev_dependency/mycrate",
155+
)
156+
157+
verify_dev_dep_absent(
158+
name = "verify_self_dev_dependency_no_self_dev_dep",
159+
dep_data = self_dev_dependency_dep_data["self_dev_dependency/mycrate"],
160+
unexpected = "//self_dev_dependency/mycrate",
161+
)
162+
163+
verify_crate_feature_present(
164+
name = "verify_self_dev_dependency_feature_present",
165+
dep_data = self_dev_dependency_dep_data["self_dev_dependency/mycrate"],
166+
expected = "test-utils",
167+
)
168+
149169
verify_dep_present(
150170
name = "verify_single_crate_dep_data_key",
151171
dep_data = build_script_aliases_dep_data["build_script_aliases"],
@@ -229,6 +249,9 @@ filegroup(
229249
":verify_cfg_feature_target_dep",
230250
":verify_first_party_feature_propagation",
231251
":verify_rust_analyzer_toolchain",
252+
":verify_self_dev_dependency_feature_present",
253+
":verify_self_dev_dependency_no_self_alias",
254+
":verify_self_dev_dependency_no_self_dev_dep",
232255
":verify_single_crate_dep_data_key",
233256
":verify_target_workspace_dep_feature_dep_absent",
234257
":verify_target_workspace_dep_feature_feature_absent",
@@ -239,6 +262,7 @@ filegroup(
239262
":verify_workspace_member_annotation_features",
240263
":verify_workspace_renamed_path_dep_aliases",
241264
"//prost:echo_rust_proto",
265+
"//self_dev_dependency/mycrate",
242266
"@binaries//:protoc-gen-prost-extra",
243267
"@binaries//:protoc-gen-prost__protoc-gen-prost",
244268
] + select({

test/MODULE.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ TESTS = [
144144
"non_optional_dep_feature",
145145
"optional_build_deps",
146146
"platform_dep_features",
147+
"self_dev_dependency",
147148
"target_workspace_dep_feature",
148149
"vendored_crate_override",
149150
"workspace_default_features",

test/self_dev_dependency/Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[workspace]
2+
resolver = "2"
3+
members = ["mycrate"]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
load("@rules_rs//rs:rust_library.bzl", "rust_library")
2+
load("@self_dev_dependency//:defs.bzl", "aliases", "all_crate_deps")
3+
4+
rust_library(
5+
name = "mycrate",
6+
srcs = glob(["src/**/*.rs"]),
7+
aliases = aliases(),
8+
crate_name = "mycrate",
9+
deps = all_crate_deps(normal = True),
10+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "mycrate"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[features]
7+
test-utils = []
8+
9+
[dev-dependencies]
10+
mycrate = { path = ".", features = ["test-utils"] }
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#[cfg(feature = "test-utils")]
2+
pub fn test_helper() {}

test/verify_aliases.bzl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ verify_alias = rule(
2323
},
2424
)
2525

26+
def verify_alias_absent(name, aliases, unexpected_label):
27+
_verify_absent(
28+
name = name,
29+
items = sorted(aliases.keys()),
30+
unexpected = unexpected_label,
31+
)
32+
2633
def _verify_absent_impl(ctx):
2734
if ctx.attr.unexpected in ctx.attr.items:
2835
fail("Did not expect %r in %r" % (ctx.attr.unexpected, ctx.attr.items))
@@ -66,6 +73,17 @@ def verify_dep_absent(name, dep_data, unexpected):
6673
unexpected = unexpected,
6774
)
6875

76+
def verify_dev_dep_absent(name, dep_data, unexpected):
77+
items = list(dep_data.get("dev_deps", []))
78+
for values in dep_data.get("dev_deps_by_platform", {}).values():
79+
items.extend(values)
80+
81+
_verify_absent(
82+
name = name,
83+
items = sorted(items),
84+
unexpected = unexpected,
85+
)
86+
6987
def verify_dep_present(name, dep_data, expected):
7088
items = list(dep_data.get("deps", []))
7189
for values in dep_data.get("deps_by_platform", {}).values():

0 commit comments

Comments
 (0)