python (3.11.7)

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