Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / distribute_setup.py @ 31bc4729

History | View | Annotate | Download (15.5 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 "
154
                    "available,\n"
155
                    "and can't be installed while this script is running. "
156
                    "Please\n"
157
                    "install a more recent version first, using\n"
158
                    "'easy_install -U distribute'."
159
                    "\n\n(Currently using %r)\n" % (version, e.args[0]))
160
                sys.exit(2)
161
            else:
162
                del pkg_resources, sys.modules['pkg_resources']    # reload ok
163
                return _do_download(version, download_base, to_dir,
164
                                    download_delay)
165
        except pkg_resources.DistributionNotFound:
166
            return _do_download(version, download_base, to_dir,
167
                                download_delay)
168
    finally:
169
        if not no_fake:
170
            _create_fake_setuptools_pkg_info(to_dir)
171

    
172

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

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

    
209

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

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

    
232
    return __no_sandbox
233

    
234

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

    
251
_patch_file = _no_sandbox(_patch_file)
252

    
253

    
254
def _same_content(path, content):
255
    return open(path).read() == content
256

    
257

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

    
264

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

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

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

    
298
_remove_flat_installation = _no_sandbox(_remove_flat_installation)
299

    
300

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

    
306

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

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

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

    
334
_create_fake_setuptools_pkg_info = _no_sandbox(
335
    _create_fake_setuptools_pkg_info)
336

    
337

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

    
356
_patch_egg_dir = _no_sandbox(_patch_egg_dir)
357

    
358

    
359
def _before_install():
360
    log.warn('Before install bootstrap.')
361
    _fake_setuptools()
362

    
363

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

    
381

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

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

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

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

    
434

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

    
445

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

    
458
    if members is None:
459
        members = self
460

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

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

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

    
492

    
493
def main(argv, version=DEFAULT_VERSION):
494
    """Install or upgrade setuptools and EasyInstall"""
495
    tarball = download_setuptools()
496
    _install(tarball)
497

    
498

    
499
if __name__ == '__main__':
500
    main(sys.argv[1:])