Statistics
| Branch: | Tag: | Revision:

root / astakosclient / distribute_setup.py @ 10d9cd27

History | View | Annotate | Download (15.4 kB)

1
#!python
2
"""Bootstrap distribute installation
3

4
If you want to use setuptools in your package's setup.py, just include this
5
file in the same directory with it, and add this to the top of your setup.py::
6

7
    from distribute_setup import use_setuptools
8
    use_setuptools()
9

10
If you want to require a specific version of setuptools, set a download
11
mirror, or use an alternate download directory, you can do so by supplying
12
the appropriate options to ``use_setuptools()``.
13

14
This file can also be run as a script to install or upgrade setuptools.
15
"""
16
import os
17
import sys
18
import time
19
import fnmatch
20
import tempfile
21
import tarfile
22
from distutils import log
23

    
24
try:
25
    from site import USER_SITE
26
except ImportError:
27
    USER_SITE = None
28

    
29
try:
30
    import subprocess
31

    
32
    def _python_cmd(*args):
33
        args = (sys.executable,) + args
34
        return subprocess.call(args) == 0
35

    
36
except ImportError:
37
    # will be used for python 2.3
38
    def _python_cmd(*args):
39
        args = (sys.executable,) + args
40
        # quoting arguments if windows
41
        if sys.platform == 'win32':
42
            def quote(arg):
43
                if ' ' in arg:
44
                    return '"%s"' % arg
45
                return arg
46
            args = [quote(arg) for arg in args]
47
        return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
48

    
49
DEFAULT_VERSION = "0.6.10"
50
DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
51
SETUPTOOLS_FAKED_VERSION = "0.6c11"
52

    
53
SETUPTOOLS_PKG_INFO = """\
54
Metadata-Version: 1.0
55
Name: setuptools
56
Version: %s
57
Summary: xxxx
58
Home-page: xxx
59
Author: xxx
60
Author-email: xxx
61
License: xxx
62
Description: xxx
63
""" % SETUPTOOLS_FAKED_VERSION
64

    
65

    
66
def _install(tarball):
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
        # installing
83
        log.warn('Installing Distribute')
84
        if not _python_cmd('setup.py', 'install'):
85
            log.warn('Something went wrong during the installation.')
86
            log.warn('See the error message above.')
87
    finally:
88
        os.chdir(old_wd)
89

    
90

    
91
def _build_egg(egg, tarball, to_dir):
92
    # extracting the tarball
93
    tmpdir = tempfile.mkdtemp()
94
    log.warn('Extracting in %s', tmpdir)
95
    old_wd = os.getcwd()
96
    try:
97
        os.chdir(tmpdir)
98
        tar = tarfile.open(tarball)
99
        _extractall(tar)
100
        tar.close()
101

    
102
        # going in the directory
103
        subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
104
        os.chdir(subdir)
105
        log.warn('Now working in %s', subdir)
106

    
107
        # building an egg
108
        log.warn('Building a Distribute egg in %s', to_dir)
109
        _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
110

    
111
    finally:
112
        os.chdir(old_wd)
113
    # returning the result
114
    log.warn(egg)
115
    if not os.path.exists(egg):
116
        raise IOError('Could not build the egg.')
117

    
118

    
119
def _do_download(version, download_base, to_dir, download_delay):
120
    egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
121
                       % (version, sys.version_info[0], sys.version_info[1]))
122
    if not os.path.exists(egg):
123
        tarball = download_setuptools(version, download_base,
124
                                      to_dir, download_delay)
125
        _build_egg(egg, tarball, to_dir)
126
    sys.path.insert(0, egg)
127
    import setuptools
128
    setuptools.bootstrap_install_from = egg
129

    
130

    
131
def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
132
                   to_dir=os.curdir, download_delay=15, no_fake=True):
133
    # making sure we use the absolute path
134
    to_dir = os.path.abspath(to_dir)
135
    was_imported = 'pkg_resources' in sys.modules or \
136
        'setuptools' in sys.modules
137
    try:
138
        try:
