From ac733478eef84b52cba436ea06627d9d6c5eb85f Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 8 Dec 2023 10:46:23 -0800 Subject: [PATCH] using a typing_extensions.Protocol for command line arguments This allows static analysis tools to analyze. --- mesonbuild/coredata.py | 51 +++++++++++++++++++++++---- mesonbuild/environment.py | 5 ++- mesonbuild/interpreter/interpreter.py | 4 +-- mesonbuild/mconf.py | 4 +-- mesonbuild/msetup.py | 15 ++++---- 5 files changed, 58 insertions(+), 21 deletions(-) diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 20bba09e5402..61602ccbdf31 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -40,6 +40,8 @@ import typing as T if T.TYPE_CHECKING: + from typing_extensions import Protocol + from . import dependencies from .compilers.compilers import Compiler, CompileResult, RunResult, CompileCheckMode from .dependencies.detect import TV_DepID @@ -47,6 +49,41 @@ from .mesonlib import FileOrString from .cmake.traceparser import CMakeCacheEntry + class CommandLineOptions(Protocol): + + """Representation of command line options from Meson setup and Meson + configure. + + :param builddir: The path to the build directory + :param clearcache: whether to wipe caches + :param cmd_line_options: Raw options converted into `OptionKey: str` + pairs + :param cross_file: List of cross files passed + :param fatal_warnings: whether Meson warnings should cause configuration + to stop + :param native_file: List of native files pass + :param pager: Should Meson start a pager for it's configure output? + :param profile: whether to profile the meson configuration step + :param project_options: A list of row option passed on the command line + :param reconfigure: should Meson should force a fall reconfigure even if + it's otherwise unnecessary + :param sourcedir: The path to the source directory + :param wipe: whether to wipe the build dir + """ + + builddir: str + clearcache: bool + cmd_line_options: T.Dict[OptionKey, str] + cross_file: T.List[str] + fatal_warnings: bool + native_file: T.List[str] + pager: bool + profile: bool + projectoptions: T.List[str] + reconfigure: bool + sourcedir: str + wipe: bool + OptionDictType = T.Union[T.Dict[str, 'UserOption[T.Any]'], 'OptionsView'] MutableKeyedOptionDictType = T.Dict['OptionKey', 'UserOption[T.Any]'] KeyedOptionDictType = T.Union[MutableKeyedOptionDictType, 'OptionsView'] @@ -555,7 +592,7 @@ def languages(self) -> T.Set[str]: class CoreData: - def __init__(self, options: argparse.Namespace, scratch_dir: str, meson_command: T.List[str]): + def __init__(self, options: CommandLineOptions, scratch_dir: str, meson_command: T.List[str]): self.lang_guids = { 'default': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942', 'c': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942', @@ -596,7 +633,7 @@ def __init__(self, options: argparse.Namespace, scratch_dir: str, meson_command: self.init_builtins('') @staticmethod - def __load_config_files(options: argparse.Namespace, scratch_dir: str, ftype: str) -> T.List[str]: + def __load_config_files(options: CommandLineOptions, scratch_dir: str, ftype: str) -> T.List[str]: # Need to try and make the passed filenames absolute because when the # files are parsed later we'll have chdir()d. if ftype == 'cross': @@ -1128,7 +1165,7 @@ def parse_machine_files(filenames: T.List[str], sourcedir: str): def get_cmd_line_file(build_dir: str) -> str: return os.path.join(build_dir, 'meson-private', 'cmd_line.txt') -def read_cmd_line_file(build_dir: str, options: argparse.Namespace) -> None: +def read_cmd_line_file(build_dir: str, options: CommandLineOptions) -> None: filename = get_cmd_line_file(build_dir) if not os.path.isfile(filename): return @@ -1150,7 +1187,7 @@ def read_cmd_line_file(build_dir: str, options: argparse.Namespace) -> None: # literal_eval to get it into the list of strings. options.native_file = ast.literal_eval(properties.get('native_file', '[]')) -def write_cmd_line_file(build_dir: str, options: argparse.Namespace) -> None: +def write_cmd_line_file(build_dir: str, options: CommandLineOptions) -> None: filename = get_cmd_line_file(build_dir) config = CmdLineFileParser() @@ -1165,7 +1202,7 @@ def write_cmd_line_file(build_dir: str, options: argparse.Namespace) -> None: with open(filename, 'w', encoding='utf-8') as f: config.write(f) -def update_cmd_line_file(build_dir: str, options: argparse.Namespace): +def update_cmd_line_file(build_dir: str, options: CommandLineOptions): filename = get_cmd_line_file(build_dir) config = CmdLineFileParser() config.read(filename) @@ -1173,7 +1210,7 @@ def update_cmd_line_file(build_dir: str, options: argparse.Namespace): with open(filename, 'w', encoding='utf-8') as f: config.write(f) -def format_cmd_line_options(options: argparse.Namespace) -> str: +def format_cmd_line_options(options: CommandLineOptions) -> str: cmdline = ['-D{}={}'.format(str(k), v) for k, v in options.cmd_line_options.items()] if options.cross_file: cmdline += [f'--cross-file={f}' for f in options.cross_file] @@ -1231,7 +1268,7 @@ def create_options_dict(options: T.List[str], subproject: str = '') -> T.Dict[Op result[k] = value return result -def parse_cmd_line_options(args: argparse.Namespace) -> None: +def parse_cmd_line_options(args: CommandLineOptions) -> None: args.cmd_line_options = create_options_dict(args.projectoptions) # Merge builtin options set with --option into the dict. diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 2ba20548f481..9692fa57ba72 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -45,7 +45,6 @@ from mesonbuild import envconfig if T.TYPE_CHECKING: - import argparse from configparser import ConfigParser from .compilers import Compiler @@ -495,7 +494,7 @@ class Environment: log_dir = 'meson-logs' info_dir = 'meson-info' - def __init__(self, source_dir: str, build_dir: str, options: 'argparse.Namespace') -> None: + def __init__(self, source_dir: str, build_dir: str, options: coredata.CommandLineOptions) -> None: self.source_dir = source_dir self.build_dir = build_dir # Do not try to create build directories when build_dir is none. @@ -790,7 +789,7 @@ def _set_default_properties_from_env(self) -> None: self.properties[for_machine].properties.setdefault(name, p_env) break - def create_new_coredata(self, options: 'argparse.Namespace') -> None: + def create_new_coredata(self, options: coredata.CommandLineOptions) -> None: # WARNING: Don't use any values from coredata in __init__. It gets # re-initialized with project options by the interpreter during # build file parsing. diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index e885010b23a1..024172b4839c 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -114,8 +114,6 @@ import copy if T.TYPE_CHECKING: - import argparse - from . import kwargs as kwtypes from ..backend.backends import Backend from ..interpreterbase.baseobjects import InterpreterObject, TYPE_var, TYPE_kwargs @@ -278,7 +276,7 @@ def __init__( ast: T.Optional[mparser.CodeBlockNode] = None, is_translated: bool = False, relaxations: T.Optional[T.Set[InterpreterRuleRelaxation]] = None, - user_defined_options: T.Optional['argparse.Namespace'] = None, + user_defined_options: T.Optional[coredata.CommandLineOptions] = None, ) -> None: super().__init__(_build.environment.get_source_dir(), subdir, subproject) self.active_projectname = '' diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py index 04f6d5bcbd5b..ce9735755ab5 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -303,7 +303,7 @@ def print_nondefault_buildtype_options(self) -> None: for m in mismatching: mlog.log(f'{m[0]:21}{m[1]:10}{m[2]:10}') -def run_impl(options: argparse.Namespace, builddir: str) -> int: +def run_impl(options: coredata.CommandLineOptions, builddir: str) -> int: print_only = not options.cmd_line_options and not options.clearcache c = None try: @@ -335,7 +335,7 @@ def run_impl(options: argparse.Namespace, builddir: str) -> int: pass return 0 -def run(options: argparse.Namespace) -> int: +def run(options: coredata.CommandLineOptions) -> int: coredata.parse_cmd_line_options(options) builddir = os.path.abspath(os.path.realpath(options.builddir)) return run_impl(options, builddir) diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py index f8e242ae2bf2..11fc0f853ef9 100644 --- a/mesonbuild/msetup.py +++ b/mesonbuild/msetup.py @@ -21,6 +21,9 @@ from . import build, coredata, environment, interpreter, mesonlib, mintro, mlog from .mesonlib import MesonException +if T.TYPE_CHECKING: + from .coredata import CommandLineOptions + git_ignore_file = '''# This file is autogenerated by Meson. If you change or delete it, it won't be recreated. * ''' @@ -63,7 +66,7 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: parser.add_argument('sourcedir', nargs='?', default=None) class MesonApp: - def __init__(self, options: argparse.Namespace) -> None: + def __init__(self, options: CommandLineOptions) -> None: self.options = options (self.source_dir, self.build_dir) = self.validate_dirs() if options.wipe: @@ -183,7 +186,7 @@ def generate(self, capture: bool = False, vslite_ctx: T.Optional[dict] = None) - def _generate(self, env: environment.Environment, capture: bool, vslite_ctx: T.Optional[dict]) -> T.Optional[dict]: # Get all user defined options, including options that have been defined # during a previous invocation or using meson configure. - user_defined_options = argparse.Namespace(**vars(self.options)) + user_defined_options = T.cast('CommandLineOptions', argparse.Namespace(**vars(self.options))) coredata.read_cmd_line_file(self.build_dir, user_defined_options) mlog.debug('Build started at', datetime.datetime.now().isoformat()) @@ -310,7 +313,7 @@ def finalize_postconf_hooks(self, b: build.Build, intr: interpreter.Interpreter) for mod in intr.modules.values(): mod.postconf_hook(b) -def run_genvslite_setup(options: argparse.Namespace) -> None: +def run_genvslite_setup(options: CommandLineOptions) -> None: # With --genvslite, we essentially want to invoke multiple 'setup' iterations. I.e. - # meson setup ... builddirprefix_debug # meson setup ... builddirprefix_debugoptimized @@ -344,11 +347,11 @@ def run_genvslite_setup(options: argparse.Namespace) -> None: app = MesonApp(options) app.generate(capture=False, vslite_ctx=vslite_ctx) -def run(options: T.Union[argparse.Namespace, T.List[str]]) -> int: - if not isinstance(options, argparse.Namespace): +def run(options: T.Union[CommandLineOptions, T.List[str]]) -> int: + if isinstance(options, list): parser = argparse.ArgumentParser() add_arguments(parser) - options = parser.parse_args(options) + options = T.cast('CommandLineOptions', parser.parse_args(options)) coredata.parse_cmd_line_options(options) if mesonlib.OptionKey('genvslite') in options.cmd_line_options.keys():