easy_install.py 46.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
url_match = re.compile('[a-z0-9+.-]+://').match
Reinout van Rees's avatar
Reinout van Rees committed
49
is_source_encoding_line = re.compile('coding[:=]\s*([-\w.]+)').search
50
# Source encoding regex from http://www.python.org/dev/peps/pep-0263/
51

52
is_win32 = sys.platform == 'win32'
Georgy Berdyshev's avatar
Georgy Berdyshev committed
53 54 55 56 57 58 59
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
60 61
distribute_loc = pkg_resources.working_set.find(
    pkg_resources.Requirement.parse('distribute')
62 63
    ).location

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

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

73

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

Tarek Ziad's avatar
Tarek Ziad committed
84

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

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

98 99 100 101 102 103
    if find_links:
        index.add_find_links(find_links)

    _indexes[key] = index
    return index

104
clear_index_cache = _indexes.clear
Jim Fulton's avatar
Jim Fulton committed
105

106
if is_win32:
Jim Fulton's avatar
Jim Fulton committed
107 108 109 110 111 112 113
    # 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

114 115 116 117 118
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
119

120 121

def _execute_permission():
122 123 124 125 126 127
    current_umask = os.umask(0o022)
    # os.umask only returns the current umask if you also give it one, so we
    # have to give it a dummy one and immediately set it back to the real
    # value...  Distribute does the same.
    os.umask(current_umask)
    return 0o777 - current_umask
128 129


130
_easy_install_cmd = 'from setuptools.command.easy_install import main; main()'
131

132 133
class Installer:

134
    _versions = {}
135 136
    _required_by = {}
    _picked_versions = {}
137
    _download_cache = None
138
    _install_from_cache = False
139
    _prefer_final = True
140
    _use_dependency_links = True
Jim Fulton's avatar
Jim Fulton committed
141
    _allow_picked_versions = True
142
    _store_picked_versions = False
143

144 145 146 147 148
    def __init__(self,
                 dest=None,
                 links=(),
                 index=None,
                 executable=sys.executable,
Jim Fulton's avatar
Jim Fulton committed
149
                 always_unzip=None, # Backward compat :/
150 151
                 path=None,
                 newest=True,
Jim Fulton's avatar
Jim Fulton committed
152
                 versions=None,
153
                 use_dependency_links=None,
Tarek Ziad's avatar
Tarek Ziad committed
154
                 allow_hosts=('*',)
155
                 ):
Jim Fulton's avatar
Jim Fulton committed
156
        assert executable == sys.executable, (executable, sys.executable)
157
        self._dest = dest
Tarek Ziad's avatar
Tarek Ziad committed
158
        self._allow_hosts = allow_hosts
159 160 161 162 163 164 165

        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
166

167 168
        if use_dependency_links is not None:
            self._use_dependency_links = use_dependency_links
169
        self._links = links = list(_fix_file_links(links))
170 171 172
        if self._download_cache and (self._download_cache not in links):
            links.insert(0, self._download_cache)

173
        self._index_url = index
Jim Fulton's avatar
Jim Fulton committed
174
        path = (path and path[:] or []) + buildout_and_distribute_path
175 176 177
        if dest is not None and dest not in path:
            path.insert(0, dest)
        self._path = path
178 179
        if self._dest is None:
            newest = False
180
        self._newest = newest
Jim Fulton's avatar
Jim Fulton committed
181 182
        self._env = pkg_resources.Environment(path)
        self._index = _get_index(index, links, self._allow_hosts)
183 184

        if versions is not None:
185
            self._versions = normalize_versions(versions)
186

187
    def _satisfied(self, req, source=None):
188 189
        dists = [dist for dist in self._env[req.project_name] if dist in req]
        if not dists:
190 191
            logger.debug('We have no distributions for %s that satisfies %r.',
                         req.project_name, str(req))
192

193
            return None, self._obtain(req, source)
194 195 196 197 198 199

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

203 204 205 206 207 208
        # 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
209

210 211 212 213 214 215 216 217 218 219 220 221 222 223
        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

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

226 227 228 229
        # 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:
230

Jim Fulton's avatar
Jim Fulton committed
231
        best_available = self._obtain(req, source)
