easy_install.py 45.4 KB
Newer Older
1
#############################################################################
2
#
3
# Copyright (c) 2005 Zope Foundation and Contributors.
4 5 6 7 8 9 10 11 12 13 14 15 16
# 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.
Jim Fulton's avatar
Jim Fulton committed
17
It doesn't install scripts.  It uses distribute and requires it to be
18 19 20
installed.
"""

21
import distutils.errors
22 23
import glob
import logging
24
import os
25
import pkg_resources
26 27 28
import py_compile
import re
import setuptools.archive_util
29 30
import setuptools.command.setopt
import setuptools.package_index
31
import shutil
32
import subprocess
33
import sys
34
import tempfile
35
import zc.buildout
36

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

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

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

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

50
is_win32 = sys.platform == 'win32'
Georgy Berdyshev's avatar
Georgy Berdyshev committed
51 52 53 54 55 56 57
is_jython = sys.platform.startswith('java')

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


Jim Fulton's avatar
Jim Fulton committed
58 59
distribute_loc = pkg_resources.working_set.find(
    pkg_resources.Requirement.parse('distribute')
60 61
    ).location

Jim Fulton's avatar
Jim Fulton committed
62 63 64
# Include buildout and distribute eggs in paths
buildout_and_distribute_path = [
    distribute_loc,
65 66
    pkg_resources.working_set.find(
        pkg_resources.Requirement.parse('zc.buildout')).location,
67 68
    ]

Tarek Ziad's avatar
Tarek Ziad committed
69 70
FILE_SCHEME = re.compile('file://', re.I).match

71

Tarek Ziad's avatar
Tarek Ziad committed
72 73 74 75 76 77 78 79 80
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)
81

Tarek Ziad's avatar
Tarek Ziad committed
82

83
_indexes = {}
Jim Fulton's avatar
Jim Fulton committed
84 85
def _get_index(index_url, find_links, allow_hosts=('*',)):
    key = index_url, tuple(find_links)
86 87 88 89
    index = _indexes.get(key)
    if index is not None:
        return index

90 91
    if index_url is None:
        index_url = default_index_url
92 93
    if index_url.startswith('file://'):
        index_url = index_url[7:]
Jim Fulton's avatar
Jim Fulton committed
94
    index = AllowHostsPackageIndex(index_url, hosts=allow_hosts)
95

96 97 98 99 100 101
    if find_links:
        index.add_find_links(find_links)

    _indexes[key] = index
    return index

102
clear_index_cache = _indexes.clear
Jim Fulton's avatar
Jim Fulton committed
103

104
if is_win32:
Jim Fulton's avatar
Jim Fulton committed
105 106 107 108 109 110 111
    # work around spawn lamosity on windows
    # XXX need safe quoting (see the subproces.list2cmdline) and test
    def _safe_arg(arg):
        return '"%s"' % arg
else:
    _safe_arg = str

112 113 114 115 116
def call_subprocess(args, **kw):
    if subprocess.call(args, **kw) != 0:
        raise Exception(
            "Failed to run command:\n%s"
            % repr(args)[1:-1])
Jim Fulton's avatar
Jim Fulton committed
117

118
_easy_install_cmd = 'from setuptools.command.easy_install import main; main()'
119

120 121
class Installer:

122
    _versions = {}
123 124
    _required_by = {}
    _picked_versions = {}
125
    _download_cache = None
126
    _install_from_cache = False
127
    _prefer_final = True
128
    _use_dependency_links = True
Jim Fulton's avatar
Jim Fulton committed
129
    _allow_picked_versions = True
130
    _show_picked_versions = False
131

132 133 134 135 136
    def __init__(self,
                 dest=None,
                 links=(),
                 index=None,
                 executable=sys.executable,
Jim Fulton's avatar
Jim Fulton committed
137
                 always_unzip=None, # Backward compat :/
138 139
                 path=None,
                 newest=True,
Jim Fulton's avatar
Jim Fulton committed
140
                 versions=None,
141
                 use_dependency_links=None,
Tarek Ziad's avatar
Tarek Ziad committed
142
                 allow_hosts=('*',)
143
                 ):
Jim Fulton's avatar
Jim Fulton committed
144
        assert executable == sys.executable, (executable, sys.executable)
145
        self._dest = dest
Tarek Ziad's avatar
Tarek Ziad committed
146
        self._allow_hosts = allow_hosts
147 148 149 150 151 152 153

        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
154

155 156
        if use_dependency_links is not None:
            self._use_dependency_links = use_dependency_links
157
        self._links = links = list(_fix_file_links(links))
158 159 160
        if self._download_cache and (self._download_cache not in links):
            links.insert(0, self._download_cache)

161
        self._index_url = index
Jim Fulton's avatar
Jim Fulton committed
162
        path = (path and path[:] or []) + buildout_and_distribute_path
163 164 165
        if dest is not None and dest not in path:
            path.insert(0, dest)
        self._path = path
166 167
        if self._dest is None:
            newest = False
168
        self._newest = newest
Jim Fulton's avatar
Jim Fulton committed
169 170
        self._env = pkg_resources.Environment(path)
        self._index = _get_index(index, links, self._allow_hosts)
171 172

        if versions is not None:
173
            self._versions = normalize_versions(versions)
174

175
    def _satisfied(self, req, source=None):
176 177
        dists = [dist for dist in self._env[req.project_name] if dist in req]
        if not dists:
178 179
            logger.debug('We have no distributions for %s that satisfies %r.',
                         req.project_name, str(req))
180

181
            return None, self._obtain(req, source)
182 183 184 185 186 187

        # 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):
188
                logger.debug('We have a develop egg: %s', dist)
189
                return dist, None
190

191 192 193 194 195 196
        # 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
197

198 199 200 201 202 203 204 205 206 207 208 209 210 211
        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

212
        best_we_have = dists[0] # Because dists are sorted from best to worst
Jim Fulton's avatar
Jim Fulton committed
213

214 215 216 217
        # 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:
218

Jim Fulton's avatar
Jim Fulton committed
219
        best_available = self._obtain(req, source)
220 221 222 223 224

        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(
225 226 227
                'There are no distros available that meet %r.\n'
                'Using our best, %s.',
                str(req), best_available)
228
            return best_we_have, None
229

230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
        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
255

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

261
    def _load_dist(self, dist):
Jim Fulton's avatar
Jim Fulton committed
262
        dists = pkg_resources.Environment(dist.location)[dist.project_name]
263 264
        assert len(dists) == 1
        return dists[0]
265

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

268 269
        tmp = tempfile.mkdtemp(dir=dest)
        try:
Jim Fulton's avatar
Jim Fulton committed
270
            path = distribute_loc
271

Jim Fulton's avatar
Jim Fulton committed
272
            args = [sys.executable, '-c', _easy_install_cmd, '-mZUNxd', tmp]
273 274
            level = logger.getEffectiveLevel()
            if level > 0:
275
                args.append('-q')
276
            elif level < 0:
277
                args.append('-v')
278

279
            args.append(spec)
280 281

            if level <= logging.DEBUG:
Jim Fulton's avatar
Jim Fulton committed
282 283
                logger.debug('Running easy_install:\n"%s"\npath=%s\n',
                             '" "'.join(args), path)
284 285

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

287 288 289
            exit_code = subprocess.call(
                list(args),
                env=dict(os.environ, PYTHONPATH=path))
290 291

            dists = []
Jim Fulton's avatar
Jim Fulton committed
292
            env = pkg_resources.Environment([tmp])
293 294
            for project in env:
                dists.extend(env[project])
295

296 297
            if exit_code:
                logger.error(
298 299
                    "An error occured when trying to install %s. "
                    "Look above this message for any errors that "
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
                    "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):
Jim Fulton's avatar
Jim Fulton committed
330
                    if os.path.isdir(newloc):
331 332 333 334 335
                        shutil.rmtree(newloc)
                    else:
                        os.remove(newloc)
                os.rename(d.location, newloc)

Jim Fulton's avatar
Jim Fulton committed
336
                [d] = pkg_resources.Environment([newloc])[d.project_name]
337

338 339 340
                result.append(d)

            return result
341

342 343
        finally:
            shutil.rmtree(tmp)
344

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

349
        if index.obtain(requirement) is None:
350
            # Nothing is available.
351
            return None
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373

        # Filter the available dists for the requirement and source flag
        dists = [dist for dist in index[requirement.project_name]
                 if ((dist in requirement)
                     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:
374 375
        best = []
        bestv = ()
376
        for dist in dists:
377 378 379 380 381 382 383 384 385
            distv = dist.parsed_version
            if distv > bestv:
                best = [dist]
                bestv = distv
            elif distv == bestv:
                best.append(dist)

        if not best:
            return None
386

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

390 391
        if self._download_cache:
            for dist in best:
392 393
                if (realpath(os.path.dirname(dist.location))
                    ==
394
                    self._download_cache
395
                    ):
396
                    return dist
397

398 399
        best.sort()
        return best[-1]
400

Jim Fulton's avatar
Jim Fulton committed
401 402
    def _fetch(self, dist, tmp, download_cache):
        if (download_cache
403
            and (realpath(os.path.dirname(dist.location)) == download_cache)
Jim Fulton's avatar
Jim Fulton committed
404 405 406 407 408
            ):
            return dist

        new_location = self._index.download(dist.location, tmp)
        if (download_cache
409
            and (realpath(new_location) == realpath(dist.location))
Jim Fulton's avatar
Jim Fulton committed
410 411
            and os.path.isfile(new_location)
            ):
Jim Fulton's avatar
Jim Fulton committed
412
            # distribute avoids making extra copies, but we want to copy
Jim Fulton's avatar
Jim Fulton committed
413 414 415
            # to the download cache
            shutil.copy2(new_location, tmp)
            new_location = os.path.join(tmp, os.path.basename(new_location))
416

Jim Fulton's avatar
Jim Fulton committed
417
        return dist.clone(location=new_location)
418

Jim Fulton's avatar
Jim Fulton committed
419
    def _get_dist(self, requirement, ws):
420

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

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

427
        if dist is None:
428 429 430 431 432 433 434
            if self._dest is None:
                raise zc.buildout.UserError(
                    "We don't have a distribution for %s\n"
                    "and can't install one in offline (no-install) mode.\n"
                    % requirement)

            logger.info(*__doing__)
435

436 437
            # Retrieve the dist:
            if avail is None:
438
                self._index.obtain(requirement)
439
                raise MissingDistribution(requirement, ws)
440

441 442 443
            # We may overwrite distributions, so clear importer
            # cache.
            sys.path_importer_cache.clear()
444

445
            tmp = self._download_cache
Jim Fulton's avatar
Jim Fulton committed
446 447 448
            if tmp is None:
                tmp = tempfile.mkdtemp('get_dist')

449
            try:
Jim Fulton's avatar
Jim Fulton committed
450
                dist = self._fetch(avail, tmp, self._download_cache)
451

452 453 454
                if dist is None:
                    raise zc.buildout.UserError(
                        "Couln't download distribution %s." % avail)
455

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

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

462 463 464 465 466
                    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:
467

Jim Fulton's avatar
Jim Fulton committed
468 469 470

                        setuptools.archive_util.unpack_archive(
                            dist.location, newloc)
471

472 473
                    redo_pyc(newloc)

474 475 476
                    # Getting the dist from the environment causes the
                    # distribution meta data to be read.  Cloning isn't
                    # good enough.
Jim Fulton's avatar
Jim Fulton committed
477 478
                    dists = pkg_resources.Environment([newloc])[
                        dist.project_name]
479 480 481 482 483
                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)
484 485
                    for dist in dists:
                        redo_pyc(dist.location)
486

487 488 489
            finally:
                if tmp != self._download_cache:
                    shutil.rmtree(tmp)
490

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

495 496
        else:
            dists = [dist]
497

498
        for dist in dists:
499 500
            if (dist.has_metadata('dependency_links.txt')
                and not self._install_from_cache
501
                and self._use_dependency_links
502
                ):
503 504 505 506 507
                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)
Jim Fulton's avatar
Jim Fulton committed
508
                        self._index = _get_index(self._index_url, self._links,
Tarek Ziad's avatar
Tarek Ziad committed
509
                                                 self._allow_hosts)
510 511 512 513 514 515 516 517 518 519

        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] == '==')
                ):
520 521
                logger.debug('Picked: %s = %s',
                             dist.project_name, dist.version)
522
                self._picked_versions[dist.project_name] = dist.version
523

Jim Fulton's avatar
Jim Fulton committed
524 525 526 527
                if not self._allow_picked_versions:
                    raise zc.buildout.UserError(
                        'Picked: %s = %s' % (dist.project_name, dist.version)
                        )
528 529

        return dists
530

Jim Fulton's avatar
Jim Fulton committed
531
    def _maybe_add_distribute(self, ws, dist):
532 533
        if dist.has_metadata('namespace_packages.txt'):
            for r in dist.requires():
Jim Fulton's avatar
Jim Fulton committed
534
                if r.project_name in ('setuptools', 'distribute'):
535 536
                    break
            else:
Jim Fulton's avatar
Jim Fulton committed
537
                # We have a namespace package but no requirement for distribute
538 539
                if dist.precedence == pkg_resources.DEVELOP_DIST:
                    logger.warn(
540
                        "Develop distribution: %s\n"
541
                        "uses namespace packages but the distribution "
Jim Fulton's avatar
Jim Fulton committed
542
                        "does not require distribute.",
543
                        dist)
Jim Fulton's avatar
Jim Fulton committed
544
                requirement = self._constrain(
Jim Fulton's avatar
Jim Fulton committed
545
                    pkg_resources.Requirement.parse('distribute')
Jim Fulton's avatar
Jim Fulton committed
546
                    )
547
                if ws.find(requirement) is None:
Jim Fulton's avatar
Jim Fulton committed
548
                    for dist in self._get_dist(requirement, ws):
549
                        ws.add(dist)
550 551


Jim Fulton's avatar
Jim Fulton committed
552
    def _constrain(self, requirement):
553
        constraint = self._versions.get(requirement.project_name.lower())
554 555
        if constraint:
            requirement = _constrained_requirement(constraint, requirement)
Jim Fulton's avatar
Jim Fulton committed
556 557
        return requirement

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

560
        logger.debug('Installing %s.', repr(specs)[1:-1])
561 562 563 564 565 566

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

Jim Fulton's avatar
Jim Fulton committed
567
        requirements = [self._constrain(pkg_resources.Requirement.parse(spec))
568 569
                        for spec in specs]

570

Jim Fulton's avatar
Jim Fulton committed
571

572 573
        if working_set is None:
            ws = pkg_resources.WorkingSet([])
Jim Fulton's avatar
Jim Fulton committed
574
        else:
575
            ws = working_set
576

577
        for requirement in requirements:
Jim Fulton's avatar
Jim Fulton committed
578
            for dist in self._get_dist(requirement, ws):
579
                ws.add(dist)
Jim Fulton's avatar
Jim Fulton committed
580
                self._maybe_add_distribute(ws, dist)
581 582 583 584 585 586

        # OK, we have the requested distributions and they're in the working
        # set, but they may have unmet requirements.  We'll simply keep
        # trying to resolve requirements, adding missing requirements as they
        # are reported.
        #
587 588 589
        # Note that we don't pass in the environment, because we want
        # to look for new eggs unless what we have is the best that
        # matches the requirement.
590 591 592
        while 1:
            try:
                ws.resolve(requirements)
593 594 595
            except pkg_resources.DistributionNotFound:
                err = sys.exc_info()[1]
                [requirement] = err.args
Jim Fulton's avatar
Jim Fulton committed
596
                requirement = self._constrain(requirement)
597
                if dest:
598 599 600 601
                    logger.debug('Getting required %r', str(requirement))
                else:
                    logger.debug('Adding required %r', str(requirement))
                _log_requirement(ws, requirement)
602

Jim Fulton's avatar
Jim Fulton committed
603
                for dist in self._get_dist(requirement, ws):
604
                    ws.add(dist)
Jim Fulton's avatar
Jim Fulton committed
605
                    self._maybe_add_distribute(ws, dist)
606 607
            except pkg_resources.VersionConflict:
                err = sys.exc_info()[1]
608
                raise VersionConflict(err, ws)
609 610
            else:
                break
611

612
        return ws
613

614
    def build(self, spec, build_ext):
615

Jim Fulton's avatar
Jim Fulton committed
616
        requirement = self._constrain(pkg_resources.Requirement.parse(spec))
617

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

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

628 629 630 631 632 633 634
        if self._dest is None:
            raise zc.buildout.UserError(
                "We don't have a distribution for %s\n"
                "and can't build one in offline (no-install) mode.\n"
                % requirement
                )

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

637
        tmp = self._download_cache
Jim Fulton's avatar
Jim Fulton committed
638 639 640
        if tmp is None:
            tmp = tempfile.mkdtemp('get_dist')

641
        try:
Jim Fulton's avatar
Jim Fulton committed
642
            dist = self._fetch(avail, tmp, self._download_cache)
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663

            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])
664

665 666 667 668 669 670 671 672 673 674 675
                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)

676 677 678
                for dist in dists:
                    redo_pyc(dist.location)

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

683
        finally:
684 685 686
            if tmp != self._download_cache:
                shutil.rmtree(tmp)

687 688 689 690 691 692 693 694 695 696

def normalize_versions(versions):
    """Return version dict with keys normalized to lowercase.

    PyPI is case-insensitive and not all distributions are consistent in
    their own naming.
    """
    return dict([(k.lower(), v) for (k, v) in versions.items()])


697 698 699
def default_versions(versions=None):
    old = Installer._versions
    if versions is not None:
700
        Installer._versions = normalize_versions(versions)
701
    return old
702

703 704 705
def download_cache(path=-1):
    old = Installer._download_cache
    if path != -1:
706
        if path:
707
            path = realpath(path)
708 709 710
        Installer._download_cache = path
    return old

711 712 713 714 715 716
def install_from_cache(setting=None):
    old = Installer._install_from_cache
    if setting is not None:
        Installer._install_from_cache = bool(setting)
    return old

717 718 719 720 721 722
def prefer_final(setting=None):
    old = Installer._prefer_final
    if setting is not None:
        Installer._prefer_final = bool(setting)
    return old

723 724 725 726 727 728
def use_dependency_links(setting=None):
    old = Installer._use_dependency_links
    if setting is not None:
        Installer._use_dependency_links = bool(setting)
    return old

Jim Fulton's avatar
Jim Fulton committed
729 730 731 732 733 734
def allow_picked_versions(setting=None):
    old = Installer._allow_picked_versions
    if setting is not None:
        Installer._allow_picked_versions = bool(setting)
    return old

735 736 737 738 739 740 741
def show_picked_versions(setting=None):
    old = Installer._show_picked_versions
    if setting is not None:
        Installer._show_picked_versions = bool(setting)
    return old


742 743
def install(specs, dest,
            links=(), index=None,
Jim Fulton's avatar
Jim Fulton committed
744 745
            executable=sys.executable,
            always_unzip=None, # Backward compat :/
746
            path=None, working_set=None, newest=True, versions=None,
747 748 749 750
            use_dependency_links=None, allow_hosts=('*',),
            include_site_packages=None,
            allowed_eggs_from_site_packages=None,
            ):
Jim Fulton's avatar
Jim Fulton committed
751
    assert executable == sys.executable, (executable, sys.executable)
752 753 754
    assert include_site_packages is None
    assert allowed_eggs_from_site_packages is None

Jim Fulton's avatar
Jim Fulton committed
755 756
    installer = Installer(dest, links, index, sys.executable,
                          always_unzip, path,
757
                          newest, versions, use_dependency_links,
Tarek Ziad's avatar
Tarek Ziad committed
758
                          allow_hosts=allow_hosts)
759 760 761 762 763 764
    return installer.install(specs, working_set)


def build(spec, dest, build_ext,
          links=(), index=None,
          executable=sys.executable,
Tarek Ziad's avatar
Tarek Ziad committed
765
          path=None, newest=True, versions=None, allow_hosts=('*',)):
Jim Fulton's avatar
Jim Fulton committed
766
    assert executable == sys.executable, (executable, sys.executable)
Jim Fulton's avatar
Jim Fulton committed
767
    installer = Installer(dest, links, index, executable,
Jim Fulton's avatar
Jim Fulton committed
768
                          True, path, newest,
Tarek Ziad's avatar
Tarek Ziad committed
769
                          versions, allow_hosts=allow_hosts)
770 771
    return installer.build(spec, build_ext)

772

773

774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
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)
790

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

794 795 796 797 798
    return result[0]

def develop(setup, dest,
            build_ext=None,
            executable=sys.executable):
Jim Fulton's avatar
Jim Fulton committed
799
    assert executable == sys.executable, (executable, sys.executable)
800 801 802 803 804
    if os.path.isdir(setup):
        directory = setup
        setup = os.path.join(directory, 'setup.py')
    else:
        directory = os.path.dirname(setup)
805

806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826
    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))

827
        os.write(fd, (runsetup_template % dict(
Jim Fulton's avatar
Jim Fulton committed
828
            distribute=distribute_loc,
829 830 831
            setupdir=directory,
            setup=setup,
            __file__ = setup,
832
            )).encode())
833 834

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

Jim Fulton's avatar
Jim Fulton committed
837
        args = [executable,  tsetup, '-q', 'develop', '-mxN', '-d', tmp3]
838 839

        log_level = logger.getEffectiveLevel()
840 841
        if log_level <= 0:
            if log_level == 0:
842
                del args[2]
843
            else:
844
                args[2] == '-v'
845
        if log_level < logging.DEBUG:
846
            logger.debug("in: %r\n%s", directory, ' '.join(args))
847

848
        call_subprocess(args)
849 850 851 852 853 854

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

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


857 858 859
def working_set(specs, executable, path=None,
                include_site_packages=None,
                allowed_eggs_from_site_packages=None):
Jim Fulton's avatar
Jim Fulton committed
860 861 862 863 864
    # Backward compat:
    if path is None:
        path = executable
    else:
        assert executable == sys.executable, (executable, sys.executable)
865 866 867
    assert include_site_packages is None
    assert allowed_eggs_from_site_packages is None

Jim Fulton's avatar
Jim Fulton committed
868
    return install(specs, None, path=path)
869

Jim Fulton's avatar
Jim Fulton committed
870
def scripts(reqs, working_set, executable, dest=None,
871 872 873
            scripts=None,
            extra_paths=(),
            arguments='',
874
            interpreter=None,
875
            initialization='',
876
            relative_paths=False,
877
            ):
Jim Fulton's avatar
Jim Fulton committed
878
    assert executable == sys.executable, (executable, sys.executable)
879

880 881
    path = [dist.location for dist in working_set]
    path.extend(extra_paths)
882 883 884 885 886
    # order preserving unique
    unique_path = []
    for p in path:
        if p not in unique_path:
            unique_path.append(p)
887
    path = list(map(realpath, unique_path))
888

889 890
    generated = []

891 892 893 894
    if isinstance(reqs, str):
        raise TypeError('Expected iterable of requirements or entry points,'
                        ' got string.')

895 896 897
    if initialization:
        initialization = '\n'+initialization+'\n'

898
    entry_points = []
899
    distutils_scripts = []
900 901 902 903
    for req in reqs:
        if isinstance(req, str):
            req = pkg_resources.Requirement.parse(req)
            dist = working_set.find(req)
904
            # regular console_scripts entry points
905
            for name in pkg_resources.get_entry_map(dist, 'console_scripts'):
906 907
                entry_point = dist.get_entry_info('console_scripts', name)
                entry_points.append(
908 909
                    (name, entry_point.module_name,
                     '.'.join(entry_point.attrs))
Jim Fulton's avatar
Jim Fulton committed
910
                    )
911 912 913
            # The metadata on "old-style" distutils scripts is not retained by
            # distutils/setuptools, except by placing the original scripts in
            # /EGG-INFO/scripts/.
914 915
            if dist.metadata_isdir('scripts'):
                for name in dist.metadata_listdir('scripts'):
916
                    contents = dist.get_metadata('scripts/' + name)
917
                    distutils_scripts.append((name, contents))
918 919
        else:
            entry_points.append(req)
920

921 922 923 924 925 926 927
    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
928

929
        sname = os.path.join(dest, sname)
930 931
        spath, rpsetup = _relative_path_and_setup(sname, path, relative_paths)

932
        generated.extend(
Jim Fulton's avatar
Jim Fulton committed
933
            _script(module_name, attrs, spath, sname, arguments,
934
                    initialization, rpsetup)
935
            )
936

Reinout van Rees's avatar
Reinout van Rees committed
937
    for name, contents in distutils_scripts:
938 939 940 941 942 943 944 945 946 947 948
        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(
Jim Fulton's avatar
Jim Fulton committed
949
            _distutils_script(spath, sname, contents, initialization, rpsetup)
950 951
            )

952 953
    if interpreter:
        sname = os.path.join(dest, interpreter)
954
        spath, rpsetup = _relative_path_and_setup(sname, path, relative_paths)
955
        generated.extend(_pyscript(spath, sname, rpsetup, initialization))
956 957 958

    return generated

959

960 961
def _relative_path_and_setup(sname, path, relative_paths):
    if relative_paths:
962 963
        relative_paths = os.path.normcase(relative_paths)
        sname = os.path.normcase(os.path.abspath(sname))
964
        spath = ',\n  '.join(
965
            [_relativitize(os.path.normcase(path_item), sname, relative_paths)
966 967 968
             for path_item in path]
            )
        rpsetup = relative_paths_setup
969 970
        for i in range(_relative_depth(relative_paths, sname)):
            rpsetup += "base = os.path.dirname(base)\n"
971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988
    else:
        spath = repr(path)[1:-1].replace(', ', ',\n  ')
        rpsetup = ''
    return spath, rpsetup


def _relative_depth(common, path):
    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

989

990 991 992 993 994 995 996 997 998 999 1000 1001 1002
def _relative_path(common, path):
    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)

1003

1004 1005 1006 1007 1008 1009 1010
def _relativitize(path, script, 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, ''))
        ):
1011
        return "join(base, %r)" % _relative_path(common, path)
1012 1013 1014 1015 1016 1017 1018 1019
    else:
        return repr(path)


relative_paths_setup = """
import os

