~allenap/python-oops-datedir-repo/dont-depend-on-launchpadlib

« back to all changes in this revision

Viewing changes to distribute_setup.py

  • Committer: Gavin Panella
  • Date: 2012-01-27 11:14:46 UTC
  • Revision ID: gavin@gromper.net-20120127111446-6ga4wn8tig74wn3y
MakeĀ pruneĀ optional.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.24"
 
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
def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
 
171
                        to_dir=os.curdir, delay=15):
 
172
    """Download distribute from a specified location and return its filename
 
173
 
 
174
    `version` should be a valid distribute version number that is available
 
175
    as an egg for download under the `download_base` URL (which should end
 
176
    with a '/'). `to_dir` is the directory where the egg will be downloaded.
 
177
    `delay` is the number of seconds to pause before an actual download
 
178
    attempt.
 
179
    """
 
180
    # making sure we use the absolute path
 
181
    to_dir = os.path.abspath(to_dir)
 
182
    try:
 
183
        from urllib.request import urlopen
 
184
    except ImportError:
 
185
        from urllib2 import urlopen
 
186
    tgz_name = "distribute-%s.tar.gz" % version
 
187
    url = download_base + tgz_name
 
188
    saveto = os.path.join(to_dir, tgz_name)
 
189
    src = dst = None
 
190
    if not os.path.exists(saveto):  # Avoid repeated downloads
 
191
        try:
 
192
            log.warn("Downloading %s", url)
 
193
            src = urlopen(url)
 
194
            # Read/write all in one block, so we don't create a corrupt file
 
195
            # if the download is interrupted.
 
196
            data = src.read()
 
197
            dst = open(saveto, "wb")
 
198
            dst.write(data)
 
199
        finally:
 
200
            if src:
 
201
                src.close()
 
202
            if dst:
 
203
                dst.close()
 
204
    return os.path.realpath(saveto)
 
205
 
 
206
def _no_sandbox(function):
 
207
    def __no_sandbox(*args, **kw):
 
208
        try:
 
209
            from setuptools.sandbox import DirectorySandbox
 
210
            if not hasattr(DirectorySandbox, '_old'):
 
211
                def violation(*args):
 
212
                    pass
 
213
                DirectorySandbox._old = DirectorySandbox._violation
 
214
                DirectorySandbox._violation = violation
 
215
                patched = True
 
216
            else:
 
217
                patched = False
 
218
        except ImportError:
 
219
            patched = False
 
220
 
 
221
        try:
 
222
            return function(*args, **kw)
 
223
        finally:
 
224
            if patched:
 
225
                DirectorySandbox._violation = DirectorySandbox._old
 
226
                del DirectorySandbox._old
 
227
 
 
228
    return __no_sandbox
 
229
 
 
230
def _patch_file(path, content):
 
231
    """Will backup the file then patch it"""
 
232
    existing_content = open(path).read()
 
233
    if existing_content == content:
 
234
        # already patched
 
235
        log.warn('Already patched.')
 
236
        return False
 
237
    log.warn('Patching...')
 
238
    _rename_path(path)
 
239
    f = open(path, 'w')
 
240
    try:
 
241
        f.write(content)
 
242
    finally:
 
243
        f.close()
 
244
    return True
 
245
 
 
246
_patch_file = _no_sandbox(_patch_file)
 
247
 
 
248
def _same_content(path, content):
 
249
    return open(path).read() == content
 
250
 
 
251
def _rename_path(path):
 
252
    new_name = path + '.OLD.%s' % time.time()
 
253
    log.warn('Renaming %s into %s', path, new_name)
 
254
    os.rename(path, new_name)
 
255
    return new_name
 
256
 
 
257
def _remove_flat_installation(placeholder):
 
258
    if not os.path.isdir(placeholder):
 
259
        log.warn('Unkown installation at %s', placeholder)
 
260
        return False
 
261
    found = False
 
262
    for file in os.listdir(placeholder):
 
263
        if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
 
264
            found = True
 
265
            break
 
266
    if not found:
 
267
        log.warn('Could not locate setuptools*.egg-info')
 
268
        return
 
269
 
 
270
    log.warn('Removing elements out of the way...')
 
271
    pkg_info = os.path.join(placeholder, file)
 
