123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- """"Vendoring script, python 3.5 needed"""
- from pathlib import Path
- import os
- import re
- import shutil
- import invoke
- TASK_NAME = 'update'
- FILE_WHITE_LIST = (
- 'Makefile',
- 'vendor.txt',
- '__init__.py',
- 'README.rst',
- )
- def drop_dir(path, **kwargs):
- shutil.rmtree(str(path), **kwargs)
- def remove_all(paths):
- for path in paths:
- if path.is_dir():
- drop_dir(path)
- else:
- path.unlink()
- def log(msg):
- print('[vendoring.%s] %s' % (TASK_NAME, msg))
- def _get_vendor_dir(ctx):
- git_root = ctx.run('git rev-parse --show-toplevel', hide=True).stdout
- return Path(git_root.strip()) / 'src' / 'pip' / '_vendor'
- def clean_vendor(ctx, vendor_dir):
- # Old _vendor cleanup
- remove_all(vendor_dir.glob('*.pyc'))
- log('Cleaning %s' % vendor_dir)
- for item in vendor_dir.iterdir():
- if item.is_dir():
- shutil.rmtree(str(item))
- elif item.name not in FILE_WHITE_LIST:
- item.unlink()
- else:
- log('Skipping %s' % item)
- def detect_vendored_libs(vendor_dir):
- retval = []
- for item in vendor_dir.iterdir():
- if item.is_dir():
- retval.append(item.name)
- elif item.name.endswith(".pyi"):
- continue
- elif item.name not in FILE_WHITE_LIST:
- retval.append(item.name[:-3])
- return retval
- def rewrite_imports(package_dir, vendored_libs):
- for item in package_dir.iterdir():
- if item.is_dir():
- rewrite_imports(item, vendored_libs)
- elif item.name.endswith('.py'):
- rewrite_file_imports(item, vendored_libs)
- def rewrite_file_imports(item, vendored_libs):
- """Rewrite 'import xxx' and 'from xxx import' for vendored_libs"""
- text = item.read_text(encoding='utf-8')
- # Revendor pkg_resources.extern first
- text = re.sub(r'pkg_resources.extern', r'pip._vendor', text)
- for lib in vendored_libs:
- text = re.sub(
- r'(\n\s*|^)import %s(\n\s*)' % lib,
- r'\1from pip._vendor import %s\2' % lib,
- text,
- )
- text = re.sub(
- r'(\n\s*|^)from %s(\.|\s+)' % lib,
- r'\1from pip._vendor.%s\2' % lib,
- text,
- )
- item.write_text(text, encoding='utf-8')
- def apply_patch(ctx, patch_file_path):
- log('Applying patch %s' % patch_file_path.name)
- ctx.run('git apply --verbose %s' % patch_file_path)
- def vendor(ctx, vendor_dir):
- log('Reinstalling vendored libraries')
- # We use --no-deps because we want to ensure that all of our dependencies
- # are added to vendor.txt, this includes all dependencies recursively up
- # the chain.
- ctx.run(
- 'pip install -t {0} -r {0}/vendor.txt --no-compile --no-deps'.format(
- str(vendor_dir),
- )
- )
- remove_all(vendor_dir.glob('*.dist-info'))
- remove_all(vendor_dir.glob('*.egg-info'))
- # Cleanup setuptools unneeded parts
- (vendor_dir / 'easy_install.py').unlink()
- drop_dir(vendor_dir / 'setuptools')
- drop_dir(vendor_dir / 'pkg_resources' / '_vendor')
- drop_dir(vendor_dir / 'pkg_resources' / 'extern')
- # Drop the bin directory (contains easy_install, distro, chardetect etc.)
- # Might not appear on all OSes, so ignoring errors
- drop_dir(vendor_dir / 'bin', ignore_errors=True)
- # Drop interpreter and OS specific msgpack libs.
- # Pip will rely on the python-only fallback instead.
- remove_all(vendor_dir.glob('msgpack/*.so'))
- # Detect the vendored packages/modules
- vendored_libs = detect_vendored_libs(vendor_dir)
- log("Detected vendored libraries: %s" % ", ".join(vendored_libs))
- # Global import rewrites
- log("Rewriting all imports related to vendored libs")
- for item in vendor_dir.iterdir():
- if item.is_dir():
- rewrite_imports(item, vendored_libs)
- elif item.name not in FILE_WHITE_LIST:
- rewrite_file_imports(item, vendored_libs)
- # Special cases: apply stored patches
- log("Apply patches")
- patch_dir = Path(__file__).parent / 'patches'
- for patch in patch_dir.glob('*.patch'):
- apply_patch(ctx, patch)
- @invoke.task
- def update_stubs(ctx):
- vendor_dir = _get_vendor_dir(ctx)
- vendored_libs = detect_vendored_libs(vendor_dir)
- print("[vendoring.update_stubs] Add mypy stubs")
- extra_stubs_needed = {
- # Some projects need stubs other than a simple <name>.pyi
- "six": [
- "six.__init__",
- "six.moves.__init__",
- "six.moves.configparser",
- ],
- # Some projects should not have stubs coz they're single file modules
- "appdirs": [],
- }
- for lib in vendored_libs:
- if lib not in extra_stubs_needed:
- (vendor_dir / (lib + ".pyi")).write_text("from %s import *" % lib)
- continue
- for selector in extra_stubs_needed[lib]:
- fname = selector.replace(".", os.sep) + ".pyi"
- if selector.endswith(".__init__"):
- selector = selector[:-9]
- f_path = vendor_dir / fname
- if not f_path.parent.exists():
- f_path.parent.mkdir()
- f_path.write_text("from %s import *" % selector)
- @invoke.task(name=TASK_NAME, post=[update_stubs])
- def main(ctx):
- vendor_dir = _get_vendor_dir(ctx)
- log('Using vendor dir: %s' % vendor_dir)
- clean_vendor(ctx, vendor_dir)
- vendor(ctx, vendor_dir)
- log('Revendoring complete')
|