import re import sys import string import subprocess import venv from tempfile import TemporaryDirectory from path import Path def remove_all(paths): for path in paths: path.rmtree() if path.isdir() else path.remove() def update_vendored(): update_pkg_resources() update_setuptools() def rewrite_packaging(pkg_files, new_root): """ Rewrite imports in packaging to redirect to vendored copies. """ for file in pkg_files.glob('*.py'): text = file.text() text = re.sub(r' (pyparsing)', rf' {new_root}.\1', text) text = text.replace( 'from six.moves.urllib import parse', 'from urllib import parse', ) file.write_text(text) def rewrite_jaraco_text(pkg_files, new_root): """ Rewrite imports in jaraco.text to redirect to vendored copies. """ for file in pkg_files.glob('*.py'): text = file.read_text() text = re.sub(r' (jaraco\.)', rf' {new_root}.\1', text) text = re.sub(r' (importlib_resources)', rf' {new_root}.\1', text) # suppress loading of lorem_ipsum; ref #3072 text = re.sub(r'^lorem_ipsum.*\n$', '', text, flags=re.M) file.write_text(text) def rewrite_jaraco(pkg_files, new_root): """ Rewrite imports in jaraco.functools to redirect to vendored copies. """ for file in pkg_files.glob('*.py'): text = file.read_text() text = re.sub(r' (more_itertools)', rf' {new_root}.\1', text) file.write_text(text) # required for zip-packaged setuptools #3084 pkg_files.joinpath('__init__.py').write_text('') def rewrite_importlib_resources(pkg_files, new_root): """ Rewrite imports in importlib_resources to redirect to vendored copies. """ for file in pkg_files.glob('*.py'): text = file.read_text().replace('importlib_resources.abc', '.abc') text = text.replace('zipp', '..zipp') file.write_text(text) def rewrite_importlib_metadata(pkg_files, new_root): """ Rewrite imports in importlib_metadata to redirect to vendored copies. """ for file in pkg_files.glob('*.py'): text = file.read_text().replace('typing_extensions', '..typing_extensions') text = text.replace('import zipp', 'from .. import zipp') file.write_text(text) def rewrite_more_itertools(pkg_files: Path): """ Defer import of concurrent.futures. Workaround for #3090. """ more_file = pkg_files.joinpath('more.py') text = more_file.read_text() text = re.sub(r'^.*concurrent.futures.*?\n', '', text, flags=re.MULTILINE) text = re.sub( 'ThreadPoolExecutor', '__import__("concurrent.futures").futures.ThreadPoolExecutor', text, ) more_file.write_text(text) def rewrite_nspektr(pkg_files: Path, new_root): for file in pkg_files.glob('*.py'): text = file.read_text() text = re.sub(r' (more_itertools)', rf' {new_root}.\1', text) text = re.sub(r' (jaraco\.\w+)', rf' {new_root}.\1', text) text = re.sub(r' (packaging)', rf' {new_root}.\1', text) text = re.sub(r' (importlib_metadata)', rf' {new_root}.\1', text) file.write_text(text) def clean(vendor): """ Remove all files out of the vendor directory except the meta data (as pip uninstall doesn't support -t). """ remove_all( path for path in vendor.glob('*') if path.basename() != 'vendored.txt' ) def install(vendor): clean(vendor) install_args = [ sys.executable, '-m', 'pip', 'install', '-r', str(vendor / 'vendored.txt'), '-t', str(vendor), ] subprocess.check_call(install_args) (vendor / '__init__.py').write_text('') def update_pkg_resources(): vendor = Path('pkg_resources/_vendor') install(vendor) rewrite_packaging(vendor / 'packaging', 'pkg_resources.extern') rewrite_jaraco_text(vendor / 'jaraco/text', 'pkg_resources.extern') rewrite_jaraco(vendor / 'jaraco', 'pkg_resources.extern') rewrite_importlib_resources(vendor / 'importlib_resources', 'pkg_resources.extern') rewrite_more_itertools(vendor / "more_itertools") def update_setuptools(): vendor = Path('setuptools/_vendor') install(vendor) install_validate_pyproject(vendor) rewrite_packaging(vendor / 'packaging', 'setuptools.extern') rewrite_jaraco_text(vendor / 'jaraco/text', 'setuptools.extern') rewrite_jaraco(vendor / 'jaraco', 'setuptools.extern') rewrite_importlib_resources(vendor / 'importlib_resources', 'setuptools.extern') rewrite_importlib_metadata(vendor / 'importlib_metadata', 'setuptools.extern') rewrite_more_itertools(vendor / "more_itertools") rewrite_nspektr(vendor / "nspektr", 'setuptools.extern') def install_validate_pyproject(vendor): """``validate-pyproject`` can be vendorized to remove all dependencies""" req = next( (x for x in (vendor / "vendored.txt").lines() if 'validate-pyproject' in x), "validate-pyproject[all]" ) pkg, _, _ = req.strip(string.whitespace + "#").partition("#") pkg = pkg.strip() opts = {} if sys.version_info[:2] >= (3, 10): opts["ignore_cleanup_errors"] = True with TemporaryDirectory(**opts) as tmp: env_builder = venv.EnvBuilder(with_pip=True) env_builder.create(tmp) context = env_builder.ensure_directories(tmp) venv_python = getattr(context, 'env_exec_cmd', context.env_exe) subprocess.check_call([venv_python, "-m", "pip", "install", pkg]) cmd = [ venv_python, "-m", "validate_pyproject.vendoring", f"--output-dir={vendor / '_validate_pyproject' !s}", "--enable-plugins", "setuptools", "distutils", "--very-verbose" ] subprocess.check_call(cmd) __name__ == '__main__' and update_vendored()