mirror of
https://github.com/pypa/pip
synced 2023-12-13 21:30:23 +01:00
Merge pull request #3396 from dstufft/no-get-pip
Move get-pip.py into it's own repository
This commit is contained in:
commit
ce4c3567fb
19010
contrib/get-pip.py
19010
contrib/get-pip.py
File diff suppressed because it is too large
Load diff
|
@ -104,11 +104,11 @@ Release Process
|
||||||
6. Upload the distribution files to PyPI using twine
|
6. Upload the distribution files to PyPI using twine
|
||||||
(``twine upload -s dist/*``). The upload should include GPG signatures of
|
(``twine upload -s dist/*``). The upload should include GPG signatures of
|
||||||
the distribution files.
|
the distribution files.
|
||||||
7. Regenerate the ``get-pip.py`` script by running
|
7. Merge the ``master`` branch into the ``develop`` branch.
|
||||||
``invoke generate.installer`` on the master branch, and committing the
|
8. Push all of the changes.
|
||||||
|
9. Regenerate the ``get-pip.py`` script by running
|
||||||
|
``invoke generate.installer`` in the get-pip repository, and committing the
|
||||||
results.
|
results.
|
||||||
8. Merge the ``master`` branch into the ``develop`` branch.
|
|
||||||
9. Push all of the changes.
|
|
||||||
|
|
||||||
|
|
||||||
Creating a Bugfix Release
|
Creating a Bugfix Release
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
import base64
|
|
||||||
import hashlib
|
|
||||||
import io
|
import io
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import urllib.request
|
|
||||||
|
|
||||||
import invoke
|
import invoke
|
||||||
|
|
||||||
from . import paths
|
|
||||||
|
|
||||||
|
|
||||||
@invoke.task
|
@invoke.task
|
||||||
def authors():
|
def authors():
|
||||||
|
@ -33,262 +26,3 @@ def authors():
|
||||||
with io.open("AUTHORS.txt", "w", encoding="utf8") as fp:
|
with io.open("AUTHORS.txt", "w", encoding="utf8") as fp:
|
||||||
fp.write(u"\n".join(authors))
|
fp.write(u"\n".join(authors))
|
||||||
fp.write(u"\n")
|
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")
|
|
||||||
|
|
|
@ -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")
|
|
Loading…
Reference in a new issue