From d9975c6a31b88bd6ae8963e9c981525cc910f82f Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Mon, 7 Oct 2024 14:12:38 +0100 Subject: [PATCH] Extract convenience wrapper of rmtree to setuptools._shutil for reuse --- setuptools/_shutil.py | 41 ++++++++++++++++++++++++++++++ setuptools/command/easy_install.py | 39 +++------------------------- 2 files changed, 44 insertions(+), 36 deletions(-) create mode 100644 setuptools/_shutil.py diff --git a/setuptools/_shutil.py b/setuptools/_shutil.py new file mode 100644 index 0000000000..8abf5faa6b --- /dev/null +++ b/setuptools/_shutil.py @@ -0,0 +1,41 @@ +"""Convenience layer on top of stdlib's shutil and os""" + +import os +import stat +from typing import Callable, TypeVar + +from .compat import py311 + +from distutils import log + +try: + from os import chmod +except ImportError: + # Jython compatibility + def chmod(*args: object, **kwargs: object) -> None: # type: ignore[misc] # Mypy reuses the imported definition anyway + pass + + +_T = TypeVar("_T") + + +def attempt_chmod_verbose(path, mode): + log.debug("changing mode of %s to %o", path, mode) + try: + chmod(path, mode) + except OSError as e: + log.debug("chmod failed: %s", e) + + +# Must match shutil._OnExcCallback +def _auto_chmod(func: Callable[..., _T], arg: str, exc: BaseException) -> _T: + """shutils onexc callback to automatically call chmod for certain functions.""" + # Only retry for scenarios known to have an issue + if func in [os.unlink, os.remove] and os.name == 'nt': + attempt_chmod_verbose(arg, stat.S_IWRITE) + return func(arg) + raise exc + + +def rmtree(path, ignore_errors=False, onexc=_auto_chmod): + return py311.shutil_rmtree(path, ignore_errors, onexc) diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 21e6f008d7..b40610f8ba 100644 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -34,7 +34,7 @@ from collections.abc import Iterable from glob import glob from sysconfig import get_path -from typing import TYPE_CHECKING, Callable, NoReturn, TypedDict, TypeVar +from typing import TYPE_CHECKING, NoReturn, TypedDict from jaraco.text import yield_lines @@ -63,7 +63,8 @@ from setuptools.wheel import Wheel from .._path import ensure_directory -from ..compat import py39, py311, py312 +from .._shutil import attempt_chmod_verbose as chmod, rmtree as _rmtree +from ..compat import py39, py312 from distutils import dir_util, log from distutils.command import install @@ -89,8 +90,6 @@ 'get_exe_prefixes', ] -_T = TypeVar("_T") - def is_64bit(): return struct.calcsize("P") == 8 @@ -1789,16 +1788,6 @@ def _first_line_re(): return re.compile(first_line_re.pattern.decode()) -# Must match shutil._OnExcCallback -def auto_chmod(func: Callable[..., _T], arg: str, exc: BaseException) -> _T: - """shutils onexc callback to automatically call chmod for certain functions.""" - # Only retry for scenarios known to have an issue - if func in [os.unlink, os.remove] and os.name == 'nt': - chmod(arg, stat.S_IWRITE) - return func(arg) - raise exc - - def update_dist_caches(dist_path, fix_zipimporter_caches): """ Fix any globally cached `dist_path` related data @@ -2021,24 +2010,6 @@ def is_python_script(script_text, filename): return False # Not any Python I can recognize -try: - from os import ( - chmod as _chmod, # pyright: ignore[reportAssignmentType] # Losing type-safety w/ pyright, but that's ok - ) -except ImportError: - # Jython compatibility - def _chmod(*args: object, **kwargs: object) -> None: # type: ignore[misc] # Mypy reuses the imported definition anyway - pass - - -def chmod(path, mode): - log.debug("changing mode of %s to %o", path, mode) - try: - _chmod(path, mode) - except OSError as e: - log.debug("chmod failed: %s", e) - - class _SplitArgs(TypedDict, total=False): comments: bool posix: bool @@ -2350,10 +2321,6 @@ def load_launcher_manifest(name): return manifest.decode('utf-8') % vars() -def _rmtree(path, ignore_errors: bool = False, onexc=auto_chmod): - return py311.shutil_rmtree(path, ignore_errors, onexc) - - def current_umask(): tmp = os.umask(0o022) os.umask(tmp)