From bd5c22d484734f7c1b77e16c91a10c7a44fa6c8a Mon Sep 17 00:00:00 2001 From: Waf Project Date: Sat, 27 Apr 2024 19:02:00 +0200 Subject: [PATCH] Move from optparse to argparse --- ChangeLog | 1 + waflib/Options.py | 160 ++++++++++++++------------------ waflib/Scripting.py | 2 +- waflib/Tools/gnu_dirs.py | 7 +- waflib/extras/cfg_altoptions.py | 2 +- waflib/extras/review.py | 2 +- waflib/extras/use_config.py | 2 +- 7 files changed, 79 insertions(+), 97 deletions(-) diff --git a/ChangeLog b/ChangeLog index 68518f26..846254d9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,7 @@ CHANGES IN WAF 2.1.0 + Added wafcache + waf_unit_test: Added task in returned tuples, custom reports should be adapted ~ Ensure ConfigurationContext.load and Context.load definitions are consistent and remove unused *k parameters +~ Remove the dependency on optparse and use argparse instead - Remove waflib.Runner.PriorityTasks.appendleft - Remove waflib.Task.TaskBase - Remove the upper class of waflib.Task.Task (metaclass syntax) diff --git a/waflib/Options.py b/waflib/Options.py index c1ee381a..86b3d3f5 100644 --- a/waflib/Options.py +++ b/waflib/Options.py @@ -10,10 +10,15 @@ Provides default and command-line options, as well the command that reads the ``options`` wscript function. """ -import os, tempfile, optparse, sys, re +import os, tempfile, argparse, sys, re from waflib import Logs, Utils, Context, Errors -options = optparse.Values() + +class OptionValues: + def __str__(self): + return str(self.__dict__) + +options = OptionValues() """ A global dictionary representing user-provided command-line options:: @@ -26,59 +31,40 @@ List of commands to execute extracted from the command-line. This list is consumed during the execution by :py:func:`waflib.Scripting.run_commands`. """ -envvars = [] -""" -List of environment variable declarations placed after the Waf executable name. -These are detected by searching for "=" in the remaining arguments. -You probably do not want to use this. -""" - lockfile = os.environ.get('WAFLOCK', '.lock-waf_%s_build' % sys.platform) """ Name of the lock file that marks a project as configured """ -class opt_parser(optparse.OptionParser): +class ArgParser(argparse.ArgumentParser): """ Command-line options parser. """ - def __init__(self, ctx, allow_unknown=False): - optparse.OptionParser.__init__(self, conflict_handler='resolve', add_help_option=False, - version='%s %s (%s)' % (Context.WAFNAME, Context.WAFVERSION, Context.WAFREVISION)) - self.formatter.width = Logs.get_term_cols() + def __init__(self, ctx): + argparse.ArgumentParser.__init__(self, add_help=False) self.ctx = ctx - self.allow_unknown = allow_unknown + self.usage = self.get_usage() - def _process_args(self, largs, rargs, values): - """ - Custom _process_args to allow unknown options according to the allow_unknown status - """ - while rargs: - try: - optparse.OptionParser._process_args(self,largs,rargs,values) - except (optparse.BadOptionError, optparse.AmbiguousOptionError) as e: - if self.allow_unknown: - largs.append(e.opt_str) - else: - self.error(str(e)) - def _process_long_opt(self, rargs, values): - # --custom-option=-ftxyz is interpreted as -f -t... see #2280 - if self.allow_unknown: - back = [] + rargs - try: - optparse.OptionParser._process_long_opt(self, rargs, values) - except optparse.BadOptionError: - while rargs: - rargs.pop() - rargs.extend(back) - rargs.pop(0) - raise - else: - optparse.OptionParser._process_long_opt(self, rargs, values) + def _get_formatter(self): + """Initialize the argument parser to the adequate terminal width""" + return self.formatter_class(prog=self.prog, width=Logs.get_term_cols()) - def print_usage(self, file=None): - return self.print_help(file) + def get_option(self, name): + if name in self._option_string_actions: + return self._option_string_actions[name] + + def remove_option(self, name): + if name in self._option_string_actions: + action = self._option_string_actions[name] + self._remove_action(action) + action.option_strings.remove(name) + self._option_string_actions.pop(name, None) + for group in self._action_groups: + try: + group._group_actions.remove(action) + except ValueError: + pass def get_usage(self): """ @@ -100,7 +86,7 @@ class opt_parser(optparse.OptionParser): continue if type(v) is type(Context.create_context): - if v.__doc__ and not k.startswith('_'): + if v.__doc__ and len(v.__doc__.splitlines()) < 3 and not k.startswith('_'): cmds_str[k] = v.__doc__ just = 0 @@ -129,8 +115,8 @@ class OptionsContext(Context.Context): def __init__(self, **kw): super(OptionsContext, self).__init__(**kw) - self.parser = opt_parser(self) - """Instance of :py:class:`waflib.Options.opt_parser`""" + self.parser = ArgParser(self) + """Instance of :py:class:`waflib.Options.ArgParser`""" self.option_groups = {} @@ -142,23 +128,22 @@ class OptionsContext(Context.Context): elif os.environ.get('CLICOLOR_FORCE', '') == '1': color = 'yes' p('-c', '--color', dest='colors', default=color, action='store', help='whether to use colors (yes/no/auto) [default: auto]', choices=('yes', 'no', 'auto')) - p('-j', '--jobs', dest='jobs', default=jobs, type='int', help='amount of parallel jobs (%r)' % jobs) + p('-j', '--jobs', dest='jobs', default=jobs, type=int, help='amount of parallel jobs (%r)' % jobs) p('-k', '--keep', dest='keep', default=0, action='count', help='continue despite errors (-kk to try harder)') p('-v', '--verbose', dest='verbose', default=0, action='count', help='verbosity level -v -vv or -vvv [default: 0]') p('--zones', dest='zones', default='', action='store', help='debugging zones (task_gen, deps, tasks, etc)') - p('--profile', dest='profile', default=0, action='store_true', help=optparse.SUPPRESS_HELP) - p('--pdb', dest='pdb', default=0, action='store_true', help=optparse.SUPPRESS_HELP) + p('--profile', dest='profile', default=0, action='store_true', help=argparse.SUPPRESS) + p('--pdb', dest='pdb', default=0, action='store_true', help=argparse.SUPPRESS) p('-h', '--help', dest='whelp', default=0, action='store_true', help="show this help message and exit") gr = self.add_option_group('Configuration options') - self.option_groups['configure options'] = gr gr.add_option('-o', '--out', action='store', default='', help='build dir for the project', dest='out') gr.add_option('-t', '--top', action='store', default='', help='src dir for the project', dest='top') - gr.add_option('--no-lock-in-run', action='store_true', default=os.environ.get('NO_LOCK_IN_RUN', ''), help=optparse.SUPPRESS_HELP, dest='no_lock_in_run') - gr.add_option('--no-lock-in-out', action='store_true', default=os.environ.get('NO_LOCK_IN_OUT', ''), help=optparse.SUPPRESS_HELP, dest='no_lock_in_out') - gr.add_option('--no-lock-in-top', action='store_true', default=os.environ.get('NO_LOCK_IN_TOP', ''), help=optparse.SUPPRESS_HELP, dest='no_lock_in_top') + gr.add_option('--no-lock-in-run', action='store_true', default=os.environ.get('NO_LOCK_IN_RUN', ''), help=argparse.SUPPRESS, dest='no_lock_in_run') + gr.add_option('--no-lock-in-out', action='store_true', default=os.environ.get('NO_LOCK_IN_OUT', ''), help=argparse.SUPPRESS, dest='no_lock_in_out') + gr.add_option('--no-lock-in-top', action='store_true', default=os.environ.get('NO_LOCK_IN_TOP', ''), help=argparse.SUPPRESS, dest='no_lock_in_top') default_prefix = getattr(Context.g_module, 'default_prefix', os.environ.get('PREFIX')) if not default_prefix: @@ -173,18 +158,15 @@ class OptionsContext(Context.Context): gr.add_option('--libdir', dest='libdir', help='libdir') gr = self.add_option_group('Build and installation options') - self.option_groups['build and install options'] = gr gr.add_option('-p', '--progress', dest='progress_bar', default=0, action='count', help= '-p: progress bar; -pp: ide output') gr.add_option('--targets', dest='targets', default='', action='store', help='task generators, e.g. "target1,target2"') gr = self.add_option_group('Step options') - self.option_groups['step options'] = gr gr.add_option('--files', dest='files', default='', action='store', help='files to process, by regexp, e.g. "*/main.c,*/test/main.o"') default_destdir = os.environ.get('DESTDIR', '') gr = self.add_option_group('Installation and uninstallation options') - self.option_groups['install/uninstall options'] = gr gr.add_option('--destdir', help='installation root [default: %r]' % default_destdir, default=default_destdir, dest='destdir') gr.add_option('-f', '--force', dest='force', default=False, action='store_true', help='force file installation') gr.add_option('--distcheck-args', metavar='ARGS', help='arguments to pass to distcheck', default=None, action='store') @@ -227,16 +209,22 @@ class OptionsContext(Context.Context): return count def add_option(self, *k, **kw): + if 'type' in kw and kw['type'] == 'int': + Logs.warn('Invalid "type=str" in add_option (must be a class, not a string)') + kw['type'] = int + return self.add_argument(*k, **kw) + + def add_argument(self, *k, **kw): """ - Wraps ``optparse.add_option``:: + Wraps ``argparse.add_argument``:: def options(ctx): ctx.add_option('-u', '--use', dest='use', default=False, action='store_true', help='a boolean option') - :rtype: optparse option object + :rtype: argparse option object """ - return self.parser.add_option(*k, **kw) + return self.parser.add_argument(*k, **kw) def add_option_group(self, *k, **kw): """ @@ -248,11 +236,11 @@ class OptionsContext(Context.Context): :rtype: optparse option group object """ - try: - gr = self.option_groups[k[0]] - except KeyError: - gr = self.parser.add_option_group(*k, **kw) - self.option_groups[k[0]] = gr + gr = self.get_option_group(k[0]) + if not gr: + gr = self.parser.add_argument_group(*k, **kw) + gr.add_option = gr.add_argument + self.option_groups[k[0]] = gr return gr def get_option_group(self, opt_str): @@ -269,7 +257,7 @@ class OptionsContext(Context.Context): try: return self.option_groups[opt_str] except KeyError: - for group in self.parser.option_groups: + for group in self.parser._action_groups: if group.title == opt_str: return group return None @@ -287,15 +275,13 @@ class OptionsContext(Context.Context): """ Just parse the arguments """ - self.parser.allow_unknown = allow_unknown - (options, leftover_args) = self.parser.parse_args(args=_args) - envvars = [] + (options, leftover_args) = self.parser.parse_known_args(args=_args) commands = [] for arg in leftover_args: - if '=' in arg: - envvars.append(arg) - elif arg != 'options': - commands.append(arg) + if not allow_unknown and arg.startswith('-'): + self.parser.print_help() + raise Errors.WafError('Unknown option: %r' % arg) + commands.append(arg) if options.jobs < 1: options.jobs = 1 @@ -304,22 +290,9 @@ class OptionsContext(Context.Context): if getattr(options, name, None): path = self.sanitize_path(getattr(options, name), cwd) setattr(options, name, path) - return options, commands, envvars + return options, commands - def init_module_vars(self, arg_options, arg_commands, arg_envvars): - options.__dict__.clear() - del commands[:] - del envvars[:] - - options.__dict__.update(arg_options.__dict__) - commands.extend(arg_commands) - envvars.extend(arg_envvars) - - for var in envvars: - (name, value) = var.split('=', 1) - os.environ[name.strip()] = value - - def init_logs(self, options, commands, envvars): + def init_logs(self, options, commands): Logs.verbose = options.verbose if options.verbose >= 1: self.load('errcheck') @@ -339,15 +312,20 @@ class OptionsContext(Context.Context): def parse_args(self, _args=None): """ Parses arguments from a list which is not necessarily the command-line. - Initializes the module variables options, commands and envvars + Initializes the module variables options and commands If help is requested, prints it and exit the application :param _args: arguments :type _args: list of strings """ - options, commands, envvars = self.parse_cmd_args(_args) - self.init_logs(options, commands, envvars) - self.init_module_vars(options, commands, envvars) + arg_options, arg_commands = self.parse_cmd_args(_args) + self.init_logs(arg_options, commands) + + options.__dict__.clear() + del commands[:] + + options.__dict__.update(arg_options.__dict__) + commands.extend(arg_commands) def execute(self): """ diff --git a/waflib/Scripting.py b/waflib/Scripting.py index a80cb367..6b7b8c7b 100644 --- a/waflib/Scripting.py +++ b/waflib/Scripting.py @@ -50,7 +50,7 @@ def waf_entry_point(current_directory, version, wafdir): sys.argv.pop(1) ctx = Context.create_context('options') - (options, commands, env) = ctx.parse_cmd_args(allow_unknown=True) + (options, commands) = ctx.parse_cmd_args(allow_unknown=True) if options.top: start_dir = Context.run_dir = Context.top_dir = options.top no_climb = True diff --git a/waflib/Tools/gnu_dirs.py b/waflib/Tools/gnu_dirs.py index 2847071d..1f09e260 100644 --- a/waflib/Tools/gnu_dirs.py +++ b/waflib/Tools/gnu_dirs.py @@ -114,7 +114,7 @@ def options(opt): option = opt.parser.get_option(k) if option: opt.parser.remove_option(k) - inst_dir.add_option(option) + inst_dir.add_argument(k, dest=option.dest, help=option.help, default=option.default) inst_dir.add_option('--exec-prefix', help = 'installation prefix for binaries [PREFIX]', @@ -125,7 +125,10 @@ def options(opt): for name, help, default in _options: option_name = '--' + name + + opt.parser.remove_option(option_name) + str_default = default str_help = '%s [%s]' % (help, re.sub(r'\$\{([^}]+)\}', r'\1', str_default)) - dirs_options.add_option(option_name, help=str_help, default='', dest=name.upper()) + dirs_options.add_option(option_name, help=str_help, default='', dest=name) diff --git a/waflib/extras/cfg_altoptions.py b/waflib/extras/cfg_altoptions.py index 47b1189f..0db115bd 100644 --- a/waflib/extras/cfg_altoptions.py +++ b/waflib/extras/cfg_altoptions.py @@ -42,7 +42,7 @@ def name_to_dest(x): def options(opt): def x(opt, param): dest = name_to_dest(param) - gr = opt.get_option_group("configure options") + gr = opt.get_option_group("Configuration options") gr.add_option('--%s-root' % dest, help="path containing include and lib subfolders for %s" \ % param, diff --git a/waflib/extras/review.py b/waflib/extras/review.py index 561e0621..5f15c3d0 100644 --- a/waflib/extras/review.py +++ b/waflib/extras/review.py @@ -116,7 +116,7 @@ class OptionsReview(Options.OptionsContext): their default value from their optparse object and store them into the review dictionaries. """ - gr = self.get_option_group('configure options') + gr = self.get_option_group('Configuration options') for opt in gr.option_list: if opt.action != 'store' or opt.dest in ("out", "top"): continue diff --git a/waflib/extras/use_config.py b/waflib/extras/use_config.py index ef5129f2..74101fd9 100644 --- a/waflib/extras/use_config.py +++ b/waflib/extras/use_config.py @@ -88,7 +88,7 @@ DEFAULT_DIR = 'wafcfg' sys.path.append(osp.abspath(DEFAULT_DIR)) def options(self): - group = self.add_option_group('configure options') + group = self.add_option_group('Configuration options') group.add_option('--download', dest='download', default=False, action='store_true', help='try to download the tools if missing') group.add_option('--use-config', action='store', default=None,