Strip xseg stuff
[archipelago] / tools / archipelago / 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)
35 except ImportError:
36     # will be used for python 2.3
37     def _python_cmd(*args):
38         args = (sys.executable,) + args
39         # quoting arguments if windows
40         if sys.platform == 'win32':
41             def quote(arg):
42                 if ' ' in arg:
43                     return '"%s"' % arg
44                 return arg
45             args = [quote(arg) for arg in args]
46         return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
47
48 DEFAULT_VERSION = "0.6.10"
49 DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
50 SETUPTOOLS_FAKED_VERSION = "0.6c11"
51
52 SETUPTOOLS_PKG_INFO = """\
53 Metadata-Version: 1.0
54 Name: setuptools
55 Version: %s
56 Summary: xxxx
57 Home-page: xxx
58 Author: xxx
59 Author-email: xxx
60 License: xxx
61 Description: xxx
62 """ % SETUPTOOLS_FAKED_VERSION
63
64
65 def _install(tarball):
66     # extracting the tarball
67     tmpdir = tempfile.mkdtemp()
68     log.warn('Extracting in %s', tmpdir)
69     old_wd = os.getcwd()
70     try:
71         os.chdir(tmpdir)
72         tar = tarfile.open(tarball)
73         _extractall(tar)
74         tar.close()
75
76         # going in the directory
77         subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
78         os.chdir(subdir)
79         log.warn('Now working in %s', subdir)
80
81         # installing
82         log.warn('Installing Distribute')
83         if not _python_cmd('setup.py', 'install'):
84             log.warn('Something went wrong during the installation.')
85             log.warn('See the error message above.')
86     finally:
87         os.chdir(old_wd)
88
89
90 def _build_egg(egg, tarball, to_dir):
91     # extracting the tarball
92     tmpdir = tempfile.mkdtemp()
93     log.warn('Extracting in %s', tmpdir)
94     old_wd = os.getcwd()
95     try:
96         os.chdir(tmpdir)
97         tar = tarfile.open(tarball)
98         _extractall(tar)
99         tar.close()
100
101         # going in the directory
102         subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
103         os.chdir(subdir)
104         log.warn('Now working in %s', subdir)
105
106         # building an egg
107         log.warn('Building a Distribute egg in %s', to_dir)
108         _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
109
110     finally:
111         os.chdir(old_wd)
112     # returning the result
113     log.warn(egg)
114     if not os.path.exists(egg):
115         raise IOError('Could not build the egg.')
116
117
118 def _do_download(version, download_base, to_dir, download_delay):
119     egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
120                        % (version, sys.version_info[0], sys.version_info[1]))
121     if not os.path.exists(egg):
122         tarball = download_setuptools(version, download_base,
123                                       to_dir, download_delay)
124         _build_egg(egg, tarball, to_dir)
125     sys.path.insert(0, egg)
126     import setuptools
127     setuptools.bootstrap_install_from = egg
128
129
130 def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
131                    to_dir=os.curdir, download_delay=15, no_fake=True):
132     # making sure we use the absolute path
133     to_dir = os.path.abspath(to_dir)
134     was_imported = 'pkg_resources' in sys.modules or \
135         'setuptools' in sys.modules
136     try:
137         try:
138             import pkg_resources
139             if not hasattr(pkg_resources, '_distribute'):
140                 if not no_fake:
141                     _fake_setuptools()
142                 raise ImportError
143         except ImportError:
144             return _do_download(version, download_base, to_dir, download_delay)
145         try:
146             pkg_resources.require("distribute>="+version)
147             return
148         except pkg_resources.VersionConflict:
149             e = sys.exc_info()[1]
150             if was_imported:
151                 sys.stderr.write(
152                 "The required version of distribute (>=%s) is not available,\n"
153                 "and can't be installed while this script is running. Please\n"
154                 "install a more recent version first, using\n"
155                 "'easy_install -U distribute'."
156                 "\n\n(Currently using %r)\n" % (version, e.args[0]))
157                 sys.exit(2)
158             else:
159                 del pkg_resources, sys.modules['pkg_resources']    # reload ok
160                 return _do_download(version, download_base, to_dir,
161                                     download_delay)
162         except pkg_resources.DistributionNotFound:
163             return _do_download(version, download_base, to_dir,
164                                 download_delay)
165     finally:
166         if not no_fake:
167             _create_fake_setuptools_pkg_info(to_dir)
168
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
207 def _no_sandbox(function):
208     def __no_sandbox(*args, **kw):
209         try:
210             from setuptools.sandbox import DirectorySandbox
211             if not hasattr(DirectorySandbox, '_old'):
212                 def violation(*args):
213                     pass
214                 DirectorySandbox._old = DirectorySandbox._violation
215                 DirectorySandbox._violation = violation
216                 patched = True
217             else:
218                 patched = False
219         except ImportError:
220             patched = False
221
222         try:
223             return function(*args, **kw)
224         finally:
225             if patched:
226                 DirectorySandbox._violation = DirectorySandbox._old
227                 del DirectorySandbox._old
228
229     return __no_sandbox
230
231
232 def _patch_file(path, content):
233     """Will backup the file then patch it"""
234     existing_content = open(path).read()
235     if existing_content == content:
236         # already patched
237         log.warn('Already patched.')
238         return False
239     log.warn('Patching...')
240     _rename_path(path)
241     f = open(path, 'w')
242     try:
243         f.write(content)
244     finally:
245         f.close()
246     return True
247
248 _patch_file = _no_sandbox(_patch_file)
249
250
251 def _same_content(path, content):
252     return open(path).read() == content
253
254
255 def _rename_path(path):
256     new_name = path + '.OLD.%s' % time.time()
257     log.warn('Renaming %s into %s', path, new_name)
258     os.rename(path, new_name)
259     return new_name
260
261
262 def _remove_flat_installation(placeholder):
263     if not os.path.isdir(placeholder):
264         log.warn('Unkown installation at %s', placeholder)
265         return False
266     found = False
267     for file in os.listdir(placeholder):
268         if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
269             found = True
270             break
271     if not found:
272         log.warn('Could not locate setuptools*.egg-info')
273         return
274
275     log.warn('Removing elements out of the way...')
276     pkg_info = os.path.join(placeholder, file)
277     if os.path.isdir(pkg_info):
278         patched = _patch_egg_dir(pkg_info)
279     else:
280         patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
281
282     if not patched:
283         log.warn('%s already patched.', pkg_info)
284         return False
285     # now let's move the files out of the way
286     for element in ('setuptools', 'pkg_resources.py', 'site.py'):
287         element = os.path.join(placeholder, element)
288         if os.path.exists(element):
289             _rename_path(element)
290         else:
291             log.warn('Could not find the %s element of the '
292                      'Setuptools distribution', element)
293     return True
294
295 _remove_flat_installation = _no_sandbox(_remove_flat_installation)
296
297
298 def _after_install(dist):
299     log.warn('After install bootstrap.')
300     placeholder = dist.get_command_obj('install').install_purelib
301     _create_fake_setuptools_pkg_info(placeholder)
302
303
304 def _create_fake_setuptools_pkg_info(placeholder):
305     if not placeholder or not os.path.exists(placeholder):
306         log.warn('Could not find the install location')
307         return
308     pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
309     setuptools_file = 'setuptools-%s-py%s.egg-info' % \
310                       (SETUPTOOLS_FAKED_VERSION, pyver)
311     pkg_info = os.path.join(placeholder, setuptools_file)
312     if os.path.exists(pkg_info):
313         log.warn('%s already exists', pkg_info)
314         return
315
316     log.warn('Creating %s', pkg_info)
317     f = open(pkg_info, 'w')
318     try:
319         f.write(SETUPTOOLS_PKG_INFO)
320     finally:
321         f.close()
322
323     pth_file = os.path.join(placeholder, 'setuptools.pth')
324     log.warn('Creating %s', pth_file)
325     f = open(pth_file, 'w')
326     try:
327         f.write(os.path.join(os.curdir, setuptools_file))
328     finally:
329         f.close()
330
331 _create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info)
332
333
334 def _patch_egg_dir(path):
335     # let's check if it's already patched
336     pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
337     if os.path.exists(pkg_info):
338         if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
339             log.warn('%s already patched.', pkg_info)
340             return False
341     _rename_path(path)
342     os.mkdir(path)
343     os.mkdir(os.path.join(path, 'EGG-INFO'))
344     pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
345     f = open(pkg_info, 'w')
346     try:
347         f.write(SETUPTOOLS_PKG_INFO)
348     finally:
349         f.close()
350     return True
351
352 _patch_egg_dir = _no_sandbox(_patch_egg_dir)
353
354 def _before_install():
355     log.warn('Before install bootstrap.')
356     _fake_setuptools()
357
358
359 def _under_prefix(location):
360     if 'install' not in sys.argv:
361         return True
362     args = sys.argv[sys.argv.index('install')+1:]
363     for index, arg in enumerate(args):
364         for option in ('--root', '--prefix'):
365             if arg.startswith('%s=' % option):
366                 top_dir = arg.split('root=')[-1]
367                 return location.startswith(top_dir)
368             elif arg == option:
369                 if len(args) > index:
370                     top_dir = args[index+1]
371                     return location.startswith(top_dir)
372         if arg == '--user' and USER_SITE is not None:
373             return location.startswith(USER_SITE)
374     return True
375
376
377 def _fake_setuptools():
378     log.warn('Scanning installed packages')
379     try:
380         import pkg_resources
381     except ImportError:
382         # we're cool
383         log.warn('Setuptools or Distribute does not seem to be installed.')
384         return
385     ws = pkg_resources.working_set
386     try:
387         setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools',
388                                   replacement=False))
389     except TypeError:
390         # old distribute API
391         setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
392
393     if setuptools_dist is None:
394         log.warn('No setuptools distribution found')
395         return
396     # detecting if it was already faked
397     setuptools_location = setuptools_dist.location
398     log.warn('Setuptools installation detected at %s', setuptools_location)
399
400     # if --root or --preix was provided, and if
401     # setuptools is not located in them, we don't patch it
402     if not _under_prefix(setuptools_location):
403         log.warn('Not patching, --root or --prefix is installing Distribute'
404                  ' in another location')
405         return
406
407     # let's see if its an egg
408     if not setuptools_location.endswith('.egg'):
409         log.warn('Non-egg installation')
410         res = _remove_flat_installation(setuptools_location)
411         if not res:
412             return
413     else:
414         log.warn('Egg installation')
415         pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
416         if (os.path.exists(pkg_info) and
417             _same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
418             log.warn('Already patched.')
419             return
420         log.warn('Patching...')
421         # let's create a fake egg replacing setuptools one
422         res = _patch_egg_dir(setuptools_location)
423         if not res:
424             return
425     log.warn('Patched done.')
426     _relaunch()
427
428
429 def _relaunch():
430     log.warn('Relaunching...')
431     # we have to relaunch the process
432     # pip marker to avoid a relaunch bug
433     if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']:
434         sys.argv[0] = 'setup.py'
435     args = [sys.executable] + sys.argv
436     sys.exit(subprocess.call(args))
437
438
439 def _extractall(self, path=".", members=None):
440     """Extract all members from the archive to the current working
441        directory and set owner, modification time and permissions on
442        directories afterwards. `path' specifies a different directory
443        to extract to. `members' is optional and must be a subset of the
444        list returned by getmembers().
445     """
446     import copy
447     import operator
448     from tarfile import ExtractError
449     directories = []
450
451     if members is None:
452         members = self
453
454     for tarinfo in members:
455         if tarinfo.isdir():
456             # Extract directories with a safe mode.
457             directories.append(tarinfo)
458             tarinfo = copy.copy(tarinfo)
459             tarinfo.mode = 448 # decimal for oct 0700
460         self.extract(tarinfo, path)
461
462     # Reverse sort directories.
463     if sys.version_info < (2, 4):
464         def sorter(dir1, dir2):
465             return cmp(dir1.name, dir2.name)
466         directories.sort(sorter)
467         directories.reverse()
468     else:
469         directories.sort(key=operator.attrgetter('name'), reverse=True)
470
471     # Set correct owner, mtime and filemode on directories.
472     for tarinfo in directories:
473         dirpath = os.path.join(path, tarinfo.name)
474         try:
475             self.chown(tarinfo, dirpath)
476             self.utime(tarinfo, dirpath)
477             self.chmod(tarinfo, dirpath)
478         except ExtractError:
479             e = sys.exc_info()[1]
480             if self.errorlevel > 1:
481                 raise
482             else:
483                 self._dbg(1, "tarfile: %s" % e)
484
485
486 def main(argv, version=DEFAULT_VERSION):
487     """Install or upgrade setuptools and EasyInstall"""
488     tarball = download_setuptools()
489     _install(tarball)
490
491
492 if __name__ == '__main__':
493     main(sys.argv[1:])