139
            import pkg_resources
140
            if not hasattr(pkg_resources, '_distribute'):
141
                if not no_fake:
142
                    _fake_setuptools()
143
                raise ImportError
144
        except ImportError:
145
            return _do_download(version, download_base, to_dir, download_delay)
146
        try:
147
            pkg_resources.require("distribute>="+version)
148
            return
149
        except pkg_resources.VersionConflict:
150
            e = sys.exc_info()[1]
151
            if was_imported:
152
                sys.stderr.write(
153
                    "The required version of distribute (>=%s) is not\n"
154
                    "available and can't be installed while this script\n"
155
                    "is running. Please install a more recent version first,\n"
156
                    "using 'easy_install -U distribute'."
157
                    "\n\n(Currently using %r)\n" % (version, e.args[0]))
158
                sys.exit(2)
159
            else:
160
                del pkg_resources, sys.modules['pkg_resources']    # reload ok
161
                return _do_download(version, download_base, to_dir,
162
                                    download_delay)
163
        except pkg_resources.DistributionNotFound:
164
            return _do_download(version, download_base, to_dir,
165
                                download_delay)
166
    finally:
167
        if not no_fake:
168
            _create_fake_setuptools_pkg_info(to_dir)
169

    
170

    
171
def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
172
                        to_dir=os.curdir, delay=15):
173
    """Download distribute from a specified location and return its filename
174

175
    `version` should be a valid distribute version number that is available
176
    as an egg for download under the `download_base` URL (which should end
177
    with a '/'). `to_dir` is the directory where the egg will be downloaded.
178
    `delay` is the number of seconds to pause before an actual download
179
    attempt.
180
    """
181
    # making sure we use the absolute path
182
    to_dir = os.path.abspath(to_dir)
183
    try:
184
        from urllib.request import urlopen
185
    except ImportError:
186
        from urllib2 import urlopen
187
    tgz_name = "distribute-%s.tar.gz" % version
188
    url = download_base + tgz_name
189
    saveto = os.path.join(to_dir, tgz_name)
190
    src = dst = None
191
    if not os.path.exists(saveto):  # Avoid repeated downloads
192
        try:
193
            log.warn("Downloading %s", url)
194
            src = urlopen(url)
195
            # Read/write all in one block, so we don't create a corrupt file
196
            # if the download is interrupted.
197
            data = src.read()
198
            dst = open(saveto, "wb")
199
            dst.write(data)
200
        finally:
201
            if src:
202
                src.close()
203
            if dst:
204
                dst.close()
205
    return os.path.realpath(saveto)
206

    
207

    
208
def _no_sandbox(function):
209
    def __no_sandbox(*args, **kw):
210
        try:
211
            from setuptools.sandbox import DirectorySandbox
212
            if not hasattr(DirectorySandbox, '_old'):
213
                def violation(*args):
214
                    pass
215
                DirectorySandbox._old = DirectorySandbox._violation
216
                DirectorySandbox._violation = violation
217
                patched = True
218
            else:
219
                patched = False
220
        except ImportError:
221
            patched = False
222

    
223
        try:
224
            return function(*args, **kw)
225
        finally:
226
            if patched:
227
                DirectorySandbox._violation = DirectorySandbox._old
228
                del DirectorySandbox._old
229

    
230
    return __no_sandbox
231

    
232

    
233
def _patch_file(path, content):
234
    """Will backup the file then patch it"""
235
    existing_content = open(path).read()
236
    if existing_content == content:
237
        # already patched
238
        log.warn('Already patched.')
239
        return False
240
    log.warn('Patching...')
241
    _rename_path(path)
242
    f = open(path, 'w')
243
    try:
244
        f.write(content)
245
    finally:
246
        f.close()
247
    return True
248

    
249
_patch_file = _no_sandbox(_patch_file)
250

    
251

    
252
def _same_content(path, content):
253
    return open(path).read() == content
254

    
255

    
256
def _rename_path(path):
257
    new_name = path + '.OLD.%s' % time.time()
