diff options
author | Donald Stufft <donald@stufft.io> | 2016-01-20 08:53:45 -0500 |
---|---|---|
committer | Donald Stufft <donald@stufft.io> | 2016-01-20 08:53:45 -0500 |
commit | 3c753c5a86303dd012a96e60013b216ebb85edbe (patch) | |
tree | 3131cce0c753b9cadae3aa77b79714fb90147c6a /tasks | |
parent | 06f13ff9772664aef9c0bbefe138aa5f235049a6 (diff) | |
download | pip-3c753c5a86303dd012a96e60013b216ebb85edbe.tar.gz |
Move get-pip.py to its own repository
Diffstat (limited to 'tasks')
-rw-r--r-- | tasks/generate.py | 266 | ||||
-rw-r--r-- | tasks/paths.py | 5 |
2 files changed, 0 insertions, 271 deletions
diff --git a/tasks/generate.py b/tasks/generate.py index 50f88c90e..7a68cd0c1 100644 --- a/tasks/generate.py +++ b/tasks/generate.py @@ -1,14 +1,7 @@ -import base64 -import hashlib import io -import json -import os -import urllib.request import invoke -from . import paths - @invoke.task def authors(): @@ -33,262 +26,3 @@ def authors(): with io.open("AUTHORS.txt", "w", encoding="utf8") as fp: fp.write(u"\n".join(authors)) fp.write(u"\n") - - -@invoke.task -def installer(installer_path=os.path.join(paths.CONTRIB, "get-pip.py")): - print("[generate.installer] Generating installer") - - # Define our wrapper script - WRAPPER_SCRIPT = """ -#!/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 base85 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 its 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, it is using an invoke task located -# in tasks/generate.py called "installer". It can be invoked by using -# ``invoke generate.installer``. - -import os.path -import pkgutil -import shutil -import sys -import struct -import tempfile - -# Useful for very coarse version differentiation. -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 - -if PY3: - iterbytes = iter -else: - def iterbytes(buf): - return (ord(byte) for byte in buf) - -try: - from base64 import b85decode -except ImportError: - _b85alphabet = (b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" - b"abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{{|}}~") - - def b85decode(b): - _b85dec = [None] * 256 - for i, c in enumerate(iterbytes(_b85alphabet)): - _b85dec[c] = i - - padding = (-len(b)) % 5 - b = b + b'~' * padding - out = [] - packI = struct.Struct('!I').pack - for i in range(0, len(b), 5): - chunk = b[i:i + 5] - acc = 0 - try: - for c in iterbytes(chunk): - acc = acc * 85 + _b85dec[c] - except TypeError: - for j, c in enumerate(iterbytes(chunk)): - if _b85dec[c] is None: - raise ValueError( - 'bad base85 character at position %d' % (i + j) - ) - raise - try: - out.append(packI(acc)) - except struct.error: - raise ValueError('base85 overflow in hunk starting at byte %d' - % i) - - result = b''.join(out) - if padding: - result = result[:-padding] - return result - - -def bootstrap(tmpdir=None): - # Import pip so we can use it to install pip and maybe setuptools too - import pip - from pip.commands.install import InstallCommand - from pip.req import InstallRequirement - - # Wrapper to provide default certificate with the lowest priority - class CertInstallCommand(InstallCommand): - def parse_args(self, args): - # If cert isn't specified in config or environment, we provide our - # own certificate through defaults. - # This allows user to specify custom cert anywhere one likes: - # config, environment variable or argv. - if not self.parser.get_default_values().cert: - self.parser.defaults["cert"] = cert_path # calculated below - return super(CertInstallCommand, self).parse_args(args) - - pip.commands_dict["install"] = CertInstallCommand - - implicit_pip = True - implicit_setuptools = True - implicit_wheel = True - - # 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"] - implicit_setuptools = False - else: - args = sys.argv[1:] - - # Check if the user has requested us not to install wheel - if "--no-wheel" in args or os.environ.get("PIP_NO_WHEEL"): - args = [x for x in args if x != "--no-wheel"] - implicit_wheel = False - - # We only want to implicitly install setuptools and wheel if they don't - # already exist on the target platform. - if implicit_setuptools: - try: - import setuptools # noqa - implicit_setuptools = False - except ImportError: - pass - if implicit_wheel: - try: - import wheel # noqa - implicit_wheel = False - except ImportError: - pass - - # We want to support people passing things like 'pip<8' to get-pip.py which - # will let them install a specific version. However because of the dreaded - # DoubleRequirement error if any of the args look like they might be a - # specific for one of our packages, then we'll turn off the implicit - # install of them. - for arg in args: - try: - req = InstallRequirement.from_line(arg) - except: - continue - - if implicit_pip and req.name == "pip": - implicit_pip = False - elif implicit_setuptools and req.name == "setuptools": - implicit_setuptools = False - elif implicit_wheel and req.name == "wheel": - implicit_wheel = False - - # Add any implicit installations to the end of our args - if implicit_pip: - args += ["pip"] - if implicit_setuptools: - args += ["setuptools"] - if implicit_wheel: - args += ["wheel"] - - 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")) - - # Execute the included pip and use it to install the latest pip and - # setuptools from PyPI - sys.exit(pip.main(["install", "--upgrade"] + args)) - finally: - # Remove our temporary directory - if delete_tmpdir and tmpdir: - shutil.rmtree(tmpdir, ignore_errors=True) - - -def main(): - tmpdir = None - try: - # 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(b85decode(DATA.replace(b"\\n", b""))) - - # Add the zipfile to sys.path so that we can import it - sys.path.insert(0, pip_zip) - - # Run the bootstrap - bootstrap(tmpdir=tmpdir) - finally: - # Clean up our temporary working directory - if tmpdir: - shutil.rmtree(tmpdir, ignore_errors=True) - - -DATA = b\"\"\" -{zipfile} -\"\"\" - - -if __name__ == "__main__": - main() -""".lstrip() - - # Determine what the latest version of pip on PyPI is. - resp = urllib.request.urlopen("https://pypi.python.org/pypi/pip/json") - data = json.loads(resp.read().decode("utf8")) - version = data["info"]["version"] - file_urls = [ - (x["url"], x["md5_digest"]) - for x in data["releases"][version] - if x["url"].endswith(".whl") - ] - assert len(file_urls) == 1 - url, expected_hash = file_urls[0] - - # Fetch the file itself. - data = urllib.request.urlopen(url).read() - assert hashlib.md5(data).hexdigest() == expected_hash - - # 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. - print( - "[generate.installer] Write the wrapper script with the bundled zip " - "file" - ) - - zipdata = base64.b85encode(data).decode("utf8") - chunked = [] - - for i in range(0, len(zipdata), 79): - chunked.append(zipdata[i:i + 79]) - - with open(installer_path, "w") as fp: - fp.write(WRAPPER_SCRIPT.format(zipfile="\n".join(chunked))) - - # Ensure the permissions on the newly created file - oldmode = os.stat(installer_path).st_mode & 0o7777 - newmode = (oldmode | 0o555) & 0o7777 - os.chmod(installer_path, newmode) - - print("[generate.installer] Generated installer") diff --git a/tasks/paths.py b/tasks/paths.py deleted file mode 100644 index abb901153..000000000 --- a/tasks/paths.py +++ /dev/null @@ -1,5 +0,0 @@ -import os.path - -PROJECT_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) - -CONTRIB = os.path.join(PROJECT_ROOT, "contrib") |