272
    if os.path.isdir(pkg_info):
 
273
        patched = _patch_egg_dir(pkg_info)
 
274
    else:
 
275
        patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
 
276
 
 
277
    if not patched:
 
278
        log.warn('%s already patched.', pkg_info)
 
279
        return False
 
280
    # now let's move the files out of the way
 
281
    for element in ('setuptools', 'pkg_resources.py', 'site.py'):
 
282
        element = os.path.join(placeholder, element)
 
283
        if os.path.exists(element):
 
284
            _rename_path(element)
 
285
        else:
 
286
            log.warn('Could not find the %s element of the '
 
287
                     'Setuptools distribution', element)
 
288
    return True
 
289
 
 
290
_remove_flat_installation = _no_sandbox(_remove_flat_installation)
 
291
 
 
292
def _after_install(dist):
 
293
    log.warn('After install bootstrap.')
 
294
    placeholder = dist.get_command_obj('install').install_purelib
 
295
    _create_fake_setuptools_pkg_info(placeholder)
 
296
 
 
297
def _create_fake_setuptools_pkg_info(placeholder):
 
298
    if not placeholder or not os.path.exists(placeholder):
 
299
        log.warn('Could not find the install location')
 
300
        return
 
301
    pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
 
302
    setuptools_file = 'setuptools-%s-py%s.egg-info' % \
 
303
            (SETUPTOOLS_FAKED_VERSION, pyver)
 
304
    pkg_info = os.path.join(placeholder, setuptools_file)
 
305
    if os.path.exists(pkg_info):
 
306
        log.warn('%s already exists', pkg_info)
 
307
        return
 
308
 
 
309
    log.warn('Creating %s', pkg_info)
 
310
    f = open(pkg_info, 'w')
 
311
    try:
 
312
        f.write(SETUPTOOLS_PKG_INFO)
 
313
    finally:
 
314
        f.close()
 
315
 
 
316
    pth_file = os.path.join(placeholder, 'setuptools.pth')
 
317
    log.warn('Creating %s', pth_file)
 
318
    f = open(pth_file, 'w')
 
319
    try:
 
320
        f.write(os.path.join(os.curdir, setuptools_file))
 
321
    finally:
 
322
        f.close()
 
323
 
 
324
_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info)
 
325
 
 
326
def _patch_egg_dir(path):
 
327
    # let's check if it's already patched
 
328
    pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
 
329
    if os.path.exists(pkg_info):
 
330
        if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
 
331
            log.warn('%s already patched.', pkg_info)
 
332
            return False
 
333
    _rename_path(path)
 
334
    os.mkdir(path)
 
335
    os.mkdir(os.path.join(path, 'EGG-INFO'))
 
336
    pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
 
337
    f = open(pkg_info, 'w')
 
338
    try:
 
339
        f.write(SETUPTOOLS_PKG_INFO)
 
340
    finally:
 
341
        f.close()
 
342
    return True
 
343
 
 
344
_patch_egg_dir = _no_sandbox(_patch_egg_dir)
 
345
 
 
346
def _before_install():
 
347
    log.warn('Before install bootstrap.')
 
348
    _fake_setuptools()
 
349
 
 
350
 
 
351
def _under_prefix(location):
 
352
    if 'install' not in sys.argv:
 
353
        return True
 
354
    args = sys.argv[sys.argv.index('install')+1:]
 
355
    for index, arg in enumerate(args):
 
356
        for option in ('--root', '--prefix'):
 
357
            if arg.startswith('%s=' % option):
 
358
                top_dir = arg.split('root=')[-1]
 
359
                return location.startswith(top_dir)
 
360
            elif arg == option:
 
361
                if len(args) > index:
 
362
                    top_dir = args[index+1]
 
363
                    return location.startswith(top_dir)
 
364
        if arg == '--user' and USER_SITE is not None:
 
365
            return location.startswith(USER_SITE)
 
366
    return True
 
367
 
 
368
 
 
369
def _fake_setuptools():
 
370
    log.warn('Scanning installed packages')
 
371
    try:
 
372
        import pkg_resources
 
373
    except ImportError:
 
374
        # we're cool
 
375
        log.warn('Setuptools or Distribute does not seem to be installed.')
 
