Skip to content

Commit 619d550

Browse files
committed
fix: Use ops::update_lockfile for consistency with non-breaking update.
1 parent 510e233 commit 619d550

File tree

3 files changed

+137
-32
lines changed

3 files changed

+137
-32
lines changed

src/bin/cargo/commands/update.rs

+26-13
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

+58-10
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,7 +109,38 @@ 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+
118+
for name in opts.to_update.iter() {
119+
// We still want to query any specified package, for the sake of
120+
// outputting errors if they don't exist.
121+
previous_resolve.query(name)?;
122+
}
123+
124+
for ((name, source_id), (version_req, _)) in upgrades.iter() {
125+
if let Some(matching_dep) = previous_resolve.iter().find(|dep| {
126+
dep.name() == *name
127+
&& dep.source_id() == *source_id
128+
&& version_req.matches(dep.version())
129+
}) {
130+
let spec = PackageIdSpec::new(name.to_string())
131+
.with_url(source_id.url().clone())
132+
.with_version(matching_dep.version().clone().into());
133+
let spec = format!("{spec}");
134+
let pid = previous_resolve.query(&spec)?;
135+
to_avoid.insert(pid);
136+
} else {
137+
// Should never happen
138+
anyhow::bail!(
139+
"no package named `{name}` with source `{source_id}` and version matching `{version_req}` in the previous lockfile",
140+
)
141+
}
142+
}
143+
} else if opts.to_update.is_empty() {
95144
if !opts.workspace {
96145
to_avoid.extend(previous_resolve.iter());
97146
to_avoid.extend(previous_resolve.unused_patches());
@@ -185,11 +234,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
185234
opts.precise.is_some(),
186235
&mut registry,
187236
)?;
188-
if opts.dry_run {
189-
opts.gctx
190-
.shell()
191-
.warn("not updating lockfile due to dry run")?;
192-
} else {
237+
if !opts.dry_run {
193238
ops::write_pkg_lockfile(ws, &mut resolve)?;
194239
}
195240
Ok(())
@@ -361,7 +406,10 @@ fn upgrade_dependency(
361406
.status_with_color("Upgrading", &upgrade_message, &style::GOOD)?;
362407
}
363408

364-
upgrades.insert((name.to_string(), dependency.source_id()), latest.clone());
409+
upgrades.insert(
410+
(name, dependency.source_id()),
411+
(current.clone(), latest.clone()),
412+
);
365413

366414
let req = OptVersionReq::Req(VersionReq::parse(&latest.to_string())?);
367415
let mut dep = dependency.clone();
@@ -433,7 +481,7 @@ pub fn write_manifest_upgrades(
433481
continue;
434482
};
435483

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

tests/testsuite/update.rs

+53-9
Original file line numberDiff line numberDiff line change
@@ -925,7 +925,7 @@ fn dry_run_update() {
925925
[LOCKING] 1 package to latest compatible version
926926
[UPDATING] serde v0.1.0 -> v0.1.1
927927
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
928-
[WARNING] not updating lockfile due to dry run
928+
[WARNING] aborting update due to dry run
929929
930930
"#]])
931931
.run();
@@ -1524,7 +1524,7 @@ fn report_behind() {
15241524
[LOCKING] 1 package to latest compatible version
15251525
[UPDATING] breaking v0.1.0 -> v0.1.1 (latest: v0.2.0)
15261526
[NOTE] pass `--verbose` to see 2 unchanged dependencies behind latest
1527-
[WARNING] not updating lockfile due to dry run
1527+
[WARNING] aborting update due to dry run
15281528
15291529
"#]])
15301530
.run();
@@ -1537,7 +1537,7 @@ fn report_behind() {
15371537
[UNCHANGED] pre v1.0.0-alpha.0 (latest: v1.0.0-alpha.1)
15381538
[UNCHANGED] two-ver v0.1.0 (latest: v0.2.0)
15391539
[NOTE] to see how you depend on a package, run `cargo tree --invert --package <dep>@<ver>`
1540-
[WARNING] not updating lockfile due to dry run
1540+
[WARNING] aborting update due to dry run
15411541
15421542
"#]])
15431543
.run();
@@ -1549,7 +1549,7 @@ fn report_behind() {
15491549
[UPDATING] `dummy-registry` index
15501550
[LOCKING] 0 packages to latest compatible versions
15511551
[NOTE] pass `--verbose` to see 3 unchanged dependencies behind latest
1552-
[WARNING] not updating lockfile due to dry run
1552+
[WARNING] aborting update due to dry run
15531553
15541554
"#]])
15551555
.run();
@@ -1562,7 +1562,7 @@ fn report_behind() {
15621562
[UNCHANGED] pre v1.0.0-alpha.0 (latest: v1.0.0-alpha.1)
15631563
[UNCHANGED] two-ver v0.1.0 (latest: v0.2.0)
15641564
[NOTE] to see how you depend on a package, run `cargo tree --invert --package <dep>@<ver>`
1565-
[WARNING] not updating lockfile due to dry run
1565+
[WARNING] aborting update due to dry run
15661566
15671567
"#]])
15681568
.run();
@@ -1912,10 +1912,13 @@ fn update_breaking() {
19121912
[UPDATING] multiple-registries v2.0.0 (registry `alternative`) -> v3.0.0
19131913
[UPDATING] multiple-registries v1.0.0 -> v2.0.0
19141914
[UPDATING] multiple-source-types v1.0.0 -> v2.0.0
1915+
[REMOVING] multiple-versions v1.0.0
1916+
[REMOVING] multiple-versions v2.0.0
19151917
[ADDING] multiple-versions v3.0.0
19161918
[UPDATING] platform-specific v1.0.0 -> v2.0.0
19171919
[UPDATING] shared v1.0.0 -> v2.0.0
19181920
[UPDATING] ws v1.0.0 -> v2.0.0
1921+
[NOTE] pass `--verbose` to see 4 unchanged dependencies behind latest
19191922
19201923
"#]])
19211924
.run();
@@ -2108,6 +2111,7 @@ fn update_breaking_specific_packages() {
21082111
[UPDATING] transitive-compatible v1.0.0 -> v1.0.1
21092112
[UPDATING] transitive-incompatible v1.0.0 -> v2.0.0
21102113
[UPDATING] ws v1.0.0 -> v2.0.0
2114+
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
21112115
21122116
"#]])
21132117
.run();
@@ -2163,6 +2167,8 @@ fn update_breaking_specific_packages_that_wont_update() {
21632167
.masquerade_as_nightly_cargo(&["update-breaking"])
21642168
.with_stderr_data(str![[r#"
21652169
[UPDATING] `dummy-registry` index
2170+
[LOCKING] 0 packages to latest compatible versions
2171+
[NOTE] pass `--verbose` to see 5 unchanged dependencies behind latest
21662172
21672173
"#]])
21682174
.run();
@@ -2271,13 +2277,27 @@ fn update_breaking_spec_version() {
22712277
// Spec version not matching our current dependencies
22722278
p.cargo("update -Zunstable-options --breaking [email protected]")
22732279
.masquerade_as_nightly_cargo(&["update-breaking"])
2274-
.with_stderr_data(str![[r#""#]])
2280+
.with_status(101)
2281+
.with_stderr_data(str![[r#"
2282+
[ERROR] package ID specification `[email protected]` did not match any packages
2283+
Did you mean one of these?
2284+
2285+
2286+
2287+
"#]])
22752288
.run();
22762289

22772290
// Spec source not matching our current dependencies
22782291
p.cargo("update -Zunstable-options --breaking https://alternative.com#[email protected]")
22792292
.masquerade_as_nightly_cargo(&["update-breaking"])
2280-
.with_stderr_data(str![[r#""#]])
2293+
.with_status(101)
2294+
.with_stderr_data(str![[r#"
2295+
[ERROR] package ID specification `https://alternative.com/#[email protected]` did not match any packages
2296+
Did you mean one of these?
2297+
2298+
2299+
2300+
"#]])
22812301
.run();
22822302

22832303
// Accepted spec
@@ -2288,6 +2308,7 @@ fn update_breaking_spec_version() {
22882308
[UPGRADING] incompatible ^1.0 -> ^2.0
22892309
[LOCKING] 1 package to latest compatible version
22902310
[UPDATING] incompatible v1.0.0 -> v2.0.0
2311+
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
22912312
22922313
"#]])
22932314
.run();
@@ -2301,6 +2322,7 @@ fn update_breaking_spec_version() {
23012322
[UPGRADING] incompatible ^2.0 -> ^3.0
23022323
[LOCKING] 1 package to latest compatible version
23032324
[UPDATING] incompatible v2.0.0 -> v3.0.0
2325+
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
23042326
23052327
"#]])
23062328
.run();
@@ -2310,19 +2332,35 @@ fn update_breaking_spec_version() {
23102332
.masquerade_as_nightly_cargo(&["update-breaking"])
23112333
.with_stderr_data(str![[r#"
23122334
[UPDATING] `dummy-registry` index
2335+
[LOCKING] 0 packages to latest compatible versions
2336+
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
23132337
23142338
"#]])
23152339
.run();
23162340

23172341
// Non-existing versions
23182342
p.cargo("update -Zunstable-options --breaking [email protected]")
23192343
.masquerade_as_nightly_cargo(&["update-breaking"])
2320-
.with_stderr_data(str![[r#""#]])
2344+
.with_status(101)
2345+
.with_stderr_data(str![[r#"
2346+
[ERROR] package ID specification `[email protected]` did not match any packages
2347+
Did you mean one of these?
2348+
2349+
2350+
2351+
"#]])
23212352
.run();
23222353

23232354
p.cargo("update -Zunstable-options --breaking [email protected]")
23242355
.masquerade_as_nightly_cargo(&["update-breaking"])
2325-
.with_stderr_data(str![[r#""#]])
2356+
.with_status(101)
2357+
.with_stderr_data(str![[r#"
2358+
[ERROR] package ID specification `[email protected]` did not match any packages
2359+
Did you mean one of these?
2360+
2361+
2362+
2363+
"#]])
23262364
.run();
23272365
}
23282366

@@ -2376,6 +2414,7 @@ fn update_breaking_spec_version_transitive() {
23762414
[UPGRADING] dep ^1.0 -> ^3.0
23772415
[LOCKING] 1 package to latest compatible version
23782416
[UPDATING] dep v1.0.0 -> v3.0.0
2417+
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
23792418
23802419
"#]])
23812420
.run();
@@ -2385,6 +2424,8 @@ fn update_breaking_spec_version_transitive() {
23852424
.masquerade_as_nightly_cargo(&["update-breaking"])
23862425
.with_stderr_data(str![[r#"
23872426
[UPDATING] `dummy-registry` index
2427+
[LOCKING] 0 packages to latest compatible versions
2428+
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
23882429
23892430
"#]])
23902431
.run();
@@ -2453,6 +2494,8 @@ fn update_breaking_mixed_compatibility() {
24532494
[UPDATING] `dummy-registry` index
24542495
[UPGRADING] mixed-compatibility ^1.0 -> ^2.0
24552496
[LOCKING] 1 package to latest compatible version
2497+
[REMOVING] mixed-compatibility v1.0.0
2498+
[REMOVING] mixed-compatibility v2.0.0
24562499
[ADDING] mixed-compatibility v2.0.1
24572500
24582501
"#]])
@@ -2544,6 +2587,7 @@ fn update_breaking_mixed_pinning_renaming() {
25442587
[ADDING] mixed-pinned v2.0.0
25452588
[ADDING] mixed-ws-pinned v2.0.0
25462589
[ADDING] renamed-from v2.0.0
2590+
[NOTE] pass `--verbose` to see 3 unchanged dependencies behind latest
25472591
25482592
"#]])
25492593
.run();

0 commit comments

Comments
 (0)