easy_install.py 40.8 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 os
Jim Fulton's avatar
Jim Fulton committed
22 23 24 25 26 27
import sys

######################################################################
# handle -S

def normpath(p):
28 29
    return p[:-1] if p.endswith(os.path.sep) else p

Jim Fulton's avatar
Jim Fulton committed
30 31 32 33 34 35 36 37
no_site = 'site' not in sys.modules
if no_site:
    initial_paths = set(map(normpath, sys.path))
    import site
    sys.path[:] = [p for p in sys.path if normpath(p) in initial_paths]
#
######################################################################

38
import distutils.errors
39 40
import glob
import logging
41
import pkg_resources
42 43 44
import py_compile
import re
import setuptools.archive_util
45 46
import setuptools.command.setopt
import setuptools.package_index
47
import shutil
48
import subprocess
49
import tempfile
50
import zc.buildout
51
import zipimport
52

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

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

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

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

66
is_win32 = sys.platform == 'win32'
Georgy Berdyshev's avatar
Georgy Berdyshev committed
67 68 69 70 71 72 73
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
74 75
distribute_loc = pkg_resources.working_set.find(
    pkg_resources.Requirement.parse('distribute')
76 77
    ).location

Jim Fulton's avatar
Jim Fulton committed
78 79 80
# Include buildout and distribute eggs in paths
buildout_and_distribute_path = [
    distribute_loc,
81 82
    pkg_resources.working_set.find(
        pkg_resources.Requirement.parse('zc.buildout')).location,
83 84
    ]

85

Jim Fulton's avatar
Jim Fulton committed
86 87 88 89
class IncompatibleVersionError(zc.buildout.UserError):
    """A specified version is incompatible with a given requirement.
    """

Tarek Ziad's avatar
Tarek Ziad committed
90 91 92 93 94 95 96 97 98 99 100
FILE_SCHEME = re.compile('file://', re.I).match

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

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

Tarek Ziad's avatar
Tarek Ziad committed
102

103
_indexes = {}
Jim Fulton's avatar
Jim Fulton committed
104 105
def _get_index(index_url, find_links, allow_hosts=('*',)):
    key = index_url, tuple(find_links)
106 107 108 109
    index = _indexes.get(key)
    if index is not None:
        return index

110 111
    if index_url is None:
        index_url = default_index_url
Jim Fulton's avatar
Jim Fulton committed
112
    index = AllowHostsPackageIndex(index_url, hosts=allow_hosts)
113

114 115 116 117 118 119
    if find_links:
        index.add_find_links(find_links)

    _indexes[key] = index
    return index

120
clear_index_cache = _indexes.clear
Jim Fulton's avatar
Jim Fulton committed
121

122
if is_win32:
Jim Fulton's avatar
Jim Fulton committed
123 124 125 126 127 128 129
    # 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

130 131 132 133 134
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
135

136
_easy_install_cmd = 'from setuptools.command.easy_install import main; main()'
137

138 139
class Installer:

140
    _versions = {}
141
    _download_cache = None
142
    _install_from_cache = False
143
    _prefer_final = True
144
    _use_dependency_links = True
Jim Fulton's avatar
Jim Fulton committed
145
    _allow_picked_versions = True
146

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

        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
169

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

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

        if versions is not None:
            self._versions = versions
189

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

196
            return None, self._obtain(req, source)
197 198 199 200 201 202

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

206 207 208 209 210 211
        # 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
212

213 214 215 216 217 218 219 220 221 222 223 224 225 226
        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

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

229 230 231 232
        # 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:
233

234
        if self._dest is not None:
235
            best_available = self._obtain(req, source)
236 237 238 239 240 241 242
        else:
            best_available = None

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

248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
        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
273

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

279
    def _load_dist(self, dist):
Jim Fulton's avatar
Jim Fulton committed
280
        dists = pkg_resources.Environment(dist.location)[dist.project_name]
