summaryrefslogtreecommitdiff
path: root/contrib/build-installer
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/build-installer')
-rwxr-xr-xcontrib/build-installer175
1 files changed, 154 insertions, 21 deletions
diff --git a/contrib/build-installer b/contrib/build-installer
index 717b8e8a8..3c1acb1f6 100755
--- a/contrib/build-installer
+++ b/contrib/build-installer
@@ -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 base64 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()