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