|
26 | 26 |
|
27 | 27 | use std::collections::{HashMap, HashSet};
|
28 | 28 | use std::fmt;
|
| 29 | +use std::path::Path; |
29 | 30 |
|
30 | 31 | use cargo::core::{PackageId, Profile, Target, TargetKind};
|
31 | 32 | use cargo::ops::{Kind, Unit, Context};
|
@@ -124,6 +125,100 @@ impl Plan {
|
124 | 125 | }
|
125 | 126 | Ok(())
|
126 | 127 | }
|
| 128 | + |
| 129 | + /// TODO: Improve detecting dirty crate targets for a set of dirty file paths. |
| 130 | + /// This uses a lousy heuristic of checking path prefix for a given crate |
| 131 | + /// target to determine whether a given unit (crate target) is dirty. This |
| 132 | + /// can easily backfire, e.g. when build script is under src/. Any change |
| 133 | + /// to a file under src/ would imply the build script is always dirty, so we |
| 134 | + /// never do work and always offload to Cargo in such case. |
| 135 | + /// Because of that, build scripts are checked separately and only other |
| 136 | + /// crate targets are checked with path prefixes. |
| 137 | + fn fetch_dirty_units<T: AsRef<Path> + fmt::Debug>(&self, files: &[T]) -> HashSet<UnitKey> { |
| 138 | + let mut result = HashSet::new(); |
| 139 | + |
| 140 | + let build_scripts: HashMap<&Path, UnitKey> = self.units.iter() |
| 141 | + .filter(|&(&(_, ref kind), _)| *kind == TargetKind::CustomBuild) |
| 142 | + .map(|(key, ref unit)| (unit.target.src_path(), key.clone())).collect(); |
| 143 | + let other_targets: HashMap<UnitKey, &Path> = self.units.iter() |
| 144 | + .filter(|&(&(_, ref kind), _)| *kind != TargetKind::CustomBuild) |
| 145 | + .map(|(key, ref unit)| (key.clone(), unit.target.src_path().parent().unwrap())).collect(); |
| 146 | + |
| 147 | + for modified in files { |
| 148 | + if let Some(unit) = build_scripts.get(modified.as_ref()) { |
| 149 | + result.insert(unit.clone()); |
| 150 | + } else { |
| 151 | + // It's not a build script, so we can check for path prefix now |
| 152 | + for (unit, src_dir) in &other_targets { |
| 153 | + if modified.as_ref().starts_with(src_dir) { |
| 154 | + trace!("Adding {:?}, as a modified file {:?} starts with {:?}", &unit, modified, src_dir); |
| 155 | + result.insert(unit.clone()); |
| 156 | + } |
| 157 | + } |
| 158 | + } |
| 159 | + } |
| 160 | + result |
| 161 | + } |
| 162 | + |
| 163 | + /// For a given set of select dirty units, returns a set of all the |
| 164 | + /// dependencies that has to be rebuilt transitively. |
| 165 | + fn transitive_dirty_units(&self, dirties: &HashSet<UnitKey>) -> HashSet<UnitKey> { |
| 166 | + let mut transitive = dirties.clone(); |
| 167 | + // Walk through a rev dep graph using a stack of nodes to collect |
| 168 | + // transitively every dirty node |
| 169 | + let mut to_process: Vec<_> = dirties.iter().cloned().collect(); |
| 170 | + while let Some(top) = to_process.pop() { |
| 171 | + if transitive.get(&top).is_some() { continue; } |
| 172 | + else { transitive.insert(top.clone()); } |
| 173 | + |
| 174 | + // Process every dirty rev dep of the processed node |
| 175 | + let dirty_rev_deps = self.rev_dep_graph.get(&top).unwrap() |
| 176 | + .iter().filter(|dep| dirties.contains(dep)); |
| 177 | + for rev_dep in dirty_rev_deps { |
| 178 | + to_process.push(rev_dep.clone()); |
| 179 | + } |
| 180 | + } |
| 181 | + transitive |
| 182 | + } |
| 183 | + |
| 184 | + /// Creates a dirty reverse dependency graph using a set of given dirty units. |
| 185 | + fn dirty_rev_dep_graph(&self, dirties: &HashSet<UnitKey>) -> HashMap<UnitKey, HashSet<UnitKey>> { |
| 186 | + let dirties = self.transitive_dirty_units(dirties); |
| 187 | + trace!("transitive_dirty_units: {:?}", dirties); |
| 188 | + |
| 189 | + self.rev_dep_graph.iter() |
| 190 | + // Remove nodes that are not dirty |
| 191 | + .filter(|&(unit, _)| dirties.contains(&unit)) |
| 192 | + // Retain only dirty dependencies of the ones that are dirty |
| 193 | + .map(|(k, deps)| (k.clone(), deps.iter().cloned().filter(|d| dirties.contains(&d)).collect())) |
| 194 | + .collect() |
| 195 | + } |
| 196 | + |
| 197 | + pub fn prepare_work<T: AsRef<Path> + fmt::Debug>(&self, modified: &[T]) -> WorkStatus { |
| 198 | + let dirties = self.fetch_dirty_units(modified); |
| 199 | + trace!("fetch_dirty_units: for files {:?}, these units are dirty: {:?}", modified, dirties); |
| 200 | + |
| 201 | + if dirties.iter().any(|&(_, ref kind)| *kind == TargetKind::CustomBuild) { |
| 202 | + WorkStatus::NeedsCargo |
| 203 | + } else { |
| 204 | + let graph = self.dirty_rev_dep_graph(&dirties); |
| 205 | + trace!("Constructed dirty rev dep graph: {:?}", graph); |
| 206 | + |
| 207 | + // TODO: Then sort topologically the deps |
| 208 | + // TODO: Then map those with a ProcessBuilder |
| 209 | + |
| 210 | + WorkStatus::Execute(JobQueue { }) |
| 211 | + } |
| 212 | + } |
| 213 | +} |
| 214 | + |
| 215 | +pub enum WorkStatus { |
| 216 | + NeedsCargo, |
| 217 | + Execute(JobQueue) |
| 218 | +} |
| 219 | + |
| 220 | +pub struct JobQueue { |
| 221 | + |
127 | 222 | }
|
128 | 223 |
|
129 | 224 | fn key_from_unit(unit: &Unit) -> UnitKey {
|
|
0 commit comments