python (3.11.7)

(root)/
lib/
python3.11/
site-packages/
setuptools/
_distutils/
dist.py
       1  """distutils.dist
       2  
       3  Provides the Distribution class, which represents the module distribution
       4  being built/installed/distributed.
       5  """
       6  
       7  import sys
       8  import os
       9  import re
      10  import pathlib
      11  import contextlib
      12  from email import message_from_file
      13  
      14  try:
      15      import warnings
      16  except ImportError:
      17      warnings = None
      18  
      19  from distutils.errors import (
      20      DistutilsOptionError,
      21      DistutilsModuleError,
      22      DistutilsArgError,
      23      DistutilsClassError,
      24  )
      25  from distutils.fancy_getopt import FancyGetopt, translate_longopt
      26  from distutils.util import check_environ, strtobool, rfc822_escape
      27  from distutils import log
      28  from distutils.debug import DEBUG
      29  
      30  # Regex to define acceptable Distutils command names.  This is not *quite*
      31  # the same as a Python NAME -- I don't allow leading underscores.  The fact
      32  # that they're very similar is no coincidence; the default naming scheme is
      33  # to look for a Python module named after the command.
      34  command_re = re.compile(r'^[a-zA-Z]([a-zA-Z0-9_]*)$')
      35  
      36  
      37  def _ensure_list(value, fieldname):
      38      if isinstance(value, str):
      39          # a string containing comma separated values is okay.  It will
      40          # be converted to a list by Distribution.finalize_options().
      41          pass
      42      elif not isinstance(value, list):
      43          # passing a tuple or an iterator perhaps, warn and convert
      44          typename = type(value).__name__
      45          msg = "Warning: '{fieldname}' should be a list, got type '{typename}'"
      46          msg = msg.format(**locals())
      47          log.log(log.WARN, msg)
      48          value = list(value)
      49      return value
      50  
      51  
      52  class ESC[4;38;5;81mDistribution:
      53      """The core of the Distutils.  Most of the work hiding behind 'setup'
      54      is really done within a Distribution instance, which farms the work out
      55      to the Distutils commands specified on the command line.
      56  
      57      Setup scripts will almost never instantiate Distribution directly,
      58      unless the 'setup()' function is totally inadequate to their needs.
      59      However, it is conceivable that a setup script might wish to subclass
      60      Distribution for some specialized purpose, and then pass the subclass
      61      to 'setup()' as the 'distclass' keyword argument.  If so, it is
      62      necessary to respect the expectations that 'setup' has of Distribution.
      63      See the code for 'setup()', in core.py, for details.
      64      """
      65  
      66      # 'global_options' describes the command-line options that may be
      67      # supplied to the setup script prior to any actual commands.
      68      # Eg. "./setup.py -n" or "./setup.py --quiet" both take advantage of
      69      # these global options.  This list should be kept to a bare minimum,
      70      # since every global option is also valid as a command option -- and we
      71      # don't want to pollute the commands with too many options that they
      72      # have minimal control over.
      73      # The fourth entry for verbose means that it can be repeated.
      74      global_options = [
      75          ('verbose', 'v', "run verbosely (default)", 1),
      76          ('quiet', 'q', "run quietly (turns verbosity off)"),
      77          ('dry-run', 'n', "don't actually do anything"),
      78          ('help', 'h', "show detailed help message"),
      79          ('no-user-cfg', None, 'ignore pydistutils.cfg in your home directory'),
      80      ]
      81  
      82      # 'common_usage' is a short (2-3 line) string describing the common
      83      # usage of the setup script.
      84      common_usage = """\
      85  Common commands: (see '--help-commands' for more)
      86  
      87    setup.py build      will build the package underneath 'build/'
      88    setup.py install    will install the package
      89  """
      90  
      91      # options that are not propagated to the commands
      92      display_options = [
      93          ('help-commands', None, "list all available commands"),
      94          ('name', None, "print package name"),
      95          ('version', 'V', "print package version"),
      96          ('fullname', None, "print <package name>-<version>"),
      97          ('author', None, "print the author's name"),
      98          ('author-email', None, "print the author's email address"),
      99          ('maintainer', None, "print the maintainer's name"),
     100          ('maintainer-email', None, "print the maintainer's email address"),
     101          ('contact', None, "print the maintainer's name if known, else the author's"),
     102          (
     103              'contact-email',
     104              None,
     105              "print the maintainer's email address if known, else the author's",
     106          ),
     107          ('url', None, "print the URL for this package"),
     108          ('license', None, "print the license of the package"),
     109          ('licence', None, "alias for --license"),
     110          ('description', None, "print the package description"),
     111          ('long-description', None, "print the long package description"),
     112          ('platforms', None, "print the list of platforms"),
     113          ('classifiers', None, "print the list of classifiers"),
     114          ('keywords', None, "print the list of keywords"),
     115          ('provides', None, "print the list of packages/modules provided"),
     116          ('requires', None, "print the list of packages/modules required"),
     117          ('obsoletes', None, "print the list of packages/modules made obsolete"),
     118      ]
     119      display_option_names = [translate_longopt(x[0]) for x in display_options]
     120  
     121      # negative options are options that exclude other options
     122      negative_opt = {'quiet': 'verbose'}
     123  
     124      # -- Creation/initialization methods -------------------------------
     125  
     126      def __init__(self, attrs=None):  # noqa: C901
     127          """Construct a new Distribution instance: initialize all the
     128          attributes of a Distribution, and then use 'attrs' (a dictionary
     129          mapping attribute names to values) to assign some of those
     130          attributes their "real" values.  (Any attributes not mentioned in
     131          'attrs' will be assigned to some null value: 0, None, an empty list
     132          or dictionary, etc.)  Most importantly, initialize the
     133          'command_obj' attribute to the empty dictionary; this will be
     134          filled in with real command objects by 'parse_command_line()'.
     135          """
     136  
     137          # Default values for our command-line options
     138          self.verbose = 1
     139          self.dry_run = 0
     140          self.help = 0
     141          for attr in self.display_option_names:
     142              setattr(self, attr, 0)
     143  
     144          # Store the distribution meta-data (name, version, author, and so
     145          # forth) in a separate object -- we're getting to have enough
     146          # information here (and enough command-line options) that it's
     147          # worth it.  Also delegate 'get_XXX()' methods to the 'metadata'
     148          # object in a sneaky and underhanded (but efficient!) way.
     149          self.metadata = DistributionMetadata()
     150          for basename in self.metadata._METHOD_BASENAMES:
     151              method_name = "get_" + basename
     152              setattr(self, method_name, getattr(self.metadata, method_name))
     153  
     154          # 'cmdclass' maps command names to class objects, so we
     155          # can 1) quickly figure out which class to instantiate when
     156          # we need to create a new command object, and 2) have a way
     157          # for the setup script to override command classes
     158          self.cmdclass = {}
     159  
     160          # 'command_packages' is a list of packages in which commands
     161          # are searched for.  The factory for command 'foo' is expected
     162          # to be named 'foo' in the module 'foo' in one of the packages
     163          # named here.  This list is searched from the left; an error
     164          # is raised if no named package provides the command being
     165          # searched for.  (Always access using get_command_packages().)
     166          self.command_packages = None
     167  
     168          # 'script_name' and 'script_args' are usually set to sys.argv[0]
     169          # and sys.argv[1:], but they can be overridden when the caller is
     170          # not necessarily a setup script run from the command-line.
     171          self.script_name = None
     172          self.script_args = None
     173  
     174          # 'command_options' is where we store command options between
     175          # parsing them (from config files, the command-line, etc.) and when
     176          # they are actually needed -- ie. when the command in question is
     177          # instantiated.  It is a dictionary of dictionaries of 2-tuples:
     178          #   command_options = { command_name : { option : (source, value) } }
     179          self.command_options = {}
     180  
     181          # 'dist_files' is the list of (command, pyversion, file) that
     182          # have been created by any dist commands run so far. This is
     183          # filled regardless of whether the run is dry or not. pyversion
     184          # gives sysconfig.get_python_version() if the dist file is
     185          # specific to a Python version, 'any' if it is good for all
     186          # Python versions on the target platform, and '' for a source
     187          # file. pyversion should not be used to specify minimum or
     188          # maximum required Python versions; use the metainfo for that
     189          # instead.
     190          self.dist_files = []
     191  
     192          # These options are really the business of various commands, rather
     193          # than of the Distribution itself.  We provide aliases for them in
     194          # Distribution as a convenience to the developer.
     195          self.packages = None
     196          self.package_data = {}
     197          self.package_dir = None
     198          self.py_modules = None
     199          self.libraries = None
     200          self.headers = None
     201          self.ext_modules = None
     202          self.ext_package = None
     203          self.include_dirs = None
     204          self.extra_path = None
     205          self.scripts = None
     206          self.data_files = None
     207          self.password = ''
     208  
     209          # And now initialize bookkeeping stuff that can't be supplied by
     210          # the caller at all.  'command_obj' maps command names to
     211          # Command instances -- that's how we enforce that every command
     212          # class is a singleton.
     213          self.command_obj = {}
     214  
     215          # 'have_run' maps command names to boolean values; it keeps track
     216          # of whether we have actually run a particular command, to make it
     217          # cheap to "run" a command whenever we think we might need to -- if
     218          # it's already been done, no need for expensive filesystem
     219          # operations, we just check the 'have_run' dictionary and carry on.
     220          # It's only safe to query 'have_run' for a command class that has
     221          # been instantiated -- a false value will be inserted when the
     222          # command object is created, and replaced with a true value when
     223          # the command is successfully run.  Thus it's probably best to use
     224          # '.get()' rather than a straight lookup.
     225          self.have_run = {}
     226  
     227          # Now we'll use the attrs dictionary (ultimately, keyword args from
     228          # the setup script) to possibly override any or all of these
     229          # distribution options.
     230  
     231          if attrs:
     232              # Pull out the set of command options and work on them
     233              # specifically.  Note that this order guarantees that aliased
     234              # command options will override any supplied redundantly
     235              # through the general options dictionary.
     236              options = attrs.get('options')
     237              if options is not None:
     238                  del attrs['options']
     239                  for (command, cmd_options) in options.items():
     240                      opt_dict = self.get_option_dict(command)
     241                      for (opt, val) in cmd_options.items():
     242                          opt_dict[opt] = ("setup script", val)
     243  
     244              if 'licence' in attrs:
     245                  attrs['license'] = attrs['licence']
     246                  del attrs['licence']
     247                  msg = "'licence' distribution option is deprecated; use 'license'"
     248                  if warnings is not None:
     249                      warnings.warn(msg)
     250                  else:
     251                      sys.stderr.write(msg + "\n")
     252  
     253              # Now work on the rest of the attributes.  Any attribute that's
     254              # not already defined is invalid!
     255              for (key, val) in attrs.items():
     256                  if hasattr(self.metadata, "set_" + key):
     257                      getattr(self.metadata, "set_" + key)(val)
     258                  elif hasattr(self.metadata, key):
     259                      setattr(self.metadata, key, val)
     260                  elif hasattr(self, key):
     261                      setattr(self, key, val)
     262                  else:
     263                      msg = "Unknown distribution option: %s" % repr(key)
     264                      warnings.warn(msg)
     265  
     266          # no-user-cfg is handled before other command line args
     267          # because other args override the config files, and this
     268          # one is needed before we can load the config files.
     269          # If attrs['script_args'] wasn't passed, assume false.
     270          #
     271          # This also make sure we just look at the global options
     272          self.want_user_cfg = True
     273  
     274          if self.script_args is not None:
     275              for arg in self.script_args:
     276                  if not arg.startswith('-'):
     277                      break
     278                  if arg == '--no-user-cfg':
     279                      self.want_user_cfg = False
     280                      break
     281  
     282          self.finalize_options()
     283  
     284      def get_option_dict(self, command):
     285          """Get the option dictionary for a given command.  If that
     286          command's option dictionary hasn't been created yet, then create it
     287          and return the new dictionary; otherwise, return the existing
     288          option dictionary.
     289          """
     290          dict = self.command_options.get(command)
     291          if dict is None:
     292              dict = self.command_options[command] = {}
     293          return dict
     294  
     295      def dump_option_dicts(self, header=None, commands=None, indent=""):
     296          from pprint import pformat
     297  
     298          if commands is None:  # dump all command option dicts
     299              commands = sorted(self.command_options.keys())
     300  
     301          if header is not None:
     302              self.announce(indent + header)
     303              indent = indent + "  "
     304  
     305          if not commands:
     306              self.announce(indent + "no commands known yet")
     307              return
     308  
     309          for cmd_name in commands:
     310              opt_dict = self.command_options.get(cmd_name)
     311              if opt_dict is None:
     312                  self.announce(indent + "no option dict for '%s' command" % cmd_name)
     313              else:
     314                  self.announce(indent + "option dict for '%s' command:" % cmd_name)
     315                  out = pformat(opt_dict)
     316                  for line in out.split('\n'):
     317                      self.announce(indent + "  " + line)
     318  
     319      # -- Config file finding/parsing methods ---------------------------
     320  
     321      def find_config_files(self):
     322          """Find as many configuration files as should be processed for this
     323          platform, and return a list of filenames in the order in which they
     324          should be parsed.  The filenames returned are guaranteed to exist
     325          (modulo nasty race conditions).
     326  
     327          There are multiple possible config files:
     328          - distutils.cfg in the Distutils installation directory (i.e.
     329            where the top-level Distutils __inst__.py file lives)
     330          - a file in the user's home directory named .pydistutils.cfg
     331            on Unix and pydistutils.cfg on Windows/Mac; may be disabled
     332            with the ``--no-user-cfg`` option
     333          - setup.cfg in the current directory
     334          - a file named by an environment variable
     335          """
     336          check_environ()
     337          files = [str(path) for path in self._gen_paths() if os.path.isfile(path)]
     338  
     339          if DEBUG:
     340              self.announce("using config files: %s" % ', '.join(files))
     341  
     342          return files
     343  
     344      def _gen_paths(self):
     345          # The system-wide Distutils config file
     346          sys_dir = pathlib.Path(sys.modules['distutils'].__file__).parent
     347          yield sys_dir / "distutils.cfg"
     348  
     349          # The per-user config file
     350          prefix = '.' * (os.name == 'posix')
     351          filename = prefix + 'pydistutils.cfg'
     352          if self.want_user_cfg:
     353              yield pathlib.Path('~').expanduser() / filename
     354  
     355          # All platforms support local setup.cfg
     356          yield pathlib.Path('setup.cfg')
     357  
     358          # Additional config indicated in the environment
     359          with contextlib.suppress(TypeError):
     360              yield pathlib.Path(os.getenv("DIST_EXTRA_CONFIG"))
     361  
     362      def parse_config_files(self, filenames=None):  # noqa: C901
     363          from configparser import ConfigParser
     364  
     365          # Ignore install directory options if we have a venv
     366          if sys.prefix != sys.base_prefix:
     367              ignore_options = [
     368                  'install-base',
     369                  'install-platbase',
     370                  'install-lib',
     371                  'install-platlib',
     372                  'install-purelib',
     373                  'install-headers',
     374                  'install-scripts',
     375                  'install-data',
     376                  'prefix',
     377                  'exec-prefix',
     378                  'home',
     379                  'user',
     380                  'root',
     381              ]
     382          else:
     383              ignore_options = []
     384  
     385          ignore_options = frozenset(ignore_options)
     386  
     387          if filenames is None:
     388              filenames = self.find_config_files()
     389  
     390          if DEBUG:
     391              self.announce("Distribution.parse_config_files():")
     392  
     393          parser = ConfigParser()
     394          for filename in filenames:
     395              if DEBUG:
     396                  self.announce("  reading %s" % filename)
     397              parser.read(filename)
     398              for section in parser.sections():
     399                  options = parser.options(section)
     400                  opt_dict = self.get_option_dict(section)
     401  
     402                  for opt in options:
     403                      if opt != '__name__' and opt not in ignore_options:
     404                          val = parser.get(section, opt)
     405                          opt = opt.replace('-', '_')
     406                          opt_dict[opt] = (filename, val)
     407  
     408              # Make the ConfigParser forget everything (so we retain
     409              # the original filenames that options come from)
     410              parser.__init__()
     411  
     412          # If there was a "global" section in the config file, use it
     413          # to set Distribution options.
     414  
     415          if 'global' in self.command_options:
     416              for (opt, (src, val)) in self.command_options['global'].items():
     417                  alias = self.negative_opt.get(opt)
     418                  try:
     419                      if alias:
     420                          setattr(self, alias, not strtobool(val))
     421                      elif opt in ('verbose', 'dry_run'):  # ugh!
     422                          setattr(self, opt, strtobool(val))
     423                      else:
     424                          setattr(self, opt, val)
     425                  except ValueError as msg:
     426                      raise DistutilsOptionError(msg)
     427  
     428      # -- Command-line parsing methods ----------------------------------
     429  
     430      def parse_command_line(self):
     431          """Parse the setup script's command line, taken from the
     432          'script_args' instance attribute (which defaults to 'sys.argv[1:]'
     433          -- see 'setup()' in core.py).  This list is first processed for
     434          "global options" -- options that set attributes of the Distribution
     435          instance.  Then, it is alternately scanned for Distutils commands
     436          and options for that command.  Each new command terminates the
     437          options for the previous command.  The allowed options for a
     438          command are determined by the 'user_options' attribute of the
     439          command class -- thus, we have to be able to load command classes
     440          in order to parse the command line.  Any error in that 'options'
     441          attribute raises DistutilsGetoptError; any error on the
     442          command-line raises DistutilsArgError.  If no Distutils commands
     443          were found on the command line, raises DistutilsArgError.  Return
     444          true if command-line was successfully parsed and we should carry
     445          on with executing commands; false if no errors but we shouldn't
     446          execute commands (currently, this only happens if user asks for
     447          help).
     448          """
     449          #
     450          # We now have enough information to show the Macintosh dialog
     451          # that allows the user to interactively specify the "command line".
     452          #
     453          toplevel_options = self._get_toplevel_options()
     454  
     455          # We have to parse the command line a bit at a time -- global
     456          # options, then the first command, then its options, and so on --
     457          # because each command will be handled by a different class, and
     458          # the options that are valid for a particular class aren't known
     459          # until we have loaded the command class, which doesn't happen
     460          # until we know what the command is.
     461  
     462          self.commands = []
     463          parser = FancyGetopt(toplevel_options + self.display_options)
     464          parser.set_negative_aliases(self.negative_opt)
     465          parser.set_aliases({'licence': 'license'})
     466          args = parser.getopt(args=self.script_args, object=self)
     467          option_order = parser.get_option_order()
     468          log.set_verbosity(self.verbose)
     469  
     470          # for display options we return immediately
     471          if self.handle_display_options(option_order):
     472              return
     473          while args:
     474              args = self._parse_command_opts(parser, args)
     475              if args is None:  # user asked for help (and got it)
     476                  return
     477  
     478          # Handle the cases of --help as a "global" option, ie.
     479          # "setup.py --help" and "setup.py --help command ...".  For the
     480          # former, we show global options (--verbose, --dry-run, etc.)
     481          # and display-only options (--name, --version, etc.); for the
     482          # latter, we omit the display-only options and show help for
     483          # each command listed on the command line.
     484          if self.help:
     485              self._show_help(
     486                  parser, display_options=len(self.commands) == 0, commands=self.commands
     487              )
     488              return
     489  
     490          # Oops, no commands found -- an end-user error
     491          if not self.commands:
     492              raise DistutilsArgError("no commands supplied")
     493  
     494          # All is well: return true
     495          return True
     496  
     497      def _get_toplevel_options(self):
     498          """Return the non-display options recognized at the top level.
     499  
     500          This includes options that are recognized *only* at the top
     501          level as well as options recognized for commands.
     502          """
     503          return self.global_options + [
     504              (
     505                  "command-packages=",
     506                  None,
     507                  "list of packages that provide distutils commands",
     508              ),
     509          ]
     510  
     511      def _parse_command_opts(self, parser, args):  # noqa: C901
     512          """Parse the command-line options for a single command.
     513          'parser' must be a FancyGetopt instance; 'args' must be the list
     514          of arguments, starting with the current command (whose options
     515          we are about to parse).  Returns a new version of 'args' with
     516          the next command at the front of the list; will be the empty
     517          list if there are no more commands on the command line.  Returns
     518          None if the user asked for help on this command.
     519          """
     520          # late import because of mutual dependence between these modules
     521          from distutils.cmd import Command
     522  
     523          # Pull the current command from the head of the command line
     524          command = args[0]
     525          if not command_re.match(command):
     526              raise SystemExit("invalid command name '%s'" % command)
     527          self.commands.append(command)
     528  
     529          # Dig up the command class that implements this command, so we
     530          # 1) know that it's a valid command, and 2) know which options
     531          # it takes.
     532          try:
     533              cmd_class = self.get_command_class(command)
     534          except DistutilsModuleError as msg:
     535              raise DistutilsArgError(msg)
     536  
     537          # Require that the command class be derived from Command -- want
     538          # to be sure that the basic "command" interface is implemented.
     539          if not issubclass(cmd_class, Command):
     540              raise DistutilsClassError(
     541                  "command class %s must subclass Command" % cmd_class
     542              )
     543  
     544          # Also make sure that the command object provides a list of its
     545          # known options.
     546          if not (
     547              hasattr(cmd_class, 'user_options')
     548              and isinstance(cmd_class.user_options, list)
     549          ):
     550              msg = (
     551                  "command class %s must provide "
     552                  "'user_options' attribute (a list of tuples)"
     553              )
     554              raise DistutilsClassError(msg % cmd_class)
     555  
     556          # If the command class has a list of negative alias options,
     557          # merge it in with the global negative aliases.
     558          negative_opt = self.negative_opt
     559          if hasattr(cmd_class, 'negative_opt'):
     560              negative_opt = negative_opt.copy()
     561              negative_opt.update(cmd_class.negative_opt)
     562  
     563          # Check for help_options in command class.  They have a different
     564          # format (tuple of four) so we need to preprocess them here.
     565          if hasattr(cmd_class, 'help_options') and isinstance(
     566              cmd_class.help_options, list
     567          ):
     568              help_options = fix_help_options(cmd_class.help_options)
     569          else:
     570              help_options = []
     571  
     572          # All commands support the global options too, just by adding
     573          # in 'global_options'.
     574          parser.set_option_table(
     575              self.global_options + cmd_class.user_options + help_options
     576          )
     577          parser.set_negative_aliases(negative_opt)
     578          (args, opts) = parser.getopt(args[1:])
     579          if hasattr(opts, 'help') and opts.help:
     580              self._show_help(parser, display_options=0, commands=[cmd_class])
     581              return
     582  
     583          if hasattr(cmd_class, 'help_options') and isinstance(
     584              cmd_class.help_options, list
     585          ):
     586              help_option_found = 0
     587              for (help_option, short, desc, func) in cmd_class.help_options:
     588                  if hasattr(opts, parser.get_attr_name(help_option)):
     589                      help_option_found = 1
     590                      if callable(func):
     591                          func()
     592                      else:
     593                          raise DistutilsClassError(
     594                              "invalid help function %r for help option '%s': "
     595                              "must be a callable object (function, etc.)"
     596                              % (func, help_option)
     597                          )
     598  
     599              if help_option_found:
     600                  return
     601  
     602          # Put the options from the command-line into their official
     603          # holding pen, the 'command_options' dictionary.
     604          opt_dict = self.get_option_dict(command)
     605          for (name, value) in vars(opts).items():
     606              opt_dict[name] = ("command line", value)
     607  
     608          return args
     609  
     610      def finalize_options(self):
     611          """Set final values for all the options on the Distribution
     612          instance, analogous to the .finalize_options() method of Command
     613          objects.
     614          """
     615          for attr in ('keywords', 'platforms'):
     616              value = getattr(self.metadata, attr)
     617              if value is None:
     618                  continue
     619              if isinstance(value, str):
     620                  value = [elm.strip() for elm in value.split(',')]
     621                  setattr(self.metadata, attr, value)
     622  
     623      def _show_help(self, parser, global_options=1, display_options=1, commands=[]):
     624          """Show help for the setup script command-line in the form of
     625          several lists of command-line options.  'parser' should be a
     626          FancyGetopt instance; do not expect it to be returned in the
     627          same state, as its option table will be reset to make it
     628          generate the correct help text.
     629  
     630          If 'global_options' is true, lists the global options:
     631          --verbose, --dry-run, etc.  If 'display_options' is true, lists
     632          the "display-only" options: --name, --version, etc.  Finally,
     633          lists per-command help for every command name or command class
     634          in 'commands'.
     635          """
     636          # late import because of mutual dependence between these modules
     637          from distutils.core import gen_usage
     638          from distutils.cmd import Command
     639  
     640          if global_options:
     641              if display_options:
     642                  options = self._get_toplevel_options()
     643              else:
     644                  options = self.global_options
     645              parser.set_option_table(options)
     646              parser.print_help(self.common_usage + "\nGlobal options:")
     647              print('')
     648  
     649          if display_options:
     650              parser.set_option_table(self.display_options)
     651              parser.print_help(
     652                  "Information display options (just display "
     653                  + "information, ignore any commands)"
     654              )
     655              print('')
     656  
     657          for command in self.commands:
     658              if isinstance(command, type) and issubclass(command, Command):
     659                  klass = command
     660              else:
     661                  klass = self.get_command_class(command)
     662              if hasattr(klass, 'help_options') and isinstance(klass.help_options, list):
     663                  parser.set_option_table(
     664                      klass.user_options + fix_help_options(klass.help_options)
     665                  )
     666              else:
     667                  parser.set_option_table(klass.user_options)
     668              parser.print_help("Options for '%s' command:" % klass.__name__)
     669              print('')
     670  
     671          print(gen_usage(self.script_name))
     672  
     673      def handle_display_options(self, option_order):
     674          """If there were any non-global "display-only" options
     675          (--help-commands or the metadata display options) on the command
     676          line, display the requested info and return true; else return
     677          false.
     678          """
     679          from distutils.core import gen_usage
     680  
     681          # User just wants a list of commands -- we'll print it out and stop
     682          # processing now (ie. if they ran "setup --help-commands foo bar",
     683          # we ignore "foo bar").
     684          if self.help_commands:
     685              self.print_commands()
     686              print('')
     687              print(gen_usage(self.script_name))
     688              return 1
     689  
     690          # If user supplied any of the "display metadata" options, then
     691          # display that metadata in the order in which the user supplied the
     692          # metadata options.
     693          any_display_options = 0
     694          is_display_option = {}
     695          for option in self.display_options:
     696              is_display_option[option[0]] = 1
     697  
     698          for (opt, val) in option_order:
     699              if val and is_display_option.get(opt):
     700                  opt = translate_longopt(opt)
     701                  value = getattr(self.metadata, "get_" + opt)()
     702                  if opt in ['keywords', 'platforms']:
     703                      print(','.join(value))
     704                  elif opt in ('classifiers', 'provides', 'requires', 'obsoletes'):
     705                      print('\n'.join(value))
     706                  else:
     707                      print(value)
     708                  any_display_options = 1
     709  
     710          return any_display_options
     711  
     712      def print_command_list(self, commands, header, max_length):
     713          """Print a subset of the list of all commands -- used by
     714          'print_commands()'.
     715          """
     716          print(header + ":")
     717  
     718          for cmd in commands:
     719              klass = self.cmdclass.get(cmd)
     720              if not klass:
     721                  klass = self.get_command_class(cmd)
     722              try:
     723                  description = klass.description
     724              except AttributeError:
     725                  description = "(no description available)"
     726  
     727              print("  %-*s  %s" % (max_length, cmd, description))
     728  
     729      def print_commands(self):
     730          """Print out a help message listing all available commands with a
     731          description of each.  The list is divided into "standard commands"
     732          (listed in distutils.command.__all__) and "extra commands"
     733          (mentioned in self.cmdclass, but not a standard command).  The
     734          descriptions come from the command class attribute
     735          'description'.
     736          """
     737          import distutils.command
     738  
     739          std_commands = distutils.command.__all__
     740          is_std = {}
     741          for cmd in std_commands:
     742              is_std[cmd] = 1
     743  
     744          extra_commands = []
     745          for cmd in self.cmdclass.keys():
     746              if not is_std.get(cmd):
     747                  extra_commands.append(cmd)
     748  
     749          max_length = 0
     750          for cmd in std_commands + extra_commands:
     751              if len(cmd) > max_length:
     752                  max_length = len(cmd)
     753  
     754          self.print_command_list(std_commands, "Standard commands", max_length)
     755          if extra_commands:
     756              print()
     757              self.print_command_list(extra_commands, "Extra commands", max_length)
     758  
     759      def get_command_list(self):
     760          """Get a list of (command, description) tuples.
     761          The list is divided into "standard commands" (listed in
     762          distutils.command.__all__) and "extra commands" (mentioned in
     763          self.cmdclass, but not a standard command).  The descriptions come
     764          from the command class attribute 'description'.
     765          """
     766          # Currently this is only used on Mac OS, for the Mac-only GUI
     767          # Distutils interface (by Jack Jansen)
     768          import distutils.command
     769  
     770          std_commands = distutils.command.__all__
     771          is_std = {}
     772          for cmd in std_commands:
     773              is_std[cmd] = 1
     774  
     775          extra_commands = []
     776          for cmd in self.cmdclass.keys():
     777              if not is_std.get(cmd):
     778                  extra_commands.append(cmd)
     779  
     780          rv = []
     781          for cmd in std_commands + extra_commands:
     782              klass = self.cmdclass.get(cmd)
     783              if not klass:
     784                  klass = self.get_command_class(cmd)
     785              try:
     786                  description = klass.description
     787              except AttributeError:
     788                  description = "(no description available)"
     789              rv.append((cmd, description))
     790          return rv
     791  
     792      # -- Command class/object methods ----------------------------------
     793  
     794      def get_command_packages(self):
     795          """Return a list of packages from which commands are loaded."""
     796          pkgs = self.command_packages
     797          if not isinstance(pkgs, list):
     798              if pkgs is None:
     799                  pkgs = ''
     800              pkgs = [pkg.strip() for pkg in pkgs.split(',') if pkg != '']
     801              if "distutils.command" not in pkgs:
     802                  pkgs.insert(0, "distutils.command")
     803              self.command_packages = pkgs
     804          return pkgs
     805  
     806      def get_command_class(self, command):
     807          """Return the class that implements the Distutils command named by
     808          'command'.  First we check the 'cmdclass' dictionary; if the
     809          command is mentioned there, we fetch the class object from the
     810          dictionary and return it.  Otherwise we load the command module
     811          ("distutils.command." + command) and fetch the command class from
     812          the module.  The loaded class is also stored in 'cmdclass'
     813          to speed future calls to 'get_command_class()'.
     814  
     815          Raises DistutilsModuleError if the expected module could not be
     816          found, or if that module does not define the expected class.
     817          """
     818          klass = self.cmdclass.get(command)
     819          if klass:
     820              return klass
     821  
     822          for pkgname in self.get_command_packages():
     823              module_name = "{}.{}".format(pkgname, command)
     824              klass_name = command
     825  
     826              try:
     827                  __import__(module_name)
     828                  module = sys.modules[module_name]
     829              except ImportError:
     830                  continue
     831  
     832              try:
     833                  klass = getattr(module, klass_name)
     834              except AttributeError:
     835                  raise DistutilsModuleError(
     836                      "invalid command '%s' (no class '%s' in module '%s')"
     837                      % (command, klass_name, module_name)
     838                  )
     839  
     840              self.cmdclass[command] = klass
     841              return klass
     842  
     843          raise DistutilsModuleError("invalid command '%s'" % command)
     844  
     845      def get_command_obj(self, command, create=1):
     846          """Return the command object for 'command'.  Normally this object
     847          is cached on a previous call to 'get_command_obj()'; if no command
     848          object for 'command' is in the cache, then we either create and
     849          return it (if 'create' is true) or return None.
     850          """
     851          cmd_obj = self.command_obj.get(command)
     852          if not cmd_obj and create:
     853              if DEBUG:
     854                  self.announce(
     855                      "Distribution.get_command_obj(): "
     856                      "creating '%s' command object" % command
     857                  )
     858  
     859              klass = self.get_command_class(command)
     860              cmd_obj = self.command_obj[command] = klass(self)
     861              self.have_run[command] = 0
     862  
     863              # Set any options that were supplied in config files
     864              # or on the command line.  (NB. support for error
     865              # reporting is lame here: any errors aren't reported
     866              # until 'finalize_options()' is called, which means
     867              # we won't report the source of the error.)
     868              options = self.command_options.get(command)
     869              if options:
     870                  self._set_command_options(cmd_obj, options)
     871  
     872          return cmd_obj
     873  
     874      def _set_command_options(self, command_obj, option_dict=None):  # noqa: C901
     875          """Set the options for 'command_obj' from 'option_dict'.  Basically
     876          this means copying elements of a dictionary ('option_dict') to
     877          attributes of an instance ('command').
     878  
     879          'command_obj' must be a Command instance.  If 'option_dict' is not
     880          supplied, uses the standard option dictionary for this command
     881          (from 'self.command_options').
     882          """
     883          command_name = command_obj.get_command_name()
     884          if option_dict is None:
     885              option_dict = self.get_option_dict(command_name)
     886  
     887          if DEBUG:
     888              self.announce("  setting options for '%s' command:" % command_name)
     889          for (option, (source, value)) in option_dict.items():
     890              if DEBUG:
     891                  self.announce("    {} = {} (from {})".format(option, value, source))
     892              try:
     893                  bool_opts = [translate_longopt(o) for o in command_obj.boolean_options]
     894              except AttributeError:
     895                  bool_opts = []
     896              try:
     897                  neg_opt = command_obj.negative_opt
     898              except AttributeError:
     899                  neg_opt = {}
     900  
     901              try:
     902                  is_string = isinstance(value, str)
     903                  if option in neg_opt and is_string:
     904                      setattr(command_obj, neg_opt[option], not strtobool(value))
     905                  elif option in bool_opts and is_string:
     906                      setattr(command_obj, option, strtobool(value))
     907                  elif hasattr(command_obj, option):
     908                      setattr(command_obj, option, value)
     909                  else:
     910                      raise DistutilsOptionError(
     911                          "error in %s: command '%s' has no such option '%s'"
     912                          % (source, command_name, option)
     913                      )
     914              except ValueError as msg:
     915                  raise DistutilsOptionError(msg)
     916  
     917      def reinitialize_command(self, command, reinit_subcommands=0):
     918          """Reinitializes a command to the state it was in when first
     919          returned by 'get_command_obj()': ie., initialized but not yet
     920          finalized.  This provides the opportunity to sneak option
     921          values in programmatically, overriding or supplementing
     922          user-supplied values from the config files and command line.
     923          You'll have to re-finalize the command object (by calling
     924          'finalize_options()' or 'ensure_finalized()') before using it for
     925          real.
     926  
     927          'command' should be a command name (string) or command object.  If
     928          'reinit_subcommands' is true, also reinitializes the command's
     929          sub-commands, as declared by the 'sub_commands' class attribute (if
     930          it has one).  See the "install" command for an example.  Only
     931          reinitializes the sub-commands that actually matter, ie. those
     932          whose test predicates return true.
     933  
     934          Returns the reinitialized command object.
     935          """
     936          from distutils.cmd import Command
     937  
     938          if not isinstance(command, Command):
     939              command_name = command
     940              command = self.get_command_obj(command_name)
     941          else:
     942              command_name = command.get_command_name()
     943  
     944          if not command.finalized:
     945              return command
     946          command.initialize_options()
     947          command.finalized = 0
     948          self.have_run[command_name] = 0
     949          self._set_command_options(command)
     950  
     951          if reinit_subcommands:
     952              for sub in command.get_sub_commands():
     953                  self.reinitialize_command(sub, reinit_subcommands)
     954  
     955          return command
     956  
     957      # -- Methods that operate on the Distribution ----------------------
     958  
     959      def announce(self, msg, level=log.INFO):
     960          log.log(level, msg)
     961  
     962      def run_commands(self):
     963          """Run each command that was seen on the setup script command line.
     964          Uses the list of commands found and cache of command objects
     965          created by 'get_command_obj()'.
     966          """
     967          for cmd in self.commands:
     968              self.run_command(cmd)
     969  
     970      # -- Methods that operate on its Commands --------------------------
     971  
     972      def run_command(self, command):
     973          """Do whatever it takes to run a command (including nothing at all,
     974          if the command has already been run).  Specifically: if we have
     975          already created and run the command named by 'command', return
     976          silently without doing anything.  If the command named by 'command'
     977          doesn't even have a command object yet, create one.  Then invoke
     978          'run()' on that command object (or an existing one).
     979          """
     980          # Already been here, done that? then return silently.
     981          if self.have_run.get(command):
     982              return
     983  
     984          log.info("running %s", command)
     985          cmd_obj = self.get_command_obj(command)
     986          cmd_obj.ensure_finalized()
     987          cmd_obj.run()
     988          self.have_run[command] = 1
     989  
     990      # -- Distribution query methods ------------------------------------
     991  
     992      def has_pure_modules(self):
     993          return len(self.packages or self.py_modules or []) > 0
     994  
     995      def has_ext_modules(self):
     996          return self.ext_modules and len(self.ext_modules) > 0
     997  
     998      def has_c_libraries(self):
     999          return self.libraries and len(self.libraries) > 0
    1000  
    1001      def has_modules(self):
    1002          return self.has_pure_modules() or self.has_ext_modules()
    1003  
    1004      def has_headers(self):
    1005          return self.headers and len(self.headers) > 0
    1006  
    1007      def has_scripts(self):
    1008          return self.scripts and len(self.scripts) > 0
    1009  
    1010      def has_data_files(self):
    1011          return self.data_files and len(self.data_files) > 0
    1012  
    1013      def is_pure(self):
    1014          return (
    1015              self.has_pure_modules()
    1016              and not self.has_ext_modules()
    1017              and not self.has_c_libraries()
    1018          )
    1019  
    1020      # -- Metadata query methods ----------------------------------------
    1021  
    1022      # If you're looking for 'get_name()', 'get_version()', and so forth,
    1023      # they are defined in a sneaky way: the constructor binds self.get_XXX
    1024      # to self.metadata.get_XXX.  The actual code is in the
    1025      # DistributionMetadata class, below.
    1026  
    1027  
    1028  class ESC[4;38;5;81mDistributionMetadata:
    1029      """Dummy class to hold the distribution meta-data: name, version,
    1030      author, and so forth.
    1031      """
    1032  
    1033      _METHOD_BASENAMES = (
    1034          "name",
    1035          "version",
    1036          "author",
    1037          "author_email",
    1038          "maintainer",
    1039          "maintainer_email",
    1040          "url",
    1041          "license",
    1042          "description",
    1043          "long_description",
    1044          "keywords",
    1045          "platforms",
    1046          "fullname",
    1047          "contact",
    1048          "contact_email",
    1049          "classifiers",
    1050          "download_url",
    1051          # PEP 314
    1052          "provides",
    1053          "requires",
    1054          "obsoletes",
    1055      )
    1056  
    1057      def __init__(self, path=None):
    1058          if path is not None:
    1059              self.read_pkg_file(open(path))
    1060          else:
    1061              self.name = None
    1062              self.version = None
    1063              self.author = None
    1064              self.author_email = None
    1065              self.maintainer = None
    1066              self.maintainer_email = None
    1067              self.url = None
    1068              self.license = None
    1069              self.description = None
    1070              self.long_description = None
    1071              self.keywords = None
    1072              self.platforms = None
    1073              self.classifiers = None
    1074              self.download_url = None
    1075              # PEP 314
    1076              self.provides = None
    1077              self.requires = None
    1078              self.obsoletes = None
    1079  
    1080      def read_pkg_file(self, file):
    1081          """Reads the metadata values from a file object."""
    1082          msg = message_from_file(file)
    1083  
    1084          def _read_field(name):
    1085              value = msg[name]
    1086              if value and value != "UNKNOWN":
    1087                  return value
    1088  
    1089          def _read_list(name):
    1090              values = msg.get_all(name, None)
    1091              if values == []:
    1092                  return None
    1093              return values
    1094  
    1095          metadata_version = msg['metadata-version']
    1096          self.name = _read_field('name')
    1097          self.version = _read_field('version')
    1098          self.description = _read_field('summary')
    1099          # we are filling author only.
    1100          self.author = _read_field('author')
    1101          self.maintainer = None
    1102          self.author_email = _read_field('author-email')
    1103          self.maintainer_email = None
    1104          self.url = _read_field('home-page')
    1105          self.license = _read_field('license')
    1106  
    1107          if 'download-url' in msg:
    1108              self.download_url = _read_field('download-url')
    1109          else:
    1110              self.download_url = None
    1111  
    1112          self.long_description = _read_field('description')
    1113          self.description = _read_field('summary')
    1114  
    1115          if 'keywords' in msg:
    1116              self.keywords = _read_field('keywords').split(',')
    1117  
    1118          self.platforms = _read_list('platform')
    1119          self.classifiers = _read_list('classifier')
    1120  
    1121          # PEP 314 - these fields only exist in 1.1
    1122          if metadata_version == '1.1':
    1123              self.requires = _read_list('requires')
    1124              self.provides = _read_list('provides')
    1125              self.obsoletes = _read_list('obsoletes')
    1126          else:
    1127              self.requires = None
    1128              self.provides = None
    1129              self.obsoletes = None
    1130  
    1131      def write_pkg_info(self, base_dir):
    1132          """Write the PKG-INFO file into the release tree."""
    1133          with open(
    1134              os.path.join(base_dir, 'PKG-INFO'), 'w', encoding='UTF-8'
    1135          ) as pkg_info:
    1136              self.write_pkg_file(pkg_info)
    1137  
    1138      def write_pkg_file(self, file):
    1139          """Write the PKG-INFO format data to a file object."""
    1140          version = '1.0'
    1141          if (
    1142              self.provides
    1143              or self.requires
    1144              or self.obsoletes
    1145              or self.classifiers
    1146              or self.download_url
    1147          ):
    1148              version = '1.1'
    1149  
    1150          # required fields
    1151          file.write('Metadata-Version: %s\n' % version)
    1152          file.write('Name: %s\n' % self.get_name())
    1153          file.write('Version: %s\n' % self.get_version())
    1154  
    1155          def maybe_write(header, val):
    1156              if val:
    1157                  file.write(f"{header}: {val}\n")
    1158  
    1159          # optional fields
    1160          maybe_write("Summary", self.get_description())
    1161          maybe_write("Home-page", self.get_url())
    1162          maybe_write("Author", self.get_contact())
    1163          maybe_write("Author-email", self.get_contact_email())
    1164          maybe_write("License", self.get_license())
    1165          maybe_write("Download-URL", self.download_url)
    1166          maybe_write("Description", rfc822_escape(self.get_long_description() or ""))
    1167          maybe_write("Keywords", ",".join(self.get_keywords()))
    1168  
    1169          self._write_list(file, 'Platform', self.get_platforms())
    1170          self._write_list(file, 'Classifier', self.get_classifiers())
    1171  
    1172          # PEP 314
    1173          self._write_list(file, 'Requires', self.get_requires())
    1174          self._write_list(file, 'Provides', self.get_provides())
    1175          self._write_list(file, 'Obsoletes', self.get_obsoletes())
    1176  
    1177      def _write_list(self, file, name, values):
    1178          values = values or []
    1179          for value in values:
    1180              file.write('{}: {}\n'.format(name, value))
    1181  
    1182      # -- Metadata query methods ----------------------------------------
    1183  
    1184      def get_name(self):
    1185          return self.name or "UNKNOWN"
    1186  
    1187      def get_version(self):
    1188          return self.version or "0.0.0"
    1189  
    1190      def get_fullname(self):
    1191          return "{}-{}".format(self.get_name(), self.get_version())
    1192  
    1193      def get_author(self):
    1194          return self.author
    1195  
    1196      def get_author_email(self):
    1197          return self.author_email
    1198  
    1199      def get_maintainer(self):
    1200          return self.maintainer
    1201  
    1202      def get_maintainer_email(self):
    1203          return self.maintainer_email
    1204  
    1205      def get_contact(self):
    1206          return self.maintainer or self.author
    1207  
    1208      def get_contact_email(self):
    1209          return self.maintainer_email or self.author_email
    1210  
    1211      def get_url(self):
    1212          return self.url
    1213  
    1214      def get_license(self):
    1215          return self.license
    1216  
    1217      get_licence = get_license
    1218  
    1219      def get_description(self):
    1220          return self.description
    1221  
    1222      def get_long_description(self):
    1223          return self.long_description
    1224  
    1225      def get_keywords(self):
    1226          return self.keywords or []
    1227  
    1228      def set_keywords(self, value):
    1229          self.keywords = _ensure_list(value, 'keywords')
    1230  
    1231      def get_platforms(self):
    1232          return self.platforms
    1233  
    1234      def set_platforms(self, value):
    1235          self.platforms = _ensure_list(value, 'platforms')
    1236  
    1237      def get_classifiers(self):
    1238          return self.classifiers or []
    1239  
    1240      def set_classifiers(self, value):
    1241          self.classifiers = _ensure_list(value, 'classifiers')
    1242  
    1243      def get_download_url(self):
    1244          return self.download_url
    1245  
    1246      # PEP 314
    1247      def get_requires(self):
    1248          return self.requires or []
    1249  
    1250      def set_requires(self, value):
    1251          import distutils.versionpredicate
    1252  
    1253          for v in value:
    1254              distutils.versionpredicate.VersionPredicate(v)
    1255          self.requires = list(value)
    1256  
    1257      def get_provides(self):
    1258          return self.provides or []
    1259  
    1260      def set_provides(self, value):
    1261          value = [v.strip() for v in value]
    1262          for v in value:
    1263              import distutils.versionpredicate
    1264  
    1265              distutils.versionpredicate.split_provision(v)
    1266          self.provides = value
    1267  
    1268      def get_obsoletes(self):
    1269          return self.obsoletes or []
    1270  
    1271      def set_obsoletes(self, value):
    1272          import distutils.versionpredicate
    1273  
    1274          for v in value:
    1275              distutils.versionpredicate.VersionPredicate(v)
    1276          self.obsoletes = list(value)
    1277  
    1278  
    1279  def fix_help_options(options):
    1280      """Convert a 4-tuple 'help_options' list as found in various command
    1281      classes to the 3-tuple form required by FancyGetopt.
    1282      """
    1283      new_options = []
    1284      for help_tuple in options:
    1285          new_options.append(help_tuple[0:3])
    1286      return new_options