1
+ use crate :: core:: dependency:: Dependency ;
1
2
use crate :: core:: registry:: PackageRegistry ;
2
3
use crate :: core:: resolver:: features:: { CliFeatures , HasDevUnits } ;
3
4
use crate :: core:: shell:: Verbosity ;
@@ -8,11 +9,18 @@ use crate::ops;
8
9
use crate :: sources:: source:: QueryKind ;
9
10
use crate :: util:: cache_lock:: CacheLockMode ;
10
11
use 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 } ;
13
19
use 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 > ;
16
24
17
25
pub struct UpdateOptions < ' a > {
18
26
pub gctx : & ' a GlobalContext ,
@@ -206,6 +214,251 @@ pub fn print_lockfile_changes(
206
214
print_lockfile_generation ( ws, resolve, registry)
207
215
}
208
216
}
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
+ }
209
462
210
463
fn print_lockfile_generation (
211
464
ws : & Workspace < ' _ > ,
0 commit comments