join = os.path.join
Jim Fulton's avatar
Jim Fulton committed
1020
base = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
1021 1022
"""

Jim Fulton's avatar
Jim Fulton committed
1023
def _script(module_name, attrs, path, dest, arguments, initialization, rsetup):
1024
    if is_win32:
Jim Fulton's avatar
Jim Fulton committed
1025
        dest += '-script.py'
1026

Jim Fulton's avatar
Jim Fulton committed
1027 1028
    python = _safe_arg(sys.executable)

1029
    contents = script_template % dict(
Jim Fulton's avatar
Jim Fulton committed
1030
        python = python,
1031
        path = path,
1032 1033
        module_name = module_name,
        attrs = attrs,
1034
        arguments = arguments,
1035
        initialization = initialization,
1036
        relative_paths_setup = rsetup,
1037
        )
1038 1039 1040
    return _create_script(contents, dest)


Jim Fulton's avatar
Jim Fulton committed
1041
def _distutils_script(path, dest, script_content, initialization, rsetup):
1042 1043
    if is_win32:
        dest += '-script.py'
1044

Reinout van Rees's avatar
Reinout van Rees committed
1045
    lines = script_content.splitlines(True)
1046 1047 1048 1049
    if not ('#!' in lines[0]) and ('python' in lines[0]):
        # The script doesn't follow distutil's rules.  Ignore it.
        return []
    original_content = ''.join(lines[1:])
Jim Fulton's avatar
Jim Fulton committed
1050 1051 1052

    python = _safe_arg(sys.executable)

1053
    contents = distutils_script_template % dict(
Jim Fulton's avatar
Jim Fulton committed
1054
        python = python,
1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
        path = path,
        initialization = initialization,
        relative_paths_setup = rsetup,
        original_content = original_content
        )
    return _create_script(contents, dest)


def _create_script(contents, dest):
    generated = []
    script = dest

1067 1068
    changed = not (os.path.exists(dest) and open(dest).read() == contents)

1069
    if is_win32:
1070
        # generate exe file and give the script a magic name:
1071
        win32_exe = os.path.splitext(dest)[0] # remove ".py"
1072
        if win32_exe.endswith('-script'):
1073 1074
            win32_exe = win32_exe[:-7] # remove "-script"
        win32_exe = win32_exe + '.exe' # add ".exe"
1075
        new_data = pkg_resources.resource_string('setuptools', 'cli.exe')
1076 1077 1078
        if (not os.path.exists(win32_exe) or
            (open(win32_exe, 'rb').read() != new_data)
            ):
1079
            # Only write it if it's different.
1080 1081
            open(win32_exe, 'wb').write(new_data)
        generated.append(win32_exe)
1082

1083 1084
    if changed:
        open(dest, 'w').write(contents)
1085 1086 1087 1088
        logger.info(
            "Generated script %r.",
            # Normalize for windows
            script.endswith('-script.py') and script[:-10] or script)
1089 1090

        try:
1091
            os.chmod(dest, 493) # 0755
1092 1093
        except (AttributeError, os.error):
            pass
1094

Jim Fulton's avatar
Jim Fulton committed
1095 1096
    generated.append(dest)
    return generated
1097

1098

Georgy Berdyshev's avatar
Georgy Berdyshev committed
1099 1100 1101 1102 1103 1104
if is_jython and jython_os_name == 'linux':
    script_header = '#!/usr/bin/env %(python)s'
else:
    script_header = '#!%(python)s'


1105
script_template = script_header + '''\
1106

1107
%(relative_paths_setup)s
1108 1109
import sys
sys.path[0:0] = [
1110
  %(path)s,
1111
  ]
1112
%(initialization)s
1113 1114 1115
import %(module_name)s

if __name__ == '__main__':
1116
    sys.exit(%(module_name)s.%(attrs)s(%(arguments)s))
1117 1118
'''

1119 1120 1121 1122 1123 1124 1125 1126 1127
distutils_script_template = script_header + '''\

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

1128
%(original_content)s'''
1129

1130

1131
def _pyscript(path, dest, rsetup, initialization=''):
Jim Fulton's avatar
Jim Fulton committed
1132
    generated = []
1133
    script = dest
1134
    if is_win32:
1135 1136
        dest += '-script.py'

Jim Fulton's avatar
Jim Fulton committed
1137 1138
    python = _safe_arg(sys.executable)

1139
    contents = py_script_template % dict(
Jim Fulton's avatar
Jim Fulton committed
1140
        python = python,
1141
        path = path,
1142
        relative_paths_setup = rsetup,
1143
        initialization=initialization,
1144 1145 1146
        )
    changed = not (os.path.exists(dest) and open(dest).read() == contents)

1147
    if is_win32:
Jim Fulton's avatar
Jim Fulton committed
1148
        # generate exe file and give the script a magic name:
1149 1150
        exe = script + '.exe'
        open(exe, 'wb').write(
1151
            pkg_resources.resource_string('setuptools', 'cli.exe')
Jim Fulton's avatar
Jim Fulton committed
1152
            )
1153
        generated.append(exe)
Jim Fulton's avatar
Jim Fulton committed
1154

1155 1156 1157
    if changed:
        open(dest, 'w').write(contents)
        try:
1158
            os.chmod(dest, 493) # 0755
1159 1160 1161 1162
        except (AttributeError, os.error):
            pass
        logger.info("Generated interpreter %r.", script)

Jim Fulton's avatar
Jim Fulton committed
1163 1164
    generated.append(dest)
    return generated
1165

Georgy Berdyshev's avatar
Georgy Berdyshev committed
1166 1167
py_script_template = script_header + '''\

1168
%(relative_paths_setup)s
1169
import sys
1170

1171
sys.path[0:0] = [
1172
  %(path)s,
1173
  ]
1174
%(initialization)s
1175

Jim Fulton's avatar
Jim Fulton committed
1176 1177
_interactive = True
if len(sys.argv) > 1:
Jim Fulton's avatar
Jim Fulton committed
1178
    _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:')
Jim Fulton's avatar
Jim Fulton committed
1179 1180 1181 1182 1183
    _interactive = False
    for (_opt, _val) in _options:
        if _opt == '-i':
            _interactive = True
        elif _opt == '-c':
1184
            exec(_val)
Jim Fulton's avatar
Jim Fulton committed
1185 1186 1187 1188 1189
        elif _opt == '-m':
            sys.argv[1:] = _args
            _args = []
            __import__("runpy").run_module(
                 _val, {}, "__main__", alter_sys=True)
1190

Jim Fulton's avatar
Jim Fulton committed
1191 1192
    if _args:
        sys.argv[:] = _args
Jim Fulton's avatar
Jim Fulton committed
1193
        __file__ = _args[0]
Jim Fulton's avatar
Jim Fulton committed
1194
        del _options, _args
1195 1196 1197
        __file__f = open(__file__)
        exec(compile(__file__f.read(), __file__, "exec"))
        __file__f.close(); del __file__f
Jim Fulton's avatar
Jim Fulton committed
1198 1199

if _interactive:
Jim Fulton's avatar
Jim Fulton committed
1200 1201
    del _interactive
    __import__("code").interact(banner="", local=globals())
1202
'''
1203

1204 1205
runsetup_template = """
import sys
1206
sys.path.insert(0, %(setupdir)r)
Jim Fulton's avatar
Jim Fulton committed
1207
sys.path.insert(0, %(distribute)r)
1208

