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