258
    log.warn('Renaming %s into %s', path, new_name)
259
    os.rename(path, new_name)
260
    return new_name
261

    
262

    
263
def _remove_flat_installation(placeholder):
264
    if not os.path.isdir(placeholder):
265
        log.warn('Unkown installation at %s', placeholder)
266
        return False
267
    found = False
268
    for file in os.listdir(placeholder):
269
        if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
270
            found = True
271
            break
272
    if not found:
273
        log.warn('Could not locate setuptools*.egg-info')
274
        return
275

    
276
    log.warn('Removing elements out of the way...')
277
    pkg_info = os.path.join(placeholder, file)
278
    if os.path.isdir(pkg_info):
279
        patched = _patch_egg_dir(pkg_info)
280
    else:
281
        patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
282

    
283
    if not patched:
284
        log.warn('%s already patched.', pkg_info)
285
        return False
286
    # now let's move the files out of the way
287
    for element in ('setuptools', 'pkg_resources.py', 'site.py'):
288
        element = os.path.join(placeholder, element)
289
        if os.path.exists(element):
290
            _rename_path(element)
291
        else:
292
            log.warn('Could not find the %s element of the '
293
                     'Setuptools distribution', element)
294
    return True
295

    
296
_remove_flat_installation = _no_sandbox(_remove_flat_installation)
297

    
298

    
299
def _after_install(dist):
300
    log.warn('After install bootstrap.')
301
    placeholder = dist.get_command_obj('install').install_purelib
302
    _create_fake_setuptools_pkg_info(placeholder)
303

    
304

    
305
def _create_fake_setuptools_pkg_info(placeholder):
306
    if not placeholder or not os.path.exists(placeholder):
307
        log.warn('Could not find the install location')
308
        return
309
    pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
310
    setuptools_file = 'setuptools-%s-py%s.egg-info' % \
311
                      (SETUPTOOLS_FAKED_VERSION, pyver)
312
    pkg_info = os.path.join(placeholder, setuptools_file)
313
    if os.path.exists(pkg_info):
314
        log.warn('%s already exists', pkg_info)
315
        return
316

    
317
    log.warn('Creating %s', pkg_info)
318
    f = open(pkg_info, 'w')
319
    try:
320
        f.write(SETUPTOOLS_PKG_INFO)
321
    finally:
322
        f.close()
323

    
324
    pth_file = os.path.join(placeholder, 'setuptools.pth')
325
    log.warn('Creating %s', pth_file)
326
    f = open(pth_file, 'w')
327
    try:
328
        f.write(os.path.join(os.curdir, setuptools_file))
329
    finally:
330
        f.close()
331

    
332
_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info)
333

    
334

    
335
def _patch_egg_dir(path):
336
    # let's check if it's already patched
337
    pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
338
    if os.path.exists(pkg_info):
339
        if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
340
            log.warn('%s already patched.', pkg_info)
341
            return False
342
    _rename_path(path)
343
    os.mkdir(path)
344
    os.mkdir(os.path.join(path, 'EGG-INFO'))
345
    pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
346
    f = open(pkg_info, 'w')
347
    try:
348
        f.write(SETUPTOOLS_PKG_INFO)
349
    finally:
350
        f.close()
351
    return True
352

    
353
_patch_egg_dir = _no_sandbox(_patch_egg_dir)
354

    
355

    
356
def _before_install():
357
    log.warn('Before install bootstrap.')
358
    _fake_setuptools()
359

    
360

    
361
def _under_prefix(location):
362
    if 'install' not in sys.argv:
363
        return True
364
    args = sys.argv[sys.argv.index('install')+1:]
365
    for index, arg in enumerate(args):
366
        for option in ('--root', '--prefix'):
367
            if arg.startswith('%s=' % option):
368
                top_dir = arg.split('root=')[-1]
369
                return location.startswith(top_dir)
370
            elif arg == option:
371
                if len(args) > index:
372
                    top_dir = args[index+1]
373
                    return location.startswith(top_dir)
374
        if arg == '--user' and USER_SITE is not None:
