Skip to content

Commit 6c6e0f6

Browse files
MusicalNinjaDadhenryiiipre-commit-ci[bot]
authored
fix: handle case where output_dir does not already exist on macos & windows (#1851)
* replace `with suppress(FileNotFoundError)` by `.unlink(missing_ok=True)` for macos * also use `.unlink(missing_ok=True)` in pyodide and windows for consistency * remove contextlib imports which are no longer required * Apply suggestions from code review * explicity resolve and create output location * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * use explicit str for move * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update comments based on review feedback * Apply suggestions from code review * Break out functionality to move files to util.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * raise instance of IsADirectoryError with meaningful message * Don't need a comment and a exception message --------- Co-authored-by: Henry Schreiner <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 877d3bf commit 6c6e0f6

File tree

4 files changed

+59
-17
lines changed

4 files changed

+59
-17
lines changed

cibuildwheel/macos.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import contextlib
43
import functools
54
import os
65
import platform
@@ -37,6 +36,7 @@
3736
get_build_verbosity_extra_flags,
3837
get_pip_version,
3938
install_certifi_script,
39+
move_file,
4040
prepare_command,
4141
read_python_configs,
4242
shell,
@@ -641,11 +641,13 @@ def build(options: Options, tmp_path: Path) -> None:
641641

642642
# we're all done here; move it to output (overwrite existing)
643643
if compatible_wheel is None:
644-
with contextlib.suppress(FileNotFoundError):
645-
(build_options.output_dir / repaired_wheel.name).unlink()
646-
647-
shutil.move(str(repaired_wheel), build_options.output_dir)
648-
built_wheels.append(build_options.output_dir / repaired_wheel.name)
644+
output_wheel = build_options.output_dir.joinpath(repaired_wheel.name)
645+
moved_wheel = move_file(repaired_wheel, output_wheel)
646+
if moved_wheel != output_wheel.resolve():
647+
log.warning(
648+
"{repaired_wheel} was moved to {moved_wheel} instead of {output_wheel}"
649+
)
650+
built_wheels.append(output_wheel)
649651

650652
# clean up
651653
shutil.rmtree(identifier_tmp_dir)

cibuildwheel/pyodide.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import contextlib
43
import os
54
import shutil
65
import sys
@@ -27,6 +26,7 @@
2726
extract_zip,
2827
find_compatible_wheel,
2928
get_pip_version,
29+
move_file,
3030
prepare_command,
3131
read_python_configs,
3232
shell,
@@ -395,10 +395,13 @@ def build(options: Options, tmp_path: Path) -> None:
395395

396396
# we're all done here; move it to output (overwrite existing)
397397
if compatible_wheel is None:
398-
with contextlib.suppress(FileNotFoundError):
399-
(build_options.output_dir / repaired_wheel.name).unlink()
398+
output_wheel = build_options.output_dir.joinpath(repaired_wheel.name)
399+
moved_wheel = move_file(repaired_wheel, output_wheel)
400+
if moved_wheel != output_wheel.resolve():
401+
log.warning(
402+
"{repaired_wheel} was moved to {moved_wheel} instead of {output_wheel}"
403+
)
404+
built_wheels.append(output_wheel)
400405

401-
shutil.move(str(repaired_wheel), build_options.output_dir)
402-
built_wheels.append(build_options.output_dir / repaired_wheel.name)
403406
finally:
404407
pass

cibuildwheel/util.py

+35
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import os
77
import re
88
import shlex
9+
import shutil
910
import ssl
1011
import subprocess
1112
import sys
@@ -359,6 +360,40 @@ def extract_tar(tar_src: Path, dest: Path) -> None:
359360
tar_.extractall(dest)
360361

361362

363+
def move_file(src_file: Path, dst_file: Path) -> Path:
364+
"""Moves a file safely while avoiding potential semantic confusion:
365+
366+
1. `dst_file` must point to the target filename, not a directory
367+
2. `dst_file` will be overwritten if it already exists
368+
3. any missing parent directories will be created
369+
370+
Returns the fully resolved Path of the resulting file.
371+
372+
Raises:
373+
NotADirectoryError: If any part of the intermediate path to `dst_file` is an existing file
374+
IsADirectoryError: If `dst_file` points directly to an existing directory
375+
"""
376+
377+
# Importing here as logger needs various functions from util -> circular imports
378+
from .logger import log
379+
380+
src_file = src_file.resolve()
381+
dst_file = dst_file.resolve()
382+
383+
if dst_file.is_dir():
384+
msg = "dst_file must be a valid target filename, not an existing directory."
385+
raise IsADirectoryError(msg)
386+
dst_file.unlink(missing_ok=True)
387+
dst_file.parent.mkdir(parents=True, exist_ok=True)
388+
389+
# using shutil.move() as Path.rename() is not guaranteed to work across filesystem boundaries
390+
# explicit str() needed for Python 3.8
391+
resulting_file = shutil.move(str(src_file), str(dst_file))
392+
resulting_file = Path(resulting_file).resolve()
393+
log.notice(f"Moved {src_file} to {resulting_file}")
394+
return Path(resulting_file)
395+
396+
362397
class DependencyConstraints:
363398
def __init__(self, base_file_path: Path):
364399
assert base_file_path.exists()

cibuildwheel/windows.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import sys
88
import textwrap
99
from collections.abc import MutableMapping, Sequence, Set
10-
from contextlib import suppress
1110
from dataclasses import dataclass
1211
from functools import lru_cache
1312
from pathlib import Path
@@ -34,6 +33,7 @@
3433
find_compatible_wheel,
3534
get_build_verbosity_extra_flags,
3635
get_pip_version,
36+
move_file,
3737
prepare_command,
3838
read_python_configs,
3939
shell,
@@ -543,11 +543,13 @@ def build(options: Options, tmp_path: Path) -> None:
543543

544544
# we're all done here; move it to output (remove if already exists)
545545
if compatible_wheel is None:
546-
with suppress(FileNotFoundError):
547-
(build_options.output_dir / repaired_wheel.name).unlink()
548-
549-
shutil.move(str(repaired_wheel), build_options.output_dir)
550-
built_wheels.append(build_options.output_dir / repaired_wheel.name)
546+
output_wheel = build_options.output_dir.joinpath(repaired_wheel.name)
547+
moved_wheel = move_file(repaired_wheel, output_wheel)
548+
if moved_wheel != output_wheel.resolve():
549+
log.warning(
550+
"{repaired_wheel} was moved to {moved_wheel} instead of {output_wheel}"
551+
)
552+
built_wheels.append(output_wheel)
551553

552554
# clean up
553555
# (we ignore errors because occasionally Windows fails to unlink a file and we

0 commit comments

Comments
 (0)