Skip to content

Commit 8575e0c

Browse files
committed
Merge pull request pypa#1510 from dstufft/use-zip-get-pip
Use a ZipFile as the get-pip.py script
2 parents ed11675 + 645180e commit 8575e0c

File tree

5 files changed

+20487
-21678
lines changed

5 files changed

+20487
-21678
lines changed

contrib/build-installer

Lines changed: 154 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,164 @@
11
#!/usr/bin/env python
2-
2+
import base64
33
import os
44
import sys
5-
from packager import generate_script
5+
import zipfile
6+
7+
WRAPPER_SCRIPT = b"""
8+
#!/usr/bin/env python
9+
#
10+
# Hi There!
11+
# You may be wondering what this giant blob of binary data here is, you might
12+
# even be worried that we're up to something nefarious (good for you for being
13+
# paranoid!). This is a base4 encoding of a zip file, this zip file contains
14+
# an entire copy of pip.
15+
#
16+
# Pip is a thing that installs packages, pip itself is a package that someone
17+
# might want to install, especially if they're looking to run this get-pip.py
18+
# script. Pip has a lot of code to deal with the security of installing
19+
# packages, various edge cases on various platforms, and other such sort of
20+
# "tribal knowledge" that has been encoded in it's code base. Because of this
21+
# we basically include an entire copy of pip inside this blob. We do this
22+
# because the alternatives are attempt to implement a "minipip" that probably
23+
# doesn't do things correctly and has weird edge cases, or compress pip itself
24+
# down into a single file.
25+
#
26+
# If you're wondering how this is created, the secret is
27+
# "contrib/build-installer" from the pip repository.
28+
29+
ZIPFILE = b\"\"\"
30+
{zipfile}
31+
\"\"\"
32+
33+
import base64
34+
import os.path
35+
import pkgutil
36+
import shutil
37+
import sys
38+
import tempfile
39+
40+
41+
def bootstrap(tmpdir=None):
42+
# Import pip so we can use it to install pip and maybe setuptools too
43+
import pip
644
7-
here = os.path.dirname(os.path.abspath(__file__))
8-
file_name = os.path.join(here, 'get-pip.py')
45+
# We always want to install pip
46+
packages = ["pip"]
47+
48+
# Check if the user has requested us not to install setuptools
49+
if "--no-setuptools" in sys.argv or os.environ.get("PIP_NO_SETUPTOOLS"):
50+
args = [x for x in sys.argv[1:] if x != "--no-setuptools"]
51+
else:
52+
args = sys.argv[1:]
53+
54+
# We want to see if setuptools is available before attempting to
55+
# install it
56+
try:
57+
import setuptools
58+
except ImportError:
59+
packages += ["setuptools"]
60+
61+
delete_tmpdir = False
62+
try:
63+
# Create a temporary directory to act as a working directory if we were
64+
# not given one.
65+
if tmpdir is None:
66+
tmpdir = tempfile.mkdtemp()
67+
delete_tmpdir = True
68+
69+
# We need to extract the SSL certificates from requests so that they
70+
# can be passed to --cert
71+
cert_path = os.path.join(tmpdir, "cacert.pem")
72+
with open(cert_path, "wb") as cert:
73+
cert.write(pkgutil.get_data("pip._vendor.requests", "cacert.pem"))
74+
75+
# Use an environment variable here so that users can still pass
76+
# --cert via sys.argv
77+
os.environ.setdefault("PIP_CERT", cert_path)
78+
79+
# Execute the included pip and use it to install the latest pip and
80+
# setuptools from PyPI
81+
sys.exit(pip.main(["install", "--upgrade"] + packages + args))
82+
finally:
83+
# Remove our temporary directory
84+
if delete_tmpdir and tmpdir:
85+
shutil.rmtree(tmpdir, ignore_errors=True)
986
10-
entry = """
11-
import pip
12-
pip.bootstrap()
13-
"""
1487
1588
def main():
16-
sys.stdout.write("Creating pip bootstrapper...")
17-
script = generate_script(entry, ['pip'])
18-
f = open(file_name, 'w')
89+
tmpdir = None
1990
try:
20-
f.write(script)
91+
# Create a temporary working directory
92+
tmpdir = tempfile.mkdtemp()
93+
94+
# Unpack the zipfile into the temporary directory
95+
pip_zip = os.path.join(tmpdir, "pip.zip")
96+
with open(pip_zip, "wb") as fp:
97+
fp.write(base64.decodestring(ZIPFILE))
98+
99+
# Add the zipfile to sys.path so that we can import it
100+
sys.path = [pip_zip] + sys.path
101+
102+
# Run the bootstrap
103+
bootstrap(tmpdir=tmpdir)
21104
finally:
22-
f.close()
23-
sys.stdout.write('done.\n')
24-
if hasattr(os, 'chmod'):
25-
oldmode = os.stat(file_name).st_mode & 07777
26-
newmode = (oldmode | 0555) & 07777
27-
os.chmod(file_name, newmode)
28-
sys.stdout.write('Made resulting file %s executable.\n\n' % file_name)
29-
30-
if __name__ == '__main__':
105+
# Clean up our temporary working directory
106+
if tmpdir:
107+
shutil.rmtree(tmpdir, ignore_errors=True)
108+
109+
110+
if __name__ == "__main__":
111+
main()
112+
""".lstrip()
113+
114+
115+
def getmodname(rootpath, path):
116+
parts = path.split(os.sep)[len(rootpath.split(os.sep)):]
117+
return '/'.join(parts)
118+
119+
120+
def main():
121+
sys.stdout.write("Creating pip bootstrapper...")
122+
123+
here = os.path.dirname(os.path.abspath(__file__))
124+
toplevel = os.path.dirname(here)
125+
get_pip = os.path.join(here, "get-pip.py")
126+
127+
# Get all of the files we want to add to the zip file
128+
all_files = []
129+
for root, dirs, files in os.walk(os.path.join(toplevel, "pip")):
130+
for pyfile in files:
131+
if os.path.splitext(pyfile)[1] in {".py", ".pem", ".cfg", ".exe"}:
132+
all_files.append(
133+
getmodname(toplevel, os.path.join(root, pyfile))
134+
)
135+
136+
with zipfile.ZipFile(get_pip, "w", compression=zipfile.ZIP_DEFLATED) as z:
137+
# Write the pip files to the zip archive
138+
for filename in all_files:
139+
z.write(os.path.join(toplevel, filename), filename)
140+
141+
# Get the binary data that compromises our zip file
142+
with open(get_pip, "rb") as fp:
143+
data = fp.read()
144+
145+
# Write out the wrapper script that will take the place of the zip script
146+
# The reason we need to do this instead of just directly executing the
147+
# zip script is that while Python will happily execute a zip script if
148+
# passed it on the file system, it will not however allow this to work if
149+
# passed it via stdin. This means that this wrapper script is required to
150+
# make ``curl https://...../get-pip.py | python`` continue to work.
151+
with open(get_pip, "wb") as fp:
152+
fp.write(WRAPPER_SCRIPT.format(zipfile=base64.encodestring(data)))
153+
154+
# Ensure the permissions on the newly created file
155+
if hasattr(os, "chmod"):
156+
oldmode = os.stat(get_pip).st_mode & 0o7777
157+
newmode = (oldmode | 0o555) & 0o7777
158+
os.chmod(get_pip, newmode)
159+
160+
sys.stdout.write("done.\n")
161+
162+
163+
if __name__ == "__main__":
31164
main()

contrib/build-standalone

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

0 commit comments

Comments
 (0)