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,17 +9,25 @@ 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 ,
19
27
pub to_update : Vec < String > ,
20
28
pub precise : Option < & ' a str > ,
21
29
pub recursive : bool ,
30
+ pub breaking : bool ,
22
31
pub dry_run : bool ,
23
32
pub workspace : bool ,
24
33
}
@@ -207,6 +216,246 @@ pub fn print_lockfile_changes(
207
216
}
208
217
}
209
218
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
+
210
459
fn print_lockfile_generation (
211
460
ws : & Workspace < ' _ > ,
212
461
resolve : & Resolve ,
0 commit comments