fix backend variable name
[pithos] / snf-pithos-tools / distribute_setup.py
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:])