@@ -11,13 +11,13 @@ use std::fs::FileType;
1111use std:: io:: { BufReader , Cursor , Read , Write } ;
1212use std:: path:: { Path , PathBuf , StripPrefixError } ;
1313use std:: { io, mem} ;
14- use tar:: { EntryType , Header } ;
14+ use tar:: { Builder , EntryType , Header } ;
1515use thiserror:: Error ;
1616use tracing:: { debug, trace} ;
1717use uv_distribution_filename:: { SourceDistExtension , SourceDistFilename , WheelFilename } ;
1818use uv_fs:: Simplified ;
1919use uv_globfilter:: { parse_portable_glob, GlobDirFilter , PortableGlobError } ;
20- use walkdir:: WalkDir ;
20+ use walkdir:: { DirEntry , WalkDir } ;
2121use zip:: { CompressionMethod , ZipWriter } ;
2222
2323#[ derive( Debug , Error ) ]
@@ -66,6 +66,8 @@ pub enum Error {
6666 Csv ( #[ from] csv:: Error ) ,
6767 #[ error( "Expected a Python module with an `__init__.py` at: `{}`" , _0. user_display( ) ) ]
6868 MissingModule ( PathBuf ) ,
69+ #[ error( "Absolute module root is not allowed: `{}`" , _0. display( ) ) ]
70+ AbsoluteModuleRoot ( PathBuf ) ,
6971 #[ error( "Inconsistent metadata between prepare and build step: `{0}`" ) ]
7072 InconsistentSteps ( & ' static str ) ,
7173 #[ error( "Failed to write to {}" , _0. user_display( ) ) ]
@@ -292,11 +294,29 @@ fn write_hashed(
292294 } )
293295}
294296
297+ /// TODO(konsti): Wire this up with actual settings and remove this struct.
298+ ///
299+ /// Which files to include in the wheel
300+ pub struct WheelSettings {
301+ /// The directory that contains the module directory, usually `src`, or an empty path when
302+ /// using the flat layout over the src layout.
303+ module_root : PathBuf ,
304+ }
305+
306+ impl Default for WheelSettings {
307+ fn default ( ) -> Self {
308+ Self {
309+ module_root : PathBuf :: from ( "src" ) ,
310+ }
311+ }
312+ }
313+
295314/// Build a wheel from the source tree and place it in the output directory.
296315pub fn build_wheel (
297316 source_tree : & Path ,
298317 wheel_dir : & Path ,
299318 metadata_directory : Option < & Path > ,
319+ wheel_settings : WheelSettings ,
300320 uv_version : & str ,
301321) -> Result < WheelFilename , Error > {
302322 let contents = fs_err:: read_to_string ( source_tree. join ( "pyproject.toml" ) ) ?;
@@ -319,7 +339,10 @@ pub fn build_wheel(
319339 let mut wheel_writer = ZipDirectoryWriter :: new_wheel ( File :: create ( & wheel_path) ?) ;
320340
321341 debug ! ( "Adding content files to {}" , wheel_path. user_display( ) ) ;
322- let strip_root = source_tree. join ( "src" ) ;
342+ if wheel_settings. module_root . is_absolute ( ) {
343+ return Err ( Error :: AbsoluteModuleRoot ( wheel_settings. module_root ) ) ;
344+ }
345+ let strip_root = source_tree. join ( wheel_settings. module_root ) ;
323346 let module_root = strip_root. join ( pyproject_toml. name ( ) . as_dist_info_name ( ) . as_ref ( ) ) ;
324347 if !module_root. join ( "__init__.py" ) . is_file ( ) {
325348 return Err ( Error :: MissingModule ( module_root) ) ;
@@ -337,6 +360,9 @@ pub fn build_wheel(
337360 let relative_path_str = relative_path
338361 . to_str ( )
339362 . ok_or_else ( || Error :: NotUtf8Path ( relative_path. to_path_buf ( ) ) ) ?;
363+
364+ debug ! ( "Adding to wheel: `{relative_path_str}`" ) ;
365+
340366 if entry. file_type ( ) . is_dir ( ) {
341367 wheel_writer. write_directory ( relative_path_str) ?;
342368 } else if entry. file_type ( ) . is_file ( ) {
@@ -514,44 +540,7 @@ pub fn build_source_dist(
514540 continue ;
515541 } ;
516542
517- debug ! ( "Including {}" , relative. user_display( ) ) ;
518-
519- let metadata = fs_err:: metadata ( entry. path ( ) ) ?;
520- let mut header = Header :: new_gnu ( ) ;
521- #[ cfg( unix) ]
522- {
523- header. set_mode ( std:: os:: unix:: fs:: MetadataExt :: mode ( & metadata) ) ;
524- }
525- #[ cfg( not( unix) ) ]
526- {
527- header. set_mode ( 0o644 ) ;
528- }
529-
530- if entry. file_type ( ) . is_dir ( ) {
531- header. set_entry_type ( EntryType :: Directory ) ;
532- header
533- . set_path ( Path :: new ( & top_level) . join ( relative) )
534- . map_err ( |err| Error :: TarWrite ( source_dist_path. clone ( ) , err) ) ?;
535- header. set_size ( 0 ) ;
536- header. set_cksum ( ) ;
537- tar. append ( & header, io:: empty ( ) )
538- . map_err ( |err| Error :: TarWrite ( source_dist_path. clone ( ) , err) ) ?;
539- continue ;
540- } else if entry. file_type ( ) . is_file ( ) {
541- header. set_size ( metadata. len ( ) ) ;
542- header. set_cksum ( ) ;
543- tar. append_data (
544- & mut header,
545- Path :: new ( & top_level) . join ( relative) ,
546- BufReader :: new ( File :: open ( entry. path ( ) ) ?) ,
547- )
548- . map_err ( |err| Error :: TarWrite ( source_dist_path. clone ( ) , err) ) ?;
549- } else {
550- return Err ( Error :: UnsupportedFileType (
551- relative. clone ( ) ,
552- entry. file_type ( ) ,
553- ) ) ;
554- }
543+ add_source_dist_entry ( & mut tar, & entry, & top_level, & source_dist_path, & relative) ?;
555544 }
556545
557546 tar. finish ( )
@@ -560,6 +549,55 @@ pub fn build_source_dist(
560549 Ok ( filename)
561550}
562551
552+ /// Add a file or a directory to a source distribution.
553+ fn add_source_dist_entry (
554+ tar : & mut Builder < GzEncoder < File > > ,
555+ entry : & DirEntry ,
556+ top_level : & str ,
557+ source_dist_path : & Path ,
558+ relative : & Path ,
559+ ) -> Result < ( ) , Error > {
560+ debug ! ( "Including {}" , relative. user_display( ) ) ;
561+
562+ let metadata = fs_err:: metadata ( entry. path ( ) ) ?;
563+ let mut header = Header :: new_gnu ( ) ;
564+ #[ cfg( unix) ]
565+ {
566+ header. set_mode ( std:: os:: unix:: fs:: MetadataExt :: mode ( & metadata) ) ;
567+ }
568+ #[ cfg( not( unix) ) ]
569+ {
570+ header. set_mode ( 0o644 ) ;
571+ }
572+
573+ if entry. file_type ( ) . is_dir ( ) {
574+ header. set_entry_type ( EntryType :: Directory ) ;
575+ header
576+ . set_path ( Path :: new ( & top_level) . join ( relative) )
577+ . map_err ( |err| Error :: TarWrite ( source_dist_path. to_path_buf ( ) , err) ) ?;
578+ header. set_size ( 0 ) ;
579+ header. set_cksum ( ) ;
580+ tar. append ( & header, io:: empty ( ) )
581+ . map_err ( |err| Error :: TarWrite ( source_dist_path. to_path_buf ( ) , err) ) ?;
582+ Ok ( ( ) )
583+ } else if entry. file_type ( ) . is_file ( ) {
584+ header. set_size ( metadata. len ( ) ) ;
585+ header. set_cksum ( ) ;
586+ tar. append_data (
587+ & mut header,
588+ Path :: new ( & top_level) . join ( relative) ,
589+ BufReader :: new ( File :: open ( entry. path ( ) ) ?) ,
590+ )
591+ . map_err ( |err| Error :: TarWrite ( source_dist_path. to_path_buf ( ) , err) ) ?;
592+ Ok ( ( ) )
593+ } else {
594+ Err ( Error :: UnsupportedFileType (
595+ relative. to_path_buf ( ) ,
596+ entry. file_type ( ) ,
597+ ) )
598+ }
599+ }
600+
563601/// Write the dist-info directory to the output directory without building the wheel.
564602pub fn metadata (
565603 source_tree : & Path ,
0 commit comments