1import re 2import sys 3import string 4import subprocess 5import venv 6from tempfile import TemporaryDirectory 7 8from path import Path 9 10 11def remove_all(paths): 12 for path in paths: 13 path.rmtree() if path.isdir() else path.remove() 14 15 16def update_vendored(): 17 update_pkg_resources() 18 update_setuptools() 19 20 21def rewrite_packaging(pkg_files, new_root): 22 """ 23 Rewrite imports in packaging to redirect to vendored copies. 24 """ 25 for file in pkg_files.glob('*.py'): 26 text = file.text() 27 text = re.sub(r' (pyparsing)', rf' {new_root}.\1', text) 28 text = text.replace( 29 'from six.moves.urllib import parse', 30 'from urllib import parse', 31 ) 32 file.write_text(text) 33 34 35def rewrite_jaraco_text(pkg_files, new_root): 36 """ 37 Rewrite imports in jaraco.text to redirect to vendored copies. 38 """ 39 for file in pkg_files.glob('*.py'): 40 text = file.read_text() 41 text = re.sub(r' (jaraco\.)', rf' {new_root}.\1', text) 42 text = re.sub(r' (importlib_resources)', rf' {new_root}.\1', text) 43 # suppress loading of lorem_ipsum; ref #3072 44 text = re.sub(r'^lorem_ipsum.*\n$', '', text, flags=re.M) 45 file.write_text(text) 46 47 48def rewrite_jaraco(pkg_files, new_root): 49 """ 50 Rewrite imports in jaraco.functools to redirect to vendored copies. 51 """ 52 for file in pkg_files.glob('*.py'): 53 text = file.read_text() 54 text = re.sub(r' (more_itertools)', rf' {new_root}.\1', text) 55 file.write_text(text) 56 # required for zip-packaged setuptools #3084 57 pkg_files.joinpath('__init__.py').write_text('') 58 59 60def rewrite_importlib_resources(pkg_files, new_root): 61 """ 62 Rewrite imports in importlib_resources to redirect to vendored copies. 63 """ 64 for file in pkg_files.glob('*.py'): 65 text = file.read_text().replace('importlib_resources.abc', '.abc') 66 text = text.replace('zipp', '..zipp') 67 file.write_text(text) 68 69 70def rewrite_importlib_metadata(pkg_files, new_root): 71 """ 72 Rewrite imports in importlib_metadata to redirect to vendored copies. 73 """ 74 for file in pkg_files.glob('*.py'): 75 text = file.read_text().replace('typing_extensions', '..typing_extensions') 76 text = text.replace('import zipp', 'from .. import zipp') 77 file.write_text(text) 78 79 80def rewrite_more_itertools(pkg_files: Path): 81 """ 82 Defer import of concurrent.futures. Workaround for #3090. 83 """ 84 more_file = pkg_files.joinpath('more.py') 85 text = more_file.read_text() 86 text = re.sub(r'^.*concurrent.futures.*?\n', '', text, flags=re.MULTILINE) 87 text = re.sub( 88 'ThreadPoolExecutor', 89 '__import__("concurrent.futures").futures.ThreadPoolExecutor', 90 text, 91 ) 92 more_file.write_text(text) 93 94 95def rewrite_nspektr(pkg_files: Path, new_root): 96 for file in pkg_files.glob('*.py'): 97 text = file.read_text() 98 text = re.sub(r' (more_itertools)', rf' {new_root}.\1', text) 99 text = re.sub(r' (jaraco\.\w+)', rf' {new_root}.\1', text) 100 text = re.sub(r' (packaging)', rf' {new_root}.\1', text) 101 text = re.sub(r' (importlib_metadata)', rf' {new_root}.\1', text) 102 file.write_text(text) 103 104 105def clean(vendor): 106 """ 107 Remove all files out of the vendor directory except the meta 108 data (as pip uninstall doesn't support -t). 109 """ 110 remove_all( 111 path 112 for path in vendor.glob('*') 113 if path.basename() != 'vendored.txt' 114 ) 115 116 117def install(vendor): 118 clean(vendor) 119 install_args = [ 120 sys.executable, 121 '-m', 'pip', 122 'install', 123 '-r', str(vendor / 'vendored.txt'), 124 '-t', str(vendor), 125 ] 126 subprocess.check_call(install_args) 127 (vendor / '__init__.py').write_text('') 128 129 130def update_pkg_resources(): 131 vendor = Path('pkg_resources/_vendor') 132 install(vendor) 133 rewrite_packaging(vendor / 'packaging', 'pkg_resources.extern') 134 rewrite_jaraco_text(vendor / 'jaraco/text', 'pkg_resources.extern') 135 rewrite_jaraco(vendor / 'jaraco', 'pkg_resources.extern') 136 rewrite_importlib_resources(vendor / 'importlib_resources', 'pkg_resources.extern') 137 rewrite_more_itertools(vendor / "more_itertools") 138 139 140def update_setuptools(): 141 vendor = Path('setuptools/_vendor') 142 install(vendor) 143 install_validate_pyproject(vendor) 144 rewrite_packaging(vendor / 'packaging', 'setuptools.extern') 145 rewrite_jaraco_text(vendor / 'jaraco/text', 'setuptools.extern') 146 rewrite_jaraco(vendor / 'jaraco', 'setuptools.extern') 147 rewrite_importlib_resources(vendor / 'importlib_resources', 'setuptools.extern') 148 rewrite_importlib_metadata(vendor / 'importlib_metadata', 'setuptools.extern') 149 rewrite_more_itertools(vendor / "more_itertools") 150 rewrite_nspektr(vendor / "nspektr", 'setuptools.extern') 151 152 153def install_validate_pyproject(vendor): 154 """``validate-pyproject`` can be vendorized to remove all dependencies""" 155 req = next( 156 (x for x in (vendor / "vendored.txt").lines() if 'validate-pyproject' in x), 157 "validate-pyproject[all]" 158 ) 159 160 pkg, _, _ = req.strip(string.whitespace + "#").partition("#") 161 pkg = pkg.strip() 162 163 opts = {} 164 if sys.version_info[:2] >= (3, 10): 165 opts["ignore_cleanup_errors"] = True 166 167 with TemporaryDirectory(**opts) as tmp: 168 env_builder = venv.EnvBuilder(with_pip=True) 169 env_builder.create(tmp) 170 context = env_builder.ensure_directories(tmp) 171 venv_python = getattr(context, 'env_exec_cmd', context.env_exe) 172 173 subprocess.check_call([venv_python, "-m", "pip", "install", pkg]) 174 cmd = [ 175 venv_python, 176 "-m", 177 "validate_pyproject.vendoring", 178 f"--output-dir={vendor / '_validate_pyproject' !s}", 179 "--enable-plugins", 180 "setuptools", 181 "distutils", 182 "--very-verbose" 183 ] 184 subprocess.check_call(cmd) 185 186 187__name__ == '__main__' and update_vendored() 188