-
Notifications
You must be signed in to change notification settings - Fork 310
Fix internals #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Fix internals #21
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
This file contains hidden or 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 |
---|---|---|
@@ -0,0 +1,210 @@ | ||
#!/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 (version {installed_version}). | ||
# | ||
# 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._internal | ||
from pip._internal.commands.install import InstallCommand | ||
from pip._internal.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._internal.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{pip_version}"] | ||
if implicit_setuptools: | ||
args += ["setuptools{setuptools_version}"] | ||
if implicit_wheel: | ||
args += ["wheel{wheel_version}"] | ||
|
||
# Add our default arguments | ||
args = ["install", "--upgrade", "--force-reinstall"] + args | ||
|
||
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.certifi", "cacert.pem")) | ||
|
||
# Execute the included pip and use it to install the latest pip and | ||
# setuptools from PyPI | ||
sys.exit(pip._internal.main(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() |
File renamed without changes.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure we want a force reinstall here? I think if it's no longer seen as a valid installed package (due to the removal of dist-info, we'd not need that).