@@ -6,7 +6,6 @@ use std::sync::Arc;
66use std:: task:: { Context , Poll } ;
77
88use futures:: { FutureExt , TryStreamExt } ;
9- use tempfile:: TempDir ;
109use tokio:: io:: { AsyncRead , AsyncSeekExt , ReadBuf } ;
1110use tokio:: sync:: Semaphore ;
1211use tokio_util:: compat:: FuturesAsyncReadCompatExt ;
@@ -20,11 +19,12 @@ use uv_client::{
2019} ;
2120use uv_distribution_filename:: { SourceDistExtension , WheelFilename } ;
2221use uv_distribution_types:: {
23- BuildInfo , BuildableSource , BuiltDist , Dist , File , HashPolicy , Hashed , IndexUrl , InstalledDist ,
24- Name , SourceDist , ToUrlError ,
22+ BuildInfo , BuildableSource , BuiltDist , Dist , DistRef , File , HashPolicy , Hashed , IndexUrl ,
23+ InstalledDist , Name , SourceDist , ToUrlError ,
2524} ;
2625use uv_extract:: hash:: Hasher ;
2726use uv_fs:: write_atomic;
27+ use uv_install_wheel:: validate_and_heal_record;
2828use uv_platform_tags:: Tags ;
2929use uv_pypi_types:: { HashDigest , HashDigests , PyProjectToml } ;
3030use uv_redacted:: DisplaySafeUrl ;
@@ -491,7 +491,11 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
491491
492492 // Otherwise, unzip the wheel.
493493 let id = self
494- . unzip_wheel ( & built_wheel. path , & built_wheel. target )
494+ . unzip_wheel (
495+ & built_wheel. path ,
496+ & built_wheel. target ,
497+ DistRef :: Source ( dist) ,
498+ )
495499 . await ?;
496500
497501 Ok ( LocalWheel {
@@ -683,41 +687,45 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
683687 let temp_dir = tempfile:: tempdir_in ( self . build_context . cache ( ) . root ( ) )
684688 . map_err ( Error :: CacheWrite ) ?;
685689
686- match progress {
690+ let files = match progress {
687691 Some ( ( reporter, progress) ) => {
688692 let mut reader = ProgressReader :: new ( & mut hasher, progress, & * * reporter) ;
689693 match extension {
690694 WheelExtension :: Whl => {
691695 uv_extract:: stream:: unzip ( query_url, & mut reader, temp_dir. path ( ) )
692696 . await
693- . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?;
697+ . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?
694698 }
695699 WheelExtension :: WhlZst => {
696700 uv_extract:: stream:: untar_zst ( & mut reader, temp_dir. path ( ) )
697701 . await
698- . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?;
702+ . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?
699703 }
700704 }
701705 }
702706 None => match extension {
703707 WheelExtension :: Whl => {
704708 uv_extract:: stream:: unzip ( query_url, & mut hasher, temp_dir. path ( ) )
705709 . await
706- . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?;
710+ . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?
707711 }
708712 WheelExtension :: WhlZst => {
709713 uv_extract:: stream:: untar_zst ( & mut hasher, temp_dir. path ( ) )
710714 . await
711- . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?;
715+ . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?
712716 }
713717 } ,
714- }
715-
718+ } ;
716719 // If necessary, exhaust the reader to compute the hash.
717720 if !hashes. is_none ( ) {
718721 hasher. finish ( ) . await . map_err ( Error :: HashExhaustion ) ?;
719722 }
720723
724+ // Before we make the wheel accessible by persisting it, ensure that the RECORD is
725+ // valid.
726+ validate_and_heal_record ( temp_dir. path ( ) , files. iter ( ) , dist)
727+ . map_err ( Error :: InstallWheelError ) ?;
728+
721729 // Persist the temporary directory to the directory store.
722730 let id = self
723731 . build_context
@@ -883,52 +891,49 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
883891 . map_err ( Error :: CacheWrite ) ?;
884892
885893 // If no hashes are required, parallelize the unzip operation.
886- let hashes = if hashes. is_none ( ) {
894+ let ( files, hashes) = if hashes. is_none ( ) {
895+ // Unzip the wheel into a temporary directory.
887896 let file = file. into_std ( ) . await ;
888- tokio:: task:: spawn_blocking ( {
889- let target = temp_dir. path ( ) . to_owned ( ) ;
890- move || -> Result < ( ) , uv_extract:: Error > {
891- // Unzip the wheel into a temporary directory.
892- match extension {
893- WheelExtension :: Whl => {
894- uv_extract:: unzip ( file, & target) ?;
895- }
896- WheelExtension :: WhlZst => {
897- uv_extract:: stream:: untar_zst_file ( file, & target) ?;
898- }
899- }
900- Ok ( ( ) )
901- }
897+ let target = temp_dir. path ( ) . to_owned ( ) ;
898+ let files = tokio:: task:: spawn_blocking ( move || match extension {
899+ WheelExtension :: Whl => uv_extract:: unzip ( file, & target) ,
900+ WheelExtension :: WhlZst => uv_extract:: stream:: untar_zst_file ( file, & target) ,
902901 } )
903902 . await ?
904903 . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?;
905904
906- HashDigests :: empty ( )
905+ ( files , HashDigests :: empty ( ) )
907906 } else {
908907 // Create a hasher for each hash algorithm.
909908 let algorithms = hashes. algorithms ( ) ;
910909 let mut hashers = algorithms. into_iter ( ) . map ( Hasher :: from) . collect :: < Vec < _ > > ( ) ;
911910 let mut hasher = uv_extract:: hash:: HashReader :: new ( file, & mut hashers) ;
912911
913- match extension {
912+ let files = match extension {
914913 WheelExtension :: Whl => {
915914 uv_extract:: stream:: unzip ( query_url, & mut hasher, temp_dir. path ( ) )
916915 . await
917- . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?;
916+ . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?
918917 }
919918 WheelExtension :: WhlZst => {
920919 uv_extract:: stream:: untar_zst ( & mut hasher, temp_dir. path ( ) )
921920 . await
922- . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?;
921+ . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?
923922 }
924- }
923+ } ;
925924
926925 // If necessary, exhaust the reader to compute the hash.
927926 hasher. finish ( ) . await . map_err ( Error :: HashExhaustion ) ?;
927+ let hashes = hashers. into_iter ( ) . map ( HashDigest :: from) . collect ( ) ;
928928
929- hashers . into_iter ( ) . map ( HashDigest :: from ) . collect ( )
929+ ( files , hashes )
930930 } ;
931931
932+ // Before we make the wheel accessible by persisting it, ensure that the RECORD is
933+ // valid.
934+ validate_and_heal_record ( temp_dir. path ( ) , files. iter ( ) , dist)
935+ . map_err ( Error :: InstallWheelError ) ?;
936+
932937 // Persist the temporary directory to the directory store.
933938 let id = self
934939 . build_context
@@ -1062,7 +1067,8 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
10621067 } else if hashes. is_none ( ) {
10631068 // Otherwise, unzip the wheel.
10641069 let archive = Archive :: new (
1065- self . unzip_wheel ( path, wheel_entry. path ( ) ) . await ?,
1070+ self . unzip_wheel ( path, wheel_entry. path ( ) , DistRef :: Built ( dist) )
1071+ . await ?,
10661072 HashDigests :: empty ( ) ,
10671073 filename. clone ( ) ,
10681074 ) ;
@@ -1100,24 +1106,29 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
11001106 let mut hasher = uv_extract:: hash:: HashReader :: new ( file, & mut hashers) ;
11011107
11021108 // Unzip the wheel to a temporary directory.
1103- match extension {
1109+ let files = match extension {
11041110 WheelExtension :: Whl => {
11051111 uv_extract:: stream:: unzip ( path. display ( ) , & mut hasher, temp_dir. path ( ) )
11061112 . await
1107- . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?;
1113+ . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?
11081114 }
11091115 WheelExtension :: WhlZst => {
11101116 uv_extract:: stream:: untar_zst ( & mut hasher, temp_dir. path ( ) )
11111117 . await
1112- . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?;
1118+ . map_err ( |err| Error :: Extract ( filename. to_string ( ) , err) ) ?
11131119 }
1114- }
1120+ } ;
11151121
11161122 // Exhaust the reader to compute the hash.
11171123 hasher. finish ( ) . await . map_err ( Error :: HashExhaustion ) ?;
11181124
11191125 let hashes = hashers. into_iter ( ) . map ( HashDigest :: from) . collect ( ) ;
11201126
1127+ // Before we make the wheel accessible by persisting it, ensure that the RECORD is
1128+ // valid.
1129+ validate_and_heal_record ( temp_dir. path ( ) , files. iter ( ) , dist)
1130+ . map_err ( Error :: InstallWheelError ) ?;
1131+
11211132 // Persist the temporary directory to the directory store.
11221133 let id = self
11231134 . build_context
@@ -1152,21 +1163,30 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
11521163 }
11531164
11541165 /// Unzip a wheel into the cache, returning the path to the unzipped directory.
1155- async fn unzip_wheel ( & self , path : & Path , target : & Path ) -> Result < ArchiveId , Error > {
1156- let temp_dir = tokio:: task:: spawn_blocking ( {
1166+ async fn unzip_wheel (
1167+ & self ,
1168+ path : & Path ,
1169+ target : & Path ,
1170+ dist : DistRef < ' _ > ,
1171+ ) -> Result < ArchiveId , Error > {
1172+ let ( temp_dir, files) = tokio:: task:: spawn_blocking ( {
11571173 let path = path. to_owned ( ) ;
11581174 let root = self . build_context . cache ( ) . root ( ) . to_path_buf ( ) ;
1159- move || -> Result < TempDir , Error > {
1175+ move || -> Result < _ , Error > {
11601176 // Unzip the wheel into a temporary directory.
11611177 let temp_dir = tempfile:: tempdir_in ( root) . map_err ( Error :: CacheWrite ) ?;
11621178 let reader = fs_err:: File :: open ( & path) . map_err ( Error :: CacheWrite ) ?;
1163- uv_extract:: unzip ( reader, temp_dir. path ( ) )
1179+ let files = uv_extract:: unzip ( reader, temp_dir. path ( ) )
11641180 . map_err ( |err| Error :: Extract ( path. to_string_lossy ( ) . into_owned ( ) , err) ) ?;
1165- Ok ( temp_dir)
1181+ Ok ( ( temp_dir, files ) )
11661182 }
11671183 } )
11681184 . await ??;
11691185
1186+ // Before we make the wheel accessible by persisting it, ensure that the RECORD is valid.
1187+ validate_and_heal_record ( temp_dir. path ( ) , files. iter ( ) , dist)
1188+ . map_err ( Error :: InstallWheelError ) ?;
1189+
11701190 // Persist the temporary directory to the directory store.
11711191 let id = self
11721192 . build_context
0 commit comments