distribute_setup.py
branch2011
changeset 393 f0f04f758f4b
parent 392 566daf404839
child 395 85bbea53523f
child 397 079ada629aaf
equal deleted inserted replaced
392:566daf404839 393:f0f04f758f4b
     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.21"
       
    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:])