@@ -7,6 +7,7 @@ use std::str::FromStr;
7
7
use configparser:: ini:: Ini ;
8
8
use fs_err as fs;
9
9
use fs_err:: { DirEntry , File } ;
10
+ use reflink_copy as reflink;
10
11
use tempfile:: tempdir_in;
11
12
use tracing:: { debug, instrument} ;
12
13
@@ -280,55 +281,118 @@ fn clone_wheel_files(
280
281
wheel : impl AsRef < Path > ,
281
282
) -> Result < usize , Error > {
282
283
let mut count = 0usize ;
284
+ let mut attempt = Attempt :: default ( ) ;
283
285
284
286
// On macOS, directly can be recursively copied with a single `clonefile` call.
285
287
// So we only need to iterate over the top-level of the directory, and copy each file or
286
288
// subdirectory unless the subdirectory exists already in which case we'll need to recursively
287
289
// merge its contents with the existing directory.
288
290
for entry in fs:: read_dir ( wheel. as_ref ( ) ) ? {
289
- clone_recursive ( site_packages. as_ref ( ) , wheel. as_ref ( ) , & entry?) ?;
291
+ clone_recursive (
292
+ site_packages. as_ref ( ) ,
293
+ wheel. as_ref ( ) ,
294
+ & entry?,
295
+ & mut attempt,
296
+ ) ?;
290
297
count += 1 ;
291
298
}
292
299
293
300
Ok ( count)
294
301
}
295
302
303
+ // Hard linking / reflinking might not be supported but we (afaik) can't detect this ahead of time,
304
+ // so we'll try hard linking / reflinking the first file - if this succeeds we'll know later
305
+ // errors are not due to lack of os/fs support. If it fails, we'll switch to copying for the rest of the
306
+ // install.
307
+ #[ derive( Debug , Default , Clone , Copy , PartialEq , Eq ) ]
308
+ enum Attempt {
309
+ #[ default]
310
+ Initial ,
311
+ Subsequent ,
312
+ UseCopyFallback ,
313
+ }
314
+
296
315
/// Recursively clone the contents of `from` into `to`.
297
- fn clone_recursive ( site_packages : & Path , wheel : & Path , entry : & DirEntry ) -> Result < ( ) , Error > {
316
+ fn clone_recursive (
317
+ site_packages : & Path ,
318
+ wheel : & Path ,
319
+ entry : & DirEntry ,
320
+ attempt : & mut Attempt ,
321
+ ) -> Result < ( ) , Error > {
298
322
// Determine the existing and destination paths.
299
323
let from = entry. path ( ) ;
300
324
let to = site_packages. join ( from. strip_prefix ( wheel) . unwrap ( ) ) ;
301
325
302
326
debug ! ( "Cloning {} to {}" , from. display( ) , to. display( ) ) ;
303
327
304
- // Attempt to copy the file or directory
305
- let reflink = reflink_copy:: reflink ( & from, & to) ;
306
-
307
- if reflink
308
- . as_ref ( )
309
- . is_err_and ( |err| matches ! ( err. kind( ) , std:: io:: ErrorKind :: AlreadyExists ) )
310
- {
311
- // If copying fails and the directory exists already, it must be merged recursively.
312
- if entry. file_type ( ) ?. is_dir ( ) {
313
- for entry in fs:: read_dir ( from) ? {
314
- clone_recursive ( site_packages, wheel, & entry?) ?;
328
+ match attempt {
329
+ Attempt :: Initial => {
330
+ if let Err ( err) = reflink:: reflink ( & from, & to) {
331
+ if matches ! ( err. kind( ) , std:: io:: ErrorKind :: AlreadyExists ) {
332
+ // If cloning/copying fails and the directory exists already, it must be merged recursively.
333
+ if entry. file_type ( ) ?. is_dir ( ) {
334
+ for entry in fs:: read_dir ( from) ? {
335
+ clone_recursive ( site_packages, wheel, & entry?, attempt) ?;
336
+ }
337
+ } else {
338
+ // If file already exists, overwrite it.
339
+ let tempdir = tempdir_in ( site_packages) ?;
340
+ let tempfile = tempdir. path ( ) . join ( from. file_name ( ) . unwrap ( ) ) ;
341
+ if reflink:: reflink ( & from, & tempfile) . is_ok ( ) {
342
+ fs:: rename ( & tempfile, to) ?;
343
+ } else {
344
+ debug ! ( "Failed to clone {} to temporary location {} - attempting to copy files as a fallback" , from. display( ) , tempfile. display( ) ) ;
345
+ * attempt = Attempt :: UseCopyFallback ;
346
+ fs:: copy ( & from, & to) ?;
347
+ }
348
+ }
349
+ } else {
350
+ debug ! (
351
+ "Failed to clone {} to {} - attempting to copy files as a fallback" ,
352
+ from. display( ) ,
353
+ to. display( )
354
+ ) ;
355
+ // switch to copy fallback
356
+ * attempt = Attempt :: UseCopyFallback ;
357
+ clone_recursive ( site_packages, wheel, entry, attempt) ?;
358
+ }
359
+ }
360
+ }
361
+ Attempt :: Subsequent => {
362
+ if let Err ( err) = reflink:: reflink ( & from, & to) {
363
+ if matches ! ( err. kind( ) , std:: io:: ErrorKind :: AlreadyExists ) {
364
+ // If cloning/copying fails and the directory exists already, it must be merged recursively.
365
+ if entry. file_type ( ) ?. is_dir ( ) {
366
+ for entry in fs:: read_dir ( from) ? {
367
+ clone_recursive ( site_packages, wheel, & entry?, attempt) ?;
368
+ }
369
+ } else {
370
+ // If file already exists, overwrite it.
371
+ let tempdir = tempdir_in ( site_packages) ?;
372
+ let tempfile = tempdir. path ( ) . join ( from. file_name ( ) . unwrap ( ) ) ;
373
+ reflink:: reflink ( & from, & tempfile) ?;
374
+ fs:: rename ( & tempfile, to) ?;
375
+ }
376
+ } else {
377
+ return Err ( Error :: Reflink { from, to, err } ) ;
378
+ }
379
+ }
380
+ }
381
+ Attempt :: UseCopyFallback => {
382
+ if entry. file_type ( ) ?. is_dir ( ) {
383
+ fs:: create_dir_all ( & to) ?;
384
+ for entry in fs:: read_dir ( from) ? {
385
+ clone_recursive ( site_packages, wheel, & entry?, attempt) ?;
386
+ }
387
+ } else {
388
+ fs:: copy ( & from, & to) ?;
315
389
}
316
- } else {
317
- // If file already exists, overwrite it.
318
- let tempdir = tempdir_in ( site_packages) ?;
319
- let tempfile = tempdir. path ( ) . join ( from. file_name ( ) . unwrap ( ) ) ;
320
- reflink_copy:: reflink ( from, & tempfile) ?;
321
- fs:: rename ( & tempfile, to) ?;
322
390
}
323
- } else {
324
- // Other errors should be tracked
325
- reflink. map_err ( |err| Error :: Reflink {
326
- from : from. clone ( ) ,
327
- to : to. clone ( ) ,
328
- err,
329
- } ) ?;
330
391
}
331
392
393
+ if * attempt == Attempt :: Initial {
394
+ * attempt = Attempt :: Subsequent ;
395
+ }
332
396
Ok ( ( ) )
333
397
}
334
398
@@ -366,18 +430,6 @@ fn hardlink_wheel_files(
366
430
site_packages : impl AsRef < Path > ,
367
431
wheel : impl AsRef < Path > ,
368
432
) -> Result < usize , Error > {
369
- // Hard linking might not be supported but we (afaik) can't detect this ahead of time, so we'll
370
- // try hard linking the first file, if this succeeds we'll know later hard linking errors are
371
- // not due to lack of os/fs support, if it fails we'll switch to copying for the rest of the
372
- // install
373
- #[ derive( Debug , Default , Clone , Copy ) ]
374
- enum Attempt {
375
- #[ default]
376
- Initial ,
377
- Subsequent ,
378
- UseCopyFallback ,
379
- }
380
-
381
433
let mut attempt = Attempt :: default ( ) ;
382
434
let mut count = 0usize ;
383
435
0 commit comments