diff --git a/src/hash.rs b/src/hash.rs index a9b8f6c2..be187e05 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -60,7 +60,8 @@ pub struct HashOptions { struct TargetDigest { overall: DigestBytes, direct: DigestBytes, - deps: Vec, + // The traversal cache and the output share these labels until all parents have been hashed. + deps: Option>>, } struct TargetDigestBuilder { @@ -97,7 +98,7 @@ impl TargetDigestBuilder { TargetDigest { overall: self.overall.finalize().into(), direct, - deps: self.deps, + deps: (!self.deps.is_empty()).then(|| Rc::new(self.deps)), } } } @@ -376,11 +377,7 @@ fn hash_targets_with_environment( generating_rule, } => { let mut digest = context.rule_digest(&generating_rule, &mut Vec::new())?; - digest.deps = if options.track_deps { - vec![generating_rule] - } else { - Vec::new() - }; + digest.deps = options.track_deps.then(|| Rc::new(vec![generating_rule])); (name, "GeneratedFile", digest) } HashTarget::Source(label) => { @@ -399,13 +396,14 @@ fn hash_targets_with_environment( TargetDigest { overall: digest, direct: digest, - deps: Vec::new(), + deps: None, }, ) } }; digests.push((key, kind, digest)); } + drop(context); let (hashes, dep_edges) = aggregate_by_label(digests, options.track_deps); Ok(HashFileData { hashes, @@ -475,7 +473,7 @@ fn aggregate_by_label( } fn label_hash( - mut instances: Vec<(String, &'static str, TargetDigest)>, + instances: Vec<(String, &'static str, TargetDigest)>, ) -> (String, TargetHash, Vec) { let label = target_label(&instances[0].0).to_owned(); let kind = instances[0].1; @@ -490,17 +488,22 @@ fn label_hash( } (overall.finalize().into(), direct.finalize().into()) }; - let mut seen = HashSet::new(); - let deps = instances - .iter_mut() - .flat_map(|(_, _, digest)| digest.deps.drain(..)) - .map(|mut dependency| { - let length = target_label(&dependency).len(); - dependency.truncate(length); - dependency - }) - .filter(|dependency| seen.insert(dependency.clone())) - .collect(); + let mut edge_lists = instances + .into_iter() + .filter_map(|(_, _, digest)| digest.deps) + .map(Rc::unwrap_or_clone); + let mut deps = edge_lists.next().unwrap_or_default(); + for mut edges in edge_lists { + deps.append(&mut edges); + } + for dependency in &mut deps { + dependency.truncate(target_label(dependency).len()); + } + // Plain-query rule inputs are normally already unique and in label order. + if !deps.is_sorted_by(|left, right| left < right) { + let mut seen = HashSet::new(); + deps.retain(|dependency| seen.insert(dependency.clone())); + } ( label, TargetHash { @@ -586,7 +589,7 @@ impl HashContext<'_> { return Ok(TargetDigest { overall: placeholder, direct: placeholder, - deps: Vec::new(), + deps: None, }); } path.push(canonical_key.clone()); @@ -2227,13 +2230,13 @@ esac tracked.direct(b"direct"); tracked.transitive("//dep:a", b"dependency"); let tracked = tracked.finish(); - assert_eq!(tracked.deps, ["//dep:a"]); + assert_eq!(tracked.deps.unwrap().as_slice(), ["//dep:a"]); assert_ne!(tracked.direct, tracked.overall); let mut untracked = TargetDigestBuilder::new(false); untracked.direct(b"direct"); untracked.transitive("//dep:a", b"dependency"); - assert!(untracked.finish().deps.is_empty()); + assert!(untracked.finish().deps.is_none()); assert_eq!(crate::model::Digest::from([0; 32]).to_string().len(), 64); } @@ -2338,6 +2341,34 @@ esac assert_ne!(included.finalize().to_vec(), ignored.finalize().to_vec()); } + #[test] + fn generated_alias_keeps_the_shared_rules_dependencies() { + let workspace = tempfile::tempdir().unwrap(); + let rule = |name: &str, inputs: &[&str]| { + rule_target(Rule { + name: name.into(), + rule_class: "genrule".into(), + rule_input: inputs.iter().map(|input| (*input).to_owned()).collect(), + ..Default::default() + }) + }; + // Raw targets can list a rule under a generated file's label. Both rule outputs + // then read the generating rule's cached digest, including its edge list. + let data = hash_targets_for_test( + &hash_options(workspace.path()), + vec![ + rule("//:a", &["//:dep"]), + rule("//:dep", &[]), + rule("//:b", &[]), + generated_target("//:b", "//:a"), + ], + workspace.path(), + ) + .unwrap(); + assert_eq!(data.dep_edges["//:a"], ["//:dep"]); + assert_eq!(data.dep_edges["//:b"], ["//:dep", "//:a"]); + } + #[cfg(unix)] #[test] fn hashes_rules_sources_generated_files_and_visibility_groups() {