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