python (3.11.7)
       1  """setuptools.command.egg_info
       2  
       3  Create a distribution's .egg-info directory and contents"""
       4  
       5  from distutils.filelist import FileList as _FileList
       6  from distutils.errors import DistutilsInternalError
       7  from distutils.util import convert_path
       8  from distutils import log
       9  import distutils.errors
      10  import distutils.filelist
      11  import functools
      12  import os
      13  import re
      14  import sys
      15  import io
      16  import warnings
      17  import time
      18  import collections
      19  
      20  from .._importlib import metadata
      21  from .. import _entry_points
      22  
      23  from setuptools import Command
      24  from setuptools.command.sdist import sdist
      25  from setuptools.command.sdist import walk_revctrl
      26  from setuptools.command.setopt import edit_config
      27  from setuptools.command import bdist_egg
      28  from pkg_resources import (
      29      Requirement, safe_name, parse_version,
      30      safe_version, to_filename)
      31  import setuptools.unicode_utils as unicode_utils
      32  from setuptools.glob import glob
      33  
      34  from setuptools.extern import packaging
      35  from setuptools.extern.jaraco.text import yield_lines
      36  from setuptools import SetuptoolsDeprecationWarning
      37  
      38  
      39  def translate_pattern(glob):  # noqa: C901  # is too complex (14)  # FIXME
      40      """
      41      Translate a file path glob like '*.txt' in to a regular expression.
      42      This differs from fnmatch.translate which allows wildcards to match
      43      directory separators. It also knows about '**/' which matches any number of
      44      directories.
      45      """
      46      pat = ''
      47  
      48      # This will split on '/' within [character classes]. This is deliberate.
      49      chunks = glob.split(os.path.sep)
      50  
      51      sep = re.escape(os.sep)
      52      valid_char = '[^%s]' % (sep,)
      53  
      54      for c, chunk in enumerate(chunks):
      55          last_chunk = c == len(chunks) - 1
      56  
      57          # Chunks that are a literal ** are globstars. They match anything.
      58          if chunk == '**':
      59              if last_chunk:
      60                  # Match anything if this is the last component
      61                  pat += '.*'
      62              else:
      63                  # Match '(name/)*'
      64                  pat += '(?:%s+%s)*' % (valid_char, sep)
      65              continue  # Break here as the whole path component has been handled
      66  
      67          # Find any special characters in the remainder
      68          i = 0
      69          chunk_len = len(chunk)
      70          while i < chunk_len:
      71              char = chunk[i]
      72              if char == '*':
      73                  # Match any number of name characters
      74                  pat += valid_char + '*'
      75              elif char == '?':
      76                  # Match a name character
      77                  pat += valid_char
      78              elif char == '[':
      79                  # Character class
      80                  inner_i = i + 1
      81                  # Skip initial !/] chars
      82                  if inner_i < chunk_len and chunk[inner_i] == '!':
      83                      inner_i = inner_i + 1
      84                  if inner_i < chunk_len and chunk[inner_i] == ']':
      85                      inner_i = inner_i + 1
      86  
      87                  # Loop till the closing ] is found
      88                  while inner_i < chunk_len and chunk[inner_i] != ']':
      89                      inner_i = inner_i + 1
      90  
      91                  if inner_i >= chunk_len:
      92                      # Got to the end of the string without finding a closing ]
      93                      # Do not treat this as a matching group, but as a literal [
      94                      pat += re.escape(char)
      95                  else:
      96                      # Grab the insides of the [brackets]
      97                      inner = chunk[i + 1:inner_i]
      98                      char_class = ''
      99  
     100                      # Class negation
     101                      if inner[0] == '!':
     102                          char_class = '^'
     103                          inner = inner[1:]
     104  
     105                      char_class += re.escape(inner)
     106                      pat += '[%s]' % (char_class,)
     107  
     108                      # Skip to the end ]
     109                      i = inner_i
     110              else:
     111                  pat += re.escape(char)
     112              i += 1
     113  
     114          # Join each chunk with the dir separator
     115          if not last_chunk:
     116              pat += sep
     117  
     118      pat += r'\Z'
     119      return re.compile(pat, flags=re.MULTILINE | re.DOTALL)
     120  
     121  
     122  class ESC[4;38;5;81mInfoCommon:
     123      tag_build = None
     124      tag_date = None
     125  
     126      @property
     127      def name(self):
     128          return safe_name(self.distribution.get_name())
     129  
     130      def tagged_version(self):
     131          return safe_version(self._maybe_tag(self.distribution.get_version()))
     132  
     133      def _maybe_tag(self, version):
     134          """
     135          egg_info may be called more than once for a distribution,
     136          in which case the version string already contains all tags.
     137          """
     138          return (
     139              version if self.vtags and self._already_tagged(version)
     140              else version + self.vtags
     141          )
     142  
     143      def _already_tagged(self, version: str) -> bool:
     144          # Depending on their format, tags may change with version normalization.
     145          # So in addition the regular tags, we have to search for the normalized ones.
     146          return version.endswith(self.vtags) or version.endswith(self._safe_tags())
     147  
     148      def _safe_tags(self) -> str:
     149          # To implement this we can rely on `safe_version` pretending to be version 0
     150          # followed by tags. Then we simply discard the starting 0 (fake version number)
     151          return safe_version(f"0{self.vtags}")[1:]
     152  
     153      def tags(self) -> str:
     154          version = ''
     155          if self.tag_build:
     156              version += self.tag_build
     157          if self.tag_date:
     158              version += time.strftime("-%Y%m%d")
     159          return version
     160      vtags = property(tags)
     161  
     162  
     163  class ESC[4;38;5;81megg_info(ESC[4;38;5;149mInfoCommon, ESC[4;38;5;149mCommand):
     164      description = "create a distribution's .egg-info directory"
     165  
     166      user_options = [
     167          ('egg-base=', 'e', "directory containing .egg-info directories"
     168                             " (default: top of the source tree)"),
     169          ('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"),
     170          ('tag-build=', 'b', "Specify explicit tag to add to version number"),
     171          ('no-date', 'D', "Don't include date stamp [default]"),
     172      ]
     173  
     174      boolean_options = ['tag-date']
     175      negative_opt = {
     176          'no-date': 'tag-date',
     177      }
     178  
     179      def initialize_options(self):
     180          self.egg_base = None
     181          self.egg_name = None
     182          self.egg_info = None
     183          self.egg_version = None
     184          self.broken_egg_info = False
     185          self.ignore_egg_info_in_manifest = False
     186  
     187      ####################################
     188      # allow the 'tag_svn_revision' to be detected and
     189      # set, supporting sdists built on older Setuptools.
     190      @property
     191      def tag_svn_revision(self):
     192          pass
     193  
     194      @tag_svn_revision.setter
     195      def tag_svn_revision(self, value):
     196          pass
     197      ####################################
     198  
     199      def save_version_info(self, filename):
     200          """
     201          Materialize the value of date into the
     202          build tag. Install build keys in a deterministic order
     203          to avoid arbitrary reordering on subsequent builds.
     204          """
     205          egg_info = collections.OrderedDict()
     206          # follow the order these keys would have been added
     207          # when PYTHONHASHSEED=0
     208          egg_info['tag_build'] = self.tags()
     209          egg_info['tag_date'] = 0
     210          edit_config(filename, dict(egg_info=egg_info))
     211  
     212      def finalize_options(self):
     213          # Note: we need to capture the current value returned
     214          # by `self.tagged_version()`, so we can later update
     215          # `self.distribution.metadata.version` without
     216          # repercussions.
     217          self.egg_name = self.name
     218          self.egg_version = self.tagged_version()
     219          parsed_version = parse_version(self.egg_version)
     220  
     221          try:
     222              is_version = isinstance(parsed_version, packaging.version.Version)
     223              spec = "%s==%s" if is_version else "%s===%s"
     224              Requirement(spec % (self.egg_name, self.egg_version))
     225          except ValueError as e:
     226              raise distutils.errors.DistutilsOptionError(
     227                  "Invalid distribution name or version syntax: %s-%s" %
     228                  (self.egg_name, self.egg_version)
     229              ) from e
     230  
     231          if self.egg_base is None:
     232              dirs = self.distribution.package_dir
     233              self.egg_base = (dirs or {}).get('', os.curdir)
     234  
     235          self.ensure_dirname('egg_base')
     236          self.egg_info = to_filename(self.egg_name) + '.egg-info'
     237          if self.egg_base != os.curdir:
     238              self.egg_info = os.path.join(self.egg_base, self.egg_info)
     239          if '-' in self.egg_name:
     240              self.check_broken_egg_info()
     241  
     242          # Set package version for the benefit of dumber commands
     243          # (e.g. sdist, bdist_wininst, etc.)
     244          #
     245          self.distribution.metadata.version = self.egg_version
     246  
     247          # If we bootstrapped around the lack of a PKG-INFO, as might be the
     248          # case in a fresh checkout, make sure that any special tags get added
     249          # to the version info
     250          #
     251          pd = self.distribution._patched_dist
     252          if pd is not None and pd.key == self.egg_name.lower():
     253              pd._version = self.egg_version
     254              pd._parsed_version = parse_version(self.egg_version)
     255              self.distribution._patched_dist = None
     256  
     257      def write_or_delete_file(self, what, filename, data, force=False):
     258          """Write `data` to `filename` or delete if empty
     259  
     260          If `data` is non-empty, this routine is the same as ``write_file()``.
     261          If `data` is empty but not ``None``, this is the same as calling
     262          ``delete_file(filename)`.  If `data` is ``None``, then this is a no-op
     263          unless `filename` exists, in which case a warning is issued about the
     264          orphaned file (if `force` is false), or deleted (if `force` is true).
     265          """
     266          if data:
     267              self.write_file(what, filename, data)
     268          elif os.path.exists(filename):
     269              if data is None and not force:
     270                  log.warn(
     271                      "%s not set in setup(), but %s exists", what, filename
     272                  )
     273                  return
     274              else:
     275                  self.delete_file(filename)
     276  
     277      def write_file(self, what, filename, data):
     278          """Write `data` to `filename` (if not a dry run) after announcing it
     279  
     280          `what` is used in a log message to identify what is being written
     281          to the file.
     282          """
     283          log.info("writing %s to %s", what, filename)
     284          data = data.encode("utf-8")
     285          if not self.dry_run:
     286              f = open(filename, 'wb')
     287              f.write(data)
     288              f.close()
     289  
     290      def delete_file(self, filename):
     291          """Delete `filename` (if not a dry run) after announcing it"""
     292          log.info("deleting %s", filename)
     293          if not self.dry_run:
     294              os.unlink(filename)
     295  
     296      def run(self):
     297          self.mkpath(self.egg_info)
     298          os.utime(self.egg_info, None)
     299          for ep in metadata.entry_points(group='egg_info.writers'):
     300              writer = ep.load()
     301              writer(self, ep.name, os.path.join(self.egg_info, ep.name))
     302  
     303          # Get rid of native_libs.txt if it was put there by older bdist_egg
     304          nl = os.path.join(self.egg_info, "native_libs.txt")
     305          if os.path.exists(nl):
     306              self.delete_file(nl)
     307  
     308          self.find_sources()
     309  
     310      def find_sources(self):
     311          """Generate SOURCES.txt manifest file"""
     312          manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
     313          mm = manifest_maker(self.distribution)
     314          mm.ignore_egg_info_dir = self.ignore_egg_info_in_manifest
     315          mm.manifest = manifest_filename
     316          mm.run()
     317          self.filelist = mm.filelist
     318  
     319      def check_broken_egg_info(self):
     320          bei = self.egg_name + '.egg-info'
     321          if self.egg_base != os.curdir:
     322              bei = os.path.join(self.egg_base, bei)
     323          if os.path.exists(bei):
     324              log.warn(
     325                  "-" * 78 + '\n'
     326                  "Note: Your current .egg-info directory has a '-' in its name;"
     327                  '\nthis will not work correctly with "setup.py develop".\n\n'
     328                  'Please rename %s to %s to correct this problem.\n' + '-' * 78,
     329                  bei, self.egg_info
     330              )
     331              self.broken_egg_info = self.egg_info
     332              self.egg_info = bei  # make it work for now
     333  
     334  
     335  class ESC[4;38;5;81mFileList(ESC[4;38;5;149m_FileList):
     336      # Implementations of the various MANIFEST.in commands
     337  
     338      def __init__(self, warn=None, debug_print=None, ignore_egg_info_dir=False):
     339          super().__init__(warn, debug_print)
     340          self.ignore_egg_info_dir = ignore_egg_info_dir
     341  
     342      def process_template_line(self, line):
     343          # Parse the line: split it up, make sure the right number of words
     344          # is there, and return the relevant words.  'action' is always
     345          # defined: it's the first word of the line.  Which of the other
     346          # three are defined depends on the action; it'll be either
     347          # patterns, (dir and patterns), or (dir_pattern).
     348          (action, patterns, dir, dir_pattern) = self._parse_template_line(line)
     349  
     350          action_map = {
     351              'include': self.include,
     352              'exclude': self.exclude,
     353              'global-include': self.global_include,
     354              'global-exclude': self.global_exclude,
     355              'recursive-include': functools.partial(
     356                  self.recursive_include, dir,
     357              ),
     358              'recursive-exclude': functools.partial(
     359                  self.recursive_exclude, dir,
     360              ),
     361              'graft': self.graft,
     362              'prune': self.prune,
     363          }
     364          log_map = {
     365              'include': "warning: no files found matching '%s'",
     366              'exclude': (
     367                  "warning: no previously-included files found "
     368                  "matching '%s'"
     369              ),
     370              'global-include': (
     371                  "warning: no files found matching '%s' "
     372                  "anywhere in distribution"
     373              ),
     374              'global-exclude': (
     375                  "warning: no previously-included files matching "
     376                  "'%s' found anywhere in distribution"
     377              ),
     378              'recursive-include': (
     379                  "warning: no files found matching '%s' "
     380                  "under directory '%s'"
     381              ),
     382              'recursive-exclude': (
     383                  "warning: no previously-included files matching "
     384                  "'%s' found under directory '%s'"
     385              ),
     386              'graft': "warning: no directories found matching '%s'",
     387              'prune': "no previously-included directories found matching '%s'",
     388          }
     389  
     390          try:
     391              process_action = action_map[action]
     392          except KeyError:
     393              raise DistutilsInternalError(
     394                  "this cannot happen: invalid action '{action!s}'".
     395                  format(action=action),
     396              )
     397  
     398          # OK, now we know that the action is valid and we have the
     399          # right number of words on the line for that action -- so we
     400          # can proceed with minimal error-checking.
     401  
     402          action_is_recursive = action.startswith('recursive-')
     403          if action in {'graft', 'prune'}:
     404              patterns = [dir_pattern]
     405          extra_log_args = (dir, ) if action_is_recursive else ()
     406          log_tmpl = log_map[action]
     407  
     408          self.debug_print(
     409              ' '.join(
     410                  [action] +
     411                  ([dir] if action_is_recursive else []) +
     412                  patterns,
     413              )
     414          )
     415          for pattern in patterns:
     416              if not process_action(pattern):
     417                  log.warn(log_tmpl, pattern, *extra_log_args)
     418  
     419      def _remove_files(self, predicate):
     420          """
     421          Remove all files from the file list that match the predicate.
     422          Return True if any matching files were removed
     423          """
     424          found = False
     425          for i in range(len(self.files) - 1, -1, -1):
     426              if predicate(self.files[i]):
     427                  self.debug_print(" removing " + self.files[i])
     428                  del self.files[i]
     429                  found = True
     430          return found
     431  
     432      def include(self, pattern):
     433          """Include files that match 'pattern'."""
     434          found = [f for f in glob(pattern) if not os.path.isdir(f)]
     435          self.extend(found)
     436          return bool(found)
     437  
     438      def exclude(self, pattern):
     439          """Exclude files that match 'pattern'."""
     440          match = translate_pattern(pattern)
     441          return self._remove_files(match.match)
     442  
     443      def recursive_include(self, dir, pattern):
     444          """
     445          Include all files anywhere in 'dir/' that match the pattern.
     446          """
     447          full_pattern = os.path.join(dir, '**', pattern)
     448          found = [f for f in glob(full_pattern, recursive=True)
     449                   if not os.path.isdir(f)]
     450          self.extend(found)
     451          return bool(found)
     452  
     453      def recursive_exclude(self, dir, pattern):
     454          """
     455          Exclude any file anywhere in 'dir/' that match the pattern.
     456          """
     457          match = translate_pattern(os.path.join(dir, '**', pattern))
     458          return self._remove_files(match.match)
     459  
     460      def graft(self, dir):
     461          """Include all files from 'dir/'."""
     462          found = [
     463              item
     464              for match_dir in glob(dir)
     465              for item in distutils.filelist.findall(match_dir)
     466          ]
     467          self.extend(found)
     468          return bool(found)
     469  
     470      def prune(self, dir):
     471          """Filter out files from 'dir/'."""
     472          match = translate_pattern(os.path.join(dir, '**'))
     473          return self._remove_files(match.match)
     474  
     475      def global_include(self, pattern):
     476          """
     477          Include all files anywhere in the current directory that match the
     478          pattern. This is very inefficient on large file trees.
     479          """
     480          if self.allfiles is None:
     481              self.findall()
     482          match = translate_pattern(os.path.join('**', pattern))
     483          found = [f for f in self.allfiles if match.match(f)]
     484          self.extend(found)
     485          return bool(found)
     486  
     487      def global_exclude(self, pattern):
     488          """
     489          Exclude all files anywhere that match the pattern.
     490          """
     491          match = translate_pattern(os.path.join('**', pattern))
     492          return self._remove_files(match.match)
     493  
     494      def append(self, item):
     495          if item.endswith('\r'):  # Fix older sdists built on Windows
     496              item = item[:-1]
     497          path = convert_path(item)
     498  
     499          if self._safe_path(path):
     500              self.files.append(path)
     501  
     502      def extend(self, paths):
     503          self.files.extend(filter(self._safe_path, paths))
     504  
     505      def _repair(self):
     506          """
     507          Replace self.files with only safe paths
     508  
     509          Because some owners of FileList manipulate the underlying
     510          ``files`` attribute directly, this method must be called to
     511          repair those paths.
     512          """
     513          self.files = list(filter(self._safe_path, self.files))
     514  
     515      def _safe_path(self, path):
     516          enc_warn = "'%s' not %s encodable -- skipping"
     517  
     518          # To avoid accidental trans-codings errors, first to unicode
     519          u_path = unicode_utils.filesys_decode(path)
     520          if u_path is None:
     521              log.warn("'%s' in unexpected encoding -- skipping" % path)
     522              return False
     523  
     524          # Must ensure utf-8 encodability
     525          utf8_path = unicode_utils.try_encode(u_path, "utf-8")
     526          if utf8_path is None:
     527              log.warn(enc_warn, path, 'utf-8')
     528              return False
     529  
     530          try:
     531              # ignore egg-info paths
     532              is_egg_info = ".egg-info" in u_path or b".egg-info" in utf8_path
     533              if self.ignore_egg_info_dir and is_egg_info:
     534                  return False
     535              # accept is either way checks out
     536              if os.path.exists(u_path) or os.path.exists(utf8_path):
     537                  return True
     538          # this will catch any encode errors decoding u_path
     539          except UnicodeEncodeError:
     540              log.warn(enc_warn, path, sys.getfilesystemencoding())
     541  
     542  
     543  class ESC[4;38;5;81mmanifest_maker(ESC[4;38;5;149msdist):
     544      template = "MANIFEST.in"
     545  
     546      def initialize_options(self):
     547          self.use_defaults = 1
     548          self.prune = 1
     549          self.manifest_only = 1
     550          self.force_manifest = 1
     551          self.ignore_egg_info_dir = False
     552  
     553      def finalize_options(self):
     554          pass
     555  
     556      def run(self):
     557          self.filelist = FileList(ignore_egg_info_dir=self.ignore_egg_info_dir)
     558          if not os.path.exists(self.manifest):
     559              self.write_manifest()  # it must exist so it'll get in the list
     560          self.add_defaults()
     561          if os.path.exists(self.template):
     562              self.read_template()
     563          self.add_license_files()
     564          self.prune_file_list()
     565          self.filelist.sort()
     566          self.filelist.remove_duplicates()
     567          self.write_manifest()
     568  
     569      def _manifest_normalize(self, path):
     570          path = unicode_utils.filesys_decode(path)
     571          return path.replace(os.sep, '/')
     572  
     573      def write_manifest(self):
     574          """
     575          Write the file list in 'self.filelist' to the manifest file
     576          named by 'self.manifest'.
     577          """
     578          self.filelist._repair()
     579  
     580          # Now _repairs should encodability, but not unicode
     581          files = [self._manifest_normalize(f) for f in self.filelist.files]
     582          msg = "writing manifest file '%s'" % self.manifest
     583          self.execute(write_file, (self.manifest, files), msg)
     584  
     585      def warn(self, msg):
     586          if not self._should_suppress_warning(msg):
     587              sdist.warn(self, msg)
     588  
     589      @staticmethod
     590      def _should_suppress_warning(msg):
     591          """
     592          suppress missing-file warnings from sdist
     593          """
     594          return re.match(r"standard file .*not found", msg)
     595  
     596      def add_defaults(self):
     597          sdist.add_defaults(self)
     598          self.filelist.append(self.template)
     599          self.filelist.append(self.manifest)
     600          rcfiles = list(walk_revctrl())
     601          if rcfiles:
     602              self.filelist.extend(rcfiles)
     603          elif os.path.exists(self.manifest):
     604              self.read_manifest()
     605  
     606          if os.path.exists("setup.py"):
     607              # setup.py should be included by default, even if it's not
     608              # the script called to create the sdist
     609              self.filelist.append("setup.py")
     610  
     611          ei_cmd = self.get_finalized_command('egg_info')
     612          self.filelist.graft(ei_cmd.egg_info)
     613  
     614      def add_license_files(self):
     615          license_files = self.distribution.metadata.license_files or []
     616          for lf in license_files:
     617              log.info("adding license file '%s'", lf)
     618              pass
     619          self.filelist.extend(license_files)
     620  
     621      def prune_file_list(self):
     622          build = self.get_finalized_command('build')
     623          base_dir = self.distribution.get_fullname()
     624          self.filelist.prune(build.build_base)
     625          self.filelist.prune(base_dir)
     626          sep = re.escape(os.sep)
     627          self.filelist.exclude_pattern(r'(^|' + sep + r')(RCS|CVS|\.svn)' + sep,
     628                                        is_regex=1)
     629  
     630      def _safe_data_files(self, build_py):
     631          """
     632          The parent class implementation of this method
     633          (``sdist``) will try to include data files, which
     634          might cause recursion problems when
     635          ``include_package_data=True``.
     636  
     637          Therefore, avoid triggering any attempt of
     638          analyzing/building the manifest again.
     639          """
     640          if hasattr(build_py, 'get_data_files_without_manifest'):
     641              return build_py.get_data_files_without_manifest()
     642  
     643          warnings.warn(
     644              "Custom 'build_py' does not implement "
     645              "'get_data_files_without_manifest'.\nPlease extend command classes"
     646              " from setuptools instead of distutils.",
     647              SetuptoolsDeprecationWarning
     648          )
     649          return build_py.get_data_files()
     650  
     651  
     652  def write_file(filename, contents):
     653      """Create a file with the specified name and write 'contents' (a
     654      sequence of strings without line terminators) to it.
     655      """
     656      contents = "\n".join(contents)
     657  
     658      # assuming the contents has been vetted for utf-8 encoding
     659      contents = contents.encode("utf-8")
     660  
     661      with open(filename, "wb") as f:  # always write POSIX-style manifest
     662          f.write(contents)
     663  
     664  
     665  def write_pkg_info(cmd, basename, filename):
     666      log.info("writing %s", filename)
     667      if not cmd.dry_run:
     668          metadata = cmd.distribution.metadata
     669          metadata.version, oldver = cmd.egg_version, metadata.version
     670          metadata.name, oldname = cmd.egg_name, metadata.name
     671  
     672          try:
     673              # write unescaped data to PKG-INFO, so older pkg_resources
     674              # can still parse it
     675              metadata.write_pkg_info(cmd.egg_info)
     676          finally:
     677              metadata.name, metadata.version = oldname, oldver
     678  
     679          safe = getattr(cmd.distribution, 'zip_safe', None)
     680  
     681          bdist_egg.write_safety_flag(cmd.egg_info, safe)
     682  
     683  
     684  def warn_depends_obsolete(cmd, basename, filename):
     685      if os.path.exists(filename):
     686          log.warn(
     687              "WARNING: 'depends.txt' is not used by setuptools 0.6!\n"
     688              "Use the install_requires/extras_require setup() args instead."
     689          )
     690  
     691  
     692  def _write_requirements(stream, reqs):
     693      lines = yield_lines(reqs or ())
     694  
     695      def append_cr(line):
     696          return line + '\n'
     697      lines = map(append_cr, lines)
     698      stream.writelines(lines)
     699  
     700  
     701  def write_requirements(cmd, basename, filename):
     702      dist = cmd.distribution
     703      data = io.StringIO()
     704      _write_requirements(data, dist.install_requires)
     705      extras_require = dist.extras_require or {}
     706      for extra in sorted(extras_require):
     707          data.write('\n[{extra}]\n'.format(**vars()))
     708          _write_requirements(data, extras_require[extra])
     709      cmd.write_or_delete_file("requirements", filename, data.getvalue())
     710  
     711  
     712  def write_setup_requirements(cmd, basename, filename):
     713      data = io.StringIO()
     714      _write_requirements(data, cmd.distribution.setup_requires)
     715      cmd.write_or_delete_file("setup-requirements", filename, data.getvalue())
     716  
     717  
     718  def write_toplevel_names(cmd, basename, filename):
     719      pkgs = dict.fromkeys(
     720          [
     721              k.split('.', 1)[0]
     722              for k in cmd.distribution.iter_distribution_names()
     723          ]
     724      )
     725      cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n')
     726  
     727  
     728  def overwrite_arg(cmd, basename, filename):
     729      write_arg(cmd, basename, filename, True)
     730  
     731  
     732  def write_arg(cmd, basename, filename, force=False):
     733      argname = os.path.splitext(basename)[0]
     734      value = getattr(cmd.distribution, argname, None)
     735      if value is not None:
     736          value = '\n'.join(value) + '\n'
     737      cmd.write_or_delete_file(argname, filename, value, force)
     738  
     739  
     740  def write_entries(cmd, basename, filename):
     741      eps = _entry_points.load(cmd.distribution.entry_points)
     742      defn = _entry_points.render(eps)
     743      cmd.write_or_delete_file('entry points', filename, defn, True)
     744  
     745  
     746  def get_pkg_info_revision():
     747      """
     748      Get a -r### off of PKG-INFO Version in case this is an sdist of
     749      a subversion revision.
     750      """
     751      warnings.warn(
     752          "get_pkg_info_revision is deprecated.", EggInfoDeprecationWarning)
     753      if os.path.exists('PKG-INFO'):
     754          with io.open('PKG-INFO') as f:
     755              for line in f:
     756                  match = re.match(r"Version:.*-r(\d+)\s*$", line)
     757                  if match:
     758                      return int(match.group(1))
     759      return 0
     760  
     761  
     762  class ESC[4;38;5;81mEggInfoDeprecationWarning(ESC[4;38;5;149mSetuptoolsDeprecationWarning):
     763      """Deprecated behavior warning for EggInfo, bypassing suppression."""