Skip to content

Commit 8a9af34

Browse files
committed
Implement cargo update --breaking. Work in progress.
1 parent d001b81 commit 8a9af34

File tree

9 files changed

+423
-59
lines changed

9 files changed

+423
-59
lines changed

Diff for: crates/cargo-test-support/src/compare.rs

+1
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ fn substitute_macros(input: &str) -> String {
205205
("[DIRTY]", " Dirty"),
206206
("[LOCKING]", " Locking"),
207207
("[UPDATING]", " Updating"),
208+
("[UPGRADING]", " Upgrading"),
208209
("[ADDING]", " Adding"),
209210
("[REMOVING]", " Removing"),
210211
("[REMOVED]", " Removed"),

Diff for: src/bin/cargo/commands/update.rs

+15-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ pub fn cli() -> Command {
3535
.value_name("PRECISE")
3636
.requires("package-group"),
3737
)
38+
.arg(
39+
flag(
40+
"breaking",
41+
"Update [SPEC] to latest incompatible versions (unless pinned)",
42+
)
43+
.short('b'),
44+
)
3845
.arg_silent_suggestion()
3946
.arg(
4047
flag("workspace", "Only update the workspace packages")
@@ -59,7 +66,8 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
5966
gctx.cli_unstable().msrv_policy,
6067
)?;
6168
}
62-
let ws = args.workspace(gctx)?;
69+
70+
let mut ws = args.workspace(gctx)?;
6371

6472
if args.is_present_with_zero_values("package") {
6573
print_available_packages(&ws)?;
@@ -84,11 +92,16 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
8492
let update_opts = UpdateOptions {
8593
recursive: args.flag("recursive"),
8694
precise: args.get_one::<String>("precise").map(String::as_str),
95+
breaking: args.flag("breaking"),
8796
to_update,
8897
dry_run: args.dry_run(),
8998
workspace: args.flag("workspace"),
9099
gctx,
91100
};
92-
ops::update_lockfile(&ws, &update_opts)?;
101+
102+
let upgrades = ops::update_manifests(&mut ws, &update_opts)?;
103+
ops::update_lockfile(&ws, &update_opts, &upgrades)?;
104+
ops::write_manifests(&ws, &update_opts, &upgrades)?;
105+
93106
Ok(())
94107
}

Diff for: src/cargo/core/summary.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -103,15 +103,25 @@ impl Summary {
103103
Rc::make_mut(&mut self.inner).checksum = Some(cksum);
104104
}
105105

106-
pub fn map_dependencies<F>(mut self, f: F) -> Summary
106+
pub fn map_dependencies<F>(self, mut f: F) -> Summary
107107
where
108108
F: FnMut(Dependency) -> Dependency,
109+
{
110+
self.try_map_dependencies(|dep| Ok(f(dep))).unwrap()
111+
}
112+
113+
pub fn try_map_dependencies<F>(mut self, f: F) -> CargoResult<Summary>
114+
where
115+
F: FnMut(Dependency) -> CargoResult<Dependency>,
109116
{
110117
{
111118
let slot = &mut Rc::make_mut(&mut self.inner).dependencies;
112-
*slot = mem::take(slot).into_iter().map(f).collect();
119+
*slot = mem::take(slot)
120+
.into_iter()
121+
.map(f)
122+
.collect::<CargoResult<_>>()?;
113123
}
114-
self
124+
Ok(self)
115125
}
116126

117127
pub fn map_source(self, to_replace: SourceId, replace_with: SourceId) -> Summary {

Diff for: src/cargo/ops/cargo_update.rs

+173-4
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,21 @@ use crate::ops;
88
use crate::sources::source::QueryKind;
99
use crate::util::cache_lock::CacheLockMode;
1010
use crate::util::context::GlobalContext;
11-
use crate::util::style;
11+
use crate::util::toml_mut::dependency::{RegistrySource, Source};
12+
use crate::util::toml_mut::manifest::LocalManifest;
1213
use crate::util::CargoResult;
14+
use crate::util::{style, OptVersionReq};
15+
use semver::{Version, VersionReq};
1316
use std::cmp::Ordering;
14-
use std::collections::{BTreeMap, HashSet};
17+
use std::collections::{BTreeMap, HashMap, HashSet};
1518
use tracing::debug;
1619

1720
pub struct UpdateOptions<'a> {
1821
pub gctx: &'a GlobalContext,
1922
pub to_update: Vec<String>,
2023
pub precise: Option<&'a str>,
2124
pub recursive: bool,
25+
pub breaking: bool,
2226
pub dry_run: bool,
2327
pub workspace: bool,
2428
}
@@ -41,7 +45,11 @@ pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> {
4145
Ok(())
4246
}
4347