375
            return location.startswith(USER_SITE)
376
    return True
377

    
378

    
379
def _fake_setuptools():
380
    log.warn('Scanning installed packages')
381
    try:
382
        import pkg_resources
383
    except ImportError:
384
        # we're cool
385
        log.warn('Setuptools or Distribute does not seem to be installed.')
386
        return
387
    ws = pkg_resources.working_set
388
    try:
389
        setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools',
390
                                  replacement=False))
391
    except TypeError:
392
        # old distribute API
393
        setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
394

    
395
    if setuptools_dist is None:
396
        log.warn('No setuptools distribution found')
397
        return
398
    # detecting if it was already faked
399
    setuptools_location = setuptools_dist.location
400
    log.warn('Setuptools installation detected at %s', setuptools_location)
401

    
402
    # if --root or --preix was provided, and if
403
    # setuptools is not located in them, we don't patch it
404
    if not _under_prefix(setuptools_location):
405
        log.warn('Not patching, --root or --prefix is installing Distribute'
406
                 ' in another location')
407
        return
408

    
409
    # let's see if its an egg
410
    if not setuptools_location.endswith('.egg'):
411
        log.warn('Non-egg installation')
412
        res = _remove_flat_installation(setuptools_location)
413
        if not res:
414
            return
415
    else:
416
        log.warn('Egg installation')
417
        pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
418
        if (os.path.exists(pkg_info) and
419
           _same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
420
            log.warn('Already patched.')
421
            return
422
        log.warn('Patching...')
423
        # let's create a fake egg replacing setuptools one
424
        res = _patch_egg_dir(setuptools_location)
425
        if not res:
426
            return
427
    log.warn('Patched done.')
428
    _relaunch()
429

    
430

    
431
def _relaunch():
432
    log.warn('Relaunching...')
433
    # we have to relaunch the process
434
    # pip marker to avoid a relaunch bug
435
    if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']:
436
        sys.argv[0] = 'setup.py'
437
    args = [sys.executable] + sys.argv
438
    sys.exit(subprocess.call(args))
439

    
440

    
441
def _extractall(self, path=".", members=None):
442
    """Extract all members from the archive to the current working
443
       directory and set owner, modification time and permissions on
444
       directories afterwards. `path' specifies a different directory
445
       to extract to. `members' is optional and must be a subset of the
446
       list returned by getmembers().
447
    """
448
    import copy
449
    import operator
450
    from tarfile import ExtractError
451
    directories = []
452

    
453
    if members is None:
454
        members = self
455

    
456
    for tarinfo in members:
457
        if tarinfo.isdir():
458
            # Extract directories with a safe mode.
459
            directories.append(tarinfo)
460
            tarinfo = copy.copy(tarinfo)
461
            tarinfo.mode = 448  # decimal for oct 0700
462
        self.extract(tarinfo, path)
463

    
464
    # Reverse sort directories.
465
    if sys.version_info < (2, 4):
466
        def sorter(dir1, dir2):
467
            return cmp(dir1.name, dir2.name)
468
        directories.sort(sorter)
469
        directories.reverse()
470
    else:
471
        directories.sort(key=operator.attrgetter('name'), reverse=True)
472

    
473
    # Set correct owner, mtime and filemode on directories.
474
    for tarinfo in directories:
475
        dirpath = os.path.join(path, tarinfo.name)
476
        try:
477
            self.chown(tarinfo, dirpath)
478
            self.utime(tarinfo, dirpath)
479
            self.chmod(tarinfo, dirpath)
480
        except ExtractError:
481
            e = sys.exc_info()[1]
482
            if self.errorlevel > 1:
483
                raise
484
            else:
485
                self._dbg(1, "tarfile: %s" % e)
486

    
487

    
488
def main(argv, version=DEFAULT_VERSION):
489
    """Install or upgrade setuptools and EasyInstall"""
490
    tarball = download_setuptools()
491
    _install(tarball)
492

    
493

    
494
if __name__ == '__main__':
495
    main(sys.argv[1:])