281 282
        assert len(dists) == 1
        return dists[0]
283

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

286 287
        tmp = tempfile.mkdtemp(dir=dest)
        try:
Jim Fulton's avatar
Jim Fulton committed
288
            path = distribute_loc
289

Jim Fulton's avatar
Jim Fulton committed
290
            args = [sys.executable, '-c', _easy_install_cmd, '-mZUNxd', tmp]
Jim Fulton's avatar
Jim Fulton committed
291 292
            if no_site:
                args.insert(1, '-S')
293 294
            level = logger.getEffectiveLevel()
            if level > 0:
295
                args.append('-q')
296
            elif level < 0:
297
                args.append('-v')
298

299
            args.append(spec)
300 301

            if level <= logging.DEBUG:
Jim Fulton's avatar
Jim Fulton committed
302 303
                logger.debug('Running easy_install:\n"%s"\npath=%s\n',
                             '" "'.join(args), path)
304 305

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

307 308 309
            exit_code = subprocess.call(
                list(args),
                env=dict(os.environ, PYTHONPATH=path))
310 311

            dists = []
Jim Fulton's avatar
Jim Fulton committed
312
            env = pkg_resources.Environment([tmp])
313 314
            for project in env:
                dists.extend(env[project])
315

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

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

358 359 360
                result.append(d)

            return result
361

362 363
        finally:
            shutil.rmtree(tmp)
364

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

369
        if index.obtain(requirement) is None:
370
            # Nothing is available.
371
            return None
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393

        # 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:
394 395
        best = []
        bestv = ()
396
        for dist in dists:
397 398 399 400 401 402 403 404 405
            distv = dist.parsed_version
            if distv > bestv:
                best = [dist]
                bestv = distv
            elif distv == bestv:
                best.append(dist)

        if not best:
            return None
406

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

410 411
        if self._download_cache:
            for dist in best:
412 413
                if (realpath(os.path.dirname(dist.location))
                    ==
414
                    self._download_cache
415
                    ):
416
                    return dist
417

418 419
        best.sort()
        return best[-1]
420

Jim Fulton's avatar
Jim Fulton committed
421 422
    def _fetch(self, dist, tmp, download_cache):
        if (download_cache
423
            and (realpath(os.path.dirname(dist.location)) == download_cache)
Jim Fulton's avatar
Jim Fulton committed
424 425 426 427 428
            ):
            return dist

        new_location = self._index.download(dist.location, tmp)
        if (download_cache
429
            and (realpath(new_location) == realpath(dist.location))
Jim Fulton's avatar
Jim Fulton committed
430 431
            and os.path.isfile(new_location)
            ):
Jim Fulton's avatar
Jim Fulton committed
432
            # distribute avoids making extra copies, but we want to copy
Jim Fulton's avatar
Jim Fulton committed
433 434 435
            # to the download cache
            shutil.copy2(new_location, tmp)
            new_location = os.path.join(tmp, os.path.basename(new_location))
436

Jim Fulton's avatar
Jim Fulton committed
437
        return dist.clone(location=new_location)
438

Jim Fulton's avatar
Jim Fulton committed
439
    def _get_dist(self, requirement, ws):
440

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

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

447 448
        if dist is None:
            if self._dest is not None:
449
                logger.info(*__doing__)
450

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

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

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

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

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

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

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

476 477 478 479 480
                    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:
481

Jim Fulton's avatar
Jim Fulton committed
482 483 484

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

486 487
                    redo_pyc(newloc)

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

501 502 503
            finally:
                if tmp != self._download_cache:
                    shutil.rmtree(tmp)
504

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

509 510
        else:
            dists = [dist]
511

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

        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] == '==')
                ):
534 535
                logger.debug('Picked: %s = %s',
                             dist.project_name, dist.version)
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 565 566 567 568
    def _constrain(self, requirement):
        version = self._versions.get(requirement.project_name)
        if version:
            if version not in requirement:
                logger.error("The version, %s, is not consistent with the "
569
                             "requirement, %r.", version, str(requirement))
