#############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Python easy_install API

This module provides a high-level Python API for installing packages.
It doesn't install scripts.  It uses setuptools and requires it to be
installed.
"""

import distutils.errors
import fnmatch
import glob
import logging
import os
import pkg_resources
import py_compile
import re
import setuptools.archive_util
import setuptools.command.setopt
import setuptools.package_index
import shutil
import subprocess
import sys
import tempfile
import warnings
import zc.buildout
import zipimport

_oprp = getattr(os.path, 'realpath', lambda path: path)
def realpath(path):
    return os.path.normcase(os.path.abspath(_oprp(path)))

default_index_url = os.environ.get(
    'buildout-testing-index-url',
    'http://pypi.python.org/simple',
    )

logger = logging.getLogger('zc.buildout.easy_install')

url_match = re.compile('[a-z0-9+.-]+://').match

is_win32 = sys.platform == 'win32'
is_jython = sys.platform.startswith('java')
is_distribute = (
    pkg_resources.Requirement.parse('setuptools').key=='distribute')

BROKEN_DASH_S_WARNING = (
    'Buildout has been asked to exclude or limit site-packages so that '
    'builds can be repeatable when using a system Python.  However, '
    'the chosen Python executable has a broken implementation of -S (see '
    'https://bugs.launchpad.net/virtualenv/+bug/572545 for an example '
    "problem) and this breaks buildout's ability to isolate site-packages.  "
    "If the executable already has a clean site-packages (e.g., "
    "using virtualenv's ``--no-site-packages`` option) you may be getting "
    'equivalent repeatability.  To silence this warning, use the -s argument '
    'to the buildout script.  Alternatively, use a Python executable with a '
    'working -S (such as a standard Python binary).')

if is_jython:
    import java.lang.System
    jython_os_name = (java.lang.System.getProperties()['os.name']).lower()

setuptools_loc = pkg_resources.working_set.find(
    pkg_resources.Requirement.parse('setuptools')
    ).location

# Include buildout and setuptools eggs in paths.  We prevent dupes just to
# keep from duplicating any log messages about them.
buildout_loc = pkg_resources.working_set.find(
    pkg_resources.Requirement.parse('zc.buildout')).location
buildout_and_setuptools_path = [setuptools_loc]
if os.path.normpath(setuptools_loc) != os.path.normpath(buildout_loc):
    buildout_and_setuptools_path.append(buildout_loc)

def _has_broken_dash_S(executable):
    """Detect https://bugs.launchpad.net/virtualenv/+bug/572545 ."""
    # The first attempt here was to simply have the executable attempt to import
    # ConfigParser and return the return code. That worked except for tests on
    # Windows, where the return code was wrong for the fake Python executable
    # generated by the virtualenv.txt test, apparently because setuptools' .exe
    # file does not pass the -script.py's returncode back properly, at least in
    # some circumstances. Therefore...print statements.
    stdout, stderr = subprocess.Popen(
        [executable, '-Sc',
         'try:\n'
         '    import ConfigParser\n'
         'except ImportError:\n'
         '    print 1\n'
         'else:\n'
         '    print 0\n'],
        stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
    return bool(int(stdout.strip()))

def _get_system_paths(executable):
    """Return lists of standard lib and site paths for executable.
    """
    # We want to get a list of the site packages, which is not easy.
    # The canonical way to do this is to use
    # distutils.sysconfig.get_python_lib(), but that only returns a
    # single path, which does not reflect reality for many system
    # Pythons, which have multiple additions.  Instead, we start Python
    # with -S, which does not import site.py and set up the extra paths
    # like site-packages or (Ubuntu/Debian) dist-packages and
    # python-support. We then compare that sys.path with the normal one
    # (minus user packages if this is Python 2.6, because we don't
    # support those (yet?).  The set of the normal one minus the set of
    # the ones in ``python -S`` is the set of packages that are
    # effectively site-packages.
    #
    # The given executable might not be the current executable, so it is
    # appropriate to do another subprocess to figure out what the
    # additional site-package paths are. Moreover, even if this
    # executable *is* the current executable, this code might be run in
    # the context of code that has manipulated the sys.path--for
    # instance, to add local zc.buildout or setuptools eggs.
    def get_sys_path(*args, **kwargs):
        cmd = [executable]
        cmd.extend(args)
        cmd.extend([
            "-c", "import sys, os;"
            "print repr([os.path.normpath(p) for p in sys.path if p])"])
        # Windows needs some (as yet to be determined) part of the real env.
        env = os.environ.copy()
        # We need to make sure that PYTHONPATH, which will often be set
        # to include a custom buildout-generated site.py, is not set, or
        # else we will not get an accurate sys.path for the executable.
        env.pop('PYTHONPATH', None)
        env.update(kwargs)
        _proc = subprocess.Popen(
            cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
        stdout, stderr = _proc.communicate();
        if _proc.returncode:
            raise RuntimeError(
                'error trying to get system packages:\n%s' % (stderr,))
        res = eval(stdout.strip())
        try:
            res.remove('.')
        except ValueError:
            pass
        return res
    stdlib = get_sys_path('-S') # stdlib only
    no_user_paths = get_sys_path(PYTHONNOUSERSITE='x')
    site_paths = [p for p in no_user_paths if p not in stdlib]
    return (stdlib, site_paths)

def _get_version_info(executable):
    cmd = [executable, '-Sc',
           'import sys; print(repr(tuple(x for x in sys.version_info)))']
    _proc = subprocess.Popen(
        cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = _proc.communicate();
    if _proc.returncode:
        raise RuntimeError(
            'error trying to get system packages:\n%s' % (stderr,))
    return eval(stdout.strip())


class IncompatibleVersionError(zc.buildout.UserError):
    """A specified version is incompatible with a given requirement.
    """

_versions = {sys.executable: '%d.%d' % sys.version_info[:2]}
def _get_version(executable):
    try:
        return _versions[executable]
    except KeyError:
        cmd = _safe_arg(executable) + ' -V'
        p = subprocess.Popen(cmd,
                             shell=True,
                             stdin=subprocess.PIPE,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.STDOUT,
                             close_fds=not is_win32)
        i, o = (p.stdin, p.stdout)
        i.close()
        version = o.read().strip()
        o.close()
        pystring, version = version.split()
        assert pystring == 'Python'
        version = re.match('(\d[.]\d)([.].*\d)?$', version).group(1)
        _versions[executable] = version
        return version

FILE_SCHEME = re.compile('file://', re.I).match


class AllowHostsPackageIndex(setuptools.package_index.PackageIndex):
    """Will allow urls that are local to the system.

    No matter what is allow_hosts.
    """
    def url_ok(self, url, fatal=False):
        if FILE_SCHEME(url):
            return True
        return setuptools.package_index.PackageIndex.url_ok(self, url, False)


_indexes = {}
def _get_index(executable, index_url, find_links, allow_hosts=('*',),
               path=None):
    # If path is None, the index will use sys.path.  If you provide an empty
    # path ([]), it will complain uselessly about missing index pages for
    # packages found in the paths that you expect to use.  Therefore, this path
    # is always the same as the _env path in the Installer.
    key = executable, index_url, tuple(find_links)
    index = _indexes.get(key)
    if index is not None:
        return index

    if index_url is None:
        index_url = default_index_url
    index = AllowHostsPackageIndex(
        index_url, hosts=allow_hosts, search_path=path,
        python=_get_version(executable)
        )

    if find_links:
        index.add_find_links(find_links)

    _indexes[key] = index
    return index

clear_index_cache = _indexes.clear

if is_win32:
    # work around spawn lamosity on windows
    # XXX need safe quoting (see the subprocess.list2cmdline) and test
    def _safe_arg(arg):
        return '"%s"' % arg
else:
    _safe_arg = str

# The following string is used to run easy_install in
# Installer._call_easy_install.  It is usually started with python -S
# (that is, don't import site at start).  That flag, and all of the code
# in this snippet above the last two lines, exist to work around a
# relatively rare problem.  If
#
# - your buildout configuration is trying to install a package that is within
#   a namespace package, and
#
# - you use a Python that has a different version of this package
#   installed in in its site-packages using
#   --single-version-externally-managed (that is, using the mechanism
#   sometimes used by system packagers:
#   http://peak.telecommunity.com/DevCenter/setuptools#install-command ), and
#
# - the new package tries to do sys.path tricks in the setup.py to get a
#   __version__,
#
# then the older package will be loaded first, making the setup version
# the wrong number. While very arguably packages simply shouldn't do
# the sys.path tricks, some do, and we don't want buildout to fall over
# when they do.
#
# The namespace packages installed in site-packages with
# --single-version-externally-managed use a mechanism that cause them to
# be processed when site.py is imported  (see
# http://mail.python.org/pipermail/distutils-sig/2009-May/011730.html
# for another description of the problem).  Simply starting Python with
# -S addresses the problem in Python 2.4 and 2.5, but Python 2.6's
# distutils imports a value from the site module, so we unfortunately
# have to do more drastic surgery in the _easy_install_preface code below.
#
# Here's an example of the .pth files created by setuptools when using that
# flag:
#
# import sys,new,os;
# p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('<NAMESPACE>',));
# ie = os.path.exists(os.path.join(p,'__init__.py'));
# m = not ie and sys.modules.setdefault('<NAMESPACE>',new.module('<NAMESPACE>'));
# mp = (m or []) and m.__dict__.setdefault('__path__',[]);
# (p not in mp) and mp.append(p)
#
# The code, below, then, runs under -S, indicating that site.py should
# not be loaded initially.  It gets the initial sys.path under these
# circumstances, and then imports site (because Python 2.6's distutils
# will want it, as mentioned above). It then reinstates the old sys.path
# value. Then it removes namespace packages (created by the setuptools
# code above) from sys.modules.  It identifies namespace packages by
# iterating over every loaded module.  It first looks if there is a
# __path__, so it is a package; and then it sees if that __path__ does
# not have an __init__.py.  (Note that PEP 382,
# http://www.python.org/dev/peps/pep-0382, makes it possible to have a
# namespace package that has an __init__.py, but also should make it
# unnecessary for site.py to preprocess these packages, so it should be
# fine, as far as can be guessed as of this writing.)  Finally, it
# imports easy_install and runs it.
_easy_install_preface = '''\
import sys,os;\
p = sys.path[:];\
import site;\
sys.path[:] = p;\
[sys.modules.pop(k) for k, v in sys.modules.items()\
 if hasattr(v, '__path__') and len(v.__path__)==1 and\
 not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))];'''
_easy_install_cmd = (
    'from setuptools.command.easy_install import main;main()')


class Installer:

    _versions = {}
    _download_cache = None
    _install_from_cache = False
    _prefer_final = True
    _use_dependency_links = True
    _allow_picked_versions = True
    _always_unzip = False
    _include_site_packages = True
    _allowed_eggs_from_site_packages = ('*',)

    def __init__(self,
                 dest=None,
                 links=(),
                 index=None,
                 executable=sys.executable,
                 always_unzip=None,
                 path=None,
                 newest=True,
                 versions=None,
                 use_dependency_links=None,
                 allow_hosts=('*',),
                 include_site_packages=None,
                 allowed_eggs_from_site_packages=None,
                 prefer_final=None,
                 ):
        self._dest = dest
        self._allow_hosts = allow_hosts

        if self._install_from_cache:
            if not self._download_cache:
                raise ValueError("install_from_cache set to true with no"
                                 " download cache")
            links = ()
            index = 'file://' + self._download_cache

        if use_dependency_links is not None:
            self._use_dependency_links = use_dependency_links
        if prefer_final is not None:
            self._prefer_final = prefer_final
        self._links = links = list(_fix_file_links(links))
        if self._download_cache and (self._download_cache not in links):
            links.insert(0, self._download_cache)

        self._index_url = index
        self._executable = executable
        self._has_broken_dash_S = _has_broken_dash_S(self._executable)
        if always_unzip is not None:
            self._always_unzip = always_unzip
        path = (path and path[:] or [])
        if include_site_packages is not None:
            self._include_site_packages = include_site_packages
        if allowed_eggs_from_site_packages is not None:
            self._allowed_eggs_from_site_packages = tuple(
                allowed_eggs_from_site_packages)
        if self._has_broken_dash_S:
            if (not self._include_site_packages or
                self._allowed_eggs_from_site_packages != ('*',)):
                # We can't do this if the executable has a broken -S.
                warnings.warn(BROKEN_DASH_S_WARNING)
                self._include_site_packages = True
                self._allowed_eggs_from_site_packages = ('*',)
            self._easy_install_cmd = _easy_install_cmd
        else:
            self._easy_install_cmd = _easy_install_preface + _easy_install_cmd
        self._easy_install_cmd = _safe_arg(self._easy_install_cmd)
        stdlib, self._site_packages = _get_system_paths(executable)
        version_info = _get_version_info(executable)
        if version_info == sys.version_info:
            # Maybe we can add the buildout and setuptools path.  If we
            # are including site_packages, we only have to include the extra
            # bits here, so we don't duplicate.  On the other hand, if we
            # are not including site_packages, we only want to include the
            # parts that are not in site_packages, so the code is the same.
            path.extend(
                set(buildout_and_setuptools_path).difference(
                    self._site_packages))
        if self._include_site_packages:
            path.extend(self._site_packages)
        if dest is not None and dest not in path:
            path.insert(0, dest)
        self._path = path
        if self._dest is None:
            newest = False
        self._newest = newest
        self._env = pkg_resources.Environment(path,
                                              python=_get_version(executable))
        self._index = _get_index(executable, index, links, self._allow_hosts,
                                 self._path)

        if versions is not None:
            self._versions = versions

    _allowed_eggs_from_site_packages_regex = None
    def allow_site_package_egg(self, name):
        if (not self._include_site_packages or
            not self._allowed_eggs_from_site_packages):
            # If the answer is a blanket "no," perform a shortcut.
            return False
        if self._allowed_eggs_from_site_packages_regex is None:
            pattern = '(%s)' % (
                '|'.join(
                    fnmatch.translate(name)
                    for name in self._allowed_eggs_from_site_packages),
                )
            self._allowed_eggs_from_site_packages_regex = re.compile(pattern)
        return bool(self._allowed_eggs_from_site_packages_regex.match(name))

    def _satisfied(self, req, source=None):
        # We get all distributions that match the given requirement.  If we are
        # not supposed to include site-packages for the given egg, we also
        # filter those out. Even if include_site_packages is False and so we
        # have excluded site packages from the _env's paths (see
        # Installer.__init__), we need to do the filtering here because an
        # .egg-link, such as one for setuptools or zc.buildout installed by
        # zc.buildout.buildout.Buildout.bootstrap, can indirectly include a
        # path in our _site_packages.
        dists = [dist for dist in self._env[req.project_name] if (
                    dist in req and (
                        dist.location not in self._site_packages or
                        self.allow_site_package_egg(dist.project_name))
                    )
                ]
        if not dists:
            logger.debug('We have no distributions for %s that satisfies %r.',
                         req.project_name, str(req))

            return None, self._obtain(req, source)

        # Note that dists are sorted from best to worst, as promised by
        # env.__getitem__

        for dist in dists:
            if (dist.precedence == pkg_resources.DEVELOP_DIST and
                dist.location not in self._site_packages):
                # System eggs are sometimes installed as develop eggs.
                # Those are not the kind of develop eggs we are looking for
                # here: we want ones that the buildout itself has locally as
                # develop eggs.
                logger.debug('We have a develop egg: %s', dist)
                return dist, None

        # Special common case, we have a specification for a single version:
        specs = req.specs
        if len(specs) == 1 and specs[0][0] == '==':
            logger.debug('We have the distribution that satisfies %r.',
                         str(req))
            return dists[0], None

        if self._prefer_final:
            fdists = [dist for dist in dists
                      if _final_version(dist.parsed_version)
                      ]
            if fdists:
                # There are final dists, so only use those
                dists = fdists

        if not self._newest:
            # We don't need the newest, so we'll use the newest one we
            # find, which is the first returned by
            # Environment.__getitem__.
            return dists[0], None

        best_we_have = dists[0] # Because dists are sorted from best to worst

        # We have some installed distros.  There might, theoretically, be
        # newer ones.  Let's find out which ones are available and see if
        # any are newer.  We only do this if we're willing to install
        # something, which is only true if dest is not None:

        if self._dest is not None:
            best_available = self._obtain(req, source)
        else:
            best_available = None

        if best_available is None:
            # That's a bit odd.  There aren't any distros available.
            # We should use the best one we have that meets the requirement.
            logger.debug(
                'There are no distros available that meet %r.\n'
                'Using our best, %s.',
                str(req), best_available)
            return best_we_have, None

        if self._prefer_final:
            if _final_version(best_available.parsed_version):
                if _final_version(best_we_have.parsed_version):
                    if (best_we_have.parsed_version
                        <
                        best_available.parsed_version
                        ):
                        return None, best_available
                else:
                    return None, best_available
            else:
                if (not _final_version(best_we_have.parsed_version)
                    and
                    (best_we_have.parsed_version
                     <
                     best_available.parsed_version
                     )
                    ):
                    return None, best_available
        else:
            if (best_we_have.parsed_version
                <
                best_available.parsed_version
                ):
                return None, best_available

        logger.debug(
            'We have the best distribution that satisfies %r.',
            str(req))
        return best_we_have, None

    def _load_dist(self, dist):
        dists = pkg_resources.Environment(
            dist.location,
            python=_get_version(self._executable),
            )[dist.project_name]
        assert len(dists) == 1
        return dists[0]

    def _call_easy_install(self, spec, ws, dest, dist):

        tmp = tempfile.mkdtemp(dir=dest)
        try:
            path = setuptools_loc

            args = ('-c', self._easy_install_cmd, '-mUNxd', _safe_arg(tmp))
            if not self._has_broken_dash_S:
                args = ('-S',) + args
            if self._always_unzip:
                args += ('-Z', )
            level = logger.getEffectiveLevel()
            if level > 0:
                args += ('-q', )
            elif level < 0:
                args += ('-v', )

            args += (_safe_arg(spec), )

            if level <= logging.DEBUG:
                logger.debug('Running easy_install:\n%s "%s"\npath=%s\n',
                             self._executable, '" "'.join(args), path)

            if is_jython:
                extra_env = dict(os.environ, PYTHONPATH=path)
            else:
                args += (dict(os.environ, PYTHONPATH=path), )

            sys.stdout.flush() # We want any pending output first

            if is_jython:
                exit_code = subprocess.Popen(
                [_safe_arg(self._executable)] + list(args),
                env=extra_env).wait()
            else:
                exit_code = os.spawnle(
                    os.P_WAIT, self._executable, _safe_arg (self._executable),
                    *args)

            dists = []
            env = pkg_resources.Environment(
                [tmp],
                python=_get_version(self._executable),
                )
            for project in env:
                dists.extend(env[project])

            if exit_code:
                logger.error(
                    "An error occurred when trying to install %s. "
                    "Look above this message for any errors that "
                    "were output by easy_install.",
                    dist)

            if not dists:
                raise zc.buildout.UserError("Couldn't install: %s" % dist)

            if len(dists) > 1:
                logger.warn("Installing %s\n"
                            "caused multiple distributions to be installed:\n"
                            "%s\n",
                            dist, '\n'.join(map(str, dists)))
            else:
                d = dists[0]
                if d.project_name != dist.project_name:
                    logger.warn("Installing %s\n"
                                "Caused installation of a distribution:\n"
                                "%s\n"
                                "with a different project name.",
                                dist, d)
                if d.version != dist.version:
                    logger.warn("Installing %s\n"
                                "Caused installation of a distribution:\n"
                                "%s\n"
                                "with a different version.",
                                dist, d)

            result = []
            for d in dists:
                newloc = os.path.join(dest, os.path.basename(d.location))
                if os.path.exists(newloc):
                    if os.path.isdir(newloc):
                        shutil.rmtree(newloc)
                    else:
                        os.remove(newloc)
                os.rename(d.location, newloc)

                [d] = pkg_resources.Environment(
                    [newloc],
                    python=_get_version(self._executable),
                    )[d.project_name]

                result.append(d)

            return result

        finally:
            shutil.rmtree(tmp)

    def _obtain(self, requirement, source=None):
        # initialize out index for this project:
        index = self._index

        if index.obtain(requirement) is None:
            # Nothing is available.
            return None

        # Filter the available dists for the requirement and source flag.  If
        # we are not supposed to include site-packages for the given egg, we
        # also filter those out. Even if include_site_packages is False and so
        # we have excluded site packages from the _env's paths (see
        # Installer.__init__), we need to do the filtering here because an
        # .egg-link, such as one for setuptools or zc.buildout installed by
        # zc.buildout.buildout.Buildout.bootstrap, can indirectly include a
        # path in our _site_packages.
        dists = [dist for dist in index[requirement.project_name] if (
                    dist in requirement and (
                        dist.location not in self._site_packages or
                        self.allow_site_package_egg(dist.project_name))
                    and (
                        (not source) or
                        (dist.precedence == pkg_resources.SOURCE_DIST))
                    )
                 ]

        # If we prefer final dists, filter for final and use the
        # result if it is non empty.
        if self._prefer_final:
            fdists = [dist for dist in dists
                      if _final_version(dist.parsed_version)
                      ]
            if fdists:
                # There are final dists, so only use those
                dists = fdists

        # Now find the best one:
        best = []
        bestv = ()
        for dist in dists:
            distv = dist.parsed_version
            if distv > bestv:
                best = [dist]
                bestv = distv
            elif distv == bestv:
                best.append(dist)

        if not best:
            return None

        if len(best) == 1:
            return best[0]

        if self._download_cache:
            for dist in best:
                if (realpath(os.path.dirname(dist.location))
                    ==
                    self._download_cache
                    ):
                    return dist

        best.sort()
        return best[-1]

    def _fetch(self, dist, tmp, download_cache):
        if (download_cache
            and (realpath(os.path.dirname(dist.location)) == download_cache)
            ):
            return dist

        new_location = self._index.download(dist.location, tmp)
        if (download_cache
            and (realpath(new_location) == realpath(dist.location))
            and os.path.isfile(new_location)
            ):
            # setuptools avoids making extra copies, but we want to copy
            # to the download cache
            shutil.copy2(new_location, tmp)
            new_location = os.path.join(tmp, os.path.basename(new_location))

        return dist.clone(location=new_location)

    def _get_dist(self, requirement, ws, always_unzip):

        __doing__ = 'Getting distribution for %r.', str(requirement)

        # Maybe an existing dist is already the best dist that satisfies the
        # requirement
        dist, avail = self._satisfied(requirement)

        if dist is None:
            if self._dest is not None:
                logger.info(*__doing__)

            # Retrieve the dist:
            if avail is None:
                raise MissingDistribution(requirement, ws)

            # We may overwrite distributions, so clear importer
            # cache.
            sys.path_importer_cache.clear()

            tmp = self._download_cache
            if tmp is None:
                tmp = tempfile.mkdtemp('get_dist')

            try:
                dist = self._fetch(avail, tmp, self._download_cache)

                if dist is None:
                    raise zc.buildout.UserError(
                        "Couldn't download distribution %s." % avail)

                if dist.precedence == pkg_resources.EGG_DIST:
                    # It's already an egg, just fetch it into the dest

                    newloc = os.path.join(
                        self._dest, os.path.basename(dist.location))

                    if os.path.isdir(dist.location):
                        # we got a directory. It must have been
                        # obtained locally.  Just copy it.
                        shutil.copytree(dist.location, newloc)
                    else:

                        if self._always_unzip:
                            should_unzip = True
                        else:
                            metadata = pkg_resources.EggMetadata(
                                zipimport.zipimporter(dist.location)
                                )
                            should_unzip = (
                                metadata.has_metadata('not-zip-safe')
                                or
                                not metadata.has_metadata('zip-safe')
                                )

                        if should_unzip:
                            setuptools.archive_util.unpack_archive(
                                dist.location, newloc)
                        else:
                            shutil.copyfile(dist.location, newloc)

                    redo_pyc(newloc)

                    # Getting the dist from the environment causes the
                    # distribution meta data to be read.  Cloning isn't
                    # good enough.
                    dists = pkg_resources.Environment(
                        [newloc],
                        python=_get_version(self._executable),
                        )[dist.project_name]
                else:
                    # It's some other kind of dist.  We'll let easy_install
                    # deal with it:
                    dists = self._call_easy_install(
                        dist.location, ws, self._dest, dist)
                    for dist in dists:
                        redo_pyc(dist.location)

            finally:
                if tmp != self._download_cache:
                    shutil.rmtree(tmp)

            self._env.scan([self._dest])
            dist = self._env.best_match(requirement, ws)
            logger.info("Got %s.", dist)

        else:
            dists = [dist]

        for dist in dists:
            if (dist.has_metadata('dependency_links.txt')
                and not self._install_from_cache
                and self._use_dependency_links
                ):
                for link in dist.get_metadata_lines('dependency_links.txt'):
                    link = link.strip()
                    if link not in self._links:
                        logger.debug('Adding find link %r from %s', link, dist)
                        self._links.append(link)
                        self._index = _get_index(self._executable,
                                                 self._index_url, self._links,
                                                 self._allow_hosts, self._path)

        for dist in dists:
            # Check whether we picked a version and, if we did, report it:
            if not (
                dist.precedence == pkg_resources.DEVELOP_DIST
                or
                (len(requirement.specs) == 1
                 and
                 requirement.specs[0][0] == '==')
                ):
                logger.debug('Picked: %s = %s',
                             dist.project_name, dist.version)
                if not self._allow_picked_versions:
                    raise zc.buildout.UserError(
                        'Picked: %s = %s' % (dist.project_name, dist.version)
                        )

        return dists

    def _maybe_add_setuptools(self, ws, dist):
        if dist.has_metadata('namespace_packages.txt'):
            for r in dist.requires():
                if r.project_name in ('setuptools', 'distribute'):
                    break
            else:
                # We have a namespace package but no requirement for setuptools
                if dist.precedence == pkg_resources.DEVELOP_DIST:
                    logger.warn(
                        "Develop distribution: %s\n"
                        "uses namespace packages but the distribution "
                        "does not require setuptools.",
                        dist)
                requirement = self._constrain(
                    pkg_resources.Requirement.parse('setuptools')
                    )
                if ws.find(requirement) is None:
                    for dist in self._get_dist(requirement, ws, False):
                        ws.add(dist)


    def _constrain(self, requirement):
        if is_distribute and requirement.key == 'setuptools':
            requirement = pkg_resources.Requirement.parse('distribute')
        version = self._versions.get(requirement.project_name)
        if version:
            if version not in requirement:
                logger.error("The version, %s, is not consistent with the "
                             "requirement, %r.", version, str(requirement))
                raise IncompatibleVersionError("Bad version", version)

            requirement = pkg_resources.Requirement.parse(
                "%s[%s] ==%s" % (requirement.project_name,
                               ','.join(requirement.extras),
                               version))

        return requirement

    def install(self, specs, working_set=None):

        logger.debug('Installing %s.', repr(specs)[1:-1])

        path = self._path
        destination = self._dest
        if destination is not None and destination not in path:
            path.insert(0, destination)

        requirements = [self._constrain(pkg_resources.Requirement.parse(spec))
                        for spec in specs]



        if working_set is None:
            ws = pkg_resources.WorkingSet([])
        else:
            ws = working_set

        for requirement in requirements:
            for dist in self._get_dist(requirement, ws, self._always_unzip):
                ws.add(dist)
                self._maybe_add_setuptools(ws, dist)

        # OK, we have the requested distributions and they're in the working
        # set, but they may have unmet requirements.  We'll resolve these
        # requirements. This is code modified from
        # pkg_resources.WorkingSet.resolve.  We can't reuse that code directly
        # because we have to constrain our requirements (see
        # versions_section_ignored_for_dependency_in_favor_of_site_packages in
        # zc.buildout.tests).
        requirements.reverse() # Set up the stack.
        processed = {}  # This is a set of processed requirements.
        best = {}  # This is a mapping of key -> dist.
        # Note that we don't use the existing environment, because we want
        # to look for new eggs unless what we have is the best that
        # matches the requirement.
        env = pkg_resources.Environment(ws.entries)
        while requirements:
            # Process dependencies breadth-first.
            req = self._constrain(requirements.pop(0))
            if req in processed:
                # Ignore cyclic or redundant dependencies.
                continue
            dist = best.get(req.key)
            if dist is None:
                # Find the best distribution and add it to the map.
                dist = ws.by_key.get(req.key)
                if dist is None:
                    try:
                        dist = best[req.key] = env.best_match(req, ws)
                    except pkg_resources.VersionConflict, err:
                        raise VersionConflict(err, ws)
                    if dist is None or (
                        dist.location in self._site_packages and not
                        self.allow_site_package_egg(dist.project_name)):
                        # If we didn't find a distribution in the
                        # environment, or what we found is from site
                        # packages and not allowed to be there, try
                        # again.
                        if destination:
                            logger.debug('Getting required %r', str(req))
                        else:
                            logger.debug('Adding required %r', str(req))
                        _log_requirement(ws, req)
                        for dist in self._get_dist(req,
                                                   ws, self._always_unzip):
                            ws.add(dist)
                            self._maybe_add_setuptools(ws, dist)
            if dist not in req:
                # Oops, the "best" so far conflicts with a dependency.
                raise VersionConflict(
                    pkg_resources.VersionConflict(dist, req), ws)
            requirements.extend(dist.requires(req.extras)[::-1])
            processed[req] = True
            if dist.location in self._site_packages:
                logger.debug('Egg from site-packages: %s', dist)
        return ws

    def build(self, spec, build_ext):

        requirement = self._constrain(pkg_resources.Requirement.parse(spec))

        dist, avail = self._satisfied(requirement, 1)
        if dist is not None:
            return [dist.location]

        # Retrieve the dist:
        if avail is None:
            raise zc.buildout.UserError(
                "Couldn't find a source distribution for %r."
                % str(requirement))

        logger.debug('Building %r', spec)

        tmp = self._download_cache
        if tmp is None:
            tmp = tempfile.mkdtemp('get_dist')

        try:
            dist = self._fetch(avail, tmp, self._download_cache)

            build_tmp = tempfile.mkdtemp('build')
            try:
                setuptools.archive_util.unpack_archive(dist.location,
                                                       build_tmp)
                if os.path.exists(os.path.join(build_tmp, 'setup.py')):
                    base = build_tmp
                else:
                    setups = glob.glob(
                        os.path.join(build_tmp, '*', 'setup.py'))
                    if not setups:
                        raise distutils.errors.DistutilsError(
                            "Couldn't find a setup script in %s"
                            % os.path.basename(dist.location)
                            )
                    if len(setups) > 1:
                        raise distutils.errors.DistutilsError(
                            "Multiple setup scripts in %s"
                            % os.path.basename(dist.location)
                            )
                    base = os.path.dirname(setups[0])

                setup_cfg = os.path.join(base, 'setup.cfg')
                if not os.path.exists(setup_cfg):
                    f = open(setup_cfg, 'w')
                    f.close()
                setuptools.command.setopt.edit_config(
                    setup_cfg, dict(build_ext=build_ext))

                dists = self._call_easy_install(
                    base, pkg_resources.WorkingSet(),
                    self._dest, dist)

                for dist in dists:
                    redo_pyc(dist.location)

                return [dist.location for dist in dists]
            finally:
                shutil.rmtree(build_tmp)

        finally:
            if tmp != self._download_cache:
                shutil.rmtree(tmp)

def default_versions(versions=None):
    old = Installer._versions
    if versions is not None:
        Installer._versions = versions
    return old

def download_cache(path=-1):
    old = Installer._download_cache
    if path != -1:
        if path:
            path = realpath(path)
        Installer._download_cache = path
    return old

def install_from_cache(setting=None):
    old = Installer._install_from_cache
    if setting is not None:
        Installer._install_from_cache = bool(setting)
    return old

def prefer_final(setting=None):
    old = Installer._prefer_final
    if setting is not None:
        Installer._prefer_final = bool(setting)
    return old

def include_site_packages(setting=None):
    old = Installer._include_site_packages
    if setting is not None:
        Installer._include_site_packages = bool(setting)
    return old

def allowed_eggs_from_site_packages(setting=None):
    old = Installer._allowed_eggs_from_site_packages
    if setting is not None:
        Installer._allowed_eggs_from_site_packages = tuple(setting)
    return old

def use_dependency_links(setting=None):
    old = Installer._use_dependency_links
    if setting is not None:
        Installer._use_dependency_links = bool(setting)
    return old

def allow_picked_versions(setting=None):
    old = Installer._allow_picked_versions
    if setting is not None:
        Installer._allow_picked_versions = bool(setting)
    return old

def always_unzip(setting=None):
    old = Installer._always_unzip
    if setting is not None:
        Installer._always_unzip = bool(setting)
    return old

def install(specs, dest,
            links=(), index=None,
            executable=sys.executable, always_unzip=None,
            path=None, working_set=None, newest=True, versions=None,
            use_dependency_links=None, allow_hosts=('*',),
            include_site_packages=None, allowed_eggs_from_site_packages=None,
            prefer_final=None):
    installer = Installer(
        dest, links, index, executable, always_unzip, path, newest,
        versions, use_dependency_links, allow_hosts=allow_hosts,
        include_site_packages=include_site_packages,
        allowed_eggs_from_site_packages=allowed_eggs_from_site_packages,
        prefer_final=prefer_final)
    return installer.install(specs, working_set)


def build(spec, dest, build_ext,
          links=(), index=None,
          executable=sys.executable,
          path=None, newest=True, versions=None, allow_hosts=('*',),
          include_site_packages=None, allowed_eggs_from_site_packages=None):
    installer = Installer(
        dest, links, index, executable, True, path, newest, versions,
        allow_hosts=allow_hosts,
        include_site_packages=include_site_packages,
        allowed_eggs_from_site_packages=allowed_eggs_from_site_packages)
    return installer.build(spec, build_ext)



def _rm(*paths):
    for path in paths:
        if os.path.isdir(path):
            shutil.rmtree(path)
        elif os.path.exists(path):
            os.remove(path)

def _copyeggs(src, dest, suffix, undo):
    result = []
    undo.append(lambda : _rm(*result))
    for name in os.listdir(src):
        if name.endswith(suffix):
            new = os.path.join(dest, name)
            _rm(new)
            os.rename(os.path.join(src, name), new)
            result.append(new)

    assert len(result) == 1, str(result)
    undo.pop()

    return result[0]

def develop(setup, dest,
            build_ext=None,
            executable=sys.executable):

    if os.path.isdir(setup):
        directory = setup
        setup = os.path.join(directory, 'setup.py')
    else:
        directory = os.path.dirname(setup)

    undo = []
    try:
        if build_ext:
            setup_cfg = os.path.join(directory, 'setup.cfg')
            if os.path.exists(setup_cfg):
                os.rename(setup_cfg, setup_cfg+'-develop-aside')
                def restore_old_setup():
                    if os.path.exists(setup_cfg):
                        os.remove(setup_cfg)
                    os.rename(setup_cfg+'-develop-aside', setup_cfg)
                undo.append(restore_old_setup)
            else:
                open(setup_cfg, 'w')
                undo.append(lambda: os.remove(setup_cfg))
            setuptools.command.setopt.edit_config(
                setup_cfg, dict(build_ext=build_ext))

        fd, tsetup = tempfile.mkstemp()
        undo.append(lambda: os.remove(tsetup))
        undo.append(lambda: os.close(fd))

        os.write(fd, runsetup_template % dict(
            setuptools=setuptools_loc,
            setupdir=directory,
            setup=setup,
            __file__ = setup,
            ))

        tmp3 = tempfile.mkdtemp('build', dir=dest)
        undo.append(lambda : shutil.rmtree(tmp3))

        args = [
            zc.buildout.easy_install._safe_arg(tsetup),
            '-q', 'develop', '-mxN',
            '-d', _safe_arg(tmp3),
            ]

        log_level = logger.getEffectiveLevel()
        if log_level <= 0:
            if log_level == 0:
                del args[1]
            else:
                args[1] == '-v'
        if log_level < logging.DEBUG:
            logger.debug("in: %r\n%s", directory, ' '.join(args))

        if is_jython:
            assert subprocess.Popen([_safe_arg(executable)] + args).wait() == 0
        else:
            assert os.spawnl(os.P_WAIT, executable, _safe_arg(executable),
                             *args) == 0

        return _copyeggs(tmp3, dest, '.egg-link', undo)

    finally:
        undo.reverse()
        [f() for f in undo]

def working_set(specs, executable, path, include_site_packages=None,
                allowed_eggs_from_site_packages=None, prefer_final=None):
    return install(
        specs, None, executable=executable, path=path,
        include_site_packages=include_site_packages,
        allowed_eggs_from_site_packages=allowed_eggs_from_site_packages,
        prefer_final=prefer_final)

############################################################################
# Script generation functions

def scripts(
    reqs, working_set, executable, dest,
    scripts=None,
    extra_paths=(),
    arguments='',
    interpreter=None,
    initialization='',
    relative_paths=False,
    ):
    """Generate scripts and/or an interpreter.

    See sitepackage_safe_scripts for a version that can be used with a Python
    that has code installed in site-packages. It has more options and a
    different approach.
    """
    path = _get_path(working_set, extra_paths)
    if initialization:
        initialization = '\n'+initialization+'\n'
    generated = _generate_scripts(
        reqs, working_set, dest, path, scripts, relative_paths,
        initialization, executable, arguments)
    if interpreter:
        sname = os.path.join(dest, interpreter)
        spath, rpsetup = _relative_path_and_setup(sname, path, relative_paths)
        generated.extend(
            _pyscript(spath, sname, executable, rpsetup))
    return generated

# We need to give an alternate name to the ``scripts`` function so that it
# can be referenced within sitepackage_safe_scripts, which uses ``scripts``
# as an argument name.
_original_scripts_function = scripts

def sitepackage_safe_scripts(
    dest, working_set, executable, site_py_dest,
    reqs=(),
    scripts=None,
    interpreter=None,
    extra_paths=(),
    initialization='',
    include_site_packages=False,
    exec_sitecustomize=False,
    relative_paths=False,
    script_arguments='',
    script_initialization='',
    ):
    """Generate scripts and/or an interpreter from a system Python.

    This accomplishes the same job as the ``scripts`` function, above,
    but it does so in an alternative way that allows safely including
    Python site packages, if desired, and  choosing to execute the Python's
    sitecustomize.
    """
    if _has_broken_dash_S(executable):
        if not include_site_packages:
            warnings.warn(BROKEN_DASH_S_WARNING)
        return _original_scripts_function(
            reqs, working_set, executable, dest, scripts, extra_paths,
            script_arguments, interpreter, initialization, relative_paths)
    generated = []
    generated.append(_generate_sitecustomize(
        site_py_dest, executable, initialization, exec_sitecustomize))
    generated.append(_generate_site(
        site_py_dest, working_set, executable, extra_paths,
        include_site_packages, relative_paths))
    script_initialization = _script_initialization_template % dict(
        site_py_dest=site_py_dest,
        script_initialization=script_initialization)
    if not script_initialization.endswith('\n'):
        script_initialization += '\n'
    generated.extend(_generate_scripts(
        reqs, working_set, dest, [site_py_dest], scripts, relative_paths,
        script_initialization, executable, script_arguments, block_site=True))
    if interpreter:
        generated.extend(_generate_interpreter(
            interpreter, dest, executable, site_py_dest, relative_paths))
    return generated

_script_initialization_template = '''
import os
path = sys.path[0]
if os.environ.get('PYTHONPATH'):
    path = os.pathsep.join([path, os.environ['PYTHONPATH']])
os.environ['BUILDOUT_ORIGINAL_PYTHONPATH'] = os.environ.get('PYTHONPATH', '')
os.environ['PYTHONPATH'] = path
import site # imports custom buildout-generated site.py
%(script_initialization)s'''

# Utilities for the script generation functions.

# These are shared by both ``scripts`` and ``sitepackage_safe_scripts``

def _get_path(working_set, extra_paths=()):
    """Given working set and extra paths, return a normalized path list."""
    path = [dist.location for dist in working_set]
    path.extend(extra_paths)
    return map(realpath, path)

def _generate_scripts(reqs, working_set, dest, path, scripts, relative_paths,
                      initialization, executable, arguments,
                      block_site=False):
    """Generate scripts for the given requirements.

    - reqs is an iterable of string requirements or entry points.
    - The requirements must be findable in the given working_set.
    - The dest is the directory in which the scripts should be created.
    - The path is a list of paths that should be added to sys.path.
    - The scripts is an optional dictionary.  If included, the keys should be
      the names of the scripts that should be created, as identified in their
      entry points; and the values should be the name the script should
      actually be created with.
    - relative_paths, if given, should be the path that is the root of the
      buildout (the common path that should be the root of what is relative).
    """
    if isinstance(reqs, str):
        raise TypeError('Expected iterable of requirements or entry points,'
                        ' got string.')
    generated = []
    entry_points = []
    for req in reqs:
        if isinstance(req, str):
            req = pkg_resources.Requirement.parse(req)
            dist = working_set.find(req)
            for name in pkg_resources.get_entry_map(dist, 'console_scripts'):
                entry_point = dist.get_entry_info('console_scripts', name)
                entry_points.append(
                    (name, entry_point.module_name,
                     '.'.join(entry_point.attrs))
                    )
        else:
            entry_points.append(req)
    for name, module_name, attrs in entry_points:
        if scripts is not None:
            sname = scripts.get(name)
            if sname is None:
                continue
        else:
            sname = name
        sname = os.path.join(dest, sname)
        spath, rpsetup = _relative_path_and_setup(sname, path, relative_paths)
        generated.extend(
            _script(sname, executable, rpsetup, spath, initialization,
                    module_name, attrs, arguments, block_site=block_site))
    return generated

def _relative_path_and_setup(sname, path,
                             relative_paths=False, indent_level=1,
                             omit_os_import=False):
    """Return a string of code of paths and of setup if appropriate.

    - sname is the full path to the script name to be created.
    - path is the list of paths to be added to sys.path.
    - relative_paths, if given, should be the path that is the root of the
      buildout (the common path that should be the root of what is relative).
    - indent_level is the number of four-space indents that the path should
      insert before each element of the path.
    """
    if relative_paths:
        relative_paths = os.path.normcase(relative_paths)
        sname = os.path.normcase(os.path.abspath(sname))
        spath = _format_paths(
            [_relativitize(os.path.normcase(path_item), sname, relative_paths)
             for path_item in path], indent_level=indent_level)
        rpsetup = relative_paths_setup
        if not omit_os_import:
            rpsetup = '\n\nimport os\n' + rpsetup
        for i in range(_relative_depth(relative_paths, sname)):
            rpsetup += "\nbase = os.path.dirname(base)"
    else:
        spath = _format_paths((repr(p) for p in path),
                              indent_level=indent_level)
        rpsetup = ''
    return spath, rpsetup

def _relative_depth(common, path):
    """Return number of dirs separating ``path`` from ancestor, ``common``.

    For instance, if path is /foo/bar/baz/bing, and common is /foo, this will
    return 2--in UNIX, the number of ".." to get from bing's directory
    to foo.

    This is a helper for _relative_path_and_setup.
    """
    n = 0
    while 1:
        dirname = os.path.dirname(path)
        if dirname == path:
            raise AssertionError("dirname of %s is the same" % dirname)
        if dirname == common:
            break
        n += 1
        path = dirname
    return n

def _relative_path(common, path):
    """Return the relative path from ``common`` to ``path``.

    This is a helper for _relativitize, which is a helper to
    _relative_path_and_setup.
    """
    r = []
    while 1:
        dirname, basename = os.path.split(path)
        r.append(basename)
        if dirname == common:
            break
        if dirname == path:
            raise AssertionError("dirname of %s is the same" % dirname)
        path = dirname
    r.reverse()
    return os.path.join(*r)

def _relativitize(path, script, relative_paths):
    """Return a code string for the given path.

    Path is relative to the base path ``relative_paths``if the common prefix
    between ``path`` and ``script`` starts with ``relative_paths``.
    """
    if path == script:
        raise AssertionError("path == script")
    common = os.path.dirname(os.path.commonprefix([path, script]))
    if (common == relative_paths or
        common.startswith(os.path.join(relative_paths, ''))
        ):
        return "join(base, %r)" % _relative_path(common, path)
    else:
        return repr(path)

relative_paths_setup = """
join = os.path.join
base = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))"""

def _write_script(full_name, contents, logged_type):
    """Write contents of script in full_name, logging the action.

    The only tricky bit in this function is that it supports Windows by
    creating exe files using a pkg_resources helper.
    """
    generated = []
    script_name = full_name
    if is_win32:
        script_name += '-script.py'
        # Generate exe file and give the script a magic name.
        exe = full_name + '.exe'
        new_data = pkg_resources.resource_string('setuptools', 'cli.exe')
        if not os.path.exists(exe) or (open(exe, 'rb').read() != new_data):
            # Only write it if it's different.
            open(exe, 'wb').write(new_data)
        generated.append(exe)
    changed = not (os.path.exists(script_name) and
                   open(script_name).read() == contents)
    if changed:
        open(script_name, 'w').write(contents)
        try:
            os.chmod(script_name, 0755)
        except (AttributeError, os.error):
            pass
        logger.info("Generated %s %r.", logged_type, full_name)
    generated.append(script_name)
    return generated

def _format_paths(paths, indent_level=1):
    """Format paths for inclusion in a script."""
    separator = ',\n' + indent_level * '    '
    return separator.join(paths)

def _script(dest, executable, relative_paths_setup, path, initialization,
            module_name, attrs, arguments, block_site=False):
    if block_site:
        dash_S = ' -S'
    else:
        dash_S = ''
    contents = script_template % dict(
        python=_safe_arg(executable),
        dash_S=dash_S,
        path=path,
        module_name=module_name,
        attrs=attrs,
        arguments=arguments,
        initialization=initialization,
        relative_paths_setup=relative_paths_setup,
        )
    return _write_script(dest, contents, 'script')

if is_jython and jython_os_name == 'linux':
    script_header = '#!/usr/bin/env %(python)s%(dash_S)s'
else:
    script_header = '#!%(python)s%(dash_S)s'

sys_path_template = '''\
import sys
sys.path[0:0] = [
    %s,
    ]
'''

script_template = script_header + '''\
%(relative_paths_setup)s

import sys
sys.path[0:0] = [
    %(path)s,
    ]

%(initialization)s
import %(module_name)s

if __name__ == '__main__':
    %(module_name)s.%(attrs)s(%(arguments)s)
'''

# These are used only by the older ``scripts`` function.

def _pyscript(path, dest, executable, rsetup):
    contents = py_script_template % dict(
        python=_safe_arg(executable),
        dash_S='',
        path=path,
        relative_paths_setup=rsetup,
        )
    return _write_script(dest, contents, 'interpreter')

py_script_template = script_header + '''\
%(relative_paths_setup)s

import sys

sys.path[0:0] = [
    %(path)s,
    ]

_interactive = True
if len(sys.argv) > 1:
    _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:')
    _interactive = False
    for (_opt, _val) in _options:
        if _opt == '-i':
            _interactive = True
        elif _opt == '-c':
            exec _val
        elif _opt == '-m':
            sys.argv[1:] = _args
            _args = []
            __import__("runpy").run_module(
                 _val, {}, "__main__", alter_sys=True)

    if _args:
        sys.argv[:] = _args
        __file__ = _args[0]
        del _options, _args
        execfile(__file__)

if _interactive:
    del _interactive
    __import__("code").interact(banner="", local=globals())
'''

# These are used only by the newer ``sitepackage_safe_scripts`` function.

def _get_module_file(executable, name, silent=False):
    """Return a module's file path.

    - executable is a path to the desired Python executable.
    - name is the name of the (pure, not C) Python module.
    """
    cmd = [executable, "-Sc",
           "import imp; "
           "fp, path, desc = imp.find_module(%r); "
           "fp.close(); "
           "print path" % (name,)]
    env = os.environ.copy()
    # We need to make sure that PYTHONPATH, which will often be set to
    # include a custom buildout-generated site.py, is not set, or else
    # we will not get an accurate value for the "real" site.py and
    # sitecustomize.py.
    env.pop('PYTHONPATH', None)
    _proc = subprocess.Popen(
        cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
    stdout, stderr = _proc.communicate();
    if _proc.returncode:
        if not silent:
            logger.info(
                'Could not find file for module %s:\n%s', name, stderr)
        return None
    # else: ...
    res = stdout.strip()
    if res.endswith('.pyc') or res.endswith('.pyo'):
        raise RuntimeError('Cannot find uncompiled version of %s' % (name,))
    if not os.path.exists(res):
        raise RuntimeError(
            'File does not exist for module %s:\n%s' % (name, res))
    return res

def _generate_sitecustomize(dest, executable, initialization='',
                            exec_sitecustomize=False):
    """Write a sitecustomize file with optional custom initialization.

    The created script will execute the underlying Python's
    sitecustomize if exec_sitecustomize is True.
    """
    sitecustomize_path = os.path.join(dest, 'sitecustomize.py')
    sitecustomize = open(sitecustomize_path, 'w')
    if initialization:
        sitecustomize.write(initialization + '\n')
    if exec_sitecustomize:
        real_sitecustomize_path = _get_module_file(
            executable, 'sitecustomize', silent=True)
        if real_sitecustomize_path:
            real_sitecustomize = open(real_sitecustomize_path, 'r')
            sitecustomize.write(
                '\n# The following is from\n# %s\n' %
                (real_sitecustomize_path,))
            sitecustomize.write(real_sitecustomize.read())
            real_sitecustomize.close()
    sitecustomize.close()
    return sitecustomize_path

def _generate_site(dest, working_set, executable, extra_paths=(),
                   include_site_packages=False, relative_paths=False):
    """Write a site.py file with eggs from working_set.

    extra_paths will be added to the path.  If include_site_packages is True,
    paths from the underlying Python will be added.
    """
    path = _get_path(working_set, extra_paths)
    site_path = os.path.join(dest, 'site.py')
    original_path_setup = preamble = ''
    if include_site_packages:
        stdlib, site_paths = _get_system_paths(executable)
        # We want to make sure that paths from site-packages, such as those
        # allowed by allowed_eggs_from_site_packages, always come last, or
        # else site-packages paths may include packages that mask the eggs we
        # really want.
        path = [p for p in path if p not in site_paths]
        # Now we set up the code we need.
        original_path_setup = original_path_snippet % (
            _format_paths((repr(p) for p in site_paths), 2),)
        distribution = working_set.find(
            pkg_resources.Requirement.parse('setuptools'))
        if distribution is not None:
            # We need to worry about namespace packages.
            if relative_paths:
                location = _relativitize(
                    distribution.location,
                    os.path.normcase(os.path.abspath(site_path)),
                    relative_paths)
            else:
                location = repr(distribution.location)
            preamble = namespace_include_site_packages_setup % (location,)
            original_path_setup = (
                addsitedir_namespace_originalpackages_snippet +
                original_path_setup)
        else:
            preamble = '\n    setuptools_path = None'
    egg_path_string, relative_preamble = _relative_path_and_setup(
        site_path, path, relative_paths, indent_level=2, omit_os_import=True)
    if relative_preamble:
        relative_preamble = '\n'.join(
            [(line and '    %s' % (line,) or line)
             for line in relative_preamble.split('\n')])
        preamble = relative_preamble + preamble
    addsitepackages_marker = 'def addsitepackages('
    enableusersite_marker = 'ENABLE_USER_SITE = '
    successful_rewrite = False
    real_site_path = _get_module_file(executable, 'site')
    real_site = open(real_site_path, 'r')
    site = open(site_path, 'w')
    try:
        for line in real_site.readlines():
            if line.startswith(enableusersite_marker):
                site.write(enableusersite_marker)
                site.write('False # buildout does not support user sites.\n')
            elif line.startswith(addsitepackages_marker):
                site.write(addsitepackages_script % (
                    preamble, egg_path_string, original_path_setup))
                site.write(line[len(addsitepackages_marker):])
                successful_rewrite = True
            else:
                site.write(line)
    finally:
        site.close()
        real_site.close()
    if not successful_rewrite:
        raise RuntimeError(
            'Buildout did not successfully rewrite %s to %s' %
            (real_site_path, site_path))
    return site_path

namespace_include_site_packages_setup = '''
    setuptools_path = %s
    sys.path.append(setuptools_path)
    known_paths.add(os.path.normcase(setuptools_path))
    import pkg_resources'''

addsitedir_namespace_originalpackages_snippet = '''
            pkg_resources.working_set.add_entry(sitedir)'''

original_path_snippet = '''
    sys.__egginsert = len(buildout_paths) # Support distribute.
    original_paths = [
        %s
        ]
    for path in original_paths:
        if path == setuptools_path or path not in known_paths:
            addsitedir(path, known_paths)'''

addsitepackages_script = '''\
def addsitepackages(known_paths):
    """Add site packages, as determined by zc.buildout.

    See original_addsitepackages, below, for the original version."""%s
    buildout_paths = [
        %s
        ]
    for path in buildout_paths:
        sitedir, sitedircase = makepath(path)
        if not sitedircase in known_paths and os.path.exists(sitedir):
            sys.path.append(sitedir)
            known_paths.add(sitedircase)%s
    return known_paths

def original_addsitepackages('''

def _generate_interpreter(name, dest, executable, site_py_dest,
                          relative_paths=False):
    """Write an interpreter script, using the site.py approach."""
    full_name = os.path.join(dest, name)
    site_py_dest_string, rpsetup = _relative_path_and_setup(
        full_name, [site_py_dest], relative_paths, omit_os_import=True)
    if rpsetup:
        rpsetup += "\n"
    if sys.platform == 'win32':
        windows_import = '\nimport subprocess'
        # os.exec* is a mess on Windows, particularly if the path
        # to the executable has spaces and the Python is using MSVCRT.
        # The standard fix is to surround the executable's path with quotes,
        # but that has been unreliable in testing.
        #
        # Here's a demonstration of the problem.  Given a Python
        # compiled with a MSVCRT-based compiler, such as the free Visual
        # C++ 2008 Express Edition, and an executable path with spaces
        # in it such as the below, we see the following.
        #
        # >>> import os
        # >>> p0 = 'C:\\Documents and Settings\\Administrator\\My Documents\\Downloads\\Python-2.6.4\\PCbuild\\python.exe'
        # >>> os.path.exists(p0)
        # True
        # >>> os.execv(p0, [])
        # Traceback (most recent call last):
        #  File "<stdin>", line 1, in <module>
        # OSError: [Errno 22] Invalid argument
        #
        # That seems like a standard problem.  The standard solution is
        # to quote the path (see, for instance
        # http://bugs.python.org/issue436259).  However, this solution,
        # and other variations, fail:
        #
        # >>> p1 = '"C:\\Documents and Settings\\Administrator\\My Documents\\Downloads\\Python-2.6.4\\PCbuild\\python.exe"'
        # >>> os.execv(p1, [])
        # Traceback (most recent call last):
        #   File "<stdin>", line 1, in <module>
        # OSError: [Errno 22] Invalid argument
        #
        # We simply use subprocess instead, since it handles everything
        # nicely, and the transparency of exec* (that is, not running,
        # perhaps unexpectedly, in a subprocess) is arguably not a
        # necessity, at least for many use cases.
        execute = 'subprocess.call(argv, env=environ)'
    else:
        windows_import = ''
        execute = 'os.execve(sys.executable, argv, environ)'
    contents = interpreter_template % dict(
        python=_safe_arg(executable),
        dash_S=' -S',
        site_dest=site_py_dest_string,
        relative_paths_setup=rpsetup,
        windows_import=windows_import,
        execute=execute,
        )
    return _write_script(full_name, contents, 'interpreter')

interpreter_template = script_header + '''
import os
import sys%(windows_import)s
%(relative_paths_setup)s
argv = [sys.executable] + sys.argv[1:]
environ = os.environ.copy()
path = %(site_dest)s
if environ.get('PYTHONPATH'):
    path = os.pathsep.join([path, environ['PYTHONPATH']])
environ['PYTHONPATH'] = path
%(execute)s
'''

# End of script generation code.
############################################################################

runsetup_template = """
import sys
sys.path.insert(0, %(setupdir)r)
sys.path.insert(0, %(setuptools)r)
import os, setuptools

__file__ = %(__file__)r

os.chdir(%(setupdir)r)
sys.argv[0] = %(setup)r
execfile(%(setup)r)
"""


class VersionConflict(zc.buildout.UserError):

    def __init__(self, err, ws):
        ws = list(ws)
        ws.sort()
        self.err, self.ws = err, ws

    def __str__(self):
        existing_dist, req = self.err
        result = ["There is a version conflict.",
                  "We already have: %s" % existing_dist,
                  ]
        for dist in self.ws:
            if req in dist.requires():
                result.append("but %s requires %r." % (dist, str(req)))
        return '\n'.join(result)


class MissingDistribution(zc.buildout.UserError):

    def __init__(self, req, ws):
        ws = list(ws)
        ws.sort()
        self.data = req, ws

    def __str__(self):
        req, ws = self.data
        return "Couldn't find a distribution for %r." % str(req)

def _log_requirement(ws, req):
    ws = list(ws)
    ws.sort()
    for dist in ws:
        if req in dist.requires():
            logger.debug("  required by %s." % dist)

def _fix_file_links(links):
    for link in links:
        if link.startswith('file://') and link[-1] != '/':
            if os.path.isdir(link[7:]):
                # work around excessive restriction in setuptools:
                link += '/'
        yield link

_final_parts = '*final-', '*final'
def _final_version(parsed_version):
    for part in parsed_version:
        if (part[:1] == '*') and (part not in _final_parts):
            return False
    return True

def redo_pyc(egg):
    if not os.path.isdir(egg):
        return
    for dirpath, dirnames, filenames in os.walk(egg):
        for filename in filenames:
            if not filename.endswith('.py'):
                continue
            filepath = os.path.join(dirpath, filename)
            if not (os.path.exists(filepath+'c')
                    or os.path.exists(filepath+'o')):
                # If it wasn't compiled, it may not be compilable
                continue

            # OK, it looks like we should try to compile.

            # Remove old files.
            for suffix in 'co':
                if os.path.exists(filepath+suffix):
                    os.remove(filepath+suffix)

            # Compile under current optimization
            try:
                py_compile.compile(filepath)
            except py_compile.PyCompileError:
                logger.warning("Couldn't compile %s", filepath)
            else:
                # Recompile under other optimization. :)
                args = [_safe_arg(sys.executable)]
                if __debug__:
                    args.append('-O')
                args.extend(['-m', 'py_compile', _safe_arg(filepath)])

                if is_jython:
                    subprocess.call([sys.executable, args])
                else:
                    os.spawnv(os.P_WAIT, sys.executable, args)