232 233 234 235 236

        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(
237 238 239
                'There are no distros available that meet %r.\n'
                'Using our best, %s.',
                str(req), best_available)
240
            return best_we_have, None
241

242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
        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
267

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

273
    def _load_dist(self, dist):
Jim Fulton's avatar
Jim Fulton committed
274
        dists = pkg_resources.Environment(dist.location)[dist.project_name]
275 276
        assert len(dists) == 1
        return dists[0]
277

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

280 281
        tmp = tempfile.mkdtemp(dir=dest)
        try:
Jim Fulton's avatar
Jim Fulton committed
282
            path = distribute_loc
283

Jim Fulton's avatar
Jim Fulton committed
284
            args = [sys.executable, '-c', _easy_install_cmd, '-mZUNxd', tmp]
285 286
            level = logger.getEffectiveLevel()
            if level > 0:
287
                args.append('-q')
288
            elif level < 0:
289
                args.append('-v')
290

291
            args.append(spec)
292 293

            if level <= logging.DEBUG:
Jim Fulton's avatar
Jim Fulton committed
294 295
                logger.debug('Running easy_install:\n"%s"\npath=%s\n',
                             '" "'.join(args), path)
296 297

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

299 300 301
            exit_code = subprocess.call(
                list(args),
                env=dict(os.environ, PYTHONPATH=path))
302 303

            dists = []
Jim Fulton's avatar
Jim Fulton committed
304
            env = pkg_resources.Environment([tmp])
305 306
            for project in env:
                dists.extend(env[project])
307

308 309
            if exit_code:
                logger.error(
310 311
                    "An error occured when trying to install %s. "
                    "Look above this message for any errors that "
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
                    "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
342
                    if os.path.isdir(newloc):
343 344 345 346 347
                        shutil.rmtree(newloc)
                    else:
                        os.remove(newloc)
                os.rename(d.location, newloc)

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

350 351 352
                result.append(d)

            return result
353

354 355
        finally:
            shutil.rmtree(tmp)
356

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

361
        if index.obtain(requirement) is None:
362
            # Nothing is available.
363
            return None
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385

        # 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:
386 387
        best = []
        bestv = ()
388
        for dist in dists:
389 390 391 392 393 394 395 396 397
            distv = dist.parsed_version
            if distv > bestv:
                best = [dist]
                bestv = distv
            elif distv == bestv:
                best.append(dist)

        if not best:
            return None
398

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

402 403
        if self._download_cache:
            for dist in best:
404 405
                if (realpath(os.path.dirname(dist.location))
                    ==
406
                    self._download_cache
407
                    ):
408
                    return dist
409

410 411
        best.sort()
        return best[-1]
412

Jim Fulton's avatar
Jim Fulton committed
413 414
    def _fetch(self, dist, tmp, download_cache):
        if (download_cache
415
            and (realpath(os.path.dirname(dist.location)) == download_cache)
Jim Fulton's avatar
Jim Fulton committed
416 417 418 419 420
            ):
            return dist

        new_location = self._index.download(dist.location, tmp)
        if (download_cache
421
            and (realpath(new_location) == realpath(dist.location))
Jim Fulton's avatar
Jim Fulton committed
422 423
            and os.path.isfile(new_location)
            ):
Jim Fulton's avatar
Jim Fulton committed
424
            # distribute avoids making extra copies, but we want to copy
Jim Fulton's avatar
Jim Fulton committed
425 426 427
            # to the download cache
            shutil.copy2(new_location, tmp)
            new_location = os.path.join(tmp, os.path.basename(new_location))
428

Jim Fulton's avatar
Jim Fulton committed
429
        return dist.clone(location=new_location)
430

Jim Fulton's avatar
Jim Fulton committed
431
    def _get_dist(self, requirement, ws):
432

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

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

439
        if dist is None:
440 441 442 443 444 445 446
            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__)
447

448 449
            # Retrieve the dist:
            if avail is None:
450
                self._index.obtain(requirement)
451
                raise MissingDistribution(requirement, ws)
452

453 454 455
            # We may overwrite distributions, so clear importer
            # cache.
            sys.path_importer_cache.clear()
456

457
            tmp = self._download_cache
Jim Fulton's avatar
Jim Fulton committed
458 459 460
            if tmp is None:
                tmp = tempfile.mkdtemp('get_dist')

