Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1510 from dstufft/use-zip-get-pip
Use a ZipFile as the get-pip.py script
- Loading branch information
Showing
5 changed files
with
20,487 additions
and
21,678 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,164 @@ | ||
#!/usr/bin/env python | ||
|
||
import base64 | ||
import os | ||
import sys | ||
from packager import generate_script | ||
import zipfile | ||
|
||
WRAPPER_SCRIPT = b""" | ||
#!/usr/bin/env python | ||
# | ||
# Hi There! | ||
# You may be wondering what this giant blob of binary data here is, you might | ||
# even be worried that we're up to something nefarious (good for you for being | ||
# paranoid!). This is a base4 encoding of a zip file, this zip file contains | ||
# an entire copy of pip. | ||
# | ||
# Pip is a thing that installs packages, pip itself is a package that someone | ||
# might want to install, especially if they're looking to run this get-pip.py | ||
# script. Pip has a lot of code to deal with the security of installing | ||
# packages, various edge cases on various platforms, and other such sort of | ||
# "tribal knowledge" that has been encoded in it's code base. Because of this | ||
# we basically include an entire copy of pip inside this blob. We do this | ||
# because the alternatives are attempt to implement a "minipip" that probably | ||
# doesn't do things correctly and has weird edge cases, or compress pip itself | ||
# down into a single file. | ||
# | ||
# If you're wondering how this is created, the secret is | ||
# "contrib/build-installer" from the pip repository. | ||
ZIPFILE = b\"\"\" | ||
{zipfile} | ||
\"\"\" | ||
import base64 | ||
import os.path | ||
import pkgutil | ||
import shutil | ||
import sys | ||
import tempfile | ||
def bootstrap(tmpdir=None): | ||
# Import pip so we can use it to install pip and maybe setuptools too | ||
import pip | ||
here = os.path.dirname(os.path.abspath(__file__)) | ||
file_name = os.path.join(here, 'get-pip.py') | ||
# We always want to install pip | ||
packages = ["pip"] | ||
# Check if the user has requested us not to install setuptools | ||
if "--no-setuptools" in sys.argv or os.environ.get("PIP_NO_SETUPTOOLS"): | ||
args = [x for x in sys.argv[1:] if x != "--no-setuptools"] | ||
else: | ||
args = sys.argv[1:] | ||
# We want to see if setuptools is available before attempting to | ||
# install it | ||
try: | ||
import setuptools | ||
except ImportError: | ||
packages += ["setuptools"] | ||
delete_tmpdir = False | ||
try: | ||
# Create a temporary directory to act as a working directory if we were | ||
# not given one. | ||
if tmpdir is None: | ||
tmpdir = tempfile.mkdtemp() | ||
delete_tmpdir = True | ||
# We need to extract the SSL certificates from requests so that they | ||
# can be passed to --cert | ||
cert_path = os.path.join(tmpdir, "cacert.pem") | ||
with open(cert_path, "wb") as cert: | ||
cert.write(pkgutil.get_data("pip._vendor.requests", "cacert.pem")) | ||
# Use an environment variable here so that users can still pass | ||
# --cert via sys.argv | ||
os.environ.setdefault("PIP_CERT", cert_path) | ||
# Execute the included pip and use it to install the latest pip and | ||
# setuptools from PyPI | ||
sys.exit(pip.main(["install", "--upgrade"] + packages + args)) | ||
finally: | ||
# Remove our temporary directory | ||
if delete_tmpdir and tmpdir: | ||
shutil.rmtree(tmpdir, ignore_errors=True) | ||
entry = """ | ||
import pip | ||
pip.bootstrap() | ||
""" | ||
def main(): | ||
sys.stdout.write("Creating pip bootstrapper...") | ||
script = generate_script(entry, ['pip']) | ||
f = open(file_name, 'w') | ||
tmpdir = None | ||
try: | ||
f.write(script) | ||
# Create a temporary working directory | ||
tmpdir = tempfile.mkdtemp() | ||
# Unpack the zipfile into the temporary directory | ||
pip_zip = os.path.join(tmpdir, "pip.zip") | ||
with open(pip_zip, "wb") as fp: | ||
fp.write(base64.decodestring(ZIPFILE)) | ||
# Add the zipfile to sys.path so that we can import it | ||
sys.path = [pip_zip] + sys.path | ||
# Run the bootstrap | ||
bootstrap(tmpdir=tmpdir) | ||
finally: | ||
f.close() | ||
sys.stdout.write('done.\n') | ||
if hasattr(os, 'chmod'): | ||
oldmode = os.stat(file_name).st_mode & 07777 | ||
newmode = (oldmode | 0555) & 07777 | ||
os.chmod(file_name, newmode) | ||
sys.stdout.write('Made resulting file %s executable.\n\n' % file_name) | ||
|
||
if __name__ == '__main__': | ||
# Clean up our temporary working directory | ||
if tmpdir: | ||
shutil.rmtree(tmpdir, ignore_errors=True) | ||
if __name__ == "__main__": | ||
main() | ||
""".lstrip() | ||
|
||
|
||
def getmodname(rootpath, path): | ||
parts = path.split(os.sep)[len(rootpath.split(os.sep)):] | ||
return '/'.join(parts) | ||
|
||
|
||
def main(): | ||
sys.stdout.write("Creating pip bootstrapper...") | ||
|
||
here = os.path.dirname(os.path.abspath(__file__)) | ||
toplevel = os.path.dirname(here) | ||
get_pip = os.path.join(here, "get-pip.py") | ||
|
||
# Get all of the files we want to add to the zip file | ||
all_files = [] | ||
for root, dirs, files in os.walk(os.path.join(toplevel, "pip")): | ||
for pyfile in files: | ||
if os.path.splitext(pyfile)[1] in {".py", ".pem", ".cfg", ".exe"}: | ||
all_files.append( | ||
getmodname(toplevel, os.path.join(root, pyfile)) | ||
) | ||
|
||
with zipfile.ZipFile(get_pip, "w", compression=zipfile.ZIP_DEFLATED) as z: | ||
# Write the pip files to the zip archive | ||
for filename in all_files: | ||
z.write(os.path.join(toplevel, filename), filename) | ||
|
||
# Get the binary data that compromises our zip file | ||
with open(get_pip, "rb") as fp: | ||
data = fp.read() | ||
|
||
# Write out the wrapper script that will take the place of the zip script | ||
# The reason we need to do this instead of just directly executing the | ||
# zip script is that while Python will happily execute a zip script if | ||
# passed it on the file system, it will not however allow this to work if | ||
# passed it via stdin. This means that this wrapper script is required to | ||
# make ``curl https://...../get-pip.py | python`` continue to work. | ||
with open(get_pip, "wb") as fp: | ||
fp.write(WRAPPER_SCRIPT.format(zipfile=base64.encodestring(data))) | ||
|
||
# Ensure the permissions on the newly created file | ||
if hasattr(os, "chmod"): | ||
oldmode = os.stat(get_pip).st_mode & 0o7777 | ||
newmode = (oldmode | 0o555) & 0o7777 | ||
os.chmod(get_pip, newmode) | ||
|
||
sys.stdout.write("done.\n") | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.