• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2"""
3Easy Install
4------------
5
6A tool for doing automatic download/extract/build of distutils-based Python
7packages.  For detailed documentation, see the accompanying EasyInstall.txt
8file, or visit the `EasyInstall home page`__.
9
10__ https://setuptools.readthedocs.io/en/latest/easy_install.html
11
12"""
13
14from glob import glob
15from distutils.util import get_platform
16from distutils.util import convert_path, subst_vars
17from distutils.errors import (
18    DistutilsArgError, DistutilsOptionError,
19    DistutilsError, DistutilsPlatformError,
20)
21from distutils.command.install import INSTALL_SCHEMES, SCHEME_KEYS
22from distutils import log, dir_util
23from distutils.command.build_scripts import first_line_re
24from distutils.spawn import find_executable
25import sys
26import os
27import zipimport
28import shutil
29import tempfile
30import zipfile
31import re
32import stat
33import random
34import textwrap
35import warnings
36import site
37import struct
38import contextlib
39import subprocess
40import shlex
41import io
42
43from setuptools.extern import six
44from setuptools.extern.six.moves import configparser, map
45
46from setuptools import Command
47from setuptools.sandbox import run_setup
48from setuptools.py31compat import get_path, get_config_vars
49from setuptools.py27compat import rmtree_safe
50from setuptools.command import setopt
51from setuptools.archive_util import unpack_archive
52from setuptools.package_index import (
53    PackageIndex, parse_requirement_arg, URL_SCHEME,
54)
55from setuptools.command import bdist_egg, egg_info
56from setuptools.wheel import Wheel
57from pkg_resources import (
58    yield_lines, normalize_path, resource_string, ensure_directory,
59    get_distribution, find_distributions, Environment, Requirement,
60    Distribution, PathMetadata, EggMetadata, WorkingSet, DistributionNotFound,
61    VersionConflict, DEVELOP_DIST,
62)
63import pkg_resources.py31compat
64
65# Turn on PEP440Warnings
66warnings.filterwarnings("default", category=pkg_resources.PEP440Warning)
67
68__all__ = [
69    'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg',
70    'main', 'get_exe_prefixes',
71]
72
73
74def is_64bit():
75    return struct.calcsize("P") == 8
76
77
78def samefile(p1, p2):
79    """
80    Determine if two paths reference the same file.
81
82    Augments os.path.samefile to work on Windows and
83    suppresses errors if the path doesn't exist.
84    """
85    both_exist = os.path.exists(p1) and os.path.exists(p2)
86    use_samefile = hasattr(os.path, 'samefile') and both_exist
87    if use_samefile:
88        return os.path.samefile(p1, p2)
89    norm_p1 = os.path.normpath(os.path.normcase(p1))
90    norm_p2 = os.path.normpath(os.path.normcase(p2))
91    return norm_p1 == norm_p2
92
93
94if six.PY2:
95
96    def _to_ascii(s):
97        return s
98
99    def isascii(s):
100        try:
101            six.text_type(s, 'ascii')
102            return True
103        except UnicodeError:
104            return False
105else:
106
107    def _to_ascii(s):
108        return s.encode('ascii')
109
110    def isascii(s):
111        try:
112            s.encode('ascii')
113            return True
114        except UnicodeError:
115            return False
116
117
118_one_liner = lambda text: textwrap.dedent(text).strip().replace('\n', '; ')
119
120
121class easy_install(Command):
122    """Manage a download/build/install process"""
123    description = "Find/get/install Python packages"
124    command_consumes_arguments = True
125
126    user_options = [
127        ('prefix=', None, "installation prefix"),
128        ("zip-ok", "z", "install package as a zipfile"),
129        ("multi-version", "m", "make apps have to require() a version"),
130        ("upgrade", "U", "force upgrade (searches PyPI for latest versions)"),
131        ("install-dir=", "d", "install package to DIR"),
132        ("script-dir=", "s", "install scripts to DIR"),
133        ("exclude-scripts", "x", "Don't install scripts"),
134        ("always-copy", "a", "Copy all needed packages to install dir"),
135        ("index-url=", "i", "base URL of Python Package Index"),
136        ("find-links=", "f", "additional URL(s) to search for packages"),
137        ("build-directory=", "b",
138         "download/extract/build in DIR; keep the results"),
139        ('optimize=', 'O',
140         "also compile with optimization: -O1 for \"python -O\", "
141         "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"),
142        ('record=', None,
143         "filename in which to record list of installed files"),
144        ('always-unzip', 'Z', "don't install as a zipfile, no matter what"),
145        ('site-dirs=', 'S', "list of directories where .pth files work"),
146        ('editable', 'e', "Install specified packages in editable form"),
147        ('no-deps', 'N', "don't install dependencies"),
148        ('allow-hosts=', 'H', "pattern(s) that hostnames must match"),
149        ('local-snapshots-ok', 'l',
150         "allow building eggs from local checkouts"),
151        ('version', None, "print version information and exit"),
152        ('no-find-links', None,
153         "Don't load find-links defined in packages being installed")
154    ]
155    boolean_options = [
156        'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy',
157        'editable',
158        'no-deps', 'local-snapshots-ok', 'version'
159    ]
160
161    if site.ENABLE_USER_SITE:
162        help_msg = "install in user site-package '%s'" % site.USER_SITE
163        user_options.append(('user', None, help_msg))
164        boolean_options.append('user')
165
166    negative_opt = {'always-unzip': 'zip-ok'}
167    create_index = PackageIndex
168
169    def initialize_options(self):
170        # the --user option seems to be an opt-in one,
171        # so the default should be False.
172        self.user = 0
173        self.zip_ok = self.local_snapshots_ok = None
174        self.install_dir = self.script_dir = self.exclude_scripts = None
175        self.index_url = None
176        self.find_links = None
177        self.build_directory = None
178        self.args = None
179        self.optimize = self.record = None
180        self.upgrade = self.always_copy = self.multi_version = None
181        self.editable = self.no_deps = self.allow_hosts = None
182        self.root = self.prefix = self.no_report = None
183        self.version = None
184        self.install_purelib = None  # for pure module distributions
185        self.install_platlib = None  # non-pure (dists w/ extensions)
186        self.install_headers = None  # for C/C++ headers
187        self.install_lib = None  # set to either purelib or platlib
188        self.install_scripts = None
189        self.install_data = None
190        self.install_base = None
191        self.install_platbase = None
192        if site.ENABLE_USER_SITE:
193            self.install_userbase = site.USER_BASE
194            self.install_usersite = site.USER_SITE
195        else:
196            self.install_userbase = None
197            self.install_usersite = None
198        self.no_find_links = None
199
200        # Options not specifiable via command line
201        self.package_index = None
202        self.pth_file = self.always_copy_from = None
203        self.site_dirs = None
204        self.installed_projects = {}
205        self.sitepy_installed = False
206        # Always read easy_install options, even if we are subclassed, or have
207        # an independent instance created.  This ensures that defaults will
208        # always come from the standard configuration file(s)' "easy_install"
209        # section, even if this is a "develop" or "install" command, or some
210        # other embedding.
211        self._dry_run = None
212        self.verbose = self.distribution.verbose
213        self.distribution._set_command_options(
214            self, self.distribution.get_option_dict('easy_install')
215        )
216
217    def delete_blockers(self, blockers):
218        extant_blockers = (
219            filename for filename in blockers
220            if os.path.exists(filename) or os.path.islink(filename)
221        )
222        list(map(self._delete_path, extant_blockers))
223
224    def _delete_path(self, path):
225        log.info("Deleting %s", path)
226        if self.dry_run:
227            return
228
229        is_tree = os.path.isdir(path) and not os.path.islink(path)
230        remover = rmtree if is_tree else os.unlink
231        remover(path)
232
233    @staticmethod
234    def _render_version():
235        """
236        Render the Setuptools version and installation details, then exit.
237        """
238        ver = sys.version[:3]
239        dist = get_distribution('setuptools')
240        tmpl = 'setuptools {dist.version} from {dist.location} (Python {ver})'
241        print(tmpl.format(**locals()))
242        raise SystemExit()
243
244    def finalize_options(self):
245        self.version and self._render_version()
246
247        py_version = sys.version.split()[0]
248        prefix, exec_prefix = get_config_vars('prefix', 'exec_prefix')
249
250        self.config_vars = {
251            'dist_name': self.distribution.get_name(),
252            'dist_version': self.distribution.get_version(),
253            'dist_fullname': self.distribution.get_fullname(),
254            'py_version': py_version,
255            'py_version_short': py_version[0:3],
256            'py_version_nodot': py_version[0] + py_version[2],
257            'sys_prefix': prefix,
258            'prefix': prefix,
259            'sys_exec_prefix': exec_prefix,
260            'exec_prefix': exec_prefix,
261            # Only python 3.2+ has abiflags
262            'abiflags': getattr(sys, 'abiflags', ''),
263        }
264
265        if site.ENABLE_USER_SITE:
266            self.config_vars['userbase'] = self.install_userbase
267            self.config_vars['usersite'] = self.install_usersite
268
269        self._fix_install_dir_for_user_site()
270
271        self.expand_basedirs()
272        self.expand_dirs()
273
274        self._expand(
275            'install_dir', 'script_dir', 'build_directory',
276            'site_dirs',
277        )
278        # If a non-default installation directory was specified, default the
279        # script directory to match it.
280        if self.script_dir is None:
281            self.script_dir = self.install_dir
282
283        if self.no_find_links is None:
284            self.no_find_links = False
285
286        # Let install_dir get set by install_lib command, which in turn
287        # gets its info from the install command, and takes into account
288        # --prefix and --home and all that other crud.
289        self.set_undefined_options(
290            'install_lib', ('install_dir', 'install_dir')
291        )
292        # Likewise, set default script_dir from 'install_scripts.install_dir'
293        self.set_undefined_options(
294            'install_scripts', ('install_dir', 'script_dir')
295        )
296
297        if self.user and self.install_purelib:
298            self.install_dir = self.install_purelib
299            self.script_dir = self.install_scripts
300        # default --record from the install command
301        self.set_undefined_options('install', ('record', 'record'))
302        # Should this be moved to the if statement below? It's not used
303        # elsewhere
304        normpath = map(normalize_path, sys.path)
305        self.all_site_dirs = get_site_dirs()
306        if self.site_dirs is not None:
307            site_dirs = [
308                os.path.expanduser(s.strip()) for s in
309                self.site_dirs.split(',')
310            ]
311            for d in site_dirs:
312                if not os.path.isdir(d):
313                    log.warn("%s (in --site-dirs) does not exist", d)
314                elif normalize_path(d) not in normpath:
315                    raise DistutilsOptionError(
316                        d + " (in --site-dirs) is not on sys.path"
317                    )
318                else:
319                    self.all_site_dirs.append(normalize_path(d))
320        if not self.editable:
321            self.check_site_dir()
322        self.index_url = self.index_url or "https://pypi.org/simple/"
323        self.shadow_path = self.all_site_dirs[:]
324        for path_item in self.install_dir, normalize_path(self.script_dir):
325            if path_item not in self.shadow_path:
326                self.shadow_path.insert(0, path_item)
327
328        if self.allow_hosts is not None:
329            hosts = [s.strip() for s in self.allow_hosts.split(',')]
330        else:
331            hosts = ['*']
332        if self.package_index is None:
333            self.package_index = self.create_index(
334                self.index_url, search_path=self.shadow_path, hosts=hosts,
335            )
336        self.local_index = Environment(self.shadow_path + sys.path)
337
338        if self.find_links is not None:
339            if isinstance(self.find_links, six.string_types):
340                self.find_links = self.find_links.split()
341        else:
342            self.find_links = []
343        if self.local_snapshots_ok:
344            self.package_index.scan_egg_links(self.shadow_path + sys.path)
345        if not self.no_find_links:
346            self.package_index.add_find_links(self.find_links)
347        self.set_undefined_options('install_lib', ('optimize', 'optimize'))
348        if not isinstance(self.optimize, int):
349            try:
350                self.optimize = int(self.optimize)
351                if not (0 <= self.optimize <= 2):
352                    raise ValueError
353            except ValueError:
354                raise DistutilsOptionError("--optimize must be 0, 1, or 2")
355
356        if self.editable and not self.build_directory:
357            raise DistutilsArgError(
358                "Must specify a build directory (-b) when using --editable"
359            )
360        if not self.args:
361            raise DistutilsArgError(
362                "No urls, filenames, or requirements specified (see --help)")
363
364        self.outputs = []
365
366    def _fix_install_dir_for_user_site(self):
367        """
368        Fix the install_dir if "--user" was used.
369        """
370        if not self.user or not site.ENABLE_USER_SITE:
371            return
372
373        self.create_home_path()
374        if self.install_userbase is None:
375            msg = "User base directory is not specified"
376            raise DistutilsPlatformError(msg)
377        self.install_base = self.install_platbase = self.install_userbase
378        scheme_name = os.name.replace('posix', 'unix') + '_user'
379        self.select_scheme(scheme_name)
380
381    def _expand_attrs(self, attrs):
382        for attr in attrs:
383            val = getattr(self, attr)
384            if val is not None:
385                if os.name == 'posix' or os.name == 'nt':
386                    val = os.path.expanduser(val)
387                val = subst_vars(val, self.config_vars)
388                setattr(self, attr, val)
389
390    def expand_basedirs(self):
391        """Calls `os.path.expanduser` on install_base, install_platbase and
392        root."""
393        self._expand_attrs(['install_base', 'install_platbase', 'root'])
394
395    def expand_dirs(self):
396        """Calls `os.path.expanduser` on install dirs."""
397        dirs = [
398            'install_purelib',
399            'install_platlib',
400            'install_lib',
401            'install_headers',
402            'install_scripts',
403            'install_data',
404        ]
405        self._expand_attrs(dirs)
406
407    def run(self):
408        if self.verbose != self.distribution.verbose:
409            log.set_verbosity(self.verbose)
410        try:
411            for spec in self.args:
412                self.easy_install(spec, not self.no_deps)
413            if self.record:
414                outputs = self.outputs
415                if self.root:  # strip any package prefix
416                    root_len = len(self.root)
417                    for counter in range(len(outputs)):
418                        outputs[counter] = outputs[counter][root_len:]
419                from distutils import file_util
420
421                self.execute(
422                    file_util.write_file, (self.record, outputs),
423                    "writing list of installed files to '%s'" %
424                    self.record
425                )
426            self.warn_deprecated_options()
427        finally:
428            log.set_verbosity(self.distribution.verbose)
429
430    def pseudo_tempname(self):
431        """Return a pseudo-tempname base in the install directory.
432        This code is intentionally naive; if a malicious party can write to
433        the target directory you're already in deep doodoo.
434        """
435        try:
436            pid = os.getpid()
437        except Exception:
438            pid = random.randint(0, sys.maxsize)
439        return os.path.join(self.install_dir, "test-easy-install-%s" % pid)
440
441    def warn_deprecated_options(self):
442        pass
443
444    def check_site_dir(self):
445        """Verify that self.install_dir is .pth-capable dir, if needed"""
446
447        instdir = normalize_path(self.install_dir)
448        pth_file = os.path.join(instdir, 'easy-install.pth')
449
450        # Is it a configured, PYTHONPATH, implicit, or explicit site dir?
451        is_site_dir = instdir in self.all_site_dirs
452
453        if not is_site_dir and not self.multi_version:
454            # No?  Then directly test whether it does .pth file processing
455            is_site_dir = self.check_pth_processing()
456        else:
457            # make sure we can write to target dir
458            testfile = self.pseudo_tempname() + '.write-test'
459            test_exists = os.path.exists(testfile)
460            try:
461                if test_exists:
462                    os.unlink(testfile)
463                open(testfile, 'w').close()
464                os.unlink(testfile)
465            except (OSError, IOError):
466                self.cant_write_to_target()
467
468        if not is_site_dir and not self.multi_version:
469            # Can't install non-multi to non-site dir
470            raise DistutilsError(self.no_default_version_msg())
471
472        if is_site_dir:
473            if self.pth_file is None:
474                self.pth_file = PthDistributions(pth_file, self.all_site_dirs)
475        else:
476            self.pth_file = None
477
478        if instdir not in map(normalize_path, _pythonpath()):
479            # only PYTHONPATH dirs need a site.py, so pretend it's there
480            self.sitepy_installed = True
481        elif self.multi_version and not os.path.exists(pth_file):
482            self.sitepy_installed = True  # don't need site.py in this case
483            self.pth_file = None  # and don't create a .pth file
484        self.install_dir = instdir
485
486    __cant_write_msg = textwrap.dedent("""
487        can't create or remove files in install directory
488
489        The following error occurred while trying to add or remove files in the
490        installation directory:
491
492            %s
493
494        The installation directory you specified (via --install-dir, --prefix, or
495        the distutils default setting) was:
496
497            %s
498        """).lstrip()
499
500    __not_exists_id = textwrap.dedent("""
501        This directory does not currently exist.  Please create it and try again, or
502        choose a different installation directory (using the -d or --install-dir
503        option).
504        """).lstrip()
505
506    __access_msg = textwrap.dedent("""
507        Perhaps your account does not have write access to this directory?  If the
508        installation directory is a system-owned directory, you may need to sign in
509        as the administrator or "root" account.  If you do not have administrative
510        access to this machine, you may wish to choose a different installation
511        directory, preferably one that is listed in your PYTHONPATH environment
512        variable.
513
514        For information on other options, you may wish to consult the
515        documentation at:
516
517          https://setuptools.readthedocs.io/en/latest/easy_install.html
518
519        Please make the appropriate changes for your system and try again.
520        """).lstrip()
521
522    def cant_write_to_target(self):
523        msg = self.__cant_write_msg % (sys.exc_info()[1], self.install_dir,)
524
525        if not os.path.exists(self.install_dir):
526            msg += '\n' + self.__not_exists_id
527        else:
528            msg += '\n' + self.__access_msg
529        raise DistutilsError(msg)
530
531    def check_pth_processing(self):
532        """Empirically verify whether .pth files are supported in inst. dir"""
533        instdir = self.install_dir
534        log.info("Checking .pth file support in %s", instdir)
535        pth_file = self.pseudo_tempname() + ".pth"
536        ok_file = pth_file + '.ok'
537        ok_exists = os.path.exists(ok_file)
538        tmpl = _one_liner("""
539            import os
540            f = open({ok_file!r}, 'w')
541            f.write('OK')
542            f.close()
543            """) + '\n'
544        try:
545            if ok_exists:
546                os.unlink(ok_file)
547            dirname = os.path.dirname(ok_file)
548            pkg_resources.py31compat.makedirs(dirname, exist_ok=True)
549            f = open(pth_file, 'w')
550        except (OSError, IOError):
551            self.cant_write_to_target()
552        else:
553            try:
554                f.write(tmpl.format(**locals()))
555                f.close()
556                f = None
557                executable = sys.executable
558                if os.name == 'nt':
559                    dirname, basename = os.path.split(executable)
560                    alt = os.path.join(dirname, 'pythonw.exe')
561                    use_alt = (
562                        basename.lower() == 'python.exe' and
563                        os.path.exists(alt)
564                    )
565                    if use_alt:
566                        # use pythonw.exe to avoid opening a console window
567                        executable = alt
568
569                from distutils.spawn import spawn
570
571                spawn([executable, '-E', '-c', 'pass'], 0)
572
573                if os.path.exists(ok_file):
574                    log.info(
575                        "TEST PASSED: %s appears to support .pth files",
576                        instdir
577                    )
578                    return True
579            finally:
580                if f:
581                    f.close()
582                if os.path.exists(ok_file):
583                    os.unlink(ok_file)
584                if os.path.exists(pth_file):
585                    os.unlink(pth_file)
586        if not self.multi_version:
587            log.warn("TEST FAILED: %s does NOT support .pth files", instdir)
588        return False
589
590    def install_egg_scripts(self, dist):
591        """Write all the scripts for `dist`, unless scripts are excluded"""
592        if not self.exclude_scripts and dist.metadata_isdir('scripts'):
593            for script_name in dist.metadata_listdir('scripts'):
594                if dist.metadata_isdir('scripts/' + script_name):
595                    # The "script" is a directory, likely a Python 3
596                    # __pycache__ directory, so skip it.
597                    continue
598                self.install_script(
599                    dist, script_name,
600                    dist.get_metadata('scripts/' + script_name)
601                )
602        self.install_wrapper_scripts(dist)
603
604    def add_output(self, path):
605        if os.path.isdir(path):
606            for base, dirs, files in os.walk(path):
607                for filename in files:
608                    self.outputs.append(os.path.join(base, filename))
609        else:
610            self.outputs.append(path)
611
612    def not_editable(self, spec):
613        if self.editable:
614            raise DistutilsArgError(
615                "Invalid argument %r: you can't use filenames or URLs "
616                "with --editable (except via the --find-links option)."
617                % (spec,)
618            )
619
620    def check_editable(self, spec):
621        if not self.editable:
622            return
623
624        if os.path.exists(os.path.join(self.build_directory, spec.key)):
625            raise DistutilsArgError(
626                "%r already exists in %s; can't do a checkout there" %
627                (spec.key, self.build_directory)
628            )
629
630    @contextlib.contextmanager
631    def _tmpdir(self):
632        tmpdir = tempfile.mkdtemp(prefix=six.u("easy_install-"))
633        try:
634            # cast to str as workaround for #709 and #710 and #712
635            yield str(tmpdir)
636        finally:
637            os.path.exists(tmpdir) and rmtree(rmtree_safe(tmpdir))
638
639    def easy_install(self, spec, deps=False):
640        if not self.editable:
641            self.install_site_py()
642
643        with self._tmpdir() as tmpdir:
644            if not isinstance(spec, Requirement):
645                if URL_SCHEME(spec):
646                    # It's a url, download it to tmpdir and process
647                    self.not_editable(spec)
648                    dl = self.package_index.download(spec, tmpdir)
649                    return self.install_item(None, dl, tmpdir, deps, True)
650
651                elif os.path.exists(spec):
652                    # Existing file or directory, just process it directly
653                    self.not_editable(spec)
654                    return self.install_item(None, spec, tmpdir, deps, True)
655                else:
656                    spec = parse_requirement_arg(spec)
657
658            self.check_editable(spec)
659            dist = self.package_index.fetch_distribution(
660                spec, tmpdir, self.upgrade, self.editable,
661                not self.always_copy, self.local_index
662            )
663            if dist is None:
664                msg = "Could not find suitable distribution for %r" % spec
665                if self.always_copy:
666                    msg += " (--always-copy skips system and development eggs)"
667                raise DistutilsError(msg)
668            elif dist.precedence == DEVELOP_DIST:
669                # .egg-info dists don't need installing, just process deps
670                self.process_distribution(spec, dist, deps, "Using")
671                return dist
672            else:
673                return self.install_item(spec, dist.location, tmpdir, deps)
674
675    def install_item(self, spec, download, tmpdir, deps, install_needed=False):
676
677        # Installation is also needed if file in tmpdir or is not an egg
678        install_needed = install_needed or self.always_copy
679        install_needed = install_needed or os.path.dirname(download) == tmpdir
680        install_needed = install_needed or not download.endswith('.egg')
681        install_needed = install_needed or (
682            self.always_copy_from is not None and
683            os.path.dirname(normalize_path(download)) ==
684            normalize_path(self.always_copy_from)
685        )
686
687        if spec and not install_needed:
688            # at this point, we know it's a local .egg, we just don't know if
689            # it's already installed.
690            for dist in self.local_index[spec.project_name]:
691                if dist.location == download:
692                    break
693            else:
694                install_needed = True  # it's not in the local index
695
696        log.info("Processing %s", os.path.basename(download))
697
698        if install_needed:
699            dists = self.install_eggs(spec, download, tmpdir)
700            for dist in dists:
701                self.process_distribution(spec, dist, deps)
702        else:
703            dists = [self.egg_distribution(download)]
704            self.process_distribution(spec, dists[0], deps, "Using")
705
706        if spec is not None:
707            for dist in dists:
708                if dist in spec:
709                    return dist
710
711    def select_scheme(self, name):
712        """Sets the install directories by applying the install schemes."""
713        # it's the caller's problem if they supply a bad name!
714        scheme = INSTALL_SCHEMES[name]
715        for key in SCHEME_KEYS:
716            attrname = 'install_' + key
717            if getattr(self, attrname) is None:
718                setattr(self, attrname, scheme[key])
719
720    def process_distribution(self, requirement, dist, deps=True, *info):
721        self.update_pth(dist)
722        self.package_index.add(dist)
723        if dist in self.local_index[dist.key]:
724            self.local_index.remove(dist)
725        self.local_index.add(dist)
726        self.install_egg_scripts(dist)
727        self.installed_projects[dist.key] = dist
728        log.info(self.installation_report(requirement, dist, *info))
729        if (dist.has_metadata('dependency_links.txt') and
730                not self.no_find_links):
731            self.package_index.add_find_links(
732                dist.get_metadata_lines('dependency_links.txt')
733            )
734        if not deps and not self.always_copy:
735            return
736        elif requirement is not None and dist.key != requirement.key:
737            log.warn("Skipping dependencies for %s", dist)
738            return  # XXX this is not the distribution we were looking for
739        elif requirement is None or dist not in requirement:
740            # if we wound up with a different version, resolve what we've got
741            distreq = dist.as_requirement()
742            requirement = Requirement(str(distreq))
743        log.info("Processing dependencies for %s", requirement)
744        try:
745            distros = WorkingSet([]).resolve(
746                [requirement], self.local_index, self.easy_install
747            )
748        except DistributionNotFound as e:
749            raise DistutilsError(str(e))
750        except VersionConflict as e:
751            raise DistutilsError(e.report())
752        if self.always_copy or self.always_copy_from:
753            # Force all the relevant distros to be copied or activated
754            for dist in distros:
755                if dist.key not in self.installed_projects:
756                    self.easy_install(dist.as_requirement())
757        log.info("Finished processing dependencies for %s", requirement)
758
759    def should_unzip(self, dist):
760        if self.zip_ok is not None:
761            return not self.zip_ok
762        if dist.has_metadata('not-zip-safe'):
763            return True
764        if not dist.has_metadata('zip-safe'):
765            return True
766        return False
767
768    def maybe_move(self, spec, dist_filename, setup_base):
769        dst = os.path.join(self.build_directory, spec.key)
770        if os.path.exists(dst):
771            msg = (
772                "%r already exists in %s; build directory %s will not be kept"
773            )
774            log.warn(msg, spec.key, self.build_directory, setup_base)
775            return setup_base
776        if os.path.isdir(dist_filename):
777            setup_base = dist_filename
778        else:
779            if os.path.dirname(dist_filename) == setup_base:
780                os.unlink(dist_filename)  # get it out of the tmp dir
781            contents = os.listdir(setup_base)
782            if len(contents) == 1:
783                dist_filename = os.path.join(setup_base, contents[0])
784                if os.path.isdir(dist_filename):
785                    # if the only thing there is a directory, move it instead
786                    setup_base = dist_filename
787        ensure_directory(dst)
788        shutil.move(setup_base, dst)
789        return dst
790
791    def install_wrapper_scripts(self, dist):
792        if self.exclude_scripts:
793            return
794        for args in ScriptWriter.best().get_args(dist):
795            self.write_script(*args)
796
797    def install_script(self, dist, script_name, script_text, dev_path=None):
798        """Generate a legacy script wrapper and install it"""
799        spec = str(dist.as_requirement())
800        is_script = is_python_script(script_text, script_name)
801
802        if is_script:
803            body = self._load_template(dev_path) % locals()
804            script_text = ScriptWriter.get_header(script_text) + body
805        self.write_script(script_name, _to_ascii(script_text), 'b')
806
807    @staticmethod
808    def _load_template(dev_path):
809        """
810        There are a couple of template scripts in the package. This
811        function loads one of them and prepares it for use.
812        """
813        # See https://github.com/pypa/setuptools/issues/134 for info
814        # on script file naming and downstream issues with SVR4
815        name = 'script.tmpl'
816        if dev_path:
817            name = name.replace('.tmpl', ' (dev).tmpl')
818
819        raw_bytes = resource_string('setuptools', name)
820        return raw_bytes.decode('utf-8')
821
822    def write_script(self, script_name, contents, mode="t", blockers=()):
823        """Write an executable file to the scripts directory"""
824        self.delete_blockers(  # clean up old .py/.pyw w/o a script
825            [os.path.join(self.script_dir, x) for x in blockers]
826        )
827        log.info("Installing %s script to %s", script_name, self.script_dir)
828        target = os.path.join(self.script_dir, script_name)
829        self.add_output(target)
830
831        if self.dry_run:
832            return
833
834        mask = current_umask()
835        ensure_directory(target)
836        if os.path.exists(target):
837            os.unlink(target)
838        with open(target, "w" + mode) as f:
839            f.write(contents)
840        chmod(target, 0o777 - mask)
841
842    def install_eggs(self, spec, dist_filename, tmpdir):
843        # .egg dirs or files are already built, so just return them
844        if dist_filename.lower().endswith('.egg'):
845            return [self.install_egg(dist_filename, tmpdir)]
846        elif dist_filename.lower().endswith('.exe'):
847            return [self.install_exe(dist_filename, tmpdir)]
848        elif dist_filename.lower().endswith('.whl'):
849            return [self.install_wheel(dist_filename, tmpdir)]
850
851        # Anything else, try to extract and build
852        setup_base = tmpdir
853        if os.path.isfile(dist_filename) and not dist_filename.endswith('.py'):
854            unpack_archive(dist_filename, tmpdir, self.unpack_progress)
855        elif os.path.isdir(dist_filename):
856            setup_base = os.path.abspath(dist_filename)
857
858        if (setup_base.startswith(tmpdir)  # something we downloaded
859                and self.build_directory and spec is not None):
860            setup_base = self.maybe_move(spec, dist_filename, setup_base)
861
862        # Find the setup.py file
863        setup_script = os.path.join(setup_base, 'setup.py')
864
865        if not os.path.exists(setup_script):
866            setups = glob(os.path.join(setup_base, '*', 'setup.py'))
867            if not setups:
868                raise DistutilsError(
869                    "Couldn't find a setup script in %s" %
870                    os.path.abspath(dist_filename)
871                )
872            if len(setups) > 1:
873                raise DistutilsError(
874                    "Multiple setup scripts in %s" %
875                    os.path.abspath(dist_filename)
876                )
877            setup_script = setups[0]
878
879        # Now run it, and return the result
880        if self.editable:
881            log.info(self.report_editable(spec, setup_script))
882            return []
883        else:
884            return self.build_and_install(setup_script, setup_base)
885
886    def egg_distribution(self, egg_path):
887        if os.path.isdir(egg_path):
888            metadata = PathMetadata(egg_path, os.path.join(egg_path,
889                                                           'EGG-INFO'))
890        else:
891            metadata = EggMetadata(zipimport.zipimporter(egg_path))
892        return Distribution.from_filename(egg_path, metadata=metadata)
893
894    def install_egg(self, egg_path, tmpdir):
895        destination = os.path.join(
896            self.install_dir,
897            os.path.basename(egg_path),
898        )
899        destination = os.path.abspath(destination)
900        if not self.dry_run:
901            ensure_directory(destination)
902
903        dist = self.egg_distribution(egg_path)
904        if not samefile(egg_path, destination):
905            if os.path.isdir(destination) and not os.path.islink(destination):
906                dir_util.remove_tree(destination, dry_run=self.dry_run)
907            elif os.path.exists(destination):
908                self.execute(
909                    os.unlink,
910                    (destination,),
911                    "Removing " + destination,
912                )
913            try:
914                new_dist_is_zipped = False
915                if os.path.isdir(egg_path):
916                    if egg_path.startswith(tmpdir):
917                        f, m = shutil.move, "Moving"
918                    else:
919                        f, m = shutil.copytree, "Copying"
920                elif self.should_unzip(dist):
921                    self.mkpath(destination)
922                    f, m = self.unpack_and_compile, "Extracting"
923                else:
924                    new_dist_is_zipped = True
925                    if egg_path.startswith(tmpdir):
926                        f, m = shutil.move, "Moving"
927                    else:
928                        f, m = shutil.copy2, "Copying"
929                self.execute(
930                    f,
931                    (egg_path, destination),
932                    (m + " %s to %s") % (
933                        os.path.basename(egg_path),
934                        os.path.dirname(destination)
935                    ),
936                )
937                update_dist_caches(
938                    destination,
939                    fix_zipimporter_caches=new_dist_is_zipped,
940                )
941            except Exception:
942                update_dist_caches(destination, fix_zipimporter_caches=False)
943                raise
944
945        self.add_output(destination)
946        return self.egg_distribution(destination)
947
948    def install_exe(self, dist_filename, tmpdir):
949        # See if it's valid, get data
950        cfg = extract_wininst_cfg(dist_filename)
951        if cfg is None:
952            raise DistutilsError(
953                "%s is not a valid distutils Windows .exe" % dist_filename
954            )
955        # Create a dummy distribution object until we build the real distro
956        dist = Distribution(
957            None,
958            project_name=cfg.get('metadata', 'name'),
959            version=cfg.get('metadata', 'version'), platform=get_platform(),
960        )
961
962        # Convert the .exe to an unpacked egg
963        egg_path = os.path.join(tmpdir, dist.egg_name() + '.egg')
964        dist.location = egg_path
965        egg_tmp = egg_path + '.tmp'
966        _egg_info = os.path.join(egg_tmp, 'EGG-INFO')
967        pkg_inf = os.path.join(_egg_info, 'PKG-INFO')
968        ensure_directory(pkg_inf)  # make sure EGG-INFO dir exists
969        dist._provider = PathMetadata(egg_tmp, _egg_info)  # XXX
970        self.exe_to_egg(dist_filename, egg_tmp)
971
972        # Write EGG-INFO/PKG-INFO
973        if not os.path.exists(pkg_inf):
974            f = open(pkg_inf, 'w')
975            f.write('Metadata-Version: 1.0\n')
976            for k, v in cfg.items('metadata'):
977                if k != 'target_version':
978                    f.write('%s: %s\n' % (k.replace('_', '-').title(), v))
979            f.close()
980        script_dir = os.path.join(_egg_info, 'scripts')
981        # delete entry-point scripts to avoid duping
982        self.delete_blockers([
983            os.path.join(script_dir, args[0])
984            for args in ScriptWriter.get_args(dist)
985        ])
986        # Build .egg file from tmpdir
987        bdist_egg.make_zipfile(
988            egg_path, egg_tmp, verbose=self.verbose, dry_run=self.dry_run,
989        )
990        # install the .egg
991        return self.install_egg(egg_path, tmpdir)
992
993    def exe_to_egg(self, dist_filename, egg_tmp):
994        """Extract a bdist_wininst to the directories an egg would use"""
995        # Check for .pth file and set up prefix translations
996        prefixes = get_exe_prefixes(dist_filename)
997        to_compile = []
998        native_libs = []
999        top_level = {}
1000
1001        def process(src, dst):
1002            s = src.lower()
1003            for old, new in prefixes:
1004                if s.startswith(old):
1005                    src = new + src[len(old):]
1006                    parts = src.split('/')
1007                    dst = os.path.join(egg_tmp, *parts)
1008                    dl = dst.lower()
1009                    if dl.endswith('.pyd') or dl.endswith('.dll'):
1010                        parts[-1] = bdist_egg.strip_module(parts[-1])
1011                        top_level[os.path.splitext(parts[0])[0]] = 1
1012                        native_libs.append(src)
1013                    elif dl.endswith('.py') and old != 'SCRIPTS/':
1014                        top_level[os.path.splitext(parts[0])[0]] = 1
1015                        to_compile.append(dst)
1016                    return dst
1017            if not src.endswith('.pth'):
1018                log.warn("WARNING: can't process %s", src)
1019            return None
1020
1021        # extract, tracking .pyd/.dll->native_libs and .py -> to_compile
1022        unpack_archive(dist_filename, egg_tmp, process)
1023        stubs = []
1024        for res in native_libs:
1025            if res.lower().endswith('.pyd'):  # create stubs for .pyd's
1026                parts = res.split('/')
1027                resource = parts[-1]
1028                parts[-1] = bdist_egg.strip_module(parts[-1]) + '.py'
1029                pyfile = os.path.join(egg_tmp, *parts)
1030                to_compile.append(pyfile)
1031                stubs.append(pyfile)
1032                bdist_egg.write_stub(resource, pyfile)
1033        self.byte_compile(to_compile)  # compile .py's
1034        bdist_egg.write_safety_flag(
1035            os.path.join(egg_tmp, 'EGG-INFO'),
1036            bdist_egg.analyze_egg(egg_tmp, stubs))  # write zip-safety flag
1037
1038        for name in 'top_level', 'native_libs':
1039            if locals()[name]:
1040                txt = os.path.join(egg_tmp, 'EGG-INFO', name + '.txt')
1041                if not os.path.exists(txt):
1042                    f = open(txt, 'w')
1043                    f.write('\n'.join(locals()[name]) + '\n')
1044                    f.close()
1045
1046    def install_wheel(self, wheel_path, tmpdir):
1047        wheel = Wheel(wheel_path)
1048        assert wheel.is_compatible()
1049        destination = os.path.join(self.install_dir, wheel.egg_name())
1050        destination = os.path.abspath(destination)
1051        if not self.dry_run:
1052            ensure_directory(destination)
1053        if os.path.isdir(destination) and not os.path.islink(destination):
1054            dir_util.remove_tree(destination, dry_run=self.dry_run)
1055        elif os.path.exists(destination):
1056            self.execute(
1057                os.unlink,
1058                (destination,),
1059                "Removing " + destination,
1060            )
1061        try:
1062            self.execute(
1063                wheel.install_as_egg,
1064                (destination,),
1065                ("Installing %s to %s") % (
1066                    os.path.basename(wheel_path),
1067                    os.path.dirname(destination)
1068                ),
1069            )
1070        finally:
1071            update_dist_caches(destination, fix_zipimporter_caches=False)
1072        self.add_output(destination)
1073        return self.egg_distribution(destination)
1074
1075    __mv_warning = textwrap.dedent("""
1076        Because this distribution was installed --multi-version, before you can
1077        import modules from this package in an application, you will need to
1078        'import pkg_resources' and then use a 'require()' call similar to one of
1079        these examples, in order to select the desired version:
1080
1081            pkg_resources.require("%(name)s")  # latest installed version
1082            pkg_resources.require("%(name)s==%(version)s")  # this exact version
1083            pkg_resources.require("%(name)s>=%(version)s")  # this version or higher
1084        """).lstrip()
1085
1086    __id_warning = textwrap.dedent("""
1087        Note also that the installation directory must be on sys.path at runtime for
1088        this to work.  (e.g. by being the application's script directory, by being on
1089        PYTHONPATH, or by being added to sys.path by your code.)
1090        """)
1091
1092    def installation_report(self, req, dist, what="Installed"):
1093        """Helpful installation message for display to package users"""
1094        msg = "\n%(what)s %(eggloc)s%(extras)s"
1095        if self.multi_version and not self.no_report:
1096            msg += '\n' + self.__mv_warning
1097            if self.install_dir not in map(normalize_path, sys.path):
1098                msg += '\n' + self.__id_warning
1099
1100        eggloc = dist.location
1101        name = dist.project_name
1102        version = dist.version
1103        extras = ''  # TODO: self.report_extras(req, dist)
1104        return msg % locals()
1105
1106    __editable_msg = textwrap.dedent("""
1107        Extracted editable version of %(spec)s to %(dirname)s
1108
1109        If it uses setuptools in its setup script, you can activate it in
1110        "development" mode by going to that directory and running::
1111
1112            %(python)s setup.py develop
1113
1114        See the setuptools documentation for the "develop" command for more info.
1115        """).lstrip()
1116
1117    def report_editable(self, spec, setup_script):
1118        dirname = os.path.dirname(setup_script)
1119        python = sys.executable
1120        return '\n' + self.__editable_msg % locals()
1121
1122    def run_setup(self, setup_script, setup_base, args):
1123        sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg)
1124        sys.modules.setdefault('distutils.command.egg_info', egg_info)
1125
1126        args = list(args)
1127        if self.verbose > 2:
1128            v = 'v' * (self.verbose - 1)
1129            args.insert(0, '-' + v)
1130        elif self.verbose < 2:
1131            args.insert(0, '-q')
1132        if self.dry_run:
1133            args.insert(0, '-n')
1134        log.info(
1135            "Running %s %s", setup_script[len(setup_base) + 1:], ' '.join(args)
1136        )
1137        try:
1138            run_setup(setup_script, args)
1139        except SystemExit as v:
1140            raise DistutilsError("Setup script exited with %s" % (v.args[0],))
1141
1142    def build_and_install(self, setup_script, setup_base):
1143        args = ['bdist_egg', '--dist-dir']
1144
1145        dist_dir = tempfile.mkdtemp(
1146            prefix='egg-dist-tmp-', dir=os.path.dirname(setup_script)
1147        )
1148        try:
1149            self._set_fetcher_options(os.path.dirname(setup_script))
1150            args.append(dist_dir)
1151
1152            self.run_setup(setup_script, setup_base, args)
1153            all_eggs = Environment([dist_dir])
1154            eggs = []
1155            for key in all_eggs:
1156                for dist in all_eggs[key]:
1157                    eggs.append(self.install_egg(dist.location, setup_base))
1158            if not eggs and not self.dry_run:
1159                log.warn("No eggs found in %s (setup script problem?)",
1160                         dist_dir)
1161            return eggs
1162        finally:
1163            rmtree(dist_dir)
1164            log.set_verbosity(self.verbose)  # restore our log verbosity
1165
1166    def _set_fetcher_options(self, base):
1167        """
1168        When easy_install is about to run bdist_egg on a source dist, that
1169        source dist might have 'setup_requires' directives, requiring
1170        additional fetching. Ensure the fetcher options given to easy_install
1171        are available to that command as well.
1172        """
1173        # find the fetch options from easy_install and write them out
1174        # to the setup.cfg file.
1175        ei_opts = self.distribution.get_option_dict('easy_install').copy()
1176        fetch_directives = (
1177            'find_links', 'site_dirs', 'index_url', 'optimize',
1178            'site_dirs', 'allow_hosts',
1179        )
1180        fetch_options = {}
1181        for key, val in ei_opts.items():
1182            if key not in fetch_directives:
1183                continue
1184            fetch_options[key.replace('_', '-')] = val[1]
1185        # create a settings dictionary suitable for `edit_config`
1186        settings = dict(easy_install=fetch_options)
1187        cfg_filename = os.path.join(base, 'setup.cfg')
1188        setopt.edit_config(cfg_filename, settings)
1189
1190    def update_pth(self, dist):
1191        if self.pth_file is None:
1192            return
1193
1194        for d in self.pth_file[dist.key]:  # drop old entries
1195            if self.multi_version or d.location != dist.location:
1196                log.info("Removing %s from easy-install.pth file", d)
1197                self.pth_file.remove(d)
1198                if d.location in self.shadow_path:
1199                    self.shadow_path.remove(d.location)
1200
1201        if not self.multi_version:
1202            if dist.location in self.pth_file.paths:
1203                log.info(
1204                    "%s is already the active version in easy-install.pth",
1205                    dist,
1206                )
1207            else:
1208                log.info("Adding %s to easy-install.pth file", dist)
1209                self.pth_file.add(dist)  # add new entry
1210                if dist.location not in self.shadow_path:
1211                    self.shadow_path.append(dist.location)
1212
1213        if not self.dry_run:
1214
1215            self.pth_file.save()
1216
1217            if dist.key == 'setuptools':
1218                # Ensure that setuptools itself never becomes unavailable!
1219                # XXX should this check for latest version?
1220                filename = os.path.join(self.install_dir, 'setuptools.pth')
1221                if os.path.islink(filename):
1222                    os.unlink(filename)
1223                f = open(filename, 'wt')
1224                f.write(self.pth_file.make_relative(dist.location) + '\n')
1225                f.close()
1226
1227    def unpack_progress(self, src, dst):
1228        # Progress filter for unpacking
1229        log.debug("Unpacking %s to %s", src, dst)
1230        return dst  # only unpack-and-compile skips files for dry run
1231
1232    def unpack_and_compile(self, egg_path, destination):
1233        to_compile = []
1234        to_chmod = []
1235
1236        def pf(src, dst):
1237            if dst.endswith('.py') and not src.startswith('EGG-INFO/'):
1238                to_compile.append(dst)
1239            elif dst.endswith('.dll') or dst.endswith('.so'):
1240                to_chmod.append(dst)
1241            self.unpack_progress(src, dst)
1242            return not self.dry_run and dst or None
1243
1244        unpack_archive(egg_path, destination, pf)
1245        self.byte_compile(to_compile)
1246        if not self.dry_run:
1247            for f in to_chmod:
1248                mode = ((os.stat(f)[stat.ST_MODE]) | 0o555) & 0o7755
1249                chmod(f, mode)
1250
1251    def byte_compile(self, to_compile):
1252        if sys.dont_write_bytecode:
1253            return
1254
1255        from distutils.util import byte_compile
1256
1257        try:
1258            # try to make the byte compile messages quieter
1259            log.set_verbosity(self.verbose - 1)
1260
1261            byte_compile(to_compile, optimize=0, force=1, dry_run=self.dry_run)
1262            if self.optimize:
1263                byte_compile(
1264                    to_compile, optimize=self.optimize, force=1,
1265                    dry_run=self.dry_run,
1266                )
1267        finally:
1268            log.set_verbosity(self.verbose)  # restore original verbosity
1269
1270    __no_default_msg = textwrap.dedent("""
1271        bad install directory or PYTHONPATH
1272
1273        You are attempting to install a package to a directory that is not
1274        on PYTHONPATH and which Python does not read ".pth" files from.  The
1275        installation directory you specified (via --install-dir, --prefix, or
1276        the distutils default setting) was:
1277
1278            %s
1279
1280        and your PYTHONPATH environment variable currently contains:
1281
1282            %r
1283
1284        Here are some of your options for correcting the problem:
1285
1286        * You can choose a different installation directory, i.e., one that is
1287          on PYTHONPATH or supports .pth files
1288
1289        * You can add the installation directory to the PYTHONPATH environment
1290          variable.  (It must then also be on PYTHONPATH whenever you run
1291          Python and want to use the package(s) you are installing.)
1292
1293        * You can set up the installation directory to support ".pth" files by
1294          using one of the approaches described here:
1295
1296          https://setuptools.readthedocs.io/en/latest/easy_install.html#custom-installation-locations
1297
1298
1299        Please make the appropriate changes for your system and try again.""").lstrip()
1300
1301    def no_default_version_msg(self):
1302        template = self.__no_default_msg
1303        return template % (self.install_dir, os.environ.get('PYTHONPATH', ''))
1304
1305    def install_site_py(self):
1306        """Make sure there's a site.py in the target dir, if needed"""
1307
1308        if self.sitepy_installed:
1309            return  # already did it, or don't need to
1310
1311        sitepy = os.path.join(self.install_dir, "site.py")
1312        source = resource_string("setuptools", "site-patch.py")
1313        source = source.decode('utf-8')
1314        current = ""
1315
1316        if os.path.exists(sitepy):
1317            log.debug("Checking existing site.py in %s", self.install_dir)
1318            with io.open(sitepy) as strm:
1319                current = strm.read()
1320
1321            if not current.startswith('def __boot():'):
1322                raise DistutilsError(
1323                    "%s is not a setuptools-generated site.py; please"
1324                    " remove it." % sitepy
1325                )
1326
1327        if current != source:
1328            log.info("Creating %s", sitepy)
1329            if not self.dry_run:
1330                ensure_directory(sitepy)
1331                with io.open(sitepy, 'w', encoding='utf-8') as strm:
1332                    strm.write(source)
1333            self.byte_compile([sitepy])
1334
1335        self.sitepy_installed = True
1336
1337    def create_home_path(self):
1338        """Create directories under ~."""
1339        if not self.user:
1340            return
1341        home = convert_path(os.path.expanduser("~"))
1342        for name, path in six.iteritems(self.config_vars):
1343            if path.startswith(home) and not os.path.isdir(path):
1344                self.debug_print("os.makedirs('%s', 0o700)" % path)
1345                os.makedirs(path, 0o700)
1346
1347    INSTALL_SCHEMES = dict(
1348        posix=dict(
1349            install_dir='$base/lib/python$py_version_short/site-packages',
1350            script_dir='$base/bin',
1351        ),
1352    )
1353
1354    DEFAULT_SCHEME = dict(
1355        install_dir='$base/Lib/site-packages',
1356        script_dir='$base/Scripts',
1357    )
1358
1359    def _expand(self, *attrs):
1360        config_vars = self.get_finalized_command('install').config_vars
1361
1362        if self.prefix:
1363            # Set default install_dir/scripts from --prefix
1364            config_vars = config_vars.copy()
1365            config_vars['base'] = self.prefix
1366            scheme = self.INSTALL_SCHEMES.get(os.name, self.DEFAULT_SCHEME)
1367            for attr, val in scheme.items():
1368                if getattr(self, attr, None) is None:
1369                    setattr(self, attr, val)
1370
1371        from distutils.util import subst_vars
1372
1373        for attr in attrs:
1374            val = getattr(self, attr)
1375            if val is not None:
1376                val = subst_vars(val, config_vars)
1377                if os.name == 'posix':
1378                    val = os.path.expanduser(val)
1379                setattr(self, attr, val)
1380
1381
1382def _pythonpath():
1383    items = os.environ.get('PYTHONPATH', '').split(os.pathsep)
1384    return filter(None, items)
1385
1386
1387def get_site_dirs():
1388    """
1389    Return a list of 'site' dirs
1390    """
1391
1392    sitedirs = []
1393
1394    # start with PYTHONPATH
1395    sitedirs.extend(_pythonpath())
1396
1397    prefixes = [sys.prefix]
1398    if sys.exec_prefix != sys.prefix:
1399        prefixes.append(sys.exec_prefix)
1400    for prefix in prefixes:
1401        if prefix:
1402            if sys.platform in ('os2emx', 'riscos'):
1403                sitedirs.append(os.path.join(prefix, "Lib", "site-packages"))
1404            elif os.sep == '/':
1405                sitedirs.extend([
1406                    os.path.join(
1407                        prefix,
1408                        "lib",
1409                        "python" + sys.version[:3],
1410                        "site-packages",
1411                    ),
1412                    os.path.join(prefix, "lib", "site-python"),
1413                ])
1414            else:
1415                sitedirs.extend([
1416                    prefix,
1417                    os.path.join(prefix, "lib", "site-packages"),
1418                ])
1419            if sys.platform == 'darwin':
1420                # for framework builds *only* we add the standard Apple
1421                # locations. Currently only per-user, but /Library and
1422                # /Network/Library could be added too
1423                if 'Python.framework' in prefix:
1424                    home = os.environ.get('HOME')
1425                    if home:
1426                        home_sp = os.path.join(
1427                            home,
1428                            'Library',
1429                            'Python',
1430                            sys.version[:3],
1431                            'site-packages',
1432                        )
1433                        sitedirs.append(home_sp)
1434    lib_paths = get_path('purelib'), get_path('platlib')
1435    for site_lib in lib_paths:
1436        if site_lib not in sitedirs:
1437            sitedirs.append(site_lib)
1438
1439    if site.ENABLE_USER_SITE:
1440        sitedirs.append(site.USER_SITE)
1441
1442    try:
1443        sitedirs.extend(site.getsitepackages())
1444    except AttributeError:
1445        pass
1446
1447    sitedirs = list(map(normalize_path, sitedirs))
1448
1449    return sitedirs
1450
1451
1452def expand_paths(inputs):
1453    """Yield sys.path directories that might contain "old-style" packages"""
1454
1455    seen = {}
1456
1457    for dirname in inputs:
1458        dirname = normalize_path(dirname)
1459        if dirname in seen:
1460            continue
1461
1462        seen[dirname] = 1
1463        if not os.path.isdir(dirname):
1464            continue
1465
1466        files = os.listdir(dirname)
1467        yield dirname, files
1468
1469        for name in files:
1470            if not name.endswith('.pth'):
1471                # We only care about the .pth files
1472                continue
1473            if name in ('easy-install.pth', 'setuptools.pth'):
1474                # Ignore .pth files that we control
1475                continue
1476
1477            # Read the .pth file
1478            f = open(os.path.join(dirname, name))
1479            lines = list(yield_lines(f))
1480            f.close()
1481
1482            # Yield existing non-dupe, non-import directory lines from it
1483            for line in lines:
1484                if not line.startswith("import"):
1485                    line = normalize_path(line.rstrip())
1486                    if line not in seen:
1487                        seen[line] = 1
1488                        if not os.path.isdir(line):
1489                            continue
1490                        yield line, os.listdir(line)
1491
1492
1493def extract_wininst_cfg(dist_filename):
1494    """Extract configuration data from a bdist_wininst .exe
1495
1496    Returns a configparser.RawConfigParser, or None
1497    """
1498    f = open(dist_filename, 'rb')
1499    try:
1500        endrec = zipfile._EndRecData(f)
1501        if endrec is None:
1502            return None
1503
1504        prepended = (endrec[9] - endrec[5]) - endrec[6]
1505        if prepended < 12:  # no wininst data here
1506            return None
1507        f.seek(prepended - 12)
1508
1509        tag, cfglen, bmlen = struct.unpack("<iii", f.read(12))
1510        if tag not in (0x1234567A, 0x1234567B):
1511            return None  # not a valid tag
1512
1513        f.seek(prepended - (12 + cfglen))
1514        init = {'version': '', 'target_version': ''}
1515        cfg = configparser.RawConfigParser(init)
1516        try:
1517            part = f.read(cfglen)
1518            # Read up to the first null byte.
1519            config = part.split(b'\0', 1)[0]
1520            # Now the config is in bytes, but for RawConfigParser, it should
1521            #  be text, so decode it.
1522            config = config.decode(sys.getfilesystemencoding())
1523            cfg.readfp(six.StringIO(config))
1524        except configparser.Error:
1525            return None
1526        if not cfg.has_section('metadata') or not cfg.has_section('Setup'):
1527            return None
1528        return cfg
1529
1530    finally:
1531        f.close()
1532
1533
1534def get_exe_prefixes(exe_filename):
1535    """Get exe->egg path translations for a given .exe file"""
1536
1537    prefixes = [
1538        ('PURELIB/', ''),
1539        ('PLATLIB/pywin32_system32', ''),
1540        ('PLATLIB/', ''),
1541        ('SCRIPTS/', 'EGG-INFO/scripts/'),
1542        ('DATA/lib/site-packages', ''),
1543    ]
1544    z = zipfile.ZipFile(exe_filename)
1545    try:
1546        for info in z.infolist():
1547            name = info.filename
1548            parts = name.split('/')
1549            if len(parts) == 3 and parts[2] == 'PKG-INFO':
1550                if parts[1].endswith('.egg-info'):
1551                    prefixes.insert(0, ('/'.join(parts[:2]), 'EGG-INFO/'))
1552                    break
1553            if len(parts) != 2 or not name.endswith('.pth'):
1554                continue
1555            if name.endswith('-nspkg.pth'):
1556                continue
1557            if parts[0].upper() in ('PURELIB', 'PLATLIB'):
1558                contents = z.read(name)
1559                if six.PY3:
1560                    contents = contents.decode()
1561                for pth in yield_lines(contents):
1562                    pth = pth.strip().replace('\\', '/')
1563                    if not pth.startswith('import'):
1564                        prefixes.append((('%s/%s/' % (parts[0], pth)), ''))
1565    finally:
1566        z.close()
1567    prefixes = [(x.lower(), y) for x, y in prefixes]
1568    prefixes.sort()
1569    prefixes.reverse()
1570    return prefixes
1571
1572
1573class PthDistributions(Environment):
1574    """A .pth file with Distribution paths in it"""
1575
1576    dirty = False
1577
1578    def __init__(self, filename, sitedirs=()):
1579        self.filename = filename
1580        self.sitedirs = list(map(normalize_path, sitedirs))
1581        self.basedir = normalize_path(os.path.dirname(self.filename))
1582        self._load()
1583        Environment.__init__(self, [], None, None)
1584        for path in yield_lines(self.paths):
1585            list(map(self.add, find_distributions(path, True)))
1586
1587    def _load(self):
1588        self.paths = []
1589        saw_import = False
1590        seen = dict.fromkeys(self.sitedirs)
1591        if os.path.isfile(self.filename):
1592            f = open(self.filename, 'rt')
1593            for line in f:
1594                if line.startswith('import'):
1595                    saw_import = True
1596                    continue
1597                path = line.rstrip()
1598                self.paths.append(path)
1599                if not path.strip() or path.strip().startswith('#'):
1600                    continue
1601                # skip non-existent paths, in case somebody deleted a package
1602                # manually, and duplicate paths as well
1603                path = self.paths[-1] = normalize_path(
1604                    os.path.join(self.basedir, path)
1605                )
1606                if not os.path.exists(path) or path in seen:
1607                    self.paths.pop()  # skip it
1608                    self.dirty = True  # we cleaned up, so we're dirty now :)
1609                    continue
1610                seen[path] = 1
1611            f.close()
1612
1613        if self.paths and not saw_import:
1614            self.dirty = True  # ensure anything we touch has import wrappers
1615        while self.paths and not self.paths[-1].strip():
1616            self.paths.pop()
1617
1618    def save(self):
1619        """Write changed .pth file back to disk"""
1620        if not self.dirty:
1621            return
1622
1623        rel_paths = list(map(self.make_relative, self.paths))
1624        if rel_paths:
1625            log.debug("Saving %s", self.filename)
1626            lines = self._wrap_lines(rel_paths)
1627            data = '\n'.join(lines) + '\n'
1628
1629            if os.path.islink(self.filename):
1630                os.unlink(self.filename)
1631            with open(self.filename, 'wt') as f:
1632                f.write(data)
1633
1634        elif os.path.exists(self.filename):
1635            log.debug("Deleting empty %s", self.filename)
1636            os.unlink(self.filename)
1637
1638        self.dirty = False
1639
1640    @staticmethod
1641    def _wrap_lines(lines):
1642        return lines
1643
1644    def add(self, dist):
1645        """Add `dist` to the distribution map"""
1646        new_path = (
1647            dist.location not in self.paths and (
1648                dist.location not in self.sitedirs or
1649                # account for '.' being in PYTHONPATH
1650                dist.location == os.getcwd()
1651            )
1652        )
1653        if new_path:
1654            self.paths.append(dist.location)
1655            self.dirty = True
1656        Environment.add(self, dist)
1657
1658    def remove(self, dist):
1659        """Remove `dist` from the distribution map"""
1660        while dist.location in self.paths:
1661            self.paths.remove(dist.location)
1662            self.dirty = True
1663        Environment.remove(self, dist)
1664
1665    def make_relative(self, path):
1666        npath, last = os.path.split(normalize_path(path))
1667        baselen = len(self.basedir)
1668        parts = [last]
1669        sep = os.altsep == '/' and '/' or os.sep
1670        while len(npath) >= baselen:
1671            if npath == self.basedir:
1672                parts.append(os.curdir)
1673                parts.reverse()
1674                return sep.join(parts)
1675            npath, last = os.path.split(npath)
1676            parts.append(last)
1677        else:
1678            return path
1679
1680
1681class RewritePthDistributions(PthDistributions):
1682    @classmethod
1683    def _wrap_lines(cls, lines):
1684        yield cls.prelude
1685        for line in lines:
1686            yield line
1687        yield cls.postlude
1688
1689    prelude = _one_liner("""
1690        import sys
1691        sys.__plen = len(sys.path)
1692        """)
1693    postlude = _one_liner("""
1694        import sys
1695        new = sys.path[sys.__plen:]
1696        del sys.path[sys.__plen:]
1697        p = getattr(sys, '__egginsert', 0)
1698        sys.path[p:p] = new
1699        sys.__egginsert = p + len(new)
1700        """)
1701
1702
1703if os.environ.get('SETUPTOOLS_SYS_PATH_TECHNIQUE', 'raw') == 'rewrite':
1704    PthDistributions = RewritePthDistributions
1705
1706
1707def _first_line_re():
1708    """
1709    Return a regular expression based on first_line_re suitable for matching
1710    strings.
1711    """
1712    if isinstance(first_line_re.pattern, str):
1713        return first_line_re
1714
1715    # first_line_re in Python >=3.1.4 and >=3.2.1 is a bytes pattern.
1716    return re.compile(first_line_re.pattern.decode())
1717
1718
1719def auto_chmod(func, arg, exc):
1720    if func in [os.unlink, os.remove] and os.name == 'nt':
1721        chmod(arg, stat.S_IWRITE)
1722        return func(arg)
1723    et, ev, _ = sys.exc_info()
1724    six.reraise(et, (ev[0], ev[1] + (" %s %s" % (func, arg))))
1725
1726
1727def update_dist_caches(dist_path, fix_zipimporter_caches):
1728    """
1729    Fix any globally cached `dist_path` related data
1730
1731    `dist_path` should be a path of a newly installed egg distribution (zipped
1732    or unzipped).
1733
1734    sys.path_importer_cache contains finder objects that have been cached when
1735    importing data from the original distribution. Any such finders need to be
1736    cleared since the replacement distribution might be packaged differently,
1737    e.g. a zipped egg distribution might get replaced with an unzipped egg
1738    folder or vice versa. Having the old finders cached may then cause Python
1739    to attempt loading modules from the replacement distribution using an
1740    incorrect loader.
1741
1742    zipimport.zipimporter objects are Python loaders charged with importing
1743    data packaged inside zip archives. If stale loaders referencing the
1744    original distribution, are left behind, they can fail to load modules from
1745    the replacement distribution. E.g. if an old zipimport.zipimporter instance
1746    is used to load data from a new zipped egg archive, it may cause the
1747    operation to attempt to locate the requested data in the wrong location -
1748    one indicated by the original distribution's zip archive directory
1749    information. Such an operation may then fail outright, e.g. report having
1750    read a 'bad local file header', or even worse, it may fail silently &
1751    return invalid data.
1752
1753    zipimport._zip_directory_cache contains cached zip archive directory
1754    information for all existing zipimport.zipimporter instances and all such
1755    instances connected to the same archive share the same cached directory
1756    information.
1757
1758    If asked, and the underlying Python implementation allows it, we can fix
1759    all existing zipimport.zipimporter instances instead of having to track
1760    them down and remove them one by one, by updating their shared cached zip
1761    archive directory information. This, of course, assumes that the
1762    replacement distribution is packaged as a zipped egg.
1763
1764    If not asked to fix existing zipimport.zipimporter instances, we still do
1765    our best to clear any remaining zipimport.zipimporter related cached data
1766    that might somehow later get used when attempting to load data from the new
1767    distribution and thus cause such load operations to fail. Note that when
1768    tracking down such remaining stale data, we can not catch every conceivable
1769    usage from here, and we clear only those that we know of and have found to
1770    cause problems if left alive. Any remaining caches should be updated by
1771    whomever is in charge of maintaining them, i.e. they should be ready to
1772    handle us replacing their zip archives with new distributions at runtime.
1773
1774    """
1775    # There are several other known sources of stale zipimport.zipimporter
1776    # instances that we do not clear here, but might if ever given a reason to
1777    # do so:
1778    # * Global setuptools pkg_resources.working_set (a.k.a. 'master working
1779    # set') may contain distributions which may in turn contain their
1780    #   zipimport.zipimporter loaders.
1781    # * Several zipimport.zipimporter loaders held by local variables further
1782    #   up the function call stack when running the setuptools installation.
1783    # * Already loaded modules may have their __loader__ attribute set to the
1784    #   exact loader instance used when importing them. Python 3.4 docs state
1785    #   that this information is intended mostly for introspection and so is
1786    #   not expected to cause us problems.
1787    normalized_path = normalize_path(dist_path)
1788    _uncache(normalized_path, sys.path_importer_cache)
1789    if fix_zipimporter_caches:
1790        _replace_zip_directory_cache_data(normalized_path)
1791    else:
1792        # Here, even though we do not want to fix existing and now stale
1793        # zipimporter cache information, we still want to remove it. Related to
1794        # Python's zip archive directory information cache, we clear each of
1795        # its stale entries in two phases:
1796        #   1. Clear the entry so attempting to access zip archive information
1797        #      via any existing stale zipimport.zipimporter instances fails.
1798        #   2. Remove the entry from the cache so any newly constructed
1799        #      zipimport.zipimporter instances do not end up using old stale
1800        #      zip archive directory information.
1801        # This whole stale data removal step does not seem strictly necessary,
1802        # but has been left in because it was done before we started replacing
1803        # the zip archive directory information cache content if possible, and
1804        # there are no relevant unit tests that we can depend on to tell us if
1805        # this is really needed.
1806        _remove_and_clear_zip_directory_cache_data(normalized_path)
1807
1808
1809def _collect_zipimporter_cache_entries(normalized_path, cache):
1810    """
1811    Return zipimporter cache entry keys related to a given normalized path.
1812
1813    Alternative path spellings (e.g. those using different character case or
1814    those using alternative path separators) related to the same path are
1815    included. Any sub-path entries are included as well, i.e. those
1816    corresponding to zip archives embedded in other zip archives.
1817
1818    """
1819    result = []
1820    prefix_len = len(normalized_path)
1821    for p in cache:
1822        np = normalize_path(p)
1823        if (np.startswith(normalized_path) and
1824                np[prefix_len:prefix_len + 1] in (os.sep, '')):
1825            result.append(p)
1826    return result
1827
1828
1829def _update_zipimporter_cache(normalized_path, cache, updater=None):
1830    """
1831    Update zipimporter cache data for a given normalized path.
1832
1833    Any sub-path entries are processed as well, i.e. those corresponding to zip
1834    archives embedded in other zip archives.
1835
1836    Given updater is a callable taking a cache entry key and the original entry
1837    (after already removing the entry from the cache), and expected to update
1838    the entry and possibly return a new one to be inserted in its place.
1839    Returning None indicates that the entry should not be replaced with a new
1840    one. If no updater is given, the cache entries are simply removed without
1841    any additional processing, the same as if the updater simply returned None.
1842
1843    """
1844    for p in _collect_zipimporter_cache_entries(normalized_path, cache):
1845        # N.B. pypy's custom zipimport._zip_directory_cache implementation does
1846        # not support the complete dict interface:
1847        # * Does not support item assignment, thus not allowing this function
1848        #    to be used only for removing existing cache entries.
1849        #  * Does not support the dict.pop() method, forcing us to use the
1850        #    get/del patterns instead. For more detailed information see the
1851        #    following links:
1852        #      https://github.com/pypa/setuptools/issues/202#issuecomment-202913420
1853        #      http://bit.ly/2h9itJX
1854        old_entry = cache[p]
1855        del cache[p]
1856        new_entry = updater and updater(p, old_entry)
1857        if new_entry is not None:
1858            cache[p] = new_entry
1859
1860
1861def _uncache(normalized_path, cache):
1862    _update_zipimporter_cache(normalized_path, cache)
1863
1864
1865def _remove_and_clear_zip_directory_cache_data(normalized_path):
1866    def clear_and_remove_cached_zip_archive_directory_data(path, old_entry):
1867        old_entry.clear()
1868
1869    _update_zipimporter_cache(
1870        normalized_path, zipimport._zip_directory_cache,
1871        updater=clear_and_remove_cached_zip_archive_directory_data)
1872
1873
1874# PyPy Python implementation does not allow directly writing to the
1875# zipimport._zip_directory_cache and so prevents us from attempting to correct
1876# its content. The best we can do there is clear the problematic cache content
1877# and have PyPy repopulate it as needed. The downside is that if there are any
1878# stale zipimport.zipimporter instances laying around, attempting to use them
1879# will fail due to not having its zip archive directory information available
1880# instead of being automatically corrected to use the new correct zip archive
1881# directory information.
1882if '__pypy__' in sys.builtin_module_names:
1883    _replace_zip_directory_cache_data = \
1884        _remove_and_clear_zip_directory_cache_data
1885else:
1886
1887    def _replace_zip_directory_cache_data(normalized_path):
1888        def replace_cached_zip_archive_directory_data(path, old_entry):
1889            # N.B. In theory, we could load the zip directory information just
1890            # once for all updated path spellings, and then copy it locally and
1891            # update its contained path strings to contain the correct
1892            # spelling, but that seems like a way too invasive move (this cache
1893            # structure is not officially documented anywhere and could in
1894            # theory change with new Python releases) for no significant
1895            # benefit.
1896            old_entry.clear()
1897            zipimport.zipimporter(path)
1898            old_entry.update(zipimport._zip_directory_cache[path])
1899            return old_entry
1900
1901        _update_zipimporter_cache(
1902            normalized_path, zipimport._zip_directory_cache,
1903            updater=replace_cached_zip_archive_directory_data)
1904
1905
1906def is_python(text, filename='<string>'):
1907    "Is this string a valid Python script?"
1908    try:
1909        compile(text, filename, 'exec')
1910    except (SyntaxError, TypeError):
1911        return False
1912    else:
1913        return True
1914
1915
1916def is_sh(executable):
1917    """Determine if the specified executable is a .sh (contains a #! line)"""
1918    try:
1919        with io.open(executable, encoding='latin-1') as fp:
1920            magic = fp.read(2)
1921    except (OSError, IOError):
1922        return executable
1923    return magic == '#!'
1924
1925
1926def nt_quote_arg(arg):
1927    """Quote a command line argument according to Windows parsing rules"""
1928    return subprocess.list2cmdline([arg])
1929
1930
1931def is_python_script(script_text, filename):
1932    """Is this text, as a whole, a Python script? (as opposed to shell/bat/etc.
1933    """
1934    if filename.endswith('.py') or filename.endswith('.pyw'):
1935        return True  # extension says it's Python
1936    if is_python(script_text, filename):
1937        return True  # it's syntactically valid Python
1938    if script_text.startswith('#!'):
1939        # It begins with a '#!' line, so check if 'python' is in it somewhere
1940        return 'python' in script_text.splitlines()[0].lower()
1941
1942    return False  # Not any Python I can recognize
1943
1944
1945try:
1946    from os import chmod as _chmod
1947except ImportError:
1948    # Jython compatibility
1949    def _chmod(*args):
1950        pass
1951
1952
1953def chmod(path, mode):
1954    log.debug("changing mode of %s to %o", path, mode)
1955    try:
1956        _chmod(path, mode)
1957    except os.error as e:
1958        log.debug("chmod failed: %s", e)
1959
1960
1961class CommandSpec(list):
1962    """
1963    A command spec for a #! header, specified as a list of arguments akin to
1964    those passed to Popen.
1965    """
1966
1967    options = []
1968    split_args = dict()
1969
1970    @classmethod
1971    def best(cls):
1972        """
1973        Choose the best CommandSpec class based on environmental conditions.
1974        """
1975        return cls
1976
1977    @classmethod
1978    def _sys_executable(cls):
1979        _default = os.path.normpath(sys.executable)
1980        return os.environ.get('__PYVENV_LAUNCHER__', _default)
1981
1982    @classmethod
1983    def from_param(cls, param):
1984        """
1985        Construct a CommandSpec from a parameter to build_scripts, which may
1986        be None.
1987        """
1988        if isinstance(param, cls):
1989            return param
1990        if isinstance(param, list):
1991            return cls(param)
1992        if param is None:
1993            return cls.from_environment()
1994        # otherwise, assume it's a string.
1995        return cls.from_string(param)
1996
1997    @classmethod
1998    def from_environment(cls):
1999        return cls([cls._sys_executable()])
2000
2001    @classmethod
2002    def from_string(cls, string):
2003        """
2004        Construct a command spec from a simple string representing a command
2005        line parseable by shlex.split.
2006        """
2007        items = shlex.split(string, **cls.split_args)
2008        return cls(items)
2009
2010    def install_options(self, script_text):
2011        self.options = shlex.split(self._extract_options(script_text))
2012        cmdline = subprocess.list2cmdline(self)
2013        if not isascii(cmdline):
2014            self.options[:0] = ['-x']
2015
2016    @staticmethod
2017    def _extract_options(orig_script):
2018        """
2019        Extract any options from the first line of the script.
2020        """
2021        first = (orig_script + '\n').splitlines()[0]
2022        match = _first_line_re().match(first)
2023        options = match.group(1) or '' if match else ''
2024        return options.strip()
2025
2026    def as_header(self):
2027        return self._render(self + list(self.options))
2028
2029    @staticmethod
2030    def _strip_quotes(item):
2031        _QUOTES = '"\''
2032        for q in _QUOTES:
2033            if item.startswith(q) and item.endswith(q):
2034                return item[1:-1]
2035        return item
2036
2037    @staticmethod
2038    def _render(items):
2039        cmdline = subprocess.list2cmdline(
2040            CommandSpec._strip_quotes(item.strip()) for item in items)
2041        return '#!' + cmdline + '\n'
2042
2043
2044# For pbr compat; will be removed in a future version.
2045sys_executable = CommandSpec._sys_executable()
2046
2047
2048class WindowsCommandSpec(CommandSpec):
2049    split_args = dict(posix=False)
2050
2051
2052class ScriptWriter(object):
2053    """
2054    Encapsulates behavior around writing entry point scripts for console and
2055    gui apps.
2056    """
2057
2058    template = textwrap.dedent(r"""
2059        # EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r
2060        __requires__ = %(spec)r
2061        import re
2062        import sys
2063        from pkg_resources import load_entry_point
2064
2065        if __name__ == '__main__':
2066            sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
2067            sys.exit(
2068                load_entry_point(%(spec)r, %(group)r, %(name)r)()
2069            )
2070    """).lstrip()
2071
2072    command_spec_class = CommandSpec
2073
2074    @classmethod
2075    def get_script_args(cls, dist, executable=None, wininst=False):
2076        # for backward compatibility
2077        warnings.warn("Use get_args", DeprecationWarning)
2078        writer = (WindowsScriptWriter if wininst else ScriptWriter).best()
2079        header = cls.get_script_header("", executable, wininst)
2080        return writer.get_args(dist, header)
2081
2082    @classmethod
2083    def get_script_header(cls, script_text, executable=None, wininst=False):
2084        # for backward compatibility
2085        warnings.warn("Use get_header", DeprecationWarning)
2086        if wininst:
2087            executable = "python.exe"
2088        cmd = cls.command_spec_class.best().from_param(executable)
2089        cmd.install_options(script_text)
2090        return cmd.as_header()
2091
2092    @classmethod
2093    def get_args(cls, dist, header=None):
2094        """
2095        Yield write_script() argument tuples for a distribution's
2096        console_scripts and gui_scripts entry points.
2097        """
2098        if header is None:
2099            header = cls.get_header()
2100        spec = str(dist.as_requirement())
2101        for type_ in 'console', 'gui':
2102            group = type_ + '_scripts'
2103            for name, ep in dist.get_entry_map(group).items():
2104                cls._ensure_safe_name(name)
2105                script_text = cls.template % locals()
2106                args = cls._get_script_args(type_, name, header, script_text)
2107                for res in args:
2108                    yield res
2109
2110    @staticmethod
2111    def _ensure_safe_name(name):
2112        """
2113        Prevent paths in *_scripts entry point names.
2114        """
2115        has_path_sep = re.search(r'[\\/]', name)
2116        if has_path_sep:
2117            raise ValueError("Path separators not allowed in script names")
2118
2119    @classmethod
2120    def get_writer(cls, force_windows):
2121        # for backward compatibility
2122        warnings.warn("Use best", DeprecationWarning)
2123        return WindowsScriptWriter.best() if force_windows else cls.best()
2124
2125    @classmethod
2126    def best(cls):
2127        """
2128        Select the best ScriptWriter for this environment.
2129        """
2130        if sys.platform == 'win32' or (os.name == 'java' and os._name == 'nt'):
2131            return WindowsScriptWriter.best()
2132        else:
2133            return cls
2134
2135    @classmethod
2136    def _get_script_args(cls, type_, name, header, script_text):
2137        # Simply write the stub with no extension.
2138        yield (name, header + script_text)
2139
2140    @classmethod
2141    def get_header(cls, script_text="", executable=None):
2142        """Create a #! line, getting options (if any) from script_text"""
2143        cmd = cls.command_spec_class.best().from_param(executable)
2144        cmd.install_options(script_text)
2145        return cmd.as_header()
2146
2147
2148class WindowsScriptWriter(ScriptWriter):
2149    command_spec_class = WindowsCommandSpec
2150
2151    @classmethod
2152    def get_writer(cls):
2153        # for backward compatibility
2154        warnings.warn("Use best", DeprecationWarning)
2155        return cls.best()
2156
2157    @classmethod
2158    def best(cls):
2159        """
2160        Select the best ScriptWriter suitable for Windows
2161        """
2162        writer_lookup = dict(
2163            executable=WindowsExecutableLauncherWriter,
2164            natural=cls,
2165        )
2166        # for compatibility, use the executable launcher by default
2167        launcher = os.environ.get('SETUPTOOLS_LAUNCHER', 'executable')
2168        return writer_lookup[launcher]
2169
2170    @classmethod
2171    def _get_script_args(cls, type_, name, header, script_text):
2172        "For Windows, add a .py extension"
2173        ext = dict(console='.pya', gui='.pyw')[type_]
2174        if ext not in os.environ['PATHEXT'].lower().split(';'):
2175            msg = (
2176                "{ext} not listed in PATHEXT; scripts will not be "
2177                "recognized as executables."
2178            ).format(**locals())
2179            warnings.warn(msg, UserWarning)
2180        old = ['.pya', '.py', '-script.py', '.pyc', '.pyo', '.pyw', '.exe']
2181        old.remove(ext)
2182        header = cls._adjust_header(type_, header)
2183        blockers = [name + x for x in old]
2184        yield name + ext, header + script_text, 't', blockers
2185
2186    @classmethod
2187    def _adjust_header(cls, type_, orig_header):
2188        """
2189        Make sure 'pythonw' is used for gui and and 'python' is used for
2190        console (regardless of what sys.executable is).
2191        """
2192        pattern = 'pythonw.exe'
2193        repl = 'python.exe'
2194        if type_ == 'gui':
2195            pattern, repl = repl, pattern
2196        pattern_ob = re.compile(re.escape(pattern), re.IGNORECASE)
2197        new_header = pattern_ob.sub(string=orig_header, repl=repl)
2198        return new_header if cls._use_header(new_header) else orig_header
2199
2200    @staticmethod
2201    def _use_header(new_header):
2202        """
2203        Should _adjust_header use the replaced header?
2204
2205        On non-windows systems, always use. On
2206        Windows systems, only use the replaced header if it resolves
2207        to an executable on the system.
2208        """
2209        clean_header = new_header[2:-1].strip('"')
2210        return sys.platform != 'win32' or find_executable(clean_header)
2211
2212
2213class WindowsExecutableLauncherWriter(WindowsScriptWriter):
2214    @classmethod
2215    def _get_script_args(cls, type_, name, header, script_text):
2216        """
2217        For Windows, add a .py extension and an .exe launcher
2218        """
2219        if type_ == 'gui':
2220            launcher_type = 'gui'
2221            ext = '-script.pyw'
2222            old = ['.pyw']
2223        else:
2224            launcher_type = 'cli'
2225            ext = '-script.py'
2226            old = ['.py', '.pyc', '.pyo']
2227        hdr = cls._adjust_header(type_, header)
2228        blockers = [name + x for x in old]
2229        yield (name + ext, hdr + script_text, 't', blockers)
2230        yield (
2231            name + '.exe', get_win_launcher(launcher_type),
2232            'b'  # write in binary mode
2233        )
2234        if not is_64bit():
2235            # install a manifest for the launcher to prevent Windows
2236            # from detecting it as an installer (which it will for
2237            #  launchers like easy_install.exe). Consider only
2238            #  adding a manifest for launchers detected as installers.
2239            #  See Distribute #143 for details.
2240            m_name = name + '.exe.manifest'
2241            yield (m_name, load_launcher_manifest(name), 't')
2242
2243
2244# for backward-compatibility
2245get_script_args = ScriptWriter.get_script_args
2246get_script_header = ScriptWriter.get_script_header
2247
2248
2249def get_win_launcher(type):
2250    """
2251    Load the Windows launcher (executable) suitable for launching a script.
2252
2253    `type` should be either 'cli' or 'gui'
2254
2255    Returns the executable as a byte string.
2256    """
2257    launcher_fn = '%s.exe' % type
2258    if is_64bit():
2259        launcher_fn = launcher_fn.replace(".", "-64.")
2260    else:
2261        launcher_fn = launcher_fn.replace(".", "-32.")
2262    return resource_string('setuptools', launcher_fn)
2263
2264
2265def load_launcher_manifest(name):
2266    manifest = pkg_resources.resource_string(__name__, 'launcher manifest.xml')
2267    if six.PY2:
2268        return manifest % vars()
2269    else:
2270        return manifest.decode('utf-8') % vars()
2271
2272
2273def rmtree(path, ignore_errors=False, onerror=auto_chmod):
2274    return shutil.rmtree(path, ignore_errors, onerror)
2275
2276
2277def current_umask():
2278    tmp = os.umask(0o022)
2279    os.umask(tmp)
2280    return tmp
2281
2282
2283def bootstrap():
2284    # This function is called when setuptools*.egg is run using /bin/sh
2285    import setuptools
2286
2287    argv0 = os.path.dirname(setuptools.__path__[0])
2288    sys.argv[0] = argv0
2289    sys.argv.append(argv0)
2290    main()
2291
2292
2293def main(argv=None, **kw):
2294    from setuptools import setup
2295    from setuptools.dist import Distribution
2296
2297    class DistributionWithoutHelpCommands(Distribution):
2298        common_usage = ""
2299
2300        def _show_help(self, *args, **kw):
2301            with _patch_usage():
2302                Distribution._show_help(self, *args, **kw)
2303
2304    if argv is None:
2305        argv = sys.argv[1:]
2306
2307    with _patch_usage():
2308        setup(
2309            script_args=['-q', 'easy_install', '-v'] + argv,
2310            script_name=sys.argv[0] or 'easy_install',
2311            distclass=DistributionWithoutHelpCommands,
2312            **kw
2313        )
2314
2315
2316@contextlib.contextmanager
2317def _patch_usage():
2318    import distutils.core
2319    USAGE = textwrap.dedent("""
2320        usage: %(script)s [options] requirement_or_url ...
2321           or: %(script)s --help
2322        """).lstrip()
2323
2324    def gen_usage(script_name):
2325        return USAGE % dict(
2326            script=os.path.basename(script_name),
2327        )
2328
2329    saved = distutils.core.gen_usage
2330    distutils.core.gen_usage = gen_usage
2331    try:
2332        yield
2333    finally:
2334        distutils.core.gen_usage = saved
2335