Skip to content

Commit

Permalink
feat(lock): Print lockfile changes on all commands
Browse files Browse the repository at this point in the history
  • Loading branch information
epage committed Mar 12, 2024
1 parent 030da59 commit 4ab2797
Show file tree
Hide file tree
Showing 166 changed files with 1,304 additions and 284 deletions.
1 change: 1 addition & 0 deletions crates/cargo-test-support/src/compare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ fn substitute_macros(input: &str) -> String {
("[SCRAPING]", " Scraping"),
("[FRESH]", " Fresh"),
("[DIRTY]", " Dirty"),
("[LOCKING]", " Locking"),
("[UPDATING]", " Updating"),
("[ADDING]", " Adding"),
("[REMOVING]", " Removing"),
Expand Down
187 changes: 180 additions & 7 deletions src/cargo/ops/cargo_generate_lockfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,19 @@ pub struct UpdateOptions<'a> {

pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> {
let mut registry = PackageRegistry::new(ws.gctx())?;
let previous_resolve = None;
let mut resolve = ops::resolve_with_previous(
&mut registry,
ws,
&CliFeatures::new_all(true),
HasDevUnits::Yes,
None,
previous_resolve,
None,
&[],
true,
)?;
ops::write_pkg_lockfile(ws, &mut resolve)?;
print_lockfile_changes(ws.gctx(), previous_resolve, &resolve, &mut registry)?;
Ok(())
}

Expand Down Expand Up @@ -164,7 +166,164 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
Ok(())
}