461
            try:
Jim Fulton's avatar
Jim Fulton committed
462
                dist = self._fetch(avail, tmp, self._download_cache)
463

464 465 466
                if dist is None:
                    raise zc.buildout.UserError(
                        "Couln't download distribution %s." % avail)
467

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

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

474 475 476 477 478
                    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:
479

Jim Fulton's avatar
Jim Fulton committed
480 481 482

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

484 485
                    redo_pyc(newloc)

486 487 488
                    # 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
489 490
                    dists = pkg_resources.Environment([newloc])[
                        dist.project_name]
491 492 493 494 495
                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)
496 497
                    for dist in dists:
                        redo_pyc(dist.location)
498

499 500 501
            finally:
                if tmp != self._download_cache:
                    shutil.rmtree(tmp)
502

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

507 508
        else:
            dists = [dist]
509

510
        for dist in dists:
511 512
            if (dist.has_metadata('dependency_links.txt')
                and not self._install_from_cache
513
                and self._use_dependency_links
514
                ):
515 516 517 518 519
                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
520
                        self._index = _get_index(self._index_url, self._links,
Tarek Ziad's avatar
Tarek Ziad committed
521
                                                 self._allow_hosts)
522 523 524 525 526 527 528 529 530 531

        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] == '==')
                ):
532 533
                logger.debug('Picked: %s = %s',
                             dist.project_name, dist.version)
534
                self._picked_versions[dist.project_name] = dist.version
535

Jim Fulton's avatar
Jim Fulton committed
536 537 538 539
                if not self._allow_picked_versions:
                    raise zc.buildout.UserError(
                        'Picked: %s = %s' % (dist.project_name, dist.version)
                        )
540 541

        return dists
542

Jim Fulton's avatar
Jim Fulton committed
543
    def _maybe_add_distribute(self, ws, dist):
544 545
        if dist.has_metadata('namespace_packages.txt'):
            for r in dist.requires():
Jim Fulton's avatar
Jim Fulton committed
546
                if r.project_name in ('setuptools', 'distribute'):
547 548
                    break
            else:
Jim Fulton's avatar
Jim Fulton committed
549
                # We have a namespace package but no requirement for distribute
550 551
                if dist.precedence == pkg_resources.DEVELOP_DIST:
                    logger.warn(
552
                        "Develop distribution: %s\n"
553
                        "uses namespace packages but the distribution "
Jim Fulton's avatar
Jim Fulton committed
554
                        "does not require distribute.",
555
                        dist)
Jim Fulton's avatar
Jim Fulton committed
556
                requirement = self._constrain(
Jim Fulton's avatar
Jim Fulton committed
557
                    pkg_resources.Requirement.parse('distribute')
Jim Fulton's avatar
Jim Fulton committed
558
                    )
559
                if ws.find(requirement) is None:
Jim Fulton's avatar
Jim Fulton committed
560
                    for dist in self._get_dist(requirement, ws):
561
                        ws.add(dist)
562 563


Jim Fulton's avatar
Jim Fulton committed
564
    def _constrain(self, requirement):
565
        constraint = self._versions.get(requirement.project_name.lower())
566 567
        if constraint:
            requirement = _constrained_requirement(constraint, requirement)
Jim Fulton's avatar
Jim Fulton committed
568 569
        return requirement

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

572
        logger.debug('Installing %s.', repr(specs)[1:-1])
573 574 575 576 577 578

        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
579
        requirements = [self._constrain(pkg_resources.Requirement.parse(spec))
580 581
                        for spec in specs]

582

Jim Fulton's avatar
Jim Fulton committed
583

584 585
        if working_set is None:
            ws = pkg_resources.WorkingSet([])
Jim Fulton's avatar
Jim Fulton committed
586
        else:
587
            ws = working_set
588

589
        for requirement in requirements:
Jim Fulton's avatar
Jim Fulton committed
590
            for dist in self._get_dist(requirement, ws):
591
                ws.add(dist)
Jim Fulton's avatar
Jim Fulton committed
592
                self._maybe_add_distribute(ws, dist)
593 594 595 596 597 598

        # 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.
        #
599 600 601
        # 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.
