easy_install.py 30 KB
Newer Older
1
#############################################################################
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Python easy_install API

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

$Id$
"""

23
import glob, logging, os, re, shutil, sys, tempfile, urlparse, zipimport
24 25 26 27 28
import distutils.errors
import pkg_resources
import setuptools.command.setopt
import setuptools.package_index
import setuptools.archive_util
29
import zc.buildout
30

31
default_index_url = os.environ.get('buildout-testing-index-url')
32

33
logger = logging.getLogger('zc.buildout.easy_install')
34
picked = logging.getLogger('zc.buildout.easy_install.picked')
35

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

38 39 40 41
setuptools_loc = pkg_resources.working_set.find(
    pkg_resources.Requirement.parse('setuptools')
    ).location

42 43
# Include buildout and setuptools eggs in paths
buildout_and_setuptools_path = [
44
    setuptools_loc,
45 46
    pkg_resources.working_set.find(
        pkg_resources.Requirement.parse('zc.buildout')).location,
47 48
    ]

jim's avatar
jim committed
49 50 51 52
class IncompatibleVersionError(zc.buildout.UserError):
    """A specified version is incompatible with a given requirement.
    """

53 54 55 56 57 58 59 60 61 62 63
_versions = {sys.executable: '%d.%d' % sys.version_info[:2]}
def _get_version(executable):
    try:
        return _versions[executable]
    except KeyError:
        i, o = os.popen4(executable + ' -V')
        i.close()
        version = o.read().strip()
        o.close()
        pystring, version = version.split()
        assert pystring == 'Python'
jim's avatar
jim committed
64
        version = re.match('(\d[.]\d)([.]\d)?$', version).group(1)
65 66 67
        _versions[executable] = version
        return version

68 69 70 71 72 73 74
_indexes = {}
def _get_index(executable, index_url, find_links):
    key = executable, index_url, tuple(find_links)
    index = _indexes.get(key)
    if index is not None:
        return index

75 76 77
    if index_url is None:
        index_url = default_index_url

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
    if index_url is None:
        index = setuptools.package_index.PackageIndex(
            python=_get_version(executable)
            )
    else:
        index = setuptools.package_index.PackageIndex(
            index_url, python=_get_version(executable)
            )
        
    if find_links:
        index.add_find_links(find_links)

    _indexes[key] = index
    return index

93
clear_index_cache = _indexes.clear
jim's avatar
jim committed
94 95 96 97 98 99 100 101 102 103 104 105 106

if sys.platform == 'win32':
    # 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

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

107 108
class Installer:

109
    _versions = {}
110
    _download_cache = None
111
    _install_from_cache = False
112

113 114 115 116 117 118 119 120
    def __init__(self,
                 dest=None,
                 links=(),
                 index=None,
                 executable=sys.executable,
                 always_unzip=False,
                 path=None,
                 newest=True,
jim's avatar
jim committed
121
                 versions=None,
122 123
                 ):
        self._dest = dest
124 125 126 127 128 129 130 131

        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
        
132 133 134 135
        self._links = links = list(links)
        if self._download_cache and (self._download_cache not in links):
            links.insert(0, self._download_cache)

136 137 138 139 140 141 142
        self._index_url = index
        self._executable = executable
        self._always_unzip = always_unzip
        path = (path and path[:] or []) + buildout_and_setuptools_path
        if dest is not None and dest not in path:
            path.insert(0, dest)
        self._path = path
143 144
        if self._dest is None:
            newest = False
145 146 147 148
        self._newest = newest
        self._env = pkg_resources.Environment(path,
                                              python=_get_version(executable))
        self._index = _get_index(executable, index, links)
149 150 151

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

153
    def _satisfied(self, req, source=None):
154 155
        dists = [dist for dist in self._env[req.project_name] if dist in req]
        if not dists:
156 157
            logger.debug('We have no distributions for %s that satisfies %s.',
                         req.project_name, req)
158
            return None, self._obtain(req, source)
159 160 161 162 163 164 165

        # 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):
                logger.debug('We have a develop egg for %s', req)
166
                return dist, None
167 168 169 170 171

        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__.
172
            return dists[0], None
173

174
        # Find an upper limit in the specs, if there is one:
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
        specs = [(pkg_resources.parse_version(v), op) for (op, v) in req.specs]
        specs.sort()
        maxv = None
        greater = False
        lastv = None
        for v, op in specs:
            if op == '==' and not greater:
                maxv = v
            elif op in ('>', '>=', '!='):
                maxv = None
                greater == True
            elif op == '<':
                maxv = None
                greater == False
            elif op == '<=':
                maxv = v
                greater == False

            if v == lastv:
                # Repeated versions values are undefined, so
                # all bets are off
                maxv = None
                greater = True
            else:
                lastv = v
200

201
        best_we_have = dists[0] # Because dists are sorted from best to worst
jim's avatar
jim committed
202

203 204 205 206
        # Check if we have the upper limit
        if maxv is not None and best_we_have.version == maxv:
            logger.debug('We have the best distribution that satisfies\n%s',
                         req)
207
            return best_we_have, None
208

209 210 211 212
        # 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:
213

214 215
        
        if self._dest is not None:
216
            best_available = self._obtain(req, source)
217 218 219 220 221 222 223 224 225
        else:
            best_available = None

        if best_available is None:
            # That's a bit odd.  There aren't any distros available.
            # We should use the best one we have that meets the requirement.
            logger.debug(
                'There are no distros available that meet %s. Using our best.',
                req)
226
            return best_we_have, None
227 228 229 230 231 232 233
        else:
            # Let's find out if we already have the best available:
            if best_we_have.parsed_version >= best_available.parsed_version:
                # Yup. Use it.
                logger.debug(
                    'We have the best distribution that satisfies\n%s',
                    req)
234
                return best_we_have, None
235

236
        return None, best_available
237

238 239 240 241 242 243 244
    def _load_dist(self, dist):
        dists = pkg_resources.Environment(
            dist.location,
            python=_get_version(self._executable),
            )[dist.project_name]
        assert len(dists) == 1
        return dists[0]
245

246
    def _call_easy_install(self, spec, ws, dest, dist):
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 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
        tmp = tempfile.mkdtemp(dir=dest)
        try:
            path = self._get_dist(
                pkg_resources.Requirement.parse('setuptools'), ws, False,
                )[0].location

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

            args += (spec, )

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

            args += (dict(os.environ, PYTHONPATH=path), )
            sys.stdout.flush() # We want any pending output first
            exit_code = os.spawnle(
                os.P_WAIT, self._executable, self._executable,
                *args)

            dists = []
            env = pkg_resources.Environment(
                [tmp],
                python=_get_version(self._executable),
                )
            for project in env:
                dists.extend(env[project])
                
            if exit_code:
                logger.error(
                    "An error occured when trying to install %s."
                    "Look above this message for any errors that"
                    "were output by easy_install.",
                    dist)

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

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

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

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

            return result
331

332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
        finally:
            shutil.rmtree(tmp)
            
    def _obtain(self, requirement, source=None):
        index = self._index
        if index.obtain(requirement) is None:
            return None
        
        best = []
        bestv = ()
        for dist in index[requirement.project_name]:
            if dist not in requirement:
                continue
            if source and dist.precedence != pkg_resources.SOURCE_DIST:
                continue
            distv = dist.parsed_version
            if distv > bestv:
                best = [dist]
                bestv = distv
            elif distv == bestv:
                best.append(dist)

        if not best:
            return None
356

357 358 359 360 361 362 363
        if len(best) == 1:
            return best[0]
        
        if self._download_cache:
            for dist in best:
                if os.path.dirname(dist.location) == self._download_cache:
                    return dist
364

365 366
        best.sort()
        return best[-1]
367

368 369
    def _fetch(self, dist, tmp):
        return dist.clone(location=self._index.download(dist.location, tmp))
370 371 372

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

373 374
        __doing__ = 'Getting distribution for %s', requirement

375 376
        # Maybe an existing dist is already the best dist that satisfies the
        # requirement
377
        dist, avail = self._satisfied(requirement)
378 379 380 381 382

        if dist is None:
            if self._dest is not None:
                logger.info("Getting new distribution for %s", requirement)

383 384 385 386 387
            # Retrieve the dist:
            if avail is None:
                raise zc.buildout.UserError(
                    "Couldn't find a distribution for %s."
                    % requirement)
388

389 390 391
            # We may overwrite distributions, so clear importer
            # cache.
            sys.path_importer_cache.clear()
392

393 394 395 396 397 398 399 400
            tmp = self._download_cache
            try:
                if tmp:
                    if os.path.dirname(avail.location) == tmp:
                        dist = avail
                    else:
                        dist = self._fetch(avail, tmp)
                else:
401
                    tmp = tempfile.mkdtemp('get_dist')
402
                    dist = self._fetch(avail, tmp)
403

404 405 406
                if dist is None:
                    raise zc.buildout.UserError(
                        "Couln't download distribution %s." % avail)
407

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

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

414 415 416 417 418
                    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:
419

420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
                        if self._always_unzip:
                            should_unzip = True
                        else:
                            metadata = pkg_resources.EggMetadata(
                                zipimport.zipimporter(dist.location)
                                )
                            should_unzip = (
                                metadata.has_metadata('not-zip-safe')
                                or
                                not metadata.has_metadata('zip-safe')
                                )

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

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

451 452 453
            finally:
                if tmp != self._download_cache:
                    shutil.rmtree(tmp)
454

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

459 460
        else:
            dists = [dist]
461 462

        # XXX Need test for this
463
        for dist in dists:
464 465 466
            if (dist.has_metadata('dependency_links.txt')
                and not self._install_from_cache
                ):
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
                for link in dist.get_metadata_lines('dependency_links.txt'):
                    link = link.strip()
                    if link not in self._links:
                        logger.debug('Adding find link %r from %s', link, dist)
                        self._links.append(link)
                        self._index = _get_index(self._executable,
                                                 self._index_url, self._links)

        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] == '==')
                ):
                picked.debug('%s = %s', dist.project_name, dist.version)

        return dists
487 488 489 490 491 492 493 494 495 496 497 498 499 500

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


jim's avatar
jim committed
509 510 511 512 513 514 515 516 517 518 519 520 521
    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 "
                             "requirement, %s", version, requirement)
                raise IncompatibleVersionError("Bad version", version)
            
            requirement = pkg_resources.Requirement.parse(
                "%s ==%s" % (requirement.project_name, version))

        return requirement

522 523 524 525 526 527 528 529 530
    def install(self, specs, working_set=None):

        logger.debug('Installing %r', specs)

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

jim's avatar
jim committed
531
        requirements = [self._constrain(pkg_resources.Requirement.parse(spec))
532 533
                        for spec in specs]

jim's avatar
jim committed
534 535
        

536 537
        if working_set is None:
            ws = pkg_resources.WorkingSet([])
jim's avatar
jim committed
538
        else:
539
            ws = working_set
540

541
        for requirement in requirements:
542 543 544
            for dist in self._get_dist(requirement, ws, self._always_unzip):
                ws.add(dist)
                self._maybe_add_setuptools(ws, dist)
545 546 547 548 549 550

        # 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.
        #
551 552 553
        # 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.
554 555 556 557 558
        while 1:
            try:
                ws.resolve(requirements)
            except pkg_resources.DistributionNotFound, err:
                [requirement] = err
jim's avatar
jim committed
559
                requirement = self._constrain(requirement)
560 561
                if dest:
                    logger.debug('Getting required %s', requirement)
562 563 564 565
                for dist in self._get_dist(requirement, ws, self._always_unzip
                                           ):
                    ws.add(dist)
                    self._maybe_add_setuptools(ws, dist)
566 567
            else:
                break
568

569
        return ws
570

571
    def build(self, spec, build_ext):
572

jim's avatar
jim committed
573
        requirement = self._constrain(pkg_resources.Requirement.parse(spec))
574

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

579 580 581 582 583 584 585
        # Retrieve the dist:
        if avail is None:
            raise zc.buildout.UserError(
                "Couldn't find a source distribution for %s."
                % requirement)

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

587 588 589 590 591 592 593
        tmp = self._download_cache
        try:
            if tmp:
                if os.path.dirname(avail.location) == tmp:
                    dist = avail
                else:
                    dist = self._fetch(avail, tmp)
594
            else:
595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
                tmp = tempfile.mkdtemp('get_dist')
                dist = self._fetch(avail, tmp)

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

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

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

634
        finally:
635 636 637
            if tmp != self._download_cache:
                shutil.rmtree(tmp)

638 639 640 641 642
def default_versions(versions=None):
    old = Installer._versions
    if versions is not None:
        Installer._versions = versions
    return old
643

644 645 646
def download_cache(path=-1):
    old = Installer._download_cache
    if path != -1:
647 648
        if path:
            path = os.path.abspath(path)
649 650 651
        Installer._download_cache = path
    return old

652 653 654 655 656 657
def install_from_cache(setting=None):
    old = Installer._install_from_cache
    if setting is not None:
        Installer._install_from_cache = bool(setting)
    return old

658 659 660
def install(specs, dest,
            links=(), index=None,
            executable=sys.executable, always_unzip=False,
jim's avatar
jim committed
661
            path=None, working_set=None, newest=True, versions=None):
662
    installer = Installer(dest, links, index, executable, always_unzip, path,
jim's avatar
jim committed
663
                          newest, versions)
664 665 666 667 668 669
    return installer.install(specs, working_set)


def build(spec, dest, build_ext,
          links=(), index=None,
          executable=sys.executable,
jim's avatar
jim committed
670 671 672
          path=None, newest=True, versions=None):
    installer = Installer(dest, links, index, executable, True, path, newest,
                          versions)
673 674
    return installer.build(spec, build_ext)

675
        
676

677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
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)
693 694
        
    assert len(result) == 1, str(result)
695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746
    undo.pop()
    
    return result[0]

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

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

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

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

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

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

        log_level = logger.getEffectiveLevel()
747 748
        if log_level <= 0:
            if log_level == 0:
749 750 751
                del args[1]
            else:
                args[1] == '-v'
752
        logger.debug("in: %s\n%r", directory, args)
753 754 755 756 757 758 759 760 761 762

        assert os.spawnl(os.P_WAIT, executable, executable, *args) == 0

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

    finally:
        undo.reverse()
        [f() for f in undo]
            
            
763 764 765
def working_set(specs, executable, path):
    return install(specs, None, executable=executable, path=path)

766 767 768 769
def scripts(reqs, working_set, executable, dest,
            scripts=None,
            extra_paths=(),
            arguments='',
770
            interpreter=None,
771
            initialization='',
772
            ):
773
    
774 775
    path = [dist.location for dist in working_set]
    path.extend(extra_paths)
jim's avatar
jim committed
776
    path = repr(path)[1:-1].replace(', ', ',\n  ')
777 778
    generated = []

779 780 781 782
    if isinstance(reqs, str):
        raise TypeError('Expected iterable of requirements or entry points,'
                        ' got string.')

783 784 785
    if initialization:
        initialization = '\n'+initialization+'\n'

786 787 788 789 790
    entry_points = []
    for req in reqs:
        if isinstance(req, str):
            req = pkg_resources.Requirement.parse(req)
            dist = working_set.find(req)
791
            for name in pkg_resources.get_entry_map(dist, 'console_scripts'):
792 793
                entry_point = dist.get_entry_info('console_scripts', name)
                entry_points.append(
794 795
                    (name, entry_point.module_name,
                     '.'.join(entry_point.attrs))
jim's avatar
jim committed
796
                    )
797 798 799 800 801 802 803 804 805 806
        else:
            entry_points.append(req)
                
    for name, module_name, attrs in entry_points:
        if scripts is not None:
            sname = scripts.get(name)
            if sname is None:
                continue
        else:
            sname = name
807

808 809
        sname = os.path.join(dest, sname)
        generated.extend(
810 811
            _script(module_name, attrs, path, sname, executable, arguments,
                    initialization)
812
            )
813

814 815 816
    if interpreter:
        sname = os.path.join(dest, interpreter)
        generated.extend(_pyscript(path, sname, executable))
817 818 819

    return generated

820 821
def _script(module_name, attrs, path, dest, executable, arguments,
            initialization):
jim's avatar
jim committed
822 823 824 825 826 827 828 829 830
    generated = []
    if sys.platform == 'win32':
        # generate exe file and give the script a magic name:
        open(dest+'.exe', 'wb').write(
            pkg_resources.resource_string('setuptools', 'cli.exe')
            )
        generated.append(dest+'.exe')
        dest += '-script.py'
        
831 832 833
    open(dest, 'w').write(script_template % dict(
        python = executable,
        path = path,
834 835
        module_name = module_name,
        attrs = attrs,
836
        arguments = arguments,
837
        initialization = initialization,
838 839 840 841 842
        ))
    try:
        os.chmod(dest, 0755)
    except (AttributeError, os.error):
        pass
jim's avatar
jim committed
843 844
    generated.append(dest)
    return generated
845 846 847 848 849 850

script_template = '''\
#!%(python)s

import sys
sys.path[0:0] = [
851
  %(path)s,
852
  ]
853
%(initialization)s
854 855 856
import %(module_name)s

if __name__ == '__main__':
857
    %(module_name)s.%(attrs)s(%(arguments)s)
858 859 860 861
'''


def _pyscript(path, dest, executable):
jim's avatar
jim committed
862 863 864 865 866 867 868 869 870
    generated = []
    if sys.platform == 'win32':
        # generate exe file and give the script a magic name:
        open(dest+'.exe', 'wb').write(
            pkg_resources.resource_string('setuptools', 'cli.exe')
            )
        generated.append(dest+'.exe')
        dest += '-script.py'

871 872 873 874 875 876 877 878
    open(dest, 'w').write(py_script_template % dict(
        python = executable,
        path = path,
        ))
    try:
        os.chmod(dest,0755)
    except (AttributeError, os.error):
        pass
jim's avatar
jim committed
879 880
    generated.append(dest)
    return generated
881 882

py_script_template = '''\
883
#!%(python)s
884
import sys
885
    
886
sys.path[0:0] = [
887
  %(path)s,
888
  ]
889

jim's avatar
jim committed
890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907
_interactive = True
if len(sys.argv) > 1:
    import getopt
    _options, _args = getopt.getopt(sys.argv[1:], 'ic:')
    _interactive = False
    for (_opt, _val) in _options:
        if _opt == '-i':
            _interactive = True
        elif _opt == '-c':
            exec _val
            
    if _args:
        sys.argv[:] = _args
        execfile(sys.argv[0])

if _interactive:
    import code
    code.interact(banner="", local=globals())
908
'''
909 910 911 912 913
        
runsetup_template = """
import sys
sys.path.insert(0, %(setuptools)r)
import os, setuptools
jim's avatar
jim committed
914

915
__file__ = %(__file__)r
jim's avatar
jim committed
916

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