python (3.11.7)

(root)/
lib/
python3.11/
site-packages/
setuptools/
_distutils/
command/
sdist.py
       1  """distutils.command.sdist
       2  
       3  Implements the Distutils 'sdist' command (create a source distribution)."""
       4  
       5  import os
       6  import sys
       7  from glob import glob
       8  from warnings import warn
       9  
      10  from distutils.core import Command
      11  from distutils import dir_util
      12  from distutils import file_util
      13  from distutils import archive_util
      14  from distutils.text_file import TextFile
      15  from distutils.filelist import FileList
      16  from distutils import log
      17  from distutils.util import convert_path
      18  from distutils.errors import DistutilsOptionError, DistutilsTemplateError
      19  
      20  
      21  def show_formats():
      22      """Print all possible values for the 'formats' option (used by
      23      the "--help-formats" command-line option).
      24      """
      25      from distutils.fancy_getopt import FancyGetopt
      26      from distutils.archive_util import ARCHIVE_FORMATS
      27  
      28      formats = []
      29      for format in ARCHIVE_FORMATS.keys():
      30          formats.append(("formats=" + format, None, ARCHIVE_FORMATS[format][2]))
      31      formats.sort()
      32      FancyGetopt(formats).print_help("List of available source distribution formats:")
      33  
      34  
      35  class ESC[4;38;5;81msdist(ESC[4;38;5;149mCommand):
      36  
      37      description = "create a source distribution (tarball, zip file, etc.)"
      38  
      39      def checking_metadata(self):
      40          """Callable used for the check sub-command.
      41  
      42          Placed here so user_options can view it"""
      43          return self.metadata_check
      44  
      45      user_options = [
      46          ('template=', 't', "name of manifest template file [default: MANIFEST.in]"),
      47          ('manifest=', 'm', "name of manifest file [default: MANIFEST]"),
      48          (
      49              'use-defaults',
      50              None,
      51              "include the default file set in the manifest "
      52              "[default; disable with --no-defaults]",
      53          ),
      54          ('no-defaults', None, "don't include the default file set"),
      55          (
      56              'prune',
      57              None,
      58              "specifically exclude files/directories that should not be "
      59              "distributed (build tree, RCS/CVS dirs, etc.) "
      60              "[default; disable with --no-prune]",
      61          ),
      62          ('no-prune', None, "don't automatically exclude anything"),
      63          (
      64              'manifest-only',
      65              'o',
      66              "just regenerate the manifest and then stop " "(implies --force-manifest)",
      67          ),
      68          (
      69              'force-manifest',
      70              'f',
      71              "forcibly regenerate the manifest and carry on as usual. "
      72              "Deprecated: now the manifest is always regenerated.",
      73          ),
      74          ('formats=', None, "formats for source distribution (comma-separated list)"),
      75          (
      76              'keep-temp',
      77              'k',
      78              "keep the distribution tree around after creating " + "archive file(s)",
      79          ),
      80          (
      81              'dist-dir=',
      82              'd',
      83              "directory to put the source distribution archive(s) in " "[default: dist]",
      84          ),
      85          (
      86              'metadata-check',
      87              None,
      88              "Ensure that all required elements of meta-data "
      89              "are supplied. Warn if any missing. [default]",
      90          ),
      91          (
      92              'owner=',
      93              'u',
      94              "Owner name used when creating a tar file [default: current user]",
      95          ),
      96          (
      97              'group=',
      98              'g',
      99              "Group name used when creating a tar file [default: current group]",
     100          ),
     101      ]
     102  
     103      boolean_options = [
     104          'use-defaults',
     105          'prune',
     106          'manifest-only',
     107          'force-manifest',
     108          'keep-temp',
     109          'metadata-check',
     110      ]
     111  
     112      help_options = [
     113          ('help-formats', None, "list available distribution formats", show_formats),
     114      ]
     115  
     116      negative_opt = {'no-defaults': 'use-defaults', 'no-prune': 'prune'}
     117  
     118      sub_commands = [('check', checking_metadata)]
     119  
     120      READMES = ('README', 'README.txt', 'README.rst')
     121  
     122      def initialize_options(self):
     123          # 'template' and 'manifest' are, respectively, the names of
     124          # the manifest template and manifest file.
     125          self.template = None
     126          self.manifest = None
     127  
     128          # 'use_defaults': if true, we will include the default file set
     129          # in the manifest
     130          self.use_defaults = 1
     131          self.prune = 1
     132  
     133          self.manifest_only = 0
     134          self.force_manifest = 0
     135  
     136          self.formats = ['gztar']
     137          self.keep_temp = 0
     138          self.dist_dir = None
     139  
     140          self.archive_files = None
     141          self.metadata_check = 1
     142          self.owner = None
     143          self.group = None
     144  
     145      def finalize_options(self):
     146          if self.manifest is None:
     147              self.manifest = "MANIFEST"
     148          if self.template is None:
     149              self.template = "MANIFEST.in"
     150  
     151          self.ensure_string_list('formats')
     152  
     153          bad_format = archive_util.check_archive_formats(self.formats)
     154          if bad_format:
     155              raise DistutilsOptionError("unknown archive format '%s'" % bad_format)
     156  
     157          if self.dist_dir is None:
     158              self.dist_dir = "dist"
     159  
     160      def run(self):
     161          # 'filelist' contains the list of files that will make up the
     162          # manifest
     163          self.filelist = FileList()
     164  
     165          # Run sub commands
     166          for cmd_name in self.get_sub_commands():
     167              self.run_command(cmd_name)
     168  
     169          # Do whatever it takes to get the list of files to process
     170          # (process the manifest template, read an existing manifest,
     171          # whatever).  File list is accumulated in 'self.filelist'.
     172          self.get_file_list()
     173  
     174          # If user just wanted us to regenerate the manifest, stop now.
     175          if self.manifest_only:
     176              return
     177  
     178          # Otherwise, go ahead and create the source distribution tarball,
     179          # or zipfile, or whatever.
     180          self.make_distribution()
     181  
     182      def check_metadata(self):
     183          """Deprecated API."""
     184          warn(
     185              "distutils.command.sdist.check_metadata is deprecated, \
     186                use the check command instead",
     187              PendingDeprecationWarning,
     188          )
     189          check = self.distribution.get_command_obj('check')
     190          check.ensure_finalized()
     191          check.run()
     192  
     193      def get_file_list(self):
     194          """Figure out the list of files to include in the source
     195          distribution, and put it in 'self.filelist'.  This might involve
     196          reading the manifest template (and writing the manifest), or just
     197          reading the manifest, or just using the default file set -- it all
     198          depends on the user's options.
     199          """
     200          # new behavior when using a template:
     201          # the file list is recalculated every time because
     202          # even if MANIFEST.in or setup.py are not changed
     203          # the user might have added some files in the tree that
     204          # need to be included.
     205          #
     206          #  This makes --force the default and only behavior with templates.
     207          template_exists = os.path.isfile(self.template)
     208          if not template_exists and self._manifest_is_not_generated():
     209              self.read_manifest()
     210              self.filelist.sort()
     211              self.filelist.remove_duplicates()
     212              return
     213  
     214          if not template_exists:
     215              self.warn(
     216                  ("manifest template '%s' does not exist " + "(using default file list)")
     217                  % self.template
     218              )
     219          self.filelist.findall()
     220  
     221          if self.use_defaults:
     222              self.add_defaults()
     223  
     224          if template_exists:
     225              self.read_template()
     226  
     227          if self.prune:
     228              self.prune_file_list()
     229  
     230          self.filelist.sort()
     231          self.filelist.remove_duplicates()
     232          self.write_manifest()
     233  
     234      def add_defaults(self):
     235          """Add all the default files to self.filelist:
     236            - README or README.txt
     237            - setup.py
     238            - test/test*.py
     239            - all pure Python modules mentioned in setup script
     240            - all files pointed by package_data (build_py)
     241            - all files defined in data_files.
     242            - all files defined as scripts.
     243            - all C sources listed as part of extensions or C libraries
     244              in the setup script (doesn't catch C headers!)
     245          Warns if (README or README.txt) or setup.py are missing; everything
     246          else is optional.
     247          """
     248          self._add_defaults_standards()
     249          self._add_defaults_optional()
     250          self._add_defaults_python()
     251          self._add_defaults_data_files()
     252          self._add_defaults_ext()
     253          self._add_defaults_c_libs()
     254          self._add_defaults_scripts()
     255  
     256      @staticmethod
     257      def _cs_path_exists(fspath):
     258          """
     259          Case-sensitive path existence check
     260  
     261          >>> sdist._cs_path_exists(__file__)
     262          True
     263          >>> sdist._cs_path_exists(__file__.upper())
     264          False
     265          """
     266          if not os.path.exists(fspath):
     267              return False
     268          # make absolute so we always have a directory
     269          abspath = os.path.abspath(fspath)
     270          directory, filename = os.path.split(abspath)
     271          return filename in os.listdir(directory)
     272  
     273      def _add_defaults_standards(self):
     274          standards = [self.READMES, self.distribution.script_name]
     275          for fn in standards:
     276              if isinstance(fn, tuple):
     277                  alts = fn
     278                  got_it = False
     279                  for fn in alts:
     280                      if self._cs_path_exists(fn):
     281                          got_it = True
     282                          self.filelist.append(fn)
     283                          break
     284  
     285                  if not got_it:
     286                      self.warn(
     287                          "standard file not found: should have one of " + ', '.join(alts)
     288                      )
     289              else:
     290                  if self._cs_path_exists(fn):
     291                      self.filelist.append(fn)
     292                  else:
     293                      self.warn("standard file '%s' not found" % fn)
     294  
     295      def _add_defaults_optional(self):
     296          optional = ['test/test*.py', 'setup.cfg']
     297          for pattern in optional:
     298              files = filter(os.path.isfile, glob(pattern))
     299              self.filelist.extend(files)
     300  
     301      def _add_defaults_python(self):
     302          # build_py is used to get:
     303          #  - python modules
     304          #  - files defined in package_data
     305          build_py = self.get_finalized_command('build_py')
     306  
     307          # getting python files
     308          if self.distribution.has_pure_modules():
     309              self.filelist.extend(build_py.get_source_files())
     310  
     311          # getting package_data files
     312          # (computed in build_py.data_files by build_py.finalize_options)
     313          for pkg, src_dir, build_dir, filenames in build_py.data_files:
     314              for filename in filenames:
     315                  self.filelist.append(os.path.join(src_dir, filename))
     316  
     317      def _add_defaults_data_files(self):
     318          # getting distribution.data_files
     319          if self.distribution.has_data_files():
     320              for item in self.distribution.data_files:
     321                  if isinstance(item, str):
     322                      # plain file
     323                      item = convert_path(item)
     324                      if os.path.isfile(item):
     325                          self.filelist.append(item)
     326                  else:
     327                      # a (dirname, filenames) tuple
     328                      dirname, filenames = item
     329                      for f in filenames:
     330                          f = convert_path(f)
     331                          if os.path.isfile(f):
     332                              self.filelist.append(f)
     333  
     334      def _add_defaults_ext(self):
     335          if self.distribution.has_ext_modules():
     336              build_ext = self.get_finalized_command('build_ext')
     337              self.filelist.extend(build_ext.get_source_files())
     338  
     339      def _add_defaults_c_libs(self):
     340          if self.distribution.has_c_libraries():
     341              build_clib = self.get_finalized_command('build_clib')
     342              self.filelist.extend(build_clib.get_source_files())
     343  
     344      def _add_defaults_scripts(self):
     345          if self.distribution.has_scripts():
     346              build_scripts = self.get_finalized_command('build_scripts')
     347              self.filelist.extend(build_scripts.get_source_files())
     348  
     349      def read_template(self):
     350          """Read and parse manifest template file named by self.template.
     351  
     352          (usually "MANIFEST.in") The parsing and processing is done by
     353          'self.filelist', which updates itself accordingly.
     354          """
     355          log.info("reading manifest template '%s'", self.template)
     356          template = TextFile(
     357              self.template,
     358              strip_comments=1,
     359              skip_blanks=1,
     360              join_lines=1,
     361              lstrip_ws=1,
     362              rstrip_ws=1,
     363              collapse_join=1,
     364          )
     365  
     366          try:
     367              while True:
     368                  line = template.readline()
     369                  if line is None:  # end of file
     370                      break
     371  
     372                  try:
     373                      self.filelist.process_template_line(line)
     374                  # the call above can raise a DistutilsTemplateError for
     375                  # malformed lines, or a ValueError from the lower-level
     376                  # convert_path function
     377                  except (DistutilsTemplateError, ValueError) as msg:
     378                      self.warn(
     379                          "%s, line %d: %s"
     380                          % (template.filename, template.current_line, msg)
     381                      )
     382          finally:
     383              template.close()
     384  
     385      def prune_file_list(self):
     386          """Prune off branches that might slip into the file list as created
     387          by 'read_template()', but really don't belong there:
     388            * the build tree (typically "build")
     389            * the release tree itself (only an issue if we ran "sdist"
     390              previously with --keep-temp, or it aborted)
     391            * any RCS, CVS, .svn, .hg, .git, .bzr, _darcs directories
     392          """
     393          build = self.get_finalized_command('build')
     394          base_dir = self.distribution.get_fullname()
     395  
     396          self.filelist.exclude_pattern(None, prefix=build.build_base)
     397          self.filelist.exclude_pattern(None, prefix=base_dir)
     398  
     399          if sys.platform == 'win32':
     400              seps = r'/|\\'
     401          else:
     402              seps = '/'
     403  
     404          vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr', '_darcs']
     405          vcs_ptrn = r'(^|{})({})({}).*'.format(seps, '|'.join(vcs_dirs), seps)
     406          self.filelist.exclude_pattern(vcs_ptrn, is_regex=1)
     407  
     408      def write_manifest(self):
     409          """Write the file list in 'self.filelist' (presumably as filled in
     410          by 'add_defaults()' and 'read_template()') to the manifest file
     411          named by 'self.manifest'.
     412          """
     413          if self._manifest_is_not_generated():
     414              log.info(
     415                  "not writing to manually maintained "
     416                  "manifest file '%s'" % self.manifest
     417              )
     418              return
     419  
     420          content = self.filelist.files[:]
     421          content.insert(0, '# file GENERATED by distutils, do NOT edit')
     422          self.execute(
     423              file_util.write_file,
     424              (self.manifest, content),
     425              "writing manifest file '%s'" % self.manifest,
     426          )
     427  
     428      def _manifest_is_not_generated(self):
     429          # check for special comment used in 3.1.3 and higher
     430          if not os.path.isfile(self.manifest):
     431              return False
     432  
     433          fp = open(self.manifest)
     434          try:
     435              first_line = fp.readline()
     436          finally:
     437              fp.close()
     438          return first_line != '# file GENERATED by distutils, do NOT edit\n'
     439  
     440      def read_manifest(self):
     441          """Read the manifest file (named by 'self.manifest') and use it to
     442          fill in 'self.filelist', the list of files to include in the source
     443          distribution.
     444          """
     445          log.info("reading manifest file '%s'", self.manifest)
     446          with open(self.manifest) as manifest:
     447              for line in manifest:
     448                  # ignore comments and blank lines
     449                  line = line.strip()
     450                  if line.startswith('#') or not line:
     451                      continue
     452                  self.filelist.append(line)
     453  
     454      def make_release_tree(self, base_dir, files):
     455          """Create the directory tree that will become the source
     456          distribution archive.  All directories implied by the filenames in
     457          'files' are created under 'base_dir', and then we hard link or copy
     458          (if hard linking is unavailable) those files into place.
     459          Essentially, this duplicates the developer's source tree, but in a
     460          directory named after the distribution, containing only the files
     461          to be distributed.
     462          """
     463          # Create all the directories under 'base_dir' necessary to
     464          # put 'files' there; the 'mkpath()' is just so we don't die
     465          # if the manifest happens to be empty.
     466          self.mkpath(base_dir)
     467          dir_util.create_tree(base_dir, files, dry_run=self.dry_run)
     468  
     469          # And walk over the list of files, either making a hard link (if
     470          # os.link exists) to each one that doesn't already exist in its
     471          # corresponding location under 'base_dir', or copying each file
     472          # that's out-of-date in 'base_dir'.  (Usually, all files will be
     473          # out-of-date, because by default we blow away 'base_dir' when
     474          # we're done making the distribution archives.)
     475  
     476          if hasattr(os, 'link'):  # can make hard links on this system
     477              link = 'hard'
     478              msg = "making hard links in %s..." % base_dir
     479          else:  # nope, have to copy
     480              link = None
     481              msg = "copying files to %s..." % base_dir
     482  
     483          if not files:
     484              log.warn("no files to distribute -- empty manifest?")
     485          else:
     486              log.info(msg)
     487          for file in files:
     488              if not os.path.isfile(file):
     489                  log.warn("'%s' not a regular file -- skipping", file)
     490              else:
     491                  dest = os.path.join(base_dir, file)
     492                  self.copy_file(file, dest, link=link)
     493  
     494          self.distribution.metadata.write_pkg_info(base_dir)
     495  
     496      def make_distribution(self):
     497          """Create the source distribution(s).  First, we create the release
     498          tree with 'make_release_tree()'; then, we create all required
     499          archive files (according to 'self.formats') from the release tree.
     500          Finally, we clean up by blowing away the release tree (unless
     501          'self.keep_temp' is true).  The list of archive files created is
     502          stored so it can be retrieved later by 'get_archive_files()'.
     503          """
     504          # Don't warn about missing meta-data here -- should be (and is!)
     505          # done elsewhere.
     506          base_dir = self.distribution.get_fullname()
     507          base_name = os.path.join(self.dist_dir, base_dir)
     508  
     509          self.make_release_tree(base_dir, self.filelist.files)
     510          archive_files = []  # remember names of files we create
     511          # tar archive must be created last to avoid overwrite and remove
     512          if 'tar' in self.formats:
     513              self.formats.append(self.formats.pop(self.formats.index('tar')))
     514  
     515          for fmt in self.formats:
     516              file = self.make_archive(
     517                  base_name, fmt, base_dir=base_dir, owner=self.owner, group=self.group
     518              )
     519              archive_files.append(file)
     520              self.distribution.dist_files.append(('sdist', '', file))
     521  
     522          self.archive_files = archive_files
     523  
     524          if not self.keep_temp:
     525              dir_util.remove_tree(base_dir, dry_run=self.dry_run)
     526  
     527      def get_archive_files(self):
     528          """Return the list of archive files created when the command
     529          was run, or None if the command hasn't run yet.
     530          """
     531          return self.archive_files