Skip to content

Commit

Permalink
Auto merge of #13564 - epage:r, r=weihanglo
Browse files Browse the repository at this point in the history
refactor(lockfile): Make diffing/printing more reusable

### What does this PR try to resolve?

This is prep for #13561 so we can tailor the printing of lockfile changes to each use without a bunch of flags.

### How should we test and review this PR?

### Additional information
  • Loading branch information
bors committed Mar 12, 2024
2 parents fe5de07 + 619238e commit 685c18d
Showing 1 changed file with 91 additions and 60 deletions.
151 changes: 91 additions & 60 deletions src/cargo/ops/cargo_generate_lockfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,17 +154,29 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
true,
)?;

print_lockfile_update(opts.gctx, &previous_resolve, &resolve, &mut registry)?;
if opts.dry_run {
opts.gctx
.shell()
.warn("not updating lockfile due to dry run")?;
} else {
ops::write_pkg_lockfile(ws, &mut resolve)?;
}
Ok(())
}

fn print_lockfile_update(
gctx: &GlobalContext,
previous_resolve: &Resolve,
resolve: &Resolve,
registry: &mut PackageRegistry<'_>,
) -> CargoResult<()> {
// Summarize what is changing for the user.
let print_change = |status: &str, msg: String, color: &Style| {
opts.gctx.shell().status_with_color(status, msg, color)
gctx.shell().status_with_color(status, msg, color)
};
let mut unchanged_behind = 0;
for ResolvedPackageVersions {
removed,
added,
unchanged,
} in compare_dependency_graphs(&previous_resolve, &resolve)
{
for diff in PackageDiff::diff(&previous_resolve, &resolve) {
fn format_latest(version: semver::Version) -> String {
let warn = style::WARN;
format!(" {warn}(latest: v{version}){warn:#}")
Expand All @@ -177,14 +189,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
&& candidate.minor == current.minor
&& candidate.patch == current.patch))
}
let possibilities = if let Some(query) = [added.iter(), unchanged.iter()]
.into_iter()
.flatten()
.next()
.filter(|s| s.source_id().is_registry())
{
let query =
crate::core::dependency::Dependency::parse(query.name(), None, query.source_id())?;
let possibilities = if let Some(query) = diff.alternatives_query() {
loop {
match registry.query_vec(&query, QueryKind::Exact) {
std::task::Poll::Ready(res) => {
Expand All @@ -197,10 +202,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
vec![]
};

if removed.len() == 1 && added.len() == 1 {
let added = added.into_iter().next().unwrap();
let removed = removed.into_iter().next().unwrap();

if let Some((removed, added)) = diff.change() {
let latest = if !possibilities.is_empty() {
possibilities
.iter()
Expand Down Expand Up @@ -233,10 +235,10 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
print_change("Updating", msg, &style::GOOD)?;
}
} else {
for package in removed.iter() {
for package in diff.removed.iter() {
print_change("Removing", format!("{package}"), &style::ERROR)?;
}
for package in added.iter() {
for package in diff.added.iter() {
let latest = if !possibilities.is_empty() {
possibilities
.iter()
Expand All @@ -253,7 +255,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
print_change("Adding", format!("{package}{latest}"), &style::NOTE)?;
}
}
for package in &unchanged {
for package in &diff.unchanged {
let latest = if !possibilities.is_empty() {
possibilities
.iter()
Expand All @@ -268,8 +270,8 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes

if let Some(latest) = latest {
unchanged_behind += 1;
if opts.gctx.shell().verbosity() == Verbosity::Verbose {
opts.gctx.shell().status_with_color(
if gctx.shell().verbosity() == Verbosity::Verbose {
gctx.shell().status_with_color(
"Unchanged",
format!("{package}{latest}"),
&anstyle::Style::new().bold(),
Expand All @@ -278,51 +280,46 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
}
}
}
if opts.gctx.shell().verbosity() == Verbosity::Verbose {
opts.gctx.shell().note(
if gctx.shell().verbosity() == Verbosity::Verbose {
gctx.shell().note(
"to see how you depend on a package, run `cargo tree --invert --package <dep>@<ver>`",
)?;
} else {
if 0 < unchanged_behind {
opts.gctx.shell().note(format!(
gctx.shell().note(format!(
"pass `--verbose` to see {unchanged_behind} unchanged dependencies behind latest"
))?;
}
}
if opts.dry_run {
opts.gctx
.shell()
.warn("not updating lockfile due to dry run")?;
} else {
ops::write_pkg_lockfile(ws, &mut resolve)?;
}
return Ok(());

fn fill_with_deps<'a>(
resolve: &'a Resolve,
dep: PackageId,
set: &mut HashSet<PackageId>,
visited: &mut HashSet<PackageId>,
) {
if !visited.insert(dep) {
return;
}
set.insert(dep);
for (dep, _) in resolve.deps_not_replaced(dep) {
fill_with_deps(resolve, dep, set, visited);
}
}
Ok(())
}

#[derive(Default, Clone, Debug)]
struct ResolvedPackageVersions {
removed: Vec<PackageId>,
added: Vec<PackageId>,
unchanged: Vec<PackageId>,
fn fill_with_deps<'a>(
resolve: &'a Resolve,
dep: PackageId,
set: &mut HashSet<PackageId>,
visited: &mut HashSet<PackageId>,
) {
if !visited.insert(dep) {
return;
}
set.insert(dep);
for (dep, _) in resolve.deps_not_replaced(dep) {
fill_with_deps(resolve, dep, set, visited);
}
fn compare_dependency_graphs(
previous_resolve: &Resolve,
resolve: &Resolve,
) -> Vec<ResolvedPackageVersions> {
}

/// All resolved versions of a package name within a [`SourceId`]
#[derive(Default, Clone, Debug)]
pub struct PackageDiff {
removed: Vec<PackageId>,
added: Vec<PackageId>,
unchanged: Vec<PackageId>,
}

impl PackageDiff {
pub fn diff(previous_resolve: &Resolve, resolve: &Resolve) -> Vec<Self> {
fn key(dep: PackageId) -> (&'static str, SourceId) {
(dep.name().as_str(), dep.source_id())
}
Expand Down Expand Up @@ -364,7 +361,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes

// Map `(package name, package source)` to `(removed versions, added versions)`.
let mut changes = BTreeMap::new();
let empty = ResolvedPackageVersions::default();
let empty = Self::default();
for dep in previous_resolve.iter() {
changes
.entry(key(dep))
Expand All @@ -381,7 +378,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
}

for v in changes.values_mut() {
let ResolvedPackageVersions {
let Self {
removed: ref mut old,
added: ref mut new,
unchanged: ref mut other,
Expand All @@ -399,4 +396,38 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes

changes.into_iter().map(|(_, v)| v).collect()
}

/// Guess if a package upgraded/downgraded
///
/// All `PackageDiff` knows is that entries were added/removed within [`Resolve`].
/// A package could be added or removed because of dependencies from other packages
/// which makes it hard to definitively say "X was upgrade to N".
pub fn change(&self) -> Option<(&PackageId, &PackageId)> {
if self.removed.len() == 1 && self.added.len() == 1 {
Some((&self.removed[0], &self.added[0]))
} else {
None
}
}

/// For querying [`PackageRegistry`] for alternative versions to report to the user
pub fn alternatives_query(&self) -> Option<crate::core::dependency::Dependency> {
let package_id = [
self.added.iter(),
self.unchanged.iter(),
self.removed.iter(),
]
.into_iter()
.flatten()
.next()
// Limit to registry as that is the only source with meaningful alternative versions
.filter(|s| s.source_id().is_registry())?;
let query = crate::core::dependency::Dependency::parse(
package_id.name(),
None,
package_id.source_id(),
)
.expect("already a valid dependency");
Some(query)
}
}

0 comments on commit 685c18d

Please sign in to comment.