Add regression testing for override_target annotations (#4248)
This is a followup to https://github.com/bazelbuild/rules_rust/pull/4226
diff --git a/crate_universe/src/context/crate_context.rs b/crate_universe/src/context/crate_context.rs
index 8e14efe..28d23b0 100644
--- a/crate_universe/src/context/crate_context.rs
+++ b/crate_universe/src/context/crate_context.rs
@@ -979,6 +979,25 @@
.unwrap()
}
+ /// `crate.annotation` takes these keys verbatim, and `extensions.bzl`
+ /// spells them out by hand. An unknown key is silently ignored, so pin the
+ /// exact strings.
+ #[test]
+ fn override_target_keys() {
+ let attrs = TargetAttributes::default();
+ let rules = [
+ Rule::Library(attrs.clone()),
+ Rule::ProcMacro(attrs.clone()),
+ Rule::Binary(attrs.clone()),
+ Rule::BuildScript(attrs),
+ ];
+
+ assert_eq!(
+ rules.map(|rule| rule.override_target_key()),
+ ["lib", "proc-macro", "bin", "custom-build"],
+ );
+ }
+
#[test]
fn new_context() {
let annotations = common_annotations();
diff --git a/crate_universe/src/rendering.rs b/crate_universe/src/rendering.rs
index fb33d59..10b9752 100644
--- a/crate_universe/src/rendering.rs
+++ b/crate_universe/src/rendering.rs
@@ -1385,6 +1385,100 @@
assert!(build_file_content.contains("name = \"_bs\""));
}
+ /// Renders a crate exposing one target of every [`Rule`] kind, so that any
+ /// `override_targets` key has something to match against.
+ fn render_override_targets_build_file(override_targets: BTreeMap<String, Label>) -> String {
+ fn target(crate_name: &str) -> TargetAttributes {
+ TargetAttributes {
+ crate_name: crate_name.to_owned(),
+ ..mock_target_attributes()
+ }
+ }
+
+ let mut context = Context::default();
+ let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
+ context.crates.insert(
+ crate_id.clone(),
+ CrateContext {
+ name: crate_id.name,
+ version: crate_id.version,
+ package_url: None,
+ repository: None,
+ targets: BTreeSet::from([
+ Rule::Library(target("mock_crate")),
+ Rule::ProcMacro(target("mock_crate_proc_macro")),
+ Rule::Binary(target("mock_crate_bin")),
+ Rule::BuildScript(target("build_script_build")),
+ ]),
+ library_target_name: Some("mock_crate".to_owned()),
+ common_attrs: CommonAttributes::default(),
+ build_script_attrs: Some(BuildScriptAttributes::default()),
+ license: None,
+ license_ids: BTreeSet::default(),
+ license_file: None,
+ additive_build_file_content: None,
+ disable_pipelining: false,
+ extra_aliased_targets: BTreeMap::default(),
+ alias_rule: None,
+ override_targets,
+ },
+ );
+
+ let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples());
+ let mut output = renderer.render(&context, None).unwrap();
+
+ output
+ .remove(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
+ .unwrap()
+ }
+
+ /// Every key returned by [`Rule::override_target_key`] must swap the
+ /// generated rule out for an `alias`. `crate.annotation` accepts these
+ /// strings verbatim, so this is the contract `crate_universe/extensions.bzl`
+ /// has to spell correctly.
+ #[test]
+ fn render_override_targets() {
+ // Overrides are applied one at a time so a key can only pass by
+ // aliasing its own target.
+ for (key, target_name, rule) in [
+ ("lib", "mock_crate", "rust_library("),
+ ("proc-macro", "mock_crate_proc_macro", "rust_proc_macro("),
+ ("bin", "mock_crate_bin", "rust_binary("),
+ ("custom-build", "build_script_build", "cargo_build_script("),
+ ] {
+ let content = render_override_targets_build_file(BTreeMap::from([(
+ key.to_owned(),
+ Label::from_str("@//custom:override").unwrap(),
+ )]));
+
+ assert!(
+ content.contains(&format!("name = \"{target_name}\""))
+ && content.contains("actual = \"@//custom:override\""),
+ "`{key}` did not alias `{target_name}`\n```\n{content}```\n",
+ );
+ assert!(
+ !content.contains(rule),
+ "`{rule}` should have been overridden by `{key}`\n```\n{content}```\n",
+ );
+ }
+ }
+
+ /// Keys that aren't produced by [`Rule::override_target_key`] are silently
+ /// ignored rather than rejected, which is why the underscored spelling
+ /// `extensions.bzl` used to emit went unnoticed.
+ #[test]
+ fn render_override_targets_ignores_unknown_keys() {
+ let content = render_override_targets_build_file(BTreeMap::from([(
+ "proc_macro".to_owned(),
+ Label::from_str("@//custom:override").unwrap(),
+ )]));
+
+ assert!(
+ !content.contains("@//custom:override"),
+ "```\n{content}```\n"
+ );
+ }
+
#[test]
fn render_cargo_build_script_complex() {
let mut context = Context::default();
diff --git a/crate_universe/tests/integration/override_target/Cargo.Bazel.lock b/crate_universe/tests/integration/override_target/Cargo.Bazel.lock
index 105b604..3aebbe5 100644
--- a/crate_universe/tests/integration/override_target/Cargo.Bazel.lock
+++ b/crate_universe/tests/integration/override_target/Cargo.Bazel.lock
@@ -1,12 +1,20 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
+
+[[package]]
+name = "anyhow"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]]
name = "direct-cargo-bazel-deps"
version = "0.0.1"
dependencies = [
+ "anyhow",
"foo",
+ "paste",
]
[[package]]
@@ -14,3 +22,9 @@
version = "0.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7dbb6acfeff1d490fba693a402456f76b344fea77a5e7cae43b5970c3332b8f"
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
diff --git a/crate_universe/tests/integration/override_target/MODULE.bazel b/crate_universe/tests/integration/override_target/MODULE.bazel
index 854e71e..20782dc 100644
--- a/crate_universe/tests/integration/override_target/MODULE.bazel
+++ b/crate_universe/tests/integration/override_target/MODULE.bazel
@@ -25,6 +25,29 @@
repositories = ["override_target"],
version = "0.0.0",
)
+
+# `proc-macro` and `custom-build` overrides -- the two keys whose spelling
+# differs from the `override_target_*` attribute name.
+crate.annotation(
+ crate = "paste",
+ override_target_proc_macro = "//overrides:proc_macro",
+ repositories = ["override_target"],
+)
+crate.spec(
+ package = "paste",
+ repositories = ["override_target"],
+ version = "=1.0.15",
+)
+crate.annotation(
+ crate = "anyhow",
+ override_target_build_script = "//overrides:build_script",
+ repositories = ["override_target"],
+)
+crate.spec(
+ package = "anyhow",
+ repositories = ["override_target"],
+ version = "=1.0.104",
+)
crate.splicing_config(
repositories = ["override_target"],
resolver_version = "2",
diff --git a/crate_universe/tests/integration/override_target/cargo-bazel-lock.json b/crate_universe/tests/integration/override_target/cargo-bazel-lock.json
index 63a4d36..7bc548a 100644
--- a/crate_universe/tests/integration/override_target/cargo-bazel-lock.json
+++ b/crate_universe/tests/integration/override_target/cargo-bazel-lock.json
@@ -1,5 +1,5 @@
{
- "checksum": "3f3f32d2b538dc4750bb48bf7bb274b56c8891b971e4f8d307fc699dcba4a8d5",
+ "checksum": "0fe315db50b993bdec51a927fe0192c1a5cb4205d3652cb02347f1b9811b8c17",
"conditions": {
"aarch64-apple-darwin": [
"aarch64-apple-darwin"
@@ -24,15 +24,71 @@
]
},
"crates": {
+ "anyhow 1.0.104": {
+ "build_script_attrs": {},
+ "common_attrs": {
+ "crate_features": [
+ "default",
+ "std"
+ ],
+ "edition": "2021",
+ "extra_deps": [
+ ":build_script_build"
+ ]
+ },
+ "library_target_name": "anyhow",
+ "license": "MIT OR Apache-2.0",
+ "license_file": "LICENSE-APACHE",
+ "license_ids": [
+ "Apache-2.0",
+ "MIT"
+ ],
+ "name": "anyhow",
+ "override_targets": {
+ "custom-build": "@@//overrides:build_script"
+ },
+ "package_url": "https://github.com/dtolnay/anyhow",
+ "repository": {
+ "Http": {
+ "sha256": "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470",
+ "url": "https://static.crates.io/crates/anyhow/1.0.104/download"
+ }
+ },
+ "targets": [
+ {
+ "Library": {
+ "crate_name": "anyhow",
+ "crate_root": "src/lib.rs"
+ }
+ },
+ {
+ "BuildScript": {
+ "crate_name": "build_script_build",
+ "crate_root": "build.rs"
+ }
+ }
+ ],
+ "version": "1.0.104"
+ },
"direct-cargo-bazel-deps 0.0.1": {
"common_attrs": {
"deps": [
{
+ "id": "anyhow 1.0.104",
+ "target": "anyhow"
+ },
+ {
"id": "foo 0.0.0",
"target": "foo"
}
],
- "edition": "2018"
+ "edition": "2018",
+ "proc_macro_deps": [
+ {
+ "id": "paste 1.0.15",
+ "target": "paste"
+ }
+ ]
},
"library_target_name": "direct_cargo_bazel_deps",
"name": "direct-cargo-bazel-deps",
@@ -75,10 +131,54 @@
}
],
"version": "0.0.0"
+ },
+ "paste 1.0.15": {
+ "build_script_attrs": {},
+ "common_attrs": {
+ "edition": "2018",
+ "extra_deps": [
+ ":build_script_build"
+ ]
+ },
+ "library_target_name": "paste",
+ "license": "MIT OR Apache-2.0",
+ "license_file": "LICENSE-APACHE",
+ "license_ids": [
+ "Apache-2.0",
+ "MIT"
+ ],
+ "name": "paste",
+ "override_targets": {
+ "proc-macro": "@@//overrides:proc_macro"
+ },
+ "package_url": "https://github.com/dtolnay/paste",
+ "repository": {
+ "Http": {
+ "sha256": "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a",
+ "url": "https://static.crates.io/crates/paste/1.0.15/download"
+ }
+ },
+ "targets": [
+ {
+ "ProcMacro": {
+ "crate_name": "paste",
+ "crate_root": "src/lib.rs"
+ }
+ },
+ {
+ "BuildScript": {
+ "crate_name": "build_script_build",
+ "crate_root": "build.rs"
+ }
+ }
+ ],
+ "version": "1.0.15"
}
},
"direct_deps": [
- "foo 0.0.0"
+ "anyhow 1.0.104",
+ "foo 0.0.0",
+ "paste 1.0.15"
],
"direct_dev_deps": [],
"workspace_members": {
diff --git a/crate_universe/tests/integration/override_target/overrides/BUILD.bazel b/crate_universe/tests/integration/override_target/overrides/BUILD.bazel
new file mode 100644
index 0000000..01793b8
--- /dev/null
+++ b/crate_universe/tests/integration/override_target/overrides/BUILD.bazel
@@ -0,0 +1,47 @@
+load("@rules_rust//cargo:defs.bzl", "cargo_build_script")
+load("@rules_rust//rust:defs.bzl", "rust_proc_macro", "rust_test")
+
+# Replaces the `proc-macro` target generated for `paste`.
+rust_proc_macro(
+ name = "proc_macro",
+ srcs = ["proc_macro.rs"],
+ crate_name = "paste",
+ edition = "2021",
+ visibility = ["//visibility:public"],
+)
+
+# Replaces the `custom-build` target generated for `anyhow`.
+cargo_build_script(
+ name = "build_script",
+ srcs = ["build_script.rs"],
+ crate_name = "build_script_build",
+ edition = "2021",
+ pkg_name = "anyhow",
+ visibility = ["//visibility:public"],
+)
+
+# `paste` is never compiled from source once its proc macro is overridden, so
+# this only links if the alias generated for it points at `:proc_macro`.
+rust_test(
+ name = "override_test",
+ srcs = ["override_test.rs"],
+ edition = "2021",
+ proc_macro_deps = ["@override_target//paste"],
+)
+
+# The build script override has no runtime signal -- `anyhow` builds either way
+# -- and compiling it from source would pull ~60 crates into this workspace, so
+# assert on the generated dependency graph instead.
+genquery(
+ name = "anyhow_deps",
+ expression = "deps(@override_target//anyhow)",
+ scope = ["@override_target//anyhow"],
+)
+
+genrule(
+ name = "build_script_is_overridden",
+ srcs = [":anyhow_deps"],
+ outs = ["build_script_is_overridden.txt"],
+ # Fails if `anyhow` still depends on its own generated build script.
+ cmd = "grep '//overrides:build_script$$' $< > $@",
+)
diff --git a/crate_universe/tests/integration/override_target/overrides/build_script.rs b/crate_universe/tests/integration/override_target/overrides/build_script.rs
new file mode 100644
index 0000000..5b85097
--- /dev/null
+++ b/crate_universe/tests/integration/override_target/overrides/build_script.rs
@@ -0,0 +1,6 @@
+//! Stands in for `anyhow`'s build script.
+//!
+//! `anyhow` only uses its build script to probe the compiler for optional
+//! `cfg`s, so it still builds against this no-op replacement.
+
+fn main() {}
diff --git a/crate_universe/tests/integration/override_target/overrides/override_test.rs b/crate_universe/tests/integration/override_target/overrides/override_test.rs
new file mode 100644
index 0000000..0b031e5
--- /dev/null
+++ b/crate_universe/tests/integration/override_target/overrides/override_test.rs
@@ -0,0 +1,8 @@
+//! A dropped `override_target_*` annotation leaves the upstream target in
+//! place rather than failing, so this needs a positive signal: `overridden!`
+//! does not exist in the real `paste`.
+
+#[test]
+fn proc_macro_is_overridden() {
+ assert!(paste::overridden!());
+}
diff --git a/crate_universe/tests/integration/override_target/overrides/proc_macro.rs b/crate_universe/tests/integration/override_target/overrides/proc_macro.rs
new file mode 100644
index 0000000..248095a
--- /dev/null
+++ b/crate_universe/tests/integration/override_target/overrides/proc_macro.rs
@@ -0,0 +1,12 @@
+//! Stands in for the `paste` proc macro.
+//!
+//! `paste` has no `overridden` macro, so anything that expands it only
+//! compiles when `crate.annotation(override_target_proc_macro = ...)` was
+//! applied.
+
+use proc_macro::TokenStream;
+
+#[proc_macro]
+pub fn overridden(_item: TokenStream) -> TokenStream {
+ "true".parse().unwrap()
+}