Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion src/bazel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2741,7 +2741,7 @@ fn decode_target_stream(
.map(|bytes| {
<Target as Message>::decode_from_slice(bytes)
.map_err(anyhow::Error::from)
.map(lower_target)
.map(lower_query_target)
})
.collect_into_vec(&mut decoded);
let start = targets.len();
Expand Down Expand Up @@ -2936,6 +2936,35 @@ pub fn target_name(target: &Target) -> Option<&str> {
}
}

fn lower_query_target(mut target: Target) -> Option<Target> {
if let Some(rule) = target
.rule
.as_option_mut()
.filter(|r| r.rule_class == "toolchain")
{
// Bazel's native toolchain rule points at its implementation with a NODEP_LABEL.
// Query prints the label as a nodep string and omits it from rule_input.
if let Some(implementation) = rule
.attribute
.iter()
.find(|a| a.name == "toolchain" && a.nodep == Some(true))
{
rule.rule_input
.extend(implementation.string_value.iter().cloned());
if let Some(select) = implementation.selector_list.as_option() {
rule.rule_input
.extend(select.elements.iter().flat_map(|element| {
element
.entries
.iter()
.filter_map(|entry| entry.string_value.clone())
}));
}
}
}
lower_target(target)
}

fn lower_target(mut target: Target) -> Option<Target> {
match target.r#type {
target::Discriminator::Rule
Expand Down
3 changes: 3 additions & 0 deletions src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ pub fn configuration_fingerprint(
hasher.update(value.as_bytes());
hasher.update([0]);
}
if !options.bazel.use_cquery {
hasher.update(b"queryToolchainImplementations=1\0");
}
if let Some(root) = &options.bazel.isolated_dependency_root {
hasher.update(b"isolatedDependencyRoot=");
hasher.update(root.as_bytes());
Expand Down
51 changes: 43 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -831,28 +831,35 @@ fn partial_graph_hashes(
Some(path) => {
let base = HashFileData::read(path)
.with_context(|| format!("failed to read --baseHashes {}", path.display()))?;
match base.tree.as_deref() {
_ if base.partial.is_none() => {
match (base.partial.as_ref(), base.tree.as_deref()) {
(None, _) => {
format!("{} carries no partial-graph recipes", path.display())
}
_ if args.dep_edges_file.is_some()
&& base.dep_edges.is_empty()
&& !base.hashes.is_empty() =>
(Some(recipes), _) if recipes.version != partial::RECIPE_VERSION => format!(
"{} carries partial-graph recipe version {}, expected {}",
path.display(),
recipes.version,
partial::RECIPE_VERSION
),
(_, _)
if args.dep_edges_file.is_some()
&& base.dep_edges.is_empty()
&& !base.hashes.is_empty() =>
{
format!(
"{} records no dependency edges, which --depEdgesFile needs",
path.display()
)
}
None => format!("{} records no git tree", path.display()),
Some(base_tree) if base_tree == tree_id => {
(_, None) => format!("{} records no git tree", path.display()),
(_, Some(base_tree)) if base_tree == tree_id => {
eprintln!(
"[Info] partial graph: {} already describes tree {tree_id}",
path.display()
);
return Ok(base);
}
Some(base_tree) => {
(_, Some(base_tree)) => {
let policy = DerivationPolicy {
seed_filepaths: &options.seed_filepaths,
rebuild_patterns: &rebuild_patterns,
Expand Down Expand Up @@ -896,6 +903,15 @@ fn ensure_same_mode(from: &HashFileData, to: &HashFileData) -> Result<()> {
"the starting and final hashes were not generated in the same mode: one carries --partialGraph recipes and the other does not, and hashes of the two modes are not comparable"
);
}
if let (Some(from), Some(to)) = (&from.partial, &to.partial) {
if from.version != to.version {
bail!(
"the starting and final hashes carry different partial-graph recipe versions ({} and {}); regenerate both with the same bazel-diff build",
from.version,
to.version
);
}
}
Ok(())
}

Expand Down Expand Up @@ -2458,6 +2474,19 @@ mod tests {
.unwrap();
calls.get()
};
// An older formula must regenerate even when the base describes the same tree.
fs::write(repo.path().join("file.txt"), "one").unwrap();
assert_eq!(expected_tree(&[&output, &base_path]), base.tree);
let mut outdated = base.clone();
outdated.partial.as_mut().unwrap().version = partial::RECIPE_VERSION - 1;
fs::write(
&base_path,
serde_json::to_vec(&outdated.serialized(true, false)).unwrap(),
)
.unwrap();
assert_eq!(counted(&args), 1);
fs::write(repo.path().join("file.txt"), "two").unwrap();

let mut no_recipes = base.clone();
no_recipes.partial = None;
fs::write(
Expand Down Expand Up @@ -2558,5 +2587,11 @@ mod tests {
assert!(error.contains("same mode"), "{error}");
let error = run_explain(&explain, false).unwrap_err().to_string();
assert!(error.contains("same mode"), "{error}");

partial.partial.as_mut().unwrap().version = bazel_diff::partial::RECIPE_VERSION;
let mut older = partial.clone();
older.partial.as_mut().unwrap().version = bazel_diff::partial::RECIPE_VERSION - 1;
let error = ensure_same_mode(&older, &partial).unwrap_err().to_string();
assert!(error.contains("recipe versions"), "{error}");
}
}
2 changes: 1 addition & 1 deletion src/partial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type DigestBytes = [u8; 32];

/// Bumped when the recipe format or the evaluation formula changes, so an entry an older
/// release wrote is regenerated rather than evaluated under a formula it was not recorded for.
pub const RECIPE_VERSION: u32 = 1;
pub const RECIPE_VERSION: u32 = 2;

// ---------------------------------------------------------------------------------------------
// Git tree index
Expand Down
56 changes: 56 additions & 0 deletions tests/e2e/external.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,62 @@ fn isolated_dependency_root_keeps_hidden_spoke_changes() {
assert!(!changed.contains("//outside:uses_owner"), "{changed:?}");
}

#[test]
fn toolchain_declarations_follow_their_implementations() {
let workspace = copy_workspace("extension_toolchain_through_owner");
let root = workspace.path();
edit(&root.join("MODULE.bazel"), |module| {
format!("{module}\nregister_toolchains(\"//declaration:registration\")\n")
});
let out = tempfile::tempdir().unwrap();
let before = out.path().join("before.json");
let after = out.path().join("after.json");
let edges = out.path().join("edges.json");
let result = out.path().join("impacted.json");
let args = [
"--bazelCommandOptions=--noproto:flatten_selects",
"--includeTargetType",
"-d",
edges.to_str().unwrap(),
];
for (hashes, value) in [(&before, "before"), (&after, "after")] {
if value == "after" {
edit(&root.join("declaration/BUILD.bazel"), |text| {
text.replace("\"before\"", "\"after\"")
});
}
assert_product(
root,
"//declaration:consumer",
"declaration/consumer.txt",
value,
);
assert!(generate(root, hashes, &args).status.success());
}
assert!(impacted(
root,
&before,
&after,
&result,
&["-d", edges.to_str().unwrap()]
)
.status
.success());
let changes: Vec<serde_json::Value> =
serde_json::from_slice(&fs::read(result).unwrap()).unwrap();
let distance = |name: &str| {
changes
.iter()
.find(|row| row["label"] == name)
.and_then(|row| row["targetDistance"].as_u64())
};
assert_eq!(distance("//declaration:primary"), Some(0));
assert_eq!(distance("//declaration:registration"), Some(1));
assert_eq!(distance("//declaration:choice"), Some(1));
assert_eq!(distance("//declaration:other"), None);
assert!(dep_edges(&edges)["//declaration:choice"].contains("//declaration:other"));
}

// Module `ext`'s extension generates `@app`, whose rule resolves a toolchain, and the repositories
// that toolchain can come from. A query never shows a resolved toolchain as an input, so with
// `@app` fine-grained the change has to reach `//:uses_app` through `ext`, however `ext` registers
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
load("@ext//:defs.bzl", "app_lib", "tool")

tool(name = "primary", version = "before")
tool(name = "other", version = "other")

config_setting(name = "alternative", values = {"define": "tool=other"})

toolchain(
name = "registration",
toolchain = ":primary",
toolchain_type = "@ext//:toolchain_type",
)

toolchain(
name = "choice",
toolchain = select({
":alternative": ":other",
"//conditions:default": ":primary",
}),
toolchain_type = "@ext//:toolchain_type",
)

app_lib(name = "consumer")
4 changes: 4 additions & 0 deletions tools/e2e/rust_e2e_cases.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ RUST_E2E_SUITES = [
"name": "external::returned_external_source_reaches_consumer",
"timeout": "moderate",
},
{
"name": "external::toolchain_declarations_follow_their_implementations",
"timeout": "moderate",
},
{
"name": "external::transitive_external_repo_change_reaches_consumer",
"timeout": "moderate",
Expand Down
Loading