Path: blob/main/test/lib/python3.9/site-packages/setuptools/command/easy_install.py
4799 views
"""1Easy Install2------------34A tool for doing automatic download/extract/build of distutils-based Python5packages. For detailed documentation, see the accompanying EasyInstall.txt6file, or visit the `EasyInstall home page`__.78__ https://setuptools.pypa.io/en/latest/deprecated/easy_install.html910"""1112from glob import glob13from distutils.util import get_platform14from distutils.util import convert_path, subst_vars15from distutils.errors import (16DistutilsArgError, DistutilsOptionError,17DistutilsError, DistutilsPlatformError,18)19from distutils import log, dir_util20from distutils.command.build_scripts import first_line_re21from distutils.spawn import find_executable22from distutils.command import install23import sys24import os25import zipimport26import shutil27import tempfile28import zipfile29import re30import stat31import random32import textwrap33import warnings34import site35import struct36import contextlib37import subprocess38import shlex39import io40import configparser41import sysconfig424344from sysconfig import get_path4546from setuptools import SetuptoolsDeprecationWarning4748from setuptools import Command49from setuptools.sandbox import run_setup50from setuptools.command import setopt51from setuptools.archive_util import unpack_archive52from setuptools.package_index import (53PackageIndex, parse_requirement_arg, URL_SCHEME,54)55from setuptools.command import bdist_egg, egg_info56from setuptools.wheel import Wheel57from pkg_resources import (58normalize_path, resource_string,59get_distribution, find_distributions, Environment, Requirement,60Distribution, PathMetadata, EggMetadata, WorkingSet, DistributionNotFound,61VersionConflict, DEVELOP_DIST,62)63import pkg_resources64from .._path import ensure_directory65from ..extern.jaraco.text import yield_lines666768# Turn on PEP440Warnings69warnings.filterwarnings("default", category=pkg_resources.PEP440Warning)7071__all__ = [72'easy_install', 'PthDistributions', 'extract_wininst_cfg',73'get_exe_prefixes',74]757677def is_64bit():78return struct.calcsize("P") == 8798081def _to_bytes(s):82return s.encode('utf8')838485def isascii(s):86try:87s.encode('ascii')88return True89except UnicodeError:90return False919293def _one_liner(text):94return textwrap.dedent(text).strip().replace('\n', '; ')959697class easy_install(Command):98"""Manage a download/build/install process"""99description = "Find/get/install Python packages"100command_consumes_arguments = True101102user_options = [103('prefix=', None, "installation prefix"),104("zip-ok", "z", "install package as a zipfile"),105("multi-version", "m", "make apps have to require() a version"),106("upgrade", "U", "force upgrade (searches PyPI for latest versions)"),107("install-dir=", "d", "install package to DIR"),108("script-dir=", "s", "install scripts to DIR"),109("exclude-scripts", "x", "Don't install scripts"),110("always-copy", "a", "Copy all needed packages to install dir"),111("index-url=", "i", "base URL of Python Package Index"),112("find-links=", "f", "additional URL(s) to search for packages"),113("build-directory=", "b",114"download/extract/build in DIR; keep the results"),115('optimize=', 'O',116"also compile with optimization: -O1 for \"python -O\", "117"-O2 for \"python -OO\", and -O0 to disable [default: -O0]"),118('record=', None,119"filename in which to record list of installed files"),120('always-unzip', 'Z', "don't install as a zipfile, no matter what"),121('site-dirs=', 'S', "list of directories where .pth files work"),122('editable', 'e', "Install specified packages in editable form"),123('no-deps', 'N', "don't install dependencies"),124('allow-hosts=', 'H', "pattern(s) that hostnames must match"),125('local-snapshots-ok', 'l',126"allow building eggs from local checkouts"),127('version', None, "print version information and exit"),128('no-find-links', None,129"Don't load find-links defined in packages being installed"),130('user', None, "install in user site-package '%s'" % site.USER_SITE)131]132boolean_options = [133'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy',134'editable',135'no-deps', 'local-snapshots-ok', 'version',136'user'137]138139negative_opt = {'always-unzip': 'zip-ok'}140create_index = PackageIndex141142def initialize_options(self):143warnings.warn(144"easy_install command is deprecated. "145"Use build and pip and other standards-based tools.",146EasyInstallDeprecationWarning,147)148149# the --user option seems to be an opt-in one,150# so the default should be False.151self.user = 0152self.zip_ok = self.local_snapshots_ok = None153self.install_dir = self.script_dir = self.exclude_scripts = None154self.index_url = None155self.find_links = None156self.build_directory = None157self.args = None158self.optimize = self.record = None159self.upgrade = self.always_copy = self.multi_version = None160self.editable = self.no_deps = self.allow_hosts = None161self.root = self.prefix = self.no_report = None162self.version = None163self.install_purelib = None # for pure module distributions164self.install_platlib = None # non-pure (dists w/ extensions)165self.install_headers = None # for C/C++ headers166self.install_lib = None # set to either purelib or platlib167self.install_scripts = None168self.install_data = None169self.install_base = None170self.install_platbase = None171self.install_userbase = site.USER_BASE172self.install_usersite = site.USER_SITE173self.no_find_links = None174175# Options not specifiable via command line176self.package_index = None177self.pth_file = self.always_copy_from = None178self.site_dirs = None179self.installed_projects = {}180# Always read easy_install options, even if we are subclassed, or have181# an independent instance created. This ensures that defaults will182# always come from the standard configuration file(s)' "easy_install"183# section, even if this is a "develop" or "install" command, or some184# other embedding.185self._dry_run = None186self.verbose = self.distribution.verbose187self.distribution._set_command_options(188self, self.distribution.get_option_dict('easy_install')189)190191def delete_blockers(self, blockers):192extant_blockers = (193filename for filename in blockers194if os.path.exists(filename) or os.path.islink(filename)195)196list(map(self._delete_path, extant_blockers))197198def _delete_path(self, path):199log.info("Deleting %s", path)200if self.dry_run:201return202203is_tree = os.path.isdir(path) and not os.path.islink(path)204remover = rmtree if is_tree else os.unlink205remover(path)206207@staticmethod208def _render_version():209"""210Render the Setuptools version and installation details, then exit.211"""212ver = '{}.{}'.format(*sys.version_info)213dist = get_distribution('setuptools')214tmpl = 'setuptools {dist.version} from {dist.location} (Python {ver})'215print(tmpl.format(**locals()))216raise SystemExit()217218def finalize_options(self): # noqa: C901 # is too complex (25) # FIXME219self.version and self._render_version()220221py_version = sys.version.split()[0]222223self.config_vars = dict(sysconfig.get_config_vars())224225self.config_vars.update({226'dist_name': self.distribution.get_name(),227'dist_version': self.distribution.get_version(),228'dist_fullname': self.distribution.get_fullname(),229'py_version': py_version,230'py_version_short': f'{sys.version_info.major}.{sys.version_info.minor}',231'py_version_nodot': f'{sys.version_info.major}{sys.version_info.minor}',232'sys_prefix': self.config_vars['prefix'],233'sys_exec_prefix': self.config_vars['exec_prefix'],234# Only python 3.2+ has abiflags235'abiflags': getattr(sys, 'abiflags', ''),236'platlibdir': getattr(sys, 'platlibdir', 'lib'),237})238with contextlib.suppress(AttributeError):239# only for distutils outside stdlib240self.config_vars.update({241'implementation_lower': install._get_implementation().lower(),242'implementation': install._get_implementation(),243})244245# pypa/distutils#113 Python 3.9 compat246self.config_vars.setdefault(247'py_version_nodot_plat',248getattr(sys, 'windir', '').replace('.', ''),249)250251self.config_vars['userbase'] = self.install_userbase252self.config_vars['usersite'] = self.install_usersite253if self.user and not site.ENABLE_USER_SITE:254log.warn("WARNING: The user site-packages directory is disabled.")255256self._fix_install_dir_for_user_site()257258self.expand_basedirs()259self.expand_dirs()260261self._expand(262'install_dir', 'script_dir', 'build_directory',263'site_dirs',264)265# If a non-default installation directory was specified, default the266# script directory to match it.267if self.script_dir is None:268self.script_dir = self.install_dir269270if self.no_find_links is None:271self.no_find_links = False272273# Let install_dir get set by install_lib command, which in turn274# gets its info from the install command, and takes into account275# --prefix and --home and all that other crud.276self.set_undefined_options(277'install_lib', ('install_dir', 'install_dir')278)279# Likewise, set default script_dir from 'install_scripts.install_dir'280self.set_undefined_options(281'install_scripts', ('install_dir', 'script_dir')282)283284if self.user and self.install_purelib:285self.install_dir = self.install_purelib286self.script_dir = self.install_scripts287# default --record from the install command288self.set_undefined_options('install', ('record', 'record'))289self.all_site_dirs = get_site_dirs()290self.all_site_dirs.extend(self._process_site_dirs(self.site_dirs))291292if not self.editable:293self.check_site_dir()294default_index = os.getenv("__EASYINSTALL_INDEX", "https://pypi.org/simple/")295# ^ Private API for testing purposes only296self.index_url = self.index_url or default_index297self.shadow_path = self.all_site_dirs[:]298for path_item in self.install_dir, normalize_path(self.script_dir):299if path_item not in self.shadow_path:300self.shadow_path.insert(0, path_item)301302if self.allow_hosts is not None:303hosts = [s.strip() for s in self.allow_hosts.split(',')]304else:305hosts = ['*']306if self.package_index is None:307self.package_index = self.create_index(308self.index_url, search_path=self.shadow_path, hosts=hosts,309)310self.local_index = Environment(self.shadow_path + sys.path)311312if self.find_links is not None:313if isinstance(self.find_links, str):314self.find_links = self.find_links.split()315else:316self.find_links = []317if self.local_snapshots_ok:318self.package_index.scan_egg_links(self.shadow_path + sys.path)319if not self.no_find_links:320self.package_index.add_find_links(self.find_links)321self.set_undefined_options('install_lib', ('optimize', 'optimize'))322self.optimize = self._validate_optimize(self.optimize)323324if self.editable and not self.build_directory:325raise DistutilsArgError(326"Must specify a build directory (-b) when using --editable"327)328if not self.args:329raise DistutilsArgError(330"No urls, filenames, or requirements specified (see --help)")331332self.outputs = []333334@staticmethod335def _process_site_dirs(site_dirs):336if site_dirs is None:337return338339normpath = map(normalize_path, sys.path)340site_dirs = [341os.path.expanduser(s.strip()) for s in342site_dirs.split(',')343]344for d in site_dirs:345if not os.path.isdir(d):346log.warn("%s (in --site-dirs) does not exist", d)347elif normalize_path(d) not in normpath:348raise DistutilsOptionError(349d + " (in --site-dirs) is not on sys.path"350)351else:352yield normalize_path(d)353354@staticmethod355def _validate_optimize(value):356try:357value = int(value)358if value not in range(3):359raise ValueError360except ValueError as e:361raise DistutilsOptionError(362"--optimize must be 0, 1, or 2"363) from e364365return value366367def _fix_install_dir_for_user_site(self):368"""369Fix the install_dir if "--user" was used.370"""371if not self.user:372return373374self.create_home_path()375if self.install_userbase is None:376msg = "User base directory is not specified"377raise DistutilsPlatformError(msg)378self.install_base = self.install_platbase = self.install_userbase379scheme_name = f'{os.name}_user'380self.select_scheme(scheme_name)381382def _expand_attrs(self, attrs):383for attr in attrs:384val = getattr(self, attr)385if val is not None:386if os.name == 'posix' or os.name == 'nt':387val = os.path.expanduser(val)388val = subst_vars(val, self.config_vars)389setattr(self, attr, val)390391def expand_basedirs(self):392"""Calls `os.path.expanduser` on install_base, install_platbase and393root."""394self._expand_attrs(['install_base', 'install_platbase', 'root'])395396def expand_dirs(self):397"""Calls `os.path.expanduser` on install dirs."""398dirs = [399'install_purelib',400'install_platlib',401'install_lib',402'install_headers',403'install_scripts',404'install_data',405]406self._expand_attrs(dirs)407408def run(self, show_deprecation=True):409if show_deprecation:410self.announce(411"WARNING: The easy_install command is deprecated "412"and will be removed in a future version.",413log.WARN,414)415if self.verbose != self.distribution.verbose:416log.set_verbosity(self.verbose)417try:418for spec in self.args:419self.easy_install(spec, not self.no_deps)420if self.record:421outputs = self.outputs422if self.root: # strip any package prefix423root_len = len(self.root)424for counter in range(len(outputs)):425outputs[counter] = outputs[counter][root_len:]426from distutils import file_util427428self.execute(429file_util.write_file, (self.record, outputs),430"writing list of installed files to '%s'" %431self.record432)433self.warn_deprecated_options()434finally:435log.set_verbosity(self.distribution.verbose)436437def pseudo_tempname(self):438"""Return a pseudo-tempname base in the install directory.439This code is intentionally naive; if a malicious party can write to440the target directory you're already in deep doodoo.441"""442try:443pid = os.getpid()444except Exception:445pid = random.randint(0, sys.maxsize)446return os.path.join(self.install_dir, "test-easy-install-%s" % pid)447448def warn_deprecated_options(self):449pass450451def check_site_dir(self): # noqa: C901 # is too complex (12) # FIXME452"""Verify that self.install_dir is .pth-capable dir, if needed"""453454instdir = normalize_path(self.install_dir)455pth_file = os.path.join(instdir, 'easy-install.pth')456457if not os.path.exists(instdir):458try:459os.makedirs(instdir)460except (OSError, IOError):461self.cant_write_to_target()462463# Is it a configured, PYTHONPATH, implicit, or explicit site dir?464is_site_dir = instdir in self.all_site_dirs465466if not is_site_dir and not self.multi_version:467# No? Then directly test whether it does .pth file processing468is_site_dir = self.check_pth_processing()469else:470# make sure we can write to target dir471testfile = self.pseudo_tempname() + '.write-test'472test_exists = os.path.exists(testfile)473try:474if test_exists:475os.unlink(testfile)476open(testfile, 'w').close()477os.unlink(testfile)478except (OSError, IOError):479self.cant_write_to_target()480481if not is_site_dir and not self.multi_version:482# Can't install non-multi to non-site dir with easy_install483pythonpath = os.environ.get('PYTHONPATH', '')484log.warn(self.__no_default_msg, self.install_dir, pythonpath)485486if is_site_dir:487if self.pth_file is None:488self.pth_file = PthDistributions(pth_file, self.all_site_dirs)489else:490self.pth_file = None491492if self.multi_version and not os.path.exists(pth_file):493self.pth_file = None # don't create a .pth file494self.install_dir = instdir495496__cant_write_msg = textwrap.dedent("""497can't create or remove files in install directory498499The following error occurred while trying to add or remove files in the500installation directory:501502%s503504The installation directory you specified (via --install-dir, --prefix, or505the distutils default setting) was:506507%s508""").lstrip() # noqa509510__not_exists_id = textwrap.dedent("""511This directory does not currently exist. Please create it and try again, or512choose a different installation directory (using the -d or --install-dir513option).514""").lstrip() # noqa515516__access_msg = textwrap.dedent("""517Perhaps your account does not have write access to this directory? If the518installation directory is a system-owned directory, you may need to sign in519as the administrator or "root" account. If you do not have administrative520access to this machine, you may wish to choose a different installation521directory, preferably one that is listed in your PYTHONPATH environment522variable.523524For information on other options, you may wish to consult the525documentation at:526527https://setuptools.pypa.io/en/latest/deprecated/easy_install.html528529Please make the appropriate changes for your system and try again.530""").lstrip() # noqa531532def cant_write_to_target(self):533msg = self.__cant_write_msg % (sys.exc_info()[1], self.install_dir,)534535if not os.path.exists(self.install_dir):536msg += '\n' + self.__not_exists_id537else:538msg += '\n' + self.__access_msg539raise DistutilsError(msg)540541def check_pth_processing(self):542"""Empirically verify whether .pth files are supported in inst. dir"""543instdir = self.install_dir544log.info("Checking .pth file support in %s", instdir)545pth_file = self.pseudo_tempname() + ".pth"546ok_file = pth_file + '.ok'547ok_exists = os.path.exists(ok_file)548tmpl = _one_liner("""549import os550f = open({ok_file!r}, 'w')551f.write('OK')552f.close()553""") + '\n'554try:555if ok_exists:556os.unlink(ok_file)557dirname = os.path.dirname(ok_file)558os.makedirs(dirname, exist_ok=True)559f = open(pth_file, 'w')560except (OSError, IOError):561self.cant_write_to_target()562else:563try:564f.write(tmpl.format(**locals()))565f.close()566f = None567executable = sys.executable568if os.name == 'nt':569dirname, basename = os.path.split(executable)570alt = os.path.join(dirname, 'pythonw.exe')571use_alt = (572basename.lower() == 'python.exe' and573os.path.exists(alt)574)575if use_alt:576# use pythonw.exe to avoid opening a console window577executable = alt578579from distutils.spawn import spawn580581spawn([executable, '-E', '-c', 'pass'], 0)582583if os.path.exists(ok_file):584log.info(585"TEST PASSED: %s appears to support .pth files",586instdir587)588return True589finally:590if f:591f.close()592if os.path.exists(ok_file):593os.unlink(ok_file)594if os.path.exists(pth_file):595os.unlink(pth_file)596if not self.multi_version:597log.warn("TEST FAILED: %s does NOT support .pth files", instdir)598return False599600def install_egg_scripts(self, dist):601"""Write all the scripts for `dist`, unless scripts are excluded"""602if not self.exclude_scripts and dist.metadata_isdir('scripts'):603for script_name in dist.metadata_listdir('scripts'):604if dist.metadata_isdir('scripts/' + script_name):605# The "script" is a directory, likely a Python 3606# __pycache__ directory, so skip it.607continue608self.install_script(609dist, script_name,610dist.get_metadata('scripts/' + script_name)611)612self.install_wrapper_scripts(dist)613614def add_output(self, path):615if os.path.isdir(path):616for base, dirs, files in os.walk(path):617for filename in files:618self.outputs.append(os.path.join(base, filename))619else:620self.outputs.append(path)621622def not_editable(self, spec):623if self.editable:624raise DistutilsArgError(625"Invalid argument %r: you can't use filenames or URLs "626"with --editable (except via the --find-links option)."627% (spec,)628)629630def check_editable(self, spec):631if not self.editable:632return633634if os.path.exists(os.path.join(self.build_directory, spec.key)):635raise DistutilsArgError(636"%r already exists in %s; can't do a checkout there" %637(spec.key, self.build_directory)638)639640@contextlib.contextmanager641def _tmpdir(self):642tmpdir = tempfile.mkdtemp(prefix=u"easy_install-")643try:644# cast to str as workaround for #709 and #710 and #712645yield str(tmpdir)646finally:647os.path.exists(tmpdir) and rmtree(tmpdir)648649def easy_install(self, spec, deps=False):650with self._tmpdir() as tmpdir:651if not isinstance(spec, Requirement):652if URL_SCHEME(spec):653# It's a url, download it to tmpdir and process654self.not_editable(spec)655dl = self.package_index.download(spec, tmpdir)656return self.install_item(None, dl, tmpdir, deps, True)657658elif os.path.exists(spec):659# Existing file or directory, just process it directly660self.not_editable(spec)661return self.install_item(None, spec, tmpdir, deps, True)662else:663spec = parse_requirement_arg(spec)664665self.check_editable(spec)666dist = self.package_index.fetch_distribution(667spec, tmpdir, self.upgrade, self.editable,668not self.always_copy, self.local_index669)670if dist is None:671msg = "Could not find suitable distribution for %r" % spec672if self.always_copy:673msg += " (--always-copy skips system and development eggs)"674raise DistutilsError(msg)675elif dist.precedence == DEVELOP_DIST:676# .egg-info dists don't need installing, just process deps677self.process_distribution(spec, dist, deps, "Using")678return dist679else:680return self.install_item(spec, dist.location, tmpdir, deps)681682def install_item(self, spec, download, tmpdir, deps, install_needed=False):683684# Installation is also needed if file in tmpdir or is not an egg685install_needed = install_needed or self.always_copy686install_needed = install_needed or os.path.dirname(download) == tmpdir687install_needed = install_needed or not download.endswith('.egg')688install_needed = install_needed or (689self.always_copy_from is not None and690os.path.dirname(normalize_path(download)) ==691normalize_path(self.always_copy_from)692)693694if spec and not install_needed:695# at this point, we know it's a local .egg, we just don't know if696# it's already installed.697for dist in self.local_index[spec.project_name]:698if dist.location == download:699break700else:701install_needed = True # it's not in the local index702703log.info("Processing %s", os.path.basename(download))704705if install_needed:706dists = self.install_eggs(spec, download, tmpdir)707for dist in dists:708self.process_distribution(spec, dist, deps)709else:710dists = [self.egg_distribution(download)]711self.process_distribution(spec, dists[0], deps, "Using")712713if spec is not None:714for dist in dists:715if dist in spec:716return dist717718def select_scheme(self, name):719try:720install._select_scheme(self, name)721except AttributeError:722# stdlib distutils723install.install.select_scheme(self, name.replace('posix', 'unix'))724725# FIXME: 'easy_install.process_distribution' is too complex (12)726def process_distribution( # noqa: C901727self, requirement, dist, deps=True, *info,728):729self.update_pth(dist)730self.package_index.add(dist)731if dist in self.local_index[dist.key]:732self.local_index.remove(dist)733self.local_index.add(dist)734self.install_egg_scripts(dist)735self.installed_projects[dist.key] = dist736log.info(self.installation_report(requirement, dist, *info))737if (dist.has_metadata('dependency_links.txt') and738not self.no_find_links):739self.package_index.add_find_links(740dist.get_metadata_lines('dependency_links.txt')741)742if not deps and not self.always_copy:743return744elif requirement is not None and dist.key != requirement.key:745log.warn("Skipping dependencies for %s", dist)746return # XXX this is not the distribution we were looking for747elif requirement is None or dist not in requirement:748# if we wound up with a different version, resolve what we've got749distreq = dist.as_requirement()750requirement = Requirement(str(distreq))751log.info("Processing dependencies for %s", requirement)752try:753distros = WorkingSet([]).resolve(754[requirement], self.local_index, self.easy_install755)756except DistributionNotFound as e:757raise DistutilsError(str(e)) from e758except VersionConflict as e:759raise DistutilsError(e.report()) from e760if self.always_copy or self.always_copy_from:761# Force all the relevant distros to be copied or activated762for dist in distros:763if dist.key not in self.installed_projects:764self.easy_install(dist.as_requirement())765log.info("Finished processing dependencies for %s", requirement)766767def should_unzip(self, dist):768if self.zip_ok is not None:769return not self.zip_ok770if dist.has_metadata('not-zip-safe'):771return True772if not dist.has_metadata('zip-safe'):773return True774return False775776def maybe_move(self, spec, dist_filename, setup_base):777dst = os.path.join(self.build_directory, spec.key)778if os.path.exists(dst):779msg = (780"%r already exists in %s; build directory %s will not be kept"781)782log.warn(msg, spec.key, self.build_directory, setup_base)783return setup_base784if os.path.isdir(dist_filename):785setup_base = dist_filename786else:787if os.path.dirname(dist_filename) == setup_base:788os.unlink(dist_filename) # get it out of the tmp dir789contents = os.listdir(setup_base)790if len(contents) == 1:791dist_filename = os.path.join(setup_base, contents[0])792if os.path.isdir(dist_filename):793# if the only thing there is a directory, move it instead794setup_base = dist_filename795ensure_directory(dst)796shutil.move(setup_base, dst)797return dst798799def install_wrapper_scripts(self, dist):800if self.exclude_scripts:801return802for args in ScriptWriter.best().get_args(dist):803self.write_script(*args)804805def install_script(self, dist, script_name, script_text, dev_path=None):806"""Generate a legacy script wrapper and install it"""807spec = str(dist.as_requirement())808is_script = is_python_script(script_text, script_name)809810if is_script:811body = self._load_template(dev_path) % locals()812script_text = ScriptWriter.get_header(script_text) + body813self.write_script(script_name, _to_bytes(script_text), 'b')814815@staticmethod816def _load_template(dev_path):817"""818There are a couple of template scripts in the package. This819function loads one of them and prepares it for use.820"""821# See https://github.com/pypa/setuptools/issues/134 for info822# on script file naming and downstream issues with SVR4823name = 'script.tmpl'824if dev_path:825name = name.replace('.tmpl', ' (dev).tmpl')826827raw_bytes = resource_string('setuptools', name)828return raw_bytes.decode('utf-8')829830def write_script(self, script_name, contents, mode="t", blockers=()):831"""Write an executable file to the scripts directory"""832self.delete_blockers( # clean up old .py/.pyw w/o a script833[os.path.join(self.script_dir, x) for x in blockers]834)835log.info("Installing %s script to %s", script_name, self.script_dir)836target = os.path.join(self.script_dir, script_name)837self.add_output(target)838839if self.dry_run:840return841842mask = current_umask()843ensure_directory(target)844if os.path.exists(target):845os.unlink(target)846with open(target, "w" + mode) as f:847f.write(contents)848chmod(target, 0o777 - mask)849850def install_eggs(self, spec, dist_filename, tmpdir):851# .egg dirs or files are already built, so just return them852installer_map = {853'.egg': self.install_egg,854'.exe': self.install_exe,855'.whl': self.install_wheel,856}857try:858install_dist = installer_map[859dist_filename.lower()[-4:]860]861except KeyError:862pass863else:864return [install_dist(dist_filename, tmpdir)]865866# Anything else, try to extract and build867setup_base = tmpdir868if os.path.isfile(dist_filename) and not dist_filename.endswith('.py'):869unpack_archive(dist_filename, tmpdir, self.unpack_progress)870elif os.path.isdir(dist_filename):871setup_base = os.path.abspath(dist_filename)872873if (setup_base.startswith(tmpdir) # something we downloaded874and self.build_directory and spec is not None):875setup_base = self.maybe_move(spec, dist_filename, setup_base)876877# Find the setup.py file878setup_script = os.path.join(setup_base, 'setup.py')879880if not os.path.exists(setup_script):881setups = glob(os.path.join(setup_base, '*', 'setup.py'))882if not setups:883raise DistutilsError(884"Couldn't find a setup script in %s" %885os.path.abspath(dist_filename)886)887if len(setups) > 1:888raise DistutilsError(889"Multiple setup scripts in %s" %890os.path.abspath(dist_filename)891)892setup_script = setups[0]893894# Now run it, and return the result895if self.editable:896log.info(self.report_editable(spec, setup_script))897return []898else:899return self.build_and_install(setup_script, setup_base)900901def egg_distribution(self, egg_path):902if os.path.isdir(egg_path):903metadata = PathMetadata(egg_path, os.path.join(egg_path,904'EGG-INFO'))905else:906metadata = EggMetadata(zipimport.zipimporter(egg_path))907return Distribution.from_filename(egg_path, metadata=metadata)908909# FIXME: 'easy_install.install_egg' is too complex (11)910def install_egg(self, egg_path, tmpdir): # noqa: C901911destination = os.path.join(912self.install_dir,913os.path.basename(egg_path),914)915destination = os.path.abspath(destination)916if not self.dry_run:917ensure_directory(destination)918919dist = self.egg_distribution(egg_path)920if not (921os.path.exists(destination) and os.path.samefile(egg_path, destination)922):923if os.path.isdir(destination) and not os.path.islink(destination):924dir_util.remove_tree(destination, dry_run=self.dry_run)925elif os.path.exists(destination):926self.execute(927os.unlink,928(destination,),929"Removing " + destination,930)931try:932new_dist_is_zipped = False933if os.path.isdir(egg_path):934if egg_path.startswith(tmpdir):935f, m = shutil.move, "Moving"936else:937f, m = shutil.copytree, "Copying"938elif self.should_unzip(dist):939self.mkpath(destination)940f, m = self.unpack_and_compile, "Extracting"941else:942new_dist_is_zipped = True943if egg_path.startswith(tmpdir):944f, m = shutil.move, "Moving"945else:946f, m = shutil.copy2, "Copying"947self.execute(948f,949(egg_path, destination),950(m + " %s to %s") % (951os.path.basename(egg_path),952os.path.dirname(destination)953),954)955update_dist_caches(956destination,957fix_zipimporter_caches=new_dist_is_zipped,958)959except Exception:960update_dist_caches(destination, fix_zipimporter_caches=False)961raise962963self.add_output(destination)964return self.egg_distribution(destination)965966def install_exe(self, dist_filename, tmpdir):967# See if it's valid, get data968cfg = extract_wininst_cfg(dist_filename)969if cfg is None:970raise DistutilsError(971"%s is not a valid distutils Windows .exe" % dist_filename972)973# Create a dummy distribution object until we build the real distro974dist = Distribution(975None,976project_name=cfg.get('metadata', 'name'),977version=cfg.get('metadata', 'version'), platform=get_platform(),978)979980# Convert the .exe to an unpacked egg981egg_path = os.path.join(tmpdir, dist.egg_name() + '.egg')982dist.location = egg_path983egg_tmp = egg_path + '.tmp'984_egg_info = os.path.join(egg_tmp, 'EGG-INFO')985pkg_inf = os.path.join(_egg_info, 'PKG-INFO')986ensure_directory(pkg_inf) # make sure EGG-INFO dir exists987dist._provider = PathMetadata(egg_tmp, _egg_info) # XXX988self.exe_to_egg(dist_filename, egg_tmp)989990# Write EGG-INFO/PKG-INFO991if not os.path.exists(pkg_inf):992f = open(pkg_inf, 'w')993f.write('Metadata-Version: 1.0\n')994for k, v in cfg.items('metadata'):995if k != 'target_version':996f.write('%s: %s\n' % (k.replace('_', '-').title(), v))997f.close()998script_dir = os.path.join(_egg_info, 'scripts')999# delete entry-point scripts to avoid duping1000self.delete_blockers([1001os.path.join(script_dir, args[0])1002for args in ScriptWriter.get_args(dist)1003])1004# Build .egg file from tmpdir1005bdist_egg.make_zipfile(1006egg_path, egg_tmp, verbose=self.verbose, dry_run=self.dry_run,1007)1008# install the .egg1009return self.install_egg(egg_path, tmpdir)10101011# FIXME: 'easy_install.exe_to_egg' is too complex (12)1012def exe_to_egg(self, dist_filename, egg_tmp): # noqa: C9011013"""Extract a bdist_wininst to the directories an egg would use"""1014# Check for .pth file and set up prefix translations1015prefixes = get_exe_prefixes(dist_filename)1016to_compile = []1017native_libs = []1018top_level = {}10191020def process(src, dst):1021s = src.lower()1022for old, new in prefixes:1023if s.startswith(old):1024src = new + src[len(old):]1025parts = src.split('/')1026dst = os.path.join(egg_tmp, *parts)1027dl = dst.lower()1028if dl.endswith('.pyd') or dl.endswith('.dll'):1029parts[-1] = bdist_egg.strip_module(parts[-1])1030top_level[os.path.splitext(parts[0])[0]] = 11031native_libs.append(src)1032elif dl.endswith('.py') and old != 'SCRIPTS/':1033top_level[os.path.splitext(parts[0])[0]] = 11034to_compile.append(dst)1035return dst1036if not src.endswith('.pth'):1037log.warn("WARNING: can't process %s", src)1038return None10391040# extract, tracking .pyd/.dll->native_libs and .py -> to_compile1041unpack_archive(dist_filename, egg_tmp, process)1042stubs = []1043for res in native_libs:1044if res.lower().endswith('.pyd'): # create stubs for .pyd's1045parts = res.split('/')1046resource = parts[-1]1047parts[-1] = bdist_egg.strip_module(parts[-1]) + '.py'1048pyfile = os.path.join(egg_tmp, *parts)1049to_compile.append(pyfile)1050stubs.append(pyfile)1051bdist_egg.write_stub(resource, pyfile)1052self.byte_compile(to_compile) # compile .py's1053bdist_egg.write_safety_flag(1054os.path.join(egg_tmp, 'EGG-INFO'),1055bdist_egg.analyze_egg(egg_tmp, stubs)) # write zip-safety flag10561057for name in 'top_level', 'native_libs':1058if locals()[name]:1059txt = os.path.join(egg_tmp, 'EGG-INFO', name + '.txt')1060if not os.path.exists(txt):1061f = open(txt, 'w')1062f.write('\n'.join(locals()[name]) + '\n')1063f.close()10641065def install_wheel(self, wheel_path, tmpdir):1066wheel = Wheel(wheel_path)1067assert wheel.is_compatible()1068destination = os.path.join(self.install_dir, wheel.egg_name())1069destination = os.path.abspath(destination)1070if not self.dry_run:1071ensure_directory(destination)1072if os.path.isdir(destination) and not os.path.islink(destination):1073dir_util.remove_tree(destination, dry_run=self.dry_run)1074elif os.path.exists(destination):1075self.execute(1076os.unlink,1077(destination,),1078"Removing " + destination,1079)1080try:1081self.execute(1082wheel.install_as_egg,1083(destination,),1084("Installing %s to %s") % (1085os.path.basename(wheel_path),1086os.path.dirname(destination)1087),1088)1089finally:1090update_dist_caches(destination, fix_zipimporter_caches=False)1091self.add_output(destination)1092return self.egg_distribution(destination)10931094__mv_warning = textwrap.dedent("""1095Because this distribution was installed --multi-version, before you can1096import modules from this package in an application, you will need to1097'import pkg_resources' and then use a 'require()' call similar to one of1098these examples, in order to select the desired version:10991100pkg_resources.require("%(name)s") # latest installed version1101pkg_resources.require("%(name)s==%(version)s") # this exact version1102pkg_resources.require("%(name)s>=%(version)s") # this version or higher1103""").lstrip() # noqa11041105__id_warning = textwrap.dedent("""1106Note also that the installation directory must be on sys.path at runtime for1107this to work. (e.g. by being the application's script directory, by being on1108PYTHONPATH, or by being added to sys.path by your code.)1109""") # noqa11101111def installation_report(self, req, dist, what="Installed"):1112"""Helpful installation message for display to package users"""1113msg = "\n%(what)s %(eggloc)s%(extras)s"1114if self.multi_version and not self.no_report:1115msg += '\n' + self.__mv_warning1116if self.install_dir not in map(normalize_path, sys.path):1117msg += '\n' + self.__id_warning11181119eggloc = dist.location1120name = dist.project_name1121version = dist.version1122extras = '' # TODO: self.report_extras(req, dist)1123return msg % locals()11241125__editable_msg = textwrap.dedent("""1126Extracted editable version of %(spec)s to %(dirname)s11271128If it uses setuptools in its setup script, you can activate it in1129"development" mode by going to that directory and running::11301131%(python)s setup.py develop11321133See the setuptools documentation for the "develop" command for more info.1134""").lstrip() # noqa11351136def report_editable(self, spec, setup_script):1137dirname = os.path.dirname(setup_script)1138python = sys.executable1139return '\n' + self.__editable_msg % locals()11401141def run_setup(self, setup_script, setup_base, args):1142sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg)1143sys.modules.setdefault('distutils.command.egg_info', egg_info)11441145args = list(args)1146if self.verbose > 2:1147v = 'v' * (self.verbose - 1)1148args.insert(0, '-' + v)1149elif self.verbose < 2:1150args.insert(0, '-q')1151if self.dry_run:1152args.insert(0, '-n')1153log.info(1154"Running %s %s", setup_script[len(setup_base) + 1:], ' '.join(args)1155)1156try:1157run_setup(setup_script, args)1158except SystemExit as v:1159raise DistutilsError(1160"Setup script exited with %s" % (v.args[0],)1161) from v11621163def build_and_install(self, setup_script, setup_base):1164args = ['bdist_egg', '--dist-dir']11651166dist_dir = tempfile.mkdtemp(1167prefix='egg-dist-tmp-', dir=os.path.dirname(setup_script)1168)1169try:1170self._set_fetcher_options(os.path.dirname(setup_script))1171args.append(dist_dir)11721173self.run_setup(setup_script, setup_base, args)1174all_eggs = Environment([dist_dir])1175eggs = []1176for key in all_eggs:1177for dist in all_eggs[key]:1178eggs.append(self.install_egg(dist.location, setup_base))1179if not eggs and not self.dry_run:1180log.warn("No eggs found in %s (setup script problem?)",1181dist_dir)1182return eggs1183finally:1184rmtree(dist_dir)1185log.set_verbosity(self.verbose) # restore our log verbosity11861187def _set_fetcher_options(self, base):1188"""1189When easy_install is about to run bdist_egg on a source dist, that1190source dist might have 'setup_requires' directives, requiring1191additional fetching. Ensure the fetcher options given to easy_install1192are available to that command as well.1193"""1194# find the fetch options from easy_install and write them out1195# to the setup.cfg file.1196ei_opts = self.distribution.get_option_dict('easy_install').copy()1197fetch_directives = (1198'find_links', 'site_dirs', 'index_url', 'optimize', 'allow_hosts',1199)1200fetch_options = {}1201for key, val in ei_opts.items():1202if key not in fetch_directives:1203continue1204fetch_options[key] = val[1]1205# create a settings dictionary suitable for `edit_config`1206settings = dict(easy_install=fetch_options)1207cfg_filename = os.path.join(base, 'setup.cfg')1208setopt.edit_config(cfg_filename, settings)12091210def update_pth(self, dist): # noqa: C901 # is too complex (11) # FIXME1211if self.pth_file is None:1212return12131214for d in self.pth_file[dist.key]: # drop old entries1215if not self.multi_version and d.location == dist.location:1216continue12171218log.info("Removing %s from easy-install.pth file", d)1219self.pth_file.remove(d)1220if d.location in self.shadow_path:1221self.shadow_path.remove(d.location)12221223if not self.multi_version:1224if dist.location in self.pth_file.paths:1225log.info(1226"%s is already the active version in easy-install.pth",1227dist,1228)1229else:1230log.info("Adding %s to easy-install.pth file", dist)1231self.pth_file.add(dist) # add new entry1232if dist.location not in self.shadow_path:1233self.shadow_path.append(dist.location)12341235if self.dry_run:1236return12371238self.pth_file.save()12391240if dist.key != 'setuptools':1241return12421243# Ensure that setuptools itself never becomes unavailable!1244# XXX should this check for latest version?1245filename = os.path.join(self.install_dir, 'setuptools.pth')1246if os.path.islink(filename):1247os.unlink(filename)1248with open(filename, 'wt') as f:1249f.write(self.pth_file.make_relative(dist.location) + '\n')12501251def unpack_progress(self, src, dst):1252# Progress filter for unpacking1253log.debug("Unpacking %s to %s", src, dst)1254return dst # only unpack-and-compile skips files for dry run12551256def unpack_and_compile(self, egg_path, destination):1257to_compile = []1258to_chmod = []12591260def pf(src, dst):1261if dst.endswith('.py') and not src.startswith('EGG-INFO/'):1262to_compile.append(dst)1263elif dst.endswith('.dll') or dst.endswith('.so'):1264to_chmod.append(dst)1265self.unpack_progress(src, dst)1266return not self.dry_run and dst or None12671268unpack_archive(egg_path, destination, pf)1269self.byte_compile(to_compile)1270if not self.dry_run:1271for f in to_chmod:1272mode = ((os.stat(f)[stat.ST_MODE]) | 0o555) & 0o77551273chmod(f, mode)12741275def byte_compile(self, to_compile):1276if sys.dont_write_bytecode:1277return12781279from distutils.util import byte_compile12801281try:1282# try to make the byte compile messages quieter1283log.set_verbosity(self.verbose - 1)12841285byte_compile(to_compile, optimize=0, force=1, dry_run=self.dry_run)1286if self.optimize:1287byte_compile(1288to_compile, optimize=self.optimize, force=1,1289dry_run=self.dry_run,1290)1291finally:1292log.set_verbosity(self.verbose) # restore original verbosity12931294__no_default_msg = textwrap.dedent("""1295bad install directory or PYTHONPATH12961297You are attempting to install a package to a directory that is not1298on PYTHONPATH and which Python does not read ".pth" files from. The1299installation directory you specified (via --install-dir, --prefix, or1300the distutils default setting) was:13011302%s13031304and your PYTHONPATH environment variable currently contains:13051306%r13071308Here are some of your options for correcting the problem:13091310* You can choose a different installation directory, i.e., one that is1311on PYTHONPATH or supports .pth files13121313* You can add the installation directory to the PYTHONPATH environment1314variable. (It must then also be on PYTHONPATH whenever you run1315Python and want to use the package(s) you are installing.)13161317* You can set up the installation directory to support ".pth" files by1318using one of the approaches described here:13191320https://setuptools.pypa.io/en/latest/deprecated/easy_install.html#custom-installation-locations132113221323Please make the appropriate changes for your system and try again.1324""").strip()13251326def create_home_path(self):1327"""Create directories under ~."""1328if not self.user:1329return1330home = convert_path(os.path.expanduser("~"))1331for path in only_strs(self.config_vars.values()):1332if path.startswith(home) and not os.path.isdir(path):1333self.debug_print("os.makedirs('%s', 0o700)" % path)1334os.makedirs(path, 0o700)13351336INSTALL_SCHEMES = dict(1337posix=dict(1338install_dir='$base/lib/python$py_version_short/site-packages',1339script_dir='$base/bin',1340),1341)13421343DEFAULT_SCHEME = dict(1344install_dir='$base/Lib/site-packages',1345script_dir='$base/Scripts',1346)13471348def _expand(self, *attrs):1349config_vars = self.get_finalized_command('install').config_vars13501351if self.prefix:1352# Set default install_dir/scripts from --prefix1353config_vars = dict(config_vars)1354config_vars['base'] = self.prefix1355scheme = self.INSTALL_SCHEMES.get(os.name, self.DEFAULT_SCHEME)1356for attr, val in scheme.items():1357if getattr(self, attr, None) is None:1358setattr(self, attr, val)13591360from distutils.util import subst_vars13611362for attr in attrs:1363val = getattr(self, attr)1364if val is not None:1365val = subst_vars(val, config_vars)1366if os.name == 'posix':1367val = os.path.expanduser(val)1368setattr(self, attr, val)136913701371def _pythonpath():1372items = os.environ.get('PYTHONPATH', '').split(os.pathsep)1373return filter(None, items)137413751376def get_site_dirs():1377"""1378Return a list of 'site' dirs1379"""13801381sitedirs = []13821383# start with PYTHONPATH1384sitedirs.extend(_pythonpath())13851386prefixes = [sys.prefix]1387if sys.exec_prefix != sys.prefix:1388prefixes.append(sys.exec_prefix)1389for prefix in prefixes:1390if not prefix:1391continue13921393if sys.platform in ('os2emx', 'riscos'):1394sitedirs.append(os.path.join(prefix, "Lib", "site-packages"))1395elif os.sep == '/':1396sitedirs.extend([1397os.path.join(1398prefix,1399"lib",1400"python{}.{}".format(*sys.version_info),1401"site-packages",1402),1403os.path.join(prefix, "lib", "site-python"),1404])1405else:1406sitedirs.extend([1407prefix,1408os.path.join(prefix, "lib", "site-packages"),1409])1410if sys.platform != 'darwin':1411continue14121413# for framework builds *only* we add the standard Apple1414# locations. Currently only per-user, but /Library and1415# /Network/Library could be added too1416if 'Python.framework' not in prefix:1417continue14181419home = os.environ.get('HOME')1420if not home:1421continue14221423home_sp = os.path.join(1424home,1425'Library',1426'Python',1427'{}.{}'.format(*sys.version_info),1428'site-packages',1429)1430sitedirs.append(home_sp)1431lib_paths = get_path('purelib'), get_path('platlib')14321433sitedirs.extend(s for s in lib_paths if s not in sitedirs)14341435if site.ENABLE_USER_SITE:1436sitedirs.append(site.USER_SITE)14371438with contextlib.suppress(AttributeError):1439sitedirs.extend(site.getsitepackages())14401441sitedirs = list(map(normalize_path, sitedirs))14421443return sitedirs144414451446def expand_paths(inputs): # noqa: C901 # is too complex (11) # FIXME1447"""Yield sys.path directories that might contain "old-style" packages"""14481449seen = {}14501451for dirname in inputs:1452dirname = normalize_path(dirname)1453if dirname in seen:1454continue14551456seen[dirname] = 11457if not os.path.isdir(dirname):1458continue14591460files = os.listdir(dirname)1461yield dirname, files14621463for name in files:1464if not name.endswith('.pth'):1465# We only care about the .pth files1466continue1467if name in ('easy-install.pth', 'setuptools.pth'):1468# Ignore .pth files that we control1469continue14701471# Read the .pth file1472f = open(os.path.join(dirname, name))1473lines = list(yield_lines(f))1474f.close()14751476# Yield existing non-dupe, non-import directory lines from it1477for line in lines:1478if line.startswith("import"):1479continue14801481line = normalize_path(line.rstrip())1482if line in seen:1483continue14841485seen[line] = 11486if not os.path.isdir(line):1487continue14881489yield line, os.listdir(line)149014911492def extract_wininst_cfg(dist_filename):1493"""Extract configuration data from a bdist_wininst .exe14941495Returns a configparser.RawConfigParser, or None1496"""1497f = open(dist_filename, 'rb')1498try:1499endrec = zipfile._EndRecData(f)1500if endrec is None:1501return None15021503prepended = (endrec[9] - endrec[5]) - endrec[6]1504if prepended < 12: # no wininst data here1505return None1506f.seek(prepended - 12)15071508tag, cfglen, bmlen = struct.unpack("<iii", f.read(12))1509if tag not in (0x1234567A, 0x1234567B):1510return None # not a valid tag15111512f.seek(prepended - (12 + cfglen))1513init = {'version': '', 'target_version': ''}1514cfg = configparser.RawConfigParser(init)1515try:1516part = f.read(cfglen)1517# Read up to the first null byte.1518config = part.split(b'\0', 1)[0]1519# Now the config is in bytes, but for RawConfigParser, it should1520# be text, so decode it.1521config = config.decode(sys.getfilesystemencoding())1522cfg.read_file(io.StringIO(config))1523except configparser.Error:1524return None1525if not cfg.has_section('metadata') or not cfg.has_section('Setup'):1526return None1527return cfg15281529finally:1530f.close()153115321533def get_exe_prefixes(exe_filename):1534"""Get exe->egg path translations for a given .exe file"""15351536prefixes = [1537('PURELIB/', ''),1538('PLATLIB/pywin32_system32', ''),1539('PLATLIB/', ''),1540('SCRIPTS/', 'EGG-INFO/scripts/'),1541('DATA/lib/site-packages', ''),1542]1543z = zipfile.ZipFile(exe_filename)1544try:1545for info in z.infolist():1546name = info.filename1547parts = name.split('/')1548if len(parts) == 3 and parts[2] == 'PKG-INFO':1549if parts[1].endswith('.egg-info'):1550prefixes.insert(0, ('/'.join(parts[:2]), 'EGG-INFO/'))1551break1552if len(parts) != 2 or not name.endswith('.pth'):1553continue1554if name.endswith('-nspkg.pth'):1555continue1556if parts[0].upper() in ('PURELIB', 'PLATLIB'):1557contents = z.read(name).decode()1558for pth in yield_lines(contents):1559pth = pth.strip().replace('\\', '/')1560if not pth.startswith('import'):1561prefixes.append((('%s/%s/' % (parts[0], pth)), ''))1562finally:1563z.close()1564prefixes = [(x.lower(), y) for x, y in prefixes]1565prefixes.sort()1566prefixes.reverse()1567return prefixes156815691570class PthDistributions(Environment):1571"""A .pth file with Distribution paths in it"""15721573dirty = False15741575def __init__(self, filename, sitedirs=()):1576self.filename = filename1577self.sitedirs = list(map(normalize_path, sitedirs))1578self.basedir = normalize_path(os.path.dirname(self.filename))1579self._load()1580super().__init__([], None, None)1581for path in yield_lines(self.paths):1582list(map(self.add, find_distributions(path, True)))15831584def _load(self):1585self.paths = []1586saw_import = False1587seen = dict.fromkeys(self.sitedirs)1588if os.path.isfile(self.filename):1589f = open(self.filename, 'rt')1590for line in f:1591if line.startswith('import'):1592saw_import = True1593continue1594path = line.rstrip()1595self.paths.append(path)1596if not path.strip() or path.strip().startswith('#'):1597continue1598# skip non-existent paths, in case somebody deleted a package1599# manually, and duplicate paths as well1600path = self.paths[-1] = normalize_path(1601os.path.join(self.basedir, path)1602)1603if not os.path.exists(path) or path in seen:1604self.paths.pop() # skip it1605self.dirty = True # we cleaned up, so we're dirty now :)1606continue1607seen[path] = 11608f.close()16091610if self.paths and not saw_import:1611self.dirty = True # ensure anything we touch has import wrappers1612while self.paths and not self.paths[-1].strip():1613self.paths.pop()16141615def save(self):1616"""Write changed .pth file back to disk"""1617if not self.dirty:1618return16191620rel_paths = list(map(self.make_relative, self.paths))1621if rel_paths:1622log.debug("Saving %s", self.filename)1623lines = self._wrap_lines(rel_paths)1624data = '\n'.join(lines) + '\n'16251626if os.path.islink(self.filename):1627os.unlink(self.filename)1628with open(self.filename, 'wt') as f:1629f.write(data)16301631elif os.path.exists(self.filename):1632log.debug("Deleting empty %s", self.filename)1633os.unlink(self.filename)16341635self.dirty = False16361637@staticmethod1638def _wrap_lines(lines):1639return lines16401641def add(self, dist):1642"""Add `dist` to the distribution map"""1643new_path = (1644dist.location not in self.paths and (1645dist.location not in self.sitedirs or1646# account for '.' being in PYTHONPATH1647dist.location == os.getcwd()1648)1649)1650if new_path:1651self.paths.append(dist.location)1652self.dirty = True1653super().add(dist)16541655def remove(self, dist):1656"""Remove `dist` from the distribution map"""1657while dist.location in self.paths:1658self.paths.remove(dist.location)1659self.dirty = True1660super().remove(dist)16611662def make_relative(self, path):1663npath, last = os.path.split(normalize_path(path))1664baselen = len(self.basedir)1665parts = [last]1666sep = os.altsep == '/' and '/' or os.sep1667while len(npath) >= baselen:1668if npath == self.basedir:1669parts.append(os.curdir)1670parts.reverse()1671return sep.join(parts)1672npath, last = os.path.split(npath)1673parts.append(last)1674else:1675return path167616771678class RewritePthDistributions(PthDistributions):1679@classmethod1680def _wrap_lines(cls, lines):1681yield cls.prelude1682for line in lines:1683yield line1684yield cls.postlude16851686prelude = _one_liner("""1687import sys1688sys.__plen = len(sys.path)1689""")1690postlude = _one_liner("""1691import sys1692new = sys.path[sys.__plen:]1693del sys.path[sys.__plen:]1694p = getattr(sys, '__egginsert', 0)1695sys.path[p:p] = new1696sys.__egginsert = p + len(new)1697""")169816991700if os.environ.get('SETUPTOOLS_SYS_PATH_TECHNIQUE', 'raw') == 'rewrite':1701PthDistributions = RewritePthDistributions170217031704def _first_line_re():1705"""1706Return a regular expression based on first_line_re suitable for matching1707strings.1708"""1709if isinstance(first_line_re.pattern, str):1710return first_line_re17111712# first_line_re in Python >=3.1.4 and >=3.2.1 is a bytes pattern.1713return re.compile(first_line_re.pattern.decode())171417151716def auto_chmod(func, arg, exc):1717if func in [os.unlink, os.remove] and os.name == 'nt':1718chmod(arg, stat.S_IWRITE)1719return func(arg)1720et, ev, _ = sys.exc_info()1721# TODO: This code doesn't make sense. What is it trying to do?1722raise (ev[0], ev[1] + (" %s %s" % (func, arg)))172317241725def update_dist_caches(dist_path, fix_zipimporter_caches):1726"""1727Fix any globally cached `dist_path` related data17281729`dist_path` should be a path of a newly installed egg distribution (zipped1730or unzipped).17311732sys.path_importer_cache contains finder objects that have been cached when1733importing data from the original distribution. Any such finders need to be1734cleared since the replacement distribution might be packaged differently,1735e.g. a zipped egg distribution might get replaced with an unzipped egg1736folder or vice versa. Having the old finders cached may then cause Python1737to attempt loading modules from the replacement distribution using an1738incorrect loader.17391740zipimport.zipimporter objects are Python loaders charged with importing1741data packaged inside zip archives. If stale loaders referencing the1742original distribution, are left behind, they can fail to load modules from1743the replacement distribution. E.g. if an old zipimport.zipimporter instance1744is used to load data from a new zipped egg archive, it may cause the1745operation to attempt to locate the requested data in the wrong location -1746one indicated by the original distribution's zip archive directory1747information. Such an operation may then fail outright, e.g. report having1748read a 'bad local file header', or even worse, it may fail silently &1749return invalid data.17501751zipimport._zip_directory_cache contains cached zip archive directory1752information for all existing zipimport.zipimporter instances and all such1753instances connected to the same archive share the same cached directory1754information.17551756If asked, and the underlying Python implementation allows it, we can fix1757all existing zipimport.zipimporter instances instead of having to track1758them down and remove them one by one, by updating their shared cached zip1759archive directory information. This, of course, assumes that the1760replacement distribution is packaged as a zipped egg.17611762If not asked to fix existing zipimport.zipimporter instances, we still do1763our best to clear any remaining zipimport.zipimporter related cached data1764that might somehow later get used when attempting to load data from the new1765distribution and thus cause such load operations to fail. Note that when1766tracking down such remaining stale data, we can not catch every conceivable1767usage from here, and we clear only those that we know of and have found to1768cause problems if left alive. Any remaining caches should be updated by1769whomever is in charge of maintaining them, i.e. they should be ready to1770handle us replacing their zip archives with new distributions at runtime.17711772"""1773# There are several other known sources of stale zipimport.zipimporter1774# instances that we do not clear here, but might if ever given a reason to1775# do so:1776# * Global setuptools pkg_resources.working_set (a.k.a. 'master working1777# set') may contain distributions which may in turn contain their1778# zipimport.zipimporter loaders.1779# * Several zipimport.zipimporter loaders held by local variables further1780# up the function call stack when running the setuptools installation.1781# * Already loaded modules may have their __loader__ attribute set to the1782# exact loader instance used when importing them. Python 3.4 docs state1783# that this information is intended mostly for introspection and so is1784# not expected to cause us problems.1785normalized_path = normalize_path(dist_path)1786_uncache(normalized_path, sys.path_importer_cache)1787if fix_zipimporter_caches:1788_replace_zip_directory_cache_data(normalized_path)1789else:1790# Here, even though we do not want to fix existing and now stale1791# zipimporter cache information, we still want to remove it. Related to1792# Python's zip archive directory information cache, we clear each of1793# its stale entries in two phases:1794# 1. Clear the entry so attempting to access zip archive information1795# via any existing stale zipimport.zipimporter instances fails.1796# 2. Remove the entry from the cache so any newly constructed1797# zipimport.zipimporter instances do not end up using old stale1798# zip archive directory information.1799# This whole stale data removal step does not seem strictly necessary,1800# but has been left in because it was done before we started replacing1801# the zip archive directory information cache content if possible, and1802# there are no relevant unit tests that we can depend on to tell us if1803# this is really needed.1804_remove_and_clear_zip_directory_cache_data(normalized_path)180518061807def _collect_zipimporter_cache_entries(normalized_path, cache):1808"""1809Return zipimporter cache entry keys related to a given normalized path.18101811Alternative path spellings (e.g. those using different character case or1812those using alternative path separators) related to the same path are1813included. Any sub-path entries are included as well, i.e. those1814corresponding to zip archives embedded in other zip archives.18151816"""1817result = []1818prefix_len = len(normalized_path)1819for p in cache:1820np = normalize_path(p)1821if (np.startswith(normalized_path) and1822np[prefix_len:prefix_len + 1] in (os.sep, '')):1823result.append(p)1824return result182518261827def _update_zipimporter_cache(normalized_path, cache, updater=None):1828"""1829Update zipimporter cache data for a given normalized path.18301831Any sub-path entries are processed as well, i.e. those corresponding to zip1832archives embedded in other zip archives.18331834Given updater is a callable taking a cache entry key and the original entry1835(after already removing the entry from the cache), and expected to update1836the entry and possibly return a new one to be inserted in its place.1837Returning None indicates that the entry should not be replaced with a new1838one. If no updater is given, the cache entries are simply removed without1839any additional processing, the same as if the updater simply returned None.18401841"""1842for p in _collect_zipimporter_cache_entries(normalized_path, cache):1843# N.B. pypy's custom zipimport._zip_directory_cache implementation does1844# not support the complete dict interface:1845# * Does not support item assignment, thus not allowing this function1846# to be used only for removing existing cache entries.1847# * Does not support the dict.pop() method, forcing us to use the1848# get/del patterns instead. For more detailed information see the1849# following links:1850# https://github.com/pypa/setuptools/issues/202#issuecomment-2029134201851# http://bit.ly/2h9itJX1852old_entry = cache[p]1853del cache[p]1854new_entry = updater and updater(p, old_entry)1855if new_entry is not None:1856cache[p] = new_entry185718581859def _uncache(normalized_path, cache):1860_update_zipimporter_cache(normalized_path, cache)186118621863def _remove_and_clear_zip_directory_cache_data(normalized_path):1864def clear_and_remove_cached_zip_archive_directory_data(path, old_entry):1865old_entry.clear()18661867_update_zipimporter_cache(1868normalized_path, zipimport._zip_directory_cache,1869updater=clear_and_remove_cached_zip_archive_directory_data)187018711872# PyPy Python implementation does not allow directly writing to the1873# zipimport._zip_directory_cache and so prevents us from attempting to correct1874# its content. The best we can do there is clear the problematic cache content1875# and have PyPy repopulate it as needed. The downside is that if there are any1876# stale zipimport.zipimporter instances laying around, attempting to use them1877# will fail due to not having its zip archive directory information available1878# instead of being automatically corrected to use the new correct zip archive1879# directory information.1880if '__pypy__' in sys.builtin_module_names:1881_replace_zip_directory_cache_data = \1882_remove_and_clear_zip_directory_cache_data1883else:18841885def _replace_zip_directory_cache_data(normalized_path):1886def replace_cached_zip_archive_directory_data(path, old_entry):1887# N.B. In theory, we could load the zip directory information just1888# once for all updated path spellings, and then copy it locally and1889# update its contained path strings to contain the correct1890# spelling, but that seems like a way too invasive move (this cache1891# structure is not officially documented anywhere and could in1892# theory change with new Python releases) for no significant1893# benefit.1894old_entry.clear()1895zipimport.zipimporter(path)1896old_entry.update(zipimport._zip_directory_cache[path])1897return old_entry18981899_update_zipimporter_cache(1900normalized_path, zipimport._zip_directory_cache,1901updater=replace_cached_zip_archive_directory_data)190219031904def is_python(text, filename='<string>'):1905"Is this string a valid Python script?"1906try:1907compile(text, filename, 'exec')1908except (SyntaxError, TypeError):1909return False1910else:1911return True191219131914def is_sh(executable):1915"""Determine if the specified executable is a .sh (contains a #! line)"""1916try:1917with io.open(executable, encoding='latin-1') as fp:1918magic = fp.read(2)1919except (OSError, IOError):1920return executable1921return magic == '#!'192219231924def nt_quote_arg(arg):1925"""Quote a command line argument according to Windows parsing rules"""1926return subprocess.list2cmdline([arg])192719281929def is_python_script(script_text, filename):1930"""Is this text, as a whole, a Python script? (as opposed to shell/bat/etc.1931"""1932if filename.endswith('.py') or filename.endswith('.pyw'):1933return True # extension says it's Python1934if is_python(script_text, filename):1935return True # it's syntactically valid Python1936if script_text.startswith('#!'):1937# It begins with a '#!' line, so check if 'python' is in it somewhere1938return 'python' in script_text.splitlines()[0].lower()19391940return False # Not any Python I can recognize194119421943try:1944from os import chmod as _chmod1945except ImportError:1946# Jython compatibility1947def _chmod(*args):1948pass194919501951def chmod(path, mode):1952log.debug("changing mode of %s to %o", path, mode)1953try:1954_chmod(path, mode)1955except os.error as e:1956log.debug("chmod failed: %s", e)195719581959class CommandSpec(list):1960"""1961A command spec for a #! header, specified as a list of arguments akin to1962those passed to Popen.1963"""19641965options = []1966split_args = dict()19671968@classmethod1969def best(cls):1970"""1971Choose the best CommandSpec class based on environmental conditions.1972"""1973return cls19741975@classmethod1976def _sys_executable(cls):1977_default = os.path.normpath(sys.executable)1978return os.environ.get('__PYVENV_LAUNCHER__', _default)19791980@classmethod1981def from_param(cls, param):1982"""1983Construct a CommandSpec from a parameter to build_scripts, which may1984be None.1985"""1986if isinstance(param, cls):1987return param1988if isinstance(param, list):1989return cls(param)1990if param is None:1991return cls.from_environment()1992# otherwise, assume it's a string.1993return cls.from_string(param)19941995@classmethod1996def from_environment(cls):1997return cls([cls._sys_executable()])19981999@classmethod2000def from_string(cls, string):2001"""2002Construct a command spec from a simple string representing a command2003line parseable by shlex.split.2004"""2005items = shlex.split(string, **cls.split_args)2006return cls(items)20072008def install_options(self, script_text):2009self.options = shlex.split(self._extract_options(script_text))2010cmdline = subprocess.list2cmdline(self)2011if not isascii(cmdline):2012self.options[:0] = ['-x']20132014@staticmethod2015def _extract_options(orig_script):2016"""2017Extract any options from the first line of the script.2018"""2019first = (orig_script + '\n').splitlines()[0]2020match = _first_line_re().match(first)2021options = match.group(1) or '' if match else ''2022return options.strip()20232024def as_header(self):2025return self._render(self + list(self.options))20262027@staticmethod2028def _strip_quotes(item):2029_QUOTES = '"\''2030for q in _QUOTES:2031if item.startswith(q) and item.endswith(q):2032return item[1:-1]2033return item20342035@staticmethod2036def _render(items):2037cmdline = subprocess.list2cmdline(2038CommandSpec._strip_quotes(item.strip()) for item in items)2039return '#!' + cmdline + '\n'204020412042# For pbr compat; will be removed in a future version.2043sys_executable = CommandSpec._sys_executable()204420452046class WindowsCommandSpec(CommandSpec):2047split_args = dict(posix=False)204820492050class ScriptWriter:2051"""2052Encapsulates behavior around writing entry point scripts for console and2053gui apps.2054"""20552056template = textwrap.dedent(r"""2057# EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r2058import re2059import sys20602061# for compatibility with easy_install; see #21982062__requires__ = %(spec)r20632064try:2065from importlib.metadata import distribution2066except ImportError:2067try:2068from importlib_metadata import distribution2069except ImportError:2070from pkg_resources import load_entry_point207120722073def importlib_load_entry_point(spec, group, name):2074dist_name, _, _ = spec.partition('==')2075matches = (2076entry_point2077for entry_point in distribution(dist_name).entry_points2078if entry_point.group == group and entry_point.name == name2079)2080return next(matches).load()208120822083globals().setdefault('load_entry_point', importlib_load_entry_point)208420852086if __name__ == '__main__':2087sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])2088sys.exit(load_entry_point(%(spec)r, %(group)r, %(name)r)())2089""").lstrip()20902091command_spec_class = CommandSpec20922093@classmethod2094def get_script_args(cls, dist, executable=None, wininst=False):2095# for backward compatibility2096warnings.warn("Use get_args", EasyInstallDeprecationWarning)2097writer = (WindowsScriptWriter if wininst else ScriptWriter).best()2098header = cls.get_script_header("", executable, wininst)2099return writer.get_args(dist, header)21002101@classmethod2102def get_script_header(cls, script_text, executable=None, wininst=False):2103# for backward compatibility2104warnings.warn(2105"Use get_header", EasyInstallDeprecationWarning, stacklevel=2)2106if wininst:2107executable = "python.exe"2108return cls.get_header(script_text, executable)21092110@classmethod2111def get_args(cls, dist, header=None):2112"""2113Yield write_script() argument tuples for a distribution's2114console_scripts and gui_scripts entry points.2115"""2116if header is None:2117header = cls.get_header()2118spec = str(dist.as_requirement())2119for type_ in 'console', 'gui':2120group = type_ + '_scripts'2121for name, ep in dist.get_entry_map(group).items():2122cls._ensure_safe_name(name)2123script_text = cls.template % locals()2124args = cls._get_script_args(type_, name, header, script_text)2125for res in args:2126yield res21272128@staticmethod2129def _ensure_safe_name(name):2130"""2131Prevent paths in *_scripts entry point names.2132"""2133has_path_sep = re.search(r'[\\/]', name)2134if has_path_sep:2135raise ValueError("Path separators not allowed in script names")21362137@classmethod2138def get_writer(cls, force_windows):2139# for backward compatibility2140warnings.warn("Use best", EasyInstallDeprecationWarning)2141return WindowsScriptWriter.best() if force_windows else cls.best()21422143@classmethod2144def best(cls):2145"""2146Select the best ScriptWriter for this environment.2147"""2148if sys.platform == 'win32' or (os.name == 'java' and os._name == 'nt'):2149return WindowsScriptWriter.best()2150else:2151return cls21522153@classmethod2154def _get_script_args(cls, type_, name, header, script_text):2155# Simply write the stub with no extension.2156yield (name, header + script_text)21572158@classmethod2159def get_header(cls, script_text="", executable=None):2160"""Create a #! line, getting options (if any) from script_text"""2161cmd = cls.command_spec_class.best().from_param(executable)2162cmd.install_options(script_text)2163return cmd.as_header()216421652166class WindowsScriptWriter(ScriptWriter):2167command_spec_class = WindowsCommandSpec21682169@classmethod2170def get_writer(cls):2171# for backward compatibility2172warnings.warn("Use best", EasyInstallDeprecationWarning)2173return cls.best()21742175@classmethod2176def best(cls):2177"""2178Select the best ScriptWriter suitable for Windows2179"""2180writer_lookup = dict(2181executable=WindowsExecutableLauncherWriter,2182natural=cls,2183)2184# for compatibility, use the executable launcher by default2185launcher = os.environ.get('SETUPTOOLS_LAUNCHER', 'executable')2186return writer_lookup[launcher]21872188@classmethod2189def _get_script_args(cls, type_, name, header, script_text):2190"For Windows, add a .py extension"2191ext = dict(console='.pya', gui='.pyw')[type_]2192if ext not in os.environ['PATHEXT'].lower().split(';'):2193msg = (2194"{ext} not listed in PATHEXT; scripts will not be "2195"recognized as executables."2196).format(**locals())2197warnings.warn(msg, UserWarning)2198old = ['.pya', '.py', '-script.py', '.pyc', '.pyo', '.pyw', '.exe']2199old.remove(ext)2200header = cls._adjust_header(type_, header)2201blockers = [name + x for x in old]2202yield name + ext, header + script_text, 't', blockers22032204@classmethod2205def _adjust_header(cls, type_, orig_header):2206"""2207Make sure 'pythonw' is used for gui and 'python' is used for2208console (regardless of what sys.executable is).2209"""2210pattern = 'pythonw.exe'2211repl = 'python.exe'2212if type_ == 'gui':2213pattern, repl = repl, pattern2214pattern_ob = re.compile(re.escape(pattern), re.IGNORECASE)2215new_header = pattern_ob.sub(string=orig_header, repl=repl)2216return new_header if cls._use_header(new_header) else orig_header22172218@staticmethod2219def _use_header(new_header):2220"""2221Should _adjust_header use the replaced header?22222223On non-windows systems, always use. On2224Windows systems, only use the replaced header if it resolves2225to an executable on the system.2226"""2227clean_header = new_header[2:-1].strip('"')2228return sys.platform != 'win32' or find_executable(clean_header)222922302231class WindowsExecutableLauncherWriter(WindowsScriptWriter):2232@classmethod2233def _get_script_args(cls, type_, name, header, script_text):2234"""2235For Windows, add a .py extension and an .exe launcher2236"""2237if type_ == 'gui':2238launcher_type = 'gui'2239ext = '-script.pyw'2240old = ['.pyw']2241else:2242launcher_type = 'cli'2243ext = '-script.py'2244old = ['.py', '.pyc', '.pyo']2245hdr = cls._adjust_header(type_, header)2246blockers = [name + x for x in old]2247yield (name + ext, hdr + script_text, 't', blockers)2248yield (2249name + '.exe', get_win_launcher(launcher_type),2250'b' # write in binary mode2251)2252if not is_64bit():2253# install a manifest for the launcher to prevent Windows2254# from detecting it as an installer (which it will for2255# launchers like easy_install.exe). Consider only2256# adding a manifest for launchers detected as installers.2257# See Distribute #143 for details.2258m_name = name + '.exe.manifest'2259yield (m_name, load_launcher_manifest(name), 't')226022612262# for backward-compatibility2263get_script_args = ScriptWriter.get_script_args2264get_script_header = ScriptWriter.get_script_header226522662267def get_win_launcher(type):2268"""2269Load the Windows launcher (executable) suitable for launching a script.22702271`type` should be either 'cli' or 'gui'22722273Returns the executable as a byte string.2274"""2275launcher_fn = '%s.exe' % type2276if is_64bit():2277if get_platform() == "win-arm64":2278launcher_fn = launcher_fn.replace(".", "-arm64.")2279else:2280launcher_fn = launcher_fn.replace(".", "-64.")2281else:2282launcher_fn = launcher_fn.replace(".", "-32.")2283return resource_string('setuptools', launcher_fn)228422852286def load_launcher_manifest(name):2287manifest = pkg_resources.resource_string(__name__, 'launcher manifest.xml')2288return manifest.decode('utf-8') % vars()228922902291def rmtree(path, ignore_errors=False, onerror=auto_chmod):2292return shutil.rmtree(path, ignore_errors, onerror)229322942295def current_umask():2296tmp = os.umask(0o022)2297os.umask(tmp)2298return tmp229923002301def only_strs(values):2302"""2303Exclude non-str values. Ref #3063.2304"""2305return filter(lambda val: isinstance(val, str), values)230623072308class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning):2309"""2310Warning for EasyInstall deprecations, bypassing suppression.2311"""231223132314