1#!python 2"""Bootstrap distribute installation 3 4If you want to use setuptools in your package's setup.py, just include this 5file 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 10If you want to require a specific version of setuptools, set a download 11mirror, or use an alternate download directory, you can do so by supplying 12the appropriate options to ``use_setuptools()``. 13 14This file can also be run as a script to install or upgrade setuptools. 15""" 16import os 17import sys 18import time 19import fnmatch 20import tempfile 21import tarfile 22from distutils import log 23 24try: 25 from site import USER_SITE 26except ImportError: 27 USER_SITE = None 28 29try: 30 import subprocess 31 32 def _python_cmd(*args): 33 args = (sys.executable,) + args 34 return subprocess.call(args) == 0 35 36except 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 49DEFAULT_VERSION = "0.6.14" 50DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" 51SETUPTOOLS_FAKED_VERSION = "0.6c11" 52 53SETUPTOOLS_PKG_INFO = """\ 54Metadata-Version: 1.0 55Name: setuptools 56Version: %s 57Summary: xxxx 58Home-page: xxx 59Author: xxx 60Author-email: xxx 61License: xxx 62Description: xxx 63""" % SETUPTOOLS_FAKED_VERSION 64 65 66def _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 91def _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 119def _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 131def 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 170def 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 206def _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 230def _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 248def _same_content(path, content): 249 return open(path).read() == content 250 251def _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 257def _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 292def _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 297def _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 326def _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 346def _before_install(): 347 log.warn('Before install bootstrap.') 348 _fake_setuptools() 349 350 351def _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 369def _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 421def _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 431def _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 478def main(argv, version=DEFAULT_VERSION): 479 """Install or upgrade setuptools and EasyInstall""" 480 tarball = download_setuptools() 481 _install(tarball) 482 483 484if __name__ == '__main__': 485 main(sys.argv[1:]) 486