fn print_lockfile_updates(
pub fn print_lockfile_changes(
gctx: &GlobalContext,
previous_resolve: Option<&Resolve>,
resolve: &Resolve,
registry: &mut PackageRegistry<'_>,
) -> CargoResult<()> {
if let Some(previous_resolve) = previous_resolve {
print_lockfile_sync(gctx, previous_resolve, resolve, registry)
} else {
print_lockfile_generation(gctx, resolve, registry)
}
}

fn print_lockfile_generation(
gctx: &GlobalContext,
resolve: &Resolve,
registry: &mut PackageRegistry<'_>,
) -> CargoResult<()> {
let mut shell = gctx.shell();

let diff = PackageDiff::new(&resolve);
let num_pkgs: usize = diff.iter().map(|d| d.added.len()).sum();
if num_pkgs <= 1 {
// just ourself, nothing worth reporting
return Ok(());
}
shell.status("Locking", format!("{num_pkgs} packages"))?;

for diff in diff {
fn format_latest(version: semver::Version) -> String {
let warn = style::WARN;
format!(" {warn}(latest: v{version}){warn:#}")
}
let possibilities = if let Some(query) = diff.alternatives_query() {
loop {
match registry.query_vec(&query, QueryKind::Exact) {
std::task::Poll::Ready(res) => {
break res?;
}
std::task::Poll::Pending => registry.block_until_ready()?,
}
}
} else {
vec![]
};

for package in diff.added.iter() {
let latest = if !possibilities.is_empty() {
possibilities
.iter()
.map(|s| s.as_summary())
.filter(|s| is_latest(s.version(), package.version()))
.map(|s| s.version().clone())
.max()
.map(format_latest)
} else {
None
};

if let Some(latest) = latest {
shell.status_with_color("Adding", format!("{package}{latest}"), &style::NOTE)?;
}
}
}

Ok(())
}

fn print_lockfile_sync(
gctx: &GlobalContext,
previous_resolve: &Resolve,
resolve: &Resolve,
registry: &mut PackageRegistry<'_>,
) -> CargoResult<()> {
let mut shell = gctx.shell();

let diff = PackageDiff::diff(&previous_resolve, &resolve);
let num_pkgs: usize = diff.iter().map(|d| d.added.len()).sum();
if num_pkgs == 0 {
return Ok(());
}
let plural = if num_pkgs == 1 { "" } else { "s" };
shell.status("Locking", format!("{num_pkgs} package{plural}"))?;

for diff in diff {
fn format_latest(version: semver::Version) -> String {
let warn = style::WARN;
format!(" {warn}(latest: v{version}){warn:#}")
}
let possibilities = if let Some(query) = diff.alternatives_query() {
loop {
match registry.query_vec(&query, QueryKind::Exact) {
std::task::Poll::Ready(res) => {
break res?;
}
std::task::Poll::Pending => registry.block_until_ready()?,
}
}
} else {
vec![]
};

if let Some((removed, added)) = diff.change() {
let latest = if !possibilities.is_empty() {
possibilities
.iter()
.map(|s| s.as_summary())
.filter(|s| is_latest(s.version(), added.version()))
.map(|s| s.version().clone())
.max()
.map(format_latest)
} else {
None
}
.unwrap_or_default();

let msg = if removed.source_id().is_git() {
format!(
"{removed} -> #{}",
&added.source_id().precise_git_fragment().unwrap()[..8],
)
} else {
format!("{removed} -> v{}{latest}", added.version())
};

// If versions differ only in build metadata, we call it an "update"
// regardless of whether the build metadata has gone up or down.
// This metadata is often stuff like git commit hashes, which are
// not meaningfully ordered.
if removed.version().cmp_precedence(added.version()) == Ordering::Greater {
shell.status_with_color("Downgrading", msg, &style::WARN)?;
} else {
shell.status_with_color("Updating", msg, &style::GOOD)?;
}
} else {
for package in diff.added.iter() {
let latest = if !possibilities.is_empty() {
possibilities
.iter()
.map(|s| s.as_summary())
.filter(|s| is_latest(s.version(), package.version()))
.map(|s| s.version().clone())
.max()
.map(format_latest)
} else {
None
}
.unwrap_or_default();

shell.status_with_color("Adding", format!("{package}{latest}"), &style::NOTE)?;
}
}
}

Ok(())
}

pub fn print_lockfile_updates(
gctx: &GlobalContext,
previous_resolve: &Resolve,
resolve: &Resolve,
Expand Down Expand Up @@ -317,11 +476,21 @@ pub struct PackageDiff {
}

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())
pub fn new(resolve: &Resolve) -> Vec<Self> {
let mut changes = BTreeMap::new();
let empty = Self::default();
for dep in resolve.iter() {
changes
.entry(Self::key(dep))
.or_insert_with(|| empty.clone())
.added
.push(dep);
}

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

pub fn diff(previous_resolve: &Resolve, resolve: &Resolve) -> Vec<Self> {
fn vec_subset(a: &[PackageId], b: &[PackageId]) -> Vec<PackageId> {
a.iter().filter(|a| !contains_id(b, a)).cloned().collect()
}
Expand Down Expand Up @@ -362,14 +531,14 @@ impl PackageDiff {
let empty = Self::default();
for dep in previous_resolve.iter() {
changes
.entry(key(dep))
.entry(Self::key(dep))
.or_insert_with(|| empty.clone())
.removed
.push(dep);
}
for dep in resolve.iter() {
changes
.entry(key(dep))
.entry(Self::key(dep))
.or_insert_with(|| empty.clone())
.added
.push(dep);
Expand All @@ -395,6 +564,10 @@ impl PackageDiff {
changes.into_iter().map(|(_, v)| v).collect()
}

fn key(dep: PackageId) -> (&'static str, SourceId) {
(dep.name().as_str(), dep.source_id())
}

/// Guess if a package upgraded/downgraded
///
/// All `PackageDiff` knows is that entries were added/removed within [`Resolve`].
Expand Down
9 changes: 6 additions & 3 deletions src/cargo/ops/lockfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,18 @@ pub fn resolve_to_string(ws: &Workspace<'_>, resolve: &Resolve) -> CargoResult<S
Ok(out)
}

/// Ensure the resolve result is written to fisk
///
/// Returns `true` if the lockfile changed
#[tracing::instrument(skip_all)]
pub fn write_pkg_lockfile(ws: &Workspace<'_>, resolve: &mut Resolve) -> CargoResult<()> {
pub fn write_pkg_lockfile(ws: &Workspace<'_>, resolve: &mut Resolve) -> CargoResult<bool> {
let (orig, mut out, lock_root) = resolve_to_string_orig(ws, resolve);

// If the lock file contents haven't changed so don't rewrite it. This is
// helpful on read-only filesystems.
if let Some(orig) = &orig {
if are_equal_lockfiles(orig, &out, ws) {
return Ok(());
return Ok(false);
}
}

Expand Down Expand Up @@ -93,7 +96,7 @@ pub fn write_pkg_lockfile(ws: &Workspace<'_>, resolve: &mut Resolve) -> CargoRes
lock_root.as_path_unlocked().join("Cargo.lock").display()
)
})?;
Ok(())
Ok(true)
}

fn resolve_to_string_orig(
Expand Down
2 changes: 2 additions & 0 deletions src/cargo/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub use self::cargo_compile::{CompileFilter, FilterRule, LibRule, Packages};
pub use self::cargo_doc::{doc, DocOptions, OutputFormat};
pub use self::cargo_fetch::{fetch, FetchOptions};
pub use self::cargo_generate_lockfile::generate_lockfile;
pub use self::cargo_generate_lockfile::print_lockfile_changes;
pub use self::cargo_generate_lockfile::print_lockfile_updates;
pub use self::cargo_generate_lockfile::update_lockfile;
pub use self::cargo_generate_lockfile::UpdateOptions;
pub use self::cargo_install::{install, install_list};
Expand Down
14 changes: 12 additions & 2 deletions src/cargo/ops/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,18 @@ fn resolve_with_registry<'gctx>(
true,
)?;

if !ws.is_ephemeral() && ws.require_optional_deps() {
ops::write_pkg_lockfile(ws, &mut resolve)?;
let print = if !ws.is_ephemeral() && ws.require_optional_deps() {
ops::write_pkg_lockfile(ws, &mut resolve)?
} else {
false
};
if print {
// We only want one Cargo at a time resolving a crate graph since this can
// involve a lot of frobbing of the global caches.
let _lock = ws
.gctx()
.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
ops::print_lockfile_changes(ws.gctx(), prev.as_ref(), &resolve, registry)?;
}
Ok(resolve)
}
Expand Down
9 changes: 9 additions & 0 deletions tests/testsuite/alt_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fn depend_on_alt_registry() {
.with_stderr(
"\
[UPDATING] `alternative` index
[LOCKING] 2 packages
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `alternative`)
[CHECKING] bar v0.0.1 (registry `alternative`)
Expand Down Expand Up @@ -87,6 +88,7 @@ fn depend_on_alt_registry_depends_on_same_registry_no_index() {
.with_stderr(
"\
[UPDATING] `alternative` index
[LOCKING] 3 packages
[DOWNLOADING] crates ...
[DOWNLOADED] [..] v0.0.1 (registry `alternative`)
[DOWNLOADED] [..] v0.0.1 (registry `alternative`)
Expand Down Expand Up @@ -130,6 +132,7 @@ fn depend_on_alt_registry_depends_on_same_registry() {
.with_stderr(
"\
[UPDATING] `alternative` index
[LOCKING] 3 packages
[DOWNLOADING] crates ...
[DOWNLOADED] [..] v0.0.1 (registry `alternative`)
[DOWNLOADED] [..] v0.0.1 (registry `alternative`)
Expand Down Expand Up @@ -174,6 +177,7 @@ fn depend_on_alt_registry_depends_on_crates_io() {
"\
[UPDATING] `alternative` index
[UPDATING] `dummy-registry` index
[LOCKING] 3 packages
[DOWNLOADING] crates ...
[DOWNLOADED] baz v0.0.1 (registry `dummy-registry`)
[DOWNLOADED] bar v0.0.1 (registry `alternative`)
Expand Down Expand Up @@ -213,6 +217,7 @@ fn registry_and_path_dep_works() {
p.cargo("check")
.with_stderr(
"\
[LOCKING] 2 packages
[CHECKING] bar v0.0.1 ([CWD]/bar)
[CHECKING] foo v0.0.1 ([CWD])
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [..]s
Expand Down Expand Up @@ -416,6 +421,7 @@ fn alt_registry_and_crates_io_deps() {
"\
[UPDATING] `alternative` index
[UPDATING] `dummy-registry` index
[LOCKING] 3 packages
[DOWNLOADING] crates ...
[DOWNLOADED] crates_io_dep v0.0.1 (registry `dummy-registry`)
[DOWNLOADED] alt_reg_dep v0.1.0 (registry `alternative`)
Expand Down Expand Up @@ -692,6 +698,7 @@ fn patch_alt_reg() {
.with_stderr(
"\
[UPDATING] `alternative` index
[LOCKING] 2 packages
[CHECKING] bar v0.1.0 ([CWD]/bar)
[CHECKING] foo v0.0.1 ([CWD])
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [..]
Expand Down Expand Up @@ -784,6 +791,7 @@ fn no_api() {
.with_stderr(
"\
[UPDATING] `alternative` index
[LOCKING] 2 packages
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `alternative`)
[CHECKING] bar v0.0.1 (registry `alternative`)
Expand Down Expand Up @@ -1346,6 +1354,7 @@ fn registries_index_relative_url() {
.with_stderr(
"\
[UPDATING] `relative` index
[LOCKING] 2 packages
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `relative`)
[CHECKING] bar v0.0.1 (registry `relative`)
Expand Down
Loading

0 comments on commit 4ab2797

Please sign in to comment.