602 603 604
        while 1:
            try:
                ws.resolve(requirements)
605 606 607
            except pkg_resources.DistributionNotFound:
                err = sys.exc_info()[1]
                [requirement] = err.args
Jim Fulton's avatar
Jim Fulton committed
608
                requirement = self._constrain(requirement)
609
                if dest:
610 611 612 613
                    logger.debug('Getting required %r', str(requirement))
                else:
                    logger.debug('Adding required %r', str(requirement))
                _log_requirement(ws, requirement)
614

Jim Fulton's avatar
Jim Fulton committed
615
                for dist in self._get_dist(requirement, ws):
616
                    ws.add(dist)
Jim Fulton's avatar
Jim Fulton committed
617
                    self._maybe_add_distribute(ws, dist)
618 619
            except pkg_resources.VersionConflict:
                err = sys.exc_info()[1]
620
                raise VersionConflict(err, ws)
621 622
            else:
                break
623

624
        return ws
625

626
    def build(self, spec, build_ext):
627

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

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

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

640 641 642 643 644 645 646
        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
                )

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

649
        tmp = self._download_cache
Jim Fulton's avatar
Jim Fulton committed
650 651 652
        if tmp is None:
            tmp = tempfile.mkdtemp('get_dist')

653
        try:
Jim Fulton's avatar
Jim Fulton committed
654
            dist = self._fetch(avail, tmp, self._download_cache)
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675

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

677 678 679 680 681 682 683 684 685 686 687
                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)

688 689 690
                for dist in dists:
                    redo_pyc(dist.location)

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

695
        finally:
696 697 698
            if tmp != self._download_cache:
                shutil.rmtree(tmp)

699 700 701 702 703 704 705 706 707 708

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


709 710 711
def default_versions(versions=None):
    old = Installer._versions
    if versions is not None:
712
        Installer._versions = normalize_versions(versions)
713
    return old
714

715 716 717
def download_cache(path=-1):
    old = Installer._download_cache
    if path != -1:
718
        if path:
719
            path = realpath(path)
720 721 722
        Installer._download_cache = path
    return old

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

729 730 731 732 733 734
def prefer_final(setting=None):
    old = Installer._prefer_final
    if setting is not None:
        Installer._prefer_final = bool(setting)
    return old

735 736 737 738 739 740
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
741 742 743 744 745 746
def allow_picked_versions(setting=None):
    old = Installer._allow_picked_versions
    if setting is not None:
        Installer._allow_picked_versions = bool(setting)
    return old

747 748
def store_picked_versions(setting=None):
    old = Installer._store_picked_versions
749
    if setting is not None:
750
        Installer._store_picked_versions = bool(setting)
751 752 753
    return old


754 755
def install(specs, dest,
            links=(), index=None,
Jim Fulton's avatar
Jim Fulton committed
756 757
            executable=sys.executable,
            always_unzip=None, # Backward compat :/
758
            path=None, working_set=None, newest=True, versions=None,
759 760 761 762
            use_dependency_links=None, allow_hosts=('*',),
            include_site_packages=None,
            allowed_eggs_from_site_packages=None,
            ):
Jim Fulton's avatar
Jim Fulton committed
763
    assert executable == sys.executable, (executable, sys.executable)
764 765 766
    assert include_site_packages is None
    assert allowed_eggs_from_site_packages is None

Jim Fulton's avatar
Jim Fulton committed
767 768
    installer = Installer(dest, links, index, sys.executable,
                          always_unzip, path,
769
                          newest, versions, use_dependency_links,
Tarek Ziad's avatar
Tarek Ziad committed
770
                          allow_hosts=allow_hosts)
771 772 773 774 775 776
    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
777
          path=None, newest=True, versions=None, allow_hosts=('*',)):
Jim Fulton's avatar
Jim Fulton committed
778
    assert executable == sys.executable, (executable, sys.executable)
Jim Fulton's avatar
Jim Fulton committed
779
    installer = Installer(dest, links, index, executable,
Jim Fulton's avatar
Jim Fulton committed
780
                          True, path, newest,
Tarek Ziad's avatar
Tarek Ziad committed
781
                          versions, allow_hosts=allow_hosts)
782 783
    return installer.build(spec, build_ext)