376
        return
 
377
    ws = pkg_resources.working_set
 
378
    try:
 
379
        setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools',
 
380
                                  replacement=False))
 
381
    except TypeError:
 
382
        # old distribute API
 
383
        setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
 
384
 
 
385
    if setuptools_dist is None:
 
386
        log.warn('No setuptools distribution found')
 
387
        return
 
388
    # detecting if it was already faked
 
389
    setuptools_location = setuptools_dist.location
 
390
    log.warn('Setuptools installation detected at %s', setuptools_location)
 
391
 
 
392
    # if --root or --preix was provided, and if
 
393
    # setuptools is not located in them, we don't patch it
 
394
    if not _under_prefix(setuptools_location):
 
395
        log.warn('Not patching, --root or --prefix is installing Distribute'
 
396
                 ' in another location')
 
397
        return
 
398
 
 
399
    # let's see if its an egg
 
400
    if not setuptools_location.endswith('.egg'):
 
401
        log.warn('Non-egg installation')
 
402
        res = _remove_flat_installation(setuptools_location)
 
403
        if not res:
 
404
            return
 
405
    else:
 
406
        log.warn('Egg installation')
 
407
        pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
 
408
        if (os.path.exists(pkg_info) and
 
409
            _same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
 
410
            log.warn('Already patched.')
 
411
            return
 
412
        log.warn('Patching...')
 
413
        # let's create a fake egg replacing setuptools one
 
414
        res = _patch_egg_dir(setuptools_location)
 
415
        if not res:
 
416
            return
 
417
    log.warn('Patched done.')
 
418
    _relaunch()
 
419
 
 
420
 
 
421
def _relaunch():
 
422
    log.warn('Relaunching...')
 
423
    # we have to relaunch the process
 
424
    # pip marker to avoid a relaunch bug
 
425
    if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']:
 
426
        sys.argv[0] = 'setup.py'
 
427
    args = [sys.executable] + sys.argv
 
428
    sys.exit(subprocess.call(args))
 
429
 
 
430
 
 
431
def _extractall(self, path=".", members=None):
 
432
    """Extract all members from the archive to the current working
 
433
       directory and set owner, modification time and permissions on
 
434
       directories afterwards. `path' specifies a different directory
 
435
       to extract to. `members' is optional and must be a subset of the
 
436
       list returned by getmembers().
 
437
    """
 
438
    import copy
 
439
    import operator
 
440
    from tarfile import ExtractError
 
441
    directories = []
 
442
 
 
443
    if members is None:
 
444
        members = self
 
445
 
 
446
    for tarinfo in members:
 
447
        if tarinfo.isdir():
 
448
            # Extract directories with a safe mode.
 
449
            directories.append(tarinfo)
 
450
            tarinfo = copy.copy(tarinfo)
 
451
            tarinfo.mode = 448 # decimal for oct 0700
 
452
        self.extract(tarinfo, path)
 
453
 
 
454
    # Reverse sort directories.
 
455
    if sys.version_info < (2, 4):
 
456
        def sorter(dir1, dir2):
 
457
            return cmp(dir1.name, dir2.name)
 
458
        directories.sort(sorter)
 
459
        directories.reverse()
 
460
    else:
 
461
        directories.sort(key=operator.attrgetter('name'), reverse=True)
 
462
 
 
463
    # Set correct owner, mtime and filemode on directories.
 
464
    for tarinfo in directories:
 
465
        dirpath = os.path.join(path, tarinfo.name)
 
466
        try:
 
467
            self.chown(tarinfo, dirpath)
 
468
            self.utime(tarinfo, dirpath)
 
469
            self.chmod(tarinfo, dirpath)
 
470
        except ExtractError:
 
471
            e = sys.exc_info()[1]
 
472
            if self.errorlevel > 1:
 
473
                raise
 
474
            else:
 
475
                self._dbg(1, "tarfile: %s" % e)
 
476
 
 
477
 
 
478
def main(argv, version=DEFAULT_VERSION):
 
479
    """Install or upgrade setuptools and EasyInstall"""
 
480
    tarball = download_setuptools()
 
481
    _install(tarball)
 
482
 
 
483
 
 
484
if __name__ == '__main__':
 
485
    main(sys.argv[1:])