1#!python
2"""Bootstrap setuptools installation
3
4If you want to use setuptools in your package's setup.py, just include this
5file in the same directory with it, and add this to the top of your setup.py::
6
7    from ez_setup import use_setuptools
8    use_setuptools()
9
10If you want to require a specific version of setuptools, set a download
11mirror, or use an alternate download directory, you can do so by supplying
12the appropriate options to ``use_setuptools()``.
13
14This file can also be run as a script to install or upgrade setuptools.
15"""
16import os
17import shutil
18import sys
19import tempfile
20import tarfile
21import optparse
22import subprocess
23
24from distutils import log
25
26try:
27    from site import USER_SITE
28except ImportError:
29    USER_SITE = None
30
31DEFAULT_VERSION = "0.9.6"
32DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/"
33
34def _python_cmd(*args):
35    args = (sys.executable,) + args
36    return subprocess.call(args) == 0
37
38def _install(tarball, install_args=()):
39    # extracting the tarball
40    tmpdir = tempfile.mkdtemp()
41    log.warn('Extracting in %s', tmpdir)
42    old_wd = os.getcwd()
43    try:
44        os.chdir(tmpdir)
45        tar = tarfile.open(tarball)
46        _extractall(tar)
47        tar.close()
48
49        # going in the directory
50        subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
51        os.chdir(subdir)
52        log.warn('Now working in %s', subdir)
53
54        # installing
55        log.warn('Installing Setuptools')
56        if not _python_cmd('setup.py', 'install', *install_args):
57            log.warn('Something went wrong during the installation.')
58            log.warn('See the error message above.')
59            # exitcode will be 2
60            return 2
61    finally:
62        os.chdir(old_wd)
63        shutil.rmtree(tmpdir)
64
65
66def _build_egg(egg, tarball, to_dir):
67    # extracting the tarball
68    tmpdir = tempfile.mkdtemp()
69    log.warn('Extracting in %s', tmpdir)
70    old_wd = os.getcwd()
71    try:
72        os.chdir(tmpdir)
73        tar = tarfile.open(tarball)
74        _extractall(tar)
75        tar.close()
76
77        # going in the directory
78        subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
79        os.chdir(subdir)
80        log.warn('Now working in %s', subdir)
81
82        # building an egg
83        log.warn('Building a Setuptools egg in %s', to_dir)
84        _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
85
86    finally:
87        os.chdir(old_wd)
88        shutil.rmtree(tmpdir)
89    # returning the result
90    log.warn(egg)
91    if not os.path.exists(egg):
92        raise IOError('Could not build the egg.')
93
94
95def _do_download(version, download_base, to_dir, download_delay):
96    egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg'
97                       % (version, sys.version_info[0], sys.version_info[1]))
98    if not os.path.exists(egg):
99        tarball = download_setuptools(version, download_base,
100                                      to_dir, download_delay)
101        _build_egg(egg, tarball, to_dir)
102    sys.path.insert(0, egg)
103    import setuptools
104    setuptools.bootstrap_install_from = egg
105
106
107def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
108                   to_dir=os.curdir, download_delay=15):
109    # making sure we use the absolute path
110    to_dir = os.path.abspath(to_dir)
111    was_imported = 'pkg_resources' in sys.modules or \
112        'setuptools' in sys.modules
113    try:
114        import pkg_resources
115    except ImportError:
116        return _do_download(version, download_base, to_dir, download_delay)
117    try:
118        pkg_resources.require("setuptools>=" + version)
119        return
120    except pkg_resources.VersionConflict:
121        e = sys.exc_info()[1]
122        if was_imported:
123            sys.stderr.write(
124            "The required version of setuptools (>=%s) is not available,\n"
125            "and can't be installed while this script is running. Please\n"
126            "install a more recent version first, using\n"
127            "'easy_install -U setuptools'."
128            "\n\n(Currently using %r)\n" % (version, e.args[0]))
129            sys.exit(2)
130        else:
131            del pkg_resources, sys.modules['pkg_resources']    # reload ok
132            return _do_download(version, download_base, to_dir,
133                                download_delay)
134    except pkg_resources.DistributionNotFound:
135        return _do_download(version, download_base, to_dir,
136                            download_delay)
137
138
139def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
140                        to_dir=os.curdir, delay=15):
141    """Download setuptools from a specified location and return its filename
142
143    `version` should be a valid setuptools version number that is available
144    as an egg for download under the `download_base` URL (which should end
145    with a '/'). `to_dir` is the directory where the egg will be downloaded.
146    `delay` is the number of seconds to pause before an actual download
147    attempt.
148    """
149    # making sure we use the absolute path
150    to_dir = os.path.abspath(to_dir)
151    try:
152        from urllib.request import urlopen
153    except ImportError:
154        from urllib2 import urlopen
155    tgz_name = "setuptools-%s.tar.gz" % version
156    url = download_base + tgz_name
157    saveto = os.path.join(to_dir, tgz_name)
158    src = dst = None
159    if not os.path.exists(saveto):  # Avoid repeated downloads
160        try:
161            log.warn("Downloading %s", url)
162            src = urlopen(url)
163            # Read/write all in one block, so we don't create a corrupt file
164            # if the download is interrupted.
165            data = src.read()
166            dst = open(saveto, "wb")
167            dst.write(data)
168        finally:
169            if src:
170                src.close()
171            if dst:
172                dst.close()
173    return os.path.realpath(saveto)
174
175
176def _extractall(self, path=".", members=None):
177    """Extract all members from the archive to the current working
178       directory and set owner, modification time and permissions on
179       directories afterwards. `path' specifies a different directory
180       to extract to. `members' is optional and must be a subset of the
181       list returned by getmembers().
182    """
183    import copy
184    import operator
185    from tarfile import ExtractError
186    directories = []
187
188    if members is None:
189        members = self
190
191    for tarinfo in members:
192        if tarinfo.isdir():
193            # Extract directories with a safe mode.
194            directories.append(tarinfo)
195            tarinfo = copy.copy(tarinfo)
196            tarinfo.mode = 448  # decimal for oct 0700
197        self.extract(tarinfo, path)
198
199    # Reverse sort directories.
200    if sys.version_info < (2, 4):
201        def sorter(dir1, dir2):
202            return cmp(dir1.name, dir2.name)
203        directories.sort(sorter)
204        directories.reverse()
205    else:
206        directories.sort(key=operator.attrgetter('name'), reverse=True)
207
208    # Set correct owner, mtime and filemode on directories.
209    for tarinfo in directories:
210        dirpath = os.path.join(path, tarinfo.name)
211        try:
212            self.chown(tarinfo, dirpath)
213            self.utime(tarinfo, dirpath)
214            self.chmod(tarinfo, dirpath)
215        except ExtractError:
216            e = sys.exc_info()[1]
217            if self.errorlevel > 1:
218                raise
219            else:
220                self._dbg(1, "tarfile: %s" % e)
221
222
223def _build_install_args(options):
224    """
225    Build the arguments to 'python setup.py install' on the setuptools package
226    """
227    install_args = []
228    if options.user_install:
229        if sys.version_info < (2, 6):
230            log.warn("--user requires Python 2.6 or later")
231            raise SystemExit(1)
232        install_args.append('--user')
233    return install_args
234
235def _parse_args():
236    """
237    Parse the command line for options
238    """
239    parser = optparse.OptionParser()
240    parser.add_option(
241        '--user', dest='user_install', action='store_true', default=False,
242        help='install in user site package (requires Python 2.6 or later)')
243    parser.add_option(
244        '--download-base', dest='download_base', metavar="URL",
245        default=DEFAULT_URL,
246        help='alternative URL from where to download the setuptools package')
247    options, args = parser.parse_args()
248    # positional arguments are ignored
249    return options
250
251def main(version=DEFAULT_VERSION):
252    """Install or upgrade setuptools and EasyInstall"""
253    options = _parse_args()
254    tarball = download_setuptools(download_base=options.download_base)
255    return _install(tarball, _build_install_args(options))
256
257if __name__ == '__main__':
258    sys.exit(main())
259