summaryrefslogtreecommitdiff
path: root/contrib/build-installer
blob: c3defcc69d43b574cb978408a66d2daa31c5624c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/usr/bin/env python
import base64
import os
import sys
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 base4 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

    # 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)


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(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:
        # 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()