784

785

786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
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)
802

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

806 807 808 809 810
    return result[0]

def develop(setup, dest,
            build_ext=None,
            executable=sys.executable):
Jim Fulton's avatar
Jim Fulton committed
811
    assert executable == sys.executable, (executable, sys.executable)
812 813 814 815 816
    if os.path.isdir(setup):
        directory = setup
        setup = os.path.join(directory, 'setup.py')
    else:
        directory = os.path.dirname(setup)
817

818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838
    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))

839
        os.write(fd, (runsetup_template % dict(
Jim Fulton's avatar
Jim Fulton committed
840
            distribute=distribute_loc,
841 842 843
            setupdir=directory,
            setup=setup,
            __file__ = setup,
844
            )).encode())
845 846

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

Jim Fulton's avatar
Jim Fulton committed
849
        args = [executable,  tsetup, '-q', 'develop', '-mxN', '-d', tmp3]
850 851

        log_level = logger.getEffectiveLevel()
852 853
        if log_level <= 0:
            if log_level == 0:
854
                del args[2]
855
            else:
856
                args[2] == '-v'
857
        if log_level < logging.DEBUG:
858
            logger.debug("in: %r\n%s", directory, ' '.join(args))
859

860
        call_subprocess(args)
861 862 863 864 865 866

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

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


869 870 871
def working_set(specs, executable, path=None,
                include_site_packages=None,
                allowed_eggs_from_site_packages=None):
Jim Fulton's avatar
Jim Fulton committed
872 873 874 875 876
    # Backward compat:
    if path is None:
        path = executable
    else:
        assert executable == sys.executable, (executable, sys.executable)
877 878 879
    assert include_site_packages is None
    assert allowed_eggs_from_site_packages is None

Jim Fulton's avatar
Jim Fulton committed
880
    return install(specs, None, path=path)
881

Jim Fulton's avatar
Jim Fulton committed
882
def scripts(reqs, working_set, executable, dest=None,
883 884 885
            scripts=None,
            extra_paths=(),
            arguments='',
886
            interpreter=None,
887
            initialization='',
888
            relative_paths=False,
889
            ):
Jim Fulton's avatar
Jim Fulton committed
890
    assert executable == sys.executable, (executable, sys.executable)
891

892 893
    path = [dist.location for dist in working_set]
    path.extend(extra_paths)
894 895 896 897 898
    # order preserving unique
    unique_path = []
    for p in path:
        if p not in unique_path:
            unique_path.append(p)
899
    path = list(map(realpath, unique_path))
900

901 902
    generated = []

903 904 905 906
    if isinstance(reqs, str):
        raise TypeError('Expected iterable of requirements or entry points,'
                        ' got string.')

907 908 909
    if initialization:
        initialization = '\n'+initialization+'\n'

910
    entry_points = []
911
    distutils_scripts = []
912 913 914 915
    for req in reqs:
        if isinstance(req, str):
            req = pkg_resources.Requirement.parse(req)
            dist = working_set.find(req)
916
            # regular console_scripts entry points
917
            for name in pkg_resources.get_entry_map(dist, 'console_scripts'):
918 919
                entry_point = dist.get_entry_info('console_scripts', name)
                entry_points.append(
920 921
                    (name, entry_point.module_name,
                     '.'.join(entry_point.attrs))
Jim Fulton's avatar
Jim Fulton committed
922
                    )
923 924 925
            # The metadata on "old-style" distutils scripts is not retained by
            # distutils/setuptools, except by placing the original scripts in
            # /EGG-INFO/scripts/.
926 927
            if dist.metadata_isdir('scripts'):
                for name in dist.metadata_listdir('scripts'):
Reinout van Rees's avatar
Reinout van Rees committed
928 929 930
                    if dist.metadata_isdir('scripts/' + name):
                        # Probably Python 3 __pycache__ directory.
                        continue
931
                    contents = dist.get_metadata('scripts/' + name)
932
                    distutils_scripts.append((name, contents))
933 934
        else:
            entry_points.append(req)
935

936 937 938 939 940 941 942
    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
943

944
        sname = os.path.join(dest, sname)
945 946
        spath, rpsetup = _relative_path_and_setup(sname, path, relative_paths)