1209
import os, setuptools
Jim Fulton's avatar
Jim Fulton committed
1210

1211
__file__ = %(__file__)r
Jim Fulton's avatar
Jim Fulton committed
1212

1213 1214
os.chdir(%(setupdir)r)
sys.argv[0] = %(setup)r
1215 1216

exec(compile(open(%(setup)r).read(), %(setup)r, 'exec'))
1217
"""
1218

1219

1220 1221 1222
class VersionConflict(zc.buildout.UserError):

    def __init__(self, err, ws):
1223 1224
        ws = list(ws)
        ws.sort()
1225 1226 1227
        self.err, self.ws = err, ws

    def __str__(self):
Jim Fulton's avatar
Jim Fulton committed
1228
        existing_dist, req = self.err.args
1229 1230 1231 1232 1233
        result = ["There is a version conflict.",
                  "We already have: %s" % existing_dist,
                  ]
        for dist in self.ws:
            if req in dist.requires():
1234
                result.append("but %s requires %r." % (dist, str(req)))
1235 1236
        return '\n'.join(result)

1237

1238 1239 1240
class MissingDistribution(zc.buildout.UserError):

    def __init__(self, req, ws):
1241 1242
        ws = list(ws)
        ws.sort()
1243 1244 1245 1246
        self.data = req, ws

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

1249
def _log_requirement(ws, req):
1250 1251
    if (not logger.isEnabledFor(logging.DEBUG) and
        not Installer._show_picked_versions):
1252
        # Sorting the working set and iterating over it's requirements
1253
        # is expensive, so short circuit the work if it won't even be
1254 1255 1256 1257 1258
        # logged.  When profiling a simple buildout with 10 parts with
        # identical and large working sets, this resulted in a
        # decrease of run time from 93.411 to 15.068 seconds, about a
        # 6 fold improvement.
        return
Jim Fulton's avatar
Jim Fulton committed
1259

1260 1261
    ws = list(ws)
    ws.sort()
1262
    for dist in ws:
1263 1264
        if req in dist.requires():
            logger.debug("  required by %s." % dist)
1265 1266 1267 1268
            req_ = str(req)
            if req_ not in Installer._required_by:
                Installer._required_by[req_] = set()
            Installer._required_by[req_].add(str(dist.as_requirement()))
1269

1270 1271 1272 1273 1274 1275 1276
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
1277

1278 1279 1280 1281 1282 1283
_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
1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294

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')):
1295
                # If it wasn't compiled, it may not be compilable
1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311
                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. :)
1312
                args = [sys.executable]
1313 1314
                if __debug__:
                    args.append('-O')
1315
                args.extend(['-m', 'py_compile', filepath])
1316

1317
                call_subprocess(args)
1318

1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331
def _constrained_requirement(constraint, requirement):
    return pkg_resources.Requirement.parse(
        "%s[%s]%s" % (
            requirement.project_name,
            ','.join(requirement.extras),
            _constrained_requirement_constraint(constraint, requirement)
            )
        )

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

1332 1333
IncompatibleVersionError = IncompatibleConstraintError # Backward compatibility

1334 1335 1336 1337 1338
def bad_constraint(constraint, requirement):
    logger.error("The constraint, %s, is not consistent with the "
                 "requirement, %r.", constraint, str(requirement))
    raise IncompatibleConstraintError("Bad constraint", constraint, requirement)

1339
_parse_constraint = re.compile(r'([<>]=?)\s*(\S+)').match
1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425
_comparef = {
    '>' : lambda x, y: x >  y,
    '>=': lambda x, y: x >= y,
    '<' : lambda x, y: x <  y,
    '<=': lambda x, y: x <= y,
    }
_opop = {'<': '>', '>': '<'}
_opeqop = {'<': '>=', '>': '<='}
def _constrained_requirement_constraint(constraint, requirement):

    # Simple cases:

    # No specs tp merge with:
    if not requirement.specs:
        if not constraint[0] in '<=>':
            constraint = '==' + constraint
        return constraint

    # Simple single-version constraint:
    if constraint[0] not in '<>':
        if constraint.startswith('='):
            assert constraint.startswith('==')
            constraint = constraint[2:]
        if constraint in requirement:
            return '=='+constraint
        bad_constraint(constraint, requirement)


    # OK, we have a complex constraint (<. <=, >=, or >) and specs.
    # In many cases, the spec needs to filter constraints.
    # In other cases, the constraints need to limit the constraint.

    specs = requirement.specs
    cop, cv = _parse_constraint(constraint).group(1, 2)
    pcv = pkg_resources.parse_version(cv)

    # Special case, all of the specs are == specs:
    if not [op for (op, v) in specs if op != '==']:
        # There aren't any non-== specs.

        # See if any of the specs satisfy the constraint:
        specs = [op+v for (op, v) in specs
                 if _comparef[cop](pkg_resources.parse_version(v), pcv)]
        if specs:
            return ','.join(specs)

        bad_constraint(constraint, requirement)

    cop0 = cop[0]

    # Normalize specs by splitting >= and <= specs. We meed tp do this
    # becaise these have really weird semantics. Also cache parsed
    # versions, which we'll need for comparisons:
    specs = []
    for op, v in requirement.specs:
        pv = pkg_resources.parse_version(v)
        if op == _opeqop[cop0]:
            specs.append((op[0], v, pv))
            specs.append(('==', v, pv))
        else:
            specs.append((op, v, pv))

    # Error if there are opposite specs that conflict with the constraint
    # and there are no equal specs that satisfy the constraint:
    if [v for (op, v, pv) in specs
        if op == _opop[cop0] and _comparef[_opop[cop0]](pv, pcv)
        ]:
        eqspecs = [op+v for (op, v, pv) in specs
                   if _comparef[cop](pv, pcv)]
        if eqspecs:
            # OK, we do, use these:
            return ','.join(eqspecs)

        bad_constraint(constraint, requirement)

    # We have a combination of range constraints and eq specs that
    # satisfy the requirement.

    # Return the constraint + the filtered specs
    return ','.join(
        op+v
        for (op, v) in (
            [(cop, cv)] +
            [(op, v) for (op, v, pv) in specs if _comparef[cop](pv, pcv)]
            )
        )