Skip to content

Commit e2162b7

Browse files
committed
fix: Use ops::update_lockfile for consistency with non-breaking update.
1 parent 8c8ad48 commit e2162b7

File tree

3 files changed

+148
-33
lines changed

3 files changed

+148
-33
lines changed

src/bin/cargo/commands/update.rs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
use std::collections::HashMap;
2+
13
use crate::command_prelude::*;
24

35
use anyhow::anyhow;
46
use cargo::ops::{self, UpdateOptions};
57
use cargo::util::print_available_packages;
8+
use tracing::trace;
69

710
pub fn cli() -> Command {
811
subcommand("update")
@@ -92,28 +95,38 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
9295
let update_opts = UpdateOptions {
9396
recursive: args.flag("recursive"),
9497
precise: args.get_one::<String>("precise").map(String::as_str),
98+
breaking: args.flag("breaking"),
9599
to_update,
96100
dry_run: args.dry_run(),
97101
workspace: args.flag("workspace"),
98102
gctx,
99103
};
100104

101-
if args.flag("breaking") {
102-
gctx.cli_unstable()
103-
.fail_if_stable_opt("--breaking", 12425)?;
104-
105-
let upgrades = ops::upgrade_manifests(&mut ws, &update_opts.to_update)?;
106-
ops::resolve_ws(&ws, update_opts.dry_run)?;
107-
ops::write_manifest_upgrades(&ws, &upgrades, update_opts.dry_run)?;
105+
let breaking_update = update_opts.breaking; // or if doing a breaking precise update, coming in #14140.
108106

109-
if update_opts.dry_run {
110-
update_opts
111-
.gctx
112-
.shell()
113-
.warn("aborting update due to dry run")?;
107+
// We are using the term "upgrade" here, which is the typical case, but it
108+
// can also be a downgrade (in the case of a precise update). In general, it
109+
// is a change to a version req matching a precise version.
110+
let upgrades = if breaking_update {
111+
if update_opts.breaking {
112+
gctx.cli_unstable()
113+
.fail_if_stable_opt("--breaking", 12425)?;
114114
}
115+
116+
trace!("allowing breaking updates");
117+
ops::upgrade_manifests(&mut ws, &update_opts.to_update)?
115118
} else {
116-
ops::update_lockfile(&ws, &update_opts)?;
119+
HashMap::new()
120+
};
121+
122+
ops::update_lockfile(&ws, &update_opts, &upgrades)?;
123+
ops::write_manifest_upgrades(&ws, &upgrades, update_opts.dry_run)?;
124+
125+
if update_opts.dry_run {
126+
update_opts
127+
.gctx
128+
.shell()
129+
.warn("aborting update due to dry run")?;
117130
}
118131

119132
Ok(())

src/cargo/ops/cargo_update.rs

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::ops;
99
use crate::sources::source::QueryKind;
1010
use crate::util::cache_lock::CacheLockMode;
1111
use crate::util::context::GlobalContext;
12+
use crate::util::interning::InternedString;
1213
use crate::util::toml_mut::dependency::{MaybeWorkspace, Source};
1314
use crate::util::toml_mut::manifest::LocalManifest;
1415
use crate::util::toml_mut::upgrade::upgrade_requirement;
@@ -20,12 +21,25 @@ use std::cmp::Ordering;
2021
use std::collections::{BTreeMap, HashMap, HashSet};
2122
use tracing::{debug, trace};
2223

23-
pub type UpgradeMap = HashMap<(String, SourceId), Version>;
24+
/// A map of all breaking upgrades which is filled in by
25+
/// upgrade_manifests/upgrade_dependency when going through workspace member
26+
/// manifests, and later used by write_manifest_upgrades in order to know which
27+
/// upgrades to write to manifest files on disk. Also used by update_lockfile to
28+
/// know which dependencies to keep unchanged if any have been upgraded (we will
29+
/// do either breaking or non-breaking updates, but not both).
30+
pub type UpgradeMap = HashMap<
31+
// The key is a package identifier consisting of the name and the source id.
32+
(InternedString, SourceId),
33+
// The value is the original version requirement before upgrade, and the
34+
// upgraded version.
35+
(VersionReq, Version),
36+
>;
2437

2538
pub struct UpdateOptions<'a> {
2639
pub gctx: &'a GlobalContext,
2740
pub to_update: Vec<String>,
2841
pub precise: Option<&'a str>,
42+
pub breaking: bool,
2943
pub recursive: bool,
3044
pub dry_run: bool,
3145
pub workspace: bool,
@@ -49,7 +63,11 @@ pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> {
4963
Ok(())
5064
}
5165

52-
pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoResult<()> {
66+
pub fn update_lockfile(
67+
ws: &Workspace<'_>,
68+
opts: &UpdateOptions<'_>,
69+
upgrades: &UpgradeMap,
70+
) -> CargoResult<()> {
5371
if opts.recursive && opts.precise.is_some() {
5472
anyhow::bail!("cannot specify both recursive and precise simultaneously")
5573
}
@@ -91,8 +109,44 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
91109
let mut registry = ws.package_registry()?;
92110
let mut to_avoid = HashSet::new();
93111

94-
if opts.to_update.is_empty() {
112+
if opts.breaking {
113+
// We don't necessarily want to update all specified packages. If we are
114+
// doing a breaking update (or precise upgrades, coming in #14140), we
115+
// don't want to touch any packages that have no breaking updates. So we
116+
// want to only avoid all packages that got upgraded.
117+
debug!("Will avoid all upgraded packages");
118+
119+
for name in opts.to_update.iter() {
120+
// We still want to query any specified package, for the sake of
121+
// outputting errors if they don't exist.
122+
previous_resolve.query(name)?;
123+
}
124+
125+
for (name, source_id) in upgrades.keys() {
126+
let (version_req, _) = upgrades.get(&(*name, *source_id)).unwrap();
127+
128+
if let Some(matching_dep) = previous_resolve.iter().find(|dep| {
129+
dep.name() == *name
130+
&& dep.source_id() == *source_id
131+
&& version_req.matches(dep.version())
132+
}) {
133+
let spec = PackageIdSpec::new(name.to_string())
134+
.with_url(source_id.url().clone())
135+
.with_version(matching_dep.version().clone().into());
136+
let spec = format!("{spec}");
137+
debug!("Will avoid {spec}");
138+
let pid = previous_resolve.query(&spec)?;
139+
to_avoid.insert(pid);
140+
} else {
141+
// Should never happen
142+
anyhow::bail!(
143+
"no package named `{name}` with source `{source_id}` and version matching `{version_req}` in the previous lockfile",
144+
)
145+
}
146+
}
147+
} else if opts.to_update.is_empty() {
95148
if !opts.workspace {
149+
// TODO: Test `cargo update --breaking` on non-ws
96150
to_avoid.extend(previous_resolve.iter());
97151
to_avoid.extend(previous_resolve.unused_patches());
98152
}
@@ -103,6 +157,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
103157
if opts.recursive {
104158
fill_with_deps(&previous_resolve, pid, &mut to_avoid, &mut HashSet::new());
105159
} else {
160+
debug!("Will avoid to_update {pid}");
106161
to_avoid.insert(pid);
107162
sources.push(match opts.precise {
108163
Some(precise) => {
@@ -125,6 +180,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
125180
if let Ok(unused_id) =
126181
PackageIdSpec::query_str(name, previous_resolve.unused_patches().iter().cloned())
127182
{
183+
debug!("Will avoid unused {unused_id}");
128184
to_avoid.insert(unused_id);
129185
}
130186
}
@@ -165,7 +221,10 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
165221
.filter(|s| !s.is_registry())
166222
.collect();
167223

168-
let keep = |p: &PackageId| !to_avoid_sources.contains(&p.source_id()) && !to_avoid.contains(p);
224+
debug!("avoiding packages: {:?}", to_avoid);
225+
226+
let keep =
227+
|p: &PackageId| (!to_avoid_sources.contains(&p.source_id()) && !to_avoid.contains(p));
169228

170229
let mut resolve = ops::resolve_with_previous(
171230
&mut registry,
@@ -185,11 +244,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
185244
opts.precise.is_some(),
186245
&mut registry,
187246
)?;
188-
if opts.dry_run {
189-
opts.gctx
190-
.shell()
191-
.warn("not updating lockfile due to dry run")?;
192-
} else {
247+
if !opts.dry_run {
193248
ops::write_pkg_lockfile(ws, &mut resolve)?;
194249
}
195250
Ok(())
@@ -361,7 +416,10 @@ fn upgrade_dependency(
361416
.status_with_color("Upgrading", &upgrade_message, &style::GOOD)?;
362417
}
363418

364-
upgrades.insert((name.to_string(), dependency.source_id()), latest.clone());
419+
upgrades.insert(
420+
(name, dependency.source_id()),
421+
(current.clone(), latest.clone()),
422+
);
365423

366424
let req = OptVersionReq::Req(VersionReq::parse(&latest.to_string())?);
367425
let mut dep = dependency.clone();
@@ -433,7 +491,7 @@ pub fn write_manifest_upgrades(
433491
continue;
434492
};
435493

436-
let Some(latest) = upgrades.get(&(name.to_owned(), source_id)) else {
494+
let Some((_, latest)) = upgrades.get(&(name.into(), source_id)) else {
437495
trace!("skipping dependency without an upgrade: {name}");
438496
continue;
439497
};

0 commit comments

Comments
 (0)