947
        generated.extend(
Jim Fulton's avatar
Jim Fulton committed
948
            _script(module_name, attrs, spath, sname, arguments,
949
                    initialization, rpsetup)
950
            )
951

Reinout van Rees's avatar
Reinout van Rees committed
952
    for name, contents in distutils_scripts:
953 954 955 956 957 958 959 960 961 962 963
        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
964
            _distutils_script(spath, sname, contents, initialization, rpsetup)
965 966
            )

967 968
    if interpreter:
        sname = os.path.join(dest, interpreter)
969
        spath, rpsetup = _relative_path_and_setup(sname, path, relative_paths)
970
        generated.extend(_pyscript(spath, sname, rpsetup, initialization))
971 972 973

    return generated

974

975 976
def _relative_path_and_setup(sname, path, relative_paths):
    if relative_paths:
977 978
        relative_paths = os.path.normcase(relative_paths)
        sname = os.path.normcase(os.path.abspath(sname))
979
        spath = ',\n  '.join(
980
            [_relativitize(os.path.normcase(path_item), sname, relative_paths)
981 982 983
             for path_item in path]
            )
        rpsetup = relative_paths_setup
984 985
        for i in range(_relative_depth(relative_paths, sname)):
            rpsetup += "base = os.path.dirname(base)\n"
986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003
    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

1004

1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
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)

1018

1019 1020 1021 1022 1023 1024 1025
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, ''))
        ):
1026
        return "join(base, %r)" % _relative_path(common, path)
1027 1028 1029 1030 1031 1032 1033 1034
    else:
        return repr(path)


relative_paths_setup = """
import os

join = os.path.join
Jim Fulton's avatar
Jim Fulton committed
1035
base = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
1036 1037
"""

Jim Fulton's avatar
Jim Fulton committed
1038
def _script(module_name, attrs, path, dest, arguments, initialization, rsetup):
1039
    if is_win32:
Jim Fulton's avatar
Jim Fulton committed
1040
        dest += '-script.py'
1041

Jim Fulton's avatar
Jim Fulton committed
1042 1043
    python = _safe_arg(sys.executable)

1044
    contents = script_template % dict(
Jim Fulton's avatar
Jim Fulton committed
1045
        python = python,
1046
        path = path,
1047 1048
        module_name = module_name,
        attrs = attrs,
1049
        arguments = arguments,
1050
        initialization = initialization,
1051
        relative_paths_setup = rsetup,
1052
        )
1053 1054 1055
    return _create_script(contents, dest)


Jim Fulton's avatar
Jim Fulton committed
1056
def _distutils_script(path, dest, script_content, initialization, rsetup):
1057 1058
    if is_win32:
        dest += '-script.py'
1059

Reinout van Rees's avatar
Reinout van Rees committed
1060
    lines = script_content.splitlines(True)
1061 1062 1063
    if not ('#!' in lines[0]) and ('python' in lines[0]):
        # The script doesn't follow distutil's rules.  Ignore it.
        return []
Reinout van Rees's avatar
Reinout van Rees committed
1064
    source_encoding_line = ''
1065
    original_content = ''.join(lines[1:])
1066
    if (len(lines) > 1) and is_source_encoding_line(lines[1]):
Reinout van Rees's avatar
Reinout van Rees committed
1067 1068 1069
        # The second line contains a source encoding line. Copy it verbatim.
        source_encoding_line = lines[1].rstrip()
        original_content = ''.join(lines[2:])
Jim Fulton's avatar
Jim Fulton committed
1070 1071 1072

    python = _safe_arg(sys.executable)

