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