44-
pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoResult<()> {
48+
pub fn update_lockfile(
49+
ws: &Workspace<'_>,
50+
opts: &UpdateOptions<'_>,
51+
upgrades: &HashMap<String, Version>,
52+
) -> CargoResult<()> {
4553
if opts.recursive && opts.precise.is_some() {
4654
anyhow::bail!("cannot specify both recursive and precise simultaneously")
4755
}
@@ -157,7 +165,12 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
157165
.filter(|s| !s.is_registry())
158166
.collect();
159167

160-
let keep = |p: &PackageId| !to_avoid_sources.contains(&p.source_id()) && !to_avoid.contains(p);
168+
let keep = |p: &PackageId| {
169+
(!to_avoid_sources.contains(&p.source_id()) && !to_avoid.contains(p))
170+
// In case of `--breaking`, we want to keep all packages unchanged that
171+
// didn't get upgraded.
172+
|| (opts.breaking && !upgrades.contains_key(&p.name().to_string()))
173+
};
161174

162175
let mut resolve = ops::resolve_with_previous(
163176
&mut registry,
@@ -207,6 +220,162 @@ pub fn print_lockfile_changes(
207220
}
208221
}
209222

223+
pub fn update_manifests(
224+
ws: &mut Workspace<'_>,
225+
opts: &UpdateOptions<'_>,
226+
) -> CargoResult<HashMap<String, Version>> {
227+
let mut upgrades = HashMap::new();
228+
229+
if !opts.breaking {
230+
return Ok(upgrades);
231+
}
232+
233+
// Updates often require a lot of modifications to the registry, so ensure
234+
// that we're synchronized against other Cargos.
235+
let _lock = ws
236+
.gctx()
237+
.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
238+
239+
let mut registry = PackageRegistry::new(opts.gctx)?;
240+
registry.lock_patches();
241+
242+
for member in ws.members_mut() {
243+
debug!("updating manifest for {}", member.name());
244+
245+
let new_summary = member.manifest().summary().clone().try_map_dependencies(
246+
|dependency| -> CargoResult<_> {
247+
if let OptVersionReq::Req(current) = dependency.version_req() {
248+
let query = crate::core::dependency::Dependency::parse(
249+
dependency.package_name(),
250+
None,
251+
dependency.source_id().clone(),
252+
)?;
253+
254+
let possibilities = {
255+
loop {
256+
match registry.query_vec(&query, QueryKind::Exact) {
257+
std::task::Poll::Ready(res) => {
258+
break res?;
259+
}
260+
std::task::Poll::Pending => registry.block_until_ready()?,
261+
}
262+
}
263+
};
264+
265+
let latest = if !possibilities.is_empty() {
266+
possibilities
267+
.iter()
268+
.map(|s| s.as_summary())
269+
.map(|s| s.version().clone())
270+
.max()
271+
} else {
272+
None
273+
};
274+
275+
if let Some(latest) = latest.clone() {
276+
if !current.matches(&latest) {
277+
debug!(
278+
"upgrading {} from {} to {}",
279+
dependency.package_name(),
280+
current,
281+
latest
282+
);
283+
284+
opts.gctx.shell().status_with_color(
285+
"Upgrading",
286+
format!(
287+
"{} {} -> v{}",
288+
dependency.package_name(),
289+
current,
290+
latest.to_string()
291+
),
292+
&style::GOOD,
293+
)?;
294+
295+
upgrades.insert(dependency.package_name().to_string(), latest.clone());
296+
297+
let req = OptVersionReq::Req(VersionReq::parse(&latest.to_string())?);
298+
let mut dep = dependency.clone();
299+
dep.set_version_req(req);
300+
return Ok(dep);
301+
}
302+
}
303+
}
304+
305+
Ok(dependency)
306+
},
307+
)?;
308+
309+
let summary = member.manifest_mut().summary_mut();
310+
*summary = new_summary;
311+
}
312+
313+
Ok(upgrades)
314+
}
315+
316+
pub fn write_manifests(
317+
ws: &Workspace<'_>,
318+
opts: &UpdateOptions<'_>,
319+
upgrades: &HashMap<String, Version>,
320+
) -> CargoResult<()> {
321+
if !opts.breaking {
322+
return Ok(());
323+
}
324+
325+
for member in ws.members() {
326+
debug!("writing manifest for {}", member.name());
327+
328+
let manifest_path = member.manifest_path();
329+
330+
let mut local_manifest = LocalManifest::try_new(&manifest_path)?;
331+
for dep_table in local_manifest.get_dependency_tables_mut() {
332+
for (dep_key, dep_item) in dep_table.iter_mut() {
333+
debug!("updating dependency {}", dep_key);
334+
335+
let dep_key = dep_key.get();
336+
let dependency = match crate::util::toml_mut::dependency::Dependency::from_toml(
337+
&manifest_path,
338+
dep_key,
339+
dep_item,
340+
) {
341+
Ok(dependency) => dependency,
342+
Err(err) => {
343+
opts.gctx
344+
.shell()
345+
.warn(&format!("ignoring {dep_key}, unsupported entry: {err}"))?;
346+
continue;
347+
}
348+
};
349+
350+
if let crate::util::toml_mut::dependency::MaybeWorkspace::Other(_) =
351+
dependency.source_id(opts.gctx)?
352+
{
353+
if let Some(latest) = upgrades.get(&dependency.name) {
354+
let mut dep =
355+
crate::util::toml_mut::dependency::Dependency::new(&dependency.name);
356+
dep.source = Some(Source::Registry(RegistrySource {
357+
version: latest.to_string(),
358+
}));
359+
360+
*dep_item = dep.to_toml(manifest_path);
361+
}
362+
}
363+
}
364+
}
365+
366+
if opts.dry_run {
367+
opts.gctx
368+
.shell()
369+
.warn("not updating manifest due to dry run")?;
370+
} else {
371+
debug!("writing updated manifest to {}", manifest_path.display());
372+
local_manifest.write()?;
373+
}
374+
}
375+
376+
Ok(())
377+
}
378+
210379
fn print_lockfile_generation(
211380
ws: &Workspace<'_>,
212381
resolve: &Resolve,

Diff for: src/cargo/ops/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pub use self::cargo_uninstall::uninstall;
1919
pub use self::cargo_update::generate_lockfile;
2020
pub use self::cargo_update::print_lockfile_changes;
2121
pub use self::cargo_update::update_lockfile;
22+
pub use self::cargo_update::update_manifests;
23+
pub use self::cargo_update::write_manifests;
2224
pub use self::cargo_update::UpdateOptions;
2325
pub use self::fix::{fix, fix_exec_rustc, fix_get_proxy_lock_addr, FixOptions};
2426
pub use self::lockfile::{load_pkg_lockfile, resolve_to_string, write_pkg_lockfile};

Diff for: src/cargo/util/toml/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::ffi::OsStr;
44
use std::path::{Path, PathBuf};
55
use std::rc::Rc;
66
use std::str::{self, FromStr};
7+
use tracing::debug;
78

89
use crate::AlreadyPrintedError;
910
use anyhow::{anyhow, bail, Context as _};
@@ -60,6 +61,12 @@ pub fn read_manifest(
6061
let mut warnings = Default::default();
6162
let mut errors = Default::default();
6263

64+
debug!(
65+
"read_manifest; path={}; source-id={}",
66+
path.display(),
67+
source_id
68+
);
69+
6370
let contents =
6471
read_toml_string(path, gctx).map_err(|err| ManifestError::new(err, path.into()))?;
6572
let document =

Diff for: src/cargo/util/toml_mut/manifest.rs

+56
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ impl DepTable {
6464
vec![self.kind.kind_table()]
6565
}
6666
}
67+
68+
fn kind_table(&self) -> &str {
69+
match self.kind {
70+
DepKind::Normal => "dependencies",
71+
DepKind::Development => "dev-dependencies",
72+
DepKind::Build => "build-dependencies",
73+
}
74+
}
6775
}
6876

6977
impl Default for DepTable {
@@ -397,6 +405,54 @@ impl LocalManifest {
397405
Ok(())
398406
}
399407

408+
/// Allow mutating depedencies, wherever they live
409+
pub fn get_dependency_tables_mut(
410+
&mut self,
411+
) -> impl Iterator<Item = &mut dyn toml_edit::TableLike> + '_ {
412+
let root = self.data.as_table_mut();
413+
root.iter_mut().flat_map(|(k, v)| {
414+
if DepTable::KINDS
415+
.iter()
416+
.any(|kind| kind.kind_table() == k.get())
417+
{
418+
v.as_table_like_mut().into_iter().collect::<Vec<_>>()
419+
} else if k == "workspace" {
420+
v.as_table_like_mut()
421+
.unwrap()
422+
.iter_mut()
423+
.filter_map(|(k, v)| {
424+
if k.get() == "dependencies" {
425+
v.as_table_like_mut()
426+
} else {
427+
None
428+
}
429+
})
430+
.collect::<Vec<_>>()
431+
} else if k == "target" {
432+
v.as_table_like_mut()
433+
.unwrap()
434+
.iter_mut()
435+
.flat_map(|(_, v)| {
436+
v.as_table_like_mut().into_iter().flat_map(|v| {
437+
v.iter_mut().filter_map(|(k, v)| {
438+
if DepTable::KINDS
439+
.iter()
440+
.any(|kind| kind.kind_table() == k.get())
441+
{
442+
v.as_table_like_mut()
443+
} else {
444+
None
445+
}
446+
})
447+
})
448+
})
449+
.collect::<Vec<_>>()
450+
} else {
451+
Vec::new()
452+
}
453+
})
454+
}
455+
400456
/// Remove references to `dep_key` if its no longer present.
401457
pub fn gc_dep(&mut self, dep_key: &str) {
402458
let explicit_dep_activation = self.is_explicit_dep_activation(dep_key);

0 commit comments

Comments
 (0)