@@ -779,6 +779,9 @@ pub(crate) enum RunCommand {
779
779
PythonGuiScript ( PathBuf , Vec < OsString > ) ,
780
780
/// Execute a Python package containing a `__main__.py` file.
781
781
PythonPackage ( PathBuf , Vec < OsString > ) ,
782
+ /// Execute a Python [zipapp].
783
+ /// [zipapp]: https://docs.python.org/3/library/zipapp.html
784
+ PythonZipapp ( PathBuf , Vec < OsString > ) ,
782
785
/// Execute a `python` script provided via `stdin`.
783
786
PythonStdin ( Vec < u8 > ) ,
784
787
/// Execute an external command.
@@ -792,10 +795,11 @@ impl RunCommand {
792
795
fn display_executable ( & self ) -> Cow < ' _ , str > {
793
796
match self {
794
797
Self :: Python ( _) => Cow :: Borrowed ( "python" ) ,
795
- Self :: PythonScript ( _, _) | Self :: PythonPackage ( _, _) | Self :: Empty => {
796
- Cow :: Borrowed ( "python" )
797
- }
798
- Self :: PythonGuiScript ( _, _) => Cow :: Borrowed ( "pythonw" ) ,
798
+ Self :: PythonScript ( ..)
799
+ | Self :: PythonPackage ( ..)
800
+ | Self :: PythonZipapp ( ..)
801
+ | Self :: Empty => Cow :: Borrowed ( "python" ) ,
802
+ Self :: PythonGuiScript ( ..) => Cow :: Borrowed ( "pythonw" ) ,
799
803
Self :: PythonStdin ( _) => Cow :: Borrowed ( "python -c" ) ,
800
804
Self :: External ( executable, _) => executable. to_string_lossy ( ) ,
801
805
}
@@ -809,7 +813,9 @@ impl RunCommand {
809
813
process. args ( args) ;
810
814
process
811
815
}
812
- Self :: PythonScript ( target, args) | Self :: PythonPackage ( target, args) => {
816
+ Self :: PythonScript ( target, args)
817
+ | Self :: PythonPackage ( target, args)
818
+ | Self :: PythonZipapp ( target, args) => {
813
819
let mut process = Command :: new ( interpreter. sys_executable ( ) ) ;
814
820
process. arg ( target) ;
815
821
process. args ( args) ;
@@ -872,7 +878,9 @@ impl std::fmt::Display for RunCommand {
872
878
}
873
879
Ok ( ( ) )
874
880
}
875
- Self :: PythonScript ( target, args) | Self :: PythonPackage ( target, args) => {
881
+ Self :: PythonScript ( target, args)
882
+ | Self :: PythonPackage ( target, args)
883
+ | Self :: PythonZipapp ( target, args) => {
876
884
write ! ( f, "python {}" , target. display( ) ) ?;
877
885
for arg in args {
878
886
write ! ( f, " {}" , arg. to_string_lossy( ) ) ?;
@@ -916,6 +924,14 @@ impl TryFrom<&ExternalCommand> for RunCommand {
916
924
} ;
917
925
918
926
let target_path = PathBuf :: from ( & target) ;
927
+ let metadata = target_path. metadata ( ) ;
928
+ let is_file = metadata
929
+ . as_ref ( )
930
+ . map_or ( false , |metadata| metadata. is_file ( ) ) ;
931
+ let is_dir = metadata
932
+ . as_ref ( )
933
+ . map_or ( false , |metadata| metadata. is_dir ( ) ) ;
934
+
919
935
if target. eq_ignore_ascii_case ( "-" ) {
920
936
let mut buf = Vec :: with_capacity ( 1024 ) ;
921
937
std:: io:: stdin ( ) . read_to_end ( & mut buf) ?;
@@ -925,18 +941,20 @@ impl TryFrom<&ExternalCommand> for RunCommand {
925
941
} else if target_path
926
942
. extension ( )
927
943
. is_some_and ( |ext| ext. eq_ignore_ascii_case ( "py" ) || ext. eq_ignore_ascii_case ( "pyc" ) )
928
- && target_path . exists ( )
944
+ && is_file
929
945
{
930
946
Ok ( Self :: PythonScript ( target_path, args. to_vec ( ) ) )
931
947
} else if cfg ! ( windows)
932
948
&& target_path
933
949
. extension ( )
934
950
. is_some_and ( |ext| ext. eq_ignore_ascii_case ( "pyw" ) )
935
- && target_path . exists ( )
951
+ && is_file
936
952
{
937
953
Ok ( Self :: PythonGuiScript ( target_path, args. to_vec ( ) ) )
938
- } else if target_path . is_dir ( ) && target_path. join ( "__main__.py" ) . exists ( ) {
954
+ } else if is_dir && target_path. join ( "__main__.py" ) . is_file ( ) {
939
955
Ok ( Self :: PythonPackage ( target_path, args. to_vec ( ) ) )
956
+ } else if is_file && is_python_zipapp ( & target_path) {
957
+ Ok ( Self :: PythonZipapp ( target_path, args. to_vec ( ) ) )
940
958
} else {
941
959
Ok ( Self :: External (
942
960
target. clone ( ) ,
@@ -945,3 +963,15 @@ impl TryFrom<&ExternalCommand> for RunCommand {
945
963
}
946
964
}
947
965
}
966
+
967
+ /// Returns `true` if the target is a ZIP archive containing a `__main__.py` file.
968
+ fn is_python_zipapp ( target : & Path ) -> bool {
969
+ if let Ok ( file) = std:: fs:: File :: open ( target) {
970
+ if let Ok ( mut archive) = zip:: ZipArchive :: new ( file) {
971
+ return archive
972
+ . by_name ( "__main__.py" )
973
+ . map_or ( false , |f| f. is_file ( ) ) ;
974
+ }
975
+ }
976
+ false
977
+ }
0 commit comments