Jim Fulton's avatar
Jim Fulton committed
570
                raise IncompatibleVersionError("Bad version", version)
571

Jim Fulton's avatar
Jim Fulton committed
572
            requirement = pkg_resources.Requirement.parse(
573 574 575
                "%s[%s] ==%s" % (requirement.project_name,
                               ','.join(requirement.extras),
                               version))
Jim Fulton's avatar
Jim Fulton committed
576 577 578

        return requirement

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

581
        logger.debug('Installing %s.', repr(specs)[1:-1])
582 583 584 585 586 587

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

591

Jim Fulton's avatar
Jim Fulton committed
592

593 594
        if working_set is None:
            ws = pkg_resources.WorkingSet([])
Jim Fulton's avatar
Jim Fulton committed
595
        else:
596
            ws = working_set
597

598
        for requirement in requirements:
Jim Fulton's avatar
Jim Fulton committed
599
            for dist in self._get_dist(requirement, ws):
600
                ws.add(dist)
Jim Fulton's avatar
Jim Fulton committed
601
                self._maybe_add_distribute(ws, dist)
602 603 604 605 606 607

        # 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.
        #
608 609 610
        # 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.
611 612 613
        while 1:
            try:
                ws.resolve(requirements)
614 615 616
            except pkg_resources.DistributionNotFound:
                err = sys.exc_info()[1]
                [requirement] = err.args
Jim Fulton's avatar
Jim Fulton committed
617
                requirement = self._constrain(requirement)
618
                if dest:
619 620 621 622
                    logger.debug('Getting required %r', str(requirement))
                else:
                    logger.debug('Adding required %r', str(requirement))
                _log_requirement(ws, requirement)
623

Jim Fulton's avatar
Jim Fulton committed
624
                for dist in self._get_dist(requirement, ws):
625
                    ws.add(dist)
Jim Fulton's avatar
Jim Fulton committed
626
                    self._maybe_add_distribute(ws, dist)
627 628
            except pkg_resources.VersionConflict:
                err = sys.exc_info()[1]
629
                raise VersionConflict(err, ws)
630 631
            else:
                break
632

633
        return ws
634

635
    def build(self, spec, build_ext):
636

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

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

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

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

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

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

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

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

690 691 692
                for dist in dists:
                    redo_pyc(dist.location)

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

697
        finally:
698 699 700
            if tmp != self._download_cache:
                shutil.rmtree(tmp)

701 702 703 704 705
def default_versions(versions=None):
    old = Installer._versions
    if versions is not None:
        Installer._versions = versions
    return old
706

707 708 709
def download_cache(path=-1):
    old = Installer._download_cache
    if path != -1:
710
        if path:
711
            path = realpath(path)
712 713 714
        Installer._download_cache = path
    return old

715 716 717 718 719 720
def install_from_cache(setting=None):
    old = Installer._install_from_cache
    if setting is not None:
        Installer._install_from_cache = bool(setting)
    return old

721 722 723 724 725 726
def prefer_final(setting=None):
    old = Installer._prefer_final
    if setting is not None:
        Installer._prefer_final = bool(setting)
    return old

727 728 729 730 731 732
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
733 734 735 736 737 738
def allow_picked_versions(setting=None):
    old = Installer._allow_picked_versions
    if setting is not None:
        Installer._allow_picked_versions = bool(setting)
    return old

739 740
def install(specs, dest,
            links=(), index=None,
Jim Fulton's avatar
Jim Fulton committed
741 742
            executable=sys.executable,
            always_unzip=None, # Backward compat :/
743
            path=None, working_set=None, newest=True, versions=None,
Tarek Ziad's avatar
Tarek Ziad committed
744
            use_dependency_links=None, allow_hosts=('*',)):
