Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-backend / distribute_setup.py @ e161c24f

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 available,\n"
154
                    "and can't be installed while this script is running. Please\n"
155
                    "install a more recent version first, using\n"
156
                    "'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(
333
    _create_fake_setuptools_pkg_info)
334

    
335

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

    
354
_patch_egg_dir = _no_sandbox(_patch_egg_dir)
355

    
356

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

    
361

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

    
379

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

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

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

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

    
432

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

    
442

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

    
455
    if members is None:
456
        members = self
457

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

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

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

    
489

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

    
495

    
496
if __name__ == '__main__':
497
    main(sys.argv[1:])