1+ use crate :: core:: dependency:: Dependency ;
12use crate :: core:: registry:: PackageRegistry ;
23use crate :: core:: resolver:: features:: { CliFeatures , HasDevUnits } ;
34use crate :: core:: shell:: Verbosity ;
@@ -8,17 +9,25 @@ use crate::ops;
89use crate :: sources:: source:: QueryKind ;
910use crate :: util:: cache_lock:: CacheLockMode ;
1011use crate :: util:: context:: GlobalContext ;
11- use crate :: util:: style;
12- use crate :: util:: CargoResult ;
12+ use crate :: util:: toml_mut:: dependency:: { MaybeWorkspace , Source } ;
13+ use crate :: util:: toml_mut:: manifest:: LocalManifest ;
14+ use crate :: util:: toml_mut:: upgrade:: upgrade_requirement;
15+ use crate :: util:: { style, OptVersionReq } ;
16+ use crate :: util:: { CargoResult , VersionExt } ;
17+ use itertools:: Itertools ;
18+ use semver:: { Op , Version , VersionReq } ;
1319use std:: cmp:: Ordering ;
14- use std:: collections:: { BTreeMap , HashSet } ;
15- use tracing:: debug;
20+ use std:: collections:: { BTreeMap , HashMap , HashSet } ;
21+ use tracing:: { debug, trace} ;
22+
23+ pub type UpgradeMap = HashMap < ( String , SourceId ) , Version > ;
1624
1725pub struct UpdateOptions < ' a > {
1826 pub gctx : & ' a GlobalContext ,
1927 pub to_update : Vec < String > ,
2028 pub precise : Option < & ' a str > ,
2129 pub recursive : bool ,
30+ pub breaking : bool ,
2231 pub dry_run : bool ,
2332 pub workspace : bool ,
2433}
@@ -207,6 +216,246 @@ pub fn print_lockfile_changes(
207216 }
208217}
209218
219+ pub fn upgrade_manifests (
220+ ws : & mut Workspace < ' _ > ,
221+ opts : & UpdateOptions < ' _ > ,
222+ ) -> CargoResult < UpgradeMap > {
223+ let mut upgrades = HashMap :: new ( ) ;
224+ let mut upgrade_messages = HashSet :: new ( ) ;
225+
226+ // Updates often require a lot of modifications to the registry, so ensure
227+ // that we're synchronized against other Cargos.
228+ let _lock = ws
229+ . gctx ( )
230+ . acquire_package_cache_lock ( CacheLockMode :: DownloadExclusive ) ?;
231+
232+ let mut registry = PackageRegistry :: new ( opts. gctx ) ?;
233+ registry. lock_patches ( ) ;
234+
235+ for member in ws. members_mut ( ) . sorted ( ) {
236+ debug ! ( "upgrading manifest for `{}`" , member. name( ) ) ;
237+
238+ * member. manifest_mut ( ) . summary_mut ( ) = member
239+ . manifest ( )
240+ . summary ( )
241+ . clone ( )
242+ . try_map_dependencies ( |d| {
243+ upgrade_dependency ( & mut registry, & mut upgrades, & mut upgrade_messages, opts, d)
244+ } ) ?;
245+ }
246+
247+ Ok ( upgrades)
248+ }
249+
250+ fn upgrade_dependency (
251+ registry : & mut PackageRegistry < ' _ > ,
252+ upgrades : & mut UpgradeMap ,
253+ upgrade_messages : & mut HashSet < String > ,
254+ opts : & UpdateOptions < ' _ > ,
255+ dependency : Dependency ,
256+ ) -> CargoResult < Dependency > {
257+ let name = dependency. package_name ( ) ;
258+ let renamed_to = dependency. name_in_toml ( ) ;
259+
260+ if name != renamed_to {
261+ trace ! (
262+ "skipping dependency renamed from `{}` to `{}`" ,
263+ name,
264+ renamed_to
265+ ) ;
266+ return Ok ( dependency) ;
267+ }
268+
269+ if !opts. to_update . is_empty ( ) && !opts. to_update . contains ( & name. to_string ( ) ) {
270+ trace ! ( "skipping dependency `{}` not selected for upgrading" , name) ;
271+ return Ok ( dependency) ;
272+ }
273+
274+ if !dependency. source_id ( ) . is_registry ( ) {
275+ trace ! ( "skipping non-registry dependency: {}" , name) ;
276+ return Ok ( dependency) ;
277+ }
278+
279+ let version_req = dependency. version_req ( ) ;
280+
281+ let OptVersionReq :: Req ( current) = version_req else {
282+ trace ! (
283+ "skipping dependency `{}` without a simple version requirement: {}" ,
284+ name,
285+ version_req
286+ ) ;
287+ return Ok ( dependency) ;
288+ } ;
289+
290+ let [ comparator] = & current. comparators [ ..] else {
291+ trace ! (
292+ "skipping dependency `{}` with multiple version comparators: {:?}" ,
293+ name,
294+ & current. comparators
295+ ) ;
296+ return Ok ( dependency) ;
297+ } ;
298+
299+ if comparator. op != Op :: Caret {
300+ trace ! ( "skipping non-caret dependency `{}`: {}" , name, comparator) ;
301+ return Ok ( dependency) ;
302+ }
303+
304+ let query =
305+ crate :: core:: dependency:: Dependency :: parse ( name, None , dependency. source_id ( ) . clone ( ) ) ?;
306+
307+ let possibilities = {
308+ loop {
309+ match registry. query_vec ( & query, QueryKind :: Exact ) {
310+ std:: task:: Poll :: Ready ( res) => {
311+ break res?;
312+ }
313+ std:: task:: Poll :: Pending => registry. block_until_ready ( ) ?,
314+ }
315+ }
316+ } ;
317+
318+ let latest = if !possibilities. is_empty ( ) {
319+ possibilities
320+ . iter ( )
321+ . map ( |s| s. as_summary ( ) )
322+ . map ( |s| s. version ( ) )
323+ . filter ( |v| !v. is_prerelease ( ) )
324+ . max ( )
325+ } else {
326+ None
327+ } ;
328+
329+ let Some ( latest) = latest else {
330+ trace ! (
331+ "skipping dependency `{}` without any published versions" ,
332+ name
333+ ) ;
334+ return Ok ( dependency) ;
335+ } ;
336+
337+ if current. matches ( & latest) {
338+ trace ! (
339+ "skipping dependency `{}` without a breaking update available" ,
340+ name
341+ ) ;
342+ return Ok ( dependency) ;
343+ }
344+
345+ let Some ( new_req_string) = upgrade_requirement ( & current. to_string ( ) , latest) ? else {
346+ trace ! (
347+ "skipping dependency `{}` because the version requirement didn't change" ,
348+ name
349+ ) ;
350+ return Ok ( dependency) ;
351+ } ;
352+
353+ let upgrade_message = format ! ( "{} {} -> {}" , name, current, new_req_string) ;
354+ trace ! ( upgrade_message) ;
355+
356+ if upgrade_messages. insert ( upgrade_message. clone ( ) ) {
357+ opts. gctx
358+ . shell ( )
359+ . status_with_color ( "Upgrading" , & upgrade_message, & style:: GOOD ) ?;
360+ }
361+
362+ upgrades. insert ( ( name. to_string ( ) , dependency. source_id ( ) ) , latest. clone ( ) ) ;
363+
364+ let req = OptVersionReq :: Req ( VersionReq :: parse ( & latest. to_string ( ) ) ?) ;
365+ let mut dep = dependency. clone ( ) ;
366+ dep. set_version_req ( req) ;
367+ Ok ( dep)
368+ }
369+
370+ /// Update manifests with upgraded versions, and write to disk. Based on cargo-edit.
371+ /// Returns true if any file has changed.
372+ pub fn write_manifest_upgrades (
373+ ws : & Workspace < ' _ > ,
374+ opts : & UpdateOptions < ' _ > ,
375+ upgrades : & UpgradeMap ,
376+ ) -> CargoResult < bool > {
377+ if upgrades. is_empty ( ) {
378+ return Ok ( false ) ;
379+ }
380+
381+ let mut any_file_has_changed = false ;
382+
383+ let manifest_paths = std:: iter:: once ( ws. root_manifest ( ) )
384+ . chain ( ws. members ( ) . map ( |member| member. manifest_path ( ) ) )
385+ . collect :: < Vec < _ > > ( ) ;
386+
387+ for manifest_path in manifest_paths {
388+ trace ! (
389+ "updating TOML manifest at `{:?}` with upgraded dependencies" ,
390+ manifest_path
391+ ) ;
392+
393+ let crate_root = manifest_path
394+ . parent ( )
395+ . expect ( "manifest path is absolute" )
396+ . to_owned ( ) ;
397+
398+ let mut local_manifest = LocalManifest :: try_new ( & manifest_path) ?;
399+ let mut manifest_has_changed = false ;
400+
401+ for dep_table in local_manifest. get_dependency_tables_mut ( ) {
402+ for ( mut dep_key, dep_item) in dep_table. iter_mut ( ) {
403+ let dep_key_str = dep_key. get ( ) ;
404+ let dependency = crate :: util:: toml_mut:: dependency:: Dependency :: from_toml (
405+ & manifest_path,
406+ dep_key_str,
407+ dep_item,
408+ ) ?;
409+
410+ let Some ( current) = dependency. version ( ) else {
411+ trace ! ( "skipping dependency without a version: {}" , dependency. name) ;
412+ continue ;
413+ } ;
414+
415+ let ( MaybeWorkspace :: Other ( source_id) , Some ( Source :: Registry ( source) ) ) =
416+ ( dependency. source_id ( opts. gctx ) ?, dependency. source ( ) )
417+ else {
418+ trace ! ( "skipping non-registry dependency: {}" , dependency. name) ;
419+ continue ;
420+ } ;
421+
422+ let Some ( latest) = upgrades. get ( & ( dependency. name . to_owned ( ) , source_id) ) else {
423+ trace ! (
424+ "skipping dependency without an upgrade: {}" ,
425+ dependency. name
426+ ) ;
427+ continue ;
428+ } ;
429+
430+ let Some ( new_req_string) = upgrade_requirement ( current, latest) ? else {
431+ trace ! (
432+ "skipping dependency `{}` because the version requirement didn't change" ,
433+ dependency. name
434+ ) ;
435+ continue ;
436+ } ;
437+
438+ let mut dep = dependency. clone ( ) ;
439+ let mut source = source. clone ( ) ;
440+ source. version = new_req_string;
441+ dep. source = Some ( Source :: Registry ( source) ) ;
442+
443+ trace ! ( "upgrading dependency {}" , dependency. name) ;
444+ dep. update_toml ( & crate_root, & mut dep_key, dep_item) ;
445+ manifest_has_changed = true ;
446+ any_file_has_changed = true ;
447+ }
448+ }
449+
450+ if manifest_has_changed && !opts. dry_run {
451+ debug ! ( "writing upgraded manifest to {}" , manifest_path. display( ) ) ;
452+ local_manifest. write ( ) ?;
453+ }
454+ }
455+
456+ Ok ( any_file_has_changed)
457+ }
458+
210459fn print_lockfile_generation (
211460 ws : & Workspace < ' _ > ,
212461 resolve : & Resolve ,
0 commit comments