1073
    contents = distutils_script_template % dict(
Jim Fulton's avatar
Jim Fulton committed
1074
        python = python,
Reinout van Rees's avatar
Reinout van Rees committed
1075
        source_encoding_line = source_encoding_line,
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
        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

1088 1089
    changed = not (os.path.exists(dest) and open(dest).read() == contents)

1090
    if is_win32:
1091
        # generate exe file and give the script a magic name:
1092
        win32_exe = os.path.splitext(dest)[0] # remove ".py"
1093
        if win32_exe.endswith('-script'):
1094 1095
            win32_exe = win32_exe[:-7] # remove "-script"
        win32_exe = win32_exe + '.exe' # add ".exe"
1096
        new_data = pkg_resources.resource_string('setuptools', 'cli.exe')
1097 1098 1099
        if (not os.path.exists(win32_exe) or
            (open(win32_exe, 'rb').read() != new_data)
            ):
1100
            # Only write it if it's different.
1101 1102
            open(win32_exe, 'wb').write(new_data)
        generated.append(win32_exe)
1103

1104 1105
    if changed:
        open(dest, 'w').write(contents)
1106 1107 1108 1109
        logger.info(
            "Generated script %r.",
            # Normalize for windows
            script.endswith('-script.py') and script[:-10] or script)
1110 1111

        try:
1112
            os.chmod(dest, _execute_permission())
1113 1114
        except (AttributeError, os.error):
            pass
1115

Jim Fulton's avatar
Jim Fulton committed
1116 1117
    generated.append(dest)
    return generated
1118

1119

Georgy Berdyshev's avatar
Georgy Berdyshev committed
1120 1121 1122 1123 1124 1125
if is_jython and jython_os_name == 'linux':
    script_header = '#!/usr/bin/env %(python)s'
else:
    script_header = '#!%(python)s'


1126
script_template = script_header + '''\
1127

1128
%(relative_paths_setup)s
1129 1130
import sys
sys.path[0:0] = [
1131
  %(path)s,
1132
  ]
1133
%(initialization)s
1134 1135 1136
import %(module_name)s

if __name__ == '__main__':
1137
    sys.exit(%(module_name)s.%(attrs)s(%(arguments)s))
1138 1139
'''

1140 1141
distutils_script_template = script_header + '''\

Reinout van Rees's avatar
Reinout van Rees committed
1142
%(source_encoding_line)s
1143 1144 1145 1146 1147 1148 1149
%(relative_paths_setup)s
import sys
sys.path[0:0] = [
  %(path)s,
  ]
%(initialization)s

1150
%(original_content)s'''
1151

1152

1153
def _pyscript(path, dest, rsetup, initialization=''):
Jim Fulton's avatar
Jim Fulton committed
1154
    generated = []
1155
    script = dest
1156
    if is_win32:
1157 1158
        dest += '-script.py'

Jim Fulton's avatar
Jim Fulton committed
1159
    python = _safe_arg(sys.executable)
Reinout van Rees's avatar
Reinout van Rees committed
1160 1161
    if path:
        path += ','  # Courtesy comma at the end of the list.
Jim Fulton's avatar
Jim Fulton committed
1162

1163
    contents = py_script_template % dict(
Jim Fulton's avatar
Jim Fulton committed
1164
        python = python,
1165
        path = path,
1166
        relative_paths_setup = rsetup,
1167
        initialization=initialization,
1168 1169 1170
        )
    changed = not (os.path.exists(dest) and open(dest).read() == contents)

1171
    if is_win32:
Jim Fulton's avatar
Jim Fulton committed
1172
        # generate exe file and give the script a magic name:
1173 1174
        exe = script + '.exe'
        open(exe, 'wb').write(
1175
            pkg_resources.resource_string('setuptools', 'cli.exe')
Jim Fulton's avatar
Jim Fulton committed
1176
            )
1177
        generated.append(exe)
Jim Fulton's avatar
Jim Fulton committed
1178

1179 1180 1181
    if changed:
        open(dest, 'w').write(contents)
        try:
1182
            os.chmod(dest, _execute_permission())
1183 1184 1185 1186
        except (AttributeError, os.error):
            pass
        logger.info("Generated interpreter %r.", script)

Jim Fulton's avatar
Jim Fulton committed
1187 1188
    generated.append(dest)
    return generated
1189

Georgy Berdyshev's avatar
Georgy Berdyshev committed
1190 1191
py_script_template = script_header + '''\

1192
%(relative_paths_setup)s
1193
import sys
1194

1195
sys.path[0:0] = [
Reinout van Rees's avatar
Reinout van Rees committed
1196
  %(path)s
1197
  ]
1198
%(initialization)s
1199

Jim Fulton's avatar
Jim Fulton committed
1200 1201
_interactive = True
if len(sys.argv) > 1:
Jim Fulton's avatar
Jim Fulton committed
1202
    _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:')
Jim Fulton's avatar
Jim Fulton committed
1203 1204 1205 1206 1207
    _interactive = False
    for (_opt, _val) in _options:
        if _opt == '-i':
            _interactive = True
        elif _opt == '-c':
1208
            exec(_val)
Jim Fulton's avatar
Jim Fulton committed
1209 1210 1211 1212 1213
        elif _opt == '-m':
            sys.argv[1:] = _args
            _args = []
            __import__("runpy").run_module(
                 _val, {}, "__main__", alter_sys=True)
1214

Jim Fulton's avatar
Jim Fulton committed
1215 1216
    if _args:
        sys.argv[:] = _args
Jim Fulton's avatar
Jim Fulton committed
1217
        __file__ = _args[0]
Jim Fulton's avatar
Jim Fulton committed
1218
        del _options, _args
1219 1220 1221
        __file__f = open(__file__)
        exec(compile(__file__f.read(), __file__, "exec"))
        __file__f.close(); del __file__f
Jim Fulton's avatar
Jim Fulton committed
1222 1223

if _interactive:
Jim Fulton's avatar
Jim Fulton committed
1224 1225
    del _interactive
    __import__("code").interact(banner="", local=globals())
1226
'''
1227

1228 1229
runsetup_template = """
import sys
1230
sys.path.insert(0, %(setupdir)r)
Jim Fulton's avatar
Jim Fulton committed
1231
sys.path.insert(0, %(distribute)r)
1232

1233
import os, setuptools
Jim Fulton's avatar
Jim Fulton committed
1234

1235
__file__ = %(__file__)r
Jim Fulton's avatar
Jim Fulton committed
1236

1237 1238
os.chdir(%(setupdir)r)
sys.argv[0] = %(setup)r
1239 1240

exec(compile(open(%(setup)r).read(), %(setup)r, 'exec'))
1241
"""
1242

1243

1244 1245 1246
class VersionConflict(zc.buildout.UserError):

    def __init__(self, err, ws):
1247 1248
        ws = list(ws)
        ws.sort()
1249 1250 1251
        self.err, self.ws = err, ws

    def __str__(self):
Jim Fulton's avatar
Jim Fulton committed
1252
        existing_dist, req = self.err.args
1253 1254 1255 1256 1257
        result = ["There is a version conflict.",
                  "We already have: %s" % existing_dist,
                  ]
        for dist in self.ws:
            if req in dist.requires():
1258
                result.append("but %s requires %r." % (dist, str(req)))
1259 1260
        return '\n'.join(result)

1261

1262 1263 1264
class MissingDistribution(zc.buildout.UserError):

    def __init__(self, req, ws):
1265 1266
        ws = list(ws)
        ws.sort()
1267 1268 1269 1270
        self.data = req, ws

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

1273
def _log_requirement(ws, req):
1274
    if (not logger.isEnabledFor(logging.DEBUG) and
1275
        not Installer._store_picked_versions):
1276
        # Sorting the working set and iterating over it's requirements
1277
        # is expensive, so short circuit the work if it won't even be
1278 1279 1280 1281 1282
        # 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
1283

1284 1285
    ws = list(ws)
    ws.sort()
1286
    for dist in ws:
1287 1288
        if req in dist.requires():
            logger.debug("  required by %s." % dist)
1289 1290 1291 1292
            req_ = str(req)
            if req_ not in Installer._required_by:
                Installer._required_by[req_] = set()
            Installer._required_by[req_].add(str(dist.as_requirement()))
1293

1294 1295 1296 1297 1298 1299 1300
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
1301

1302 1303 1304 1305 1306 1307
_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
1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318

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')):
1319
                # If it wasn't compiled, it may not be compilable
1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335
                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. :)
1336
                args = [sys.executable]
1337 1338
                if __debug__:
                    args.append('-O')
1339
                args.extend(['-m', 'py_compile', filepath])
1340

1341
                call_subprocess(args)
1342

1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355
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.
    """

1356 1357
IncompatibleVersionError = IncompatibleConstraintError # Backward compatibility

1358 1359 1360 1361 1362
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)

1363
_parse_constraint = re.compile(r'([<>]=?)\s*(\S+)').match
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 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449
_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)]
            )
        )