1
1
use std:: collections:: HashSet ;
2
2
use std:: fmt:: Write ;
3
3
use std:: path:: Path ;
4
+ use std:: time:: Instant ;
4
5
5
6
use anstream:: eprint;
6
7
use anyhow:: { anyhow, Context , Result } ;
@@ -11,7 +12,8 @@ use tempfile::tempdir_in;
11
12
use tracing:: debug;
12
13
13
14
use distribution_types:: {
14
- IndexLocations , InstalledMetadata , LocalDist , LocalEditable , Name , Resolution ,
15
+ DistributionMetadata , IndexLocations , InstalledMetadata , LocalDist , LocalEditable , Name ,
16
+ Resolution ,
15
17
} ;
16
18
use install_wheel_rs:: linker:: LinkMode ;
17
19
use pep508_rs:: { MarkerEnvironment , Requirement } ;
@@ -39,7 +41,7 @@ use crate::commands::{compile_bytecode, elapsed, ChangeEvent, ChangeEventKind, E
39
41
use crate :: printer:: Printer ;
40
42
use crate :: requirements:: { ExtrasSpecification , RequirementsSource , RequirementsSpecification } ;
41
43
42
- use super :: Upgrade ;
44
+ use super :: { DryRunEvent , Upgrade } ;
43
45
44
46
/// Install packages into the current environment.
45
47
#[ allow( clippy:: too_many_arguments, clippy:: fn_params_excessive_bools) ]
@@ -69,6 +71,7 @@ pub(crate) async fn pip_install(
69
71
break_system_packages : bool ,
70
72
native_tls : bool ,
71
73
cache : Cache ,
74
+ dry_run : bool ,
72
75
printer : Printer ,
73
76
) -> Result < ExitStatus > {
74
77
let start = std:: time:: Instant :: now ( ) ;
@@ -164,6 +167,9 @@ pub(crate) async fn pip_install(
164
167
)
165
168
. dimmed( )
166
169
) ?;
170
+ if dry_run {
171
+ writeln ! ( printer. stderr( ) , "Would make no changes" ) ?;
172
+ }
167
173
return Ok ( ExitStatus :: Success ) ;
168
174
}
169
175
@@ -320,6 +326,7 @@ pub(crate) async fn pip_install(
320
326
& install_dispatch,
321
327
& cache,
322
328
& venv,
329
+ dry_run,
323
330
printer,
324
331
)
325
332
. await ?;
@@ -392,7 +399,7 @@ async fn build_editables(
392
399
build_dispatch : & BuildDispatch < ' _ > ,
393
400
printer : Printer ,
394
401
) -> Result < Vec < BuiltEditable > , Error > {
395
- let start = std :: time :: Instant :: now ( ) ;
402
+ let start = Instant :: now ( ) ;
396
403
397
404
let downloader = Downloader :: new ( cache, tags, client, build_dispatch)
398
405
. with_reporter ( DownloadReporter :: from ( printer) . with_length ( editables. len ( ) as u64 ) ) ;
@@ -558,6 +565,7 @@ async fn install(
558
565
build_dispatch : & BuildDispatch < ' _ > ,
559
566
cache : & Cache ,
560
567
venv : & PythonEnvironment ,
568
+ dry_run : bool ,
561
569
printer : Printer ,
562
570
) -> Result < ( ) , Error > {
563
571
let start = std:: time:: Instant :: now ( ) ;
@@ -572,12 +580,7 @@ async fn install(
572
580
573
581
// Partition into those that should be linked from the cache (`local`), those that need to be
574
582
// downloaded (`remote`), and those that should be removed (`extraneous`).
575
- let Plan {
576
- local,
577
- remote,
578
- reinstalls,
579
- extraneous : _,
580
- } = Planner :: with_requirements ( & requirements)
583
+ let plan = Planner :: with_requirements ( & requirements)
581
584
. with_editable_requirements ( & editables)
582
585
. build (
583
586
site_packages,
@@ -590,6 +593,17 @@ async fn install(
590
593
)
591
594
. context ( "Failed to determine installation plan" ) ?;
592
595
596
+ if dry_run {
597
+ return report_dry_run ( resolution, plan, start, printer) ;
598
+ }
599
+
600
+ let Plan {
601
+ local,
602
+ remote,
603
+ reinstalls,
604
+ extraneous : _,
605
+ } = plan;
606
+
593
607
// Nothing to do.
594
608
if remote. is_empty ( ) && local. is_empty ( ) && reinstalls. is_empty ( ) {
595
609
let s = if resolution. len ( ) == 1 { "" } else { "s" } ;
@@ -603,7 +617,6 @@ async fn install(
603
617
)
604
618
. dimmed( )
605
619
) ?;
606
-
607
620
return Ok ( ( ) ) ;
608
621
}
609
622
@@ -622,7 +635,7 @@ async fn install(
622
635
let wheels = if remote. is_empty ( ) {
623
636
vec ! [ ]
624
637
} else {
625
- let start = std :: time :: Instant :: now ( ) ;
638
+ let start = Instant :: now ( ) ;
626
639
627
640
let downloader = Downloader :: new ( cache, tags, client, build_dispatch)
628
641
. with_reporter ( DownloadReporter :: from ( printer) . with_length ( remote. len ( ) as u64 ) ) ;
@@ -728,6 +741,135 @@ async fn install(
728
741
}
729
742
}
730
743
744
+ #[ allow( clippy:: items_after_statements) ]
745
+ fn report_dry_run (
746
+ resolution : & Resolution ,
747
+ plan : Plan ,
748
+ start : Instant ,
749
+ printer : Printer ,
750
+ ) -> Result < ( ) , Error > {
751
+ let Plan {
752
+ local,
753
+ remote,
754
+ reinstalls,
755
+ extraneous : _,
756
+ } = plan;
757
+
758
+ // Nothing to do.
759
+ if remote. is_empty ( ) && local. is_empty ( ) && reinstalls. is_empty ( ) {
760
+ let s = if resolution. len ( ) == 1 { "" } else { "s" } ;
761
+ writeln ! (
762
+ printer. stderr( ) ,
763
+ "{}" ,
764
+ format!(
765
+ "Audited {} in {}" ,
766
+ format!( "{} package{}" , resolution. len( ) , s) . bold( ) ,
767
+ elapsed( start. elapsed( ) )
768
+ )
769
+ . dimmed( )
770
+ ) ?;
771
+ writeln ! ( printer. stderr( ) , "Would make no changes" ) ?;
772
+ return Ok ( ( ) ) ;
773
+ }
774
+
775
+ // Map any registry-based requirements back to those returned by the resolver.
776
+ let remote = remote
777
+ . iter ( )
778
+ . map ( |dist| {
779
+ resolution
780
+ . get ( & dist. name )
781
+ . cloned ( )
782
+ . expect ( "Resolution should contain all packages" )
783
+ } )
784
+ . collect :: < Vec < _ > > ( ) ;
785
+
786
+ // Download, build, and unzip any missing distributions.
787
+ let wheels = if remote. is_empty ( ) {
788
+ vec ! [ ]
789
+ } else {
790
+ let s = if remote. len ( ) == 1 { "" } else { "s" } ;
791
+ writeln ! (
792
+ printer. stderr( ) ,
793
+ "{}" ,
794
+ format!(
795
+ "Would download {}" ,
796
+ format!( "{} package{}" , remote. len( ) , s) . bold( ) ,
797
+ )
798
+ . dimmed( )
799
+ ) ?;
800
+ remote
801
+ } ;
802
+
803
+ // Remove any existing installations.
804
+ if !reinstalls. is_empty ( ) {
805
+ let s = if reinstalls. len ( ) == 1 { "" } else { "s" } ;
806
+ writeln ! (
807
+ printer. stderr( ) ,
808
+ "{}" ,
809
+ format!(
810
+ "Would uninstall {}" ,
811
+ format!( "{} package{}" , reinstalls. len( ) , s) . bold( ) ,
812
+ )
813
+ . dimmed( )
814
+ ) ?;
815
+ }
816
+
817
+ // Install the resolved distributions.
818
+ let installs = wheels. len ( ) + local. len ( ) ;
819
+
820
+ if installs > 0 {
821
+ let s = if installs == 1 { "" } else { "s" } ;
822
+ writeln ! (
823
+ printer. stderr( ) ,
824
+ "{}" ,
825
+ format!( "Would install {}" , format!( "{installs} package{s}" ) . bold( ) ) . dimmed( )
826
+ ) ?;
827
+ }
828
+
829
+ for event in reinstalls
830
+ . into_iter ( )
831
+ . map ( |distribution| DryRunEvent {
832
+ name : distribution. name ( ) . clone ( ) ,
833
+ version : distribution. installed_version ( ) . to_string ( ) ,
834
+ kind : ChangeEventKind :: Removed ,
835
+ } )
836
+ . chain ( wheels. into_iter ( ) . map ( |distribution| DryRunEvent {
837
+ name : distribution. name ( ) . clone ( ) ,
838
+ version : distribution. version_or_url ( ) . to_string ( ) ,
839
+ kind : ChangeEventKind :: Added ,
840
+ } ) )
841
+ . chain ( local. into_iter ( ) . map ( |distribution| DryRunEvent {
842
+ name : distribution. name ( ) . clone ( ) ,
843
+ version : distribution. installed_version ( ) . to_string ( ) ,
844
+ kind : ChangeEventKind :: Added ,
845
+ } ) )
846
+ . sorted_unstable_by ( |a, b| a. name . cmp ( & b. name ) . then_with ( || a. kind . cmp ( & b. kind ) ) )
847
+ {
848
+ match event. kind {
849
+ ChangeEventKind :: Added => {
850
+ writeln ! (
851
+ printer. stderr( ) ,
852
+ " {} {}{}" ,
853
+ "+" . green( ) ,
854
+ event. name. as_ref( ) . bold( ) ,
855
+ event. version. dimmed( )
856
+ ) ?;
857
+ }
858
+ ChangeEventKind :: Removed => {
859
+ writeln ! (
860
+ printer. stderr( ) ,
861
+ " {} {}{}" ,
862
+ "-" . red( ) ,
863
+ event. name. as_ref( ) . bold( ) ,
864
+ event. version. dimmed( )
865
+ ) ?;
866
+ }
867
+ }
868
+ }
869
+
870
+ Ok ( ( ) )
871
+ }
872
+
731
873
// TODO(konstin): Also check the cache whether any cached or installed dist is already known to
732
874
// have been yanked, we currently don't show this message on the second run anymore
733
875
for dist in & remote {
0 commit comments