Jim Fulton's avatar
Jim Fulton committed
745 746 747
    assert executable == sys.executable, (executable, sys.executable)
    installer = Installer(dest, links, index, sys.executable,
                          always_unzip, path,
748
                          newest, versions, use_dependency_links,
Tarek Ziad's avatar
Tarek Ziad committed
749
                          allow_hosts=allow_hosts)
750 751 752 753 754 755
    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
756
          path=None, newest=True, versions=None, allow_hosts=('*',)):
Jim Fulton's avatar
Jim Fulton committed
757
    assert executable == sys.executable, (executable, sys.executable)
Jim Fulton's avatar
Jim Fulton committed
758
    installer = Installer(dest, links, index, executable,
Jim Fulton's avatar
Jim Fulton committed
759
                          True, path, newest,
Tarek Ziad's avatar
Tarek Ziad committed
760
                          versions, allow_hosts=allow_hosts)
761 762
    return installer.build(spec, build_ext)

763

764

765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780
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)
781

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

785 786 787 788 789
    return result[0]

def develop(setup, dest,
            build_ext=None,
            executable=sys.executable):
Jim Fulton's avatar
Jim Fulton committed
790
    assert executable == sys.executable, (executable, sys.executable)
791 792 793 794 795
    if os.path.isdir(setup):
        directory = setup
        setup = os.path.join(directory, 'setup.py')
    else:
        directory = os.path.dirname(setup)
796

797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817
    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))

818
        os.write(fd, (runsetup_template % dict(
Jim Fulton's avatar
Jim Fulton committed
819
            distribute=distribute_loc,
820 821 822
            setupdir=directory,
            setup=setup,
            __file__ = setup,
823
            )).encode())
824 825

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

Jim Fulton's avatar
Jim Fulton committed
828
        args = [executable,  tsetup, '-q', 'develop', '-mxN', '-d', tmp3]
829 830

        log_level = logger.getEffectiveLevel()
831 832
        if log_level <= 0:
            if log_level == 0:
833
                del args[2]
834
            else:
835
                args[2] == '-v'
836
        if log_level < logging.DEBUG:
837
            logger.debug("in: %r\n%s", directory, ' '.join(args))
838

Jim Fulton's avatar
Jim Fulton committed
839 840 841
        if no_site:
            args.insert(1, '-S')

842
        call_subprocess(args)
843 844 845 846 847 848

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

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


Jim Fulton's avatar
Jim Fulton committed
851 852 853 854 855 856 857
def working_set(specs, executable, path=None):
    # Backward compat:
    if path is None:
        path = executable
    else:
        assert executable == sys.executable, (executable, sys.executable)
    return install(specs, None, path=path)
858

Jim Fulton's avatar
Jim Fulton committed
859
def scripts(reqs, working_set, executable, dest=None,
860 861 862
            scripts=None,
            extra_paths=(),
            arguments='',
863
            interpreter=None,
864
            initialization='',
865
            relative_paths=False,
866
            ):
Jim Fulton's avatar
Jim Fulton committed
867
    assert executable == sys.executable, (executable, sys.executable)
868

869 870
    path = [dist.location for dist in working_set]
    path.extend(extra_paths)
871 872 873 874 875
    # order preserving unique
    unique_path = []
    for p in path:
        if p not in unique_path:
            unique_path.append(p)
876
    path = list(map(realpath, unique_path))
877

878 879
    generated = []

880 881 882 883
    if isinstance(reqs, str):
        raise TypeError('Expected iterable of requirements or entry points,'
                        ' got string.')

884 885 886
    if initialization:
        initialization = '\n'+initialization+'\n'

887
    entry_points = []
888
    distutils_scripts = []
889 890 891 892
    for req in reqs:
        if isinstance(req, str):
            req = pkg_resources.Requirement.parse(req)
            dist = working_set.find(req)
893
            # regular console_scripts entry points
