Skip to content

Commit 2c97354

Browse files
xavfernandezdstufft
authored andcommitted
Automatic vendoring (#4093)
1 parent bb796ad commit 2c97354

File tree

9 files changed

+240
-44
lines changed

9 files changed

+240
-44
lines changed

pip/_vendor/README.rst

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ Policy
1515
pure Python.
1616

1717
* Any modifications made to libraries **MUST** be noted in
18-
``pip/_vendor/README.rst``.
18+
``pip/_vendor/README.rst`` and their corresponding patches **MUST** be
19+
included ``tasks/vendoring/patches``.
1920

2021

2122
Rationale
@@ -92,23 +93,28 @@ situation that we expect pip to be used and not mandate some external mechanism
9293
such as OS packages.
9394

9495

95-
pkg_resources
96-
-------------
97-
98-
pkg_resources has been pulled in from setuptools 28.8.0
99-
100-
10196
Modifications
10297
-------------
10398

10499
* html5lib has been modified to import six from pip._vendor
100+
* setuptools is completely stripped to only keep pkg_resources
105101
* pkg_resources has been modified to import its externs from pip._vendor
106102
* CacheControl has been modified to import its dependencies from pip._vendor
107103
* packaging has been modified to import its dependencies from pip._vendor
108104
* requests has been modified *not* to optionally load any C dependencies.
109105
* Modified distro to delay importing argparse to avoid errors on 2.6
110106

111107

108+
Automatic Vendoring
109+
-------------------
110+
111+
Vendoring is automated via the ``vendoring.update`` task (defined in
112+
``tasks/vendoring/__init__.py``) from the content of
113+
``pip/_vendor/vendor.txt`` and the different patches in
114+
``tasks/vendoring/patches/``.
115+
Launch it via ``invoke vendoring.update`` (you will need ``invoke>=0.13.0``).
116+
117+
112118
Debundling
113119
----------
114120

pip/_vendor/re-vendor.py

Lines changed: 0 additions & 34 deletions
This file was deleted.

pip/_vendor/vendor.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ ipaddress==1.0.17 # Only needed on 2.6 and 2.7
1313
packaging==16.8
1414
pyparsing==2.1.10
1515
retrying==1.3.3
16+
setuptools==28.8.0
1617
webencodings==0.5

tasks/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import invoke
22

33
from . import generate
4+
from . import vendoring
45

5-
ns = invoke.Collection(generate)
6+
ns = invoke.Collection(generate, vendoring)

tasks/generate.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44

55

66
@invoke.task
7-
def authors():
7+
def authors(ctx):
88
print("[generate.authors] Generating AUTHORS")
99

1010
# Get our list of authors
1111
print("[generate.authors] Collecting author names")
12-
r = invoke.run("git log --use-mailmap --format'=%aN <%aE>'", hide=True)
12+
r = ctx.run("git log --use-mailmap --format'=%aN <%aE>'", hide=True)
1313
authors = []
1414
seen_authors = set()
1515
for author in r.stdout.splitlines():

tasks/vendoring/__init__.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
""""Vendoring script, python 3.5 needed"""
2+
3+
from pathlib import Path
4+
import re
5+
import shutil
6+
7+
import invoke
8+
9+
TASK_NAME = 'update'
10+
11+
FILE_WHITE_LIST = (
12+
'Makefile',
13+
'vendor.txt',
14+
'__init__.py',
15+
'README.rst',
16+
)
17+
18+
19+
def drop_dir(path):
20+
shutil.rmtree(str(path))
21+
22+
23+
def remove_all(paths):
24+
for path in paths:
25+
if path.is_dir():
26+
drop_dir(path)
27+
else:
28+
path.unlink()
29+
30+
31+
def log(msg):
32+
print('[vendoring.%s] %s' % (TASK_NAME, msg))
33+
34+
35+
def clean_vendor(ctx, vendor_dir):
36+
# Old _vendor cleanup
37+
remove_all(vendor_dir.glob('*.pyc'))
38+
log('Cleaning %s' % vendor_dir)
39+
for item in vendor_dir.iterdir():
40+
if item.is_dir():
41+
shutil.rmtree(str(item))
42+
elif item.name not in FILE_WHITE_LIST:
43+
item.unlink()
44+
else:
45+
log('Skipping %s' % item)
46+
47+
48+
def rewrite_imports(package_dir, vendored_libs):
49+
for item in package_dir.iterdir():
50+
if item.is_dir():
51+
rewrite_imports(item, vendored_libs)
52+
elif item.name.endswith('.py'):
53+
rewrite_file_imports(item, vendored_libs)
54+
55+
56+
def rewrite_file_imports(item, vendored_libs):
57+
"""Rewrite 'import xxx' and 'from xxx import' for vendored_libs"""
58+
text = item.read_text()
59+
# Revendor pkg_resources.extern first
60+
text = re.sub(r'pkg_resources.extern', r'pip._vendor', text)
61+
for lib in vendored_libs:
62+
text = re.sub(
63+
r'(\n\s*)import %s' % lib,
64+
r'\1from pip._vendor import %s' % lib,
65+
text,
66+
)
67+
text = re.sub(
68+
r'(\n\s*)from %s' % lib,
69+
r'\1from pip._vendor.%s' % lib,
70+
text,
71+
)
72+
item.write_text(text)
73+
74+
75+
def apply_patch(ctx, patch_file_path):
76+
log('Applying patch %s' % patch_file_path.name)
77+
ctx.run('git apply %s' % patch_file_path)
78+
79+
80+
def vendor(ctx, vendor_dir):
81+
log('Reinstalling vendored libraries')
82+
ctx.run(
83+
'pip install -t {0} -r {0}/vendor.txt --no-compile'.format(
84+
str(vendor_dir),
85+
)
86+
)
87+
remove_all(vendor_dir.glob('*.dist-info'))
88+
remove_all(vendor_dir.glob('*.egg-info'))
89+
90+
# Cleanup setuptools unneeded parts
91+
(vendor_dir / 'easy_install.py').unlink()
92+
drop_dir(vendor_dir / 'setuptools')
93+
drop_dir(vendor_dir / 'pkg_resources' / '_vendor')
94+
drop_dir(vendor_dir / 'pkg_resources' / 'extern')
95+
96+
# Detect the vendored packages/modules
97+
vendored_libs = []
98+
for item in vendor_dir.iterdir():
99+
if item.is_dir():
100+
vendored_libs.append(item.name)
101+
elif item.name not in FILE_WHITE_LIST:
102+
vendored_libs.append(item.name[:-3])
103+
log("Detected vendored libraries: %s" % ", ".join(vendored_libs))
104+
105+
# Global import rewrites
106+
log("Rewriting all imports related to vendored libs")
107+
for item in vendor_dir.iterdir():
108+
if item.is_dir():
109+
rewrite_imports(item, vendored_libs)
110+
elif item.name not in FILE_WHITE_LIST:
111+
rewrite_file_imports(item, vendored_libs)
112+
113+
# Special cases: apply stored patches
114+
log("Apply patches")
115+
patch_dir = Path(__file__).parent / 'patches'
116+
for patch in patch_dir.glob('*.patch'):
117+
apply_patch(ctx, patch)
118+
119+
120+
@invoke.task(name=TASK_NAME)
121+
def main(ctx):
122+
git_root = Path(
123+
ctx.run('git rev-parse --show-toplevel', hide=True).stdout.strip()
124+
)
125+
vendor_dir = git_root / 'pip' / '_vendor'
126+
log('Using vendor dir: %s' % vendor_dir)
127+
clean_vendor(ctx, vendor_dir)
128+
vendor(ctx, vendor_dir)
129+
log('Revendoring complete')
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
diff --git a/pip/_vendor/cachecontrol/compat.py b/pip/_vendor/cachecontrol/compat.py
2+
index 54ec211..018e6ac 100644
3+
--- a/pip/_vendor/cachecontrol/compat.py
4+
+++ b/pip/_vendor/cachecontrol/compat.py
5+
@@ -10,17 +10,8 @@ except ImportError:
6+
import pickle
7+
8+
9+
-# Handle the case where the requests module has been patched to not have
10+
-# urllib3 bundled as part of its source.
11+
-try:
12+
- from pip._vendor.requests.packages.urllib3.response import HTTPResponse
13+
-except ImportError:
14+
- from urllib3.response import HTTPResponse
15+
-
16+
-try:
17+
- from pip._vendor.requests.packages.urllib3.util import is_fp_closed
18+
-except ImportError:
19+
- from urllib3.util import is_fp_closed
20+
+from pip._vendor.requests.packages.urllib3.response import HTTPResponse
21+
+from pip._vendor.requests.packages.urllib3.util import is_fp_closed
22+
23+
# Replicate some six behaviour
24+
try:

tasks/vendoring/patches/distro.patch

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
diff --git a/pip/_vendor/distro.py b/pip/_vendor/distro.py
2+
index 0afa527..9e7daad 100644
3+
--- a/pip/_vendor/distro.py
4+
+++ b/pip/_vendor/distro.py
5+
@@ -34,7 +34,6 @@ import sys
6+
import json
7+
import shlex
8+
import logging
9+
-import argparse
10+
import subprocess
11+
12+
13+
@@ -1052,6 +1051,8 @@ _distro = LinuxDistribution()
14+
15+
16+
def main():
17+
+ import argparse
18+
+
19+
logger = logging.getLogger(__name__)
20+
logger.setLevel(logging.DEBUG)
21+
logger.addHandler(logging.StreamHandler(sys.stdout))
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
diff --git a/pip/_vendor/requests/__init__.py b/pip/_vendor/requests/__init__.py
2+
index 9c3b769..44f6836 100644
3+
--- a/pip/_vendor/requests/__init__.py
4+
+++ b/pip/_vendor/requests/__init__.py
5+
@@ -48,11 +48,13 @@ __license__ = 'Apache 2.0'
6+
__copyright__ = 'Copyright 2016 Kenneth Reitz'
7+
8+
# Attempt to enable urllib3's SNI support, if possible
9+
-try:
10+
- from .packages.urllib3.contrib import pyopenssl
11+
- pyopenssl.inject_into_urllib3()
12+
-except ImportError:
13+
- pass
14+
+# Note: Patched by pip to prevent using the PyOpenSSL module. On Windows this
15+
+# prevents upgrading cryptography.
16+
+# try:
17+
+# from .packages.urllib3.contrib import pyopenssl
18+
+# pyopenssl.inject_into_urllib3()
19+
+# except ImportError:
20+
+# pass
21+
22+
import warnings
23+
24+
diff --git a/pip/_vendor/requests/compat.py b/pip/_vendor/requests/compat.py
25+
index eb6530d..353ec29 100644
26+
--- a/pip/_vendor/requests/compat.py
27+
+++ b/pip/_vendor/requests/compat.py
28+
@@ -25,12 +25,14 @@ is_py2 = (_ver[0] == 2)
29+
#: Python 3.x?
30+
is_py3 = (_ver[0] == 3)
31+
32+
-try:
33+
- import simplejson as json
34+
-except (ImportError, SyntaxError):
35+
- # simplejson does not support Python 3.2, it throws a SyntaxError
36+
- # because of u'...' Unicode literals.
37+
- import json
38+
+# Note: We've patched out simplejson support in pip because it prevents
39+
+# upgrading simplejson on Windows.
40+
+# try:
41+
+# import simplejson as json
42+
+# except (ImportError, SyntaxError):
43+
+# # simplejson does not support Python 3.2, it throws a SyntaxError
44+
+# # because of u'...' Unicode literals.
45+
+import json
46+
47+
# ---------
48+
# Specifics

0 commit comments

Comments
 (0)