codekingpro/portable-devtools
114k
1"""distutils.core2 3The only module that needs to be imported to use the Distutils; provides4the 'setup' function (which is to be called from the setup script). Also5indirectly provides the Distribution and Command classes, although they are6really defined in distutils.dist and distutils.cmd.7"""8 9from __future__ import annotations10 11import os12import sys13import tokenize14from collections.abc import Iterable15 16from .cmd import Command17from .debug import DEBUG18 19# Mainly import these so setup scripts can "from distutils.core import" them.20from .dist import Distribution21from .errors import (22 CCompilerError,23 DistutilsArgError,24 DistutilsError,25 DistutilsSetupError,26)27from .extension import Extension28 29__all__ = ['Distribution', 'Command', 'Extension', 'setup']30 31# This is a barebones help message generated displayed when the user32# runs the setup script with no arguments at all. More useful help33# is generated with various --help options: global help, list commands,34# and per-command help.35USAGE = """\36usage: %(script)s [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]37 or: %(script)s --help [cmd1 cmd2 ...]38 or: %(script)s --help-commands39 or: %(script)s cmd --help40"""41 42 43def gen_usage(script_name):44 script = os.path.basename(script_name)45 return USAGE % locals()46 47 48# Some mild magic to control the behaviour of 'setup()' from 'run_setup()'.49_setup_stop_after = None50_setup_distribution = None51 52# Legal keyword arguments for the setup() function53setup_keywords = (54 'distclass',55 'script_name',56 'script_args',57 'options',58 'name',59 'version',60 'author',61 'author_email',62 'maintainer',63 'maintainer_email',64 'url',65 'license',66 'description',67 'long_description',68 'keywords',69 'platforms',70 'classifiers',71 'download_url',72 'requires',73 'provides',74 'obsoletes',75)76 77# Legal keyword arguments for the Extension constructor78extension_keywords = (79 'name',80 'sources',81 'include_dirs',82 'define_macros',83 'undef_macros',84 'library_dirs',85 'libraries',86 'runtime_library_dirs',87 'extra_objects',88 'extra_compile_args',89 'extra_link_args',90 'swig_opts',91 'export_symbols',92 'depends',93 'language',94)95 96 97def setup(**attrs): # noqa: C90198 """The gateway to the Distutils: do everything your setup script needs99 to do, in a highly flexible and user-driven way. Briefly: create a100 Distribution instance; find and parse config files; parse the command101 line; run each Distutils command found there, customized by the options102 supplied to 'setup()' (as keyword arguments), in config files, and on103 the command line.104 105 The Distribution instance might be an instance of a class supplied via106 the 'distclass' keyword argument to 'setup'; if no such class is107 supplied, then the Distribution class (in dist.py) is instantiated.108 All other arguments to 'setup' (except for 'cmdclass') are used to set109 attributes of the Distribution instance.110 111 The 'cmdclass' argument, if supplied, is a dictionary mapping command112 names to command classes. Each command encountered on the command line113 will be turned into a command class, which is in turn instantiated; any114 class found in 'cmdclass' is used in place of the default, which is115 (for command 'foo_bar') class 'foo_bar' in module116 'distutils.command.foo_bar'. The command class must provide a117 'user_options' attribute which is a list of option specifiers for118 'distutils.fancy_getopt'. Any command-line options between the current119 and the next command are used to set attributes of the current command120 object.121 122 When the entire command-line has been successfully parsed, calls the123 'run()' method on each command object in turn. This method will be124 driven entirely by the Distribution object (which each command object125 has a reference to, thanks to its constructor), and the126 command-specific options that became attributes of each command127 object.128 """129 130 global _setup_stop_after, _setup_distribution131 132 # Determine the distribution class -- either caller-supplied or133 # our Distribution (see below).134 klass = attrs.get('distclass')135 if klass:136 attrs.pop('distclass')137 else:138 klass = Distribution139 140 if 'script_name' not in attrs:141 attrs['script_name'] = os.path.basename(sys.argv[0])142 if 'script_args' not in attrs:143 attrs['script_args'] = sys.argv[1:]144 145 # Create the Distribution instance, using the remaining arguments146 # (ie. everything except distclass) to initialize it147 try:148 _setup_distribution = dist = klass(attrs)149 except DistutilsSetupError as msg:150 if 'name' not in attrs:151 raise SystemExit(f"error in setup command: {msg}")152 else:153 raise SystemExit("error in {} setup command: {}".format(attrs['name'], msg))154 155 if _setup_stop_after == "init":156 return dist157 158 # Find and parse the config file(s): they will override options from159 # the setup script, but be overridden by the command line.160 dist.parse_config_files()161 162 if DEBUG:163 print("options (after parsing config files):")164 dist.dump_option_dicts()165 166 if _setup_stop_after == "config":167 return dist168 169 # Parse the command line and override config files; any170 # command-line errors are the end user's fault, so turn them into171 # SystemExit to suppress tracebacks.172 try:173 ok = dist.parse_command_line()174 except DistutilsArgError as msg:175 raise SystemExit(gen_usage(dist.script_name) + f"\nerror: {msg}")176 177 if DEBUG:178 print("options (after parsing command line):")179 dist.dump_option_dicts()180 181 if _setup_stop_after == "commandline":182 return dist183 184 # And finally, run all the commands found on the command line.185 if ok:186 return run_commands(dist)187 188 return dist189 190 191# setup ()192 193 194def run_commands(dist):195 """Given a Distribution object run all the commands,196 raising ``SystemExit`` errors in the case of failure.197 198 This function assumes that either ``sys.argv`` or ``dist.script_args``199 is already set accordingly.200 """201 try:202 dist.run_commands()203 except KeyboardInterrupt:204 raise SystemExit("interrupted")205 except OSError as exc:206 if DEBUG:207 sys.stderr.write(f"error: {exc}\n")208 raise209 else:210 raise SystemExit(f"error: {exc}")211 212 except (DistutilsError, CCompilerError) as msg:213 if DEBUG:214 raise215 else:216 raise SystemExit("error: " + str(msg))217 218 return dist219 220 221def run_setup(script_name, script_args: Iterable[str] | None = None, stop_after="run"):222 """Run a setup script in a somewhat controlled environment, and223 return the Distribution instance that drives things. This is useful224 if you need to find out the distribution meta-data (passed as225 keyword args from 'script' to 'setup()', or the contents of the226 config files or command-line.227 228 'script_name' is a file that will be read and run with 'exec()';229 'sys.argv[0]' will be replaced with 'script' for the duration of the230 call. 'script_args' is a list of strings; if supplied,231 'sys.argv[1:]' will be replaced by 'script_args' for the duration of232 the call.233 234 'stop_after' tells 'setup()' when to stop processing; possible235 values:236 init237 stop after the Distribution instance has been created and238 populated with the keyword arguments to 'setup()'239 config240 stop after config files have been parsed (and their data241 stored in the Distribution instance)242 commandline243 stop after the command-line ('sys.argv[1:]' or 'script_args')244 have been parsed (and the data stored in the Distribution)245 run [default]246 stop after all commands have been run (the same as if 'setup()'247 had been called in the usual way248 249 Returns the Distribution instance, which provides all information250 used to drive the Distutils.251 """252 if stop_after not in ('init', 'config', 'commandline', 'run'):253 raise ValueError(f"invalid value for 'stop_after': {stop_after!r}")254 255 global _setup_stop_after, _setup_distribution256 _setup_stop_after = stop_after257 258 save_argv = sys.argv.copy()259 g = {'__file__': script_name, '__name__': '__main__'}260 try:261 try:262 sys.argv[0] = script_name263 if script_args is not None:264 sys.argv[1:] = script_args265 # tokenize.open supports automatic encoding detection266 with tokenize.open(script_name) as f:267 code = f.read().replace(r'\r\n', r'\n')268 exec(code, g)269 finally:270 sys.argv = save_argv271 _setup_stop_after = None272 except SystemExit:273 # Hmm, should we do something if exiting with a non-zero code274 # (ie. error)?275 pass276 277 if _setup_distribution is None:278 raise RuntimeError(279 "'distutils.core.setup()' was never called -- "280 f"perhaps '{script_name}' is not a Distutils setup script?"281 )282 283 # I wonder if the setup script's namespace -- g and l -- would be of284 # any interest to callers?285 # print "_setup_distribution:", _setup_distribution286 return _setup_distribution287 288 289# run_setup ()290 