894
            for name in pkg_resources.get_entry_map(dist, 'console_scripts'):
895 896
                entry_point = dist.get_entry_info('console_scripts', name)
                entry_points.append(
897 898
                    (name, entry_point.module_name,
                     '.'.join(entry_point.attrs))
Jim Fulton's avatar
Jim Fulton committed
899
                    )
900 901 902
            # The metadata on "old-style" distutils scripts is not retained by
            # distutils/setuptools, except by placing the original scripts in
            # /EGG-INFO/scripts/.
903 904
            if dist.metadata_isdir('scripts'):
                for name in dist.metadata_listdir('scripts'):
905
                    contents = dist.get_metadata('scripts/' + name)
906
                    distutils_scripts.append((name, contents))
907 908
        else:
            entry_points.append(req)
909

910 911 912 913 914 915 916
    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
917

918
        sname = os.path.join(dest, sname)
919 920
        spath, rpsetup = _relative_path_and_setup(sname, path, relative_paths)

921
        generated.extend(
Jim Fulton's avatar
Jim Fulton committed
922
            _script(module_name, attrs, spath, sname, arguments,
923
                    initialization, rpsetup)
924
            )
925

Reinout van Rees's avatar
Reinout van Rees committed
926
    for name, contents in distutils_scripts:
927 928 929 930 931 932 933 934 935 936 937
        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
938
            _distutils_script(spath, sname, contents, initialization, rpsetup)
939 940
            )

941 942
    if interpreter:
        sname = os.path.join(dest, interpreter)
943
        spath, rpsetup = _relative_path_and_setup(sname, path, relative_paths)
Jim Fulton's avatar
Jim Fulton committed
944
        generated.extend(_pyscript(spath, sname, rpsetup))
945 946 947

    return generated

948

949 950
def _relative_path_and_setup(sname, path, relative_paths):
    if relative_paths:
951 952
        relative_paths = os.path.normcase(relative_paths)
        sname = os.path.normcase(os.path.abspath(sname))
953
        spath = ',\n  '.join(
954
            [_relativitize(os.path.normcase(path_item), sname, relative_paths)
955 956 957
             for path_item in path]
            )
        rpsetup = relative_paths_setup
958 959
        for i in range(_relative_depth(relative_paths, sname)):
            rpsetup += "base = os.path.dirname(base)\n"
960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977
    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

978

979 980 981 982 983 984 985 986 987 988 989 990 991
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)

992

993 994 995 996 997 998 999
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, ''))
        ):
1000
        return "join(base, %r)" % _relative_path(common, path)
1001 1002 1003 1004 1005 1006 1007 1008
    else:
        return repr(path)


relative_paths_setup = """
import os

join = os.path.join
Jim Fulton's avatar
Jim Fulton committed
1009
base = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
1010 1011
"""

Jim Fulton's avatar
Jim Fulton committed
1012
def _script(module_name, attrs, path, dest, arguments, initialization, rsetup):
Jim Fulton's avatar
Jim Fulton committed
1013
    generated = []
1014
    script = dest
1015
    if is_win32:
Jim Fulton's avatar
Jim Fulton committed
1016
        dest += '-script.py'
1017

Jim Fulton's avatar
Jim Fulton committed
1018 1019
    python = _safe_arg(sys.executable)
    if no_site:
1020
        python += ' -S'
Jim Fulton's avatar
Jim Fulton committed
1021

1022
    contents = script_template % dict(
Jim Fulton's avatar
Jim Fulton committed
1023
        python = python,
1024
        path = path,
1025 1026
        module_name = module_name,
        attrs = attrs,
1027
        arguments = arguments,
1028
        initialization = initialization,
1029
        relative_paths_setup = rsetup,
1030
        )
1031 1032 1033
    return _create_script(contents, dest)


Jim Fulton's avatar
Jim Fulton committed
1034
def _distutils_script(path, dest, script_content, initialization, rsetup):
1035

