@@ -41,7 +41,7 @@ use crate::commands::project::ProjectError;
41
41
use crate :: commands:: reporters:: { PythonDownloadReporter , ResolverReporter } ;
42
42
use crate :: commands:: { pip, project, ExitStatus , SharedState } ;
43
43
use crate :: printer:: Printer ;
44
- use crate :: settings:: ResolverInstallerSettings ;
44
+ use crate :: settings:: { ResolverInstallerSettings , ResolverInstallerSettingsRef } ;
45
45
46
46
/// Add one or more packages to the project requirements.
47
47
#[ allow( clippy:: fn_params_excessive_bools) ]
@@ -479,22 +479,27 @@ pub(crate) async fn add(
479
479
return Ok ( ExitStatus :: Success ) ;
480
480
}
481
481
482
- let existing = project. pyproject_toml ( ) ;
482
+ // Store the content prior to any modifications.
483
+ let existing = project. pyproject_toml ( ) . as_ref ( ) . to_vec ( ) ;
484
+ let root = project. root ( ) . to_path_buf ( ) ;
483
485
484
486
// Update the `pypackage.toml` in-memory.
485
- let mut project = project
486
- . clone ( )
487
- . with_pyproject_toml ( toml:: from_str ( & content) ?)
488
- . context ( "Failed to update `pyproject.toml`" ) ?;
489
-
490
- // Lock and sync the environment, if necessary.
491
- let mut lock = match project:: lock:: do_safe_lock (
487
+ let project = project
488
+ . with_pyproject_toml ( toml:: from_str ( & content) . map_err ( ProjectError :: TomlParse ) ?)
489
+ . ok_or ( ProjectError :: TomlUpdate ) ?;
490
+
491
+ match lock_and_sync (
492
+ project,
493
+ & mut toml,
494
+ & edits,
495
+ & venv,
496
+ state,
492
497
locked,
493
498
frozen,
494
- project . workspace ( ) ,
495
- venv . interpreter ( ) ,
496
- settings . as_ref ( ) . into ( ) ,
497
- Box :: new ( DefaultResolveLogger ) ,
499
+ no_sync ,
500
+ & dependency_type ,
501
+ raw_sources ,
502
+ settings . as_ref ( ) ,
498
503
connectivity,
499
504
concurrency,
500
505
native_tls,
@@ -503,7 +508,7 @@ pub(crate) async fn add(
503
508
)
504
509
. await
505
510
{
506
- Ok ( result ) => result . into_lock ( ) ,
511
+ Ok ( ( ) ) => Ok ( ExitStatus :: Success ) ,
507
512
Err ( ProjectError :: Operation ( pip:: operations:: Error :: Resolve (
508
513
uv_resolver:: ResolveError :: NoSolution ( err) ,
509
514
) ) ) => {
@@ -513,13 +518,56 @@ pub(crate) async fn add(
513
518
514
519
// Revert the changes to the `pyproject.toml`, if necessary.
515
520
if modified {
516
- fs_err:: write ( project . root ( ) . join ( "pyproject.toml" ) , existing) ?;
521
+ fs_err:: write ( root. join ( "pyproject.toml" ) , existing) ?;
517
522
}
518
523
519
- return Ok ( ExitStatus :: Failure ) ;
524
+ Ok ( ExitStatus :: Failure )
520
525
}
521
- Err ( err) => return Err ( err. into ( ) ) ,
522
- } ;
526
+ Err ( err) => {
527
+ // Revert the changes to the `pyproject.toml`, if necessary.
528
+ if modified {
529
+ fs_err:: write ( root. join ( "pyproject.toml" ) , existing) ?;
530
+ }
531
+ Err ( err. into ( ) )
532
+ }
533
+ }
534
+ }
535
+
536
+ /// Re-lock and re-sync the project after a series of edits.
537
+ #[ allow( clippy:: fn_params_excessive_bools) ]
538
+ async fn lock_and_sync (
539
+ mut project : VirtualProject ,
540
+ toml : & mut PyProjectTomlMut ,
541
+ edits : & [ DependencyEdit < ' _ > ] ,
542
+ venv : & PythonEnvironment ,
543
+ state : SharedState ,
544
+ locked : bool ,
545
+ frozen : bool ,
546
+ no_sync : bool ,
547
+ dependency_type : & DependencyType ,
548
+ raw_sources : bool ,
549
+ settings : ResolverInstallerSettingsRef < ' _ > ,
550
+ connectivity : Connectivity ,
551
+ concurrency : Concurrency ,
552
+ native_tls : bool ,
553
+ cache : & Cache ,
554
+ printer : Printer ,
555
+ ) -> Result < ( ) , ProjectError > {
556
+ let mut lock = project:: lock:: do_safe_lock (
557
+ locked,
558
+ frozen,
559
+ project. workspace ( ) ,
560
+ venv. interpreter ( ) ,
561
+ settings. into ( ) ,
562
+ Box :: new ( DefaultResolveLogger ) ,
563
+ connectivity,
564
+ concurrency,
565
+ native_tls,
566
+ cache,
567
+ printer,
568
+ )
569
+ . await ?
570
+ . into_lock ( ) ;
523
571
524
572
// Avoid modifying the user request further if `--raw-sources` is set.
525
573
if !raw_sources {
@@ -543,7 +591,7 @@ pub(crate) async fn add(
543
591
544
592
// If any of the requirements were added without version specifiers, add a lower bound.
545
593
let mut modified = false ;
546
- for edit in & edits {
594
+ for edit in edits {
547
595
// Only set a minimum version for newly-added dependencies (as opposed to updates).
548
596
let ArrayEdit :: Add ( index) = & edit. edit else {
549
597
continue ;
@@ -599,49 +647,31 @@ pub(crate) async fn add(
599
647
600
648
// Update the `pypackage.toml` in-memory.
601
649
project = project
602
- . clone ( )
603
- . with_pyproject_toml ( toml:: from_str ( & content) ?)
604
- . context ( "Failed to update `pyproject.toml`" ) ?;
650
+ . with_pyproject_toml ( toml:: from_str ( & content) . map_err ( ProjectError :: TomlParse ) ?)
651
+ . ok_or ( ProjectError :: TomlUpdate ) ?;
605
652
606
653
// If the file was modified, we have to lock again, though the only expected change is
607
654
// the addition of the minimum version specifiers.
608
- lock = match project:: lock:: do_safe_lock (
655
+ lock = project:: lock:: do_safe_lock (
609
656
locked,
610
657
frozen,
611
658
project. workspace ( ) ,
612
659
venv. interpreter ( ) ,
613
- settings. as_ref ( ) . into ( ) ,
660
+ settings. into ( ) ,
614
661
Box :: new ( SummaryResolveLogger ) ,
615
662
connectivity,
616
663
concurrency,
617
664
native_tls,
618
665
cache,
619
666
printer,
620
667
)
621
- . await
622
- {
623
- Ok ( result) => result. into_lock ( ) ,
624
- Err ( ProjectError :: Operation ( pip:: operations:: Error :: Resolve (
625
- uv_resolver:: ResolveError :: NoSolution ( err) ,
626
- ) ) ) => {
627
- let header = err. header ( ) ;
628
- let report = miette:: Report :: new ( WithHelp { header, cause : err, help : Some ( "If this is intentional, run `uv add --frozen` to skip the lock and sync steps." ) } ) ;
629
- anstream:: eprint!( "{report:?}" ) ;
630
-
631
- // Revert the changes to the `pyproject.toml`, if necessary.
632
- if modified {
633
- fs_err:: write ( project. root ( ) . join ( "pyproject.toml" ) , existing) ?;
634
- }
635
-
636
- return Ok ( ExitStatus :: Failure ) ;
637
- }
638
- Err ( err) => return Err ( err. into ( ) ) ,
639
- } ;
668
+ . await ?
669
+ . into_lock ( ) ;
640
670
}
641
671
}
642
672
643
673
if no_sync {
644
- return Ok ( ExitStatus :: Success ) ;
674
+ return Ok ( ( ) ) ;
645
675
}
646
676
647
677
// Sync the environment.
@@ -663,19 +693,15 @@ pub(crate) async fn add(
663
693
}
664
694
} ;
665
695
666
- // Initialize any shared state.
667
- let state = SharedState :: default ( ) ;
668
- let install_options = InstallOptions :: default ( ) ;
669
-
670
- if let Err ( err) = project:: sync:: do_sync (
696
+ project:: sync:: do_sync (
671
697
InstallTarget :: from ( & project) ,
672
- & venv,
698
+ venv,
673
699
& lock,
674
700
& extras,
675
701
dev,
676
- install_options ,
702
+ InstallOptions :: default ( ) ,
677
703
Modifications :: Sufficient ,
678
- settings. as_ref ( ) . into ( ) ,
704
+ settings. into ( ) ,
679
705
& state,
680
706
Box :: new ( DefaultInstallLogger ) ,
681
707
connectivity,
@@ -684,16 +710,9 @@ pub(crate) async fn add(
684
710
cache,
685
711
printer,
686
712
)
687
- . await
688
- {
689
- // Revert the changes to the `pyproject.toml`, if necessary.
690
- if modified {
691
- fs_err:: write ( project. root ( ) . join ( "pyproject.toml" ) , existing) ?;
692
- }
693
- return Err ( err. into ( ) ) ;
694
- }
713
+ . await ?;
695
714
696
- Ok ( ExitStatus :: Success )
715
+ Ok ( ( ) )
697
716
}
698
717
699
718
/// Resolves the source for a requirement and processes it into a PEP 508 compliant format.
0 commit comments