|
1 | 1 | #!/usr/bin/env python
|
2 |
| - |
| 2 | +import base64 |
3 | 3 | import os
|
4 | 4 | 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 |
6 | 44 |
|
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) |
9 | 86 |
|
10 |
| -entry = """ |
11 |
| -import pip |
12 |
| -pip.bootstrap() |
13 |
| -""" |
14 | 87 |
|
15 | 88 | def main():
|
16 |
| - sys.stdout.write("Creating pip bootstrapper...") |
17 |
| - script = generate_script(entry, ['pip']) |
18 |
| - f = open(file_name, 'w') |
| 89 | + tmpdir = None |
19 | 90 | 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) |
21 | 104 | 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__": |
31 | 164 | main()
|
0 commit comments