Reinout van Rees's avatar
Reinout van Rees committed
1036
    lines = script_content.splitlines(True)
1037 1038 1039 1040
    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
1041 1042 1043

    python = _safe_arg(sys.executable)
    if no_site:
1044
        python += ' -S'
Jim Fulton's avatar
Jim Fulton committed
1045

1046
    contents = distutils_script_template % dict(
Jim Fulton's avatar
Jim Fulton committed
1047
        python = python,
1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061
        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
    if is_win32:
        dest += '-script.py'

1062 1063
    changed = not (os.path.exists(dest) and open(dest).read() == contents)

1064
    if is_win32:
1065
        # generate exe file and give the script a magic name:
1066
        exe = script+'.exe'
Jim Fulton's avatar
Jim Fulton committed
1067
        new_data = pkg_resources.resource_string('distribute', 'cli.exe')
1068 1069 1070
        if not os.path.exists(exe) or (open(exe, 'rb').read() != new_data):
            # Only write it if it's different.
            open(exe, 'wb').write(new_data)
1071
        generated.append(exe)
1072

1073 1074 1075 1076 1077
    if changed:
        open(dest, 'w').write(contents)
        logger.info("Generated script %r.", script)

        try:
1078
            os.chmod(dest, 0o755)
1079 1080
        except (AttributeError, os.error):
            pass
1081

Jim Fulton's avatar
Jim Fulton committed
1082 1083
    generated.append(dest)
    return generated
1084

1085

Georgy Berdyshev's avatar
Georgy Berdyshev committed
1086 1087 1088 1089 1090 1091
if is_jython and jython_os_name == 'linux':
    script_header = '#!/usr/bin/env %(python)s'
else:
    script_header = '#!%(python)s'


1092
script_template = script_header + '''\
1093

1094
%(relative_paths_setup)s
1095 1096
import sys
sys.path[0:0] = [
1097
  %(path)s,
1098
  ]
1099
%(initialization)s
1100 1101 1102
import %(module_name)s

if __name__ == '__main__':
1103
    sys.exit(%(module_name)s.%(attrs)s(%(arguments)s))
1104 1105
'''

1106 1107 1108 1109 1110 1111 1112 1113 1114
distutils_script_template = script_header + '''\

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

1115
%(original_content)s'''
1116

1117

Jim Fulton's avatar
Jim Fulton committed
1118
def _pyscript(path, dest, rsetup):
Jim Fulton's avatar
Jim Fulton committed
1119
    generated = []
1120
    script = dest
1121
    if is_win32:
1122 1123
        dest += '-script.py'

Jim Fulton's avatar
Jim Fulton committed
1124 1125
    python = _safe_arg(sys.executable)
    if no_site:
1126
        python += ' -S'
Jim Fulton's avatar
Jim Fulton committed
1127

1128
    contents = py_script_template % dict(
Jim Fulton's avatar
Jim Fulton committed
1129
        python = python,
1130
        path = path,
1131
        relative_paths_setup = rsetup,
1132 1133 1134
        )
    changed = not (os.path.exists(dest) and open(dest).read() == contents)

1135
    if is_win32:
Jim Fulton's avatar
Jim Fulton committed
1136
        # generate exe file and give the script a magic name:
1137 1138
        exe = script + '.exe'
        open(exe, 'wb').write(
Jim Fulton's avatar
Jim Fulton committed
1139
            pkg_resources.resource_string('distribute', 'cli.exe')
Jim Fulton's avatar
Jim Fulton committed
1140
            )
1141
        generated.append(exe)
Jim Fulton's avatar
Jim Fulton committed
1142

1143 1144 1145
    if changed:
        open(dest, 'w').write(contents)
        try:
1146
            os.chmod(dest,0o755)
1147 1148 1149 1150
        except (AttributeError, os.error):
            pass
        logger.info("Generated interpreter %r.", script)

Jim Fulton's avatar
Jim Fulton committed
1151 1152
    generated.append(dest)
    return generated
1153

Georgy Berdyshev's avatar
Georgy Berdyshev committed
1154 1155
py_script_template = script_header + '''\

1156
%(relative_paths_setup)s
1157
import sys
1158

1159
sys.path[0:0] = [
1160
  %(path)s,
1161
  ]
1162

Jim Fulton's avatar
Jim Fulton committed
1163 1164
_interactive = True
if len(sys.argv) > 1:
Jim Fulton's avatar
Jim Fulton committed
1165
    _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:')
Jim Fulton's avatar
Jim Fulton committed
1166 1167 1168 1169 1170
    _interactive = False
    for (_opt, _val) in _options:
        if _opt == '-i':
            _interactive = True
        elif _opt == '-c':
1171
            exec(_val)
Jim Fulton's avatar
Jim Fulton committed
1172 1173 1174 1175 1176
        elif _opt == '-m':
            sys.argv[1:] = _args
            _args = []
            __import__("runpy").run_module(
                 _val, {}, "__main__", alter_sys=True)
1177

Jim Fulton's avatar
Jim Fulton committed
1178 1179
    if _args:
        sys.argv[:] = _args
Jim Fulton's avatar
Jim Fulton committed
1180
        __file__ = _args[0]
Jim Fulton's avatar
Jim Fulton committed
1181
        del _options, _args
1182 1183 1184
        __file__f = open(__file__)
        exec(compile(__file__f.read(), __file__, "exec"))
        __file__f.close(); del __file__f
Jim Fulton's avatar
Jim Fulton committed
1185 1186

if _interactive:
Jim Fulton's avatar
Jim Fulton committed
1187 1188
    del _interactive
    __import__("code").interact(banner="", local=globals())
1189
'''
1190

1191 1192
runsetup_template = """
import sys
1193
sys.path.insert(0, %(setupdir)r)
Jim Fulton's avatar
Jim Fulton committed
1194
sys.path.insert(0, %(distribute)r)
1195
import os, setuptools
Jim Fulton's avatar
Jim Fulton committed
1196

1197
__file__ = %(__file__)r
Jim Fulton's avatar
Jim Fulton committed
1198

1199 1200
os.chdir(%(setupdir)r)
sys.argv[0] = %(setup)r
1201 1202

exec(compile(open(%(setup)r).read(), %(setup)r, 'exec'))
1203
"""
1204

1205

1206 1207 1208
class VersionConflict(zc.buildout.UserError):

    def __init__(self, err, ws):
1209 1210
        ws = list(ws)
        ws.sort()
1211 1212 1213 1214 1215 1216 1217 1218 1219
        self.err, self.ws = err, ws

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

1223

1224 1225 1226
class MissingDistribution(zc.buildout.UserError):

    def __init__(self, req, ws):
1227 1228
        ws = list(ws)
        ws.sort()
1229 1230 1231 1232
        self.data = req, ws

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

1235
def _log_requirement(ws, req):
1236 1237 1238 1239 1240 1241 1242 1243
    if not logger.isEnabledFor(logging.DEBUG):
        # Sorting the working set and iterating over it's requirements
        # is expensive, so short cirtuit the work if it won't even be
        # 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
1244

1245 1246
    ws = list(ws)
    ws.sort()
1247
    for dist in ws:
1248 1249
        if req in dist.requires():
            logger.debug("  required by %s." % dist)
1250

1251 1252 1253 1254 1255 1256 1257
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
1258

1259 1260 1261 1262 1263 1264
_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
1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275

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')):
1276
                # If it wasn't compiled, it may not be compilable
1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292
                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. :)
1293
                args = [sys.executable]
1294 1295
                if __debug__:
                    args.append('-O')
1296
                args.extend(['-m', 'py_compile', filepath])
1297

1298
                call_subprocess(args)
1299